different large refactorings (subdirectores, removed obsolete stuff) and
bug fixes
This commit is contained in:
@ -0,0 +1,88 @@
|
||||
<?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</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="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">
|
||||
<submenu name="Finger-jointed/Tabbed Boxes"/>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">fablabchemnitz_boxmaker.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
@ -0,0 +1,640 @@
|
||||
#!/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
|
||||
|
||||
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
|
||||
|
||||
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.99" ### please report bugs, suggestions etc at https://github.com/paulh-rnd/TabbedBoxMaker ###
|
||||
|
||||
import os
|
||||
import inkex
|
||||
import math
|
||||
from copy import deepcopy
|
||||
|
||||
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
|
||||
#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' }
|
||||
return circle
|
||||
|
||||
def dimpleStr(tabVector,vectorX,vectorY,directionX,directionY,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*directionX)*dimpleHeight
|
||||
Vyd=Vyd+(tabSgn*diryN-ddir*directionY)*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*directionX)*dimpleHeight
|
||||
Vyd=Vyd+(tabSgn*diryN+ddir*directionY)*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
|
||||
directionX, directionY = direction
|
||||
|
||||
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-=correction
|
||||
tabWidth+=correction
|
||||
first=correction/2
|
||||
else:
|
||||
gapWidth+=correction
|
||||
tabWidth-=correction
|
||||
first=-correction/2
|
||||
|
||||
s=[]
|
||||
h=[]
|
||||
firstVec=0; secondVec=tabVec
|
||||
dividerEdgeOffsetX = dividerEdgeOffsetY = thickness
|
||||
dirxN=0 if directionX else 1 # used to select operation on x or y
|
||||
diryN=0 if directionY else 1
|
||||
if (tabSymmetry==1):
|
||||
dividerEdgeOffsetX = directionX*thickness;
|
||||
#dividerEdgeOffsetY = ;
|
||||
vectorX = rootX + (startOffsetX*thickness if dirxN else 0)
|
||||
vectorY = rootY + (startOffsetY*thickness if diryN else 0)
|
||||
s='M '+str(vectorX)+','+str(vectorY)+' '
|
||||
vectorX = rootX+(startOffsetX if startOffsetX else directionX)*thickness
|
||||
vectorY = rootY+(startOffsetY if startOffsetY else directionY)*thickness
|
||||
if dirxN: endOffsetX=0
|
||||
if diryN: endOffsetY=0
|
||||
else:
|
||||
(vectorX,vectorY)=(rootX+startOffsetX*thickness,rootY+startOffsetY*thickness)
|
||||
dividerEdgeOffsetX=directionY*thickness
|
||||
dividerEdgeOffsetY=directionX*thickness
|
||||
s='M '+str(vectorX)+','+str(vectorY)+' '
|
||||
if dirxN: vectorY=rootY # set correct line start for tab generation
|
||||
if diryN: 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 n in range(1,int(divisions)):
|
||||
if ((n%2) ^ (not isTab)) and numDividers>0 and not isDivider: # draw holes for divider joints in side walls
|
||||
w=gapWidth if isTab else tabWidth
|
||||
if n==1 and tabSymmetry==0:
|
||||
w-=startOffsetX*thickness
|
||||
for m in range(1,int(numDividers)+1):
|
||||
Dx=vectorX+-directionY*dividerSpacing*m
|
||||
Dy=vectorY+directionX*dividerSpacing*m
|
||||
if n==1 and tabSymmetry==0:
|
||||
Dx+=startOffsetX*thickness
|
||||
h='M '+str(Dx)+','+str(Dy)+' '
|
||||
Dx=Dx+directionX*w+dirxN*firstVec+first*directionX
|
||||
Dy=Dy+directionY*w+diryN*firstVec+first*directionY
|
||||
h+='L '+str(Dx)+','+str(Dy)+' '
|
||||
Dx=Dx+dirxN*secondVec
|
||||
Dy=Dy+diryN*secondVec
|
||||
h+='L '+str(Dx)+','+str(Dy)+' '
|
||||
Dx=Dx-(directionX*w+dirxN*firstVec+first*directionX)
|
||||
Dy=Dy-(directionY*w+diryN*firstVec+first*directionY)
|
||||
h+='L '+str(Dx)+','+str(Dy)+' '
|
||||
Dx=Dx-dirxN*secondVec
|
||||
Dy=Dy-diryN*secondVec
|
||||
h+='L '+str(Dx)+','+str(Dy)+' '
|
||||
group.add(getLine(h))
|
||||
if n%2:
|
||||
if n==1 and numDividers>0 and isDivider: # draw slots for dividers to slot into each other
|
||||
for m in range(1,int(numDividers)+1):
|
||||
Dx=vectorX+-directionY*dividerSpacing*m-dividerEdgeOffsetX
|
||||
Dy=vectorY+directionX*dividerSpacing*m-dividerEdgeOffsetY
|
||||
h='M '+str(Dx)+','+str(Dy)+' '
|
||||
Dx=Dx+directionX*(first+length/2)
|
||||
Dy=Dy+directionY*(first+length/2)
|
||||
h+='L '+str(Dx)+','+str(Dy)+' '
|
||||
Dx=Dx+dirxN*thickness
|
||||
Dy=Dy+diryN*thickness
|
||||
h+='L '+str(Dx)+','+str(Dy)+' '
|
||||
Dx=Dx-directionX*(first+length/2)
|
||||
Dy=Dy-directionY*(first+length/2)
|
||||
h+='L '+str(Dx)+','+str(Dy)+' '
|
||||
Dx=Dx-dirxN*thickness
|
||||
Dy=Dy-diryN*thickness
|
||||
h+='L '+str(Dx)+','+str(Dy)+' '
|
||||
group.add(getLine(h))
|
||||
# draw the gap
|
||||
vectorX=vectorX+directionX*gapWidth+dirxN*firstVec+first*directionX
|
||||
vectorY=vectorY+directionY*gapWidth+diryN*firstVec+first*directionY
|
||||
s+='L '+str(vectorX)+','+str(vectorY)+' '
|
||||
# draw the starting edge of the tab
|
||||
s+=dimpleStr(secondVec,vectorX,vectorY,directionX,directionY,dirxN,diryN,1,isTab)
|
||||
vectorX=vectorX+dirxN*secondVec
|
||||
vectorY=vectorY+diryN*secondVec
|
||||
s+='L '+str(vectorX)+','+str(vectorY)+' '
|
||||
else:
|
||||
# draw the tab
|
||||
vectorX=vectorX+directionX*tabWidth+dirxN*firstVec
|
||||
vectorY=vectorY+directionY*tabWidth+diryN*firstVec
|
||||
s+='L '+str(vectorX)+','+str(vectorY)+' '
|
||||
# draw the ending edge of the tab
|
||||
s+=dimpleStr(secondVec,vectorX,vectorY,directionX,directionY,dirxN,diryN,-1,isTab)
|
||||
vectorX=vectorX+dirxN*secondVec
|
||||
vectorY=vectorY+diryN*secondVec
|
||||
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+directionX*length)+','+str(rootY+endOffsetY*thickness+directionY*length)+' '
|
||||
if isTab and numDividers>0 and tabSymmetry==0 and not isDivider: # draw last for divider joints in side walls
|
||||
for m in range(1,int(numDividers)+1):
|
||||
Dx=vectorX
|
||||
Dy=vectorY+directionX*dividerSpacing*m
|
||||
h='M '+str(Dx)+','+str(Dy)+' '
|
||||
Dx=rootX+endOffsetX*thickness+directionX*length
|
||||
Dy=Dy+directionY*tabWidth+diryN*firstVec+first*directionY
|
||||
h+='L '+str(Dx)+','+str(Dy)+' '
|
||||
Dx=Dx+dirxN*secondVec
|
||||
Dy=Dy+diryN*secondVec
|
||||
h+='L '+str(Dx)+','+str(Dy)+' '
|
||||
Dx=vectorX
|
||||
Dy=Dy-(directionY*tabWidth+diryN*firstVec+first*directionY)
|
||||
h+='L '+str(Dx)+','+str(Dy)+' '
|
||||
Dx=Dx-dirxN*secondVec
|
||||
Dy=Dy-diryN*secondVec
|
||||
h+='L '+str(Dx)+','+str(Dy)+' '
|
||||
group.add(getLine(h))
|
||||
group.add(getLine(s))
|
||||
return s
|
||||
|
||||
|
||||
class BoxMaker(inkex.Effect):
|
||||
def __init__(self):
|
||||
# Call the base class constructor.
|
||||
inkex.Effect.__init__(self)
|
||||
# Define options
|
||||
self.arg_parser.add_argument('--schroff',type=int,default=0,help='Enable Schroff mode')
|
||||
self.arg_parser.add_argument('--rail_height',type=float,default=10.0,help='Height of rail')
|
||||
self.arg_parser.add_argument('--rail_mount_depth',type=float,default=17.4,help='Depth at which to place hole for rail mount bolt')
|
||||
self.arg_parser.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)')
|
||||
self.arg_parser.add_argument('--rows',type=int,default=0,help='Number of Schroff rows')
|
||||
self.arg_parser.add_argument('--hp',type=int,default=0,help='Width (TE/HP units) of Schroff rows')
|
||||
self.arg_parser.add_argument('--row_spacing',type=float,default=10.0,help='Height of rail')
|
||||
self.arg_parser.add_argument('--unit', default='mm',help='Measure Units')
|
||||
self.arg_parser.add_argument('--inside',type=int,default=0,help='Int/Ext Dimension')
|
||||
self.arg_parser.add_argument('--length',type=float,default=100,help='Length of Box')
|
||||
self.arg_parser.add_argument('--width',type=float,default=100,help='Width of Box')
|
||||
self.arg_parser.add_argument('--depth',type=float,default=100,help='Height of Box')
|
||||
self.arg_parser.add_argument('--tab',type=float,default=25,help='Nominal Tab Width')
|
||||
self.arg_parser.add_argument('--equal',type=int,default=0,help='Equal/Prop Tabs')
|
||||
self.arg_parser.add_argument('--tabsymmetry',type=int,default=0,help='Tab style')
|
||||
self.arg_parser.add_argument('--dimpleheight',type=float,default=0,help='Tab Dimple Height')
|
||||
self.arg_parser.add_argument('--dimplelength',type=float,default=0,help='Tab Dimple Tip Length')
|
||||
self.arg_parser.add_argument('--hairline',type=int,default=0,help='Line Thickness')
|
||||
self.arg_parser.add_argument('--thickness',type=float,default=10,help='Thickness of Material')
|
||||
self.arg_parser.add_argument('--kerf',type=float,default=0.5,help='Kerf (width) of cut')
|
||||
self.arg_parser.add_argument('--clearance',type=float,default=0.01,help='Clearance of joints')
|
||||
self.arg_parser.add_argument('--style',type=int,default=25,help='Layout/Style')
|
||||
self.arg_parser.add_argument('--spacing',type=float,default=25,help='Part Spacing')
|
||||
self.arg_parser.add_argument('--boxtype',type=int,default=25,help='Box type')
|
||||
self.arg_parser.add_argument('--div_l',type=int,default=25,help='Dividers (Length axis)')
|
||||
self.arg_parser.add_argument('--div_w',type=int,default=25,help='Dividers (Width axis)')
|
||||
self.arg_parser.add_argument('--keydiv',type=int,default=3,help='Key dividers into walls/floor')
|
||||
|
||||
def effect(self):
|
||||
global group,nomTab,equalTabs,tabSymmetry,dimpleHeight,dimpleLength,thickness,correction,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
|
||||
|
||||
# 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) + unit )
|
||||
Y = self.svg.unittouu( str(self.options.width) + unit )
|
||||
|
||||
Z = self.svg.unittouu( str(self.options.rail_height) + 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
|
||||
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 )
|
||||
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
|
||||
|
||||
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 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 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:
|
||||
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 # 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
|
||||
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:
|
||||
if not keydivwalls:
|
||||
a=b=c=d=1
|
||||
atabs=btabs=ctabs=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 keydivwalls*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
|
||||
|
||||
if __name__ == '__main__':
|
||||
BoxMaker().run()
|
@ -0,0 +1,48 @@
|
||||
<?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.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="clearance" type="float" precision="3" min="0.0" max="10000.0" gui-text="Joint clearance">0.01</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">
|
||||
<submenu name="Finger-jointed/Tabbed Boxes"/>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">fablabchemnitz_boxmaker.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
Reference in New Issue
Block a user