readded several extensions

This commit is contained in:
Mario Voigt 2022-09-29 23:16:09 +02:00
parent 7e8872efcd
commit 985f901579
41 changed files with 4474 additions and 0 deletions

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Box Maker - Conical</name>
<id>fablabchemnitz.de.box_maker_conical</id>
<param name="unit" type="optiongroup" appearance="combo" gui-text="Unit">
<option value="mm">mm</option>
<option value="cm">cm</option>
<option value="in">in</option>
<option value="pt">pt</option>
<option value="px">px</option>
<option value="pc">pc</option>
</param>
<param name="thickness" type="float" min="1.0" max="10.0" gui-text="Material thickness">3.0</param>
<param name="d1" type="float" min="30.0" max="1000.0" gui-text="Diameter small circle">100.0</param>
<param name="d2" type="float" min="31.0" max="1000.0" gui-text="Diameter large circle">150.0</param>
<param name="zc" type="float" min="15.0" max="1000.0" gui-text="Cone height">50.0</param>
<param name="nb_pieces" type="int" min="1" max="100" gui-text="# pieces for cone">1</param>
<param name="inner_size" type="bool" gui-text="Internal dimensions">true</param>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz Boxes/Papercraft">
<submenu name="Finger-jointed/Tabbed Boxes"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">box_maker_conical.py</command>
</script>
</inkscape-extension>

View File

@ -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()

View File

@ -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"
]
}
]

View File

@ -0,0 +1 @@
/DebugEllConicBox.txt

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Box Maker - Elliptical Cone</name>
<id>fablabchemnitz.de.box_maker_elliptical_cone</id>
<param name="unit" type="optiongroup" appearance="combo" gui-text="Unit">
<option value="mm">mm</option>
<option value="cm">cm</option>
<option value="in">in</option>
<option value="pt">pt</option>
<option value="px">px</option>
<option value="pc">pc</option>
</param>
<param name="thickness" type="float" min="1.0" max="10.0" gui-text="Material thickness">3.0</param>
<param name="d1" type="float" min="30.0" max="1000.0" gui-text="Smallest ellipse diameter (longest axis)">60.0</param>
<param name="d2" type="float" min="29.0" max="1000.0" gui-text="Largest ellipse diameter (longest axis)">90.0</param>
<param name="eccentricity" type="float" min="0.01" max="1.0" gui-text="Ellipse eccentricity, ratio minor vs major axis">0.5</param>
<param name="zc" type="float" min="15.0" max="1000.0" gui-text="Cone height">50.0</param>
<param name="notch_interval" type="int" min="2" max="10" gui-text="Interval between notches, integer in mm">2</param>
<param name="cut_position" type="int" min="0" max="360" gui-text="Cut position angle in degrees">0</param>
<param name="inner_size" type="bool" gui-text="Internal dimensions">true</param>
<param name="Mode_Debug" type="bool" gui-text="Debugging information output">true</param>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz Boxes/Papercraft">
<submenu name="Finger-jointed/Tabbed Boxes"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">box_maker_elliptical_cone.py</command>
</script>
</inkscape-extension>

View File

@ -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()

View File

@ -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"
]
}
]

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Box Maker - Living Hinge</name>
<id>fablabchemnitz.de.box_maker_living_hinge</id>
<param name="hingeOpt" gui-text="Hinge Type" type="optiongroup" appearance="combo">
<option value="0">Standard parallel slit</option>
<option value="1">Single spiral</option>
<option value="2">Double spiral</option>
<option value="3">Parallel snake</option>
<option value="4">Perpendicular snake</option>
<option value="5">Double perpendicular snake</option>
</param>
<param name="unit" gui-text="Unit" type="optiongroup" appearance="combo">
<option value="mm">mm</option>
<option value="cm">cm</option>
<option value="in">in</option>
</param>
<param name="inside" type="optiongroup" appearance="combo" gui-text="Box Dimensions">
<option value="1">Inside</option>
<option value="0">Outside</option>
</param>
<param name="length" type="float" precision="3" min="0.0" max="10000.0" gui-text="Length">40.0</param>
<param name="width" type="float" precision="3" min="0.0" max="10000.0" gui-text="Width">80.0</param>
<param name="height" type="float" precision="3" min="0.0" max="10000.0" gui-text="Height">50.0</param>
<param name="tab" type="float" precision="2" min="0.0" max="10000.0" gui-text="Minimum/Preferred Tab Width">6.0</param>
<param name="equal" type="optiongroup" appearance="combo" gui-text="Tab Width">
<option value="0">Fixed</option>
<option value="1">Proportional</option>
</param>
<param name="thickness" type="float" precision="2" min="0.0" max="10000.0" gui-text="Material Thickness">3.0</param>
<param name="kerf" type="float" precision="3" min="0.0" max="10000.0" gui-text="Kerf (cut width)">0.1</param>
<param name="clearance" type="float" precision="3" min="0.0" max="10000.0" gui-text="Clearance">0.01</param>
<param name="style" gui-text="Layout/Style" type="optiongroup" appearance="combo">
<option value="0">Diagrammatic</option>
<option value="1">In-line(compact)</option>
</param>
<param name="spacing" type="float" precision="2" min="0.0" max="10000.0" gui-text="Space Between Parts">1.0</param>
<param name="hingeThick" type="float" precision="3" min="0.0" max="10000.0" gui-text="Hinge remaining wood thickness">2.0</param>
<param name="thumbTab" type="float" precision="3" min="0.0" max="10000.0" gui-text="Lid thumb tab length (0 for no tab)">15.000</param>
<effect needs-live-preview="true">
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz Boxes/Papercraft">
<submenu name="Finger-jointed/Tabbed Boxes"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">box_maker_living_hinge.py</command>
</script>
</inkscape-extension>

