fix stroke width in living hinge boxes

This commit is contained in:
Mario Voigt 2022-11-30 21:26:16 +01:00
parent fc2261ce96
commit a52b8ccf08

View File

@ -27,17 +27,17 @@ import math
from lxml import etree from lxml import etree
import math import math
def drawS(XYstring): # Draw lines from a list class BoxMakerLivingHinge(inkex.EffectExtension):
def drawS(self, XYstring): # Draw lines from a list
name='part' name='part'
style = { 'stroke': '#000000', 'fill': 'none' } style = { 'stroke': '#000000', 'stroke-width': self.svg.unittouu('1px'), 'fill': 'none' }
drw = {'style': str(inkex.Style(style)),inkex.addNS('label','inkscape'):name,'d':XYstring} drw = {'style': str(inkex.Style(style)),inkex.addNS('label','inkscape'):name,'d':XYstring}
etree.SubElement(parent, inkex.addNS('path','svg'), drw ) etree.SubElement(parent, inkex.addNS('path','svg'), drw )
return return
def draw_SVG_ellipse(centerx, centery, radiusx, radiusy, start_end): def draw_SVG_ellipse(self, centerx, centery, radiusx, radiusy, start_end):
style = { 'stroke': '#000000', 'stroke-width': self.svg.unittouu('1px'), 'fill': 'none' }
style = { 'stroke' : '#000000',
'fill' : 'none' }
ell_attribs = {'style': str(inkex.Style(style)), ell_attribs = {'style': str(inkex.Style(style)),
inkex.addNS('cx','sodipodi') :str(centerx), inkex.addNS('cx','sodipodi') :str(centerx),
inkex.addNS('cy','sodipodi') :str(centery), inkex.addNS('cy','sodipodi') :str(centery),
@ -49,19 +49,16 @@ def draw_SVG_ellipse(centerx, centery, radiusx, radiusy, start_end):
inkex.addNS('type','sodipodi') :'arc', inkex.addNS('type','sodipodi') :'arc',
'transform' :'' 'transform' :''
} }
ell = etree.SubElement(parent, inkex.addNS('path','svg'), ell_attribs ) ell = etree.SubElement(parent, inkex.addNS('path','svg'), ell_attribs )
#draw an SVG line segment between the given (raw) points #draw an SVG line segment between the given (raw) points
def draw_SVG_line( x1, y1, x2, y2, parent): def draw_SVG_line(self, x1, y1, x2, y2, parent):
style = { 'stroke': '#000000', 'fill': 'none' } style = { 'stroke': '#000000', 'stroke-width': self.svg.unittouu('1px'), 'fill': 'none' }
line_attribs = {'style' : str(inkex.Style(style)), line_attribs = {'style' : str(inkex.Style(style)),
'd' : 'M '+str(x1)+','+str(y1)+' L '+str(x2)+','+str(y2)} 'd' : 'M '+str(x1)+','+str(y1)+' L '+str(x2)+','+str(y2)}
line = etree.SubElement(parent, inkex.addNS('path','svg'), line_attribs ) line = etree.SubElement(parent, inkex.addNS('path','svg'), line_attribs )
def EllipseCircumference(a, b): def EllipseCircumference(self, a, b):
""" """
Compute the circumference of an ellipse with semi-axes a and b. Compute the circumference of an ellipse with semi-axes a and b.
Require a >= 0 and b >= 0. Relative accuracy is about 0.5^53. Require a >= 0 and b >= 0. Relative accuracy is about 0.5^53.
@ -76,12 +73,12 @@ def EllipseCircumference(a, b):
m *= 2; s += m * math.pow(x - y, 2) m *= 2; s += m * math.pow(x - y, 2)
return math.pi * (math.pow(a + b, 2) - s) / (x + y) return math.pi * (math.pow(a + b, 2) - s) / (x + y)
""" """
Gives you a list of points that make up a box. Gives you a list of points that make up a box.
Returns string suitable for input to drawS Returns string suitable for input to drawS
""" """
def box(sx, sy,ex, ey, leaveLeftSideOpen = False): def box(self, sx, sy,ex, ey, leaveLeftSideOpen = False):
s=[] s=[]
s='M '+str(sx)+','+str(sy)+' ' s='M '+str(sx)+','+str(sy)+' '
s+='L '+str(ex)+','+str(sy)+' ' s+='L '+str(ex)+','+str(sy)+' '
@ -91,19 +88,19 @@ def box(sx, sy,ex, ey, leaveLeftSideOpen = False):
s+='L '+str(sx)+','+str(sy)+' ' s+='L '+str(sx)+','+str(sy)+' '
return s return s
""" """
Side function is used to render any of the sides so needs all this functionality: Side function is used to render any of the sides so needs all this functionality:
isLongSide -- long sides without tabs (for cover), isLongSide -- long sides without tabs (for cover),
truncate -- partial sides for the elipse truncate -- partial sides for the elipse
gap -- extend the tabs on the curved side for ease of movement gap -- extend the tabs on the curved side for ease of movement
thumbTab -- Render individual boxes for slots instead of one continuous line thumbTab -- Render individual boxes for slots instead of one continuous line
isTab is used to specify the male/female designation for a side so they mesh properly. Otherwise the tabs isTab is used to specify the male/female designation for a side so they mesh properly. Otherwise the tabs
would be in the same spot for opposing sides, instead of interleaved. would be in the same spot for opposing sides, instead of interleaved.
Returns a list of lines to draw. Returns a list of lines to draw.
""" """
def side(rx,ry,sox,soy,eox,eoy,tabVec,length, dirx, diry, isTab, isLongSide, truncate = False, gap = False, thumbTab = False): def side(self, rx,ry,sox,soy,eox,eoy,tabVec,length, dirx, diry, isTab, isLongSide, truncate = False, gap = False, thumbTab = False):
# root startOffset endOffset tabVec length direction isTab # root startOffset endOffset tabVec length direction isTab
#Long side length= length+((math.pi*(length/2))/4 #Long side length= length+((math.pi*(length/2))/4
@ -181,7 +178,7 @@ def side(rx,ry,sox,soy,eox,eoy,tabVec,length, dirx, diry, isTab, isLongSide, tru
Vy=Vy+diryN*secondVec Vy=Vy+diryN*secondVec
s+='L '+str(Vx)+','+str(Vy)+' ' s+='L '+str(Vx)+','+str(Vy)+' '
if thumbTab: if thumbTab:
drawS(box(Vxs,Vys,Vx,Vy)) self.drawS(self.box(Vxs,Vys,Vx,Vy))
(secondVec,firstVec)=(-secondVec,-firstVec) # swap tab direction (secondVec,firstVec)=(-secondVec,-firstVec) # swap tab direction
first=0 first=0
if not truncate: if not truncate:
@ -190,8 +187,7 @@ def side(rx,ry,sox,soy,eox,eoy,tabVec,length, dirx, diry, isTab, isLongSide, tru
s+='L '+str(rx+eox*thickness+dirx*(length/2))+','+str(ry+eoy*thickness+diry*(length/2))+' ' s+='L '+str(rx+eox*thickness+dirx*(length/2))+','+str(ry+eoy*thickness+diry*(length/2))+' '
return s return s
#God class. Makes poor design, but not much object oriented in this guy...
class BoxMakerLivingHinge(inkex.EffectExtension):
def add_arguments(self, pars): def add_arguments(self, pars):
pars.add_argument('--unit',default='mm',help='Measure Units') pars.add_argument('--unit',default='mm',help='Measure Units')
@ -241,14 +237,14 @@ class BoxMakerLivingHinge(inkex.EffectExtension):
for n in range(0,horizontalSlots+1): for n in range(0,horizontalSlots+1):
if n%2: #odd, exterior slot (slot should go all the way to the part edge) if n%2: #odd, exterior slot (slot should go all the way to the part edge)
draw_SVG_line(Sx + (space * n), Sy, Sx + (space * n), Sy+(height/4)-(solidGap/2), grp) self.draw_SVG_line(Sx + (space * n), Sy, Sx + (space * n), Sy+(height/4)-(solidGap/2), grp)
draw_SVG_line(Sx + (space * n), Sy+(height/4)+(solidGap/2), Sx + (space * n), Ey-(height/4)-(solidGap/2), grp) self.draw_SVG_line(Sx + (space * n), Sy+(height/4)+(solidGap/2), Sx + (space * n), Ey-(height/4)-(solidGap/2), grp)
draw_SVG_line(Sx + (space * n), Ey-(height/4)+(solidGap/2), Sx + (space * n), Ey, grp) self.draw_SVG_line(Sx + (space * n), Ey-(height/4)+(solidGap/2), Sx + (space * n), Ey, grp)
else: else:
#even, interior slot (slot shoud not touch edge of part) #even, interior slot (slot shoud not touch edge of part)
draw_SVG_line(Sx + (space * n), Sy+solidGap, Sx + (space * n), Sy+(height/2)-(solidGap/2), grp) self.draw_SVG_line(Sx + (space * n), Sy+solidGap, Sx + (space * n), Sy+(height/2)-(solidGap/2), grp)
draw_SVG_line(Sx + (space * n), Ey-(height/2)+(solidGap/2), Sx + (space * n), Ey-solidGap, grp) self.draw_SVG_line(Sx + (space * n), Ey-(height/2)+(solidGap/2), Sx + (space * n), Ey-solidGap, grp)
""" """
The sprial based designs are built from multiple calls of this function. The sprial based designs are built from multiple calls of this function.
@ -280,13 +276,13 @@ class BoxMakerLivingHinge(inkex.EffectExtension):
for n in range(0,horizontalSlots): for n in range(0,horizontalSlots):
newX = (((space/2) + (space*n)) * reverse) newX = (((space/2) + (space*n)) * reverse)
draw_SVG_line((centerX - newX), centerY + (space/2) + (space * n), (centerX - newX ), centerY - (space * 1.5) - (space * n), grp) self.draw_SVG_line((centerX - newX), centerY + (space/2) + (space * n), (centerX - newX ), centerY - (space * 1.5) - (space * n), grp)
if horizontalSlots - 1 != n: #Last line in center should be omited if horizontalSlots - 1 != n: #Last line in center should be omited
draw_SVG_line((centerX - (space + (space/2 * -reverse)) - (space*n) ), centerY - (space * 1.5) - (space * n), (centerX + (space + (space/2 * reverse)) + (space*n) ), centerY - (space * 1.5) - (space * n), grp) self.draw_SVG_line((centerX - (space + (space/2 * -reverse)) - (space*n) ), centerY - (space * 1.5) - (space * n), (centerX + (space + (space/2 * reverse)) + (space*n) ), centerY - (space * 1.5) - (space * n), grp)
draw_SVG_line((centerX + newX ), centerY - (space/2) - (space * n), (centerX + newX ), centerY + (space * 1.5) + (space * n), grp) self.draw_SVG_line((centerX + newX ), centerY - (space/2) - (space * n), (centerX + newX ), centerY + (space * 1.5) + (space * n), grp)
if horizontalSlots - 1 != n: #Last line in center should be omited if horizontalSlots - 1 != n: #Last line in center should be omited
draw_SVG_line((centerX + (space + (space/2 * -reverse)) + (space*n) ), centerY + (space * 1.5) + (space * n), (centerX - (space + (space/2 * reverse)) - (space*n) ), centerY + (space * 1.5) + (space * n), grp) self.draw_SVG_line((centerX + (space + (space/2 * -reverse)) + (space*n) ), centerY + (space * 1.5) + (space * n), (centerX - (space + (space/2 * reverse)) - (space*n) ), centerY + (space * 1.5) + (space * n), grp)
""" """
The snake based designs are built from multiple calls of this function. The snake based designs are built from multiple calls of this function.
@ -324,20 +320,20 @@ class BoxMakerLivingHinge(inkex.EffectExtension):
for n in range(1 - skew,horizontalSlots + skew): for n in range(1 - skew,horizontalSlots + skew):
if not rotate: if not rotate:
if (n+mirror)%2: if (n+mirror)%2:
draw_SVG_line(Sx + (space * n), Sy + solidGap, Sx + (space * n), Ey, grp) self.draw_SVG_line(Sx + (space * n), Sy + solidGap, Sx + (space * n), Ey, grp)
else: else:
draw_SVG_line(Sx + (space * n), Sy, Sx + (space * n), Ey - solidGap, grp) self.draw_SVG_line(Sx + (space * n), Sy, Sx + (space * n), Ey - solidGap, grp)
else: else:
if (n+mirror)%2: if (n+mirror)%2:
draw_SVG_line(Sx + solidGap, Sy + (space * n), Ex, Sy + (space * n), grp) self.draw_SVG_line(Sx + solidGap, Sy + (space * n), Ex, Sy + (space * n), grp)
else: else:
draw_SVG_line(Sx, Sy + (space * n), Ex - solidGap, Sy + (space * n), grp) self.draw_SVG_line(Sx, Sy + (space * n), Ex - solidGap, Sy + (space * n), grp)
if rotate and not mirror: if rotate and not mirror:
draw_SVG_line(Sx, Sy, Sx, Ey - space, grp) self.draw_SVG_line(Sx, Sy, Sx, Ey - space, grp)
draw_SVG_line(Ex, Sy + space, Ex, Ey, grp) self.draw_SVG_line(Ex, Sy + space, Ex, Ey, grp)
elif mirror: elif mirror:
draw_SVG_line(Sx, Sy + space, Sx, Ey, grp) self.draw_SVG_line(Sx, Sy + space, Sx, Ey, grp)
draw_SVG_line(Ex, Sy, Ex, Ey - space, grp) self.draw_SVG_line(Ex, Sy, Ex, Ey - space, grp)
def effect(self): def effect(self):
global parent,nomTab,equalTabs,thickness,correction, Z, unit global parent,nomTab,equalTabs,thickness,correction, Z, unit
@ -427,7 +423,7 @@ class BoxMakerLivingHinge(inkex.EffectExtension):
#center middle row #center middle row
[(2,0,0,1),(2,0,0,1),X,Y,0b0000,0], [(2,0,0,1),(2,0,0,1),X,Y,0b0000,0],
#right middle row #right middle row
[(3,1,0,1),(2,0,0,1),Z+(EllipseCircumference(X/2, Z/2)/4)+thickness,Y,0b1011,1], [(3,1,0,1),(2,0,0,1),Z+(self.EllipseCircumference(X/2, Z/2)/4)+thickness,Y,0b1011,1],
#center top row #center top row
[(2,0,0,1),(1,0,0,0),X,Z,0b0010,-1]] [(2,0,0,1),(1,0,0,0),X,Z,0b0010,-1]]
elif layout==1: # Inline(compact) Layout elif layout==1: # Inline(compact) Layout
@ -439,7 +435,6 @@ class BoxMakerLivingHinge(inkex.EffectExtension):
[(3,1,0,1),(1,0,0,0),X,Z,0b1000,-2], [(3,1,0,1),(1,0,0,0),X,Z,0b1000,-2],
[(4,2,0,1),(1,0,0,0),X,Z,0b0010,-1], [(4,2,0,1),(1,0,0,0),X,Z,0b0010,-1],
#Long piece w/ hinge #Long piece w/ hinge
[(5,3,0,1),(1,0,0,0),Z+(EllipseCircumference(X/2, Z/2)/4)+thickness,Y,0b1011,1]
] ]
for piece in pieces: # generate and draw each piece of the box for piece in pieces: # generate and draw each piece of the box
@ -462,50 +457,50 @@ class BoxMakerLivingHinge(inkex.EffectExtension):
# generate and draw the sides of each piece # generate and draw the sides of each piece
if piece[5] != -1: if piece[5] != -1:
drawS(side(x,y,d,a,-b,a,-thickness if a else thickness,dx,1,0,a,longSide)) # side a (top) self.drawS(self.side(x,y,d,a,-b,a,-thickness if a else thickness,dx,1,0,a,longSide)) # side a (top)
else: else:
drawS(side(x,y,d,a,-b,a,-thickness if a else thickness,dx/2,1,0,a,-1)) # side a (top) when the top participates in a curve self.drawS(self.side(x,y,d,a,-b,a,-thickness if a else thickness,dx/2,1,0,a,-1)) # side a (top) when the top participates in a curve
if piece[5] != -1 and piece[5] != 1: if piece[5] != -1 and piece[5] != 1:
drawS(side(x+dx+skew,y,-b,a,-b,-c,thickness if b else -thickness,dy,0,1,b,shortSide, False if piece[5] != -2 else True, False if piece[5] != 1 else True)) # side b (right) except for side with living hinge or curves self.drawS(self.side(x+dx+skew,y,-b,a,-b,-c,thickness if b else -thickness,dy,0,1,b,shortSide, False if piece[5] != -2 else True, False if piece[5] != 1 else True)) # side b (right) except for side with living hinge or curves
elif piece[5] == -1: elif piece[5] == -1:
drawS(side(x+dx+skew,y+dy,-b,-c,-b,a,thickness if b else -thickness,dy,0,-1,b,shortSide, True)) # side b (right) when the right side participates in a curve self.drawS(self.side(x+dx+skew,y+dy,-b,-c,-b,a,thickness if b else -thickness,dy,0,-1,b,shortSide, True)) # side b (right) when the right side participates in a curve
else: else:
#It is a cardnal sin to compare floats, so assume <0.0005 is 0 since the front end only gives you 3 digits of precision #It is a cardnal sin to compare floats, so assume <0.0005 is 0 since the front end only gives you 3 digits of precision
if float(0.0005) <= float(self.options.thumbTab): if float(0.0005) <= float(self.options.thumbTab):
side(x+dx+skew,y,-b,a,-b,-c,thickness if b else -thickness,dy,0,1,b,shortSide, False, True, True) #The one call to side that doesn't actually draw. Instead, side draws boxes on its own self.side(x+dx+skew,y,-b,a,-b,-c,thickness if b else -thickness,dy,0,1,b,shortSide, False, True, True) #The one call to side that doesn't actually draw. Instead, side draws boxes on its own
drawS(box(x+dx+skew,y+thickness,x+dx+skew+self.svg.unittouu( thumbTab + unit ),y+dy-thickness, True)) self.drawS(self.box(x+dx+skew,y+thickness,x+dx+skew+self.svg.unittouu( thumbTab + unit ),y+dy-thickness, True))
else: else:
drawS(side(x+dx+skew,y,-b,a,-b,-c,thickness if b else -thickness,dy,0,1,b,shortSide, False, True)) #side b (right) on the right side of a living hinge self.drawS(self.side(x+dx+skew,y,-b,a,-b,-c,thickness if b else -thickness,dy,0,1,b,shortSide, False, True)) #side b (right) on the right side of a living hinge
if piece[5] != -2: if piece[5] != -2:
drawS(side(x,y+dy,d,-c,-b,-c,thickness if c else -thickness,dx,1,0,c,longSide)) # side c (bottom) self.drawS(self.side(x,y+dy,d,-c,-b,-c,thickness if c else -thickness,dx,1,0,c,longSide)) # side c (bottom)
else: else:
drawS(side(x,y+dy,d,-c,-b,-c,thickness if c else -thickness,dx/2,1,0,c,-1)) # side c (bottom) when the bottom participates in a curve self.drawS(self.side(x,y+dy,d,-c,-b,-c,thickness if c else -thickness,dx/2,1,0,c,-1)) # side c (bottom) when the bottom participates in a curve
drawS(side(x,y+dy,d,-c,d,a,-thickness if d else thickness,dy,0,-1,d,0)) # side d (left) self.drawS(self.side(x,y+dy,d,-c,d,a,-thickness if d else thickness,dy,0,-1,d,0)) # side d (left)
if piece[5] < 0: if piece[5] < 0:
draw_SVG_ellipse(x+(dx/2), y+(dy/2), (dx/2), (dy/2), [(1.5*math.pi), 0] if piece[5] == -1 else [0, 0.5*math.pi]) #draw the curve self.draw_SVG_ellipse(x+(dx/2), y+(dy/2), (dx/2), (dy/2), [(1.5*math.pi), 0] if piece[5] == -1 else [0, 0.5*math.pi]) #draw the curve
if piece[5] == 1: #Piece should contain a living hinge if piece[5] == 1: #Piece should contain a living hinge
if hingeOpt == 0: #Traditional parallel slit if hingeOpt == 0: #Traditional parallel slit
self.livingHinge2(x+(Z/2), y, x+(Z/2)+(EllipseCircumference(X/2, Z/2)/4), y + (dy), hingeThick) self.livingHinge2(x+(Z/2), y, x+(Z/2)+(self.EllipseCircumference(X/2, Z/2)/4), y + (dy), hingeThick)
elif hingeOpt == 1: #Single spiral elif hingeOpt == 1: #Single spiral
if not inside: if not inside:
self.livingHinge3(x+(Z/2), y+thickness, x+(Z/2)+(EllipseCircumference(X/2, Z/2)/4), y + dy - thickness, 1, hingeThick) self.livingHinge3(x+(Z/2), y+thickness, x+(Z/2)+(self.EllipseCircumference(X/2, Z/2)/4), y + dy - thickness, 1, hingeThick)
else: else:
self.livingHinge3(x+(Z/2), y + 2*thickness, x+(Z/2)+(EllipseCircumference(X/2, Z/2)/4), y + dy - 2*thickness, 1, hingeThick) self.livingHinge3(x+(Z/2), y + 2*thickness, x+(Z/2)+(self.EllipseCircumference(X/2, Z/2)/4), y + dy - 2*thickness, 1, hingeThick)
elif hingeOpt == 2: #Double spiral elif hingeOpt == 2: #Double spiral
self.livingHinge3(x+(Z/2), y+thickness, x+(Z/2)+(EllipseCircumference(X/2, Z/2)/4), y + (dy/2), 1, hingeThick) self.livingHinge3(x+(Z/2), y+thickness, x+(Z/2)+(self.EllipseCircumference(X/2, Z/2)/4), y + (dy/2), 1, hingeThick)
self.livingHinge3(x+(Z/2), y+(dy/2), x+(Z/2)+(EllipseCircumference(X/2, Z/2)/4), y + dy - thickness, -1, hingeThick) self.livingHinge3(x+(Z/2), y+(dy/2), x+(Z/2)+(self.EllipseCircumference(X/2, Z/2)/4), y + dy - thickness, -1, hingeThick)
elif hingeOpt == 3 or hingeOpt == 4: #Both snake-based designs elif hingeOpt == 3 or hingeOpt == 4: #Both snake-based designs
self.livingHinge4(x+(Z/2), y, x+(Z/2)+(EllipseCircumference(X/2, Z/2)/4), y + (dy), False if hingeOpt == 3 else True, 0, hingeThick) self.livingHinge4(x+(Z/2), y, x+(Z/2)+(self.EllipseCircumference(X/2, Z/2)/4), y + (dy), False if hingeOpt == 3 else True, 0, hingeThick)
elif hingeOpt == 5: #Double snake design elif hingeOpt == 5: #Double snake design
self.livingHinge4(x+(Z/2), y, x+(Z/2)+EllipseCircumference(X/2, Z/2)/4, y + (dy/2) + thickness, True, 0, hingeThick) #Add thickness as a cheat so design 4 doesn't have to know if it's a short or long variant self.livingHinge4(x+(Z/2), y, x+(Z/2)+self.EllipseCircumference(X/2, Z/2)/4, y + (dy/2) + thickness, True, 0, hingeThick) #Add thickness as a cheat so design 4 doesn't have to know if it's a short or long variant
self.livingHinge4(x+(Z/2), y + (dy/2) - thickness, (x+(Z/2)+(EllipseCircumference(X/2, Z/2)/4)), y + dy, True, 1, hingeThick) self.livingHinge4(x+(Z/2), y + (dy/2) - thickness, (x+(Z/2)+(self.EllipseCircumference(X/2, Z/2)/4)), y + dy, True, 1, hingeThick)
if __name__ == '__main__': if __name__ == '__main__':
BoxMakerLivingHinge().run() BoxMakerLivingHinge().run()