#!/usr/bin/env python # coding: utf8 # We will use the inkex module with the predefined Effect base class. import inkex # The simplestyle module provides functions for style parsing. import simplestyle import math objStyle = simplestyle.formatStyle( {'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} inkex.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, 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.Offset = (xOffset, yOffset) 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): group = inkex.etree.SubElement(self.parent, 'g') self.group = group #Taille traits courts et longs entre les dents self.TailleTraitCourt = (self.L2 - self.L1) / 6 - 1 self.TailleTraitLong = (self.L2 - self.L1- 2) / 3 #genere les pas du "flex" current_pas = 0 self.genere_pas_debut() while current_pas < self.nombre_pas: self.genere_pas(current_pas) current_pas += 1 self.genere_pas_fin(current_pas) def gen_cercle(diametre, nombre_pas, epaisseur, xOffset, yOffset, parent): group = inkex.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 ConicalBox(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.OptionParser.add_option('--unit', action = 'store', type = 'string', dest = 'unit', default = 'mm', help = 'Unit, should be one of ') self.OptionParser.add_option('--thickness', action = 'store', type = 'float', dest = 'thickness', default = '3.0', help = 'Material thickness') self.OptionParser.add_option('--d1', action = 'store', type = 'float', dest = 'd1', default = '50.0', help = 'Small circle diameter') self.OptionParser.add_option('--d2', action = 'store', type = 'float', dest = 'd2', default = '100.0', help = 'Large circle diameter') self.OptionParser.add_option('--zc', action = 'store', type = 'float', dest = 'zc', default = '50.0', help = 'Cone height') self.OptionParser.add_option('--inner_size', action = 'store', type = 'inkbool', dest = 'inner_size', 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.unittouu(str(self.options.d1) + unit) d2 = self.unittouu(str(self.options.d2) + unit) zc = self.unittouu(str(self.options.zc) + unit) thickness = self.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.unittouu(svg.get('width')) docHeigh = self.unittouu(svg.attrib['height']) layer = inkex.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) taille_exacte_pas = math.pi * d1 / (nombre_pas+1.5) # 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, 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) # Create effect instance and apply it. effect = ConicalBox() effect.affect()