readded several extensions
This commit is contained in:
parent
7e8872efcd
commit
985f901579
@ -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>
|
353
extensions/fablabchemnitz/box_maker_conical/box_maker_conical.py
Normal file
353
extensions/fablabchemnitz/box_maker_conical/box_maker_conical.py
Normal 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()
|
21
extensions/fablabchemnitz/box_maker_conical/meta.json
Normal file
21
extensions/fablabchemnitz/box_maker_conical/meta.json
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
1
extensions/fablabchemnitz/box_maker_elliptical_cone/.gitignore
vendored
Normal file
1
extensions/fablabchemnitz/box_maker_elliptical_cone/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/DebugEllConicBox.txt
|
@ -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>
|
@ -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()
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
@ -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>
|
@ -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()
|
22
extensions/fablabchemnitz/box_maker_living_hinge/meta.json
Normal file
22
extensions/fablabchemnitz/box_maker_living_hinge/meta.json
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
@ -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>
|
@ -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()
|
92
extensions/fablabchemnitz/box_maker_mehr_boxes/mehr_plate.py
Normal file
92
extensions/fablabchemnitz/box_maker_mehr_boxes/mehr_plate.py
Normal 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
|
21
extensions/fablabchemnitz/box_maker_mehr_boxes/meta.json
Normal file
21
extensions/fablabchemnitz/box_maker_mehr_boxes/meta.json
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
@ -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>
|
712
extensions/fablabchemnitz/box_maker_tabbed/box_maker_tabbed.py
Normal file
712
extensions/fablabchemnitz/box_maker_tabbed/box_maker_tabbed.py
Normal 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()
|
33
extensions/fablabchemnitz/box_maker_tabbed/meta.json
Normal file
33
extensions/fablabchemnitz/box_maker_tabbed/meta.json
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
41
extensions/fablabchemnitz/box_maker_tabbed/schroffmaker.inx
Normal file
41
extensions/fablabchemnitz/box_maker_tabbed/schroffmaker.inx
Normal 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>
|
158
extensions/fablabchemnitz/label_guides/label_guides.inx
Normal file
158
extensions/fablabchemnitz/label_guides/label_guides.inx
Normal 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 & 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>
|
559
extensions/fablabchemnitz/label_guides/label_guides.py
Normal file
559
extensions/fablabchemnitz/label_guides/label_guides.py
Normal 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()
|
22
extensions/fablabchemnitz/label_guides/meta.json
Normal file
22
extensions/fablabchemnitz/label_guides/meta.json
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
73
extensions/fablabchemnitz/layer_clip/clip.py
Normal file
73
extensions/fablabchemnitz/layer_clip/clip.py
Normal 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)
|
||||
|
16
extensions/fablabchemnitz/layer_clip/clip_above.inx
Normal file
16
extensions/fablabchemnitz/layer_clip/clip_above.inx
Normal 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>
|
53
extensions/fablabchemnitz/layer_clip/clip_above.py
Normal file
53
extensions/fablabchemnitz/layer_clip/clip_above.py
Normal 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()
|
16
extensions/fablabchemnitz/layer_clip/clip_below.inx
Normal file
16
extensions/fablabchemnitz/layer_clip/clip_below.inx
Normal 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>
|
57
extensions/fablabchemnitz/layer_clip/clip_below.py
Normal file
57
extensions/fablabchemnitz/layer_clip/clip_below.py
Normal 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()
|
16
extensions/fablabchemnitz/layer_clip/clip_current.inx
Normal file
16
extensions/fablabchemnitz/layer_clip/clip_current.inx
Normal 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>
|
41
extensions/fablabchemnitz/layer_clip/clip_current.py
Normal file
41
extensions/fablabchemnitz/layer_clip/clip_current.py
Normal 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()
|
16
extensions/fablabchemnitz/layer_clip/clip_fixtransform.inx
Normal file
16
extensions/fablabchemnitz/layer_clip/clip_fixtransform.inx
Normal 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>
|
42
extensions/fablabchemnitz/layer_clip/clip_fixtransform.py
Normal file
42
extensions/fablabchemnitz/layer_clip/clip_fixtransform.py
Normal 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()
|
||||
|
||||
|
16
extensions/fablabchemnitz/layer_clip/clip_parent.inx
Normal file
16
extensions/fablabchemnitz/layer_clip/clip_parent.inx
Normal 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>
|
47
extensions/fablabchemnitz/layer_clip/clip_parent.py
Normal file
47
extensions/fablabchemnitz/layer_clip/clip_parent.py
Normal 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()
|
16
extensions/fablabchemnitz/layer_clip/clip_remove.inx
Normal file
16
extensions/fablabchemnitz/layer_clip/clip_remove.inx
Normal 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>
|
57
extensions/fablabchemnitz/layer_clip/clip_remove.py
Normal file
57
extensions/fablabchemnitz/layer_clip/clip_remove.py
Normal 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()
|
||||
|
||||
|
22
extensions/fablabchemnitz/layer_clip/meta.json
Normal file
22
extensions/fablabchemnitz/layer_clip/meta.json
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
21
extensions/fablabchemnitz/open_closed_path/meta.json
Normal file
21
extensions/fablabchemnitz/open_closed_path/meta.json
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
@ -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>
|
@ -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()
|
21
extensions/fablabchemnitz/remove_duplicate_guides/meta.json
Normal file
21
extensions/fablabchemnitz/remove_duplicate_guides/meta.json
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
@ -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>
|
@ -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()
|
Loading…
Reference in New Issue
Block a user