View File

@ -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 <http://www.gnu.org/licenses/>.
'''
__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 nomTab<thickness:
inkex.errormsg('Error: Tab size too small')
error=1
if thickness==0:
inkex.errormsg('Error: Thickness is zero')
error=1
if thickness>min(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<kerf: #if spacing is less then kerf, the laser cuts will overlap and blast meaningful material.
inkex.errormsg('Error: Spacing too small')
error=1
if error: exit()
# layout format:(rootx),(rooty),Xlength,Ylength,tabInfo
# root= (spacing,X,Y,Z) * values in tuple
# tabInfo= <abcd> 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()

View File

@ -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"
]
}
]

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Box Maker - Mehr Boxes</name>
<id>fablabchemnitz.de.box_maker_mehr_boxes</id>
<param name="page" type="notebook">
<page name="page_1" gui-text="Sizes">
<param name="unit" gui-text="Unit" type="optiongroup" appearance="combo">
<option value="mm">mm</option>
<option value="cm">cm</option>
<option value="in">in</option>
</param>
<label appearance="header">Size Options</label>
<param name="inside" type="optiongroup" appearance="combo" gui-text="Box Dimensions">
<option value="1">Inside</option>
<option value="0">Outside</option>
</param>
<param name="X_size" type="float" precision="2" min="0.0" max="1000000.0" gui-text="X axis lenght">100.0</param>
<param name="Y_size" type="float" precision="2" min="0.0" max="1000000.0" gui-text="Y axis lenght">100.0</param>
<param name="Z_size" type="float" precision="2" min="0.0" max="1000000.0" gui-text="Z axis lenght">100.0</param>
<label appearance="header">Tab Options</label>
<param name="tab_mode" gui-text="Tab Mode" type="optiongroup" appearance="combo">
<option value="number">amount</option>
<option value="size">size</option>
</param>
<param name="tab_size" type="float" precision="2" min="0.0" max="10000.0" gui-text="target tab size">4.0</param>
<param name="X_tabs" type="int" min="0" max="100000" gui-text="Tabs in X axis">3</param>
<param name="Y_tabs" type="int" min="0" max="100000" gui-text="Tabs in Y axis">3</param>
<param name="Z_tabs" type="int" min="0" max="100000" gui-text="Tabs in Z axis">3</param>
<label appearance="header">Material Options</label>
<param name="thickness" type="float" precision="2" min="0.0" max="10000.0" gui-text="Material Thickness">4.0</param>
<param name="kerf" type="float" precision="3" min="0.0" max="10000.0" gui-text="Beam diameter">0.2</param>
<param name="spaceing" type="float" precision="2" min="0.0" max="10000.0" gui-text="Space Between Parts">1.0</param>
</page>
<page name="page_2" gui-text="Draw sides">
<param name="d_top" type="bool" gui-text="Draw top side (XxY)">true</param>
<param name="d_bottom" type="bool" gui-text="Draw bottom side (XxY)">true</param>
<param name="d_left" type="bool" gui-text="Draw left side (YxZ)">true</param>
<param name="d_right" type="bool" gui-text="Draw right side(YxZ)">true</param>
<param name="d_front" type="bool" gui-text="Draw front side (XxZ)">true</param>
<param name="d_back" type="bool" gui-text="Draw back side (XxZ)">true</param>
</page>
<page name="page_3" gui-text="Compartments">
<label appearance="header">Compartments on the X Axis</label>
<param name="X_compartments" type="int" min="1" max="100000" gui-text="X Compartments">1</param>
<param name="X_mode" type="optiongroup" appearance="combo" gui-text="Mode">
<option value="even">even</option>
<option value="relative">relative</option>
<option value="absolut">absolut</option>
</param>
<param name="X_divisions" type="string" gui-text="Distances">20.0;40.0</param>
<param name="X_fit" type="bool" gui-text="fit axis lenght to compartments">false</param>
<label appearance="header">Compartments on the Y Axis</label>
<param name="Y_compartments" type="int" min="1" max="100000" gui-text="Y Compartments">1</param>
<param name="Y_mode" type="optiongroup" appearance="combo" gui-text="Mode">
<option value="even">even</option>
<option value="relative">relative</option>
<option value="absolut">absolut</option>
</param>
<param name="Y_divisions" type="string" gui-text="Distances">20.0;40.0</param>
<param name="Y_fit" type="bool" gui-text="fit axis lenght to compartments">false</param>
</page>
</param>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz Boxes/Papercraft">
<submenu name="Finger-jointed/Tabbed Boxes"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">box_maker_mehr_boxes.py</command>
</script>
</inkscape-extension>

View File

@ -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 <http://www.gnu.org/licenses/>.
'''
__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)<self.options.X_compartments+1):#making the list long enought for relative offsets
X_divisions_distances+=X_divisions_distances
for i in range(1,self.options.X_compartments):#offset to absolut distances
X_divisions_distances[i]+=X_divisions_distances[i-1]+thickness-kerf
X_divisions_distances=X_divisions_distances[0:self.options.X_compartments]#cutting excesive lenght off
if(X_divisions_distances[-2]+thickness>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)<self.options.Y_compartments+1):#making the list long enought for relative offsets
Y_divisions_distances+=Y_divisions_distances
for i in range(1,self.options.Y_compartments):#offset to absolut distances
Y_divisions_distances[i]+=Y_divisions_distances[i-1]+thickness-kerf
Y_divisions_distances=Y_divisions_distances[0:self.options.Y_compartments]#cutting excesive lenght off
if(Y_divisions_distances[-2]+thickness>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()

View File

@ -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

View File

@ -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"
]
}
]

