diff --git a/README.md b/README.md index 80ad6fb..9f226b8 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,19 @@ # BoxMaker -(Note: This Github Repository is unofficial. It should be considered a fork of the original -BoxMaker at http://www.keppel.demon.co.uk/111000/111000.html) +(Note: This is a fork of https://github.com/hellerbarde/inkscape-boxmaker) A free tool for creating boxes using tabbed construction version 0.85 - February 27 2012 -please report bugs, comments etc to bugs@twot.eu +version 0.86 - Sep 6 2020 + +please raise an issue on github if you find bugs # About This tool is designed to simplify and speed up process of making practical boxes using -a laser cutter (though it can be used with any CNC cutter) to prepare the pieces. +a laser cutter to prepare the pieces. The tool works by generating a drawing of the pieces of the box with the tab and hole size corrected to account for the kerf (width of cut), these pieces are composed of sides, @@ -21,26 +22,23 @@ grouped together. # Release Notes -So far no serious bugs( i.e causing runtime errors ) have been found. -The program works with python versions 2.6.5, 2.6.7 and 2.7.2, other version have not yet -been tried except python 2.5.1 which fails with a syntax error. - -This version has been tried on windows XP, windows 7, Ubuntu and Mac OS X with no -serious problems (the live preview works most of the time but can be flaky). - Only crude input checking has been implemented in the program but as the only output is a drawing the worst that can happen is a messed up picture (control-Z cures that problem). # To Do -Improve program documentation. Improve input checking to restrict values to correct +- Improve program documentation. Improve input checking to restrict values to correct solutions. +- Correct for large kerf correctly for use with CNC mills + +- Use 'dogbones' for inner corners + # Use The interface is pretty self explanatory, the extension is 'Tabbed Box Maker' in the -'Laser Tools' group (hopefully more tools will soon(ish) join it). +'CNC Tools' group. In order of appearance: @@ -97,3 +95,5 @@ how far apart the pieces are in the drawing produced 0.5 ( 9 oct 2011) beta 0.7 (24 oct 2011) first release 0.8 (26 oct 2011) basic input checking implemented + 0.85 (Feb 27 2012) (hellerbarde) + 0.86 (6 Sep 2020) Ported to Inkscape 1.0 (roastedneutrons) diff --git a/boxmaker.inx b/boxmaker.inx index 49b41ea..7d5b6de 100644 --- a/boxmaker.inx +++ b/boxmaker.inx @@ -1,19 +1,15 @@ <_name>Tabbed Box Maker - eu.twot.render.boxmaker + org.kreu.boxmaker - boxmaker.py - simpletransform.py - inkex.py - - + - + <_option value="1">Inside <_option value="0">Outside @@ -23,7 +19,7 @@ 1.0 1.0 - + <_option value="0">Fixed <_option value="1">Proportional @@ -32,7 +28,7 @@ 0.1 0.01 - + @@ -44,10 +40,10 @@ all - + diff --git a/boxmaker.py b/boxmaker.py index 30efe4c..9668072 100644 --- a/boxmaker.py +++ b/boxmaker.py @@ -1,228 +1,244 @@ -#! /usr/bin/env python -''' -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 elliot@twot.eu -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -''' -__version__ = "0.8" ### please report bugs, suggestions etc to bugs@twot.eu ### - -import sys,inkex,simplestyle,gettext -_ = gettext.gettext +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright (C) 2020 Sudhir Palliyil, sudhir@kreu.org +# +# 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. +# +""" +Creating SVG paths for sides of a tabbed box, to be cut on a laser cutter. For Inkscape 1.0 +""" + +import inkex,simplestyle +from lxml import etree + def drawS(XYstring): # Draw lines from a list - name='part' - style = { 'stroke': '#000000', 'fill': 'none' } - drw = {'style':simplestyle.formatStyle(style),inkex.addNS('label','inkscape'):name,'d':XYstring} - inkex.etree.SubElement(parent, inkex.addNS('path','svg'), drw ) - return - -def side((rx,ry),(sox,soy),(eox,eoy),tabVec,length,(dirx,diry),isTab): - # root startOffset endOffset tabVec length direction isTab - - divs=int(length/nomTab) # divisions - if not divs%2: divs-=1 # make divs odd - divs=float(divs) - tabs=(divs-1)/2 # tabs for side + #global parent + name='part' + style = { 'stroke': '#000000', 'fill': 'none' } + drw = {'style':str(inkex.Style(style)),inkex.addNS('label','inkscape'):name,'d':XYstring} + #drw = {'style':simplestyle.formatStyle(style),inkex.addNS('label','inkscape'):name,'d':XYstring} + etree.SubElement(parent, inkex.addNS('path','svg'), drw ) + + +# def side((rx,ry),(sox,soy),(eox,eoy),tabVec,length,(dirx,diry),isTab): + +def side(r,s,e,tabVec,length,dir,isTab): + (rx,ry)=r + (sox,soy)=s + (eox,eoy)=e + (dirx,diry)=dir + # root startOffset endOffset tabVec length direction isTab + + divs=int(length/nomTab) # divisions + if not divs%2: divs-=1 # make divs odd + divs=float(divs) + tabs=(divs-1)/2 # tabs for side - if equalTabs: - gapWidth=tabWidth=length/divs - else: - tabWidth=nomTab - gapWidth=(length-tabs*nomTab)/(divs-tabs) - - if isTab: # kerf correction - gapWidth-=correction - tabWidth+=correction - first=correction/2 - else: - gapWidth+=correction - tabWidth-=correction - first=-correction/2 + if equalTabs: + gapWidth=tabWidth=length/divs + else: + tabWidth=nomTab + gapWidth=(length-tabs*nomTab)/(divs-tabs) - s=[] - firstVec=0; secondVec=tabVec - 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 - - # 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)+' ' + if isTab: # kerf correction + gapWidth-=correction + tabWidth+=correction + first=correction/2 else: - 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)+' ' - (secondVec,firstVec)=(-secondVec,-firstVec) # swap tab direction - first=0 - s+='L '+str(rx+eox*thickness+dirx*length)+','+str(ry+eoy*thickness+diry*length)+' ' - return s + gapWidth+=correction + tabWidth-=correction + first=-correction/2 + + s=[] + firstVec=0; secondVec=tabVec + 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 + + # 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: + 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)+' ' + (secondVec,firstVec)=(-secondVec,-firstVec) # swap tab direction + first=0 + s+='L '+str(rx+eox*thickness+dirx*length)+','+str(ry+eoy*thickness+diry*length)+' ' + return s - class BoxMaker(inkex.Effect): - def __init__(self): - # Call the base class constructor. - inkex.Effect.__init__(self) - # Define options - self.OptionParser.add_option('--unit',action='store',type='string', - dest='unit',default='mm',help='Measure Units') - self.OptionParser.add_option('--inside',action='store',type='int', - dest='inside',default=0,help='Int/Ext Dimension') - self.OptionParser.add_option('--length',action='store',type='float', - dest='length',default=100,help='Length of Box') - self.OptionParser.add_option('--width',action='store',type='float', - dest='width',default=100,help='Width of Box') - self.OptionParser.add_option('--depth',action='store',type='float', - dest='height',default=100,help='Height of Box') - self.OptionParser.add_option('--tab',action='store',type='float', - dest='tab',default=25,help='Nominal Tab Width') - self.OptionParser.add_option('--equal',action='store',type='int', - dest='equal',default=0,help='Equal/Prop Tabs') - self.OptionParser.add_option('--thickness',action='store',type='float', - dest='thickness',default=10,help='Thickness of Material') - self.OptionParser.add_option('--kerf',action='store',type='float', - dest='kerf',default=0.5,help='Kerf (width) of cut') - self.OptionParser.add_option('--clearance',action='store',type='float', - dest='clearance',default=0.01,help='Clearance of joints') - self.OptionParser.add_option('--style',action='store',type='int', - dest='style',default=25,help='Layout/Style') - self.OptionParser.add_option('--spacing',action='store',type='float', - dest='spacing',default=25,help='Part Spacing') - - def effect(self): - global parent,nomTab,equalTabs,thickness,correction - + """Please rename this class, don't keep it unnamed""" + def add_arguments(self, pars): + #pars.add_argument("--my_option", type=inkex.Boolean,\ + # help="An example option, put your options here") + pars.add_argument('--unit',type=str, + dest='unit',default='mm',help='Measure Units') + pars.add_argument('--inside',type=int, + dest='inside',default=0,help='Int/Ext Dimension') + pars.add_argument('--length',type=float, + dest='length',default=100,help='Length of Box') + pars.add_argument('--width',type=float, + dest='width',default=100,help='Width of Box') + pars.add_argument('--depth',type=float, + dest='height',default=100,help='Height of Box') + pars.add_argument('--tab',type=float, + dest='tab',default=25,help='Nominal Tab Width') + pars.add_argument('--equal',type=int, + dest='equal',default=0,help='Equal/Prop Tabs') + pars.add_argument('--thickness',type=float, + dest='thickness',default=10,help='Thickness of Material') + pars.add_argument('--kerf',type=float, + dest='kerf',default=0.5,help='Kerf (width) of cut') + pars.add_argument('--clearance',type=float, + dest='clearance',default=0.01,help='Clearance of joints') + pars.add_argument('--style',type=int, + dest='style',default=25,help='Layout/Style') + pars.add_argument('--spacing',type=float, + dest='spacing',default=25,help='Part Spacing') + + def effect(self): + global parent,nomTab,equalTabs,thickness,correction # Get access to main SVG document element and get its dimensions. - svg = self.document.getroot() - - # Get the attibutes: - widthDoc = self.unittouu(svg.get('width')) - heightDoc = self.unittouu(svg.get('height')) + 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 = inkex.etree.SubElement(svg, 'g') - layer.set(inkex.addNS('label', 'inkscape'), 'newlayer') - layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer') - - parent=self.current_layer - - # Get script's option values. - unit=self.options.unit - inside=self.options.inside - X = self.unittouu( str(self.options.length) + unit ) - Y = self.unittouu( str(self.options.width) + unit ) - Z = self.unittouu( str(self.options.height) + unit ) - thickness = self.unittouu( str(self.options.thickness) + unit ) - nomTab = self.unittouu( str(self.options.tab) + unit ) - equalTabs=self.options.equal - kerf = self.unittouu( str(self.options.kerf) + unit ) - clearance = self.unittouu( str(self.options.clearance) + unit ) - layout=self.options.style - spacing = self.unittouu( str(self.options.spacing) + unit ) - - if inside: # if inside dimension selected correct values to outside dimension - X+=thickness*2 - Y+=thickness*2 - Z+=thickness*2 + layer = etree.SubElement(svg, 'g') + layer.set(inkex.addNS('label', 'inkscape'), 'box') + layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer') - correction=kerf-clearance + parent=self.svg.get_current_layer() + #inkex.utils.debug(parent) - # check input values mainly to avoid python errors - # TODO restrict values to *correct* solutions - error=0 - - if min(X,Y,Z)==0: - inkex.errormsg(_('Error: Dimensions must be non zero')) - error=1 - if max(X,Y,Z)>max(widthDoc,heightDoc)*10: # crude test - inkex.errormsg(_('Error: Dimensions Too Large')) - error=1 - if min(X,Y,Z)<3*nomTab: - inkex.errormsg(_('Error: Tab size too large')) - error=1 - if nomTabmin(X,Y,Z)/3: # crude test - inkex.errormsg(_('Error: Material too thick')) - error=1 - if correction>min(X,Y,Z)/3: # crude test - inkex.errormsg(_('Error: Kerf/Clearence too large')) - error=1 - if spacing>max(X,Y,Z)*10: # crude test - inkex.errormsg(_('Error: Spacing too large')) - error=1 - if spacing 0=holes 1=tabs - if layout==1: # Diagramatic Layout - pieces=[[(2,0,0,1),(3,0,1,1),X,Z,0b1010],[(1,0,0,0),(2,0,0,1),Z,Y,0b1111], - [(2,0,0,1),(2,0,0,1),X,Y,0b0000],[(3,1,0,1),(2,0,0,1),Z,Y,0b1111], - [(4,1,0,2),(2,0,0,1),X,Y,0b0000],[(2,0,0,1),(1,0,0,0),X,Z,0b1010]] - elif layout==2: # 3 Piece Layout - pieces=[[(2,0,0,1),(2,0,1,0),X,Z,0b1010],[(1,0,0,0),(1,0,0,0),Z,Y,0b1111], - [(2,0,0,1),(1,0,0,0),X,Y,0b0000]] - elif layout==3: # Inline(compact) Layout - pieces=[[(1,0,0,0),(1,0,0,0),X,Y,0b0000],[(2,1,0,0),(1,0,0,0),X,Y,0b0000], - [(3,2,0,0),(1,0,0,0),Z,Y,0b0101],[(4,2,0,1),(1,0,0,0),Z,Y,0b0101], - [(5,2,0,2),(1,0,0,0),X,Z,0b1111],[(6,3,0,2),(1,0,0,0),X,Z,0b1111]] - elif layout==4: # Diagramatic Layout with Alternate Tab Arrangement - pieces=[[(2,0,0,1),(3,0,1,1),X,Z,0b1001],[(1,0,0,0),(2,0,0,1),Z,Y,0b1100], - [(2,0,0,1),(2,0,0,1),X,Y,0b1100],[(3,1,0,1),(2,0,0,1),Z,Y,0b0110], - [(4,1,0,2),(2,0,0,1),X,Y,0b0110],[(2,0,0,1),(1,0,0,0),X,Z,0b1100]] - - 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 - # generate and draw the sides of each piece - drawS(side((x,y),(d,a),(-b,a),-thickness if a else thickness,dx,(1,0),a)) # side a - drawS(side((x+dx,y),(-b,a),(-b,-c),thickness if b else -thickness,dy,(0,1),b)) # side b - drawS(side((x+dx,y+dy),(-b,-c),(d,-c),thickness if c else -thickness,dx,(-1,0),c)) # side c - drawS(side((x,y+dy),(d,-c),(d,a),-thickness if d else thickness,dy,(0,-1),d)) # side d - -# Create effect instance and apply it. -effect = BoxMaker() -effect.affect() + # 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 ) + + 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 + error=0 + if min(X,Y,Z)==0: + inkex.errormsg(_('Error: Dimensions must be non zero')) + error=1 + if max(X,Y,Z)>max(widthDoc,heightDoc)*10: # crude test + inkex.errormsg(_('Error: Dimensions Too Large')) + error=1 + if min(X,Y,Z)<3*nomTab: + inkex.errormsg(_('Error: Tab size too large')) + error=1 + if nomTabmin(X,Y,Z)/3: # crude test + inkex.errormsg(_('Error: Material too thick')) + error=1 + if correction>min(X,Y,Z)/3: # crude test + inkex.errormsg(_('Error: Kerf/Clearence too large')) + error=1 + if spacing>max(X,Y,Z)*10: # crude test + inkex.errormsg(_('Error: Spacing too large')) + error=1 + if spacing 0=holes 1=tabs + if layout==1: # Diagramatic Layout + pieces=[[(2,0,0,1),(3,0,1,1),X,Z,0b1010],[(1,0,0,0),(2,0,0,1),Z,Y,0b1111], + [(2,0,0,1),(2,0,0,1),X,Y,0b0000],[(3,1,0,1),(2,0,0,1),Z,Y,0b1111], + [(4,1,0,2),(2,0,0,1),X,Y,0b0000],[(2,0,0,1),(1,0,0,0),X,Z,0b1010]] + elif layout==2: # 3 Piece Layout + pieces=[[(2,0,0,1),(2,0,1,0),X,Z,0b1010],[(1,0,0,0),(1,0,0,0),Z,Y,0b1111], + [(2,0,0,1),(1,0,0,0),X,Y,0b0000]] + elif layout==3: # Inline(compact) Layout + pieces=[[(1,0,0,0),(1,0,0,0),X,Y,0b0000],[(2,1,0,0),(1,0,0,0),X,Y,0b0000], + [(3,2,0,0),(1,0,0,0),Z,Y,0b0101],[(4,2,0,1),(1,0,0,0),Z,Y,0b0101], + [(5,2,0,2),(1,0,0,0),X,Z,0b1111],[(6,3,0,2),(1,0,0,0),X,Z,0b1111]] + elif layout==4: # Diagramatic Layout with Alternate Tab Arrangement + pieces=[[(2,0,0,1),(3,0,1,1),X,Z,0b1001],[(1,0,0,0),(2,0,0,1),Z,Y,0b1100], + [(2,0,0,1),(2,0,0,1),X,Y,0b1100],[(3,1,0,1),(2,0,0,1),Z,Y,0b0110], + [(4,1,0,2),(2,0,0,1),X,Y,0b0110],[(2,0,0,1),(1,0,0,0),X,Z,0b1100]] + + 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 + # generate and draw the sides of each piece + #inkex.utils.debug(side((x,y),(d,a),(-b,a),-thickness if a else thickness,dx,(1,0),a)) + drawS(side((x,y),(d,a),(-b,a),-thickness if a else thickness,dx,(1,0),a)) # side a + drawS(side((x+dx,y),(-b,a),(-b,-c),thickness if b else -thickness,dy,(0,1),b)) # side b + drawS(side((x+dx,y+dy),(-b,-c),(d,-c),thickness if c else -thickness,dx,(-1,0),c)) # side c + drawS(side((x,y+dy),(d,-c),(d,a),-thickness if d else thickness,dy,(0,-1),d)) # side d + + #selectedObject = self.svg.selected[self.options.ids[0]] + #typeOfSelectedObject = selectedObject.tag + #inkex.utils.debug("I am a: ") + #inkex.utils.debug(pieces) + #inkex.utils.debug(" !") + #for elem in self.svg.get_selected(): + # elem.style['fill']='red' + +if __name__ == '__main__': + BoxMaker().run() \ No newline at end of file