diff --git a/extensions/fablabchemnitz/box_maker_conical/box_maker_conical.inx b/extensions/fablabchemnitz/box_maker_conical/box_maker_conical.inx
new file mode 100644
index 0000000..37d337b
--- /dev/null
+++ b/extensions/fablabchemnitz/box_maker_conical/box_maker_conical.inx
@@ -0,0 +1,30 @@
+
+
+ Box Maker - Conical
+ fablabchemnitz.de.box_maker_conical
+
+
+
+
+
+
+
+
+ 3.0
+ 100.0
+ 150.0
+ 50.0
+ 1
+ true
+
+ all
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/box_maker_conical/box_maker_conical.py b/extensions/fablabchemnitz/box_maker_conical/box_maker_conical.py
new file mode 100644
index 0000000..e7cfd8e
--- /dev/null
+++ b/extensions/fablabchemnitz/box_maker_conical/box_maker_conical.py
@@ -0,0 +1,353 @@
+#!/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()
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/box_maker_conical/meta.json b/extensions/fablabchemnitz/box_maker_conical/meta.json
new file mode 100644
index 0000000..4f0c542
--- /dev/null
+++ b/extensions/fablabchemnitz/box_maker_conical/meta.json
@@ -0,0 +1,21 @@
+[
+ {
+ "name": "Box Maker - Conical",
+ "id": "fablabchemnitz.de.box_maker_conical",
+ "path": "box_maker_conical",
+ "dependent_extensions": null,
+ "original_name": "Conical Box Maker",
+ "original_id": "fr.fablab-lannion.inkscape.conical_box",
+ "license": "GNU LGPL v3",
+ "license_url": "https://github.com/thierry7100/ConicBox/blob/master/LICENSE",
+ "comment": "successor of https://github.com/thierry7100/ConeFlex",
+ "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/box_maker_conical",
+ "fork_url": "https://github.com/thierry7100/ConicBox",
+ "documentation_url": "https://stadtfabrikanten.org/display/IFM/Box+Maker+-+Conical",
+ "inkscape_gallery_url": null,
+ "main_authors": [
+ "github.com/thierry7100",
+ "github.com/vmario89"
+ ]
+ }
+]
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/box_maker_elliptical_cone/.gitignore b/extensions/fablabchemnitz/box_maker_elliptical_cone/.gitignore
new file mode 100644
index 0000000..6b11214
--- /dev/null
+++ b/extensions/fablabchemnitz/box_maker_elliptical_cone/.gitignore
@@ -0,0 +1 @@
+/DebugEllConicBox.txt
diff --git a/extensions/fablabchemnitz/box_maker_elliptical_cone/box_maker_elliptical_cone.inx b/extensions/fablabchemnitz/box_maker_elliptical_cone/box_maker_elliptical_cone.inx
new file mode 100644
index 0000000..e3be1c6
--- /dev/null
+++ b/extensions/fablabchemnitz/box_maker_elliptical_cone/box_maker_elliptical_cone.inx
@@ -0,0 +1,33 @@
+
+
+ Box Maker - Elliptical Cone
+ fablabchemnitz.de.box_maker_elliptical_cone
+
+
+
+
+
+
+
+
+ 3.0
+ 60.0
+ 90.0
+ 0.5
+ 50.0
+ 2
+ 0
+ true
+ true
+
+ all
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/box_maker_elliptical_cone/box_maker_elliptical_cone.py b/extensions/fablabchemnitz/box_maker_elliptical_cone/box_maker_elliptical_cone.py
new file mode 100644
index 0000000..a7f77ae
--- /dev/null
+++ b/extensions/fablabchemnitz/box_maker_elliptical_cone/box_maker_elliptical_cone.py
@@ -0,0 +1,704 @@
+#!/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 BoxMakerEllipticalCone(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 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
+ self.fDebug = None
+ 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()
+
+if __name__ == '__main__':
+ BoxMakerEllipticalCone().run()
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/box_maker_elliptical_cone/meta.json b/extensions/fablabchemnitz/box_maker_elliptical_cone/meta.json
new file mode 100644
index 0000000..a18d43a
--- /dev/null
+++ b/extensions/fablabchemnitz/box_maker_elliptical_cone/meta.json
@@ -0,0 +1,21 @@
+[
+ {
+ "name": "Box Maker - Elliptical Cone",
+ "id": "fablabchemnitz.de.box_maker_elliptical_cone",
+ "path": "box_maker_elliptical_cone",
+ "dependent_extensions": null,
+ "original_name": "Elliptical cone box maker",
+ "original_id": "fr.fablab-lannion.inkscape.ell_conical_box",
+ "license": "MIT License",
+ "license_url": "https://github.com/thierry7100/ConeFlex/blob/master/LICENSE",
+ "comment": "",
+ "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/box_maker_elliptical_cone",
+ "fork_url": "https://github.com/thierry7100/ConeFlex",
+ "documentation_url": "https://stadtfabrikanten.org/display/IFM/Box+Maker+-+Elliptical+Cone",
+ "inkscape_gallery_url": null,
+ "main_authors": [
+ "github.com/thierry7100",
+ "github.com/vmario89"
+ ]
+ }
+]
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/box_maker_living_hinge/box_maker_living_hinge.inx b/extensions/fablabchemnitz/box_maker_living_hinge/box_maker_living_hinge.inx
new file mode 100644
index 0000000..d55bc49
--- /dev/null
+++ b/extensions/fablabchemnitz/box_maker_living_hinge/box_maker_living_hinge.inx
@@ -0,0 +1,51 @@
+
+
+ Box Maker - Living Hinge
+ fablabchemnitz.de.box_maker_living_hinge
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 40.0
+ 80.0
+ 50.0
+ 6.0
+
+
+
+
+ 3.0
+ 0.1
+ 0.01
+
+
+
+
+ 1.0
+ 2.0
+ 15.000
+
+ all
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/box_maker_living_hinge/box_maker_living_hinge.py b/extensions/fablabchemnitz/box_maker_living_hinge/box_maker_living_hinge.py
new file mode 100644
index 0000000..950b8e4
--- /dev/null
+++ b/extensions/fablabchemnitz/box_maker_living_hinge/box_maker_living_hinge.py
@@ -0,0 +1,511 @@
+#!/usr/bin/env python3
+'''
+Generates Inkscape SVG file containing box components needed to
+laser cut a tabbed construction box taking kerf and clearance into account
+
+Original Author -- 2011 elliot white elliot@twot.eu
+Forked -- 2013 Reid Borsuk reid.borsuk@live.com
+Updated for 0.91 2016 Maren Hachmann marenhachmann@yahoo.com
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+'''
+__version__ = "0.8rb"
+
+import inkex
+import math
+from lxml import etree
+import math
+
+def drawS(XYstring): # Draw lines from a list
+ name='part'
+ style = { 'stroke': '#000000', 'fill': 'none' }
+ drw = {'style': str(inkex.Style(style)),inkex.addNS('label','inkscape'):name,'d':XYstring}
+ etree.SubElement(parent, inkex.addNS('path','svg'), drw )
+ return
+
+def draw_SVG_ellipse(centerx, centery, radiusx, radiusy, start_end):
+
+ style = { 'stroke' : '#000000',
+ 'fill' : 'none' }
+ ell_attribs = {'style': str(inkex.Style(style)),
+ inkex.addNS('cx','sodipodi') :str(centerx),
+ inkex.addNS('cy','sodipodi') :str(centery),
+ inkex.addNS('rx','sodipodi') :str(radiusx),
+ inkex.addNS('ry','sodipodi') :str(radiusy),
+ inkex.addNS('start','sodipodi') :str(start_end[0]),
+ inkex.addNS('end','sodipodi') :str(start_end[1]),
+ inkex.addNS('open','sodipodi') :'true', #all ellipse sectors we will draw are open
+ inkex.addNS('type','sodipodi') :'arc',
+ 'transform' :''
+ }
+
+ ell = etree.SubElement(parent, inkex.addNS('path','svg'), ell_attribs )
+
+#draw an SVG line segment between the given (raw) points
+def draw_SVG_line( x1, y1, x2, y2, parent):
+ style = { 'stroke': '#000000', 'fill': 'none' }
+
+ line_attribs = {'style' : str(inkex.Style(style)),
+ 'd' : 'M '+str(x1)+','+str(y1)+' L '+str(x2)+','+str(y2)}
+
+ line = etree.SubElement(parent, inkex.addNS('path','svg'), line_attribs )
+
+def EllipseCircumference(a, 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.
+ """
+ import math
+ x, y = max(a, b), min(a, b)
+ digits = 53; tol = math.sqrt(math.pow(0.5, digits))
+ if digits * y < tol * x: return 4 * x
+ s = 0; m = 1
+ while x - y > tol * y:
+ x, y = 0.5 * (x + y), math.sqrt(x * y)
+ m *= 2; s += m * math.pow(x - y, 2)
+ return math.pi * (math.pow(a + b, 2) - s) / (x + y)
+
+"""
+Gives you a list of points that make up a box.
+
+Returns string suitable for input to drawS
+"""
+def box(sx, sy,ex, ey, leaveLeftSideOpen = False):
+ s=[]
+ s='M '+str(sx)+','+str(sy)+' '
+ s+='L '+str(ex)+','+str(sy)+' '
+ s+='L '+str(ex)+','+str(ey)+' '
+ s+='L '+str(sx)+','+str(ey)+' '
+ if not leaveLeftSideOpen:
+ s+='L '+str(sx)+','+str(sy)+' '
+ return s
+
+"""
+Side function is used to render any of the sides so needs all this functionality:
+ isLongSide -- long sides without tabs (for cover),
+ truncate -- partial sides for the elipse
+ gap -- extend the tabs on the curved side for ease of movement
+ 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
+would be in the same spot for opposing sides, instead of interleaved.
+
+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):
+ # root startOffset endOffset tabVec length direction isTab
+
+ #Long side length= length+((math.pi*(length/2))/4
+ tmpLength = 0
+ correctionLocal = correction
+ if gap:
+ correctionLocal = (correction)
+ if isLongSide > 0:
+ tmpLength = length
+ length = isLongSide
+
+ divs=int(length/nomTab) # divisions
+ if not divs%2: divs-=1 # make divs odd
+ if isLongSide < 0:
+ divs = 1
+
+ divs=float(divs)
+ tabs=(divs-1)/2 # tabs for side
+
+ if isLongSide < 0:
+ divs = 1
+ tabWidth = length
+ gapWidth = 0
+ elif equalTabs:
+ gapWidth=tabWidth=length/divs
+ else:
+ tabWidth=nomTab
+ gapWidth=(length-tabs*nomTab)/(divs-tabs)
+
+ if isTab: # kerf correction
+ gapWidth-=correctionLocal
+ tabWidth+=correctionLocal
+ first=correctionLocal/2
+ else:
+ gapWidth+=correctionLocal
+ tabWidth-=correctionLocal
+ first=-correctionLocal/2
+
+ s=[]
+ firstVec=0; secondVec=tabVec
+ if gap:
+ secondVec *= 2
+ dirxN=0 if dirx else 1 # used to select operation on x or y
+ diryN=0 if diry else 1
+ (Vx,Vy)=(rx+sox*thickness,ry+soy*thickness)
+ s='M '+str(Vx)+','+str(Vy)+' '
+
+ if dirxN: Vy=ry # set correct line start
+ if diryN: Vx=rx
+
+ if isLongSide > 0: #LongSide is a side without tabs for a portion.
+ length = tmpLength
+ divs=int((Z/2)/nomTab)
+ if not divs%2: divs-=1
+ divs = float(divs)
+
+ # generate line as tab or hole using:
+ # last co-ord:Vx,Vy ; tab dir:tabVec ; direction:dirx,diry ; thickness:thickness
+ # divisions:divs ; gap width:gapWidth ; tab width:tabWidth
+ for n in range(1,int(divs)):
+ if n%2:
+ Vx=Vx+dirx*gapWidth+dirxN*firstVec+first*dirx
+ Vy=Vy+diry*gapWidth+diryN*firstVec+first*diry
+ s+='L '+str(Vx)+','+str(Vy)+' '
+ Vx=Vx+dirxN*secondVec
+ Vy=Vy+diryN*secondVec
+ s+='L '+str(Vx)+','+str(Vy)+' '
+ else:
+ Vxs = Vx
+ Vys = Vy
+ Vx=Vx+dirx*tabWidth+dirxN*firstVec
+ Vy=Vy+diry*tabWidth+diryN*firstVec
+ s+='L '+str(Vx)+','+str(Vy)+' '
+ Vx=Vx+dirxN*secondVec
+ Vy=Vy+diryN*secondVec
+ s+='L '+str(Vx)+','+str(Vy)+' '
+ if thumbTab:
+ drawS(box(Vxs,Vys,Vx,Vy))
+ (secondVec,firstVec)=(-secondVec,-firstVec) # swap tab direction
+ first=0
+ if not truncate:
+ s+='L '+str(rx+eox*thickness+dirx*length)+','+str(ry+eoy*thickness+diry*length)+' '
+ else: #Truncate specifies that a side is incomplete in preperation for a curve
+ s+='L '+str(rx+eox*thickness+dirx*(length/2))+','+str(ry+eoy*thickness+diry*(length/2))+' '
+ return s
+
+#God class. Makes poor design, but not much object oriented in this guy...
+class BoxMakerLivingHinge(inkex.EffectExtension):
+
+ def add_arguments(self, pars):
+ pars.add_argument('--unit',default='mm',help='Measure Units')
+ pars.add_argument('--inside',type=int,default=0,help='Int/Ext Dimension')
+ pars.add_argument('--length',type=float,default=100,help='Length of Box')
+ pars.add_argument('--width',type=float,default=100,help='Width of Box')
+ pars.add_argument('--height',type=float,default=100,help='Height of Box')
+ pars.add_argument('--tab',type=float,default=25,help='Nominal Tab Width')
+ pars.add_argument('--equal',type=int,default=0,help='Equal/Prop Tabs')
+ pars.add_argument('--thickness',type=float,default=10,help='Thickness of Material')
+ pars.add_argument('--kerf',type=float,default=0.5,help='Kerf (width) of cut')
+ pars.add_argument('--clearance',type=float,default=0.01,help='Clearance of joints')
+ pars.add_argument('--style',type=int,default=25,help='Layout/Style')
+ pars.add_argument('--spacing',type=float,default=25,help='Part Spacing')
+ pars.add_argument('--hingeOpt',type=int,default=0,help='Hinge type')
+ pars.add_argument('--hingeThick',type=float,default=0,help='Hinge thickness')
+ pars.add_argument('--thumbTab',default=0,help='Add a thumb tab')
+
+ """
+ Traditional multi-slit design.
+ Sx, Sy : Start X, Y (pixels, not user units)
+ Ex, Ey : End X, Y (pixels, not user units)
+ space : gap between slots in the X direction, in user specified units (IE: wood between two rows of slots)
+ solidGap : gap between slots in the Y direction, in user specified units (IE: how much wood is left between 2 or 3 cuts)
+ """
+ def livingHinge2(self, Sx, Sy, Ex, Ey, space = 2, solidGap = 4):
+
+ space = self.svg.unittouu( str(space) + unit )
+ solidGap = self.svg.unittouu( str(solidGap) + unit )
+ Sy += thickness
+ Ey -= thickness
+
+ height = Ey - Sy
+
+ width = Ex - Sx
+ # inkex.utils.debug(width)
+ horizontalSlots = int(round(width / space))
+ # inkex.utils.debug(horizontalSlots)
+ if horizontalSlots % 2 and horizontalSlots != 1:
+ horizontalSlots-=1 # make it even so you end with an interior slot
+ # inkex.utils.debug(horizontalSlots)
+ space = width / horizontalSlots
+
+ grp_name = 'Living Hinge'
+ grp_attribs = {inkex.addNS('label','inkscape'):grp_name }
+ grp = etree.SubElement(parent, 'g', grp_attribs)#the group to put everything in
+
+ for n in range(0,horizontalSlots+1):
+ 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)
+ 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)
+
+ else:
+ #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)
+ 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.
+ Sx, Sy : Start X, Y (pixels, not user units)
+ Ex, Ey : End X, Y (pixels, not user units)
+ reverse : specifies the spin of the spiral (1 = outer spiral is counterclockwise, -1 otherwise)
+ space : gap between slots, in user specified units (IE: how thick the wood remainder is)
+ """
+ def livingHinge3(self, Sx, Sy, Ex, Ey, reverse = 1, space = 2):
+ space = self.svg.unittouu( str(space) + unit )
+
+ height = (Ey - Sy)
+
+ width = (Ex - Sx)
+
+ horizontalSlots = int(math.floor(height / (space)))
+ if not horizontalSlots%2: horizontalSlots-=1 # make it odd otherwise the below division will result in an outer cut too thin
+
+ space = (height / horizontalSlots)
+
+ horizontalSlots = int(round(horizontalSlots * 1/2)) #We do 2 passes per render, so divide slots requirement in half
+
+ grp_name = 'Living Hinge'
+ grp_attribs = {inkex.addNS('label','inkscape'):grp_name }
+ grp = etree.SubElement(parent, 'g', grp_attribs)#the group to put everything in
+
+ centerX = Sx + (width/2)
+ centerY = Sy + (height/2)
+
+ for n in range(0,horizontalSlots):
+ 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)
+ 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)
+
+ 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
+ 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.
+ Sx, Sy : Start X, Y (pixels, not user units)
+ Ex, Ey : End X, Y (pixels, not user units)
+ rotate : False means the traditional flexable design (cuts are prependuclar to long sides). True rotates 90 degrees.
+ mirror : mirror inverts the left and right slots, used for inverting during double design
+ space : gap between adjecent slots, in user specified units (IE: wood between two rows of slots, X if rotate is false, Y if true)
+ solidGap : gap between slot and edge, in user specified units (IE: how much wood is left between cut and edge, Y if rotate is false, X if true)
+ """
+ def livingHinge4(self, Sx, Sy, Ex, Ey, rotate = False, mirror = 0, space = 2, solidGap = 5):
+
+ space = self.svg.unittouu( str(space) + unit )
+ solidGap = self.svg.unittouu( str(solidGap) + unit )
+ Sy += thickness
+ Ey -= thickness
+
+ height = Ey - Sy
+ width = Ex - Sx
+
+ if not rotate:
+ horizontalSlots = int(round(width / space))
+ space = width / horizontalSlots
+ skew = 1 #Paint extra lines at the start and end because in this direction there are no existing lines already
+ else:
+ horizontalSlots = int(round(height / space))
+ if not horizontalSlots%2: horizontalSlots-=1 #make sure we always end on the same side, otherwise we'll cut off the last tooh
+ space = height / horizontalSlots
+ skew = 0 #Don't paint the first and last lines, as they're on the cut already, and double cuts on a laser are messy
+
+ grp_name = 'Living Hinge'
+ grp_attribs = {inkex.addNS('label','inkscape'):grp_name }
+ grp = etree.SubElement(parent, 'g', grp_attribs)#the group to put everything in
+
+ for n in range(1 - skew,horizontalSlots + skew):
+ if not rotate:
+ if (n+mirror)%2:
+ draw_SVG_line(Sx + (space * n), Sy + solidGap, Sx + (space * n), Ey, grp)
+ else:
+ draw_SVG_line(Sx + (space * n), Sy, Sx + (space * n), Ey - solidGap, grp)
+ else:
+ if (n+mirror)%2:
+ draw_SVG_line(Sx + solidGap, Sy + (space * n), Ex, Sy + (space * n), grp)
+ else:
+ draw_SVG_line(Sx, Sy + (space * n), Ex - solidGap, Sy + (space * n), grp)
+ if rotate and not mirror:
+ draw_SVG_line(Sx, Sy, Sx, Ey - space, grp)
+ draw_SVG_line(Ex, Sy + space, Ex, Ey, grp)
+ elif mirror:
+ draw_SVG_line(Sx, Sy + space, Sx, Ey, grp)
+ draw_SVG_line(Ex, Sy, Ex, Ey - space, grp)
+
+ def effect(self):
+ global parent,nomTab,equalTabs,thickness,correction, Z, unit
+
+ # Get access to main SVG document element and get its dimensions.
+ svg = self.document.getroot()
+
+ # Get the attibutes:
+ widthDoc = self.svg.unittouu(svg.get('width'))
+ heightDoc = self.svg.unittouu(svg.get('height'))
+
+ # Create a new layer.
+ layer = etree.SubElement(svg, 'g')
+ layer.set(inkex.addNS('label', 'inkscape'), 'newlayer')
+ layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
+
+ parent=self.svg.get_current_layer()
+
+ # Get script's option values.
+ unit=self.options.unit
+ inside=self.options.inside
+ X = self.svg.unittouu( str(self.options.length) + unit )
+ Y = self.svg.unittouu( str(self.options.width) + unit )
+ Z = self.svg.unittouu( str(self.options.height) + unit )
+ thickness = self.svg.unittouu( str(self.options.thickness) + unit )
+ nomTab = self.svg.unittouu( str(self.options.tab) + unit )
+ equalTabs=self.options.equal
+ kerf = self.svg.unittouu( str(self.options.kerf) + unit )
+ clearance = self.svg.unittouu( str(self.options.clearance) + unit )
+ layout=self.options.style
+ spacing = self.svg.unittouu( str(self.options.spacing) + unit )
+ ring = 1
+ hingeOpt = self.options.hingeOpt
+ hingeThick = self.options.hingeThick
+ thumbTab = self.options.thumbTab
+
+ if inside: # if inside dimension selected correct values to outside dimension
+ X+=thickness*2
+ Y+=thickness*2
+ Z+=thickness*2
+
+ correction=kerf-clearance
+
+ # check input values mainly to avoid python errors
+ # TODO restrict values to *correct* solutions
+ # TODO -- Do what the origial author suggested I do. QUALITY!
+ error=0
+
+ if min(X,Y,Z)==0:
+ inkex.errormsg('Error: Dimensions must be non zero')
+ error=1
+ if max(X,Y,Z)>max(widthDoc,heightDoc)*10: # crude test
+ inkex.errormsg('Error: Dimensions Too Large')
+ error=1
+ if min(X,Y,Z)<3*nomTab:
+ inkex.errormsg('Error: Tab size too large')
+ error=1
+ if nomTabmin(X,Y,Z)/3: # crude test
+ inkex.errormsg('Error: Material too thick')
+ error=1
+ if correction>min(X,Y,Z)/3: # crude test
+ inkex.errormsg('Error: Kerf/Clearence too large')
+ error=1
+ if spacing>max(X,Y,Z)*10: # crude test
+ inkex.errormsg('Error: Spacing too large')
+ error=1
+ if spacing 0=holes 1=tabs
+ if layout==0: # Diagramatic Layout TRBL
+ pieces=[ #center low row
+ [(2,0,0,1),(3,0,1,1),X,Z,0b1000,-2],
+ #left middle row
+ [(1,0,0,0),(2,0,0,1),Z,Y,0b1111,0],
+ #center middle row
+ [(2,0,0,1),(2,0,0,1),X,Y,0b0000,0],
+ #right middle row
+ [(3,1,0,1),(2,0,0,1),Z+(EllipseCircumference(X/2, Z/2)/4)+thickness,Y,0b1011,1],
+ #center top row
+ [(2,0,0,1),(1,0,0,0),X,Z,0b0010,-1]]
+ elif layout==1: # Inline(compact) Layout
+ pieces=[#Base
+ [(1,0,0,0),(1,0,0,0),X,Y,0b0000,0],
+ #Front panel
+ [(2,1,0,0),(1,0,0,0),Z,Y,0b1111,0],
+ #Sides with curves
+ [(3,1,0,1),(1,0,0,0),X,Z,0b1000,-2],
+ [(4,2,0,1),(1,0,0,0),X,Z,0b0010,-1],
+ #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
+ (xs,xx,xy,xz)=piece[0]
+ (ys,yx,yy,yz)=piece[1]
+ x=xs*spacing+xx*X+xy*Y+xz*Z # root x co-ord for piece
+ y=ys*spacing+yx*X+yy*Y+yz*Z # root y co-ord for piece
+ dx=piece[2]
+ dy=piece[3]
+ tabs=piece[4]
+ a=tabs>>3&1; b=tabs>>2&1; c=tabs>>1&1; d=tabs&1 # extract tab status for each side. It's a nasty packed binary flag format, but I'm not fixing it now.
+ longSide = 0
+ shortSide = 0
+ skew = 0
+
+ if piece[5] == 1:
+ longSide = Z
+ elif piece[5] < 0:
+ shortSide = Z
+
+ # generate and draw the sides of each piece
+ 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)
+ 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
+
+ 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
+ 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
+ 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
+ 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
+ drawS(box(x+dx+skew,y+thickness,x+dx+skew+self.svg.unittouu( thumbTab + unit ),y+dy-thickness, True))
+ 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
+
+
+ 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)
+ 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
+
+ drawS(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:
+ 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 hingeOpt == 0: #Traditional parallel slit
+ self.livingHinge2(x+(Z/2), y, x+(Z/2)+(EllipseCircumference(X/2, Z/2)/4), y + (dy), hingeThick)
+ elif hingeOpt == 1: #Single spiral
+ if not inside:
+ self.livingHinge3(x+(Z/2), y+thickness, x+(Z/2)+(EllipseCircumference(X/2, Z/2)/4), y + dy - thickness, 1, hingeThick)
+ else:
+ self.livingHinge3(x+(Z/2), y + 2*thickness, x+(Z/2)+(EllipseCircumference(X/2, Z/2)/4), y + dy - 2*thickness, 1, hingeThick)
+
+ 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+(dy/2), x+(Z/2)+(EllipseCircumference(X/2, Z/2)/4), y + dy - thickness, -1, hingeThick)
+ 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)
+ 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 + (dy/2) - thickness, (x+(Z/2)+(EllipseCircumference(X/2, Z/2)/4)), y + dy, True, 1, hingeThick)
+
+if __name__ == '__main__':
+ BoxMakerLivingHinge().run()
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/box_maker_living_hinge/meta.json b/extensions/fablabchemnitz/box_maker_living_hinge/meta.json
new file mode 100644
index 0000000..8b67ca7
--- /dev/null
+++ b/extensions/fablabchemnitz/box_maker_living_hinge/meta.json
@@ -0,0 +1,22 @@
+[
+ {
+ "name": "Box Maker - Living Hinge",
+ "id": "fablabchemnitz.de.box_maker_living_hinge",
+ "path": "box_maker_living_hinge",
+ "dependent_extensions": null,
+ "original_name": "",
+ "original_id": "eu.twot.render.livinghinge",
+ "license": "GNU GPL v3",
+ "license_url": "https://www.reidb.net/code/LICENSE.txt",
+ "comment": "",
+ "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/box_maker_living_hinge",
+ "fork_url": "https://www.reidb.net/code/LivingHinge.zip",
+ "documentation_url": "https://stadtfabrikanten.org/display/IFM/Box+Maker+-+Living+Hinge",
+ "inkscape_gallery_url": null,
+ "main_authors": [
+ "Elliot White:elliot@twot.eu",
+ "reidb.net/Reid Borsuk:reid.borsuk@live.com",
+ "github.com/vmario89"
+ ]
+ }
+]
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/box_maker_mehr_boxes/box_maker_mehr_boxes.inx b/extensions/fablabchemnitz/box_maker_mehr_boxes/box_maker_mehr_boxes.inx
new file mode 100644
index 0000000..d839e24
--- /dev/null
+++ b/extensions/fablabchemnitz/box_maker_mehr_boxes/box_maker_mehr_boxes.inx
@@ -0,0 +1,74 @@
+
+
+ Box Maker - Mehr Boxes
+ fablabchemnitz.de.box_maker_mehr_boxes
+
+
+
+
+
+
+
+
+
+
+
+
+ 100.0
+ 100.0
+ 100.0
+
+
+
+
+
+ 4.0
+ 3
+ 3
+ 3
+
+ 4.0
+ 0.2
+ 1.0
+
+
+ true
+ true
+ true
+ true
+ true
+ true
+
+
+
+ 1
+
+
+
+
+
+ 20.0;40.0
+ false
+
+ 1
+
+
+
+
+
+ 20.0;40.0
+ false
+
+
+
+ all
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/box_maker_mehr_boxes/box_maker_mehr_boxes.py b/extensions/fablabchemnitz/box_maker_mehr_boxes/box_maker_mehr_boxes.py
new file mode 100644
index 0000000..0471468
--- /dev/null
+++ b/extensions/fablabchemnitz/box_maker_mehr_boxes/box_maker_mehr_boxes.py
@@ -0,0 +1,188 @@
+#!/usr/bin/env python3
+'''
+Generates Inkscape SVG file containing box components needed to
+laser cut a tabbed construction box taking kerf into account
+
+Copyright (C) 2018 Thore Mehr thore.mehr@gmail.com
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+'''
+__version__ = "1.0" ### please report bugs, suggestions etc to bugs@twot.eu ###
+
+import math
+import inkex
+import mehr_plate
+
+class mehr_box_maker(inkex.EffectExtension):
+
+ def add_arguments(self, pars):
+ pars.add_argument('--page',default='page_1')
+ pars.add_argument('--unit',default='mm')
+ pars.add_argument('--inside')
+ pars.add_argument('--X_size',type=float,default='0.0')
+ pars.add_argument('--Y_size',type=float,default='0.0')
+ pars.add_argument('--Z_size',type=float,default='0.0')
+ pars.add_argument('--tab_mode',default='number')
+ pars.add_argument('--tab_size',type=float,default='0.0')
+ pars.add_argument('--X_tabs',type=int,default='0')
+ pars.add_argument('--Y_tabs',type=int,default='0')
+ pars.add_argument('--Z_tabs',type=int,default='0')
+ pars.add_argument('--d_top',type=inkex.Boolean,default=True)
+ pars.add_argument('--d_bottom',type=inkex.Boolean,default=True)
+ pars.add_argument('--d_left',type=inkex.Boolean,default=True)
+ pars.add_argument('--d_right',type=inkex.Boolean,default=True)
+ pars.add_argument('--d_front',type=inkex.Boolean,default=True)
+ pars.add_argument('--d_back',type=inkex.Boolean,default=True)
+ pars.add_argument('--thickness',type=float,default=4,help='Thickness of Material')
+ pars.add_argument('--kerf',type=float,default=0.2)
+ pars.add_argument('--spaceing',type=float,default=1)
+ pars.add_argument('--X_compartments',type=int,default=1)
+ pars.add_argument('--X_divisions')
+ pars.add_argument('--X_mode')
+ pars.add_argument('--X_fit',type=inkex.Boolean)
+ pars.add_argument('--Y_compartments',type=int,default=1)
+ pars.add_argument('--Y_divisions')
+ pars.add_argument('--Y_mode')
+ pars.add_argument('--Y_fit',type=inkex.Boolean)
+
+ def effect(self):
+ thickness=self.svg.unittouu(str(self.options.thickness)+self.options.unit)
+ kerf=self.svg.unittouu(str(self.options.kerf)+self.options.unit)/2#kerf is diameter in UI and radius in lib
+
+ spaceing=self.svg.unittouu(str(self.options.spaceing)+self.options.unit)
+ XYZ=[self.svg.unittouu(str(self.options.X_size)+self.options.unit),self.svg.unittouu(str(self.options.Y_size)+self.options.unit),self.svg.unittouu(str(self.options.Z_size)+self.options.unit)]
+
+ if(self.options.inside=='0'):#if the sizes are outside sizes reduce the size by thickness if the side gets drawn
+ draw=(self.options.d_left,self.options.d_front,self.options.d_top,self.options.d_right,self.options.d_back,self.options.d_bottom)#order in XYZXYZ
+ for i in range(6):
+ XYZ[i%3]-=(thickness if draw[i] else 0)#remove a thickness if drawn
+
+#compartments on the X axis, devisons in Y direction
+ X_divisions_distances=[]
+ if (self.options.X_compartments>1):
+ if (self.options.X_mode=='even'):#spliting in even compartments
+ X_divisions_distances=[((XYZ[0])-(self.options.X_compartments-1)*(thickness))/self.options.X_compartments]
+ else:
+ for dist in self.options.X_divisions.replace(",",".").split(";"):#fixing seperator, spliting string
+ X_divisions_distances+=[float(self.svg.unittouu(dist+self.options.unit))]#translate into universal units
+ X_divisions_distances[0]+=kerf#fixing for kerf
+ if self.options.X_mode!='absolut':#for even and relative fix list lenght and offset compartments to absolut distances
+ while (len(X_divisions_distances)XYZ[0])and not self.options.X_fit:
+ inkex.errormsg("X Axis compartments outside of plate")
+ if self.options.X_fit:
+ XYZ[0]=X_divisions_distances[-1]-kerf
+ X_divisions_distances=X_divisions_distances[0:-1]#cutting the last of
+
+ Y_divisions_distances=[]
+ if (self.options.Y_compartments>1):
+ if (self.options.Y_mode=='even'):#spliting in even compartments
+ Y_divisions_distances=[((XYZ[1])-(self.options.Y_compartments-1)*(thickness))/self.options.Y_compartments]
+ else:
+ for dist in self.options.Y_divisions.replace(",",".").split(";"):#fixing seperator, spliting string
+ Y_divisions_distances+=[float(self.svg.unittouu(dist+self.options.unit))]#translate into universal units
+ Y_divisions_distances[0]+=kerf#fixing for kerf
+ if self.options.Y_mode!='absolut':#for even and relative fix list lenght and offset compartments to absolut distances
+ while (len(Y_divisions_distances)XYZ[1])and not self.options.X_fit:
+ inkex.errormsg("Y Axis compartments outside of plate")
+ if self.options.Y_fit:
+ XYZ[1]=Y_divisions_distances[-1]-kerf
+ Y_divisions_distances=Y_divisions_distances[0:-1]#cutting the last of
+
+ if (self.options.tab_mode=='number'):#fixed number of tabs
+ Tabs_XYZ=[self.options.X_tabs,self.options.Y_tabs,self.options.Z_tabs]
+ else:#compute apropriate number of tabs for the edges
+ tab_size=float(self.svg.unittouu(str(self.options.tab_size)+self.options.unit))
+ Tabs_XYZ=[max(1,int(XYZ[0]/(tab_size))/2),max(1,int(XYZ[1]/(tab_size))/2),max(1,int(XYZ[2]/(tab_size))/2)]
+
+#top and bottom plate
+ tabs_tb=(Tabs_XYZ[0] if self.options.d_back else 0,Tabs_XYZ[1] if self.options.d_right else 0,Tabs_XYZ[0] if self.options.d_front else 0,Tabs_XYZ[1] if self.options.d_left else 0)
+ start_tb=(True if self.options.d_back else False,True if self.options.d_right else False,True if self.options.d_front else False,True if self.options.d_left else False)
+ Plate_tb=mehr_plate.Mehr_plate((XYZ[0],XYZ[1]),tabs_tb,start_tb,thickness,kerf)#top and bottom plate
+ for d in X_divisions_distances:
+ Plate_tb.add_holes('Y',d,Tabs_XYZ[1])
+ for d in Y_divisions_distances:
+ Plate_tb.add_holes('X',d,Tabs_XYZ[0])
+#left and right plate
+ tabs_lr=(Tabs_XYZ[2] if self.options.d_back else 0,Tabs_XYZ[1] if self.options.d_top else 0,Tabs_XYZ[2] if self.options.d_front else 0,Tabs_XYZ[1] if self.options.d_bottom else 0)
+ start_lr=(True if self.options.d_back else False,False,True if self.options.d_front else False,False)
+ Plate_lr=mehr_plate.Mehr_plate((XYZ[2],XYZ[1]),tabs_lr,start_lr,thickness,kerf)#left and right plate
+ for d in Y_divisions_distances:
+ Plate_lr.add_holes('X',d,Tabs_XYZ[2])
+#front and back plate
+ tabs_fb=(Tabs_XYZ[0] if self.options.d_top else 0,Tabs_XYZ[2] if self.options.d_right else 0,Tabs_XYZ[0] if self.options.d_bottom else 0,Tabs_XYZ[2] if self.options.d_left else 0)#
+ start_fb=(False,False,False,False)
+ Plate_fb=mehr_plate.Mehr_plate((XYZ[0],XYZ[2]),tabs_fb,start_fb,thickness,kerf)#font and back plate
+ for d in X_divisions_distances:
+ Plate_fb.add_holes('Y',d,Tabs_XYZ[2])
+
+ Plate_xc=mehr_plate.Mehr_plate((XYZ[2],XYZ[1]),tabs_lr,(False,False,False,False),thickness,kerf)
+ for d in Y_divisions_distances:
+ Plate_xc.holes+=[Plate_xc.rect([0,Plate_xc.corner_offset[1]+d+kerf],[Plate_xc.AABB[0]/2-kerf,thickness-2*kerf])]
+
+ Plate_yc=mehr_plate.Mehr_plate((XYZ[0],XYZ[2]),tabs_fb,(False,False,False,False),thickness,kerf)
+ for d in X_divisions_distances:
+ Plate_yc.holes+=[Plate_yc.rect([Plate_yc.corner_offset[0]+d+kerf,0],[thickness-2*kerf,Plate_yc.AABB[1]/2-kerf])]
+
+
+ X_offset=0
+ Y_offset=0
+ if(self.options.d_top):
+ Plate_tb.draw([X_offset+spaceing,spaceing],["#000000","#ff0000"],self.svg.get_current_layer())#drawing a plate using black for the outline and red for holes
+ X_offset+=Plate_tb.AABB[0]+spaceing
+ Y_offset=max(Y_offset,Plate_tb.AABB[1])
+ if(self.options.d_bottom):
+ Plate_tb.draw([X_offset+spaceing,spaceing],["#000000","#ff0000"],self.svg.get_current_layer())
+ X_offset+=Plate_tb.AABB[0]+spaceing
+ Y_offset=max(Y_offset,Plate_tb.AABB[1])
+
+ if(self.options.d_left):
+ Plate_lr.draw([X_offset+spaceing,spaceing],["#000000","#ff0000"],self.svg.get_current_layer())
+ X_offset+=Plate_lr.AABB[0]+spaceing
+ Y_offset=max(Y_offset,Plate_lr.AABB[1])
+ if(self.options.d_right):
+ Plate_lr.draw([X_offset+spaceing,spaceing],["#000000","#ff0000"],self.svg.get_current_layer())
+ X_offset+=Plate_lr.AABB[0]+spaceing
+ Y_offset=max(Y_offset,Plate_lr.AABB[1])
+
+ if(self.options.d_front):
+ Plate_fb.draw([X_offset+spaceing,spaceing],["#000000","#ff0000"],self.svg.get_current_layer())
+ X_offset+=Plate_fb.AABB[0]+spaceing
+ Y_offset=max(Y_offset,Plate_fb.AABB[1])
+ if(self.options.d_back):
+ Plate_fb.draw([X_offset+spaceing,spaceing],["#000000","#ff0000"],self.svg.get_current_layer())
+ X_offset+=Plate_fb.AABB[0]+spaceing
+ Y_offset=max(Y_offset,Plate_fb.AABB[1])
+ X_offset=0
+ for i in range(self.options.X_compartments-1):
+ Plate_xc.draw([X_offset+spaceing,spaceing+Y_offset],["#000000","#ff0000"],self.svg.get_current_layer())
+ X_offset+=Plate_xc.AABB[0]+spaceing
+ X_offset=0
+ Y_offset+=spaceing+Plate_xc.AABB[1]
+ for i in range(self.options.Y_compartments-1):
+ Plate_yc.draw([X_offset+spaceing,spaceing+Y_offset],["#000000","#ff0000"],self.svg.get_current_layer())
+ X_offset+=Plate_yc.AABB[0]+spaceing
+
+if __name__ == '__main__':
+ mehr_box_maker().run()
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/box_maker_mehr_boxes/mehr_plate.py b/extensions/fablabchemnitz/box_maker_mehr_boxes/mehr_plate.py
new file mode 100644
index 0000000..86adf1b
--- /dev/null
+++ b/extensions/fablabchemnitz/box_maker_mehr_boxes/mehr_plate.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python3
+__version__ = "1.0"
+import inkex, math
+from lxml import etree
+
+class Mehr_plate():
+ def __init__(self,size,tabs,starts,thickness,kerf):
+ #general note svg pos x is right, pos y is down
+ self.size=size#(X,Y) inner size
+ self.tabs=tabs#number of tabs (top,right,bottom,left)
+ self.starts=starts#4 elements array of boolean if true start=high (top,right,bottom,left)
+ self.kerf=kerf#number, beam radius
+ self.thickness=thickness#number
+ self.holes=[]#list of SVG Strings
+ self.offset=[0.0 if self.starts[3] or self.tabs[3]==0 else self.thickness,0.0 if self.starts[0] or self.tabs[0]==0 else self.thickness]#set global offset so that AABB is at position (0,0)
+ self.corner_offset=[0.0 if (self.tabs[3]==0 and not self.starts[3]) else self.thickness,0.0 if (self.tabs[0]==0 and not self.starts[0]) else self.thickness]
+ self.AABB=[self.size[0]+(0.0 if (self.tabs[1]==0 and not self.starts[1]) else self.thickness)+self.corner_offset[0]+2*self.kerf,
+ self.size[1]+(0.0 if (self.tabs[2]==0 and not self.starts[2]) else self.thickness)+self.corner_offset[1]+2*self.kerf]
+
+ points=[]
+ for i in range(4):
+ points+=self.rotate(self.side(i),i*math.pi/2)#creating the points of the four sides and rotate them in the radiant system
+ self.SVG_String=self.to_SVG_String(points)
+ #self.AABB=[self.size[0]+2*self.thickness,0]
+
+ def side(self,index): #creating a side as a list of relative points. total lenght should be side lenght+2*kerf.
+ points=[[2*self.kerf,0.0]] if not self.starts[index] else [] #if this one starts low add two kerf
+ if (self.starts[(index-1)%4]):#if the privious one ended high add a thickness
+ points+=[[self.thickness,0.0]]
+ if self.tabs[index]>0:
+ parts=self.tabs[index]*2+1#number of parts= number of tabs+number of spaces between tabs +1
+ tab_state=self.starts[index]#makes a high part if true, low else
+ for i in range(parts):#creates the side
+ points+=[[(self.size[(index%2)]/parts)+2*self.kerf,0.0]] if (tab_state) else [[(self.size[index%2]/parts)-2*self.kerf,0.0]]#a longer part for tabs and a shorter one for the spaces in between
+ if not (i==parts-1):
+ points+=[[0.0,self.thickness]] if (tab_state) else [[0.0,-self.thickness]]# if high go down else go up
+ tab_state=not tab_state #invert tab_state
+ else:
+ points+=[[self.size[index%2]+2*self.kerf,0.0]] if (self.starts[index]) else [[self.size[index%2]-2*self.kerf,0.0]]#single line if there are no tabs
+ if(self.starts[(index+1)%len(self.starts)]):#if the next one starts high add a thickness
+ points+=[[self.thickness,0.0]]
+ if not self.starts[index]:#if this one starts and so also ends low add 2 kerf
+ points+=[[2*self.kerf,0.0]]
+ return points
+
+ def to_SVG_String(self,PointList):#creates a SVG_String as connected points from list so that AABB upper left corner is at 0,0
+ s="M"+str(self.offset[0])+','+str(self.offset[1])
+ for i in range(len(PointList)):
+ s+="l"+str(PointList[i][0])+","+str(PointList[i][1])
+ return s
+
+ def rotate(self,pointlist,angle):#rotate all points by angle in 2*pi system
+ matrix=[[math.cos(angle),-math.sin(angle)],[math.sin(angle),math.cos(angle)]]
+ ret=[]
+ for i in range(len(pointlist)):
+ ret+=[[pointlist[i][0]*matrix[0][0]+pointlist[i][1]*matrix[0][1],pointlist[i][0]*matrix[1][0]+pointlist[i][1]*matrix[1][1]]]
+ return ret
+
+ def rect(self,pos,size,center=[False,False]):#SVG_String for a rectangle
+ SVG_String="M"+str(pos[0]-((size[0]/2)if center[0]else 0))+','+str(pos[1]-((size[1]/2)if center[1]else 0))
+ SVG_String+='h'+str(size[0])+'v'+str(size[1])+'h'+str(-size[0])+'z'
+ return SVG_String
+
+ def add_holes(self,direction,position,number_of,center=False):
+ SVG_String=""
+ side=self.size[0] if direction=='X' else self.size[1]#geting size of the relevant side
+ hole_offset=(side/(2*number_of+1))#offset each hole
+ holesize=[hole_offset-2*self.kerf,self.thickness-2*self.kerf]#size of the holes
+ for i in range(number_of):#d=(2*i+1)*hole_offset+self.kerf
+ if direction=='X':
+ SVG_String+=self.rect([self.corner_offset[0]+self.kerf+(2*i+1)*hole_offset+self.kerf,self.corner_offset[1]+self.kerf+position],holesize,[False,center])
+ else:
+ SVG_String+=self.rect([self.corner_offset[0]+self.kerf+position,(2*i+1)*hole_offset+self.kerf+self.corner_offset[1]+self.kerf],holesize[::-1],[center,False])#reversed axis
+# inkex.errormsg(SVG_String)
+ self.holes+=[SVG_String]
+
+ def draw(self,position,colors,parent):
+ if len(self.holes)>0:#creating a new group if there are any holes to be drawn
+ grp_name = 'Group'
+ grp_attribs = {inkex.addNS('label','inkscape'):grp_name}
+ parent = etree.SubElement(parent, 'g', grp_attribs)#the group to put everything in
+ for s in self.holes:
+ self.draw_SVG_String(s,colors[1],parent,position)#drawing the holes if there are any
+ self.draw_SVG_String(self.SVG_String,colors[0],parent,position)#drawing the plate
+
+ def draw_SVG_String(self,SVG_String,color,parent,position=(0,0)):# Adding an SVG_String to the drawing
+ name='part'
+ transform='translate('+str(position[0])+','+str(position[1])+')'
+ style = { 'stroke': color, 'fill': 'none','stroke-width':str(max(self.kerf*2,0.2))}
+ drw = {'style':str(inkex.Style(style)),'transform':transform, inkex.addNS('label','inkscape'):name,'d':SVG_String}
+ etree.SubElement(parent, inkex.addNS('path','svg'), drw )
+ return
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/box_maker_mehr_boxes/meta.json b/extensions/fablabchemnitz/box_maker_mehr_boxes/meta.json
new file mode 100644
index 0000000..8f2fcfa
--- /dev/null
+++ b/extensions/fablabchemnitz/box_maker_mehr_boxes/meta.json
@@ -0,0 +1,21 @@
+[
+ {
+ "name": "Box Maker - Mehr Boxes",
+ "id": "fablabchemnitz.de.box_maker_mehr_boxes",
+ "path": "box_maker_mehr_boxes",
+ "dependent_extensions": null,
+ "original_name": "Mehr Box Maker",
+ "original_id": "de.mehr.laser.mehr_box_maker",
+ "license": "GNU GPL v3",
+ "license_url": "https://github.com/ThoreMehr/Mehr_BoxMaker/blob/master/LICENSE",
+ "comment": "",
+ "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/box_maker_mehr_boxes",
+ "fork_url": "https://github.com/ThoreMehr/Mehr_BoxMaker",
+ "documentation_url": "https://stadtfabrikanten.org/display/IFM/Box+Maker+-+Mehr+Boxes",
+ "inkscape_gallery_url": null,
+ "main_authors": [
+ "github.com/ThoreMehr",
+ "github.com/vmario89"
+ ]
+ }
+]
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/box_maker_tabbed/box_maker_tabbed.inx b/extensions/fablabchemnitz/box_maker_tabbed/box_maker_tabbed.inx
new file mode 100644
index 0000000..17de5c2
--- /dev/null
+++ b/extensions/fablabchemnitz/box_maker_tabbed/box_maker_tabbed.inx
@@ -0,0 +1,92 @@
+
+
+ Box Maker - Tabbed
+ fablabchemnitz.de.boxmaker_tabbed.box_maker_tabbed
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 180
+ 240
+ 50
+
+
+
+
+
+
+
+ 3.0
+
+
+
+
+
+
+
+
+
+ 0.0
+ 0.0
+
+
+
+
+
+
+
+
+
+
+
+ 3.0
+ 0.1
+ 0.01
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2
+ 3
+
+
+
+
+
+
+ 1.0
+
+
+
+ path
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/box_maker_tabbed/box_maker_tabbed.py b/extensions/fablabchemnitz/box_maker_tabbed/box_maker_tabbed.py
new file mode 100644
index 0000000..05d0ccd
--- /dev/null
+++ b/extensions/fablabchemnitz/box_maker_tabbed/box_maker_tabbed.py
@@ -0,0 +1,712 @@
+#! /usr/bin/env python3
+'''
+Generates Inkscape SVG file containing box components needed to
+CNC (laser/mill) cut a box with tabbed joints taking kerf and clearance into account
+
+Original Tabbed Box Maker Copyright (C) 2011 elliot white
+
+Changelog:
+19/12/2014 Paul Hutchison:
+ - Ability to generate 6, 5, 4, 3 or 2-panel cutouts
+ - Ability to also generate evenly spaced dividers within the box
+ including tabbed joints to box sides and slots to slot into each other
+
+23/06/2015 by Paul Hutchison:
+ - Updated for Inkscape's 0.91 breaking change (unittouu)
+
+v0.93 - 15/8/2016 by Paul Hutchison:
+ - Added Hairline option and fixed open box height bug
+
+v0.94 - 05/01/2017 by Paul Hutchison:
+ - Added option for keying dividers into walls/floor/none
+
+v0.95 - 2017-04-20 by Jim McBeath
+ - Added optional dimples
+
+v0.96 - 2017-04-24 by Jim McBeath
+ - Refactored to make box type, tab style, and layout all orthogonal
+ - Added Tab Style option to allow creating waffle-block-style tabs
+ - Made open box size correct based on inner or outer dimension choice
+ - Fixed a few tab bugs
+
+v0.99 - 2020-06-01 by Paul Hutchison
+ - Preparatory release with Inkscape 1.0 compatibility upgrades (further fixes to come!)
+ - Removed Antisymmetric option as it's broken, kinda pointless and looks weird
+ - Fixed divider issues with Rotate Symmetric
+ - Made individual panels and their keyholes/slots grouped
+
+v1.0 - 2020-06-17 by Paul Hutchison
+ - Removed clearance parameter, as this was just subtracted from kerf - pointless?
+ - Corrected kerf adjustments for overall box size and divider keyholes
+ - Added dogbone cuts: CNC mills now supported!
+ - Fix for floor/ceiling divider key issue (#17)
+ - Increased max dividers to 20 (#35)
+
+v1.1 - 2021-08-09 by Paul Hutchison
+ - Fixed for current Inkscape release version 1.1 - thanks to PR from https://github.com/roastedneutrons
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+'''
+__version__ = "1.0" ### please report bugs, suggestions etc at https://github.com/paulh-rnd/TabbedBoxMaker ###
+
+import os,sys,inkex,simplestyle,gettext,math
+from copy import deepcopy
+_ = gettext.gettext
+
+linethickness = 1 # default unless overridden by settings
+
+def log(text):
+ if 'SCHROFF_LOG' in os.environ:
+ f = open(os.environ.get('SCHROFF_LOG'), 'a')
+ f.write(text + "\n")
+
+def newGroup(canvas):
+ # Create a new group and add element created from line string
+ panelId = canvas.svg.get_unique_id('panel')
+ group = canvas.svg.get_current_layer().add(inkex.Group(id=panelId))
+ return group
+
+def getLine(XYstring):
+ line = inkex.PathElement()
+ line.style = { 'stroke': '#000000', 'stroke-width' : str(linethickness), 'fill': 'none' }
+ line.path = XYstring
+ #inkex.etree.SubElement(parent, inkex.addNS('path','svg'), drw)
+ return line
+
+# jslee - shamelessly adapted from sample code on below Inkscape wiki page 2015-07-28
+# http://wiki.inkscape.org/wiki/index.php/Generating_objects_from_extensions
+def getCircle(r, c):
+ (cx, cy) = c
+ log("putting circle at (%d,%d)" % (cx,cy))
+ circle = inkex.PathElement.arc((cx, cy), r)
+ circle.style = { 'stroke': '#000000', 'stroke-width': str(linethickness), 'fill': 'none' }
+
+ # ell_attribs = {'style':simplestyle.formatStyle(style),
+ # inkex.addNS('cx','sodipodi') :str(cx),
+ # inkex.addNS('cy','sodipodi') :str(cy),
+ # inkex.addNS('rx','sodipodi') :str(r),
+ # inkex.addNS('ry','sodipodi') :str(r),
+ # inkex.addNS('start','sodipodi') :str(0),
+ # inkex.addNS('end','sodipodi') :str(2*math.pi),
+ # inkex.addNS('open','sodipodi') :'true', #all ellipse sectors we will draw are open
+ # inkex.addNS('type','sodipodi') :'arc',
+ # 'transform' :'' }
+ #inkex.etree.SubElement(parent, inkex.addNS('path','svg'), ell_attribs )
+ return circle
+
+def dimpleStr(tabVector,vectorX,vectorY,dirX,dirY,dirxN,diryN,ddir,isTab):
+ ds=''
+ if not isTab:
+ ddir = -ddir
+ if dimpleHeight>0 and tabVector!=0:
+ if tabVector>0:
+ dimpleStart=(tabVector-dimpleLength)/2-dimpleHeight
+ tabSgn=1
+ else:
+ dimpleStart=(tabVector+dimpleLength)/2+dimpleHeight
+ tabSgn=-1
+ Vxd=vectorX+dirxN*dimpleStart
+ Vyd=vectorY+diryN*dimpleStart
+ ds+='L '+str(Vxd)+','+str(Vyd)+' '
+ Vxd=Vxd+(tabSgn*dirxN-ddir*dirX)*dimpleHeight
+ Vyd=Vyd+(tabSgn*diryN-ddir*dirY)*dimpleHeight
+ ds+='L '+str(Vxd)+','+str(Vyd)+' '
+ Vxd=Vxd+tabSgn*dirxN*dimpleLength
+ Vyd=Vyd+tabSgn*diryN*dimpleLength
+ ds+='L '+str(Vxd)+','+str(Vyd)+' '
+ Vxd=Vxd+(tabSgn*dirxN+ddir*dirX)*dimpleHeight
+ Vyd=Vyd+(tabSgn*diryN+ddir*dirY)*dimpleHeight
+ ds+='L '+str(Vxd)+','+str(Vyd)+' '
+ return ds
+
+def side(group,root,startOffset,endOffset,tabVec,length,direction,isTab,isDivider,numDividers,dividerSpacing):
+ rootX, rootY = root
+ startOffsetX, startOffsetY = startOffset
+ endOffsetX, endOffsetY = endOffset
+ dirX, dirY = direction
+ notTab=0 if isTab else 1
+
+ if (tabSymmetry==1): # waffle-block style rotationally symmetric tabs
+ divisions=int((length-2*thickness)/nomTab)
+ if divisions%2: divisions+=1 # make divs even
+ divisions=float(divisions)
+ tabs=divisions/2 # tabs for side
+ else:
+ divisions=int(length/nomTab)
+ if not divisions%2: divisions-=1 # make divs odd
+ divisions=float(divisions)
+ tabs=(divisions-1)/2 # tabs for side
+
+ if (tabSymmetry==1): # waffle-block style rotationally symmetric tabs
+ gapWidth=tabWidth=(length-2*thickness)/divisions
+ elif equalTabs:
+ gapWidth=tabWidth=length/divisions
+ else:
+ tabWidth=nomTab
+ gapWidth=(length-tabs*nomTab)/(divisions-tabs)
+
+ if isTab: # kerf correction
+ gapWidth-=kerf
+ tabWidth+=kerf
+ first=halfkerf
+ else:
+ gapWidth+=kerf
+ tabWidth-=kerf
+ first=-halfkerf
+ firstholelenX=0
+ firstholelenY=0
+ s=[]
+ h=[]
+ firstVec=0; secondVec=tabVec
+ dividerEdgeOffsetX = dividerEdgeOffsetY = thickness
+ notDirX=0 if dirX else 1 # used to select operation on x or y
+ notDirY=0 if dirY else 1
+ if (tabSymmetry==1):
+ dividerEdgeOffsetX = dirX*thickness;
+ #dividerEdgeOffsetY = ;
+ vectorX = rootX + (startOffsetX*thickness if notDirX else 0)
+ vectorY = rootY + (startOffsetY*thickness if notDirY else 0)
+ s='M '+str(vectorX)+','+str(vectorY)+' '
+ vectorX = rootX+(startOffsetX if startOffsetX else dirX)*thickness
+ vectorY = rootY+(startOffsetY if startOffsetY else dirY)*thickness
+ if notDirX: endOffsetX=0
+ if notDirY: endOffsetY=0
+ else:
+ (vectorX,vectorY)=(rootX+startOffsetX*thickness,rootY+startOffsetY*thickness)
+ dividerEdgeOffsetX=dirY*thickness
+ dividerEdgeOffsetY=dirX*thickness
+ s='M '+str(vectorX)+','+str(vectorY)+' '
+ if notDirX: vectorY=rootY # set correct line start for tab generation
+ if notDirY: vectorX=rootX
+
+ # generate line as tab or hole using:
+ # last co-ord:Vx,Vy ; tab dir:tabVec ; direction:dirx,diry ; thickness:thickness
+ # divisions:divs ; gap width:gapWidth ; tab width:tabWidth
+
+ for tabDivision in range(1,int(divisions)):
+ if ((tabDivision%2) ^ (not isTab)) and numDividers>0 and not isDivider: # draw holes for divider tabs to key into side walls
+ w=gapWidth if isTab else tabWidth
+ if tabDivision==1 and tabSymmetry==0:
+ w-=startOffsetX*thickness
+ holeLenX=dirX*w+notDirX*firstVec+first*dirX
+ holeLenY=dirY*w+notDirY*firstVec+first*dirY
+ if first:
+ firstholelenX=holeLenX
+ firstholelenY=holeLenY
+ for dividerNumber in range(1,int(numDividers)+1):
+ Dx=vectorX+-dirY*dividerSpacing*dividerNumber+notDirX*halfkerf+dirX*dogbone*halfkerf-dogbone*first*dirX
+ Dy=vectorY+dirX*dividerSpacing*dividerNumber-notDirY*halfkerf+dirY*dogbone*halfkerf-dogbone*first*dirY
+ if tabDivision==1 and tabSymmetry==0:
+ Dx+=startOffsetX*thickness
+ h='M '+str(Dx)+','+str(Dy)+' '
+ Dx=Dx+holeLenX
+ Dy=Dy+holeLenY
+ h+='L '+str(Dx)+','+str(Dy)+' '
+ Dx=Dx+notDirX*(secondVec-kerf)
+ Dy=Dy+notDirY*(secondVec+kerf)
+ h+='L '+str(Dx)+','+str(Dy)+' '
+ Dx=Dx-holeLenX
+ Dy=Dy-holeLenY
+ h+='L '+str(Dx)+','+str(Dy)+' '
+ Dx=Dx-notDirX*(secondVec-kerf)
+ Dy=Dy-notDirY*(secondVec+kerf)
+ h+='L '+str(Dx)+','+str(Dy)+' '
+ group.add(getLine(h))
+ if tabDivision%2:
+ if tabDivision==1 and numDividers>0 and isDivider: # draw slots for dividers to slot into each other
+ for dividerNumber in range(1,int(numDividers)+1):
+ Dx=vectorX+-dirY*dividerSpacing*dividerNumber-dividerEdgeOffsetX+notDirX*halfkerf
+ Dy=vectorY+dirX*dividerSpacing*dividerNumber-dividerEdgeOffsetY+notDirY*halfkerf
+ h='M '+str(Dx)+','+str(Dy)+' '
+ Dx=Dx+dirX*(first+length/2)
+ Dy=Dy+dirY*(first+length/2)
+ h+='L '+str(Dx)+','+str(Dy)+' '
+ Dx=Dx+notDirX*(thickness-kerf)
+ Dy=Dy+notDirY*(thickness-kerf)
+ h+='L '+str(Dx)+','+str(Dy)+' '
+ Dx=Dx-dirX*(first+length/2)
+ Dy=Dy-dirY*(first+length/2)
+ h+='L '+str(Dx)+','+str(Dy)+' '
+ Dx=Dx-notDirX*(thickness-kerf)
+ Dy=Dy-notDirY*(thickness-kerf)
+ h+='L '+str(Dx)+','+str(Dy)+' '
+ group.add(getLine(h))
+ # draw the gap
+ vectorX+=dirX*(gapWidth+(isTab&dogbone&1 ^ 0x1)*first+dogbone*kerf*isTab)+notDirX*firstVec
+ vectorY+=dirY*(gapWidth+(isTab&dogbone&1 ^ 0x1)*first+dogbone*kerf*isTab)+notDirY*firstVec
+ s+='L '+str(vectorX)+','+str(vectorY)+' '
+ if dogbone and isTab:
+ vectorX-=dirX*halfkerf
+ vectorY-=dirY*halfkerf
+ s+='L '+str(vectorX)+','+str(vectorY)+' '
+ # draw the starting edge of the tab
+ s+=dimpleStr(secondVec,vectorX,vectorY,dirX,dirY,notDirX,notDirY,1,isTab)
+ vectorX+=notDirX*secondVec
+ vectorY+=notDirY*secondVec
+ s+='L '+str(vectorX)+','+str(vectorY)+' '
+ if dogbone and notTab:
+ vectorX-=dirX*halfkerf
+ vectorY-=dirY*halfkerf
+ s+='L '+str(vectorX)+','+str(vectorY)+' '
+
+ else:
+ # draw the tab
+ vectorX+=dirX*(tabWidth+dogbone*kerf*notTab)+notDirX*firstVec
+ vectorY+=dirY*(tabWidth+dogbone*kerf*notTab)+notDirY*firstVec
+ s+='L '+str(vectorX)+','+str(vectorY)+' '
+ if dogbone and notTab:
+ vectorX-=dirX*halfkerf
+ vectorY-=dirY*halfkerf
+ s+='L '+str(vectorX)+','+str(vectorY)+' '
+ # draw the ending edge of the tab
+ s+=dimpleStr(secondVec,vectorX,vectorY,dirX,dirY,notDirX,notDirY,-1,isTab)
+ vectorX+=notDirX*secondVec
+ vectorY+=notDirY*secondVec
+ s+='L '+str(vectorX)+','+str(vectorY)+' '
+ if dogbone and isTab:
+ vectorX-=dirX*halfkerf
+ vectorY-=dirY*halfkerf
+ s+='L '+str(vectorX)+','+str(vectorY)+' '
+ (secondVec,firstVec)=(-secondVec,-firstVec) # swap tab direction
+ first=0
+
+ #finish the line off
+ s+='L '+str(rootX+endOffsetX*thickness+dirX*length)+','+str(rootY+endOffsetY*thickness+dirY*length)+' '
+
+ if isTab and numDividers>0 and tabSymmetry==0 and not isDivider: # draw last for divider joints in side walls
+ for dividerNumber in range(1,int(numDividers)+1):
+ Dx=vectorX+-dirY*dividerSpacing*dividerNumber+notDirX*halfkerf+dirX*dogbone*halfkerf-dogbone*first*dirX
+ # Dy=vectorY+dirX*dividerSpacing*dividerNumber-notDirY*halfkerf+dirY*dogbone*halfkerf-dogbone*first*dirY
+ # Dx=vectorX+-dirY*dividerSpacing*dividerNumber-dividerEdgeOffsetX+notDirX*halfkerf
+ Dy=vectorY+dirX*dividerSpacing*dividerNumber-dividerEdgeOffsetY+notDirY*halfkerf
+ h='M '+str(Dx)+','+str(Dy)+' '
+ Dx=Dx+firstholelenX
+ Dy=Dy+firstholelenY
+ h+='L '+str(Dx)+','+str(Dy)+' '
+ Dx=Dx+notDirX*(thickness-kerf)
+ Dy=Dy+notDirY*(thickness-kerf)
+ h+='L '+str(Dx)+','+str(Dy)+' '
+ Dx=Dx-firstholelenX
+ Dy=Dy-firstholelenY
+ h+='L '+str(Dx)+','+str(Dy)+' '
+ Dx=Dx-notDirX*(thickness-kerf)
+ Dy=Dy-notDirY*(thickness-kerf)
+ h+='L '+str(Dx)+','+str(Dy)+' '
+ group.add(getLine(h))
+ # for dividerNumber in range(1,int(numDividers)+1):
+ # Dx=vectorX+-dirY*dividerSpacing*dividerNumber+notDirX*halfkerf+dirX*dogbone*halfkerf
+ # Dy=vectorY+dirX*dividerSpacing*dividerNumber-notDirY*halfkerf+dirY*dogbone*halfkerf
+ # # Dx=vectorX+dirX*dogbone*halfkerf
+ # # Dy=vectorY+dirX*dividerSpacing*dividerNumber-dirX*halfkerf+dirY*dogbone*halfkerf
+ # h='M '+str(Dx)+','+str(Dy)+' '
+ # Dx=rootX+endOffsetX*thickness+dirX*length
+ # Dy+=dirY*tabWidth+notDirY*firstVec+first*dirY
+ # h+='L '+str(Dx)+','+str(Dy)+' '
+ # Dx+=notDirX*(secondVec-kerf)
+ # Dy+=notDirY*(secondVec+kerf)
+ # h+='L '+str(Dx)+','+str(Dy)+' '
+ # Dx-=vectorX
+ # Dy-=(dirY*tabWidth+notDirY*firstVec+first*dirY)
+ # h+='L '+str(Dx)+','+str(Dy)+' '
+ # Dx-=notDirX*(secondVec-kerf)
+ # Dy-=notDirY*(secondVec+kerf)
+ # h+='L '+str(Dx)+','+str(Dy)+' '
+ # group.add(getLine(h))
+ group.add(getLine(s))
+ return s
+
+
+class BoxMaker(inkex.EffectExtension):
+
+ def add_arguments(self, pars):
+ pars.add_argument('--schroff',type=int,default=0,help='Enable Schroff mode')
+ pars.add_argument('--rail_height',type=float,default=10.0,help='Height of rail')
+ pars.add_argument('--rail_mount_depth',type=float,default=17.4,help='Depth at which to place hole for rail mount bolt')
+ pars.add_argument('--rail_mount_centre_offset',type=float, default=0.0,help='How far toward row centreline to offset rail mount bolt (from rail centreline)')
+ pars.add_argument('--rows',type=int,default=0,help='Number of Schroff rows')
+ pars.add_argument('--hp',type=int,default=0,help='Width (TE/HP units) of Schroff rows')
+ pars.add_argument('--row_spacing',type=float,default=10.0,help='Height of rail')
+ pars.add_argument('--unit', default='mm',help='Measure Units')
+ pars.add_argument('--inside',type=int,default=0,help='Int/Ext Dimension')
+ pars.add_argument('--length',type=float,default=100,help='Length of Box')
+ pars.add_argument('--width',type=float,default=100,help='Width of Box')
+ pars.add_argument('--depth',type=float,default=100,help='Height of Box')
+ pars.add_argument('--tab',type=float,default=25,help='Nominal Tab Width')
+ pars.add_argument('--tabtype',type=int,default=0,help='Tab type: regular or dogbone')
+ pars.add_argument('--equal',type=int,default=0,help='Equal/Prop Tabs')
+ pars.add_argument('--tabsymmetry',type=int,default=0,help='Tab style')
+ pars.add_argument('--dimpleheight',type=float,default=0,help='Tab Dimple Height')
+ pars.add_argument('--dimplelength',type=float,default=0,help='Tab Dimple Tip Length')
+ pars.add_argument('--hairline',type=int,default=0,help='Line Thickness')
+ pars.add_argument('--thickness',type=float,default=10,help='Thickness of Material')
+ pars.add_argument('--kerf',type=float,default=0.5,help='Kerf (width) of cut')
+ pars.add_argument('--clearance',type=float,default=0.01,help='Clearance of joints')
+ pars.add_argument('--style',type=int,default=25,help='Layout/Style')
+ pars.add_argument('--spacing',type=float,default=25,help='Part Spacing')
+ pars.add_argument('--boxtype',type=int,default=25,help='Box type')
+ pars.add_argument('--div_l',type=int,default=25,help='Dividers (Length axis)')
+ pars.add_argument('--div_w',type=int,default=25,help='Dividers (Width axis)')
+ pars.add_argument('--keydiv',type=int,default=3,help='Key dividers into walls/floor')
+
+ def effect(self):
+ global group,nomTab,equalTabs,tabSymmetry,dimpleHeight,dimpleLength,thickness,kerf,halfkerf,dogbone,divx,divy,hairline,linethickness,keydivwalls,keydivfloor
+
+ # Get access to main SVG document element and get its dimensions.
+ svg = self.document.getroot()
+
+ # Get the attributes:
+ widthDoc = self.svg.unittouu(svg.get('width'))
+ heightDoc = self.svg.unittouu(svg.get('height'))
+
+ # Get script's option values.
+ hairline=self.options.hairline
+ unit=self.options.unit
+ inside=self.options.inside
+ schroff=self.options.schroff
+ kerf = self.svg.unittouu( str(self.options.kerf) + unit )
+ halfkerf=kerf/2
+
+ # Set the line thickness
+ if hairline:
+ linethickness=self.svg.unittouu('0.002in')
+ else:
+ linethickness=1
+
+ if schroff:
+ rows=self.options.rows
+ rail_height=self.svg.unittouu(str(self.options.rail_height)+unit)
+ row_centre_spacing=self.svg.unittouu(str(122.5)+unit)
+ row_spacing=self.svg.unittouu(str(self.options.row_spacing)+unit)
+ rail_mount_depth=self.svg.unittouu(str(self.options.rail_mount_depth)+unit)
+ rail_mount_centre_offset=self.svg.unittouu(str(self.options.rail_mount_centre_offset)+unit)
+ rail_mount_radius=self.svg.unittouu(str(2.5)+unit)
+
+ ## minimally different behaviour for schroffmaker.inx vs. boxmaker.inx
+ ## essentially schroffmaker.inx is just an alternate interface with different
+ ## default settings, some options removed, and a tiny amount of extra logic
+ if schroff:
+ ## schroffmaker.inx
+ X = self.svg.unittouu(str(self.options.hp * 5.08) + unit)
+ # 122.5mm vertical distance between mounting hole centres of 3U Schroff panels
+ row_height = rows * (row_centre_spacing + rail_height)
+ # rail spacing in between rows but never between rows and case panels
+ row_spacing_total = (rows - 1) * row_spacing
+ Y = row_height + row_spacing_total
+ else:
+ ## boxmaker.inx
+ X = self.svg.unittouu( str(self.options.length + kerf) + unit )
+ Y = self.svg.unittouu( str(self.options.width + kerf) + unit )
+
+ Z = self.svg.unittouu( str(self.options.depth + kerf) + unit )
+ thickness = self.svg.unittouu( str(self.options.thickness) + unit )
+ nomTab = self.svg.unittouu( str(self.options.tab) + unit )
+ equalTabs=self.options.equal
+ tabSymmetry=self.options.tabsymmetry
+ dimpleHeight=self.options.dimpleheight
+ dimpleLength=self.options.dimplelength
+ dogbone = 1 if self.options.tabtype == 1 else 0
+ layout=self.options.style
+ spacing = self.svg.unittouu( str(self.options.spacing) + unit )
+ boxtype = self.options.boxtype
+ divx = self.options.div_l
+ divy = self.options.div_w
+ keydivwalls = 0 if self.options.keydiv == 3 or self.options.keydiv == 1 else 1
+ keydivfloor = 0 if self.options.keydiv == 3 or self.options.keydiv == 2 else 1
+ initOffsetX=0
+ initOffsetY=0
+
+ if inside: # if inside dimension selected correct values to outside dimension
+ X+=thickness*2
+ Y+=thickness*2
+ Z+=thickness*2
+
+ # check input values mainly to avoid python errors
+ # TODO restrict values to *correct* solutions
+ # TODO restrict divisions to logical values
+ error=0
+
+ if min(X,Y,Z)==0:
+ inkex.errormsg(_('Error: Dimensions must be non zero'))
+ error=1
+ if max(X,Y,Z)>max(widthDoc,heightDoc)*10: # crude test
+ inkex.errormsg(_('Error: Dimensions Too Large'))
+ error=1
+ if min(X,Y,Z)<3*nomTab:
+ inkex.errormsg(_('Error: Tab size too large'))
+ error=1
+ if nomTabmin(X,Y,Z)/3: # crude test
+ inkex.errormsg(_('Error: Material too thick'))
+ error=1
+ if kerf>min(X,Y,Z)/3: # crude test
+ inkex.errormsg(_('Error: Kerf too large'))
+ error=1
+ if spacing>max(X,Y,Z)*10: # crude test
+ inkex.errormsg(_('Error: Spacing too large'))
+ error=1
+ if spacing 0=holes 1=tabs
+ # tabbed= 0=no tabs 1=tabs on this side
+ # (sides: a=top, b=right, c=bottom, d=left)
+ # pieceType: 1=XY, 2=XZ, 3=ZY
+ tpFace=1
+ bmFace=1
+ ftFace=2
+ bkFace=2
+ ltFace=3
+ rtFace=3
+
+ def reduceOffsets(aa, start, dx, dy, dz):
+ for ix in range(start+1,len(aa)):
+ (s,x,y,z) = aa[ix]
+ aa[ix] = (s-1, x-dx, y-dy, z-dz)
+
+ # note first two pieces in each set are the X-divider template and Y-divider template respectively
+ pieces=[]
+ if layout==1: # Diagramatic Layout
+ rr = deepcopy([row0, row1z, row2])
+ cc = deepcopy([col0, col1z, col2xz, col3xzz])
+ if not hasFt: reduceOffsets(rr, 0, 0, 0, 1) # remove row0, shift others up by Z
+ if not hasLt: reduceOffsets(cc, 0, 0, 0, 1)
+ if not hasRt: reduceOffsets(cc, 2, 0, 0, 1)
+ if hasBk: pieces.append([cc[1], rr[2], X,Z, bkTabInfo, bkTabbed, bkFace])
+ if hasLt: pieces.append([cc[0], rr[1], Z,Y, ltTabInfo, ltTabbed, ltFace])
+ if hasBm: pieces.append([cc[1], rr[1], X,Y, bmTabInfo, bmTabbed, bmFace])
+ if hasRt: pieces.append([cc[2], rr[1], Z,Y, rtTabInfo, rtTabbed, rtFace])
+ if hasTp: pieces.append([cc[3], rr[1], X,Y, tpTabInfo, tpTabbed, tpFace])
+ if hasFt: pieces.append([cc[1], rr[0], X,Z, ftTabInfo, ftTabbed, ftFace])
+ elif layout==2: # 3 Piece Layout
+ rr = deepcopy([row0, row1y])
+ cc = deepcopy([col0, col1z])
+ if hasBk: pieces.append([cc[1], rr[1], X,Z, bkTabInfo, bkTabbed, bkFace])
+ if hasLt: pieces.append([cc[0], rr[0], Z,Y, ltTabInfo, ltTabbed, ltFace])
+ if hasBm: pieces.append([cc[1], rr[0], X,Y, bmTabInfo, bmTabbed, bmFace])
+ elif layout==3: # Inline(compact) Layout
+ rr = deepcopy([row0])
+ cc = deepcopy([col0, col1x, col2xx, col3xxz, col4, col5])
+ if not hasTp: reduceOffsets(cc, 0, 1, 0, 0) # remove col0, shift others left by X
+ if not hasBm: reduceOffsets(cc, 1, 1, 0, 0)
+ if not hasLt: reduceOffsets(cc, 2, 0, 0, 1)
+ if not hasRt: reduceOffsets(cc, 3, 0, 0, 1)
+ if not hasBk: reduceOffsets(cc, 4, 1, 0, 0)
+ if hasBk: pieces.append([cc[4], rr[0], X,Z, bkTabInfo, bkTabbed, bkFace])
+ if hasLt: pieces.append([cc[2], rr[0], Z,Y, ltTabInfo, ltTabbed, ltFace])
+ if hasTp: pieces.append([cc[0], rr[0], X,Y, tpTabInfo, tpTabbed, tpFace])
+ if hasBm: pieces.append([cc[1], rr[0], X,Y, bmTabInfo, bmTabbed, bmFace])
+ if hasRt: pieces.append([cc[3], rr[0], Z,Y, rtTabInfo, rtTabbed, rtFace])
+ if hasFt: pieces.append([cc[5], rr[0], X,Z, ftTabInfo, ftTabbed, ftFace])
+
+ for idx, piece in enumerate(pieces): # generate and draw each piece of the box
+ (xs,xx,xy,xz)=piece[0]
+ (ys,yx,yy,yz)=piece[1]
+ x=xs*spacing+xx*X+xy*Y+xz*Z+initOffsetX # root x co-ord for piece
+ y=ys*spacing+yx*X+yy*Y+yz*Z+initOffsetY # root y co-ord for piece
+ dx=piece[2]
+ dy=piece[3]
+ tabs=piece[4]
+ a=tabs>>3&1; b=tabs>>2&1; c=tabs>>1&1; d=tabs&1 # extract tab status for each side
+ tabbed=piece[5]
+ atabs=tabbed>>3&1; btabs=tabbed>>2&1; ctabs=tabbed>>1&1; dtabs=tabbed&1 # extract tabbed flag for each side
+ xspacing=(X-thickness)/(divy+1)
+ yspacing=(Y-thickness)/(divx+1)
+ xholes = 1 if piece[6]<3 else 0
+ yholes = 1 if piece[6]!=2 else 0
+ wall = 1 if piece[6]>1 else 0
+ floor = 1 if piece[6]==1 else 0
+ railholes = 1 if piece[6]==3 else 0
+
+ group = newGroup(self)
+
+ if schroff and railholes:
+ log("rail holes enabled on piece %d at (%d, %d)" % (idx, x+thickness,y+thickness))
+ log("abcd = (%d,%d,%d,%d)" % (a,b,c,d))
+ log("dxdy = (%d,%d)" % (dx,dy))
+ rhxoffset = rail_mount_depth + thickness
+ if idx == 1:
+ rhx=x+rhxoffset
+ elif idx == 3:
+ rhx=x-rhxoffset+dx
+ else:
+ rhx=0
+ log("rhxoffset = %d, rhx= %d" % (rhxoffset, rhx))
+ rystart=y+(rail_height/2)+thickness
+ if rows == 1:
+ log("just one row this time, rystart = %d" % rystart)
+ rh1y=rystart+rail_mount_centre_offset
+ rh2y=rh1y+(row_centre_spacing-rail_mount_centre_offset)
+ group.add(getCircle(rail_mount_radius,(rhx,rh1y)))
+ group.add(getCircle(rail_mount_radius,(rhx,rh2y)))
+ else:
+ for n in range(0,rows):
+ log("drawing row %d, rystart = %d" % (n+1, rystart))
+ # if holes are offset (eg. Vector T-strut rails), they should be offset
+ # toward each other, ie. toward the centreline of the Schroff row
+ rh1y=rystart+rail_mount_centre_offset
+ rh2y=rh1y+row_centre_spacing-rail_mount_centre_offset
+ group.add(getCircle(rail_mount_radius,(rhx,rh1y)))
+ group.add(getCircle(rail_mount_radius,(rhx,rh2y)))
+ rystart+=row_centre_spacing+row_spacing+rail_height
+
+ # generate and draw the sides of each piece
+ side(group,(x,y),(d,a),(-b,a),atabs * (-thickness if a else thickness),dx,(1,0),a,0,(keydivfloor|wall) * (keydivwalls|floor) * divx*yholes*atabs,yspacing) # side a
+ side(group,(x+dx,y),(-b,a),(-b,-c),btabs * (thickness if b else -thickness),dy,(0,1),b,0,(keydivfloor|wall) * (keydivwalls|floor) * divy*xholes*btabs,xspacing) # side b
+ if atabs:
+ side(group,(x+dx,y+dy),(-b,-c),(d,-c),ctabs * (thickness if c else -thickness),dx,(-1,0),c,0,0,0) # side c
+ else:
+ side(group,(x+dx,y+dy),(-b,-c),(d,-c),ctabs * (thickness if c else -thickness),dx,(-1,0),c,0,(keydivfloor|wall) * (keydivwalls|floor) * divx*yholes*ctabs,yspacing) # side c
+ if btabs:
+ side(group,(x,y+dy),(d,-c),(d,a),dtabs * (-thickness if d else thickness),dy,(0,-1),d,0,0,0) # side d
+ else:
+ side(group,(x,y+dy),(d,-c),(d,a),dtabs * (-thickness if d else thickness),dy,(0,-1),d,0,(keydivfloor|wall) * (keydivwalls|floor) * divy*xholes*dtabs,xspacing) # side d
+
+ if idx==0:
+ # remove tabs from dividers if not required
+ if not keydivfloor:
+ a=c=1
+ atabs=ctabs=0
+ if not keydivwalls:
+ b=d=1
+ btabs=dtabs=0
+
+ y=4*spacing+1*Y+2*Z # root y co-ord for piece
+ for n in range(0,divx): # generate X dividers
+ group = newGroup(self)
+ x=n*(spacing+X) # root x co-ord for piece
+ side(group,(x,y),(d,a),(-b,a),keydivfloor*atabs*(-thickness if a else thickness),dx,(1,0),a,1,0,0) # side a
+ side(group,(x+dx,y),(-b,a),(-b,-c),keydivwalls*btabs*(thickness if b else -thickness),dy,(0,1),b,1,divy*xholes,xspacing) # side b
+ side(group,(x+dx,y+dy),(-b,-c),(d,-c),keydivfloor*ctabs*(thickness if c else -thickness),dx,(-1,0),c,1,0,0) # side c
+ side(group,(x,y+dy),(d,-c),(d,a),keydivwalls*dtabs*(-thickness if d else thickness),dy,(0,-1),d,1,0,0) # side d
+ elif idx==1:
+ y=5*spacing+1*Y+3*Z # root y co-ord for piece
+ for n in range(0,divy): # generate Y dividers
+ group = newGroup(self)
+ x=n*(spacing+Z) # root x co-ord for piece
+ side(group,(x,y),(d,a),(-b,a),keydivwalls*atabs*(-thickness if a else thickness),dx,(1,0),a,1,divx*yholes,yspacing) # side a
+ side(group,(x+dx,y),(-b,a),(-b,-c),keydivfloor*btabs*(thickness if b else -thickness),dy,(0,1),b,1,0,0) # side b
+ side(group,(x+dx,y+dy),(-b,-c),(d,-c),keydivwalls*ctabs*(thickness if c else -thickness),dx,(-1,0),c,1,0,0) # side c
+ side(group,(x,y+dy),(d,-c),(d,a),keydivfloor*dtabs*(-thickness if d else thickness),dy,(0,-1),d,1,0,0) # side d
+
+# Create effect instance and apply it.
+BoxMaker().run()
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/box_maker_tabbed/meta.json b/extensions/fablabchemnitz/box_maker_tabbed/meta.json
new file mode 100644
index 0000000..abcffea
--- /dev/null
+++ b/extensions/fablabchemnitz/box_maker_tabbed/meta.json
@@ -0,0 +1,33 @@
+[
+ {
+ "name": "Box Maker - ",
+ "id": "fablabchemnitz.de.box_maker_tabbed.",
+ "path": "box_maker_tabbed",
+ "dependent_extensions": null,
+ "original_name": [
+ "Schroff Box Maker",
+ "CNC Tabbed Box Maker"
+ ],
+ "original_id": [
+ "eu.twot.render.schroffboxmaker",
+ "nz.paulh-rnd.tabbedboxmaker"
+ ],
+ "license": "GNU GPL v2",
+ "license_url": "https://github.com/paulh-rnd/TabbedBoxMaker/blob/master/LICENSE",
+ "comment": "",
+ "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/boxmaker_tabbed",
+ "fork_url": "https://github.com/paulh-rnd/TabbedBoxMaker",
+ "documentation_url": [
+ "https://stadtfabrikanten.org/display/IFM/Box+Maker+-+Schroff",
+ "https://stadtfabrikanten.org/display/IFM/Box+Maker+-+Tabbed"
+ ],
+ "inkscape_gallery_url": null,
+ "main_authors": [
+ "github.com/paulh-rnd",
+ "github.com/jimmc",
+ "github.com/jsleeio",
+ "github.com/paulpce",
+ "github.com/vmario89"
+ ]
+ }
+]
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/box_maker_tabbed/schroffmaker.inx b/extensions/fablabchemnitz/box_maker_tabbed/schroffmaker.inx
new file mode 100644
index 0000000..13f7e1b
--- /dev/null
+++ b/extensions/fablabchemnitz/box_maker_tabbed/schroffmaker.inx
@@ -0,0 +1,41 @@
+
+
+ Box Maker - Schroff
+ fablabchemnitz.de.boxmaker_tabbed.box_maker_schroff
+ mm
+ 1
+ 1
+ 1
+ 84
+ 0.0
+ 0.0
+ 65
+
+ 10.0
+ 17.4
+ 0.0
+ 0.0
+ 3.0
+
+
+
+
+ 3.0
+ 0.1
+ 0
+ 0
+ 1
+ 2
+ 1.0
+
+ all
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/label_guides/label_guides.inx b/extensions/fablabchemnitz/label_guides/label_guides.inx
new file mode 100644
index 0000000..f97d2d7
--- /dev/null
+++ b/extensions/fablabchemnitz/label_guides/label_guides.inx
@@ -0,0 +1,158 @@
+
+
+ Label Guides
+ fablabchemnitz.de.label_guides
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 8.5
+ 13
+ 37
+ 37
+ 39
+ 39
+ 5
+ 7
+
+
+
+
+
+
+
+ false
+ true
+ true
+ 5
+ true
+ true
+ 5
+ true
+ false
+
+ all
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/label_guides/label_guides.py b/extensions/fablabchemnitz/label_guides/label_guides.py
new file mode 100644
index 0000000..9192110
--- /dev/null
+++ b/extensions/fablabchemnitz/label_guides/label_guides.py
@@ -0,0 +1,559 @@
+#!/usr/bin/env python3
+'''
+Label Guides Creator
+
+Copyright (C) 2018 John Beard - john.j.beard **guesswhat** gmail.com
+
+## Simple Extension to draw guides and outlines for common paper labels
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; version 2 of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+'''
+
+import inkex
+from lxml import etree
+
+# Colours to use for the guides
+GUIDE_COLOURS = {
+ 'edge': '#00A000',
+ 'centre': '#A00000',
+ 'inset': '#0000A0'
+}
+
+# Preset list
+# Regular grids defined as:
+# 'reg', unit, page_szie, l marg, t marg, X size, Y size,
+# X pitch, Y pitch, Number across, Number down, shapes
+PRESETS = {
+ # Rounded rectangular labels in grid layout
+ 'L7167': ['reg', 'mm', 'a4', 5.2, 3.95, 199.6, 289.1, 199.6, 289.1, 1, 1, 'rrect'],
+ 'L7168': ['reg', 'mm', 'a4', 5.2, 5, 199.6, 143.5, 199.6, 143.5, 1, 2, 'rrect'],
+ 'L7169': ['reg', 'mm', 'a4', 4.65, 9.5, 99.1, 139, 101.6, 139, 2, 2, 'rrect'],
+ 'L7701': ['reg', 'mm', 'a4', 9, 24.5, 192, 62, 192, 62, 1, 4, 'rrect'],
+ 'L7171': ['reg', 'mm', 'a4', 5, 28.5, 200, 60, 200, 60, 1, 4, 'rrect'],
+ 'L7166': ['reg', 'mm', 'a4', 4.65, 8.85, 99.1, 93.1, 101.6, 93.1, 2, 3, 'rrect'],
+ 'L4760': ['reg', 'mm', 'a4', 9, 12, 192, 39, 192, 39, 1, 7, 'rrect'],
+ 'L7165': ['reg', 'mm', 'a4', 4.65, 13.1, 99.1, 67.7, 101.6, 67.7, 2, 4, 'rrect'],
+ 'L7664': ['reg', 'mm', 'a4', 18, 4.9, 70, 71.8, 104, 71.8, 2, 4, 'rrect'],
+ 'L7667': ['reg', 'mm', 'a4', 38.5, 15.3, 133, 29.6, 133, 29.6, 1, 9, 'rrect'],
+ 'L7173': ['reg', 'mm', 'a4', 4.65, 6, 99.1, 57, 101.6, 57, 2, 5, 'rrect'],
+ 'J5103': ['reg', 'mm', 'a4', 4.75, 13.5, 38.1, 135, 40.6, 135, 5, 2, 'rrect'],
+ 'L7666': ['reg', 'mm', 'a4', 23, 18.5, 70, 52, 94, 52, 2, 5, 'rrect'],
+ 'L7783': ['reg', 'mm', 'a4', 7.85, 21.75, 95.8, 50.7, 98.5, 50.7, 2, 5, 'rrect'],
+ 'L7164': ['reg', 'mm', 'a4', 7.25, 4.5, 63.5, 72, 66, 72, 3, 4, 'rrect'],
+ 'L7671': ['reg', 'mm', 'a4', 27.55, 9.3, 76.2, 46.4, 78.7, 46.4, 2, 6, 'rrect'],
+ 'L7177': ['reg', 'mm', 'a4', 4.65, 21.6, 99.1, 42.3, 101.6, 42.3, 2, 6, 'rrect'],
+ 'L7163': ['reg', 'mm', 'a4', 4.65, 15.15, 99.1, 38.1, 101.6, 38.1, 2, 7, 'rrect'],
+ 'L7668': ['reg', 'mm', 'a4', 13.5, 21.25, 59, 50.9, 62, 50.9, 3, 5, 'rrect'],
+ 'L7162': ['reg', 'mm', 'a4', 4.65, 12.9, 99.1, 33.9, 101.6, 33.9, 2, 8, 'rrect'],
+ 'L7674': ['reg', 'mm', 'a4', 32.5, 12.5, 145, 17, 145, 17, 1, 16, 'rrect'],
+ 'L7161': ['reg', 'mm', 'a4', 7.25, 8.7, 63.5, 46.6, 66, 46.6, 3, 6, 'rrect'],
+ 'L7172': ['reg', 'mm', 'a4', 3.75, 13.5, 100, 30, 102.5, 30, 2, 9, 'rrect'],
+ 'J5101': ['reg', 'mm', 'a4', 4.75, 10.5, 38.1, 69, 40.6, 69, 5, 4, 'rrect'],
+ 'L7160': ['reg', 'mm', 'a4', 7.25, 15.15, 63.5, 38.1, 66, 38.1, 3, 7, 'rrect'],
+ 'L7159': ['reg', 'mm', 'a4', 7.25, 12.9, 63.5, 33.9, 66, 33.9, 3, 8, 'rrect'],
+ 'L7665': ['reg', 'mm', 'a4', 22, 21.6, 72, 21.15, 94, 21.15, 2, 12, 'rrect'],
+ 'L7170': ['reg', 'mm', 'a4', 38, 16.5, 134, 11, 134, 11, 1, 24, 'rrect'],
+ 'L6011': ['reg', 'mm', 'a4', 7.25, 15.3, 63.5, 29.6, 66, 29.6, 3, 9, 'rrect'],
+ 'LP33_53': ['reg', 'mm', 'a4', 21, 17.5, 54, 22, 57, 24, 3, 11, 'rrect'],
+ 'LP36_49': ['reg', 'mm', 'a4', 4.8, 15.3, 48.9, 29.6, 50.5, 29.6, 4, 9, 'rrect'],
+ 'L7654': ['reg', 'mm', 'a4', 9.7, 21.5, 45.7, 25.4, 48.3, 25.4, 4, 10, 'rrect'],
+ 'L7636': ['reg', 'mm', 'a4', 9.85, 21.3, 45.7, 21.2, 48.2, 21.2, 4, 12, 'rrect'],
+ 'LP56_89': ['reg', 'mm', 'a4', 8, 8.5, 89, 10, 105, 10, 2, 28, 'rrect'],
+ 'L7651': ['reg', 'mm', 'a4', 4.75, 10.7, 38.1, 21.2, 40.6, 21.2, 5, 13, 'rrect'],
+ 'L7656': ['reg', 'mm', 'a4', 5.95, 15.95, 46, 11.1, 50.7, 12.7, 4, 21, 'rrect'],
+ 'L7658': ['reg', 'mm', 'a4', 8.6, 13.5, 25.4, 10, 27.9, 10, 7, 27, 'rrect'],
+ 'L7657': ['reg', 'mm', 'a4', 4.75, 13.5, 17.8, 10, 20.3, 10, 10, 27, 'rrect'],
+
+ # Rect labels
+ 'L7784': ['reg', 'mm', 'a4', 0, 0, 210, 297, 210, 297, 1, 1, 'rect'],
+ 'LP2_105': ['reg', 'mm', 'a4', 0, 0, 105, 297, 105, 297, 2, 1, 'rect'],
+ '3655': ['reg', 'mm', 'a4', 0, 0, 210, 148.5, 210, 148.5, 1, 2, 'rect'],
+ 'LP3_210': ['reg', 'mm', 'a4', 0, 0, 210, 99, 210, 99, 1, 3, 'rect'],
+ '3483': ['reg', 'mm', 'a4', 0, 0, 105, 148.5, 105, 148.5, 2, 2, 'rect'],
+ 'LP4_210': ['reg', 'mm', 'a4', 0, 0, 210, 74.25, 210, 74.25, 1, 4, 'rect'],
+ 'LP6_70': ['reg', 'mm', 'a4', 0, 0, 70, 148.5, 70, 148.5, 3, 2, 'rect'],
+ 'LP6_105': ['reg', 'mm', 'a4', 0, 0, 105, 99, 105, 99, 2, 3, 'rect'],
+ '3427': ['reg', 'mm', 'a4', 0, 0, 105, 74.25, 105, 74.25, 2, 4, 'rect'],
+ 'LP8_105S': ['reg', 'mm', 'a4', 0, 7.1, 105, 70.7, 105, 70.7, 2, 4, 'rect'],
+ 'LP10_105': ['reg', 'mm', 'a4', 0, 0, 105, 59.4, 105, 59.4, 2, 5, 'rect'],
+ '3425': ['reg', 'mm', 'a4', 0, 4.625, 105, 57.55, 105, 57.55, 2, 5, 'rect'],
+ 'LP12_105': ['reg', 'mm', 'a4', 0, 0, 105, 49.5, 105, 49.5, 2, 6, 'rect'],
+ '3424': ['reg', 'mm', 'a4', 0, 4.8, 105, 47.9, 105, 47.9, 2, 6, 'rect'],
+ '3653': ['reg', 'mm', 'a4', 0, 0, 105, 42.42, 105, 42.42, 2, 7, 'rect'],
+ 'LP15_70': ['reg', 'mm', 'a4', 0, 0, 70, 59.4, 70, 59.4, 3, 5, 'rect'],
+ 'LP15_70S': ['reg', 'mm', 'a4', 0, 21.75, 70, 50.7, 70, 50.7, 3, 5, 'rect'],
+ '3484': ['reg', 'mm', 'a4', 0, 0, 105, 37.12, 105, 37.12, 2, 8, 'rect'],
+ '3423': ['reg', 'mm', 'a4', 0, 8.7, 105, 34.95, 105, 34.95, 2, 8, 'rect'],
+ '3652': ['reg', 'mm', 'a4', 0, 0, 70, 42.42, 70, 42.42, 3, 7, 'rect'],
+ 'LP21_70S': ['reg', 'mm', 'a4', 0, 15.15, 70, 38.1, 70, 38.1, 3, 7, 'rect'],
+ '3474': ['reg', 'mm', 'a4', 0, 0, 70, 37.12, 70, 37.12, 3, 8, 'rect'],
+ '3422': ['reg', 'mm', 'a4', 0, 8.7, 70, 34.95, 70, 34.95, 3, 8, 'rect'],
+ 'LP24_70LS': ['reg', 'mm', 'a4', 0, 12.5, 70, 34, 70, 34, 3, 8, 'rect'],
+ '3475': ['reg', 'mm', 'a4', 0, 4.5, 70, 36, 70, 36, 3, 8, 'rect'],
+ 'LP27_70S': ['reg', 'mm', 'a4', 0, 4.725, 70, 31.95, 70, 31.95, 3, 9, 'rect'],
+ '3489': ['reg', 'mm', 'a4', 0, 0, 70, 29.7, 70, 29.7, 3, 10, 'rect'],
+ '3421': ['reg', 'mm', 'a4', 0, 8.8, 70, 25.4, 70, 25.4, 3, 11, 'rect'],
+ 'L7409': ['reg', 'mm', 'a4', 19.5, 21, 57, 15, 57, 15, 3, 17, 'rect'],
+ 'LP56_52': ['reg', 'mm', 'a4', 0, 0, 52.5, 21.21, 52.5, 21.21, 4, 14, 'rect'],
+
+ # Round labels
+ 'LP2_115R': ['reg', 'mm', 'a4', 47.75, 16.65, 114.5, 114.5, 114.5, 149.2, 1, 2, 'circle'],
+ 'LP6_88R': ['reg', 'mm', 'a4', 16, 14.5, 88, 88, 90, 90, 2, 3, 'circle'],
+ 'LP6_85R': ['reg', 'mm', 'a4', 17.5, 16, 85, 85, 90, 90, 2, 3, 'circle'],
+ 'LP6_76R': ['reg', 'mm', 'a4', 27, 31, 76, 76, 80, 79.5, 2, 3, 'circle'],
+ 'C2244': ['reg', 'mm', 'a4', 29.7, 33.9, 72, 72, 78.6, 78.6, 2, 3, 'circle'],
+ 'LP8_69R': ['reg', 'mm', 'a4', 34.5, 6, 69, 69, 72, 72, 2, 4, 'circle'],
+ 'L7670': ['reg', 'mm', 'a4', 5.25, 14.75, 63.5, 63.5, 68, 68, 3, 4, 'circle'],
+ 'LP15_51R': ['reg', 'mm', 'a4', 26.5, 17, 51, 51, 53, 53, 3, 5, 'circle'],
+ 'LP24_45R': ['reg', 'mm', 'a4', 9, 6, 45, 45, 49, 48, 4, 6, 'circle'],
+ 'L7780': ['reg', 'mm', 'a4', 16, 13.5, 40, 40, 46, 46, 4, 6, 'circle'],
+ 'LP35_37R': ['reg', 'mm', 'a4', 8.5, 13, 37, 37, 39, 39, 5, 7, 'circle'],
+ 'LP35_35R': ['reg', 'mm', 'a4', 9.5, 17, 35, 35, 39, 38, 5, 7, 'circle'],
+ 'LP40_32R': ['reg', 'mm', 'a4', 19, 10.35, 32, 32, 35, 34.9, 5, 8, 'circle'],
+ 'LP54_29R': ['reg', 'mm', 'a4', 8, 6, 29, 29, 33, 32, 6, 9, 'circle'],
+ 'LP70_25R': ['reg', 'mm', 'a4', 11.5, 14.5, 25, 25, 27, 27, 7, 10, 'circle'],
+ 'LP117_19R': ['reg', 'mm', 'a4', 11.5, 13, 19, 19, 21, 21, 9, 13, 'circle'],
+ 'LP216_13R': ['reg', 'mm', 'a4', 13.25, 10.25, 13, 13, 15.5, 15.5, 12, 18, 'circle'],
+
+ # Oval labels
+ 'LP2_195OV': ['reg', 'mm', 'a4', 7.5, 8.5, 195, 138, 195, 142, 1, 2, 'circle'],
+ 'LP4_90OV': ['reg', 'mm', 'a4', 14, 12.5, 90, 135, 92, 137, 2, 2, 'circle'],
+ 'LP8_90OV': ['reg', 'mm', 'a4', 9.25, 15.95, 90, 62, 101.5, 67.7, 2, 4, 'circle'],
+ 'LP10_95OV': ['reg', 'mm', 'a4', 7, 8, 95, 53, 101, 57, 2, 5, 'circle'],
+ 'LP14_95OV': ['reg', 'mm', 'a4', 7, 17.5, 95, 34, 101, 38, 2, 7, 'circle'],
+ 'LP21_60OV': ['reg', 'mm', 'a4', 11, 10, 60, 34, 64, 40.5, 3, 7, 'circle'],
+ 'LP32_40OV': ['reg', 'mm', 'a4', 22, 21.5, 40, 30, 42, 32, 4, 8, 'circle'],
+ 'LP65_35OV': ['reg', 'mm', 'a4', 5.975, 13.3, 35.05, 16, 40.75, 21.2, 5, 13, 'circle'],
+
+ # Square labels
+ 'LP6_95SQ': ['reg', 'mm', 'a4', 6.5, 3, 95, 95, 98, 98, 2, 3, 'rrect'],
+ 'LP12_65SQ': ['reg', 'mm', 'a4', 5, 15.5, 65, 65, 67.5, 67, 3, 4, 'rrect'],
+ 'LP15_51SQ': ['reg', 'mm', 'a4', 26.6, 17.2, 51, 51, 52.9, 52.9, 3, 5, 'rrect'],
+ 'LP35_37SQ': ['reg', 'mm', 'a4', 8.5 / 12, 13.3, 37, 37, 39, 38.9, 5, 7, 'rrect'],
+ 'LP70_25SQ': ['reg', 'mm', 'a4', 11.5, 14.5, 25, 25, 27, 27, 7, 10, 'rrect'],
+}
+
+
+def add_SVG_guide(x, y, orientation, colour, parent):
+ """ Create a sodipodi:guide node on the given parent
+ """
+
+ try:
+ # convert mnemonics to actual orientations
+ orientation = {
+ 'vert': '1,0',
+ 'horz': '0,1'
+ }[orientation]
+ except KeyError:
+ pass
+
+ attribs = {
+ 'position': str(x) + "," + str(y),
+ 'orientation': orientation
+ }
+
+ if colour is not None:
+ attribs[inkex.addNS('color', 'inkscape')] = colour
+
+ etree.SubElement(
+ parent,
+ inkex.addNS('guide', 'sodipodi'),
+ attribs)
+
+
+def delete_all_guides(document):
+ # getting the parent's tag of the guides
+ nv = document.xpath(
+ '/svg:svg/sodipodi:namedview', namespaces=inkex.NSS)[0]
+
+ # getting all the guides
+ children = document.xpath('/svg:svg/sodipodi:namedview/sodipodi:guide',
+ namespaces=inkex.NSS)
+
+ # removing each guides
+ for element in children:
+ nv.remove(element)
+
+
+def draw_SVG_ellipse(rx, ry, cx, cy, style, parent):
+
+ attribs = {
+ 'style': str(inkex.Style(style)),
+ inkex.addNS('cx', 'sodipodi'): str(cx),
+ inkex.addNS('cy', 'sodipodi'): str(cy),
+ inkex.addNS('rx', 'sodipodi'): str(rx),
+ inkex.addNS('ry', 'sodipodi'): str(ry),
+ inkex.addNS('type', 'sodipodi'): 'arc',
+ }
+
+ etree.SubElement(parent, inkex.addNS('path', 'svg'), attribs)
+
+
+def draw_SVG_rect(x, y, w, h, round, style, parent):
+
+ attribs = {
+ 'style': str(inkex.Style(style)),
+ 'height': str(h),
+ 'width': str(w),
+ 'x': str(x),
+ 'y': str(y)
+ }
+
+ if round:
+ attribs['ry'] = str(round)
+
+ etree.SubElement(parent, inkex.addNS('rect', 'svg'), attribs)
+
+
+def add_SVG_layer(parent, gid, label):
+
+ layer = etree.SubElement(parent, 'g', {
+ 'id': gid,
+ inkex.addNS('groupmode', 'inkscape'): 'layer',
+ inkex.addNS('label', 'inkscape'): label
+ })
+
+ return layer
+
+
+class LabelGuides(inkex.Effect):
+
+ def __init__(self):
+
+ inkex.Effect.__init__(self)
+ self.arg_parser.add_argument('--units', default="mm", help='The units to use for custom label sizing')
+ self.arg_parser.add_argument('--preset_tab', default='rrect', help='The preset section that is selected (other sections will be ignored)')
+
+ # ROUNDED RECTANGLE PRESET OPTIONS
+ self.arg_parser.add_argument('--rrect_preset', default='L7167', help='Use the given rounded rectangle preset template')
+ self.arg_parser.add_argument('--rrect_radius', type=float, default=1, help='Rectangle corner radius') # RECTANGULAR PRESET OPTIONS
+ self.arg_parser.add_argument('--rect_preset', default='L7784', help='Use the given square-corner rectangle template')
+
+ # CIRCULAR PRESET OPTIONS
+ self.arg_parser.add_argument('--circ_preset', default='LP2_115R', help='Use the given circular template')
+
+ # CUSTOM LABEL OPTIONS
+ self.arg_parser.add_argument('--margin_l', type=float, default=8.5, help='Left page margin')
+ self.arg_parser.add_argument('--margin_t', type=float, default=13, help='Top page margin')
+ self.arg_parser.add_argument('--size_x', type=float, default=37, help='Label X size')
+ self.arg_parser.add_argument('--size_y', type=float, default=37, help='Label Y size')
+ self.arg_parser.add_argument('--pitch_x', type=float, default=39, help='Label X pitch')
+ self.arg_parser.add_argument('--pitch_y', type=float, default=39, help='Label Y pitch')
+ self.arg_parser.add_argument('--count_x', type=int, default=5, help='Number of labels across')
+ self.arg_parser.add_argument('--count_y', type=int, default=7, help='Number of labels down')
+ self.arg_parser.add_argument('--shapes', default='rect', help='Label shapes to draw')
+
+ # GENERAL DRAWING OPTIONS
+ self.arg_parser.add_argument('--delete_existing_guides', type=inkex.Boolean, default=False, help='Delete existing guides from document')
+ self.arg_parser.add_argument('--draw_edge_guides', type=inkex.Boolean, default=True, help='Draw guides at label edges')
+ self.arg_parser.add_argument('--draw_centre_guides', type=inkex.Boolean, default=True, help='Draw guides at label centres')
+ self.arg_parser.add_argument('--inset', type=float, default=5, help='Inset to use for inset guides')
+ self.arg_parser.add_argument('--draw_inset_guides', type=inkex.Boolean, default=True, help='Draw guides inset to label edges')
+ self.arg_parser.add_argument('--draw_shapes', type=inkex.Boolean, default=True, help='Draw label outline shapes')
+ self.arg_parser.add_argument('--shape_inset', default=5, help='Inset to use for inset shapes')
+ self.arg_parser.add_argument('--draw_inset_shapes', type=inkex.Boolean, default=True, help='Draw shapes inset in the label outline')
+ self.arg_parser.add_argument('--set_page_size', type=inkex.Boolean, default=True, help='Set page size (presets only)')
+
+ def _to_uu(self, val, unit):
+ """
+ Transform a value in given units to User Units
+ """
+ return self.svg.unittouu(str(val) + unit)
+
+ def _get_page_size(self, size):
+ """
+ Get a page size from a definition entry - can be in the form
+ [x, y], or a string (one of ['a4'])
+ """
+
+ if isinstance(size, (list,)):
+ # Explicit size
+ return size
+ elif size == "a4":
+ return [210, 297]
+
+ # Failed to find a useful size, None will inhibit setting the size
+ return None
+
+ def _set_SVG_page_size(self, document, x, y, unit):
+ """
+ Set the SVG page size to the given absolute size. The viewbox is
+ also rescaled as needed to maintain the scale factor.
+ """
+
+ svg = document.getroot()
+
+ # Re-calculate viewbox in terms of User Units
+ new_uu_w = self._to_uu(x, unit)
+ new_uu_h = self._to_uu(y, unit)
+
+ # set SVG page size
+ svg.attrib['width'] = str(x) + unit
+ svg.attrib['height'] = str(y) + unit
+
+ svg.attrib['viewBox'] = "0 0 %f %f" % (new_uu_w, new_uu_h)
+
+ def _read_custom_options(self, options):
+ """
+ Read custom label geometry options and produce
+ a dictionary of parameters for ingestion
+ """
+ unit = options.units
+
+ custom_opts = {
+ 'units': options.units,
+ 'page_size': None,
+ 'margin': {
+ 'l': self._to_uu(options.margin_l, unit),
+ 't': self._to_uu(options.margin_t, unit)
+ },
+ 'size': {
+ 'x': self._to_uu(options.size_x, unit),
+ 'y': self._to_uu(options.size_y, unit)
+ },
+ 'pitch': {
+ 'x': self._to_uu(options.pitch_x, unit),
+ 'y': self._to_uu(options.pitch_y, unit)
+ },
+ 'count': {
+ 'x': options.count_x,
+ 'y': options.count_y
+ },
+ 'shapes': options.shapes,
+ 'corner_rad': None,
+ }
+
+ return custom_opts
+
+ def _construct_preset_opts(self, preset_type, preset_id, options):
+ """Construct an options object for a preset label template
+ """
+ preset = PRESETS[preset_id]
+
+ unit = preset[1]
+
+ opts = {
+ 'units': unit,
+ 'page_size': self._get_page_size(preset[2]),
+ 'margin': {
+ 'l': self._to_uu(preset[3], unit),
+ 't': self._to_uu(preset[4], unit)
+ },
+ 'size': {
+ 'x': self._to_uu(preset[5], unit),
+ 'y': self._to_uu(preset[6], unit)
+ },
+ 'pitch': {
+ 'x': self._to_uu(preset[7], unit),
+ 'y': self._to_uu(preset[8], unit)
+ },
+ 'count': {
+ 'x': preset[9],
+ 'y': preset[10]
+ },
+ 'shapes': preset[11],
+ 'corner_rad': None,
+ }
+
+ # add addtional options by preset type
+ if preset_type == "rrect":
+ opts["corner_rad"] = self._to_uu(options.rrect_radius, unit)
+
+ return opts
+
+ def _get_regular_guides(self, label_opts, inset):
+ """
+ Get the guides for a set of labels defined by a regular grid
+
+ This is done so that irregular-grid presets can be defined if
+ needed
+ """
+
+ guides = {'v': [], 'h': []}
+
+ x = label_opts['margin']['l']
+
+ # Vertical guides, left to right
+ for x_idx in range(label_opts['count']['x']):
+
+ l_pos = x + inset
+ r_pos = x + label_opts['size']['x'] - inset
+
+ guides['v'].extend([l_pos, r_pos])
+
+ # Step over to next label
+ x += label_opts['pitch']['x']
+
+ # Horizontal guides, bottom to top
+ y = label_opts['margin']['t']
+
+ for y_idx in range(label_opts['count']['y']):
+
+ t_pos = y + inset
+ b_pos = y + label_opts['size']['y'] - inset
+
+ guides['h'].extend([t_pos, b_pos])
+
+ # Step over to next label
+ y += label_opts['pitch']['y']
+
+ return guides
+
+ def _draw_label_guides(self, document, label_opts, inset, colour):
+ """
+ Draws label guides from a regular guide description object
+ """
+ # convert to UU
+ inset = self._to_uu(inset, label_opts['units'])
+
+ guides = self._get_regular_guides(label_opts, inset)
+
+ # Get parent tag of the guides
+ nv = self.svg.namedview
+
+ # Draw vertical guides
+ for g in guides['v']:
+ add_SVG_guide(g, 0, 'vert', colour, nv)
+
+ # Draw horizontal guides
+ for g in guides['h']:
+ add_SVG_guide(0, self.svg.viewport_height - g, 'horz', colour, nv)
+
+ def _draw_centre_guides(self, document, label_opts, colour):
+ """
+ Draw guides in the centre of labels defined by the given options
+ """
+
+ guides = self._get_regular_guides(label_opts, 0)
+ nv = self.svg.namedview
+
+ for g in range(0, len(guides['v']), 2):
+ pos = (guides['v'][g] + guides['v'][g + 1]) / 2
+ add_SVG_guide(pos, 0, 'vert', colour, nv)
+
+ for g in range(0, len(guides['h']), 2):
+ pos = (guides['h'][g] + guides['h'][g + 1]) / 2
+ add_SVG_guide(0, self.svg.viewport_height - pos, 'horz', colour, nv)
+
+ def _draw_shapes(self, document, label_opts, inset):
+ """
+ Draw label shapes from a regular grid
+ """
+
+ style = {
+ 'stroke': '#000000',
+ 'stroke-width': self._to_uu(1, "px"),
+ 'fill': "none"
+ }
+
+ inset = self._to_uu(inset, label_opts['units'])
+
+ guides = self._get_regular_guides(label_opts, 0)
+ shape = label_opts['shapes']
+
+ shapeLayer = add_SVG_layer(
+ self.document.getroot(),
+ self.svg.get_unique_id("outlineLayer"),
+ "Label outlines")
+
+ # draw shapes between every set of two guides
+ for xi in range(0, len(guides['v']), 2):
+
+ for yi in range(0, len(guides['h']), 2):
+
+ if shape == 'circle':
+ cx = (guides['v'][xi] + guides['v'][xi + 1]) / 2
+ cy = (guides['h'][yi] + guides['h'][yi + 1]) / 2
+
+ rx = cx - guides['v'][xi] - inset
+ ry = cy - guides['h'][yi] - inset
+
+ draw_SVG_ellipse(rx, ry, cx, cy, style, shapeLayer)
+
+ elif shape in ["rect", "rrect"]:
+
+ x = guides['v'][xi] + inset
+ w = guides['v'][xi + 1] - x - inset
+
+ y = guides['h'][yi] + inset
+ h = guides['h'][yi + 1] - y - inset
+
+ rnd = self._to_uu(label_opts['corner_rad'],
+ label_opts['units'])
+
+ draw_SVG_rect(x, y, w, h, rnd, style, shapeLayer)
+ def _set_page_size(self, document, label_opts):
+ """
+ Set the SVG page size from the given label template definition
+ """
+
+ size = label_opts['page_size']
+ unit = label_opts['units']
+
+ if size is not None:
+ self._set_SVG_page_size(document, size[0], size[1], unit)
+
+ def effect(self):
+ """
+ Perform the label template generation effect
+ """
+
+ preset_type = self.options.preset_tab.strip('"')
+
+ if preset_type == "custom":
+ # construct from parameters
+ label_opts = self._read_custom_options(self.options)
+ else:
+ # construct from a preset
+
+ # get the preset ID from the relevant enum entry
+ preset_id = {
+ "rrect": self.options.rrect_preset,
+ "rect": self.options.rect_preset,
+ "circ": self.options.circ_preset,
+ }[preset_type]
+
+ label_opts = self._construct_preset_opts(preset_type, preset_id,
+ self.options)
+
+ if self.options.delete_existing_guides:
+ delete_all_guides(self.document)
+
+ # Resize page first, otherwise guides won't be in the right places
+ if self.options.set_page_size:
+ self._set_page_size(self.document, label_opts)
+
+ if self.options.draw_edge_guides:
+ self._draw_label_guides(self.document, label_opts, 0,
+ GUIDE_COLOURS['edge'])
+
+ if self.options.draw_centre_guides:
+ self._draw_centre_guides(self.document, label_opts,
+ GUIDE_COLOURS['centre'])
+
+ if self.options.draw_inset_guides and self.options.inset > 0.0:
+ self._draw_label_guides(self.document, label_opts,
+ self.options.inset,
+ GUIDE_COLOURS['inset'])
+
+ if self.options.draw_shapes:
+ self._draw_shapes(self.document, label_opts, 0)
+
+ if self.options.draw_inset_shapes:
+ self._draw_shapes(self.document, label_opts,
+ self.options.shape_inset)
+
+
+if __name__ == '__main__':
+ LabelGuides().run()
diff --git a/extensions/fablabchemnitz/label_guides/meta.json b/extensions/fablabchemnitz/label_guides/meta.json
new file mode 100644
index 0000000..be6ad74
--- /dev/null
+++ b/extensions/fablabchemnitz/label_guides/meta.json
@@ -0,0 +1,22 @@
+[
+ {
+ "name": "Label Guides",
+ "id": "fablabchemnitz.de.label_guides",
+ "path": "label_guides",
+ "dependent_extensions": null,
+ "original_name": "Label Guides",
+ "original_id": "org.inkscape.effect.labelguides",
+ "license": "GNU GPL v2",
+ "license_url": "https://github.com/johnbeard/inkscape-label-guides/blob/master/COPYING",
+ "comment": "",
+ "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/label_guides",
+ "fork_url": "https://github.com/johnbeard/inkscape-label-guides",
+ "documentation_url": "https://stadtfabrikanten.org/display/IFM/Label+Guides",
+ "inkscape_gallery_url": null,
+ "main_authors": [
+ "github.com/johnbeard",
+ "github.com/PaulSchulz",
+ "github.com/vmario89"
+ ]
+ }
+]
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/layer_clip/clip.py b/extensions/fablabchemnitz/layer_clip/clip.py
new file mode 100644
index 0000000..f62338f
--- /dev/null
+++ b/extensions/fablabchemnitz/layer_clip/clip.py
@@ -0,0 +1,73 @@
+# Copyright (c) 2012 Stuart Pernsteiner
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import sys
+import inkex
+import gettext
+from lxml import etree
+_ = gettext.gettext
+
+class ClipEffect(inkex.EffectExtension):
+
+ def __init__(self, dirmark):
+ inkex.Effect.__init__(self)
+ self.dirmark = dirmark
+
+ def effect(self):
+ defs = self.svg.getElement('//svg:defs')
+
+ if len(self.svg.selected) != 1:
+ die(_("Error: You must select exactly one path"))
+ # Create the svg:clipPath inside of svg:defs
+ pathId = list(self.svg.selected.values())[0].get('id')
+
+ #inkex.utils.debug(pathId)
+ clippath = etree.SubElement(defs, 'clipPath',
+ {'clipPathUnits': 'userSpaceOnUse'
+ ,'id': self.svg.get_unique_id("clipPath")})
+ use = etree.SubElement(clippath, 'use',
+ {inkex.addNS('href','xlink'): '#' + pathId
+ ,'id': self.svg.get_unique_id("use")})
+
+ # Find the target layer and set its clip-path attribute
+ layer = self.svg.selected[pathId].getparent()
+ target = self.find_target(pathId)
+
+ target.set('clip-path', 'url(#%s)' % clippath.get('id'))
+
+ # Update layer names
+ label_attr = inkex.addNS('label', 'inkscape')
+
+ layer_label = layer.get(label_attr)
+ layer.set(label_attr, layer_label + ' (c%s)' % self.dirmark)
+
+ target_label = target.get(label_attr)
+ target.set(label_attr, target_label + ' (C)')
+
+def layername(layer):
+ return layer.get(inkex.addNS('label','inkscape'))
+
+def die(msg):
+ inkex.errormsg(msg)
+ sys.exit(1)
+
diff --git a/extensions/fablabchemnitz/layer_clip/clip_above.inx b/extensions/fablabchemnitz/layer_clip/clip_above.inx
new file mode 100644
index 0000000..0e8ede4
--- /dev/null
+++ b/extensions/fablabchemnitz/layer_clip/clip_above.inx
@@ -0,0 +1,16 @@
+
+
+ Clip Layer Above
+ fablabchemnitz.de.layer_clip.clip_layer_above
+
+ path
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/layer_clip/clip_above.py b/extensions/fablabchemnitz/layer_clip/clip_above.py
new file mode 100644
index 0000000..1b91eb9
--- /dev/null
+++ b/extensions/fablabchemnitz/layer_clip/clip_above.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2012 Stuart Pernsteiner
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import clip
+import inkex
+import gettext
+_ = gettext.gettext
+
+class ClipAboveEffect(clip.ClipEffect):
+ def __init__(self):
+ clip.ClipEffect.__init__(self, '+')
+
+ def find_target(self, pathId):
+ layer = self.svg.selected[pathId].getparent()
+ parent = layer.getparent()
+ if parent.get(inkex.addNS('groupmode','inkscape')) != 'layer':
+ clip.die(_("Error: Layer '%s' has no parent layer") % clip.layername(layer))
+ if parent is None:
+ clip.die(_("Error: no parent layer in selection."))
+ children = parent.getchildren()
+ children.reverse()
+ last = children[0]
+ for child in children:
+ if child == layer and last is not None:
+ return last
+ last = child
+
+ clip.die(_("Error: There is no layer above '%s'") % clip.layername(layer))
+
+# Create effect instance and apply it.
+ClipAboveEffect().run()
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/layer_clip/clip_below.inx b/extensions/fablabchemnitz/layer_clip/clip_below.inx
new file mode 100644
index 0000000..31b6662
--- /dev/null
+++ b/extensions/fablabchemnitz/layer_clip/clip_below.inx
@@ -0,0 +1,16 @@
+
+
+ Clip Layer Below
+ fablabchemnitz.de.layer_clip.clip_layer_below
+
+ path
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/layer_clip/clip_below.py b/extensions/fablabchemnitz/layer_clip/clip_below.py
new file mode 100644
index 0000000..0ebbf7d
--- /dev/null
+++ b/extensions/fablabchemnitz/layer_clip/clip_below.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2012 Stuart Pernsteiner
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import clip
+import inkex
+import gettext
+_ = gettext.gettext
+
+class ClipBelowEffect(clip.ClipEffect):
+ def __init__(self):
+ clip.ClipEffect.__init__(self, '-')
+
+ def find_target(self, pathId):
+ layer = self.svg.selected[pathId].getparent()
+ children = layer.getchildren()
+ children.reverse()
+ last = None
+
+ #check if children contain a deeper layer
+ deeperLayers = []
+ for child in children:
+ if child.get(inkex.addNS('groupmode','inkscape')) == 'layer':
+ deeperLayers.append(child)
+
+ if len(deeperLayers) == 0:
+ clip.die(_("Error: There is no layer below '%s'") % clip.layername(layer))
+ for child in children:
+ if child != layer and last is not None:
+ return last
+ last = child
+
+ #clip.die(_("Error: There is no layer below '%s'") % clip.layername(layer))
+
+# Create effect instance and apply it.
+ClipBelowEffect().run()
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/layer_clip/clip_current.inx b/extensions/fablabchemnitz/layer_clip/clip_current.inx
new file mode 100644
index 0000000..22a27ac
--- /dev/null
+++ b/extensions/fablabchemnitz/layer_clip/clip_current.inx
@@ -0,0 +1,16 @@
+
+
+ Clip Layer Current
+ fablabchemnitz.de.layer_clip.clip_layer_current
+
+ path
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/layer_clip/clip_current.py b/extensions/fablabchemnitz/layer_clip/clip_current.py
new file mode 100644
index 0000000..b4bd6b2
--- /dev/null
+++ b/extensions/fablabchemnitz/layer_clip/clip_current.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2012 Stuart Pernsteiner
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import clip
+import inkex
+from lxml import etree
+class ClipCurrentEffect(clip.ClipEffect):
+ def __init__(self):
+ clip.ClipEffect.__init__(self, '*')
+
+ def find_target(self, pathId):
+ element = self.svg.selected[pathId]
+ parent = element.getparent()
+ if not isinstance (parent, inkex.ShapeElement):
+ clip.die(_("Error: Selected element's parent is not a shape element"))
+ return parent
+
+# Create effect instance and apply it.
+ClipCurrentEffect().run()
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/layer_clip/clip_fixtransform.inx b/extensions/fablabchemnitz/layer_clip/clip_fixtransform.inx
new file mode 100644
index 0000000..605cd5d
--- /dev/null
+++ b/extensions/fablabchemnitz/layer_clip/clip_fixtransform.inx
@@ -0,0 +1,16 @@
+
+
+ Clip Layer Fix Transform
+ fablabchemnitz.de.layer_clip.clip_layer_fix_transform
+
+ path
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/layer_clip/clip_fixtransform.py b/extensions/fablabchemnitz/layer_clip/clip_fixtransform.py
new file mode 100644
index 0000000..bd9d876
--- /dev/null
+++ b/extensions/fablabchemnitz/layer_clip/clip_fixtransform.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2012 Stuart Pernsteiner
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import sys
+import inkex
+
+class FixTransformEffect(inkex.EffectExtension):
+
+ def effect(self):
+ defs = self.document.xpath('//svg:defs/svg:clipPath/svg:use',
+ namespaces=inkex.NSS)
+
+ for d in defs:
+ if d.attrib.has_key('transform'):
+ del d.attrib['transform']
+
+# Create effect instance and apply it.
+FixTransformEffect().run()
+
+
diff --git a/extensions/fablabchemnitz/layer_clip/clip_parent.inx b/extensions/fablabchemnitz/layer_clip/clip_parent.inx
new file mode 100644
index 0000000..362d222
--- /dev/null
+++ b/extensions/fablabchemnitz/layer_clip/clip_parent.inx
@@ -0,0 +1,16 @@
+
+
+ Clip Layer Parent
+ fablabchemnitz.de.layer_clip.clip_layer_parent
+
+ path
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/layer_clip/clip_parent.py b/extensions/fablabchemnitz/layer_clip/clip_parent.py
new file mode 100644
index 0000000..8e4b183
--- /dev/null
+++ b/extensions/fablabchemnitz/layer_clip/clip_parent.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2012 Stuart Pernsteiner
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import clip
+import inkex
+import gettext
+_ = gettext.gettext
+
+class ClipParentEffect(clip.ClipEffect):
+ def __init__(self):
+ clip.ClipEffect.__init__(self, '**')
+
+ def find_target(self, pathId):
+ layer = self.svg.getElementById(pathId).getparent()
+ parent = layer.getparent()
+
+ if parent is None:
+ clip.die(_("Error: no parent in selection. Omit selecting layers from 'Layers and Objects' tab! Instead use 'XML Editor' or visual canvas selection."))
+
+ if parent.get(inkex.addNS('groupmode','inkscape')) != 'layer':
+ clip.die(_("Error: Layer '%s' has no parent layer") % clip.layername(layer))
+ return parent
+
+# Create effect instance and apply it.
+ClipParentEffect().run()
diff --git a/extensions/fablabchemnitz/layer_clip/clip_remove.inx b/extensions/fablabchemnitz/layer_clip/clip_remove.inx
new file mode 100644
index 0000000..b3e1dd5
--- /dev/null
+++ b/extensions/fablabchemnitz/layer_clip/clip_remove.inx
@@ -0,0 +1,16 @@
+
+
+ Clip Layer Remove
+ fablabchemnitz.de.layer_clip.clip_layer_remove
+
+ path
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/layer_clip/clip_remove.py b/extensions/fablabchemnitz/layer_clip/clip_remove.py
new file mode 100644
index 0000000..2c7a491
--- /dev/null
+++ b/extensions/fablabchemnitz/layer_clip/clip_remove.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2012 Stuart Pernsteiner
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import sys
+import inkex
+
+class RemoveClipEffect(inkex.Effect):
+ def __init__(self):
+ inkex.Effect.__init__(self)
+
+ def effect(self):
+ vals = list(self.svg.selected.values())
+ if len(vals) == 0:
+ inkex.errormsg(_("Error: No selection"))
+ exit(1)
+ path = vals[0]
+ layer = path.getparent()
+ if layer.attrib.has_key('clip-path'):
+ del layer.attrib['clip-path']
+
+ # Update layer name
+ label_attr = inkex.addNS('label', 'inkscape')
+ if layer.get(inkex.addNS('groupmode','inkscape')) != 'layer':
+ inkex.errormsg(_("Error: Selection's parent is not a layer"))
+ exit(1)
+
+ layer_label = layer.get(label_attr)
+ if layer_label[-4:] == " (C)":
+ layer.set(label_attr, layer_label[:-4])
+
+
+# Create effect instance and apply it.
+RemoveClipEffect().run()
+
+
diff --git a/extensions/fablabchemnitz/layer_clip/meta.json b/extensions/fablabchemnitz/layer_clip/meta.json
new file mode 100644
index 0000000..09f0b2d
--- /dev/null
+++ b/extensions/fablabchemnitz/layer_clip/meta.json
@@ -0,0 +1,22 @@
+[
+ {
+ "name": "",
+ "id": "fablabchemnitz.de.layer_clip.",
+ "path": "layer_clip",
+ "dependent_extensions": null,
+ "original_name": "",
+ "original_id": "org.pernsteiner.inkscape.layerclip.",
+ "license": "BSD-2-Clause License",
+ "license_url": "https://gitlab.com/jczapla79/inkscape-extension-layer-clip/-/blob/master/clip.py",
+ "comment": "Fixed and ported to Inkscape 1.2+ by Mario Voigt",
+ "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/layer_clip",
+ "fork_url": "https://gitlab.com/jczapla79/inkscape-extension-layer-clip",
+ "documentation_url": "https://stadtfabrikanten.org/display/IFM/Layer+Clip",
+ "inkscape_gallery_url": null,
+ "main_authors": [
+ "pernsteiger.org/Stuart Pernsteiner",
+ "gitlab.com/jczapla79",
+ "github.com/vmario89"
+ ]
+ }
+]
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/open_closed_path/meta.json b/extensions/fablabchemnitz/open_closed_path/meta.json
new file mode 100644
index 0000000..08bcb3b
--- /dev/null
+++ b/extensions/fablabchemnitz/open_closed_path/meta.json
@@ -0,0 +1,21 @@
+[
+ {
+ "name": "Open Closed Path",
+ "id": "fablabchemnitz.de.open_closed_path",
+ "path": "open_closed_path",
+ "dependent_extensions": null,
+ "original_name": "Open closed Path",
+ "original_id": "EllenWasbo.cutlings.OpenClosedPath",
+ "license": "GNU GPL v2",
+ "license_url": "https://gitlab.com/EllenWasbo/inkscape-extension-openpaths/-/blob/master/openClosedPath.py",
+ "comment": "",
+ "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/open_closed_path",
+ "fork_url": "https://gitlab.com/EllenWasbo/inkscape-extension-openpaths",
+ "documentation_url": "https://stadtfabrikanten.org/display/IFM/Open+Closed+Path",
+ "inkscape_gallery_url": null,
+ "main_authors": [
+ "gitlab.com/EllenWasbo",
+ "github.com/vmario89"
+ ]
+ }
+]
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/open_closed_path/open_closed_path.inx b/extensions/fablabchemnitz/open_closed_path/open_closed_path.inx
new file mode 100644
index 0000000..f8c8303
--- /dev/null
+++ b/extensions/fablabchemnitz/open_closed_path/open_closed_path.inx
@@ -0,0 +1,16 @@
+
+
+ Open Closed Path
+ fablabchemnitz.de.open_closed_path
+
+ path
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/open_closed_path/open_closed_path.py b/extensions/fablabchemnitz/open_closed_path/open_closed_path.py
new file mode 100644
index 0000000..dc0a59c
--- /dev/null
+++ b/extensions/fablabchemnitz/open_closed_path/open_closed_path.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python3
+# coding=utf-8
+#
+# Copyright (C) 2020 Ellen Wasboe, ellen@wasbo.net
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Remove all z-s from all selected paths
+My purpose: to open paths of single line fonts with a temporary closing to fit into .ttf/.otf format files
+"""
+
+import inkex, re
+from inkex import PathElement
+
+class OpenClosedPath(inkex.EffectExtension):
+# Extension to open a closed path by z or by last node
+
+ def effect(self):
+ elements = self.svg.selection.filter(PathElement).values()
+ for elem in elements:
+ pp = elem.path.to_absolute() #remove transformation matrix
+ elem.path = re.sub(r"Z","",str(pp))
+
+ sp = elem.path.to_superpath()
+ if sp[0] == sp[-1]: #if first is last point the path is also closed. The "Z" command is not required
+ raw = elem.path.to_arrays()
+ del raw[-1] #delete last point in path
+ elem.path = raw
+
+if __name__ == '__main__':
+ OpenClosedPath().run()
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/remove_duplicate_guides/meta.json b/extensions/fablabchemnitz/remove_duplicate_guides/meta.json
new file mode 100644
index 0000000..eaed4b3
--- /dev/null
+++ b/extensions/fablabchemnitz/remove_duplicate_guides/meta.json
@@ -0,0 +1,21 @@
+[
+ {
+ "name": "Remove Duplicate Guides",
+ "id": "fablabchemnitz.de.remove_duplicate_guides",
+ "path": "remove_duplicate_guides",
+ "dependent_extensions": null,
+ "original_name": "Remove Duplicate Guides",
+ "original_id": "phillips.effect.removeduplicateguides",
+ "license": "GNU GPL v2",
+ "license_url": "https://sourceforge.net/p/razorfoss/svn/HEAD/tree/trunk/Inkscape/RemoveDuplicateGuides/RemoveDuplicateGuides.py",
+ "comment": "",
+ "source_url": "https://stadtfabrikanten.org/display/IFM/Remove+Duplicate+Guides",
+ "fork_url": "https://sourceforge.net/p/razorfoss/svn/HEAD/tree/trunk/Inkscape/RemoveDuplicateGuides",
+ "documentation_url": "https://stadtfabrikanten.org/display/IFM/Remove+Duplicate+Guides",
+ "inkscape_gallery_url": null,
+ "main_authors": [
+ "Luke Phillips:lukerazor@hotmail.com",
+ "github.com/vmario89"
+ ]
+ }
+]
diff --git a/extensions/fablabchemnitz/remove_duplicate_guides/remove_duplicate_guides.inx b/extensions/fablabchemnitz/remove_duplicate_guides/remove_duplicate_guides.inx
new file mode 100644
index 0000000..54411e0
--- /dev/null
+++ b/extensions/fablabchemnitz/remove_duplicate_guides/remove_duplicate_guides.inx
@@ -0,0 +1,16 @@
+
+
+ Remove Duplicate Guides
+ fablabchemnitz.remove_duplicate_guides
+
+ all
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/remove_duplicate_guides/remove_duplicate_guides.py b/extensions/fablabchemnitz/remove_duplicate_guides/remove_duplicate_guides.py
new file mode 100644
index 0000000..1d23c9e
--- /dev/null
+++ b/extensions/fablabchemnitz/remove_duplicate_guides/remove_duplicate_guides.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python3
+
+
+# Copyright 2016 Luke Phillips (lukerazor@hotmail.com)
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+#along with this program; if not, write to the Free Software
+#Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+# Extension dirs
+# linux:~/.config/inkscape/extensions
+# windows: [Drive]:\Program Files\Inkscape\share\extensions
+
+import inkex
+import math
+inkex.NSS[u'cs'] = u'http://www.razorfoss.org/tuckboxextension/'
+
+def printDebug(string):
+ inkex.utils.debug( _(str(string)) )
+
+def isAlmost0(num):
+ return (abs(num) < 0.000001)
+
+def formatFloat(f):
+ if f == None:
+ return "None"
+
+ return "{:.3f}".format(f)
+
+class Point():
+ def __init__(self, x, y):
+ self.x = x
+ self.y = y
+
+ def rotate(self, angle, origin):
+ """
+ Rotate a point counterclockwise by a given angle around a given origin.
+
+ The angle should be given in degrees.
+ """
+ rads = math.radians(angle)
+ newX = origin.x + math.cos(rads) * (self.x - origin.x) - math.sin(rads) * (self.y - origin.y)
+ newY = origin.y + math.sin(rads) * (self.x - origin.x) + math.cos(rads) * (self.y - origin.y)
+
+ return Point(newX, newY)
+
+ def add(self, point):
+ return Point(self.x + point.x, self.y + point.y)
+
+ @staticmethod
+ def parsePoint(pointString):
+ x, y = map(lambda v: float(v), pointString.split(","))
+ return Point(x, y)
+
+ @staticmethod
+ def parse(pointString, orientationString=None):
+ p1 = Point.parsePoint(pointString)
+ p = Point(p1.x, p1.y)
+
+ if orientationString != None:
+ po = Point.parsePoint(orientationString)
+ p = p1.add(po.rotate(270, Point(0, 0)))
+
+ return p
+
+class GuideDefiniton():
+ def __init__(self, xmlNode):
+ self.node = xmlNode
+ self.id = self.node.attrib["id"]
+
+ self.p1 = Point.parse(self.node.attrib["position"])
+ self.p2 = Point.parse(self.node.attrib["position"], self.node.attrib["orientation"])
+
+ # calculate the slope and y intercept
+ self.slope = None # default to vertical line
+ self.yIntercept = None
+ self.xIntercept = self.p1.x
+
+ if not isAlmost0(self.p1.x - self.p2.x): # not vertical
+ self.slope = (self.p2.y - self.p1.y)/(self.p2.x - self.p1.x)
+
+ self.yIntercept = self.p1.y - (self.slope*self.p1.x)
+ if not isAlmost0(self.slope): # horizontal
+ self.xIntercept = -(self.yIntercept)/self.slope
+ else:
+ self.slope=0
+ self.xIntercept = None
+
+ #printDebug(self.toString())
+
+ def interceptSerial(self):
+ s = "{},{}".format(formatFloat(self.xIntercept), formatFloat(self.yIntercept))
+ return s
+
+ def toString(self):
+ #return "{} - p1:({}, {}) p2:({}, {}) - {}, {}, {}".format(self.id, self.p1.x, self.p1.y, self.p2.x, self.p2.y, self.slope, self.xIntercept, self.yIntercept)
+ return "{} - {}".format(self.id, self.interceptSerial())
+
+class RemoveDuplicateGuides(inkex.EffectExtension):
+
+ def effect(self):
+ # enumerate all guides
+ guideNodes = list(map(lambda n: GuideDefiniton(n), self.document.xpath('//sodipodi:guide',namespaces=inkex.NSS)))
+
+ # sort guides into match xy intercept groups
+ groups = {}
+ for guide in guideNodes:
+ serial = guide.interceptSerial()
+ if serial not in groups:
+ groups[serial] = []
+
+ groups[serial].append(guide)
+
+
+ # now remove all the excess guides
+ for serial in groups:
+ for guide in groups[serial][1:]: # keep the first member of group
+ guide.node.delete()
+
+if __name__ == '__main__':
+ effect = RemoveDuplicateGuides().run()
\ No newline at end of file