View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Box Maker - Tabbed</name>
<id>fablabchemnitz.de.boxmaker_tabbed.box_maker_tabbed</id>
<hbox>
<vbox>
<label>Dimensions</label>
<separator />
<param name="unit" gui-text=" Units" type="optiongroup" appearance="combo">
<option value="mm">mm</option>
<option value="cm">cm</option>
<option value="in">in</option>
</param>
<param name="inside" type="optiongroup" gui-text=" Box Dimensions" appearance="combo">
<option value="1">Inside</option>
<option value="0">Outside</option>
</param>
<param name="length" type="float" precision="3" min="0.0" max="10000.0" gui-text=" Length">180</param>
<param name="width" type="float" precision="3" min="0.0" max="10000.0" gui-text=" Width">240</param>
<param name="depth" type="float" precision="3" min="0.0" max="10000.0" gui-text=" Height">50</param>
<spacer />
<label>Tabs</label>
<separator />
<param name="equal" type="optiongroup" appearance="combo" gui-text=" Width">
<option value="0">Fixed</option>
<option value="1">Proportional</option>
</param>
<param name="tab" type="float" precision="2" min="0.0" max="10000.0" gui-text=" Min/Preferred Width">3.0</param>
<param name="tabtype" type="optiongroup" gui-text=" Type" appearance="combo">
<option value="0">Regular (Laser)</option>
<option value="1">Dogbone (Mill)</option>
</param>
<param name="tabsymmetry" type="optiongroup" gui-text=" Symmetry" appearance="combo">
<option value="0">XY Symmetric</option>
<option value="1">Rotate Symmetric</option>
<!--option value="2">Antisymmetric</option-->
</param>
<param name="dimpleheight" type="float" precision="2" min="0.0" max="10000.0" gui-text=" Dimple Height">0.0</param>
<param name="dimplelength" type="float" precision="2" min="0.0" max="10000.0" gui-text=" Dimple Length">0.0</param>
</vbox>
<spacer />
<separator />
<spacer />
<vbox>
<label>Line and kerf</label>
<separator />
<param name="hairline" type="optiongroup" gui-text=" Line Thickness" appearance="combo">
<option value="0">Default</option>
<option value="1">Hairline (0.002" for Epilog)</option>
</param>
<param name="thickness" type="float" precision="2" min="0.0" max="10000.0" gui-text=" Material Thickness">3.0</param>
<param name="kerf" type="float" precision="3" min="0.0" max="10000.0" gui-text=" Kerf (cut width)">0.1</param>
<param name="clearance" type="float" precision="3" min="0.0" max="10000.0" gui-text=" Joint clearance">0.01</param>
<spacer />
<label>Layout</label>
<separator />
<param name="style" gui-text=" Layout" type="optiongroup" appearance="combo">
<option value="1">Diagramatic</option>
<option value="2">3 piece</option>
<option value="3">Inline(compact)</option>
</param>
<param name="boxtype" gui-text=" Box Type" type="optiongroup" appearance="combo">
<option value="1">Fully enclosed</option>
<option value="2">One side open (LxW)</option>
<option value="3">Two sides open (LxW and LxH)</option>
<option value="4">Three sides open (LxW, LxH, HxW)</option>
<option value="5">Opposite ends open (LxW)</option>
<option value="6">Two panels only (LxW and LxH)</option>
</param>
<param name="div_l" type="int" min="0" max="10" gui-text=" Dividers (Length axis)">2</param>
<param name="div_w" type="int" min="0" max="10" gui-text=" Dividers (Width axis)">3</param>
<param name="keydiv" gui-text=" Key the dividers into" type="optiongroup" appearance="combo">
<option value="3">None</option>
<option value="2">Walls</option>
<option value="1">Floor / Ceiling</option>
<option value="0">All sides</option>
</param>
<param name="spacing" type="float" precision="2" min="0.0" max="10000.0" gui-text=" Space Between Parts">1.0</param>
</vbox>
</hbox>
<effect needs-live-preview="true">
<object-type>path</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz Boxes/Papercraft">
<submenu name="Finger-jointed/Tabbed Boxes"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">box_maker_tabbed.py</command>
</script>
</inkscape-extension>

View File

@ -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 <http://www.gnu.org/licenses/>.
'''
__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 nomTab<thickness:
inkex.errormsg(_('Error: Tab size too small'))
error=1
if thickness==0:
inkex.errormsg(_('Error: Thickness is zero'))
error=1
if thickness>min(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<kerf:
inkex.errormsg(_('Error: Spacing too small'))
error=1
if error: exit()
# For code spacing consistency, we use two-character abbreviations for the six box faces,
# where each abbreviation is the first and last letter of the face name:
# tp=top, bm=bottom, ft=front, bk=back, lt=left, rt=right
# Determine which faces the box has based on the box type
hasTp=hasBm=hasFt=hasBk=hasLt=hasRt = True
if boxtype==2: hasTp=False
elif boxtype==3: hasTp=hasFt=False
elif boxtype==4: hasTp=hasFt=hasRt=False
elif boxtype==5: hasTp=hasBm=False
elif boxtype==6: hasTp=hasFt=hasBk=hasRt=False
# else boxtype==1, full box, has all sides
# Determine where the tabs go based on the tab style
if tabSymmetry==2: # Antisymmetric (deprecated)
tpTabInfo=0b0110
bmTabInfo=0b1100
ltTabInfo=0b1100
rtTabInfo=0b0110
ftTabInfo=0b1100
bkTabInfo=0b1001
elif tabSymmetry==1: # Rotationally symmetric (Waffle-blocks)
tpTabInfo=0b1111
bmTabInfo=0b1111
ltTabInfo=0b1111
rtTabInfo=0b1111
ftTabInfo=0b1111
bkTabInfo=0b1111
else: # XY symmetric
tpTabInfo=0b0000
bmTabInfo=0b0000
ltTabInfo=0b1111
rtTabInfo=0b1111
ftTabInfo=0b1010
bkTabInfo=0b1010
def fixTabBits(tabbed, tabInfo, bit):
newTabbed = tabbed & ~bit
if inside:
newTabInfo = tabInfo | bit # set bit to 1 to use tab base line
else:
newTabInfo = tabInfo & ~bit # set bit to 0 to use tab tip line
return newTabbed, newTabInfo
# Update the tab bits based on which sides of the box don't exist
tpTabbed=bmTabbed=ltTabbed=rtTabbed=ftTabbed=bkTabbed=0b1111
if not hasTp:
bkTabbed, bkTabInfo = fixTabBits(bkTabbed, bkTabInfo, 0b0010)
ftTabbed, ftTabInfo = fixTabBits(ftTabbed, ftTabInfo, 0b1000)
ltTabbed, ltTabInfo = fixTabBits(ltTabbed, ltTabInfo, 0b0001)
rtTabbed, rtTabInfo = fixTabBits(rtTabbed, rtTabInfo, 0b0100)
tpTabbed=0
if not hasBm:
bkTabbed, bkTabInfo = fixTabBits(bkTabbed, bkTabInfo, 0b1000)
ftTabbed, ftTabInfo = fixTabBits(ftTabbed, ftTabInfo, 0b0010)
ltTabbed, ltTabInfo = fixTabBits(ltTabbed, ltTabInfo, 0b0100)
rtTabbed, rtTabInfo = fixTabBits(rtTabbed, rtTabInfo, 0b0001)
bmTabbed=0
if not hasFt:
tpTabbed, tpTabInfo = fixTabBits(tpTabbed, tpTabInfo, 0b1000)
bmTabbed, bmTabInfo = fixTabBits(bmTabbed, bmTabInfo, 0b1000)
ltTabbed, ltTabInfo = fixTabBits(ltTabbed, ltTabInfo, 0b1000)
rtTabbed, rtTabInfo = fixTabBits(rtTabbed, rtTabInfo, 0b1000)
ftTabbed=0
if not hasBk:
tpTabbed, tpTabInfo = fixTabBits(tpTabbed, tpTabInfo, 0b0010)
bmTabbed, bmTabInfo = fixTabBits(bmTabbed, bmTabInfo, 0b0010)
ltTabbed, ltTabInfo = fixTabBits(ltTabbed, ltTabInfo, 0b0010)
rtTabbed, rtTabInfo = fixTabBits(rtTabbed, rtTabInfo, 0b0010)
bkTabbed=0
if not hasLt:
tpTabbed, tpTabInfo = fixTabBits(tpTabbed, tpTabInfo, 0b0100)
bmTabbed, bmTabInfo = fixTabBits(bmTabbed, bmTabInfo, 0b0001)
bkTabbed, bkTabInfo = fixTabBits(bkTabbed, bkTabInfo, 0b0001)
ftTabbed, ftTabInfo = fixTabBits(ftTabbed, ftTabInfo, 0b0001)
ltTabbed=0
if not hasRt:
tpTabbed, tpTabInfo = fixTabBits(tpTabbed, tpTabInfo, 0b0001)
bmTabbed, bmTabInfo = fixTabBits(bmTabbed, bmTabInfo, 0b0100)
bkTabbed, bkTabInfo = fixTabBits(bkTabbed, bkTabInfo, 0b0100)
ftTabbed, ftTabInfo = fixTabBits(ftTabbed, ftTabInfo, 0b0100)
rtTabbed=0
# Layout positions are specified in a grid of rows and columns
row0=(1,0,0,0) # top row
row1y=(2,0,1,0) # second row, offset by Y
row1z=(2,0,0,1) # second row, offset by Z
row2=(3,0,1,1) # third row, always offset by Y+Z
col0=(1,0,0,0) # left column
col1x=(2,1,0,0) # second column, offset by X
col1z=(2,0,0,1) # second column, offset by Z
col2xx=(3,2,0,0) # third column, offset by 2*X
col2xz=(3,1,0,1) # third column, offset by X+Z
col3xzz=(4,1,0,2) # fourth column, offset by X+2*Z
col3xxz=(4,2,0,1) # fourth column, offset by 2*X+Z
col4=(5,2,0,2) # fifth column, always offset by 2*X+2*Z
col5=(6,3,0,2) # sixth column, always offset by 3*X+2*Z
# layout format:(rootx),(rooty),Xlength,Ylength,tabInfo,tabbed,pieceType
# root= (spacing,X,Y,Z) * values in tuple
# tabInfo= <abcd> 0=holes 1=tabs
# tabbed= <abcd> 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()

View File

@ -0,0 +1,33 @@
[
{
"name": "Box Maker - <various>",
"id": "fablabchemnitz.de.box_maker_tabbed.<various>",
"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"
]
}
]

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Box Maker - Schroff</name>
<id>fablabchemnitz.de.boxmaker_tabbed.box_maker_schroff</id>
<param name="unit" type="string" gui-hidden="true">mm</param>
<param name="inside" type="string" gui-hidden="true">1</param>
<param name="schroff" type="int" gui-hidden="true">1</param>
<param name="rows" type="int" min="1" max="6" gui-text="Number of 3U rows:">1</param>
<param name="hp" type="int" min="4" max="168" gui-text="Width (TE/HP units):">84</param>
<param name="length" type="float" precision="3" gui-hidden="true">0.0</param>
<param name="width" type="float" precision="3" gui-hidden="true">0.0</param>
<param name="depth" type="float" precision="3" min="30" max="300" gui-text="Depth:">65</param>
<!-- these defaults are suitable for the rails sold by Elby Designs -->
<param name="rail_height" type="float" precision="3" min="7.0" max="10.0" gui-text="Rail height:">10.0</param>
<param name="rail_mount_depth" type="float" precision="3" min="5" max="30" gui-text="Rail mounting hole depth:">17.4</param>
<param name="rail_mount_centre_offset" type="float" precision="3" min="0.0" max="5.0" gui-text="Rail mount hole centre-offset:">0.0</param>
<param name="row_spacing" type="float" precision="3" min="0.0" max="50.0" gui-text="Spacing between rows:">0.0</param>
<param name="tab" type="float" precision="2" min="0.0" max="10000.0" gui-text="Minimum/Prefered Tab Width">3.0</param>
<param name="equal" type="optiongroup" appearance="combo" gui-text="Tab Width">
<option value="0">Fixed</option>
<option value="1">Proportional</option>
</param>
<param name="thickness" type="float" precision="2" min="0.0" max="10000.0" gui-text="Material Thickness">3.0</param>
<param name="kerf" type="float" precision="3" min="0.0" max="10000.0" gui-text="Kerf (cut width)">0.1</param>
<param name="div_l" type="int" gui-hidden="true">0</param>
<param name="div_w" type="int" gui-hidden="true">0</param>
<param name="style" type="string" gui-hidden="true">1</param>
<param name="boxtype" type="int" gui-hidden="true">2</param>
<param name="spacing" type="float" precision="2" min="0.0" max="10000.0" gui-text="Space Between Parts">1.0</param>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz Boxes/Papercraft">
<submenu name="Finger-jointed/Tabbed Boxes" />
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">box_maker_tabbed.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,158 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Label Guides</name>
<id>fablabchemnitz.de.label_guides</id>
<param name="units" type="optiongroup" appearance="combo" gui-text="Units:">
<option value="mm">mm</option>
<option value="in">inch</option>
</param>
<param name="preset_tab" type="notebook">
<page name="rrect" gui-text="Preset (Rounded Rect.)">
<param name="rrect_preset" type="optiongroup" appearance="combo" gui-text="Label preset:">
<option value="L7167">199.6 x 289.1mm Labels (1/sheet, A4) [L7167, LP1/199]</option>
<option value="L7168">199.6 x 143.5mm Labels (2/sheet, A4) [L7168, LP2/199]</option>
<option value="L7169">99.1 x 139mm Labels (4/sheet, A4) [L7169, LP4/99]</option>
<option value="L7701">192 x 62mm Lever Arch File Labels (4/sheet, A4) [L7701, LP4/192]</option>
<option value="L7171">200 x 60mm Lever Arch File Labels (4/sheet, A4) [L7171, LP4/200]</option>
<option value="L7166">99.1 x 93.1mm Labels (6/sheet, A4) [L7166, LP6/99]</option>
<option value="L4760">192 x 39mm Lever Arch File Labels (7/sheet, A4) [L4760, LP7/192]</option>
<option value="L7165">99.1 x 67.7mm Labels (8/sheet, A4) [L7165, LP8/99]</option>
<option value="L7664">70 x 71.8mm Diskette Labels (8/sheet, A4) [L7664, LP8/71]</option>
<option value="L7667">133 x 29.6mm Data Cartridge Labels (9/sheet, A4) [L7667, LP9/133]</option>
<option value="L7173">99.1 x 57mm Labels (10/sheet, A4) [L7173, LP10/99]</option>
<option value="J5103">38.1 x 135mm Smartstamp “Logo” Labels (10/sheet, A4) [J5103, LP10/38]</option>
<option value="L7666">70 x 52mm Labels (10/sheet, A4) [L7666, LP10/70]</option>
<option value="L7783">95.8 x 50.7mm Labels (10/sheet, A4) [L7783, LP10/96]</option>
<option value="L7164">63.5 x 72mm Labels (12/sheet, A4) [L7164, LP12/63]</option>
<option value="L7671">76.2 x 46.4mm Labels (12/sheet, A4) [L7671, LP12/76]</option>
<option value="L7177">99.1 x 42.3mm Labels (12/sheet, A4) [L7177, LP12/99]</option>
<option value="L7163">99.1 x 38.1mm Labels (14/sheet, A4) [L7163, LP14/99]</option>
<option value="L7668">59 x 50.9mm Labels (15/sheet, A4) [L7668, LP15/59]</option>
<option value="L7162">99.1 x 33.9mm Labels (16/sheet, A4) [L7162, LP16/99]</option>
<option value="L7674">145 x 17mm Labels (16/sheet, A4) [L7674, LP16/145]</option>
<option value="L7161">63.5 x 46.6mm Labels (18/sheet, A4) [L7161, LP18/63]</option>
<option value="L7172">100 x 30mm Labels (18/sheet, A4) [L7172, LP18/100]</option>
<option value="J5101">38.1 x 69mm Smartstamp “Bulk Print” Labels (20/sheet, A4) [J5101, LP20/38]</option>
<option value="L7160">63.5 x 38.1mm Labels (21/sheet, A4) [L7160, LP21/63]</option>
<option value="L7159">63.5 x 33.9mm Labels (24/sheet, A4) [L7159, LP24/63]</option>
<option value="L7665">72 x 21.15mm Labels (24/sheet, A4) [L7665, LP24/72]</option>
<option value="L7170">134 x 11mm Eurofolio Labels (24/sheet, A4) [L7170, LP24/134]</option>
<option value="L6011">63.5 x 29.6mm Labels (27/sheet, A4) [L6011, LP27/63]</option>
<option value="LP33_53">54 x 22mm Labels (33/sheet, A4) [LP33/53]</option>
<option value="LP36_49">48.9 x 29.6mm Labels (36/sheet, A4) [LP36/49]</option>
<option value="L7654">45.7 x 25.4mm Labels (40/sheet, A4) [L7654, LP40/45]</option>
<option value="L7636">45.7 x 21.2mm Labels (48/sheet, A4) [L7636, LP48/45]</option>
<option value="LP56_89">89 x 10mm Labels (56/sheet, A4) [LP56/89]</option>
<option value="L7651">38.1 x 21.2mm Labels (65/sheet, A4) [L7651, LP65/38]</option>
<option value="L7656">46 x 11.1mm Labels (84/sheet, A4) [L7656, LP84/46]</option>
<option value="L7658">25.4 x 10mm Labels (189/sheet, A4) [L7658, LP189/25]</option>
<option value="L7657">17.8 x 10mm Labels (270/sheet, A4) [L7657, LP270/18]</option>
<option value="LP6_95SQ">95 x 95mm Labels (6/sheet, A4) [LP6/95SQ]</option>
<option value="LP12_65SQ">65 x 65mm Labels (12/sheet, A4) [LP12/65SQ]</option>
<option value="LP15_51SQ">51 x 51mm Labels (15/sheet, A4) [LP15/51SQ]</option>
<option value="LP35_37SQ">37 x 37mm Labels (35/sheet, A4) [LP35/37SQ]</option>
<option value="LP70_25SQ">25 x 25mm Labels (70/sheet, A4) [LP70/25SQ]</option>
</param>
<param name="rrect_radius" type="float" min="0" max="100" gui-text="Corner Radius">1</param>
</page>
<page name="rect" gui-text="Preset (Square Rect.)">
<param name="rect_preset" type="optiongroup" appearance="combo" gui-text="Label preset:">
<option value="L7784">210 x 297mm Labels (1/sheet, A4) [L7784, LP1/210H,J,V]</option>
<option value="LP2_105">105 x 297mm Labels (2/sheet, A4) [LP2/105]</option>
<option value="3655">210 x 148.5mm Labels (2/sheet, A4) [3655, LP2/210]</option>
<option value="LP3_210">210 x 99mm Labels (3/sheet, A4) [LP3/210]</option>
<option value="3483">105 x 148.5mm Labels (4/sheet, A4) [3483, LP4/105]</option>
<option value="LP4_210">210 x 74.25mm Labels (4/sheet, A4) [LP4/210]</option>
<option value="LP6_70">70 x 148.5mm Labels (6/sheet, A4) [LP6/70]</option>
<option value="LP6_105">105 x 99mm Labels (6/sheet, A4) [LP6/105]</option>
<option value="3427">105 x 74.25mm Labels (8/sheet, A4) [3427, LP8/105]</option>
<option value="LP8_105S">105 x 70.7mm Labels (8/sheet, A4) [LP8/105S]</option>
<option value="LP10_105">105 x 59.4mm Labels (10/sheet, A4) [LP10/105]</option>
<option value="3425">105 x 57.55mm Labels (10/sheet, A4) [3425, LP10/105S]</option>
<option value="LP12_105">105 x 49.5mm Labels (12/sheet, A4) [LP12/105]</option>
<option value="3424">105 x 47.9mm Labels (12/sheet, A4) [3424, LP12/105S]</option>
<option value="3653">105 x 42.42mm Labels (14/sheet, A4) [3653, LP14/105]</option>
<option value="LP15_70">70 x 59.4mm Labels (15/sheet, A4) [LP15/70]</option>
<option value="LP15_70S">70 x 50.7mm Labels (15/sheet, A4) [LP15/70S]</option>
<option value="3484">105 x 37.12mm Labels (16/sheet, A4) [3484, LP16/105]</option>
<option value="3423">105 x 34.95mm Labels (16/sheet, A4) [3423, LP16/105S]</option>
<option value="3652">70 x 42.42mm Labels (21/sheet, A4) [3652, LP21/70]</option>
<option value="LP21_70S">70 x 38.1mm Labels (21/sheet, A4) [LP21/70S]</option>
<option value="3474">70 x 37.12mm Labels (24/sheet, A4) [3474, LP24/70]</option>
<option value="3422">70 x 34.95mm Labels (24/sheet, A4) [3422, LP24/70S]</option>
<option value="LP24_70LS">70 x 34mm Labels (24/sheet, A4) [LP24/70LS]</option>
<option value="3475">70 x 36mm Labels (24/sheet, A4) [3475, LP24/70SS]</option>
<option value="LP27_70S">70 x 31.95mm Labels (27/sheet, A4) [LP27/70S]</option>
<option value="3489">70 x 29.7mm Labels (30/sheet, A4) [3489, LP30/70]</option>
<option value="3421">70 x 25.4mm Labels (33/sheet, A4) [3421, LP33/70S]</option>
<option value="L7409">57 x 15mm Labels (51/sheet, A4) [L7409, LP51/57]</option>
<option value="LP56_52">52.5 x 21.21mm Labels (56/sheet, A4) [LP56/52]</option>
</param>
</page>
<page name="circ" gui-text="Preset (Round &amp; Oval)">
<param name="circ_preset" type="optiongroup" appearance="combo" gui-text="Label preset:">
<option value="LP2_115R">114.5mm Labels (2/sheet, A4) [LP2/115R]</option>
<option value="LP6_88R">88mm Labels (6/sheet, A4) [LP6/88R]</option>
<option value="LP6_85R">85mm Labels (6/sheet, A4) [LP6/85R]</option>
<option value="LP6_76R">76mm Labels (6/sheet, A4) [LP6/76R]</option>
<option value="C2244">72mm Labels (6/sheet, A4) [C2244, LP6/72R]</option>
<option value="LP8_69R">69mm Labels (8/sheet, A4) [LP8/69R]</option>
<option value="L7670">63.5mm Labels (12/sheet, A4) [L7670, LP12/64R]</option>
<option value="LP15_51R">51mm Labels (15/sheet, A4) [LP15/51R]</option>
<option value="LP24_45R">45mm Labels (24/sheet, A4) [LP24/45R]</option>
<option value="L7780">40mm Labels (24/sheet, A4) [L7780, LP24/40R]</option>
<option value="LP35_37R">37mm Labels (35/sheet, A4) [LP35/37R]</option>
<option value="LP35_35R">35mm Labels (35/sheet, A4) [LP35/35R]</option>
<option value="LP40_32R">32mm Labels (40/sheet, A4) [LP40/32R]</option>
<option value="LP54_29R">29mm Labels (54/sheet, A4) [LP54/29R]</option>
<option value="LP70_25R">25mm Labels (70/sheet, A4) [LP70/25R]</option>
<option value="LP117_19R">19mm Labels (117/sheet, A4) [LP117/19R]</option>
<option value="LP216_13R">13mm Labels (216/sheet, A4) [LP216/13R]</option>
<option value="LP2_195OV">195 x 138mm Labels (2/sheet, A4) [LP2/195OV]</option>
<option value="LP4_90OV">90 x 135mm Labels (4/sheet, A4) [LP4/90OV]</option>
<option value="LP8_90OV">90 x 62mm Labels (8/sheet, A4) [LP8/90OV]</option>
<option value="LP10_95OV">95 x 53mm Labels (10/sheet, A4) [LP10/95OV]</option>
<option value="LP14_95OV">95 x 34mm Labels (14/sheet, A4) [LP14/95OV]</option>
<option value="LP21_60OV">60 x 34mm Labels (21/sheet, A4) [LP21/60OV]</option>
<option value="LP32_40OV">40 x 30mm Labels (32/sheet, A4) [LP32/40OV]</option>
<option value="LP65_35OV">35.05 x 16mm Labels (65/sheet, A4) [LP65/35OV]</option>
</param>
</page>
<page name="custom" gui-text="Custom">
<label appearance="header">Custom Label Options</label>
<param name="margin_l" type="float" min="0" max="1000" gui-text="Left margin">8.5</param>
<param name="margin_t" type="float" min="0" max="1000" gui-text="Top margin">13</param>
<param name="size_x" type="float" min="0" max="1000" gui-text="Label X size">37</param>
<param name="size_y" type="float" min="0" max="1000" gui-text="Label Y size">37</param>
<param name="pitch_x" type="float" min="0" max="1000" gui-text="Label X pitch">39</param>
<param name="pitch_y" type="float" min="0" max="1000" gui-text="Label Y pitch">39</param>
<param name="count_x" type="int" min="0" max="1000" gui-text="Number Across">5</param>
<param name="count_y" type="int" min="0" max="1000" gui-text="Number Down">7</param>
<param name="shapes" type="optiongroup" appearance="combo" gui-text="Label Shapes:">
<option value="rect">Rectangle</option>
<option value="circle">Circle/Ellipse</option>
</param>
</page>
</param>
<label>Drawing Options</label>
<param name="delete_existing_guides" type="bool" gui-text="Delete existing guides">false</param>
<param name="draw_edge_guides" type="bool" gui-text="Draw label edge guides">true</param>
<param name="draw_centre_guides" type="bool" gui-text="Draw label centre guides">true</param>
<param name="inset" type="float" min="0" max="1000" gui-text="Guide inset">5</param>
<param name="draw_inset_guides" type="bool" gui-text="Draw label inset guides">true</param>
<param name="draw_shapes" type="bool" gui-text="Draw label shapes">true</param>
<param name="shape_inset" type="float" min="0" max="1000" gui-text="Shape inset">5</param>
<param name="draw_inset_shapes" type="bool" gui-text="Draw inset shapes">true</param>
<param name="set_page_size" type="bool" gui-text="Set page size (presets only)">false</param>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Grids/Guides"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">label_guides.py</command>
</script>
</inkscape-extension>

View File

@ -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()

View File

@ -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"
]
}
]

View File

@ -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)

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Clip Layer Above</name>
<id>fablabchemnitz.de.layer_clip.clip_layer_above</id>
<effect>
<object-type>path</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Layer Clip" />
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">clip_above.py</command>
</script>
</inkscape-extension>

View File

@ -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()

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Clip Layer Below</name>
<id>fablabchemnitz.de.layer_clip.clip_layer_below</id>
<effect>
<object-type>path</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Layer Clip" />
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">clip_below.py</command>
</script>
</inkscape-extension>

View File

@ -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()

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Clip Layer Current</name>
<id>fablabchemnitz.de.layer_clip.clip_layer_current</id>
<effect>
<object-type>path</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Layer Clip" />
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">clip_current.py</command>
</script>
</inkscape-extension>

View File

@ -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()

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Clip Layer Fix Transform</name>
<id>fablabchemnitz.de.layer_clip.clip_layer_fix_transform</id>
<effect>
<object-type>path</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Layer Clip" />
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">clip_fixtransform.py</command>
</script>
</inkscape-extension>

View File

@ -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()

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Clip Layer Parent</name>
<id>fablabchemnitz.de.layer_clip.clip_layer_parent</id>
<effect>
<object-type>path</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Layer Clip" />
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">clip_parent.py</command>
</script>
</inkscape-extension>

View File

@ -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()

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Clip Layer Remove</name>
<id>fablabchemnitz.de.layer_clip.clip_layer_remove</id>
<effect>
<object-type>path</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Layer Clip" />
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">clip_remove.py</command>
</script>
</inkscape-extension>

View File

@ -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()

View File

@ -0,0 +1,22 @@
[
{
"name": "<various>",
"id": "fablabchemnitz.de.layer_clip.<various>",
"path": "layer_clip",
"dependent_extensions": null,
"original_name": "<various>",
"original_id": "org.pernsteiner.inkscape.layerclip.<various>",
"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"
]
}
]

View File

@ -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"
]
}
]

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Open Closed Path</name>
<id>fablabchemnitz.de.open_closed_path</id>
<effect>
<object-type>path</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Modify existing Path(s)"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">open_closed_path.py</command>
</script>
</inkscape-extension>

View File

@ -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()

View File

@ -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"
]
}
]

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Remove Duplicate Guides</name>
<id>fablabchemnitz.remove_duplicate_guides</id>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Grids/Guides" />
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">remove_duplicate_guides.py</command>
</script>
</inkscape-extension>

View File

@ -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()