More fixes to recent 1.2.1 extensions

This commit is contained in:
2022-11-03 23:39:39 +01:00
parent 41a3592340
commit 942b295ed7
17 changed files with 3090 additions and 183 deletions

View File

@ -3,14 +3,20 @@
<name>Quick Joint</name>
<id>fablabchemnitz.de.quick_joint</id>
<label xml:space="preserve">Adding box joint tabs or slots to selected object!</label>
<label xml:space="preserve">Version 0.3</label>
<label xml:space="preserve">Version 0.5</label>
<param name="activetab" type="notebook">
<page name="tabpage" gui-text="Tabs">
<param name="side" type="int" min="0" max="512" gui-text="Side:">0</param>
<param name="numtabs" type="int" min="1" max="512" gui-text="Number of tabs:">1</param>
<param name="tSlotNutHeight" type="float" min="0.01" max="99999.00" precision="2" gui-text="t-slot nut height:">1.80</param>
<param name="tSlotNutWidth" type="float" min="0.01" max="99999.00" precision="2" gui-text="t-slot nut width:">5.50</param>
<param name="tSlotScrewWidth" type="float" min="0.01" max="99999.00" precision="2" gui-text="t-slot screw width:">3.10</param>
<param name="tSlotScrewDepth" type="float" min="0.01" max="99999.00" precision="2" gui-text="t-slot screw depth:">10.00</param>
</page>
<page name="slotpage" gui-text="Slots">
<param name="numslots" type="int" min="1" max="512" gui-text="Number of slots:">1</param>
<param name="tSlotHoleDiameter" type="float" min="0.01" max="99999.00" precision="2" gui-text="t-slot hole diameter:">3.00</param>
</page>
</param>
<param name="thickness" type="float" min="0.0" max="1000.0" precision="3" gui-text="Material thickness:">3.0</param>
@ -22,8 +28,10 @@
<option value="in">in</option>
<option value="cm">cm</option>
</param>
<param name="edgefeatures" type="bool" gui-text="Features on edges">false</param>
<param name="featureStart" type="bool" gui-text="Feature at start">false</param>
<param name="featureEnd" type="bool" gui-text="Feature at end">false</param>
<param name="flipside" type="bool" gui-text="Flip side">false</param>
<param name="tSlotEnable" type="bool" gui-text="Use t-slot definitions">false</param>
<effect needs-live-preview="true">
<object-type>path</object-type>
<effects-menu>

View File

