mightyscape-1.2/extensions/fablabchemnitz/box_maker_conical/box_maker_conical.py

353 lines
17 KiB
Python

#!/usr/bin/env python3
# We will use the inkex module with the predefined Effect base class.
import inkex
import math
from lxml import etree
objStyle = str(inkex.Style(
{'stroke': '#000000',
'stroke-width': 0.1,
'fill': 'none'
}))
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):
#Retourne chaine de caractères donnant la position du point avec des coordonnées polaires
self.Path += ' M ' + str(round(r*math.cos(angle)-self.offsetX, 3)) + ',' + str(round(r*math.sin(angle)-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 CurvedSurface:
def __init__(self, L1, L2, nombre_pas, angle_par_pas, taille_exacte_pas, nb_parts, epaisseur, parent, xOffset, yOffset):
self.L1 = L1
self.L2 = L2
self.nombre_pas = nombre_pas
self.angle_par_pas = angle_par_pas
self.taille_exacte_pas = taille_exacte_pas
self.epaisseur = epaisseur
self.parent = parent
self.OffsetX = xOffset
self.OffsetY = yOffset
self.nb_parts = nb_parts
def genere_element_1(self, angle):
path = inkcape_polar(self.Offset, self.group)
# Commence par le pas de liaison avec le suivant. Va de (L1, angle+angle_par_pas) vers (L1, angle+angle_par_pas/2)
path.Line(self.L1, angle+self.angle_par_pas, self.L1, angle+self.angle_par_pas/2)
# Puis trait court de (L1+TraiteTraitCourt, angle+angle_par_pas/2) vers (L1-epaisseur, angle+angle_par_pas/2)
path.Line(self.L1+self.TailleTraitCourt, angle+self.angle_par_pas/2, self.L1-self.epaisseur, angle+self.angle_par_pas/2)
# Puis fait la dent inétrieur et va en (L1-epaisseur, angle)
path.LineTo(self.L1-self.epaisseur, angle)
# Puis trait court, va en (L1+TailleTraitCourt, angle)
path.LineTo(self.L1+self.TailleTraitCourt, angle)
path.GenPath()
def genere_element_1_debut(self, angle):
path = inkcape_polar(self.Offset, self.group)
# Commence par le pas de liaison avec le suivant
# Commence en (L1,0) et finit en (L1, angle/2)
path.Line(self.L1, 0, self.L1, angle/2)
#Se déplace pour se positionner à (self.L1+TailleTraitCourt, angle/2) et termine en (L1-epaisseur, angle /2)
path.Line(self.L1+self.TailleTraitCourt, angle/2, self.L1-self.epaisseur, angle/2)
#Puis trace la dent. Se déplace en (L1-epaisseur, angle*0.75)
path.LineTo(self.L1-self.epaisseur, angle*0.75)
# Puis bord complet : se déplace en (L2+epaisseur,angle*0.75)
path.LineTo(self.L2+self.epaisseur, angle*0.75)
# Puis dent externe, revient en (L2+epaisseur, angle/2)
path.LineTo(self.L2+self.epaisseur, angle*0.5)
# Puis trait de taille TailleTraitcourt --> va en (L2-TailleTraitCourt) avec angle/2
path.LineTo(self.L2-self.TailleTraitCourt, angle*0.5)
# Puis liaison vers suivant. Se déplace en (L2, taille_exacte_pas*0.5) avec angle/2 et va en (L2,0) avec angle 0
path.Line(self.L2, angle*0.5, self.L2, 0)
path.GenPath()
def genere_element_1_fin(self, angle):
path = inkcape_polar(self.Offset, self.group)
# Génère le dernier path, pour le dernier pas. Proche du cas normal, mais path plus complet, prend en compte la découpe du bord
# Par rapport au premier, pas de liaison avec le suivant !
# Commence par le trait court intérieur : Va de (L1+TailleTraitCourt, angle) vers (L1-epaisseur, angle)
path.Line(self.L1+self.TailleTraitCourt, angle, self.L1-self.epaisseur, angle)
# Puis la dent coté intérieur : Va en (L1-epaisseur, angle+angle_par_pas/4)
path.LineTo(self.L1-self.epaisseur, angle+self.angle_par_pas/4)
# Puis découpe complète du bord. Va en (L2+epaisseur, angle+angle_par_pas/4)
path.LineTo(self.L2+self.epaisseur, angle+self.angle_par_pas/4)
# Puis dent coté extérieur, va en (L2+epaisseur, angle)
path.LineTo(self.L2+self.epaisseur, angle)
# et enfin traitcourt, va en (L2-TailleTraitCOurt, angle)
path.LineTo(self.L2-self.TailleTraitCourt, angle)
path.GenPath()
def genere_element_2(self, angle):
path = inkcape_polar(self.Offset, self.group)
# Génère 2nd path, 2 traits entre bords des dents de l'interieur vers l'exterieur
# Se positionne en (L1 + TailleTraitCourt+2, angle) et va en ((L2+L1)/2-1, angle)
path.Line(self.L1+self.TailleTraitCourt+2, angle, (self.L2+self.L1)/2-1, angle)
# Se positionne en ((L2+L1)/2+1, angle) et va en (L2-TailleTraitCourt-2, angle)
path.Line((self.L2+self.L1)/2+1, angle, self.L2-self.TailleTraitCourt-2, angle)
path.GenPath()
def genere_element_3(self, angle):
path = inkcape_polar(self.Offset, self.group)
# Génère la dent, la liaison avec le suivant coté extérieur
# Commence en (L2-TailleTraitCourt, angle) et va en (L2+epaisseur, angle)
path.Line(self.L2-self.TailleTraitCourt, angle, self.L2+self.epaisseur, angle)
# Trace la dent, va en (L2+epaisseur, angle+angle_par_pas/2)
# Ajoute angle_par_pas / 2 à l'angle car ce sera la nouvelle origine
angle += self.angle_par_pas/2
path.LineTo(self.L2+self.epaisseur, angle)
# Revient vers l'intérieur en (L2-TailleTraitCourt, angle+angle_par_pas/2) mais c'est le nouvel angle
path.LineTo(self.L2-self.TailleTraitCourt, angle)
# Trace liaison avec le suivant. Début en (L2, nouvel_angle) fin en (L2, nouvel_angle+angle_par_pas/2)
path.Line(self.L2, angle, self.L2, angle+self.angle_par_pas/2)
path.GenPath()
def genere_element_4(self, angle):
path = inkcape_polar(self.Offset, self.group)
# Génère 2nd path, 2 traits entre bords des dents de l'extérieur vers l'intérieur
# Se positionne en (L2-TailleTraitCourt-2, angle+angle_par_pas/2) et va en ((L2+L1)/2+1, angle+angle_par_pas/2)
path.Line(self.L2-self.TailleTraitCourt-2, angle+self.angle_par_pas/2, (self.L2+self.L1)/2+1, angle+self.angle_par_pas/2)
# Se positionne en ((L2+L1)/2-1, angle+angle_par_pas/2) et va en (L1 + TailleTraitCourt+2, angle+angle_par_pas/2)
path.Line((self.L2+self.L1)/2-1, angle+self.angle_par_pas/2, self.L1+self.TailleTraitCourt+2, angle+self.angle_par_pas/2)
path.GenPath()
def genere_element_5(self, angle):
path = inkcape_polar(self.Offset, self.group)
# Génère path avec 3 traits longueur TailleTraitLong entre les dents externes
#Tous les angles de ce motifs sont incrénetés de angle_par_pas/4
angle += self.angle_par_pas/4
# Se positionne en (L1-epaisseur+1, angle) et va en (L1+TailleTraitLong-1)
path.Line(self.L1-self.epaisseur+1, angle, self.L1+self.TailleTraitLong-1, angle)
# Se positionne en (L1+TailleTraitLong+1, angle) et va en (L1+2*TailleTraitLong+1)
path.Line(self.L1+self.TailleTraitLong+1, angle, self.L1+2*self.TailleTraitLong+1, angle)
# Se positionne en (L2 - TailleTraitLong + 1 et va en L2+epaisseur-1)
path.Line(self.L2-self.TailleTraitLong+1, angle, self.L2+self.epaisseur-1, angle)
path.GenPath()
def genere_element_6(self, angle):
path = inkcape_polar(self.Offset, self.group)
#Tous les angles de ce motifs sont incrénetés de angle_par_pas*0.75
angle += self.angle_par_pas*0.75
# Se positionne en (L2-1, angle) et va en (L2-TailleTraitLong+1)
path.Line(self.L2-1, angle, self.L2-self.TailleTraitLong+1, angle)
# Se positionne en (L2 - TailleTraitLong-1, angle) et va en (L1+TailleTraitLong+1)
path.Line(self.L2-self.TailleTraitLong - 1, angle, self.L1+self.TailleTraitLong+1, angle)
# Se positionne en (L1+TailleTraitLong-1) et va en (L1+1,angle)
path.Line(self.L1+self.TailleTraitLong - 1, angle, self.L1+1, angle)
path.GenPath()
def genere_pas_debut(self):
# Génère les paths du premier pas, le bord est complètement coupé et son épaisseur est divisée par 2 (élement_1 début)
angle = -1*self.angle_par_pas
#Taille traits court et long premiere partie
self.genere_element_1_debut(angle)
self.genere_element_4(angle)
self.genere_element_6(angle)
def genere_pas_fin(self, index_pas_fin):
# Génère les paths du dernier pas, le bord est complètement coupé et son épaisseur est divisée par 2 (élement_1 fin)
angle = index_pas_fin*self.angle_par_pas
# Génère les deux traits courts entre les dents
self.genere_element_2(angle)
# Génère la dent et ferme le contour
self.genere_element_1_fin(angle)
def genere_pas(self, index_pas):
# Génère les paths d'un des pas du milieu. 6 paths sont créés
angle = self.angle_par_pas * index_pas
# Premier élément : dent intérieure
self.genere_element_1(angle)
# Second élément : 2 trait courts entre dents proche précédent
self.genere_element_2(angle)
# 3ème élément : dent extérieure
self.genere_element_3(angle)
# 4ème élément : 2 traits courts entre dents vers milieu
self.genere_element_4(angle)
# 5ème élément : 3 traits longs proche du précédent
self.genere_element_5(angle)
# 6ème élément : 3 traits longs vers suivant
self.genere_element_6(angle)
def GeneratePaths(self):
#Taille traits courts et longs entre les dents
self.TailleTraitCourt = (self.L2 - self.L1) / 6 - 1
self.TailleTraitLong = (self.L2 - self.L1- 2) / 3
pas_par_bloc = int(self.nombre_pas/self.nb_parts)
#genere les pas du "flex"
for i in range(self.nb_parts):
group = etree.SubElement(self.parent, 'g')
self.group = group
current_pas = 0
pas_pour_ce_bloc = pas_par_bloc
self.Offset = (self.OffsetX, self.OffsetY)
self.OffsetX += self.L2*2+ 5
if i == self.nb_parts - 1:
pas_pour_ce_bloc = self.nombre_pas - i * pas_par_bloc
self.genere_pas_debut()
while current_pas < pas_pour_ce_bloc:
self.genere_pas(current_pas)
current_pas += 1
self.genere_pas_fin(pas_pour_ce_bloc)
def gen_cercle(diametre, nombre_pas, epaisseur, xOffset, yOffset, parent):
group = etree.SubElement(parent, 'g')
angle_par_pas = 2 * math.pi / nombre_pas
#Rayons des cercle, avec et sans picots
r1 = diametre / 2
r2 = r1 + epaisseur
path = inkcape_polar((xOffset, yOffset), group)
path.MoveTo(r1, 0)
index_pas = 0
while index_pas < nombre_pas:
angle = index_pas * angle_par_pas
path.LineTo(r2, angle)
path.LineTo(r2, angle+angle_par_pas/2)
path.LineTo(r1, angle+angle_par_pas/2)
path.LineTo(r1, angle+angle_par_pas)
index_pas += 1
path.GenPath()
class BoxMakerConical(inkex.EffectExtension):
"""
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 circle diameter')
self.arg_parser.add_argument('--d2', type = float, default = 100.0, help = 'Large circle diameter')
self.arg_parser.add_argument('--zc', type = float, default = 50.0, help = 'Cone height')
self.arg_parser.add_argument('--nb_pieces', type = int, default = 1, help = '# pieces for cone')
self.arg_parser.add_argument('--inner_size', type = inkex.Boolean, default = True, help = 'Dimensions are internal')
try:
inkex.Effect.unittouu # unitouu has moved since Inkscape 0.91
except AttributeError:
try:
def unittouu(self, unit):
return inkex.unittouu(unit)
except AttributeError:
pass
def effect(self):
"""
Draws a conic box, based on provided parameters
"""
# input sanity check
error = False
if self.options.zc < 15:
inkex.errormsg('Error: 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.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
d1 = self.svg.unittouu(str(self.options.d1) + unit)
d2 = self.svg.unittouu(str(self.options.d2) + unit)
zc = self.svg.unittouu(str(self.options.zc) + unit)
nb_parts = self.options.nb_pieces
thickness = self.svg.unittouu(str(self.options.thickness) + unit)
#Si prend dimensions externes, corrige les tailles
if self.options.inner_size == False:
d1 -= 2*thickness
d2 -= 2*thickness
zc -= 2*thickness
svg = self.document.getroot()
docWidth = self.svg.unittouu(svg.get('width'))
docHeigh = self.svg.unittouu(svg.attrib['height'])
layer = etree.SubElement(svg, 'g')
layer.set(inkex.addNS('label', 'inkscape'), 'Conical Box')
layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
#Compute size of projection
h1 = math.sqrt(zc*zc + (d2-d1)*(d2-d1)/4)
L1 = d1 * h1 / (d2 - d1)
L2 = d2 * h1 / (d2 - d1)
alpha = math.pi*d2/L2
#calcul nombre de pas (sauf premeirs et derniers) pour D1, avec 2*2 mm par pas
nombre_pas = round((d1 * math.pi - 6) / 4)
#calcul angle par pas, ajoute 1.5 pour tenir compte des premiers et derniers pas qui font 3/4 de pas.
angle_par_pas = alpha / (nombre_pas+1.5*nb_parts)
taille_exacte_pas = math.pi * d1 / (nombre_pas+1.5*nb_parts)
# do not put elements right at the edge of the page.
# Drawing will max left will be L2*cos(alpha) - thickness
if alpha > math.pi:
xOffset = -L2 - thickness - 10
xmin = -L2 - thickness
xmax = L2 + thickness
else:
xOffset = L2 * math.cos(alpha) - thickness - 10
xmin = (L2+thickness) * math.cos(alpha)
xmax = L2 + thickness
if alpha > math.pi*1.5:
yOffset = -L2 - thickness - 10
ymin = -L2 - thickness
ymax = L2 + thickness
elif alpha > math.pi:
yOffset = (L2+thickness)*math.sin(alpha) - 10
ymin = (L2+thickness)*math.sin(alpha) - thickness
ymax = L2 + thickness
elif alpha > math.pi/2:
yOffset = 0
ymin = 0
ymax = L2 + thickness
else:
yOffset = 0
ymin = 0
ymax = (L2+thickness)*math.sin(alpha)
#dessine la partie "souple"
PartieSouple = CurvedSurface(L1, L2, nombre_pas, angle_par_pas, taille_exacte_pas, nb_parts, thickness, layer, xOffset, yOffset)
PartieSouple.GeneratePaths()
#génère maintenant le path du grand cercle
#Un pas de plus pour les cercles, pour tenir compte du début et de la fin
nombre_pas += 1
#Positionne Offset
gen_cercle(d2, nombre_pas, thickness, -xmax - d2/2 + xOffset + 10, yOffset - ymax - d2/2 - 10 , layer)
#puis pour le petit cercle
gen_cercle(d1, nombre_pas, thickness, -xmax - d1/2 + xOffset + 10, d1/2 + yOffset - ymin + 10, layer)
if __name__ == '__main__':
BoxMakerConical().run()