Skip to content

Commit

Permalink
Merge pull request #15 from ferrency/fix-kerf
Browse files Browse the repository at this point in the history
Fix kerf
  • Loading branch information
JarrettR authored Oct 20, 2022
2 parents a49d86a + f6225d0 commit b85e8fa
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 167 deletions.
3 changes: 2 additions & 1 deletion quickjoint.inx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
<item value="in">in</item>
<item value="cm">cm</item>
</param>
<param name="edgefeatures" type="boolean" _gui-text="Features on edges">False</param>
<param name="featureStart" type="boolean" _gui-text="Feature at start">False</param>
<param name="featureEnd" type="boolean" _gui-text="Feature at end">False</param>
<param name="flipside" type="boolean" _gui-text="Flip side">False</param>
<effect needs-live-preview="true">
<object-type>path</object-type>
Expand Down
313 changes: 147 additions & 166 deletions quickjoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@
'''
import inkex, cmath
from inkex.paths import Path, ZoneClose, Move
from inkex.paths import Path, ZoneClose, Move, Line, line
from lxml import etree

debugEn = False
debugEn = False
def debugMsg(input):
if debugEn:
inkex.utils.debug(input)
Expand All @@ -40,9 +40,38 @@ def linesNumber(path):
debugMsg('Number of lines : ' + str(retval))
return retval

def to_complex(point):
return complex(point.x, point.y)

class QuickJointPath (Path):
def Move(self, point):
'''Append an absolute move instruction to the path, to the specified complex point'''
debugMsg("- move: " + str(point))
self.append(Move(point.real, point.imag))

def Line(self, point):
'''Add an absolute line instruction to the path, to the specified complex point'''
debugMsg("- line: " + str(point))
self.append(Line(point.real, point.imag))

def close(self):
'''Add a Close Path instriction to the path'''
self.append(ZoneClose())

def line(self, vector):
'''Append a relative line command to the path, using the specified vector'''
self.append(line(vector.real, vector.imag))

def get_line(self, n):
'''Return the end points of the nth line in the path as complex numbers, as well as whether that line closes the path.'''

start = complex(self[n].x, self[n].y)
# If the next point in the path closes the path, go back to the start.
end = None
closePath = False
if isinstance(self[n+1], ZoneClose):
end = complex(self[0].x, self[0].y)
closePath = True
else:
end = complex(self[n+1].x, self[n+1].y)
return (start, end, closePath)

class QuickJoint(inkex.Effect):
def add_arguments(self, pars):
Expand All @@ -52,19 +81,11 @@ def add_arguments(self, pars):
pars.add_argument('-t', '--thickness', type=float, default=3.0, help='Material thickness')
pars.add_argument('-k', '--kerf', type=float, default=0.14, help='Measured kerf of cutter')
pars.add_argument('-u', '--units', default='mm', help='Measurement units')
pars.add_argument('-e', '--edgefeatures', type=inkex.Boolean, default=False, help='Allow tabs to go right to edges')
pars.add_argument('-f', '--flipside', type=inkex.Boolean, default=False, help='Flip side of lines that tabs are drawn onto')
pars.add_argument('-a', '--activetab', default='', help='Tab or slot menus')
pars.add_argument('-S', '--featureStart', type=inkex.Boolean, default=False, help='Tab/slot instead of space on the start edge')
pars.add_argument('-E', '--featureEnd', type=inkex.Boolean, default=False, help='Tab/slot instead of space on the end edge')

def to_complex(self, command, line):
debugMsg('To complex: ' + command + ' ' + str(line))

return complex(line[0], line[1])

def get_length(self, line):
polR, polPhi = cmath.polar(line)
return polR

def draw_parallel(self, start, guideLine, stepDistance):
polR, polPhi = cmath.polar(guideLine)
polR = stepDistance
Expand All @@ -81,192 +102,149 @@ def draw_perpendicular(self, start, guideLine, stepDistance, invert = False):
debugMsg(polPhi)
debugMsg(cmath.rect(polR, polPhi))
return (cmath.rect(polR, polPhi) + start)


def draw_box(self, start, guideLine, xDistance, yDistance, kerf):
polR, polPhi = cmath.polar(guideLine)
def draw_box(self, start, lengthVector, height, kerf):

# Kerf is a provided as a positive kerf width. Although tabs
# need to be made larger by the width of the kerf, slots need
# to be made narrower instead, since the cut widens them.

# Calculate kerfed height and length vectors
heightEdge = self.draw_perpendicular(0, lengthVector, height - kerf, self.flipside)
lengthEdge = self.draw_parallel(lengthVector, lengthVector, -kerf)

#Kerf expansion
if self.flipside:
start -= cmath.rect(kerf / 2, polPhi)
start -= cmath.rect(kerf / 2, polPhi + (cmath.pi / 2))
else:
start -= cmath.rect(kerf / 2, polPhi)
start -= cmath.rect(kerf / 2, polPhi - (cmath.pi / 2))

lines = []
lines.append(['M', [start.real, start.imag]])
debugMsg("draw_box; lengthEdge: " + str(lengthEdge) + ", heightEdge: " + str(heightEdge))

#Horizontal
polR = xDistance
move = cmath.rect(polR + kerf, polPhi) + start
lines.append(['L', [move.real, move.imag]])
start = move
cursor = self.draw_parallel(start, lengthEdge, kerf/2)
cursor = self.draw_parallel(cursor, heightEdge, kerf/2)

#Vertical
polR = yDistance
if self.flipside:
polPhi += (cmath.pi / 2)
else:
polPhi -= (cmath.pi / 2)
move = cmath.rect(polR + kerf, polPhi) + start
lines.append(['L', [move.real, move.imag]])
start = move
path = QuickJointPath()
path.Move(cursor)

#Horizontal
polR = xDistance
if self.flipside:
polPhi += (cmath.pi / 2)
else:
polPhi -= (cmath.pi / 2)
move = cmath.rect(polR + kerf, polPhi) + start
lines.append(['L', [move.real, move.imag]])
start = move
cursor += lengthEdge
path.Line(cursor)

lines.append(['Z', []])
cursor += heightEdge
path.Line(cursor)

return lines

def draw_tabs(self, path, line):
#Male tab creation
start = to_complex(path[line])
cursor -= lengthEdge
path.Line(cursor)

closePath = False
#Line is between last and first (closed) nodes
end = None
if isinstance(path[line+1], ZoneClose):
end = to_complex(path[0])
closePath = True
else:
end = to_complex(path[line+1])

debugMsg('start')
debugMsg(start)
debugMsg('end')
debugMsg(end)

debugMsg('5-')

if self.edgefeatures:
segCount = (self.numtabs * 2) - 1
drawValley = False
else:
segCount = (self.numtabs * 2)
drawValley = False

distance = end - start
debugMsg('distance ' + str(distance))
debugMsg('segCount ' + str(segCount))
cursor -= heightEdge
path.Line(cursor)

try:
if self.edgefeatures:
segLength = self.get_length(distance) / segCount
else:
segLength = self.get_length(distance) / (segCount + 1)
except:
debugMsg('in except')
segLength = self.get_length(distance)
path.close()

debugMsg('segLength - ' + str(segLength))
newLines = []
return path


def draw_tabs(self, path, line):
cursor, segCount, segment, closePath = self.get_segments(path, line, self.numtabs)

# when handling firlt line need to set M back
if isinstance(path[line], Move):
newLines.append(['M', [start.real, start.imag]])
# Calculate kerf-compensated vectors for the parallel portion of tab and space
tabLine = self.draw_parallel(segment, segment, self.kerf)
spaceLine = self.draw_parallel(segment, segment, -self.kerf)
endspaceLine = segment

# Calculate vectors for tabOut and tabIn: perpendicular away and towards baseline
tabOut = self.draw_perpendicular(0, segment, self.thickness, not self.flipside)
tabIn = self.draw_perpendicular(0, segment, self.thickness, self.flipside)

if self.edgefeatures == False:
newLines.append(['L', [start.real, start.imag]])
start = self.draw_parallel(start, distance, segLength)
newLines.append(['L', [start.real, start.imag]])
debugMsg('Initial - ' + str(start))
debugMsg("draw_tabs; tabLine=" + str(tabLine) + " spaceLine=" + str(spaceLine) + " segment=" + str(segment))

drawTab = self.featureStart
newLines = QuickJointPath()

# First line is a move or line to our start point
if isinstance(path[line], Move):
newLines.Move(cursor)
else:
newLines.Line(cursor)


for i in range(segCount):
if drawValley == True:
#Vertical
start = self.draw_perpendicular(start, distance, self.thickness, self.flipside)
newLines.append(['L', [start.real, start.imag]])
debugMsg('ValleyV - ' + str(start))
drawValley = False
#Horizontal
start = self.draw_parallel(start, distance, segLength)
newLines.append(['L', [start.real, start.imag]])
debugMsg('ValleyH - ' + str(start))
debugMsg("i = " + str(i))
if drawTab == True:
debugMsg("- tab")
newLines.line(tabOut)
newLines.line(tabLine)
newLines.line(tabIn)
else:
#Vertical
start = self.draw_perpendicular(start, distance, self.thickness, not self.flipside)
newLines.append(['L', [start.real, start.imag]])
debugMsg('HillV - ' + str(start))
drawValley = True
#Horizontal
start = self.draw_parallel(start, distance, segLength)
newLines.append(['L', [start.real, start.imag]])
debugMsg('HillH - ' + str(start))

if self.edgefeatures == True:
start = self.draw_perpendicular(start, distance, self.thickness, self.flipside)
newLines.append(['L', [start.real, start.imag]])
debugMsg('Final - ' + str(start))

if i == 0 or i == segCount - 1:
debugMsg("- endspace")
newLines.line(endspaceLine)
else:
debugMsg("- space")
newLines.line(spaceLine)
drawTab = not drawTab

if closePath:
newLines.append(['Z', []])
newLines.close
return newLines


def draw_slots(self, path):
#Female slot creation
def add_new_path_from_lines(self, lines, line_style):
slot_id = self.svg.get_unique_id('slot')
g = etree.SubElement(self.svg.get_current_layer(), 'g', {'id':slot_id})

start = to_complex(path[0])
end = to_complex(path[1])
line_atts = { 'style':line_style, 'id':slot_id+'-inner-close-tab', 'd':str(Path(lines)) }
etree.SubElement(g, inkex.addNS('path','svg'), line_atts )

if self.edgefeatures:
segCount = (self.numslots * 2) - 1
else:
segCount = (self.numslots * 2)
def get_segments(self, path, line, num):

distance = end - start
debugMsg('distance ' + str(distance))
debugMsg('segCount ' + str(segCount))
# Calculate number of segments, including all features and spaces
segCount = num * 2 - 1
if not self.featureStart: segCount = segCount + 1
if not self.featureEnd: segCount = segCount + 1

start, end, closePath = QuickJointPath(path).get_line(line)

try:
if self.edgefeatures:
segLength = self.get_length(distance) / segCount
else:
segLength = self.get_length(distance) / (segCount + 1)
except:
segLength = self.get_length(distance)
# Calculate the length of each feature prior to kerf compensation.
# Here we divide the specified edge into equal portions, one for each feature or space.

# Because the specified edge has no kerf compensation, the
# actual length we end up with will be smaller by a kerf. We
# need to use that distance to calculate our segment vector.
edge = end - start
edge = self.draw_parallel(edge, edge, -self.kerf)
segVector = edge / segCount

debugMsg('segLength - ' + str(segLength))
newLines = []
debugMsg("get_segments; start=" + str(start) + " end=" + str(end) + " edge=" + str(edge) + " segCount=" + str(segCount) + " segVector=" + str(segVector))

return (start, segCount, segVector, closePath)

def draw_slots(self, path):
# Female slot creation

cursor, segCount, segVector, closePath = self.get_segments(path, 0, self.numslots)

# I'm having a really hard time wording why this is necessary, but it is.
# get_segments returns a vector based on a narrower edge; adjust that edge to fit within the edge we were given.
cursor = self.draw_parallel(cursor, segVector, self.kerf/2)

newLines = []
line_style = str(inkex.Style({ 'stroke': '#000000', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('0.1mm')) }))

drawSlot = self.featureStart

for i in range(segCount):
if (self.edgefeatures and (i % 2) == 0) or (not self.edgefeatures and (i % 2)):
newLines = self.draw_box(start, distance, segLength, self.thickness, self.kerf)
debugMsg(newLines)

slot_id = self.svg.get_unique_id('slot')
g = etree.SubElement(self.svg.get_current_layer(), 'g', {'id':slot_id})

line_atts = { 'style':line_style, 'id':slot_id+'-inner-close-tab', 'd':str(Path(newLines)) }
etree.SubElement(g, inkex.addNS('path','svg'), line_atts )

#Find next point
polR, polPhi = cmath.polar(distance)
polR = segLength
start = cmath.rect(polR, polPhi) + start

if drawSlot:
self.add_new_path_from_lines(self.draw_box(cursor, segVector, self.thickness, self.kerf), line_style)
cursor = cursor + segVector
drawSlot = not drawSlot
debugMsg("i: " + str(i) + ", cursor: " + str(cursor))
# (We don't modify the path so we don't need to close it)

def effect(self):
self.side = self.options.side
self.numtabs = self.options.numtabs
self.numslots = self.options.numslots
self.thickness = self.svg.unittouu(str(self.options.thickness) + self.options.units)
self.kerf = self.svg.unittouu(str(self.options.kerf) + self.options.units)
self.units = self.options.units
self.edgefeatures = self.options.edgefeatures
self.featureStart = self.options.featureStart
self.featureEnd = self.options.featureEnd
self.flipside = self.options.flipside
self.activetab = self.options.activetab

for id, node in self.svg.selected.items():
debugMsg(node)
debugMsg('1')
Expand Down Expand Up @@ -296,5 +274,8 @@ def effect(self):
elif self.activetab == 'slotpage':
newPath = self.draw_slots(p)




if __name__ == '__main__':
QuickJoint().run()

0 comments on commit b85e8fa

Please sign in to comment.