@ -22,11 +22,12 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
'''
import inkex, cmath
from inkex.paths import Path, ZoneClose, Move
import inkex, cmath, math
from inkex import Circle
from inkex.paths import Path, ZoneClose, Move, Line, line, Curve
from lxml import etree
debugEn = False
debugEn = False
def debugMsg(input):
if debugEn:
inkex.utils.debug(input)
@ -40,21 +41,46 @@ def linesNumber(path):
debugMsg('Number of lines : ' + str(retval))
return retval
def to_complex(point):
c = None
try:
c = complex(point.x, point.y)
except Exception as e:
pass
if c is not None:
return c
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.'''
if isinstance(self[n], (Move, Line, ZoneClose)):
start = complex(self[n].x, self[n].y)
elif isinstance(self[n], Curve):
start = complex(self[n].x4, self[n].y4)
# 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:
inkex.utils.debug("The selection seems not be be a usable polypath. QuickJoint does not operate on curves. Try to flatten bezier curves or splitting up the path.")
exit(1)
if isinstance(self[n+1], (Move, Line, ZoneClose)):
end = complex(self[n+1].x, self[n+1].y)
elif isinstance(self[n+1], Curve):
end = complex(self[n+1].x4, self[n+1].y4)
return (start, end, closePath)
class QuickJoint(inkex.EffectExtension):
def add_arguments(self, pars):
pars.add_argument('-s', '--side', type=int, default=0, help='Object face to tabify')
pars.add_argument('-n', '--numtabs', type=int, default=1, help='Number of tabs to add')
@ -62,19 +88,17 @@ class QuickJoint(inkex.EffectExtension):
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')
pars.add_argument('-T', '--tSlotEnable', type=inkex.Boolean, default=False, help='Enable to use t-slot definitions')
pars.add_argument('-D', '--tSlotHoleDiameter', type=float, default=3.00, help='Diameter of t slot hole')
pars.add_argument('-H', '--tSlotNutHeight', type=float, default=1.80, help='Height of t slot nut')
pars.add_argument('-W', '--tSlotNutWidth', type=float, default=5.50, help='Width of t slot nut')
pars.add_argument('-N', '--tSlotScrewWidth', type=float, default=3.10, help='Scew width of t slot')
pars.add_argument('-d', '--tSlotScrewDepth', type=float, default=10.00, help='Screw depth of t slot')
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
@ -91,192 +115,172 @@ class QuickJoint(inkex.EffectExtension):
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
cursor -= lengthEdge
path.Line(cursor)
cursor -= heightEdge
path.Line(cursor)
path.close()
return path
def draw_tabs(self, path, line):
#Male tab creation
start = to_complex(path[line])
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, segCount, segment, closePath = self.get_segments(path, line, self.numtabs)
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)
debugMsg('segLength - ' + str(segLength))
newLines = []
# when handling firlt line need to set M back
# 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)
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.append(['M', [start.real, start.imag]])
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))
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")
if self.options.tSlotEnable is False:
newLines.line(tabOut)
newLines.line(tabLine)
newLines.line(tabIn)
else: #TODO
#self.options.tSlotNutHeight
#self.options.tSlotNutWidth
#self.options.tSlotScrewWidth
#self.options.tSlotScrewDepth
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 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})
line_atts = { 'style':line_style, 'id':slot_id+'-inner-close-tab', 'd':str(Path(lines)) }
return etree.SubElement(g, inkex.addNS('path','svg'), line_atts )
def get_segments(self, path, line, num):
# 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)
# 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("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
# Female slot creation
start = to_complex(path[0])
end = to_complex(path[1])
cursor, segCount, segVector, closePath = self.get_segments(path, 0, self.numslots)
if self.edgefeatures:
segCount = (self.numslots * 2) - 1
else:
segCount = (self.numslots * 2)
# 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)
distance = end - start
debugMsg('distance ' + str(distance))
debugMsg('segCount ' + str(segCount))
try:
if self.edgefeatures:
segLength = self.get_length(distance) / segCount
else:
segLength = self.get_length(distance) / (segCount + 1)
except:
segLength = self.get_length(distance)
debugMsg('segLength - ' + str(segLength))
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)
if drawSlot:
slot = self.add_new_path_from_lines(self.draw_box(cursor, segVector, self.thickness, self.kerf), line_style)
if self.options.tSlotEnable is True:
cx, cy = slot.bounding_box().center
circle = slot.getparent().add(inkex.Circle(id=self.svg.get_unique_id('tSlotHole')))
circle.set('transform', "rotate({:0.6f} {:0.6f} {:0.6f})".format(0, cx, cy))
circle.set('r', "{:0.6f}".format(self.tSlotHoleDiameter / 2 - self.kerf))
circle.set('cx', "{:0.6f}".format(cx))
circle.set('cy', "{:0.6f}".format(cy))
circle.style = line_style
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
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.tSlotNutHeight = self.svg.unittouu(str(self.options.tSlotNutHeight) + self.options.units)
self.tSlotNutWidth = self.svg.unittouu(str(self.options.tSlotNutWidth) + self.options.units)
self.tSlotScrewWidth = self.svg.unittouu(str(self.options.tSlotScrewWidth) + self.options.units)
self.tSlotScrewDepth = self.svg.unittouu(str(self.options.tSlotScrewDepth) + self.options.units)
self.tSlotHoleDiameter = self.svg.unittouu(str(self.options.tSlotHoleDiameter) + 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')
@ -298,13 +302,13 @@ class QuickJoint(inkex.EffectExtension):
debugMsg(newPath)
debugMsg('4')
debugMsg( p[lineNum + 1:])
finalPath = p[:lineNum] + newPath + p[lineNum + 1:]
finalPath = p[:lineNum + 1] + newPath + p[lineNum + 2:]
debugMsg(finalPath)
node.set('d',str(Path(finalPath)))
elif self.activetab == 'slotpage':
newPath = self.draw_slots(p)
if __name__ == '__main__':
QuickJoint().run()