2323
2424'''
2525import inkex , cmath
26- from inkex .paths import Path , ZoneClose , Move
26+ from inkex .paths import Path , ZoneClose , Move , Line , line
2727from lxml import etree
2828
29- debugEn = False
29+ debugEn = False
3030def debugMsg (input ):
3131 if debugEn :
3232 inkex .utils .debug (input )
@@ -40,9 +40,38 @@ def linesNumber(path):
4040 debugMsg ('Number of lines : ' + str (retval ))
4141 return retval
4242
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 )
4675
4776class QuickJoint (inkex .Effect ):
4877 def add_arguments (self , pars ):
@@ -52,19 +81,11 @@ def add_arguments(self, pars):
5281 pars .add_argument ('-t' , '--thickness' , type = float , default = 3.0 , help = 'Material thickness' )
5382 pars .add_argument ('-k' , '--kerf' , type = float , default = 0.14 , help = 'Measured kerf of cutter' )
5483 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' )
5684 pars .add_argument ('-f' , '--flipside' , type = inkex .Boolean , default = False , help = 'Flip side of lines that tabs are drawn onto' )
5785 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' )
5888
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-
6889 def draw_parallel (self , start , guideLine , stepDistance ):
6990 polR , polPhi = cmath .polar (guideLine )
7091 polR = stepDistance
@@ -81,192 +102,149 @@ def draw_perpendicular(self, start, guideLine, stepDistance, invert = False):
81102 debugMsg (polPhi )
82103 debugMsg (cmath .rect (polR , polPhi ))
83104 return (cmath .rect (polR , polPhi ) + start )
105+
84106
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 )
87116
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 ))
98118
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 )
104121
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 )
114124
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 )
124127
125- lines .append (['Z' , []])
128+ cursor += heightEdge
129+ path .Line (cursor )
126130
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 )
132133
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 )
159136
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 ()
168138
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 )
171144
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 )
175153
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 )
181164
182-
183165 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 )
194172 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+
210181 if closePath :
211- newLines .append ([ 'Z' , []])
182+ newLines .close
212183 return newLines
213184
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 })
217188
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 )
220191
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 ):
225193
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 )
229200
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
237210
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 ))
240212
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 = []
241225 line_style = str (inkex .Style ({ 'stroke' : '#000000' , 'fill' : 'none' , 'stroke-width' : str (self .svg .unittouu ('0.1mm' )) }))
242-
226+ drawSlot = self .featureStart
227+
243228 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+
259236 def effect (self ):
260237 self .side = self .options .side
261238 self .numtabs = self .options .numtabs
262239 self .numslots = self .options .numslots
263240 self .thickness = self .svg .unittouu (str (self .options .thickness ) + self .options .units )
264241 self .kerf = self .svg .unittouu (str (self .options .kerf ) + self .options .units )
265242 self .units = self .options .units
266- self .edgefeatures = self .options .edgefeatures
243+ self .featureStart = self .options .featureStart
244+ self .featureEnd = self .options .featureEnd
267245 self .flipside = self .options .flipside
268246 self .activetab = self .options .activetab
269-
247+
270248 for id , node in self .svg .selected .items ():
271249 debugMsg (node )
272250 debugMsg ('1' )
@@ -296,5 +274,8 @@ def effect(self):
296274 elif self .activetab == 'slotpage' :
297275 newPath = self .draw_slots (p )
298276
277+
278+
279+
299280if __name__ == '__main__' :
300281 QuickJoint ().run ()
0 commit comments