From 985f90157934e63e176afb890d0ca8be4a296d9a Mon Sep 17 00:00:00 2001 From: Mario Voigt Date: Thu, 29 Sep 2022 23:16:09 +0200 Subject: [PATCH] readded several extensions --- .../box_maker_conical/box_maker_conical.inx | 30 + .../box_maker_conical/box_maker_conical.py | 353 +++++++++ .../box_maker_conical/meta.json | 21 + .../box_maker_elliptical_cone/.gitignore | 1 + .../box_maker_elliptical_cone.inx | 33 + .../box_maker_elliptical_cone.py | 704 +++++++++++++++++ .../box_maker_elliptical_cone/meta.json | 21 + .../box_maker_living_hinge.inx | 51 ++ .../box_maker_living_hinge.py | 511 +++++++++++++ .../box_maker_living_hinge/meta.json | 22 + .../box_maker_mehr_boxes.inx | 74 ++ .../box_maker_mehr_boxes.py | 188 +++++ .../box_maker_mehr_boxes/mehr_plate.py | 92 +++ .../box_maker_mehr_boxes/meta.json | 21 + .../box_maker_tabbed/box_maker_tabbed.inx | 92 +++ .../box_maker_tabbed/box_maker_tabbed.py | 712 ++++++++++++++++++ .../fablabchemnitz/box_maker_tabbed/meta.json | 33 + .../box_maker_tabbed/schroffmaker.inx | 41 + .../label_guides/label_guides.inx | 158 ++++ .../label_guides/label_guides.py | 559 ++++++++++++++ .../fablabchemnitz/label_guides/meta.json | 22 + extensions/fablabchemnitz/layer_clip/clip.py | 73 ++ .../fablabchemnitz/layer_clip/clip_above.inx | 16 + .../fablabchemnitz/layer_clip/clip_above.py | 53 ++ .../fablabchemnitz/layer_clip/clip_below.inx | 16 + .../fablabchemnitz/layer_clip/clip_below.py | 57 ++ .../layer_clip/clip_current.inx | 16 + .../fablabchemnitz/layer_clip/clip_current.py | 41 + .../layer_clip/clip_fixtransform.inx | 16 + .../layer_clip/clip_fixtransform.py | 42 ++ .../fablabchemnitz/layer_clip/clip_parent.inx | 16 + .../fablabchemnitz/layer_clip/clip_parent.py | 47 ++ .../fablabchemnitz/layer_clip/clip_remove.inx | 16 + .../fablabchemnitz/layer_clip/clip_remove.py | 57 ++ .../fablabchemnitz/layer_clip/meta.json | 22 + .../fablabchemnitz/open_closed_path/meta.json | 21 + .../open_closed_path/open_closed_path.inx | 16 + .../open_closed_path/open_closed_path.py | 43 ++ .../remove_duplicate_guides/meta.json | 21 + .../remove_duplicate_guides.inx | 16 + .../remove_duplicate_guides.py | 130 ++++ 41 files changed, 4474 insertions(+) create mode 100644 extensions/fablabchemnitz/box_maker_conical/box_maker_conical.inx create mode 100644 extensions/fablabchemnitz/box_maker_conical/box_maker_conical.py create mode 100644 extensions/fablabchemnitz/box_maker_conical/meta.json create mode 100644 extensions/fablabchemnitz/box_maker_elliptical_cone/.gitignore create mode 100644 extensions/fablabchemnitz/box_maker_elliptical_cone/box_maker_elliptical_cone.inx create mode 100644 extensions/fablabchemnitz/box_maker_elliptical_cone/box_maker_elliptical_cone.py create mode 100644 extensions/fablabchemnitz/box_maker_elliptical_cone/meta.json create mode 100644 extensions/fablabchemnitz/box_maker_living_hinge/box_maker_living_hinge.inx create mode 100644 extensions/fablabchemnitz/box_maker_living_hinge/box_maker_living_hinge.py create mode 100644 extensions/fablabchemnitz/box_maker_living_hinge/meta.json create mode 100644 extensions/fablabchemnitz/box_maker_mehr_boxes/box_maker_mehr_boxes.inx create mode 100644 extensions/fablabchemnitz/box_maker_mehr_boxes/box_maker_mehr_boxes.py create mode 100644 extensions/fablabchemnitz/box_maker_mehr_boxes/mehr_plate.py create mode 100644 extensions/fablabchemnitz/box_maker_mehr_boxes/meta.json create mode 100644 extensions/fablabchemnitz/box_maker_tabbed/box_maker_tabbed.inx create mode 100644 extensions/fablabchemnitz/box_maker_tabbed/box_maker_tabbed.py create mode 100644 extensions/fablabchemnitz/box_maker_tabbed/meta.json create mode 100644 extensions/fablabchemnitz/box_maker_tabbed/schroffmaker.inx create mode 100644 extensions/fablabchemnitz/label_guides/label_guides.inx create mode 100644 extensions/fablabchemnitz/label_guides/label_guides.py create mode 100644 extensions/fablabchemnitz/label_guides/meta.json create mode 100644 extensions/fablabchemnitz/layer_clip/clip.py create mode 100644 extensions/fablabchemnitz/layer_clip/clip_above.inx create mode 100644 extensions/fablabchemnitz/layer_clip/clip_above.py create mode 100644 extensions/fablabchemnitz/layer_clip/clip_below.inx create mode 100644 extensions/fablabchemnitz/layer_clip/clip_below.py create mode 100644 extensions/fablabchemnitz/layer_clip/clip_current.inx create mode 100644 extensions/fablabchemnitz/layer_clip/clip_current.py create mode 100644 extensions/fablabchemnitz/layer_clip/clip_fixtransform.inx create mode 100644 extensions/fablabchemnitz/layer_clip/clip_fixtransform.py create mode 100644 extensions/fablabchemnitz/layer_clip/clip_parent.inx create mode 100644 extensions/fablabchemnitz/layer_clip/clip_parent.py create mode 100644 extensions/fablabchemnitz/layer_clip/clip_remove.inx create mode 100644 extensions/fablabchemnitz/layer_clip/clip_remove.py create mode 100644 extensions/fablabchemnitz/layer_clip/meta.json create mode 100644 extensions/fablabchemnitz/open_closed_path/meta.json create mode 100644 extensions/fablabchemnitz/open_closed_path/open_closed_path.inx create mode 100644 extensions/fablabchemnitz/open_closed_path/open_closed_path.py create mode 100644 extensions/fablabchemnitz/remove_duplicate_guides/meta.json create mode 100644 extensions/fablabchemnitz/remove_duplicate_guides/remove_duplicate_guides.inx create mode 100644 extensions/fablabchemnitz/remove_duplicate_guides/remove_duplicate_guides.py 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