├── LICENSE ├── README.md ├── deploy.sh ├── ell_box.inx ├── ell_box.py └── images ├── boxes.jpeg ├── dinosaur.jpeg ├── fin detail.png ├── fish n shark.jpeg └── foobarbox.jpeg /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014 Bastiaan van der Peet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # elliptical-box-maker 2 | 3 | Inkscape plug-in that generates drawings for an elliptical box which can be made using a laser cutter. 4 | ##Overview 5 | 6 | This [Inkscape](https://inkscape.org/) plugin is intended to help you create laser cuttable designs. 7 | To give you an idea about what it can do. Here are some examples that were made using this plugin. 8 | ![Boxes](/images/boxes.jpeg) 9 | ![Simple box](/images/foobarbox.jpeg) 10 | 11 | With a little bit of effort it is also possible to make something a bit more original. For example: 12 | ![A dinosaur](/images/dinosaur.jpeg) 13 | ![Sea life](/images/fish%20n%20shark.jpeg) 14 | 15 | 16 | ## Installation 17 | It's probably a good idea to use the [latest release](https://github.com/BvdP/elliptical-box-maker/releases) in stead of downloading the latest commit. 18 | The latest commit is probably work in progress and may not be a very good user experience. 19 | 20 | You need to download the source code and copy `ell_box.py` and `ell_box.inx` to your Inkscape extensions directory (see below). You also need another extension https://github.com/BvdP/Inkscape_helper . Put the two files in a folder called "Inkscape_helper" (note the capital I) and put the folder into the extensions directory where you put the ell_box.pi and ell_box.inx files. 21 | 22 | The other files are part of this documentation that you are reading, there is no need to copy those. 23 | If you then start Inkscape the plugin should show up in the Extensions menu under Laser Tools --> Elliptical Box Maker. 24 | 25 | In the following paragraphs "local install" means installing in your own inkscape folders. You don't need special rights to do this but other users cannot use the plugin (unless they install it themselves). 26 | "System wide install" means that every user on the system can use the plugins but you need admin rights to install them. 27 | 28 | ### On Windows: 29 | * Local install: put both files in `C:\Users\USERNAME\AppData\Roaming\inkscape\extensions` (where USERNAME is your username, i.e. the name you use when you log in) 30 | * System wide install: put the files in `C:\Program Files\Inkscape\share\extensions` 31 | 32 | ### On Linux or Mac: 33 | * Local install: put both files in `~/.config/inkscape/extensions` 34 | * System wide install: put them in `/usr/share/inkscape/extensions` 35 | 36 | # Usage 37 | 38 | For now check the [tutorial](http://www.instructables.com/id/Generating-elliptical-boxes-using-a-laser-cutter-a/) on Instructables. 39 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | cp ell_box.py ~/.config/inkscape/extensions 2 | cp ell_box.inx ~/.config/inkscape/extensions -------------------------------------------------------------------------------- /ell_box.inx: -------------------------------------------------------------------------------- 1 | 2 | Elliptical Box Maker 3 | be.fablab-leuven.inkscape.elliptical_box 4 | inkex.py 5 | ell_box.py 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 3.0 19 | 10.0 20 | 10.0 21 | 10.0 22 | 1.5 23 | false 24 | 3 25 | 120 26 | false 27 | false 28 | false 29 | 30 | all 31 | 32 | 33 | 34 | 35 | 38 | 39 | -------------------------------------------------------------------------------- /ell_box.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from inkscape_helper.Coordinate import Coordinate 4 | import inkscape_helper.Effect as eff 5 | import inkscape_helper.SVG as svg 6 | from inkscape_helper.Ellipse import Ellipse 7 | 8 | from inkscape_helper.Line import Line 9 | from inkscape_helper.EllipticArc import EllipticArc 10 | 11 | from math import * 12 | 13 | #Note: keep in mind that SVG coordinates start in the top-left corner i.e. with an inverted y-axis 14 | 15 | # first define some SVG primitives 16 | greenStyle = svg.green_style 17 | 18 | def _makeCurvedSurface(topLeft, w, h, cutSpacing, hCutCount, thickness, parent, invertNotches = False, centralRib = False): 19 | width = Coordinate(w, 0) 20 | height = Coordinate(0, h) 21 | wCutCount = int(floor(w / cutSpacing)) 22 | if wCutCount % 2 == 0: 23 | wCutCount += 1 # make sure we have an odd number of cuts 24 | xCutDist = w / wCutCount 25 | xSpacing = Coordinate(xCutDist, 0) 26 | ySpacing = Coordinate(0, cutSpacing) 27 | cut = height / hCutCount - ySpacing 28 | plateThickness = Coordinate(0, thickness) 29 | notchEdges = [] 30 | topHCuts = [] 31 | bottomHCuts = [] 32 | 33 | p = svg.Path() 34 | 35 | for cutIndex in range(wCutCount): 36 | if (cutIndex % 2 == 1) != invertNotches: # make a notch here 37 | inset = plateThickness 38 | else: 39 | inset = Coordinate(0, 0) 40 | 41 | # A-column of cuts 42 | aColStart = topLeft + xSpacing * cutIndex 43 | notchEdges.append((aColStart - topLeft).x) 44 | 45 | if cutIndex > 0: # no cuts at x == 0 46 | p.move_to(aColStart, True) 47 | p.line_to(cut / 2) 48 | 49 | for j in range(hCutCount - 1): 50 | pos = aColStart + cut / 2 + ySpacing + (cut + ySpacing) * j 51 | p.move_to(pos, True) 52 | p.line_to(cut) 53 | 54 | p.move_to(aColStart + height - cut / 2, True) 55 | p.line_to(cut / 2) 56 | 57 | 58 | # B-column of cuts, offset by half the cut length; these cuts run in the opposite direction 59 | bColStart = topLeft + xSpacing * cutIndex + xSpacing / 2 60 | for j in reversed(range(hCutCount)): 61 | end = bColStart + ySpacing / 2 + (cut + ySpacing) * j 62 | start = end + cut 63 | if centralRib and hCutCount % 2 == 0 and cutIndex % 2 == 1: 64 | holeTopLeft = start + (ySpacing - plateThickness - xSpacing) / 2 65 | if j == hCutCount // 2 - 1: 66 | start -= plateThickness / 2 67 | p.move_to(holeTopLeft + plateThickness + xSpacing, True) 68 | p.line_to(-xSpacing) 69 | 70 | p.move_to(holeTopLeft, True) 71 | p.line_to(xSpacing) 72 | 73 | elif j == hCutCount // 2: 74 | end += plateThickness / 2 75 | if j == 0: # first row 76 | end += inset 77 | elif j == hCutCount - 1: # last row 78 | start -= inset 79 | p.move_to(start, True) 80 | p.line_to(end, True) 81 | 82 | #horizontal cuts (should be done last) 83 | topHCuts.append((aColStart + inset, aColStart + inset + xSpacing)) 84 | bottomHCuts.append((aColStart + height - inset, aColStart + height - inset + xSpacing)) 85 | 86 | # draw the outline 87 | for c in reversed(bottomHCuts): 88 | p.move_to(c[1], True) 89 | p.line_to(c[0], True) 90 | 91 | p.move_to(topLeft + height, True) 92 | p.line_to(-height) 93 | 94 | for c in topHCuts: 95 | p.move_to(c[0], True) 96 | p.line_to(c[1], True) 97 | 98 | p.move_to(topLeft + width, True) 99 | p.line_to(height) 100 | 101 | 102 | group = svg.group(parent) 103 | p.path(group) 104 | 105 | notchEdges.append(w) 106 | return notchEdges 107 | 108 | def _makeNotchedEllipse(center, ellipse, start_theta, thickness, notches, parent, invertNotches): 109 | start_theta += pi # rotate 180 degrees to put the lid on the topside 110 | 111 | ell_radius = Coordinate(ellipse.x_radius, ellipse.y_radius) 112 | ell_radius_t = ell_radius + Coordinate(thickness, thickness) 113 | 114 | theta = ellipse.theta_from_dist(start_theta, notches[0]) 115 | ell_point = center + ellipse.coordinate_at_theta(theta) 116 | prev_offset = ellipse.tangent(theta) * thickness 117 | 118 | p = svg.Path() 119 | p.move_to(ell_point, absolute=True) 120 | 121 | for n in range(len(notches) - 1): 122 | theta = ellipse.theta_from_dist(start_theta, notches[n + 1]) 123 | ell_point = center + ellipse.coordinate_at_theta(theta) 124 | notch_offset = ellipse.tangent(theta) * thickness 125 | notch_point = ell_point + notch_offset 126 | 127 | if (n % 2 == 0) != invertNotches: 128 | p.arc_to(ell_radius, ell_point, absolute=True) 129 | prev_offset = notch_offset 130 | 131 | else: 132 | p.line_to(prev_offset) 133 | p.arc_to(ell_radius_t, notch_point, absolute=True) 134 | p.line_to(-notch_offset) 135 | 136 | p.path(parent) 137 | 138 | 139 | 140 | class EllipticalBox(eff.Effect): 141 | """ 142 | Creates a new layer with the drawings for a parametrically generaded box. 143 | """ 144 | def __init__(self): 145 | options = [ 146 | ['unit', str, 'mm', 'Unit, one of: cm, mm, in, ft, ...'], 147 | ['thickness', float, '3.0', 'Material thickness'], 148 | ['width', float, '100', 'Box width'], 149 | ['height', float, '100', 'Box height'], 150 | ['depth', float, '100', 'Box depth'], 151 | ['cut_dist', float, '1.5', 'Distance between cuts on the wrap around. Note that this value will change slightly to evenly fill up the available space.'], 152 | ['auto_cut_dist', eff.inkex.Boolean, 'false', 'Automatically set the cut distance based on the curvature.'], # in progress 153 | ['cut_nr', int, '3', 'Number of cuts across the depth of the box.'], 154 | ['lid_angle', float, '120', 'Angle that forms the lid (in degrees, measured from centerpoint of the ellipse)'], 155 | ['body_ribcount', int, '0', 'Number of ribs in the body'], 156 | ['lid_ribcount', int, '0', 'Number of ribs in the lid'], 157 | ['invert_lid_notches', eff.inkex.Boolean, 'false', 'Invert the notch pattern on the lid (keeps the lid from sliding sideways)'], 158 | ['central_rib_lid', eff.inkex.Boolean, 'false', 'Create a central rib in the lid'], 159 | ['central_rib_body', eff.inkex.Boolean, 'false', 'Create a central rib in the body'] 160 | ] 161 | eff.Effect.__init__(self, options) 162 | 163 | 164 | def effect(self): 165 | """ 166 | Draws as basic elliptical box, based on provided parameters 167 | """ 168 | 169 | # input sanity check 170 | error = False 171 | if min(self.options.height, self.options.width, self.options.depth) == 0: 172 | eff.errormsg('Error: Dimensions must be non zero') 173 | error = True 174 | 175 | if self.options.cut_nr < 1: 176 | eff.errormsg('Error: Number of cuts should be at least 1') 177 | error = True 178 | 179 | if (self.options.central_rib_lid or self.options.central_rib_body) and self.options.cut_nr % 2 == 1: 180 | eff.errormsg('Error: Central rib is only valid with an even number of cuts') 181 | error = True 182 | 183 | if self.options.unit not in self.knownUnits: 184 | eff.errormsg('Error: unknown unit. '+ self.options.unit) 185 | error = True 186 | 187 | if error: 188 | exit() 189 | 190 | 191 | # convert units 192 | unit = self.options.unit 193 | H = self.svg.unittouu(str(self.options.height) + unit) 194 | W = self.svg.unittouu(str(self.options.width) + unit) 195 | D = self.svg.unittouu(str(self.options.depth) + unit) 196 | thickness = self.svg.unittouu(str(self.options.thickness) + unit) 197 | cutSpacing = self.svg.unittouu(str(self.options.cut_dist) + unit) 198 | cutNr = self.options.cut_nr 199 | 200 | doc_root = self.document.getroot() 201 | docWidth = self.svg.unittouu(doc_root.get('width')) 202 | docHeigh = self.svg.unittouu(doc_root.attrib['height']) 203 | 204 | layer = svg.layer(doc_root, 'Elliptical Box') 205 | 206 | ell = Ellipse(W, H) 207 | 208 | #body and lid 209 | lidAngleRad = self.options.lid_angle * 2 * pi / 360 210 | lid_start_theta = ell.theta_at_angle(pi / 2 - lidAngleRad / 2) 211 | lid_end_theta = ell.theta_at_angle(pi / 2 + lidAngleRad / 2) 212 | 213 | lidLength = ell.dist_from_theta(lid_start_theta, lid_end_theta) 214 | bodyLength = ell.dist_from_theta(lid_end_theta, lid_start_theta) 215 | 216 | # do not put elements right at the edge of the page 217 | xMargin = 3 218 | yMargin = 3 219 | 220 | bottom_grp = svg.group(layer) 221 | top_grp = svg.group(layer) 222 | 223 | bodyNotches = _makeCurvedSurface(Coordinate(xMargin, yMargin), bodyLength, D, cutSpacing, cutNr, 224 | thickness, bottom_grp, False, self.options.central_rib_body) 225 | lidNotches = _makeCurvedSurface(Coordinate(xMargin, D + 2 * yMargin), lidLength, D, cutSpacing, cutNr, 226 | thickness, top_grp, not self.options.invert_lid_notches, 227 | self.options.central_rib_lid) 228 | 229 | # create elliptical sides 230 | sidesGrp = svg.group(layer) 231 | 232 | elCenter = Coordinate(xMargin + thickness + W / 2, 2 * D + H / 2 + thickness + 3 * yMargin) 233 | 234 | # indicate the division between body and lid 235 | p = svg.Path() 236 | if self.options.invert_lid_notches: 237 | p.move_to(elCenter + ell.coordinate_at_theta(lid_start_theta + pi), True) 238 | p.line_to(elCenter, True) 239 | p.line_to(elCenter + ell.coordinate_at_theta(lid_end_theta + pi), True) 240 | 241 | else: 242 | angleA = ell.theta_from_dist(lid_start_theta, lidNotches[1]) 243 | angleB = ell.theta_from_dist(lid_start_theta, lidNotches[-2]) 244 | 245 | p.move_to(elCenter + ell.coordinate_at_theta(angleA + pi), True) 246 | p.line_to(elCenter, True) 247 | p.line_to(elCenter + ell.coordinate_at_theta(angleB + pi), True) 248 | 249 | _makeNotchedEllipse(elCenter, ell, lid_end_theta, thickness, bodyNotches, sidesGrp, False) 250 | _makeNotchedEllipse(elCenter, ell, lid_start_theta, thickness, lidNotches, sidesGrp, 251 | not self.options.invert_lid_notches) 252 | 253 | p.path(sidesGrp, greenStyle) 254 | 255 | # ribs 256 | if self.options.central_rib_lid or self.options.central_rib_body: 257 | innerRibCenter = Coordinate(xMargin + thickness + W / 2, 2 * D + 1.5 * (H + 2 *thickness) + 4 * yMargin) 258 | innerRibGrp = svg.group(layer) 259 | 260 | outerRibCenter = Coordinate(2 * xMargin + 1.5 * (W + 2 * thickness), 261 | 2 * D + 1.5 * (H + 2 * thickness) + 4 * yMargin) 262 | outerRibGrp = svg.group(layer) 263 | 264 | 265 | if self.options.central_rib_lid: 266 | _makeNotchedEllipse(innerRibCenter, ell, lid_start_theta, thickness, lidNotches, innerRibGrp, False) 267 | _makeNotchedEllipse(outerRibCenter, ell, lid_start_theta, thickness, lidNotches, outerRibGrp, True) 268 | 269 | if self.options.central_rib_body: 270 | spacer = Coordinate(0, 10) 271 | _makeNotchedEllipse(innerRibCenter + spacer, ell, lid_end_theta, thickness, bodyNotches, innerRibGrp, False) 272 | _makeNotchedEllipse(outerRibCenter + spacer, ell, lid_end_theta, thickness, bodyNotches, outerRibGrp, True) 273 | 274 | if self.options.central_rib_lid or self.options.central_rib_body: 275 | svg.text(sidesGrp, elCenter, 'side (duplicate this)') 276 | svg.text(innerRibGrp, innerRibCenter, 'inside rib') 277 | svg.text(outerRibGrp, outerRibCenter, 'outside rib') 278 | 279 | # Create effect instance and apply it. 280 | effect = EllipticalBox() 281 | effect.run() 282 | -------------------------------------------------------------------------------- /images/boxes.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BvdP/elliptical-box-maker/e98b7caf3dac07c0ee0675cb75add4e10dee1397/images/boxes.jpeg -------------------------------------------------------------------------------- /images/dinosaur.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BvdP/elliptical-box-maker/e98b7caf3dac07c0ee0675cb75add4e10dee1397/images/dinosaur.jpeg -------------------------------------------------------------------------------- /images/fin detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BvdP/elliptical-box-maker/e98b7caf3dac07c0ee0675cb75add4e10dee1397/images/fin detail.png -------------------------------------------------------------------------------- /images/fish n shark.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BvdP/elliptical-box-maker/e98b7caf3dac07c0ee0675cb75add4e10dee1397/images/fish n shark.jpeg -------------------------------------------------------------------------------- /images/foobarbox.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BvdP/elliptical-box-maker/e98b7caf3dac07c0ee0675cb75add4e10dee1397/images/foobarbox.jpeg --------------------------------------------------------------------------------