23
23
24
24
'''
25
25
import inkex , cmath
26
- from inkex .paths import Path , ZoneClose , Move
26
+ from inkex .paths import Path , ZoneClose , Move , Line , line
27
27
from lxml import etree
28
28
29
- debugEn = False
29
+ debugEn = False
30
30
def debugMsg (input ):
31
31
if debugEn :
32
32
inkex .utils .debug (input )
@@ -40,9 +40,38 @@ def linesNumber(path):
40
40
debugMsg ('Number of lines : ' + str (retval ))
41
41
return retval
42
42
43
- def to_complex (point ):
44
- return complex (point .x , point .y )
45
-
43
+ class QuickJointPath (Path ):
44
+ def Move (self , point ):
45
+ '''Append an absolute move instruction to the path, to the specified complex point'''
46
+ debugMsg ("- move: " + str (point ))
47
+ self .append (Move (point .real , point .imag ))
48
+
49
+ def Line (self , point ):
50
+ '''Add an absolute line instruction to the path, to the specified complex point'''
51
+ debugMsg ("- line: " + str (point ))
52
+ self .append (Line (point .real , point .imag ))
53
+
54
+ def close (self ):
55
+ '''Add a Close Path instriction to the path'''
56
+ self .append (ZoneClose ())
57
+
58
+ def line (self , vector ):
59
+ '''Append a relative line command to the path, using the specified vector'''
60
+ self .append (line (vector .real , vector .imag ))
61
+
62
+ def get_line (self , n ):
63
+ '''Return the end points of the nth line in the path as complex numbers, as well as whether that line closes the path.'''
64
+
65
+ start = complex (self [n ].x , self [n ].y )
66
+ # If the next point in the path closes the path, go back to the start.
67
+ end = None
68
+ closePath = False
69
+ if isinstance (self [n + 1 ], ZoneClose ):
70
+ end = complex (self [0 ].x , self [0 ].y )
71
+ closePath = True
72
+ else :
73
+ end = complex (self [n + 1 ].x , self [n + 1 ].y )
74
+ return (start , end , closePath )
46
75
47
76
class QuickJoint (inkex .Effect ):
48
77
def add_arguments (self , pars ):
@@ -52,19 +81,11 @@ def add_arguments(self, pars):
52
81
pars .add_argument ('-t' , '--thickness' , type = float , default = 3.0 , help = 'Material thickness' )
53
82
pars .add_argument ('-k' , '--kerf' , type = float , default = 0.14 , help = 'Measured kerf of cutter' )
54
83
pars .add_argument ('-u' , '--units' , default = 'mm' , help = 'Measurement units' )
55
- pars .add_argument ('-e' , '--edgefeatures' , type = inkex .Boolean , default = False , help = 'Allow tabs to go right to edges' )
56
84
pars .add_argument ('-f' , '--flipside' , type = inkex .Boolean , default = False , help = 'Flip side of lines that tabs are drawn onto' )
57
85
pars .add_argument ('-a' , '--activetab' , default = '' , help = 'Tab or slot menus' )
86
+ pars .add_argument ('-S' , '--featureStart' , type = inkex .Boolean , default = False , help = 'Tab/slot instead of space on the start edge' )
87
+ pars .add_argument ('-E' , '--featureEnd' , type = inkex .Boolean , default = False , help = 'Tab/slot instead of space on the end edge' )
58
88
59
- def to_complex (self , command , line ):
60
- debugMsg ('To complex: ' + command + ' ' + str (line ))
61
-
62
- return complex (line [0 ], line [1 ])
63
-
64
- def get_length (self , line ):
65
- polR , polPhi = cmath .polar (line )
66
- return polR
67
-
68
89
def draw_parallel (self , start , guideLine , stepDistance ):
69
90
polR , polPhi = cmath .polar (guideLine )
70
91
polR = stepDistance
@@ -81,192 +102,149 @@ def draw_perpendicular(self, start, guideLine, stepDistance, invert = False):
81
102
debugMsg (polPhi )
82
103
debugMsg (cmath .rect (polR , polPhi ))
83
104
return (cmath .rect (polR , polPhi ) + start )
105
+
84
106
85
- def draw_box (self , start , guideLine , xDistance , yDistance , kerf ):
86
- polR , polPhi = cmath .polar (guideLine )
107
+ def draw_box (self , start , lengthVector , height , kerf ):
108
+
109
+ # Kerf is a provided as a positive kerf width. Although tabs
110
+ # need to be made larger by the width of the kerf, slots need
111
+ # to be made narrower instead, since the cut widens them.
112
+
113
+ # Calculate kerfed height and length vectors
114
+ heightEdge = self .draw_perpendicular (0 , lengthVector , height - kerf , self .flipside )
115
+ lengthEdge = self .draw_parallel (lengthVector , lengthVector , - kerf )
87
116
88
- #Kerf expansion
89
- if self .flipside :
90
- start -= cmath .rect (kerf / 2 , polPhi )
91
- start -= cmath .rect (kerf / 2 , polPhi + (cmath .pi / 2 ))
92
- else :
93
- start -= cmath .rect (kerf / 2 , polPhi )
94
- start -= cmath .rect (kerf / 2 , polPhi - (cmath .pi / 2 ))
95
-
96
- lines = []
97
- lines .append (['M' , [start .real , start .imag ]])
117
+ debugMsg ("draw_box; lengthEdge: " + str (lengthEdge ) + ", heightEdge: " + str (heightEdge ))
98
118
99
- #Horizontal
100
- polR = xDistance
101
- move = cmath .rect (polR + kerf , polPhi ) + start
102
- lines .append (['L' , [move .real , move .imag ]])
103
- start = move
119
+ cursor = self .draw_parallel (start , lengthEdge , kerf / 2 )
120
+ cursor = self .draw_parallel (cursor , heightEdge , kerf / 2 )
104
121
105
- #Vertical
106
- polR = yDistance
107
- if self .flipside :
108
- polPhi += (cmath .pi / 2 )
109
- else :
110
- polPhi -= (cmath .pi / 2 )
111
- move = cmath .rect (polR + kerf , polPhi ) + start
112
- lines .append (['L' , [move .real , move .imag ]])
113
- start = move
122
+ path = QuickJointPath ()
123
+ path .Move (cursor )
114
124
115
- #Horizontal
116
- polR = xDistance
117
- if self .flipside :
118
- polPhi += (cmath .pi / 2 )
119
- else :
120
- polPhi -= (cmath .pi / 2 )
121
- move = cmath .rect (polR + kerf , polPhi ) + start
122
- lines .append (['L' , [move .real , move .imag ]])
123
- start = move
125
+ cursor += lengthEdge
126
+ path .Line (cursor )
124
127
125
- lines .append (['Z' , []])
128
+ cursor += heightEdge
129
+ path .Line (cursor )
126
130
127
- return lines
128
-
129
- def draw_tabs (self , path , line ):
130
- #Male tab creation
131
- start = to_complex (path [line ])
131
+ cursor -= lengthEdge
132
+ path .Line (cursor )
132
133
133
- closePath = False
134
- #Line is between last and first (closed) nodes
135
- end = None
136
- if isinstance (path [line + 1 ], ZoneClose ):
137
- end = to_complex (path [0 ])
138
- closePath = True
139
- else :
140
- end = to_complex (path [line + 1 ])
141
-
142
- debugMsg ('start' )
143
- debugMsg (start )
144
- debugMsg ('end' )
145
- debugMsg (end )
146
-
147
- debugMsg ('5-' )
148
-
149
- if self .edgefeatures :
150
- segCount = (self .numtabs * 2 ) - 1
151
- drawValley = False
152
- else :
153
- segCount = (self .numtabs * 2 )
154
- drawValley = False
155
-
156
- distance = end - start
157
- debugMsg ('distance ' + str (distance ))
158
- debugMsg ('segCount ' + str (segCount ))
134
+ cursor -= heightEdge
135
+ path .Line (cursor )
159
136
160
- try :
161
- if self .edgefeatures :
162
- segLength = self .get_length (distance ) / segCount
163
- else :
164
- segLength = self .get_length (distance ) / (segCount + 1 )
165
- except :
166
- debugMsg ('in except' )
167
- segLength = self .get_length (distance )
137
+ path .close ()
168
138
169
- debugMsg ('segLength - ' + str (segLength ))
170
- newLines = []
139
+ return path
140
+
141
+
142
+ def draw_tabs (self , path , line ):
143
+ cursor , segCount , segment , closePath = self .get_segments (path , line , self .numtabs )
171
144
172
- # when handling firlt line need to set M back
173
- if isinstance (path [line ], Move ):
174
- newLines .append (['M' , [start .real , start .imag ]])
145
+ # Calculate kerf-compensated vectors for the parallel portion of tab and space
146
+ tabLine = self .draw_parallel (segment , segment , self .kerf )
147
+ spaceLine = self .draw_parallel (segment , segment , - self .kerf )
148
+ endspaceLine = segment
149
+
150
+ # Calculate vectors for tabOut and tabIn: perpendicular away and towards baseline
151
+ tabOut = self .draw_perpendicular (0 , segment , self .thickness , not self .flipside )
152
+ tabIn = self .draw_perpendicular (0 , segment , self .thickness , self .flipside )
175
153
176
- if self .edgefeatures == False :
177
- newLines .append (['L' , [start .real , start .imag ]])
178
- start = self .draw_parallel (start , distance , segLength )
179
- newLines .append (['L' , [start .real , start .imag ]])
180
- debugMsg ('Initial - ' + str (start ))
154
+ debugMsg ("draw_tabs; tabLine=" + str (tabLine ) + " spaceLine=" + str (spaceLine ) + " segment=" + str (segment ))
155
+
156
+ drawTab = self .featureStart
157
+ newLines = QuickJointPath ()
158
+
159
+ # First line is a move or line to our start point
160
+ if isinstance (path [line ], Move ):
161
+ newLines .Move (cursor )
162
+ else :
163
+ newLines .Line (cursor )
181
164
182
-
183
165
for i in range (segCount ):
184
- if drawValley == True :
185
- #Vertical
186
- start = self .draw_perpendicular (start , distance , self .thickness , self .flipside )
187
- newLines .append (['L' , [start .real , start .imag ]])
188
- debugMsg ('ValleyV - ' + str (start ))
189
- drawValley = False
190
- #Horizontal
191
- start = self .draw_parallel (start , distance , segLength )
192
- newLines .append (['L' , [start .real , start .imag ]])
193
- debugMsg ('ValleyH - ' + str (start ))
166
+ debugMsg ("i = " + str (i ))
167
+ if drawTab == True :
168
+ debugMsg ("- tab" )
169
+ newLines .line (tabOut )
170
+ newLines .line (tabLine )
171
+ newLines .line (tabIn )
194
172
else :
195
- #Vertical
196
- start = self .draw_perpendicular (start , distance , self .thickness , not self .flipside )
197
- newLines .append (['L' , [start .real , start .imag ]])
198
- debugMsg ('HillV - ' + str (start ))
199
- drawValley = True
200
- #Horizontal
201
- start = self .draw_parallel (start , distance , segLength )
202
- newLines .append (['L' , [start .real , start .imag ]])
203
- debugMsg ('HillH - ' + str (start ))
204
-
205
- if self .edgefeatures == True :
206
- start = self .draw_perpendicular (start , distance , self .thickness , self .flipside )
207
- newLines .append (['L' , [start .real , start .imag ]])
208
- debugMsg ('Final - ' + str (start ))
209
-
173
+ if i == 0 or i == segCount - 1 :
174
+ debugMsg ("- endspace" )
175
+ newLines .line (endspaceLine )
176
+ else :
177
+ debugMsg ("- space" )
178
+ newLines .line (spaceLine )
179
+ drawTab = not drawTab
180
+
210
181
if closePath :
211
- newLines .append ([ 'Z' , []])
182
+ newLines .close
212
183
return newLines
213
184
214
-
215
- def draw_slots ( self , path ):
216
- #Female slot creation
185
+ def add_new_path_from_lines ( self , lines , line_style ):
186
+ slot_id = self . svg . get_unique_id ( 'slot' )
187
+ g = etree . SubElement ( self . svg . get_current_layer (), 'g' , { 'id' : slot_id })
217
188
218
- start = to_complex ( path [ 0 ])
219
- end = to_complex ( path [ 1 ] )
189
+ line_atts = { 'style' : line_style , 'id' : slot_id + '-inner-close-tab' , 'd' : str ( Path ( lines )) }
190
+ etree . SubElement ( g , inkex . addNS ( ' path' , 'svg' ), line_atts )
220
191
221
- if self .edgefeatures :
222
- segCount = (self .numslots * 2 ) - 1
223
- else :
224
- segCount = (self .numslots * 2 )
192
+ def get_segments (self , path , line , num ):
225
193
226
- distance = end - start
227
- debugMsg ('distance ' + str (distance ))
228
- debugMsg ('segCount ' + str (segCount ))
194
+ # Calculate number of segments, including all features and spaces
195
+ segCount = num * 2 - 1
196
+ if not self .featureStart : segCount = segCount + 1
197
+ if not self .featureEnd : segCount = segCount + 1
198
+
199
+ start , end , closePath = QuickJointPath (path ).get_line (line )
229
200
230
- try :
231
- if self .edgefeatures :
232
- segLength = self .get_length (distance ) / segCount
233
- else :
234
- segLength = self .get_length (distance ) / (segCount + 1 )
235
- except :
236
- segLength = self .get_length (distance )
201
+ # Calculate the length of each feature prior to kerf compensation.
202
+ # Here we divide the specified edge into equal portions, one for each feature or space.
203
+
204
+ # Because the specified edge has no kerf compensation, the
205
+ # actual length we end up with will be smaller by a kerf. We
206
+ # need to use that distance to calculate our segment vector.
207
+ edge = end - start
208
+ edge = self .draw_parallel (edge , edge , - self .kerf )
209
+ segVector = edge / segCount
237
210
238
- debugMsg ('segLength - ' + str (segLength ))
239
- newLines = []
211
+ debugMsg ("get_segments; start=" + str (start ) + " end=" + str (end ) + " edge=" + str (edge ) + " segCount=" + str (segCount ) + " segVector=" + str (segVector ))
240
212
213
+ return (start , segCount , segVector , closePath )
214
+
215
+ def draw_slots (self , path ):
216
+ # Female slot creation
217
+
218
+ cursor , segCount , segVector , closePath = self .get_segments (path , 0 , self .numslots )
219
+
220
+ # I'm having a really hard time wording why this is necessary, but it is.
221
+ # get_segments returns a vector based on a narrower edge; adjust that edge to fit within the edge we were given.
222
+ cursor = self .draw_parallel (cursor , segVector , self .kerf / 2 )
223
+
224
+ newLines = []
241
225
line_style = str (inkex .Style ({ 'stroke' : '#000000' , 'fill' : 'none' , 'stroke-width' : str (self .svg .unittouu ('0.1mm' )) }))
242
-
226
+ drawSlot = self .featureStart
227
+
243
228
for i in range (segCount ):
244
- if (self .edgefeatures and (i % 2 ) == 0 ) or (not self .edgefeatures and (i % 2 )):
245
- newLines = self .draw_box (start , distance , segLength , self .thickness , self .kerf )
246
- debugMsg (newLines )
247
-
248
- slot_id = self .svg .get_unique_id ('slot' )
249
- g = etree .SubElement (self .svg .get_current_layer (), 'g' , {'id' :slot_id })
250
-
251
- line_atts = { 'style' :line_style , 'id' :slot_id + '-inner-close-tab' , 'd' :str (Path (newLines )) }
252
- etree .SubElement (g , inkex .addNS ('path' ,'svg' ), line_atts )
253
-
254
- #Find next point
255
- polR , polPhi = cmath .polar (distance )
256
- polR = segLength
257
- start = cmath .rect (polR , polPhi ) + start
258
-
229
+ if drawSlot :
230
+ self .add_new_path_from_lines (self .draw_box (cursor , segVector , self .thickness , self .kerf ), line_style )
231
+ cursor = cursor + segVector
232
+ drawSlot = not drawSlot
233
+ debugMsg ("i: " + str (i ) + ", cursor: " + str (cursor ))
234
+ # (We don't modify the path so we don't need to close it)
235
+
259
236
def effect (self ):
260
237
self .side = self .options .side
261
238
self .numtabs = self .options .numtabs
262
239
self .numslots = self .options .numslots
263
240
self .thickness = self .svg .unittouu (str (self .options .thickness ) + self .options .units )
264
241
self .kerf = self .svg .unittouu (str (self .options .kerf ) + self .options .units )
265
242
self .units = self .options .units
266
- self .edgefeatures = self .options .edgefeatures
243
+ self .featureStart = self .options .featureStart
244
+ self .featureEnd = self .options .featureEnd
267
245
self .flipside = self .options .flipside
268
246
self .activetab = self .options .activetab
269
-
247
+
270
248
for id , node in self .svg .selected .items ():
271
249
debugMsg (node )
272
250
debugMsg ('1' )
@@ -296,5 +274,8 @@ def effect(self):
296
274
elif self .activetab == 'slotpage' :
297
275
newPath = self .draw_slots (p )
298
276
277
+
278
+
279
+
299
280
if __name__ == '__main__' :
300
281
QuickJoint ().run ()
0 commit comments