703 lines
37 KiB
Python
703 lines
37 KiB
Python
|
#!/usr/bin/env python3
|
||
|
import inkex
|
||
|
import math
|
||
|
import numpy as np
|
||
|
from lxml import etree
|
||
|
|
||
|
sizeTab = 10000 #Any value greater than 1000 should give goo results
|
||
|
|
||
|
objStyle = str(inkex.Style(
|
||
|
{'stroke': '#000000',
|
||
|
'stroke-width': 0.1,
|
||
|
'fill': 'none'
|
||
|
}))
|
||
|
|
||
|
|
||
|
objStyleStart = str(inkex.Style(
|
||
|
{'stroke': '#FF0000',
|
||
|
'stroke-width': 0.1,
|
||
|
'fill': 'none'
|
||
|
}))
|
||
|
|
||
|
def lengthCurve(Xarray, Yarray, npoints):
|
||
|
'''
|
||
|
Give length of a path between point of a curve
|
||
|
Beware, go from 0 to Index included, so the arrays should have at least npoints+1 elements
|
||
|
'''
|
||
|
x = Xarray[0]
|
||
|
y = Yarray[0]
|
||
|
Length = 0.0
|
||
|
i = 1
|
||
|
while i <= npoints:
|
||
|
Length += math.hypot((Xarray[i] - x), (Yarray[i] - y))
|
||
|
x = Xarray[i]
|
||
|
y = Yarray[i]
|
||
|
i += 1
|
||
|
return Length
|
||
|
|
||
|
class inkcape_polar:
|
||
|
def __init__(self, Offset, group):
|
||
|
self.offsetX = Offset[0]
|
||
|
self.offsetY = Offset[1]
|
||
|
self.Path = ''
|
||
|
self.group = group
|
||
|
|
||
|
def MoveTo(self, r, angle):
|
||
|
#Return string for moving to point given as parameter, with polar coordinates radius, angle
|
||
|
self.Path += ' M ' + str(round(r*math.cos(angle)-self.offsetX, 3)) + ',' + str(round(r*math.sin(angle)-self.offsetY, 3))
|
||
|
|
||
|
def MoveTo_cartesian(self, pt):
|
||
|
#Return string for moving to point given as parameter
|
||
|
self.Path += ' M ' + str(round(pt[0]-self.offsetX, 3)) + ',' + str(round(pt[1]-self.offsetY, 3))
|
||
|
|
||
|
|
||
|
def LineTo_cartesian(self, pt):
|
||
|
#Return string for moving to point given as parameter
|
||
|
self.Path += ' L ' + str(round(pt[0]-self.offsetX, 3)) + ',' + str(round(pt[1]-self.offsetY, 3))
|
||
|
|
||
|
def LineTo(self, r, angle):
|
||
|
#Retourne chaine de caractères donnant la position du point avec des coordonnées polaires
|
||
|
self.Path += ' L ' + str(round(r*math.cos(angle)-self.offsetX, 3)) + ',' + str(round(r*math.sin(angle)-self.offsetY, 3))
|
||
|
|
||
|
def Line(self, r1, angle1, r2, angle2):
|
||
|
#Retourne chaine de caractères donnant la position du point avec des coordonnées polaires
|
||
|
self.Path += ' M ' + str(round(r1*math.cos(angle1)-self.offsetX, 3)) + ',' + str(round(r1*math.sin(angle1)-self.offsetY, 3)) + ' L ' + str(round(r2*math.cos(angle2)-self.offsetX, 3)) + ',' + str(round(r2*math.sin(angle2)-self.offsetY, 3))
|
||
|
|
||
|
def GenPath(self):
|
||
|
line_attribs = {'style': objStyle, 'd': self.Path}
|
||
|
etree.SubElement(self.group, inkex.addNS('path', 'svg'), line_attribs)
|
||
|
|
||
|
class EllConicalBox(inkex.Effect):
|
||
|
"""
|
||
|
Creates a new layer with the drawings for a parametrically generaded box.
|
||
|
"""
|
||
|
def __init__(self):
|
||
|
inkex.Effect.__init__(self)
|
||
|
self.knownUnits = ['in', 'pt', 'px', 'mm', 'cm', 'm', 'km', 'pc', 'yd', 'ft']
|
||
|
self.arg_parser.add_argument('--unit', default = 'mm', help = 'Unit, should be one of ')
|
||
|
self.arg_parser.add_argument('--thickness', type = float, default = '3.0', help = 'Material thickness')
|
||
|
self.arg_parser.add_argument('--d1', type = float, default = '50.0', help = 'Small ellipse diameter')
|
||
|
self.arg_parser.add_argument('--d2', type = float, default = '100.0', help = 'Large ellipse diameter')
|
||
|
self.arg_parser.add_argument('--eccentricity', type = float, default = '1.0', help = 'Ratio minor vs major axis, should be less than 1')
|
||
|
self.arg_parser.add_argument('--zc', type = float, default = '50.0', help = 'Cone height')
|
||
|
self.arg_parser.add_argument('--notch_interval', type = int, default = '2', help = 'Interval between notches, should be even')
|
||
|
self.arg_parser.add_argument('--cut_position', type = int, default = '0', help = 'Cut position angle')
|
||
|
self.arg_parser.add_argument('--inner_size', type = inkex.Boolean, default = 'true', help = 'Dimensions are internal')
|
||
|
self.arg_parser.add_argument('--Mode_Debug', type = inkex.Boolean, default = 'false', help = 'Output Debug information in file')
|
||
|
|
||
|
# Create list of points for the ellipse, will be filled later
|
||
|
self.xEllipse = np.zeros(sizeTab+1) #X coordiantes
|
||
|
self.yEllipse = np.zeros(sizeTab+1) # Y coordinates
|
||
|
self.lEllipse = np.zeros(sizeTab+1) # Length of curve until this point
|
||
|
|
||
|
def unittouu(self, unit):
|
||
|
return inkex.unittouu(unit)
|
||
|
|
||
|
def DebugMsg(self, s):
|
||
|
if self.fDebug:
|
||
|
self.fDebug.write(s)
|
||
|
|
||
|
def length2Angle(self, StartIdx, Position):
|
||
|
'''
|
||
|
Return the first index which is near the requested position.
|
||
|
Start search at StartIdx to optimize computation
|
||
|
'''
|
||
|
while Position > self.lEllipse[StartIdx]:
|
||
|
StartIdx += 1
|
||
|
if StartIdx >= sizeTab:
|
||
|
return sizeTab
|
||
|
# Now return value between StartIdx and StartIdx - 1 which is nearer
|
||
|
if StartIdx == 0:
|
||
|
return 0
|
||
|
if abs(Position - self.lEllipse[StartIdx]) > abs(Position - self.lEllipse[StartIdx-1]):
|
||
|
return StartIdx - 1
|
||
|
return StartIdx
|
||
|
|
||
|
def ellipse_ext(self, a, b, alpha, e):
|
||
|
'''
|
||
|
Compute the point which is on line orthogonal to ellipse (a, b) and angle alpha and on the ellipse of parameters ( a+e, b+e)
|
||
|
As equations are quite complex, use an approximation method
|
||
|
'''
|
||
|
Slope = math.atan2(b*math.cos(alpha), -a*math.sin(alpha)) #Ellipse slope in point at angle alpha
|
||
|
Ortho = Slope - math.pi/2 # Use -pi/2 because e positive means second ellipse larger
|
||
|
''' The point is on the line x = a*cos(alpha) + L*cos(Ortho), y= b*sin(alpha) + L*sin(Ortho)
|
||
|
We have to determine L
|
||
|
For this, change L and compare with equation of larger ellipse
|
||
|
Start with L = e
|
||
|
Result should lie between L/2 and 2L
|
||
|
'''
|
||
|
#self.DebugMsg("Enter ellipse_ext"+str((a,b,alpha,e))+" Slope="+str(Slope*180/math.pi)+" Ortho="+str(Ortho*180/math.pi)+'\n')
|
||
|
L = e
|
||
|
step = e
|
||
|
ntry = 1
|
||
|
x = a*math.cos(alpha) + L*math.cos(Ortho)
|
||
|
y = b*math.sin(alpha) + L*math.sin(Ortho)
|
||
|
# Compute difference which the error between this point and the large ellipse
|
||
|
distance = (x*x)/((a+e)*(a+e)) + (y*y)/((b+e)*(b+e)) - 1
|
||
|
#self.DebugMsg("ellipse_ext First try with L=e pt="+str((x,y))+" distance="+str(distance)+'\n')
|
||
|
while abs(distance) > 0.001 and step >0.001:
|
||
|
if distance > 0:
|
||
|
#L is too large, decrease by step/2
|
||
|
step /= 2
|
||
|
L -= step
|
||
|
else:
|
||
|
#L is not large enough, increase by step/2
|
||
|
step /= 2
|
||
|
L += step
|
||
|
ntry += 1
|
||
|
x = a*math.cos(alpha) + L*math.cos(Ortho)
|
||
|
y = b*math.sin(alpha) + L*math.sin(Ortho)
|
||
|
# Compute difference which the error between this point and the large ellipse
|
||
|
distance = (x*x)/((a+e)*(a+e)) + (y*y)/((b+e)*(b+e)) - 1
|
||
|
#self.DebugMsg(" try "+str(ntry)+" with L="+str(L)+" pt="+str((x,y))+" distance="+str(distance)+'\n')
|
||
|
if distance > 0.001:
|
||
|
self.DebugMsg("Problem, solution do not converge. Error is "+str(distance)+" after "+str(ntry)+" tries\n")
|
||
|
return(x, y)
|
||
|
#self.DebugMsg("Solution converge after "+str(ntry)+" tries. Error is "+str(distance)+"\n")
|
||
|
return(x, y)
|
||
|
|
||
|
def Coordinates_Step_SubStep(self, step, substep):
|
||
|
'''
|
||
|
Return the radius and angle on resulting curve for step i, substep j
|
||
|
'''
|
||
|
if step == self.num_notches:
|
||
|
# Specific case for last notch on curve
|
||
|
if substep == 0: #Last position on curve
|
||
|
return (self.ResultingCurve_R[sizeTab], self.ResultingCurve_Theta[sizeTab])
|
||
|
else:
|
||
|
AngleEllipse = self.Notches_Angle_ellipse[self.Offset_Notch][1] - self.Offset_Ellipse #To match first step
|
||
|
if AngleEllipse < 0:
|
||
|
AngleEllipse += sizeTab
|
||
|
return(self.ResultingCurve_R[AngleEllipse], self.ResultingCurve_Theta[AngleEllipse]+self.ResultingCurve_Theta[sizeTab])
|
||
|
new_step = step + self.Offset_Notch
|
||
|
if new_step >= self.num_notches:
|
||
|
new_step -= self.num_notches
|
||
|
AngleEllipse = self.Notches_Angle_ellipse[new_step][substep] - self.Offset_Ellipse
|
||
|
if AngleEllipse < 0:
|
||
|
AngleEllipse += sizeTab
|
||
|
if substep == 0 or step == 0:
|
||
|
self.DebugMsg("Coordinates_Step_SubStep"+str((step, substep))+" --> AngleEllipse ="+str(self.Notches_Angle_ellipse[new_step][substep])+" --> "+str(AngleEllipse)+" Result="+str((self.ResultingCurve_R[AngleEllipse], self.ResultingCurve_Theta[AngleEllipse]))+'\n')
|
||
|
|
||
|
return (self.ResultingCurve_R[AngleEllipse], self.ResultingCurve_Theta[AngleEllipse])
|
||
|
|
||
|
def gen_flex_step(self, index_step):
|
||
|
'''
|
||
|
Draw a flex step. Each step has N + 2 vertical lines where N is the distance between notches.
|
||
|
The notch itself is 2 mm (roughly) large, the whole notch is N+2 mm large
|
||
|
'''
|
||
|
#Each step is a path for inkscape
|
||
|
path = inkcape_polar(self.Offset, self.group)
|
||
|
# first draw the line between next notch and current one
|
||
|
R, angle = self.Coordinates_Step_SubStep(index_step+1, 0)
|
||
|
self.DebugMsg("gen_flex_step("+str(index_step)+") : MoveTo("+str((R, 180*angle/math.pi))+'\n')
|
||
|
path.MoveTo(R, angle)
|
||
|
R, angle = self.Coordinates_Step_SubStep(index_step, 2)
|
||
|
path.LineTo(R, angle)
|
||
|
self.DebugMsg(" From next notch, LineTo("+str((R, 180*angle/math.pi))+'\n')
|
||
|
# Then the notch, starting internal to external
|
||
|
# Compute radius to largest ellipse
|
||
|
R2 = R * self.large_ell_a / self.small_ell_a
|
||
|
# The short vertical line begins at (R2 - R)/2/NbVerticalLines - 1
|
||
|
v = (R2-R)/2/self.nbVerticalLines - 1
|
||
|
path.Line(R+v, angle, R-self.thickness, angle)
|
||
|
self.DebugMsg(" Int notch, LineFrom("+str((R+v, 180*angle/math.pi))+" to "+str((R-self.thickness, 180*angle/math.pi))+" v="+str(v)+'\n')
|
||
|
# Then notch (internal side)
|
||
|
R, angle = self.Coordinates_Step_SubStep(index_step, 0)
|
||
|
path.LineTo(R-self.thickness, angle)
|
||
|
self.DebugMsg(" Int notch, LineTo "+str((R-self.thickness, 180*angle/math.pi))+'\n')
|
||
|
# Compute radius to largest ellipse
|
||
|
R2 = R * self.large_ell_a / self.small_ell_a
|
||
|
# The short vertical line ends at (R2 - R)/2/NbVerticalLines - 1
|
||
|
v = (R2-R)/2/self.nbVerticalLines - 1
|
||
|
v2 = (R2-R)/self.nbVerticalLines - 2
|
||
|
path.LineTo(v + R , angle)
|
||
|
self.DebugMsg(" notch, LineTo "+str((R+v, 180*angle/math.pi))+" v ="+str(v)+" v2="+str(v2)+'\n')
|
||
|
# Now draw N-1 vertical lines, size v2
|
||
|
for i in range(self.nbVerticalLines-1):
|
||
|
path.Line(i*(v2+2)+v+2+R, angle, (i+1)*(v2+2)+v+R, angle)
|
||
|
self.DebugMsg(" Vertical lines_1 , Line from "+str((i*(v2+2)+v+2+R, 180*angle/math.pi))+" to "+str(((i+1)*(v2+2)+v+R, 180*angle/math.pi))+'\n')
|
||
|
# Then external notch
|
||
|
path.Line((i+1)*(v2+2)+v+R+2, angle, R2 + self.thickness, angle)
|
||
|
self.DebugMsg(" Ext_notch , Line from "+str(((i+1)*(v2+2)+v+R+2, 180*angle/math.pi))+" to "+str((R2 + self.thickness, 180*angle/math.pi))+'\n')
|
||
|
R, angle = self.Coordinates_Step_SubStep(index_step, 2)
|
||
|
R21 = R * self.large_ell_a / self.small_ell_a
|
||
|
path.LineTo(R21+self.thickness, angle)
|
||
|
self.DebugMsg(" Ext notch, LineTo "+str((R21+self.thickness, 180*angle/math.pi))+'\n')
|
||
|
R01, angle2 = self.Coordinates_Step_SubStep(index_step+1, 0)
|
||
|
R21 = R01 * self.large_ell_a / self.small_ell_a
|
||
|
# Then return
|
||
|
v = (R2-R)/2/self.nbVerticalLines - 1
|
||
|
v2 = (R2-R)/self.nbVerticalLines - 2
|
||
|
path.LineTo(R2-v, angle)
|
||
|
self.DebugMsg(" Ext notch, LineTo "+str((R2-v, 180*angle/math.pi))+" v="+str(v)+" v2="+str(v2)+'\n')
|
||
|
#Line to next notch (external)
|
||
|
path.Line(R2, angle, R21, angle2)
|
||
|
self.DebugMsg(" To next notch, Line From "+str((R21, 180*angle/math.pi))+" To "+str((R2, 180*angle2/math.pi))+'\n')
|
||
|
# Now draw N-1 vertical lines
|
||
|
for i in range(self.nbVerticalLines-2, -1, -1):
|
||
|
path.Line((i+1)*(v2+2)+v+R, angle, i*(v2+2)+v+2+R, angle)
|
||
|
self.DebugMsg(" Vertical lines_2 , Line from "+str(((i+1)*v2+R+v+1, 180*angle/math.pi))+" to "+str((i*(v2+2)+v+2+R, 180*angle/math.pi))+'\n')
|
||
|
# Then draw nbVerticalLines inside notch, "top" to "bottom"
|
||
|
R, angle = self.Coordinates_Step_SubStep(index_step, 1)
|
||
|
v2 = (R2-R)/self.nbVerticalLines - 2
|
||
|
for i in range(self.nbVerticalLines):
|
||
|
if i == 0:
|
||
|
path.Line(R-self.thickness+1, angle, R+v2+1, angle)
|
||
|
self.DebugMsg(" Vertical lines_3 , Line from "+str((R-self.thickness+1, 180*angle/math.pi))+" to "+str((R+(i+1)*(v2+2)-1, 180*angle/math.pi))+'\n')
|
||
|
elif i == self.nbVerticalLines - 1:
|
||
|
path.Line(R+i*(v2+2)+1, angle, R2 + self.thickness - 1, angle)
|
||
|
self.DebugMsg(" Vertical lines_3 , Line from "+str((R+i*(v2+2)+1, 180*angle/math.pi))+" to "+str((R2 + self.thickness - 1, 180*angle/math.pi))+'\n')
|
||
|
else:
|
||
|
path.Line(R+i*(v2+2)+1, angle, R+(i+1)*(v2+2)-1, angle)
|
||
|
self.DebugMsg(" Vertical lines_3 , Line from "+str((R+i*(v2+2)+1, 180*angle/math.pi))+" to "+str((R+(i+1)*(v2+2)-1, 180*angle/math.pi))+'\n')
|
||
|
# Then notch_interval set of nbVerticalLines
|
||
|
#
|
||
|
for line in range(1, self.options.notch_interval):
|
||
|
R, angle = self.Coordinates_Step_SubStep(index_step, line+2)
|
||
|
v = (R2-R)/2/self.nbVerticalLines - 1
|
||
|
v2 = (R2-R)/self.nbVerticalLines - 2
|
||
|
if line % 2 == 0:
|
||
|
#line is even, draw nbVerticalLines top to bottom
|
||
|
for i in range(self.nbVerticalLines):
|
||
|
path.Line(R+i*(v2+2)+1, angle, R+(i+1)*(v2+2)-1, angle)
|
||
|
self.DebugMsg(" Vertical lines_4_0 , Line from "+str((R+i*(v2+2)+1, 180*angle/math.pi))+" to "+str((R+(i+1)*(v2+2)-1, 180*angle/math.pi))+'\n')
|
||
|
else:
|
||
|
# line is odd, draw bottom to top, first line half size
|
||
|
path.Line(R2 - 1, angle, R2 - v, angle)
|
||
|
# then nbVerticalLines - 1 lines
|
||
|
for i in range(self.nbVerticalLines-2, -1, -1):
|
||
|
path.Line((i+1)*(v2+2)+v+R, angle, i*(v2+2)+v+2+R, angle)
|
||
|
#and at last, one vertical line half size
|
||
|
path.Line(v+R, angle, R+1, angle)
|
||
|
path.GenPath()
|
||
|
|
||
|
def gen_flex_first_step(self):
|
||
|
'''
|
||
|
Draw the first step flex.
|
||
|
This specific step has a notch which is only 1mm (roughly) wide, because first and last step shoul lie in same notch
|
||
|
This has step has N + 2 vertical lines where N is the distance between notches.
|
||
|
'''
|
||
|
#Each step is a path for inkscape
|
||
|
path = inkcape_polar(self.Offset, self.group)
|
||
|
# first draw the line between next notch and current one
|
||
|
R, angle = self.Coordinates_Step_SubStep(1, 0) # Position of next step, which is 1
|
||
|
self.DebugMsg("gen_first_flex_step : MoveTo("+str((R, 180*angle/math.pi))+'\n')
|
||
|
path.MoveTo(R, angle)
|
||
|
R, angle = self.Coordinates_Step_SubStep(0, 2)
|
||
|
path.LineTo(R, angle)
|
||
|
self.DebugMsg(" From next notch, LineTo("+str((R, 180*angle/math.pi))+'\n')
|
||
|
# Then the notch, starting internal to external
|
||
|
# Compute radius to largest ellipse
|
||
|
R2 = R * self.large_ell_a / self.small_ell_a
|
||
|
# The short vertical line begins at (R2 - R)/2/NbVerticalLines - 1
|
||
|
v = (R2-R)/2/self.nbVerticalLines - 1
|
||
|
path.Line(R+v, angle, R-self.thickness, angle)
|
||
|
self.DebugMsg(" Int notch, LineFrom("+str((R+v, 180*angle/math.pi))+" to "+str((R-self.thickness, 180*angle/math.pi))+" v="+str(v)+'\n')
|
||
|
# Then notch (internal side)
|
||
|
R, angle = self.Coordinates_Step_SubStep(0, 1)
|
||
|
path.LineTo(R-self.thickness, angle)
|
||
|
self.DebugMsg(" Int notch, LineTo "+str((R-self.thickness, 180*angle/math.pi))+'\n')
|
||
|
# Compute radius to largest ellipse
|
||
|
R2 = R * self.large_ell_a / self.small_ell_a
|
||
|
# Then edge, full line towards R2
|
||
|
path.LineTo(R2+self.thickness , angle)
|
||
|
self.DebugMsg(" edge, LineTo "+str((R2, 180*angle/math.pi))+'\n')
|
||
|
R, angle = self.Coordinates_Step_SubStep(0, 2)
|
||
|
R21 = R * self.large_ell_a / self.small_ell_a
|
||
|
path.LineTo(R21+self.thickness, angle)
|
||
|
self.DebugMsg(" Ext notch, LineTo "+str((R21+self.thickness, 180*angle/math.pi))+'\n')
|
||
|
R01, angle2 = self.Coordinates_Step_SubStep(1, 0)
|
||
|
R21 = R01 * self.large_ell_a / self.small_ell_a
|
||
|
# Then return
|
||
|
v = (R2-R)/2/self.nbVerticalLines - 1
|
||
|
v2 = (R2-R)/self.nbVerticalLines - 2
|
||
|
path.LineTo(R2-v, angle)
|
||
|
self.DebugMsg(" Ext notch, LineTo "+str((R2-v, 180*angle/math.pi))+" v="+str(v)+" v2="+str(v2)+'\n')
|
||
|
#Line to next notch (external)
|
||
|
path.Line(R2, angle, R21, angle2)
|
||
|
self.DebugMsg(" To next notch, Line From "+str((R21, 180*angle/math.pi))+" To "+str((R2, 180*angle2/math.pi))+'\n')
|
||
|
# Now draw N-1 vertical lines
|
||
|
for i in range(self.nbVerticalLines-2, -1, -1):
|
||
|
path.Line((i+1)*(v2+2)+v+R, angle, i*(v2+2)+v+2+R, angle)
|
||
|
self.DebugMsg(" Vertical lines_2 , Line from "+str(((i+1)*v2+R+v+1, 180*angle/math.pi))+" to "+str((i*(v2+2)+v+2+R, 180*angle/math.pi))+'\n')
|
||
|
# Then notch_interval -1 or +1 set of nbVerticalLines
|
||
|
if self.options.notch_interval == 2:
|
||
|
numstep = 3
|
||
|
else:
|
||
|
numstep = self.options.notch_interval - 1
|
||
|
for line in range(1, numstep):
|
||
|
R, angle = self.Coordinates_Step_SubStep(0, line+2)
|
||
|
v = (R2-R)/2/self.nbVerticalLines - 1
|
||
|
v2 = (R2-R)/self.nbVerticalLines - 2
|
||
|
if line % 2 == 0:
|
||
|
#line is even, draw nbVerticalLines top to bottom
|
||
|
for i in range(self.nbVerticalLines):
|
||
|
path.Line(R+i*(v2+2)+1, angle, R+(i+1)*(v2+2)-1, angle)
|
||
|
self.DebugMsg(" Vertical lines_4_0 , Line from "+str((R+i*(v2+2)+1, 180*angle/math.pi))+" to "+str((R+(i+1)*(v2+2)-1, 180*angle/math.pi))+'\n')
|
||
|
else:
|
||
|
# line is odd, draw bottom to top, first line half size
|
||
|
path.Line(R2 - 1, angle, R2 - v, angle)
|
||
|
# then nbVerticalLines - 1 lines
|
||
|
for i in range(self.nbVerticalLines-2, -1, -1):
|
||
|
path.Line((i+1)*(v2+2)+v+R, angle, i*(v2+2)+v+2+R, angle)
|
||
|
#and at last, one vertical line half size
|
||
|
path.Line(v+R, angle, R+1, angle)
|
||
|
path.GenPath()
|
||
|
|
||
|
def gen_flex_last_step(self):
|
||
|
'''
|
||
|
Draw the last step flex.
|
||
|
This specific step has a notch which is only 1mm (roughly) wide, because first and last step shoul lie in same notch
|
||
|
This is a very simple step, with only the narrow notch
|
||
|
'''
|
||
|
#Each step is a path for inkscape
|
||
|
path = inkcape_polar(self.Offset, self.group)
|
||
|
# Then the notch, starting internal to external
|
||
|
R, angle = self.Coordinates_Step_SubStep(self.num_notches, 0)
|
||
|
# Compute radius to largest ellipse
|
||
|
R2 = R * self.large_ell_a / self.small_ell_a
|
||
|
# The short vertical line begins at (R2 - R)/2/NbVerticalLines - 1
|
||
|
v = (R2-R)/2/self.nbVerticalLines - 1
|
||
|
path.Line(R+v, angle, R-self.thickness, angle)
|
||
|
self.DebugMsg("GenLast_Step, LineFrom("+str((R+v, 180*angle/math.pi))+" to "+str((R-self.thickness, 180*angle/math.pi))+" v="+str(v)+'\n')
|
||
|
# Then notch (internal side)
|
||
|
R, angle = self.Coordinates_Step_SubStep(self.num_notches, 1)
|
||
|
path.LineTo(R-self.thickness, angle)
|
||
|
self.DebugMsg(" Last notch, LineTo "+str((R-self.thickness, 180*angle/math.pi))+'\n')
|
||
|
# Compute radius to largest ellipse
|
||
|
R2 = R * self.large_ell_a / self.small_ell_a
|
||
|
# Then edge, full line towards R2
|
||
|
path.LineTo(R2+self.thickness , angle)
|
||
|
self.DebugMsg(" edge, LineTo "+str((R2, 180*angle/math.pi))+'\n')
|
||
|
R, angle = self.Coordinates_Step_SubStep(self.num_notches, 0)
|
||
|
R21 = R * self.large_ell_a / self.small_ell_a
|
||
|
path.LineTo(R21+self.thickness, angle)
|
||
|
self.DebugMsg(" Ext notch, LineTo "+str((R21+self.thickness, 180*angle/math.pi))+'\n')
|
||
|
# Then return
|
||
|
v = (R2-R)/2/self.nbVerticalLines - 1
|
||
|
v2 = (R2-R)/self.nbVerticalLines - 2
|
||
|
path.LineTo(R2-v, angle)
|
||
|
self.DebugMsg(" Ext notch, LineTo "+str((R2-v, 180*angle/math.pi))+" v="+str(v)+" v2="+str(v2)+'\n')
|
||
|
# Now draw N-1 vertical lines
|
||
|
for i in range(self.nbVerticalLines-2, -1, -1):
|
||
|
path.Line((i+1)*(v2+2)+v+R, angle, i*(v2+2)+v+2+R, angle)
|
||
|
self.DebugMsg(" Last Vertical lines_2 , Line from "+str(((i+1)*v2+R+v+1, 180*angle/math.pi))+" to "+str((i*(v2+2)+v+2+R, 180*angle/math.pi))+'\n')
|
||
|
path.GenPath()
|
||
|
|
||
|
def compute_notches_angle(self):
|
||
|
'''
|
||
|
Compute the angles associated with each notch
|
||
|
Indeed, do not compute angles, but index in the reference ellipse array.
|
||
|
After this angle could be easily computed by multiplying by 2*pi/sizeTab
|
||
|
For each notch build a list of n+2 angles, corresponding to each step in the notch
|
||
|
2 steps for the notch itself and n steps as requested between notches
|
||
|
Angles are computed to match length of the small ellipse, length of the larger one will be longer accordingly to size ratio
|
||
|
return an array which will be used for drawing both ellipses and curved surface
|
||
|
'''
|
||
|
self.Notches_Angle_ellipse = []
|
||
|
LastIdx = 0
|
||
|
curPos = 0
|
||
|
#Compute offset, this is the first notch greater or equal to offset
|
||
|
Delta_i = self.options.cut_position * sizeTab / 360 #expected offset
|
||
|
|
||
|
self.Offset_Notch = -1
|
||
|
for iNotch in range(self.num_notches):
|
||
|
# First point is same as end of previous one, do not compute
|
||
|
# Second and third one are 1mm (roughly) farther to make the notch
|
||
|
idx1 = self.length2Angle( LastIdx, (curPos + self.notch_size / ( self.options.notch_interval + 2.0))/self.small_ell_a)
|
||
|
idx2 = self.length2Angle( LastIdx, (curPos + self.notch_size * 2.0 / ( self.options.notch_interval + 2.0))/self.small_ell_a)
|
||
|
# Check if this is the "special notch"
|
||
|
if idx1 >= Delta_i:
|
||
|
self.Offset_Ellipse = LastIdx
|
||
|
self.Offset_Notch = iNotch
|
||
|
Delta_i = sizeTab * 2 # To make sure there is only one match !
|
||
|
elif self.Offset_Notch < 0 and iNotch >= self.num_notches -1:
|
||
|
#If not found the special notch, this is the last one
|
||
|
self.Offset_Ellipse = LastIdx
|
||
|
self.Offset_Notch = iNotch
|
||
|
current_notch = [LastIdx, idx1, idx2]
|
||
|
#self.DebugMsg("Notch "+str(iNotch)+" First points="+str(current_notch)+'\n')
|
||
|
NumStep = self.options.notch_interval
|
||
|
if iNotch == self.Offset_Notch:
|
||
|
self.DebugMsg("Angle offset="+str(self.options.cut_position)+" Delta notch="+str(self.Offset_Notch)+" Real offset="+str(self.Offset_Ellipse)+'\n')
|
||
|
if NumStep == 2:
|
||
|
NumStep = 3 # In this case, special notch is longer
|
||
|
else:
|
||
|
NumStep -= 1 # In this case, it is shorter
|
||
|
# Now for each remaining steps
|
||
|
for i in range(NumStep):
|
||
|
# Even with specific notch, use self.options.notch_interval to keep global notch_size different
|
||
|
idx = self.length2Angle( LastIdx, (curPos + self.notch_size * (2.0+i+1) / ( self.options.notch_interval + 2.0))/self.small_ell_a)
|
||
|
current_notch.append(idx) # add to the list
|
||
|
LastIdx = idx
|
||
|
curPos = self.lEllipse[idx] * self.small_ell_a
|
||
|
self.Notches_Angle_ellipse.append(current_notch)
|
||
|
if iNotch == self.Offset_Notch:
|
||
|
self.DebugMsg(" Special Notch "+str(iNotch)+" with Numstep="+str(NumStep)+" ="+str(current_notch)+'\n')
|
||
|
|
||
|
#self.DebugMsg(" Complete Notch "+str(iNotch)+"="+str(current_notch)+'\n')
|
||
|
self.DebugMsg("Angles are computed, last position : "+str(curPos)+'\n')
|
||
|
# Now change position of notch next to Offset to make assembly easier
|
||
|
# if notch_interval is 2, add one for this
|
||
|
|
||
|
|
||
|
def gen_Resulting_Curve(self):
|
||
|
'''
|
||
|
Each point from the smallest ellipse will be on a curve defined by
|
||
|
1) The distance from the cone summit will sqrt(h**2 + a**2*cos(alpha)**2 + b**2*sin(alpha)**2) where h is the cone height (full cone up to summit)
|
||
|
and a and b are the ellipse dimensions.
|
||
|
2) The distance between two points on the curve should be equal at the distance between two points on the ellipse.
|
||
|
If when on alpha1 on the ellipse the angle on the resulting curbe is Theta1.
|
||
|
When on alpha2 on the ellipse, the angle on the resulting curve will be Theta2, and distance between Point(Theta2), Point(Theta1) will be equal
|
||
|
to distance Point(Alpha1), Point(Alpha2)
|
||
|
3) Theta=0 on resulting curve should correspond to parameter cut_position on the ellipse.
|
||
|
|
||
|
'''
|
||
|
#First compute the cone summit with the dimensions of the two ellipses
|
||
|
# When angle is 0, positions are (small_a, 0) and (large_a,0)
|
||
|
h1 = self.zc*self.small_ell_a/(self.large_ell_a - self.small_ell_a)
|
||
|
self.DebugMsg("gen_Resulting_Curve: height for small ellipse "+str(h1)+" For large one "+str(h1*self.large_ell_a/self.small_ell_a)+'\n')
|
||
|
# Now for each angle (index) in Notches_Angle compute the corresponding Theta angle on the resulting curve and the associated distance (polar coordinates)
|
||
|
# Do the computation with the small ellipse and large ellipse
|
||
|
self.ResultingCurve_R = np.zeros(sizeTab+1) # Distance from center for the small ellipse, once projection is applied
|
||
|
self.ResultingCurve_Theta = np.zeros(sizeTab+1) # Angle on resulting curve, for each point in initial ellipse
|
||
|
LengthResultingCurve = 0
|
||
|
alpha = (math.pi * 2 / sizeTab) * self.Offset_Ellipse
|
||
|
#Offset to length computation on ellipse
|
||
|
length_Offset = self.lEllipse[self.Offset_Ellipse]
|
||
|
#Compute first point
|
||
|
self.ResultingCurve_R[0] = math.sqrt(h1**2 + self.small_ell_a**2*math.cos(alpha)**2 + self.small_ell_b**2*math.sin(alpha)**2)
|
||
|
self.ResultingCurve_Theta[0] = 0
|
||
|
oldR = self.ResultingCurve_R[0]
|
||
|
oldX = oldR
|
||
|
oldY = 0
|
||
|
self.BoundingXmax = oldX
|
||
|
self.BoundingXmin = oldX
|
||
|
self.BoundingYmax = oldY
|
||
|
self.BoundingYmin = oldY
|
||
|
i = 1
|
||
|
error = 0
|
||
|
maxError = 0
|
||
|
maxErrorPos = 0
|
||
|
while i <= sizeTab:
|
||
|
index_ellipse = i + self.Offset_Ellipse
|
||
|
if index_ellipse > sizeTab:
|
||
|
index_ellipse -= sizeTab
|
||
|
# First radius
|
||
|
alpha = (math.pi * 2 / sizeTab) * index_ellipse
|
||
|
R = math.sqrt(h1**2 + self.small_ell_a**2*math.cos(alpha)**2 + self.small_ell_b**2*math.sin(alpha)**2)
|
||
|
self.ResultingCurve_R[i] = R
|
||
|
# Then angle
|
||
|
# First get distance on ellipse and delta from distance on result curve
|
||
|
if i == sizeTab: #Specific case, whole ellipse
|
||
|
Distance = self.small_ell_a * self.lEllipse[sizeTab]
|
||
|
else:
|
||
|
Distance = self.small_ell_a * (self.lEllipse[index_ellipse] - length_Offset)
|
||
|
if Distance < 0:
|
||
|
Distance += self.lEllipse[sizeTab] * self.small_ell_a
|
||
|
Delta_Distance = Distance - LengthResultingCurve
|
||
|
if i == sizeTab:
|
||
|
self.DebugMsg("gen_Resulting_Curve["+str(i)+"] : oldR="+str(oldR)+" R="+str(R)+" Distance="+str(Distance)+" Delta_Distance="+str(Delta_Distance)+" Compute acos("+str((oldR**2 + R**2 - Delta_Distance**2 ) / (2*oldR*R))+")\n")
|
||
|
dTheta = math.acos((oldR**2 + R**2 - Delta_Distance**2 ) / (2*oldR*R))
|
||
|
Theta = self.ResultingCurve_Theta[i-1] + dTheta
|
||
|
self.ResultingCurve_Theta[i] = Theta
|
||
|
X = R*math.cos(Theta)
|
||
|
Y = R*math.sin(Theta)
|
||
|
LengthResultingCurve += math.sqrt((X - oldX)**2 + (Y - oldY)**2)
|
||
|
oldR = R
|
||
|
oldX = X
|
||
|
oldY = Y
|
||
|
if self.BoundingXmax < X:
|
||
|
self.BoundingXmax = X
|
||
|
if self.BoundingXmin > X:
|
||
|
self.BoundingXmin = X
|
||
|
if self.BoundingYmax < Y:
|
||
|
self.BoundingYmax = Y
|
||
|
if self.BoundingYmin > Y:
|
||
|
self.BoundingYmin = Y
|
||
|
#self.DebugMsg("Index= "+str(i)+" R= "+str(R)+" Theta= "+str(180*Theta/math.pi)+" Longueur= "+str(LengthResultingCurve)+'\n')
|
||
|
error = abs(Distance - LengthResultingCurve)
|
||
|
if error > maxError:
|
||
|
maxError = error
|
||
|
maxErrorPos = i
|
||
|
self.DebugMsg("New max error reached at index "+str(i)+" Distance Ellipse="+str(Distance)+" on curve="+str(LengthResultingCurve)+" Error="+str(error)+'\n')
|
||
|
i += 1
|
||
|
|
||
|
|
||
|
def gen_ellipse(self, axis_a, axis_b, xOffset, yOffset, parent):
|
||
|
''' Generate an ellipse with notches as a path.
|
||
|
Ellipse dimensions' are parameters axis_a and axis_b
|
||
|
Notches size gives the exact distance between two notches
|
||
|
Notches number gives the number of notches to be drawed
|
||
|
xOffset and yOffset gives the offset within the inkscape page
|
||
|
Parent gives the parent structure of the path which will be created, most often the inkscape page itself
|
||
|
'''
|
||
|
group = etree.SubElement(parent, 'g') # Create a group which will hold the ellipse
|
||
|
path = inkcape_polar((xOffset, yOffset), group)
|
||
|
# First point is in (major_axis, 0)
|
||
|
Angle = 0
|
||
|
idx = 0
|
||
|
for iNotch in range(self.num_notches):
|
||
|
#Angle on ellipse
|
||
|
angle = (math.pi * 2.0 / sizeTab) * self.Notches_Angle_ellipse[iNotch][0]
|
||
|
# First point is external
|
||
|
pt1 = self.ellipse_ext(axis_a, axis_b, angle, self.thickness)
|
||
|
#Second point is on ellipse at angle given by Notches_Angle_ellipse
|
||
|
pt2 = (axis_a*math.cos(angle), axis_b*math.sin(angle))
|
||
|
#Third point is on ellipse at angle with substep=2
|
||
|
angle1 = (math.pi * 2.0 / sizeTab) * self.Notches_Angle_ellipse[iNotch][2]
|
||
|
pt3 = (axis_a*math.cos(angle1), axis_b*math.sin(angle1))
|
||
|
#Fourth point is external
|
||
|
pt4 = self.ellipse_ext(axis_a, axis_b, angle1, self.thickness)
|
||
|
if iNotch == 0:
|
||
|
#Specific case, use MoveTo
|
||
|
path.MoveTo_cartesian(pt1)
|
||
|
else:
|
||
|
#Draw line from previous fourth point
|
||
|
path.LineTo_cartesian(pt1)
|
||
|
#Then pt1 --> pt2
|
||
|
path.LineTo_cartesian(pt2)
|
||
|
#Then pt2 --> pt3
|
||
|
path.LineTo_cartesian(pt3)
|
||
|
#And at last pt3 --> pt4
|
||
|
path.LineTo_cartesian(pt4)
|
||
|
self.DebugMsg("Draw Ellipse, notch "+str(iNotch)+" Pts="+str([pt1, pt2, pt3, pt4])+'\n')
|
||
|
#Last line will be drawed with next notch
|
||
|
#For the last one
|
||
|
path.LineTo_cartesian((axis_a+self.thickness, 0))
|
||
|
path.GenPath()
|
||
|
|
||
|
def gen_flex(self, xOffset, yOffset, parent):
|
||
|
group = etree.SubElement(parent, 'g')
|
||
|
self.group = group
|
||
|
self.Offset = (xOffset, yOffset)
|
||
|
#Compute number of vertical lines, depends on cone's height
|
||
|
R = self.ResultingCurve_R[0]
|
||
|
R2 = self.large_ell_a / self.small_ell_a * self.ResultingCurve_R[0]
|
||
|
if R2 - R > 60:
|
||
|
self.nbVerticalLines = int((R2 - R)/25)
|
||
|
else:
|
||
|
self.nbVerticalLines = 2
|
||
|
self.DebugMsg("Starting gen flex with "+str(self.nbVerticalLines)+" vertical lines R1="+str(R)+" R2="+str(R2)+ "R2-R1="+str(R2-R)+"\n")
|
||
|
# First start step
|
||
|
self.gen_flex_first_step()
|
||
|
# Then middle steps
|
||
|
for step in range(1, self.num_notches):
|
||
|
self.gen_flex_step(step)
|
||
|
# and alst one, (very short)
|
||
|
self.gen_flex_last_step()
|
||
|
|
||
|
def effect(self):
|
||
|
"""
|
||
|
Draws an elliptical conic box, based on provided parameters
|
||
|
"""
|
||
|
|
||
|
# input sanity check
|
||
|
error = False
|
||
|
if self.options.zc < 15:
|
||
|
inkex.errormsg('Error: Cone height should be at least 15mm')
|
||
|
error = True
|
||
|
|
||
|
if self.options.d1 < 30:
|
||
|
inkex.errormsg('Error: d1 should be at least 30mm')
|
||
|
error = True
|
||
|
|
||
|
if self.options.d2 < self.options.d1 + 0.009999:
|
||
|
inkex.errormsg('Error: d2 should be at d1 + 0.01mm')
|
||
|
error = True
|
||
|
|
||
|
if self.options.eccentricity > 1.0 or self.options.eccentricity < 0.01:
|
||
|
inkex.errormsg('Ratio minor axis / major axis should be between 0.01 and 1.0')
|
||
|
error = True
|
||
|
|
||
|
if self.options.notch_interval > 10:
|
||
|
inkex.errormsg('Distance between notches should be less than 10')
|
||
|
error = True
|
||
|
|
||
|
if self.options.thickness < 1 or self.options.thickness > 10:
|
||
|
inkex.errormsg('Error: thickness should be at least 1mm and less than 10mm')
|
||
|
error = True
|
||
|
|
||
|
if error:
|
||
|
exit()
|
||
|
|
||
|
# convert units
|
||
|
unit = self.options.unit
|
||
|
self.small_ell_a = round(0.5 * self.svg.unittouu(str(self.options.d1) + unit), 2)
|
||
|
self.large_ell_a = round(0.5 * self.svg.unittouu(str(self.options.d2) + unit), 2)
|
||
|
self.zc = self.svg.unittouu(str(self.options.zc) + unit)
|
||
|
self.thickness = self.svg.unittouu(str(self.options.thickness) + unit)
|
||
|
if self.options.notch_interval % 2:
|
||
|
#Should be even !
|
||
|
self.options.notch_interval += 1
|
||
|
# If dimensions are external, correct d1, d2 and zc by thickness
|
||
|
if self.options.inner_size == False:
|
||
|
self.large_ell_a -= 2*self.thickness
|
||
|
self.d2 -= 2*self.thickness
|
||
|
self.zc -= 2*self.thickness
|
||
|
#Compute minor axes sizes
|
||
|
self.small_ell_b = round(self.small_ell_a * self.options.eccentricity, 2)
|
||
|
self.large_ell_b = round(self.large_ell_a * self.options.eccentricity, 2)
|
||
|
|
||
|
svg = self.document.getroot()
|
||
|
docWidth = self.svg.unittouu(svg.get('width'))
|
||
|
docHeight = self.svg.unittouu(svg.attrib['height'])
|
||
|
|
||
|
# Open Debug file if requested
|
||
|
if self.options.Mode_Debug:
|
||
|
try:
|
||
|
self.fDebug = open( 'DebugEllConicBox.txt', 'w')
|
||
|
except IOError:
|
||
|
print ('cannot open debug output file')
|
||
|
self.DebugMsg("Start processing, doc size="+str((docWidth, docHeight))+"\n")
|
||
|
|
||
|
|
||
|
layer = etree.SubElement(svg, 'g')
|
||
|
layer.set(inkex.addNS('label', 'inkscape'), 'Conical Box')
|
||
|
layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
|
||
|
#Create reference ellipse points.
|
||
|
self.xEllipse[0] = 1
|
||
|
self.yEllipse[0] = 0
|
||
|
self.lEllipse[0] = 0
|
||
|
i = 1
|
||
|
while i <= sizeTab:
|
||
|
self.xEllipse[i] = math.cos(2*math.pi*i/sizeTab) # Major axis size : 1
|
||
|
self.yEllipse[i] = self.options.eccentricity*math.sin(2*math.pi*i/sizeTab) # Minor axis size
|
||
|
self.lEllipse[i] = self.lEllipse[i-1] + math.hypot( self.xEllipse[i] - self.xEllipse[i-1], self.yEllipse[i] - self.yEllipse[i-1])
|
||
|
i += 1
|
||
|
|
||
|
|
||
|
# Compute notches size of small ellipse
|
||
|
Ell_Length = self.small_ell_a * self.lEllipse[sizeTab]
|
||
|
# One notch is different to make assembly easier, as the notch on flex are NOT evenly spaced
|
||
|
if self.options.notch_interval == 2:
|
||
|
self.num_notches = int(round((Ell_Length - self.options.notch_interval - 3) / (2.0 + self.options.notch_interval)+1))
|
||
|
self.notch_size = Ell_Length / (self.num_notches -1 + (self.options.notch_interval+3.0)/(self.options.notch_interval+2.0))
|
||
|
else:
|
||
|
self.num_notches = int(round((Ell_Length - self.options.notch_interval - 1) / (2.0 + self.options.notch_interval)+1))
|
||
|
self.notch_size = Ell_Length / (self.num_notches -1 + (self.options.notch_interval+1.0)/(self.options.notch_interval+2.0))
|
||
|
self.DebugMsg("Small ellipse dimensions a ="+str(self.small_ell_a)+" b="+str(self.small_ell_b)+" Length ="+str(Ell_Length)+'\n')
|
||
|
self.DebugMsg("Number of notches : "+str(self.num_notches)+" Real notch size="+str(self.notch_size)+'\n')
|
||
|
#Compute angles of all points which be drawed
|
||
|
self.compute_notches_angle()
|
||
|
#Then draw small ellipse
|
||
|
self.gen_ellipse(self.small_ell_a, self.small_ell_b, -self.small_ell_a - self.thickness - 1, -self.small_ell_b - self.thickness - 1, layer)
|
||
|
# Then large one
|
||
|
self.gen_ellipse(self.large_ell_a, self.large_ell_b, -self.large_ell_a-2*self.small_ell_a - 3*self.thickness - 5, -self.large_ell_b - self.thickness - 1, layer)
|
||
|
# Compute points on resulting curve
|
||
|
self.gen_Resulting_Curve()
|
||
|
# Then generate flex
|
||
|
# Bounding box of flex has been computed in gen_Resulting_Curve
|
||
|
self.DebugMsg("Flex bounding box : "+str((self.BoundingXmin, self.BoundingYmin))+","+str((self.BoundingXmax, self.BoundingYmax))+'\n')
|
||
|
# yOffset is below large ellipse
|
||
|
yOffset = -2*(self.large_ell_b+self.thickness) - 5 - self.BoundingYmin
|
||
|
# xOffset, center on page
|
||
|
xOffset = 0.5*(self.BoundingXmin + self.BoundingXmax) - 0.5*docWidth
|
||
|
self.DebugMsg("Offset Flex="+str((xOffset, yOffset))+'\n')
|
||
|
self.gen_flex(xOffset, yOffset, layer)
|
||
|
|
||
|
if self.fDebug:
|
||
|
self.fDebug.close()
|
||
|
|
||
|
# Create effect instance and apply it.
|
||
|
EllConicalBox().run()
|