Boxes.py¶
Create boxes and more with a laser cutter!
Contents:
About Boxes.py¶
- Boxes.py is an online box generator
- Boxes.py is an Inkscape plug-in
- Boxes.py is library to write your own
- Boxes.py is free software licensed under GPL v3+
- Boxes.py is written in Python and runs with Python 3
Boxes.py comes with a growing set of ready-to-use, fully parametrized generators. See https://florianfesti.github.io/boxes/html/generators.html for the full list.
Features¶
Boxes.py generates SVG images that can be viewed directly in a web browser but also postscript and - with pstoedit as external helper - other vector formats including dxf, plt (aka hpgl) and gcode.
Of course the library and the generators allow selecting the “thickness” of the material used and automatically adjusts lengths and width of joining fingers and other elements.
The “burn” parameter compensates for the material removed by the laser. This allows fine tuning the gaps between joins up to the point where plywood can be press fitted even without any glue.
Finger Joints are the work horse of the library. They allow 90° edges and T connections. Their size is scaled up with the material “thickness” to maintain the same appearance. The library also allows putting holes and slots for screws (bed bolts) into finger joints, although this is currently not supported for the included generators.
Dovetail joints can be used to join pieces in the same plane.
Flex cuts allows bending and stretching the material in one direction. This is used for rounded edges and living hinges.
Documentation¶
Boxes.py comes with Sphinx based documentation for usage, installation and development.
The rendered version can be viewed at <https://florianfesti.github.io/boxes/html/index.html>.
Installation¶
Boxes.py is a pure Python project that does support the regular setuptools
method of shipping with setup.py
. setup.py --help-commands
and
setup.py CMD --help
provide the necessary documentation for building,
installing or building binary formats.
Requirements¶
Affine¶
Affine
(package name may be python-affine
or
python3-affine
) is used for vector calculation.
Markdown¶
Markdown
(package name may be python-markdown
or
python3-markdown
) is used to format the description texts.
LXML¶
lxml
(package name may be python-lxml
or python3-lxml
)
is needed for the Inkscape plugin.
setuptools¶
Setup.py uses the setuptools
library (package name may be
python*-setuptools
). You only need it if you want to build the
package.
ps2edit¶
While not a hard requirement Boxes.py uses ps2edit
to offer formats
that are not supported by Cairo: DXF, gcode, PLT. Currently the location
Boxes.py looks for ps2edit
is hard coded to /usr/bin/pstoedit
in the boxes.formats.Formats
class.
Python¶
Boxes.py is implemented in Python 3. It used to work on Python 2.7, too. But with the Python 2 approaching end of life support has been dropped.
Sphinx¶
For building the documentation locally you need the Sphinx documentation generator (package name may be python-sphinx or python3-sphinx). It is not needed for anything else. Boxes.py can be run and changed just fine without.
Running from working dir¶
Due to lazy developer(s) Boxes.py can also run from the Git checkout.
The scripts in scripts/
are all suppossed to just work right
after git clone
. The Inkscape needs a bit manual work to get
running. See below.
Inkscape¶
As binary
Boxes.py can be used as a set of Inkscape plugins. The package does
install the necessary .inx files to /usr/share/inkscape/extensions
on unix operating systems. The .inx files assume that the boxes
executable is available in the path (which it is when installing the
binary package)
git repository easy way
After cloning it may be most convenient to generate the .inx files
right in place by executing scripts/boxes2inkscape
with the taget
path as only parameter.
- global:
scripts/boxes2inkscape /usr/share/inkscape/extensions/
- userspace:
scripts/boxes2inkscape ~/.config/inkscape/extensions/
On non unix operating the target directories may differ. You can look up the directories “User extensions” and “Inkscape extensions” within the Inkscape preferences Edit -> Preferences… -> System.
git repository manual way
setup.py build
creates the *.inx
files in the inkex/
directory.
They then have to be copied in either the global or the per user
extension directory of Inkscape. These are
/usr/share/inkscape/extensions/
and
~/.config/inkscape/extensions/
on a unix operating system.
On non unix operating the target directories may differ. You can look
up the directories “User extensions” and “Inkscape extensions” within
the Inkscape preferences Edit -> Preferences… -> System.
As an alternative you can create a symlink to the inkex/
directory
within the desired inkscape extension directory.
Platform specific instructions¶
macOS¶
It is recommended to use Homebrew to install the dependencies for Boxes.py. See brew.sh on how to install Homebrew.
General¶
Install Python 3 and other dependencies:
brew install python3 git
Optional:
brew install pstoedit
Install cairio:
brew install pkg-config
Install required Python modules:
pip3 install Markdown lxml affine
Download Boxes.py via Git:
git clone https://github.com/florianfesti/boxes.git
Run Boxes.py:
Local web server on port 8000:
./scripts/boxesserver
Command line variant (CLI):
./scripts/boxes
System-wide with Inkscape extension¶
To install Boxes.py system-wide with the Inkscape extension, following steps are required:
Install Inkscape with Homebrew Cask (requires XQuartz):
brew cask install inkscape
From the root directory of the repository, run:
./setup.py install
Now
boxes
andboxesserver
can be executed like other commands and the Inkscape extension should be available.
Troubleshooting¶
When using the Inkscape extension something like the following error might occur:
Traceback (most recent call last):
File "/Users/martin/.config/inkscape/extensions/boxes", line 107, in <module>
main()
File "/Users/martin/.config/inkscape/extensions/boxes", line 47, in main
run_generator(name, sys.argv[2:])
File "/Users/martin/.config/inkscape/extensions/boxes", line 73, in run_generator
box.close()
File "/usr/local/lib/python3.7/site-packages/boxes-0.1-py3.7.egg/boxes/__init__.py", line 594, in close
svgutil.svgMerge(self.output, self.inkscapefile, out)
File "/usr/local/lib/python3.7/site-packages/boxes-0.1-py3.7.egg/boxes/svgutil.py", line 144, in svgMerge
from lxml import etree as et
ImportError: dlopen(/Applications/Inkscape.app/Contents/Resources/lib/python2.7/site-packages/lxml/etree.so, 2): Symbol not found: _PyBaseString_Type
Referenced from: /Applications/Inkscape.app/Contents/Resources/lib/python2.7/site-packages/lxml/etree.so
Expected in: flat namespace
This is because Inkscape on macOS ships its own version of Python 2.7 where
lxml
and other dependencies are missing.
A workaround is to edit the file at
/Applications/Inkscape.app/Contents/Resources/bin/inkscape
.
At line 79 there should be following code:
export PYTHONPATH="$TOP/lib/python$PYTHON_VERS/site-packages/"
which needs to be changed to
#export PYTHONPATH="$TOP/lib/python$PYTHON_VERS/site-packages/"
This forces Inkscape to use the Python version installed by Homebrew which has all the necessary dependencies installed.
Note: This might break other extensions. In this case simply change the line back and restart Inkscape.
Windows¶
Getting the Inkscape plugins to run will likely need manual installation (see above). Note that Inkscape may come with its own Python. If you run into trouble or have better installation instructions please open a ticket on GitHub.
Native¶
Following steps are known to work under Windows 10 (64-bit):
Go to https://www.python.org/downloads/windows/ and download the “Windows x86-64 executable installer” for Python 3.7
Install Python 3.7 and make sure to check “Add Python 3.7 to PATH” while doing so
Run the command
pip install Markdown lxml affine
(Note: If the command pip is not found, you probably forgot to add the Python installation to the PATH environment variable in step 2)Download Boxes.py as ZIP archive from GitHub
Extract the ZIP archive (e.g. via the built-in Windows feature or other tools like 7-Zip)
- Change into the folder for Boxes.py,
e.g. with the command
cd \Users\[USERNAME]\Downloads\boxes-master
- Run the development server with the command
python scripts\boxesserver
Note: You likely will be notified by your firewall that it blocked network access. If you want to use boxesserver you need to allow connections.
Open the address http://localhost:8000/ in your browser and have fun :)
Additionally the command line version of Boxes.py can be used with
the command python scripts\boxes
.
Windows Subsystem for Linux¶
Another way of installing Boxes.py on Windows is to use the Windows Subsystem for Linux (WSL). This requires newer versions of Windows 10. Once it is installed (e.g. via the Ubuntu App from the Microsoft Store), the installation is identical to the installation on Linux systems.
Using Boxes.py¶
Boxes.py is made of a library that is not visible to the user and multiple generators – each having its own set of parameters and creating a drawing for it own type of object. These generators are divided up into different groups to make it easier to find them:
- Boxes
- Boxes with flex
- Trays and Drawer Inserts
- Shelves
- Parts and Samples
- Misc
- Unstable
The parameters for each generators also come in groups.
Units of meassurement¶
In general all measurements are in Millimeters (mm). There is no option to change the units of measurement and there is no plan to add such a option.
A second way to define lengths is as multiple of the material thickness which is one of the standard parameters described below. This allows features to retain their proportions even if some parts depend on the material thickness.
The description texts should state the unit of each argument - please open a ticket if the units are missing somewhere.
Default arguments¶
In the web interface this is the bottom group right before the
Render
button. These are basically all technical settings that
have little to do with the object being rendered but more with the
material used and the way the drawing and the material is processed.
The settings are
thickness¶
The thickness of the material used. This value is used at many places to define the sizes of features like finger joints, hinges, … It is very important to get the value right - especially if there are fingers that need to fit into some holes. Be aware that many materials may differ from their nominal value. You should always measure the thickness for every sheet unless you have a very reliable supply that is known to stick very closely to specifications. For (ply) wood even a 100th of a millimeter makes a notable difference in how stiff the fit is. Harder more brittle materials may be even more picky.
burn¶
The burn correction aka kerf is the distance the laser has to keep from the edge of the parts. If the laser would cut right on the edge it would cut away the outside perimeter of the part. So the burn value is basically the radius of the laser - or half the width of the laser cut.
The value of the burn parameter depends on your laser cutter, the material cut and the thickness of the material. In addition it depends on whether you want the parts to be over or under sized. Materials that are spongy like wood can be cut oversized (larger burn value) so they can be press fitted with some force and may be assembled without glue. Brittle materials (like Acrylic) need to be cut undersized to leave a gap for the glue.
Note: The way the burn param works is a bit counter intuitive. Bigger burn values make a tighter fit. Smaller values make a looser fit.
Small changes in the burn param can make a notable difference. Typical steps for adjustment are 0.01 or even 0.005mm to choose between different amounts of force needed to press plywood together.
To find the right burn value cut out a rectangle and then meassure how much smaller it is than its nominal size. The burn value should be around half of the difference. To test the fit for several values at once you can use the BurnTest generator in the “Parts and Samples” section.
format¶
Boxes.py is able to create multiple formats. For most of them it
requires ps2edit
. Without ps2edit
only SVG
and postscript
(ps) is supported. Otherwise you can also
select
- ai
- dxf
- gcode
- plt
Other formats supported by ps2edit
can be added easily. Please
open a ticket on GitHub if you need one.
tabs¶
Tabs are small bridges between the parts and surrounding material that keep the part from falling out. In theory their width should be affected by the burn parameter. But it is more practical to have both independent so you can tune them separately. Most parts and generators support this features but there may be some that don’t.
For plywood values of 0.2 to 0.3mm still allow getting the parts out by hand (Depending on you laser cutter and the exact material). With little more you will need a knife to cut them loose.
debug¶
Most regular users won’t need this option.
It adds some construction lines that are helpful for developing new generators. Only few pieces actually support the parameter. The most notable being finger holes that show the border of the piece they belong to. This helps checking whether the finger holes are placed correctly.
reference¶
Converting vector graphics is error prone. Many formats have very weird ideas how their internal units translates to real world dimensions. If reference is set to non zero Boxes.py renders a rectangle of the given length. It can be used to check if the drawing is still at the right scale or may give clues on how to scale it back to the right proportions.
Edge Type parameters¶
All but the simplest edge types have a number of settings controlling how exactly they should look. Generators are encouraged to offer these settings to the user. In the web interface they are folded up. In the command line interfacce they are grouped together. Users should be aware that not all settings are practical to change. For now Boxes.py does not allow hiding some settings.
Contributing to Boxes.py¶
You are thinking about contributing to Boxes.py? That’s great! Boxes.py is designed to be re-used and extended.
This document gives you some guidelines how your contribution is most likely to impact the development and your changes are most likely to be merged into the upstream repository.
Most of them should be just general best practises and not be surprising. Don’t worry if you find them too complicated. It is OK leave the final touch to someone else.
Writing code for Boxes.py¶
You will often be compelled to just do a quick thing that will solve your immediate needs. That’s fine. But nevertheless it is often worth doing things the right way and be able to submit your changes upstream. For one to give something back to the community. But also for purely selfish reasons like getting the code maintained. Also Boxes.py is designed to make doing things properly the easy way.
Here are some guidelines that make this easier. Depending on what you are up to they may apply to a varying degree. It’s ok to submit patches that are not quite ready yet. But please state in the pull request message what you think the status is and whether you want help or are going to finish it on your own.
- Please fork the repository at GitHub before getting started
- Start with creating separate branches for each of your new generators or features
- You can merge them into your master branch to have them all in one place
- Please continue your work in the branches and repeatedly merge them to master
- Before submitting a pull request intended to go upstream have clean patches that are self contained and error free
- Re-order and squash patches with git rebase -i
- The patches should containing meaningful changes and not (necessarily) reflect how the code was created
- Rebase your branch to the current master branch
- Be prepared that your code may get reworked before being merged upstream
- Submit a pull request in GitHub based on your feature branch
- Describe the status of the patch set and your intentions with it in the pull request message
If you want to discuss your idea open a ticket describing it and ask questions there. This is encouraged even if you think you know what you want to do. There are many short cuts in Boxes.py and pointing you in the right direction may save you a lot of work.
If you want feed back on you code feel free to open a PR. State that this is work in progress in the PR message. It’s OK if it does not follow the guide lines (yet).
Writing new Generators¶
Writing new generators is the most straight forward thing to do with Boxes.py. Here are some guidelines that make it easier to get them added:
- Start with a copy of another generator or boxes/generators/_template.py
- Commit changes to the library in separate patches
- Use parameters with sane defaults instead of hard coding dimensions
- Simple generators can end up as one single commit
- For more complicated generators there can be multiple patches - each adding another feature
Improving the Documentation¶
Boxes.py comes with Sphinx based documentation that is in large parts generated from the doc strings in the code. Nevertheless documentation has a tendency to get outdated. If you encounter outdated pieces of documentation feel free to submit a pull request or open a ticket pointing out what should be changed or even suggesting a better text.
To check your changes docs need to be build with make html in documentation/src. This places the compiled documentation in documentation/build/html. You need to have sphinx installed for this to work.
The online documentation gets build and updated automatically by the Travis CI as soon as the changes makes it into the GitHub master branch.
Improving the User Interface¶
Coming up with good names and good descriptions is hard. Often writing a new generator is much easier than coming up with a good name for it and its arguments. If you think something deserves a better name or description and you can come up with one please don’t hesitate to open a ticket. It is this small things that make something like Boxes.py easy or hard to use.
There is also an - often empty - space for a longer text for each generator that could house assembling instructions, instructions for use or just more detailed descriptions. If you are interested in writing some please open a ticket. Your text does not have to be perfect. We can work on it together.
Reporting bugs¶
If you encounter issues with Boxes.py, please open a ticket at GitHub. Please provide all information necessary to reproduce the bug. Often this can be the URL of the broken result. If the issue is easy to spot it may be sufficient to just give a brief description. Otherwise it can be helpful to attach the resulting SVG, a screen shot or the error message. Add a “bug” tag to draw additional attention.
Suggesting new generators or features¶
If you have an idea for a new generator or feature please open a ticket. Give some short rational how or where you would use such a thing. Try to give a precise description how it should look like and which features and details are important. The less is left open the easier it is to implement. You can add an “enhancement” tag.
Using the Boxes.py API¶
If there is no generator fitting your needs you can either adjust an existing one (may be by copying it to another name first) or writing a new one from scratch.
Architecture¶
Boxes.py it structured into several distinct tiers.
User Interfaces¶
User interfaces allow users to render the different generators. They handle the parameters of Generators and convert them to a readable form. The user interfaces are located in scripts/. Currently there is
- scripts/boxes – the command line interface
- scripts/boxesserver – the web interface
- scripts/boxes2inx – generates Inkscape extensions
- scripts/boxes_example.ipynb – Jupyter notebook
Generators¶
A (box) generator is an sub class of boxes.Boxes. It generates one drawing. The sub classes over load .__init__() to set their parameters and implement .render() that does the actual drawing.
Generators are found in boxes/generators/
. They are included into
the web UI and the CLI tool by the name of their class. So whenever
you copy either an existing generator or the sceleton in
boxes/generators/_template.py
you need to change the name of the
main class first.
Parts¶
Parts are a single call that draws something according to a set of parameters. There is a number of standard parts. Their typical params are explained in the API docs.
Only real requirement for a part it supporting the move parameter for placement.
Part Callbacks¶
Most parts support callbacks - either one in the middle for round parts or one for each edge. They allow placing holes or other features on the part.
Edges¶
Edges are turtle graphic commands. But they have been elevated to
proper Classes to handle outsets. They can be passed as parameters to parts.
There is a set of standard edges found in .edges
. They are
associated with a single char which can be used instead of the
Edge object itself at most places. This allows passing the edge
description of a part as a string.
Turtle graphics¶
There are a few turtle graphics commands that do the actual drawing. Corners with an positive angle (going counter clockwise) close the part while negative angles (going clockwise) create protrusions. This is inversed for holes which need to be drawn clockwise.
Getting this directions right is important to make the burn correction (aka kerf) work properly.
Simple drawing commands¶
These also are simple drawing commands. Some of them get x
, y
and
angle
parameters to draw somewhere specific. Some just draw right
at the current coordinate origin. Often these commands create holes or
hole patterns.
Back end¶
Boxes.py used to use cairo as graphics library. It now uses its own - pure Python - back end. It is not fully encapsulated within the drawing methods of the Boxes class. Although this is the long term goal. Boxes.ctx is the context all drawing is made on.
Generators¶
Generators are sub classes of
Most code is directly in this class. Sub class are supposed to over
write the .__init__()
and .render()
method.
The Boxes class keeps a canvas object (self.ctx) that all
drawing is made on. In addition it keeps a couple of global settings
used for various drawing operations. See the .__init__()
method
for the details.
For implementing a new generator forking an existing one or using the
boxes/generators/_template.py
is probably easier than starting
from scratch.
Many methods and attributes are for use of the sub classes. These methods are the interface for the user interfaces to interact with the generators:
-
Boxes.
parseArgs
(args=None)[source]¶ Parse command line parameters
Parameters: args – (Default value = None) parameters, None for using sys.argv
-
Boxes.
render
()[source]¶ Implement this method in your sub class.
You will typically need to call .parseArgs() before calling this one
-
Boxes.
open
()[source]¶ Prepare for rendering
Create canvas and edge and other objects Call this before .render()
-
Boxes.
close
()[source]¶ Finish rendering
Flush canvas to disk and convert output to requested format if needed. Call after .render()
Handling Generators¶
To handle the generators there is code in the boxes.generators
package.
This adds generators to the user interfaces automatically. For this to work it is important that the class names are unique. So whenever you start a new generator please change the class name right away.
Generator Arguments¶
Boxes.py uses the argparse
standard library for handling the
arguments for the generators. It is used directly for the boxes
command line tool. But it also handles – with some additional code –
the web interface and the Inkscape extensions. To make this work one
has to limit the kind of parameters used. Boxes.py supports the
following types:
int
float
str
boxes.boolarg
– an alternative tobool
that works with the web interfaceboxes.argparseSections
– multiple lengths e.g. for dividing up a box in one direction
and
For the standard types there is code to create HTML and Inkscape
extensions. The other types can have .html()
and .inx()
methods.
The argument parser need to be built in the .__init__()
method
after calling the method of the super class. Have a look at
As many arguments are used over and over there is a function that can add the most common ones:
-
Boxes.
buildArgParser
(*l, **kw)[source]¶ Add commonly used arguments
Parameters: - *l – parameter names
- **kw – parameters with new default values
Supported parameters are
- floats: x, y, h, hi
- argparseSections: sx, sy, sh
- ArgparseEdgeType: bottom_edge, top_edge
- boolarg: outside
- str (selection): nema_mount
Check the source for details about the single arguments.
Other arguments can be added with the normal argparser API - namely
-
ArgumentParser.
add_argument
(dest, ..., name=value, ...)¶ add_argument(option_string, option_string, …, name=value, …)
of the Boxes.argparser
attribute.
Edge style arguments¶
Edges that work together share a Settings class (and object). These
classes can create argparse
groups:
See
-
BOX.
__init__
()[source] Initialize self. See help(type(self)) for accurate signature.
for a list of possible edge settings. These regular settings are used
in the standard edge instances used everywhere. For special edge
instances you can call them with a prefix
parameter. But you then
need to deal with the results on your own.
Default Arguments¶
The Default arguments get added automatically by the super class’s constructor.
Accessing the Arguments¶
For convenience content of the arguments are written to attributes of
the Boxes instance before .render()
is called. This is done by
Boxes.parseArgs
. But most people won’t need to care as this is
handled by the frame work. Be careful to not overwrite important
methods or attributes by using conflicting argument names.
Parts¶
There are a few parameter shared by many of those parts:
The callback parameter¶
The callback parameter can take on of the following forms:
- A function (or bound method) that expects one parameter: the number of the side the callback is currently called for.
- A dict with some of the numbers of the sides as keys and functions without parameters as values.
- A list of functions without parameters. The list may contain None as place holder and be shorter than the number of sides.
The callback functions are called with the side of the part at the positive x and y axis. If the edge uses up space this space is below the x axis. You do not have to restore the coordinate settings in the callback.
Instead of functions it can be handy to use a lambda expression calling the one building block function you need (e.g. fingerHolesAt).
For your own parts you can use this helper function:
-
Boxes.
cc
(callback, number, x=0.0, y=None)[source]¶ Call callback from edge of a part
Parameters: - callback – callback (callable or list of callables)
- number – number of the callback
- x – (Default value = 0.0) x position to be call on
- y – (Default value = None) y position to be called on (default does burn correction)
For finding the right piece to the callback parameter this function is used:
The move parameter¶
For placing the parts the move
parameter can be used. It is string
with space separated words - at most one of each of those options:
- left / right
- up / down
- only
If “only” is given the part is not drawn but only the move is
done. This can be useful to go in one direction after having placed
multiple parts in the other and have returned with .ctx.restore()
.
For implementing parts the following helper function can be used to
implement a move
parameter:
-
Boxes.
move
(x, y, where, before=False)[source]¶ Intended to be used by parts where can be combinations of “up” or “down”, “left” or “right”, “only”, “mirror” and “rotated” when “only” is included the move is only done when before is True “mirror” will flip the part along the y axis “rotated” draws the parts rotated 90 counter clockwise The function returns whether actual drawing of the part should be omited.
Parameters: - x – width of part
- y – height of part
- where – which direction to move
- before – (Default value = False) called before or after part being drawn
It needs to be called before and after drawing the actual part with
the proper before
parameter set.
The edges parameter¶
The edges
parameter needs to be an iterable of Edge instances to be
used as edges of the part. Instead of instances it is possible to pass
a single character that is looked up in the .edges
dict. This
allows to pass a string with the desired characters per edge. By
default the following character are supported:
- e : straight edge
- E : as above but extended outside by one thickness
- f, F : finger joints
- h : edge with holes for finger joints
- d, D : dove tail joints
Generators can register their own Edges by putting them into the
.edges
dictionary.
Same applies to the parameters of .surroundingWall
although they
denominate single edge (types) only.
PartsMatrix¶
To place many of the same part partMatrix can used:
-
Boxes.
partsMatrix
(n, width, move, part, *l, **kw)[source]¶ place many of the same part
Parameters: - n – number of parts
- width – number of parts in a row (0 for same as n)
- move – (Default value = None)
- part – callable that draws a part and knows move param
- *l – params for part
- **kw – keyword params for part
It creates one big block of parts. The move param treat this block like on big part.
Existing Parts¶
A couple of commands can create whole parts like walls. Typically the sizes given are the inner dimension not including additional space needed for burn compensation or joints.
Currently there are the following parts:
-
Boxes.
rectangularWall
(x, y, edges='eeee', ignore_widths=[], holesMargin=None, holesSettings=None, bedBolts=None, bedBoltSettings=None, callback=None, move=None)[source]¶ Rectangular wall for all kind of box like objects
Parameters: - x – width
- y – height
- edges – (Default value = “eeee”) bottom, right, top, left
- ignore_widths – list of edge_widths added to adjacent edge
- holesMargin – (Default value = None)
- holesSettings – (Default value = None)
- bedBolts – (Default value = None)
- bedBoltSettings – (Default value = None)
- callback – (Default value = None)
- move – (Default value = None)
-
Boxes.
flangedWall
(x, y, edges='FFFF', flanges=None, r=0.0, callback=None, move=None)[source]¶ Rectangular wall with flanges extending the regular size
This is similar to the rectangularWall but it may extend to either side replacing the F edge with fingerHoles. Use with E and F for edges only.
Parameters: - x – width
- y – height
- edges – (Default value = “FFFF”) bottom, right, top, left
- flanges – (Default value = None) list of width of the flanges
- r – radius of the corners of the flange
- callback – (Default value = None)
- move – (Default value = None)
-
Boxes.
rectangularTriangle
(x, y, edges='eee', r=0.0, num=1, bedBolts=None, bedBoltSettings=None, callback=None, move=None)[source]¶ Rectangular triangular wall
Parameters: - x – width
- y – height
- edges – (Default value = “eee”) bottom, right[, diagonal]
- r – radius towards the hypothenuse
- num – (Default value = 1) number of triangles
- bedBolts – (Default value = None)
- bedBoltSettings – (Default value = None)
- callback – (Default value = None)
- move – (Default value = None)
-
Boxes.
regularPolygonWall
(corners=3, r=None, h=None, side=None, edges='e', hole=None, callback=None, move=None)[source]¶ Create regular polygone as a wall
Parameters: - corners – number of corners of the polygone
- radius – distance center to one of the corners
- h – distance center to one of the sides (height of sector)
- side – length of one side
- edges – (Default value = “e”, may be string/list of length corners)
- hole – diameter of central hole (Default value = 0)
- callback – (Default value = None, middle=0, then sides=1..)
- move – (Default value = None)
-
Boxes.
roundedPlate
(x, y, r, edge='f', callback=None, holesMargin=None, holesSettings=None, bedBolts=None, bedBoltSettings=None, wallpieces=1, extend_corners=True, move=None)[source]¶ Plate with rounded corner fitting to .surroundingWall()
For the callbacks the sides are counted depending on wallpieces
Parameters: - x – width
- y – hight
- r – radius of the corners
- callback – (Default value = None)
- holesMargin – (Default value = None) set to get hex holes
- holesSettings – (Default value = None)
- bedBolts – (Default value = None)
- bedBoltSettings – (Default value = None)
- wallpieces – (Default value = 1) # of separate surrounding walls
- extend_corners – (Default value = True) have corners outset with teh edges
- move – (Default value = None)
-
Boxes.
surroundingWall
(x, y, r, h, bottom='e', top='e', left='D', right='d', pieces=1, extend_corners=True, callback=None, move=None)[source]¶ Wall(s) with flex fiting around a roundedPlate()
For the callbacks the sides are counted depending on pieces
Parameters: - x – width of matching roundedPlate
- y – height of matching roundedPlate
- r – corner radius of matching roundedPlate
- h – inner height of the wall (without edges)
- bottom – (Default value = ‘e’) Edge type
- top – (Default value = ‘e’) Edge type
- left – (Default value = ‘D’) left edge(s)
- right – (Default value = ‘d’) right edge(s)
- pieces – (Default value = 1) number of separate pieces
- callback – (Default value = None)
- move – (Default value = None)
Parts Class¶
More parts are available in a separete class. An instance is available as Boxes.parts
-
Parts.
disc
(diameter, hole=0, callback=None, move='')[source]¶ Simple disc
Parameters: - diameter – diameter of the disc
- hole – (Default value = 0)
- callback – (Default value = None) called in the center
- move – (Defaultvalue = None)
-
Parts.
waivyKnob
(diameter, n=20, angle=45, hole=0, callback=None, move='')[source]¶ Disc with a waivy edge to be easier to be gripped
Parameters: - diameter – diameter of the knob
- n – (Default value = 20) number of waves
- angle – (Default value = 45) maximum angle of the wave
- hole – (Default value = 0)
- callback – (Default value = None) called in the center
- move – (Defaultvalue = None)
-
Parts.
concaveKnob
(diameter, n=3, rounded=0.2, angle=70, hole=0, callback=None, move='')[source]¶ Knob with dents to be easier to be gripped
Parameters: - diameter – diameter of the knob
- n – (Default value = 3) number of dents
- rounded – (Default value = 0.2) proportion of circumferen remaining
- angle – (Default value = 70) angle the dents meet the circumference
- hole – (Default value = 0)
- callback – (Default value = None) called in the center
- move – (Defaultvalue = None)
Edges¶
Edges are what makes Boxes.py work. They draw a – more or less – straight border to the current piece. They are part of the turtle graphics part of Boxes.py. This means they start at the current position and current direction and move the current position to the end of the edge.
Edge instances have a Settings object associated with them that keeps the details about how the edge should look like. Edges that are supposed to work together share the same Settings object to ensure they fit together - assuming they have the same length. Most edges are symetrical to unsure they fit together even when drawn from different directions. Although there are a few exception - mainly edges that provide special features like hinges.
As edges started out as methods of the main Boxes class they still are
callables. It turned out that the edges need to provide a bit more
information to allow the surrounding code to handle them
properly. When drawing an Edge there is a virtual straight line that
is the border the shape of the part (e.g. an rectangle). But the
actual Edge has often to be drawn elsewhere. Best example if probably
the F
Edge that matches the normal finger joints. It has to start
one material thickness outside of the virual border of the part so the
cutouts for the opposing fingers just touch the border. The Edge
classes have a number of methods to deal with these kind of offsets.
A set of instances are kept the .edges
attribute of the
Boxes
class. It is a dict with strings of length one as keys:
- d : DoveTailJoint
- D : DoveTailJointCounterPart
- e : Edge
- E : OutSetEdge
- f : FingerJointEdge
- F : FingerJointEdgeCounterPart
- g : GrippingEdge
- h : FingerHoleEdge
- ijk : Hinge (start, end, both sides)
- IJK : HingePin (start, end, both sides)
- s : StackableEdge
- S : StackableEdgeTop
Edge base class¶
Settings Class¶
-
class
boxes.edges.
Settings
(thickness, relative=True, **kw)[source]¶ Generic Settings class
Used by different other classes to store messurements and details. Supports absolute values and settings that grow with the thickness of the material used.
Overload the absolute_params and relative_params class attributes with the suported keys and default values. The values are available via attribute access.
Straight Edges¶
Grip¶
Stackable Edges¶
-
class
boxes.edges.
StackableEdge
(boxes, settings, fingerjointsettings)[source]¶ Edge for having stackable Boxes. The Edge creates feet on the bottom and has matching recesses on the top corners.
Stackable Edge Settings¶
-
class
boxes.edges.
StackableSettings
(thickness, relative=True, **kw)[source]¶ Settings for Stackable Edges
Values:
- absolute_params
- angle : 60 : inside angle of the feet
- relative (in multiples of thickness)
- height : 2.0 : height of the feet
- width : 4.0 : width of the feet
- holedistance : 1.0 : distance from finger holes to bottom edge
- absolute_params
Finger joints¶
Finger joints are a simple way of joining two sheets (e.g. of plywood). They work best at an 90° angle. There are two different sides matching each other. As a third alternative there are holes that the fingers of one sheet can plug into. This allows stable T connections especially useful for inner walls.
-
class
boxes.edges.
FingerJointEdgeCounterPart
(boxes, settings)[source]¶ Finger joint edge - other side
-
class
boxes.edges.
FingerHoleEdge
(boxes, fingerHoles=None, **kw)[source]¶ Edge with holes for a parallel finger joint
-
class
boxes.edges.
CrossingFingerHoleEdge
(boxes, height, fingerHoles=None, **kw)[source]¶ Edge with holes for finger joints 90° above
In addition there is
which is no Edge but fits FingerJointEdge
.
An instance of is accessible as Boxes.fingerHolesAt.
Finger Joint Settings¶
-
class
boxes.edges.
FingerJointSettings
(thickness, relative=True, **kw)[source]¶ Settings for Finger Joints
Values:
- absolute * style : “rectangular” : style of the fingers * surroundingspaces : 2 : maximum space at the start and end in multiple of normal spaces * angle: 90 : Angle of the walls meeting
- relative (in multiples of thickness)
- space : 2.0 : space between fingers
- finger : 2.0 : width of the fingers
- width : 1.0 : width of finger holes
- edge_width : 1.0 : space below holes of FingerHoleEdge
- play : 0.0 : extra space to allow finger move in and out
Dove Tail Joints¶
Dovetails joints can only be used to join two pieces flatly. This limits their use to closing some round form created with flex areas or for joining several parts to a bigger one. For this use case they are much stronger than simple finger joints and can also bare pulling forces.
-
class
boxes.edges.
DoveTailJointCounterPart
(boxes, settings)[source]¶ Edge for other side of dove joints
Dove Tail Settings¶
-
class
boxes.edges.
DoveTailSettings
(thickness, relative=True, **kw)[source]¶ Settings for Dove Tail Joints
Values:
- absolute
- angle : 50 : how much should fingers widen (-80 to 80)
- relative (in multiples of thickness)
- size : 3 : from one middle of a dove tail to another
- depth : 1.5 : how far the dove tails stick out of/into the edge
- radius : 0.2 : radius used on all four corners
- absolute
Flex¶
-
class
boxes.edges.
FlexEdge
(boxes, settings)[source]¶ Edge with flex cuts - use straight edge for the opposing side
Flex Settings¶
-
class
boxes.edges.
FlexSettings
(thickness, relative=True, **kw)[source]¶ Settings for Flex
Values:
- absolute
- stretch : 1.05 : Hint of how much the flex part should be shortend
- relative (in multiples of thickness)
- distance : 0.5 : width of the pattern perpendicular to the cuts
- connection : 1.0 : width of the gaps in the cuts
- width” : 5.0 : width of the pattern in direction of the cuts
Slots¶
CompoundEdge¶
Hinges¶
Hinge Settings¶
-
class
boxes.edges.
HingeSettings
(thickness, relative=True, **kw)[source]¶ Settings for Hinges and HingePins Values:
- absolute_params
- style : “outset” : “outset” or “flush”
- outset : False : have lid overlap at the sides (similar to OutSetEdge)
- pinwidth : 1.0 : set to lower value to get disks surrounding the pins
- grip_percentage” : 0 : percentage of the lid that should get grips
- relative (in multiples of thickness)
- hingestrength : 1 : thickness of the arc holding the pin in place
- axle : 2 : diameter of the pin hole
- grip_length : 0 : fixed length of the grips on he lids
Drawing commands¶
Turtle Graphics commands¶
These commands all move the coordinate system with them.
-
Boxes.
corner
(degrees, radius=0, tabs=0)[source]¶ Draw a corner
This is what does the burn corrections
Parameters: - degrees – angle
- radius – (Default value = 0)
-
Boxes.
curveTo
(x1, y1, x2, y2, x3, y3)[source]¶ control point 1, control point 2, end point
Parameters: - x1 –
- y1 –
- x2 –
- y2 –
- x3 –
- y3 –
-
Boxes.
polyline
(*args)[source]¶ Draw multiple connected lines
Parameters: *args – Alternating length in mm and angle in degrees. lengths may be a tuple (length, #tabs) angles may be tuple (angle, radius)
Special Functions¶
Latch and Grip¶
These should probably be Edge classes. But right now they are still functions.
-
Boxes.
grip
(length, depth)[source]¶ Corrugated edge useful as an gipping area
Parameters: - length – length
- depth – depth of the grooves
Tab support¶
Tabs are small interuptions in the border of a part to keep it in
place. They are enabled with the tabs parameter. All
Edges automatically create about two tabs. So parts like
boxes.Boxes.rectangularWall()
will have 8 tabs holding them
in place. Because of this developers often don’t need to be concerned
about tabs. But some part may be completely drawn by low level Turtle
Graphics commands. For those both boxes.Boxes.edge()
and
boxes.Boxes.corner()
do support a tabs parameter. In
addition the length of the line segments in boxes.Boxes.polyline()
can
be given as a tuple (length, tabs).
Draw Commands¶
These commands do not change the coordinate system but get the coordinates passed as parameters. All of them are either som sort of hole or text. These artifacts are placed somewhere independently of some continuous outline of the part their on.
-
Boxes.
hole
(x, y, r=0.0, d=0.0, tabs=0)[source]¶ Draw a round hole
Parameters: - x – position
- y – postion
- r – radius
-
Boxes.
rectangularHole
(x, y, dx, dy, r=0)[source]¶ Draw an rectangulat hole
Parameters: - x – position
- y – position
- dx – width
- dy – height
- r – (Default value = 0) radius of the corners
-
Boxes.
text
(text, x=0, y=0, angle=0, align='', fontsize=10, color=[0.0, 0.0, 0.0])[source]¶ Draw text
Parameters: - text – text to render
- x – (Default value = 0)
- y – (Default value = 0)
- angle – (Default value = 0)
- align – (Default value = “”) string with combinations of (top|middle|bottom) and (left|center|right) separated by a space
-
Boxes.
NEMA
(size, x=0, y=0, angle=0, screwholes=None)[source]¶ Draw holes for mounting a NEMA stepper motor
Parameters: - size – Nominal size in tenths of inches
- x – (Default value = 0)
- y – (Default value = 0)
- angle – (Default value = 0)
-
Boxes.
TX
(size, x=0, y=0, angle=0)[source]¶ Draw a star pattern
Parameters: - size – 1 to 100
- x – (Default value = 0)
- y – (Default value = 0)
- angle – (Default value = 0)
-
Boxes.
flex2D
(x, y, width=1)[source]¶ Fill a rectangle with a pattern allowing bending in both axis
Parameters: - x – width
- y – height
- width – width between the lines of the pattern in multiples of thickness
-
class
NutHole
¶
An instance is available as boxes.Boxes.nutHole()
An instance of
-
class
boxes.edges.
FingerHoles
(boxes, settings)[source] Hole matching a finger joint edge
is accessible as Boxes.fingerHolesAt.
Hexagonal Hole patterns¶
Hexagonal hole patterns are one way to have some ventilation for
housings made with Boxes.py. Right now both .rectangularWall()
and .roundedPlate()
do supports this pattern directly by passing
the parameters to the calls. For other use cases these more low level
methods can be used.
For now this is the only supported pattern for ventilation slots. More may be added in the future.
There is a global Boxes.hexHolesSettings object that is used if no settings are passed. It currently is just a tuple of (r, dist, style) defaulting to (5, 3, ‘circle’) but might be replace by a Settings instance in the future.
-
Boxes.
hexHolesRectangle
(x, y, settings=None, skip=None)[source]¶ Fills a rectangle with holes in a hex pattern.
Settings have: r : radius of holes b : space between holes style : what types of holes (not yet implemented)
Parameters: - x – width
- y – height
- settings – (Default value = None)
- skip – (Default value = None) function to check if hole should be present gets x, y, r, b, posx, posy
-
Boxes.
hexHolesCircle
(d, settings=None)[source]¶ Fill circle with holes in a hex pattern
Parameters: - d – diameter of the circle
- settings – (Default value = None)
Burn correction¶
The burn correction – aka kerf – is integrated into the low level commands of Boxes.py. So for the most part developer do not need to care about it. Nevertheless they need to understand how it works to catch the places the do need to care.
Burn correction is done by increasing the radius of all outer corners. This moves all the straight lines outward by the same amount. This has the added benefit of not needing to change the length of the straight lines – making them independent of the adjacent angles. An issue arises when it comes to inner corners. If they do have a radius reducing it by the burn value does the right thing. But for small radii and sharp corners (radius zero) this results in a negative values. It turns out flipping over the arc for negative radii allows keeping the lengths of the straight lines unchanged. So this is what Boxes.py does:
This results in the straight lines touching the piece. This leads to overcuts. They are not as nice as proper dog bones as might be used by a dedicated CAM software. But as Boxes.py is meant to be used for laser cutting this is deemed acceptable:
Programmer’s perspective¶
For this to work it is important that outside is drawn in a counter clock wise direction while holes are drawn in a clock wise direction.
boxes.Boxes.corner()
adjusts the radius automatically
according to .burn. This propagates to higher level
functions. Parts shipped with Boxes.py do take the
burn out-set into account and execute callbacks at the correct position.
In case developers move to a feature inside of a part or executing
callbacks while implementing a part they need to be aware of the burn
correction. boxes.Boxes.cc()
does correct for the out-set if
called without an y parameter. But if a value is given one has to
add self.burn to compensate. Note that the x value typically
does not have to be corrected as the callbacks are executed from right
underneath the part.
A similar approach is necessary when moving to a feature drawn inside the part without the use of callbacks. Here you typically have to correct for the out-set at the outside of the part and again for in-set of the hole one is about to cut. This can be done in x or y direction depending on whether the cut ist started vertical or horizontally.
Examples¶
Decide whether you want to start from scratch or want to rework an existing generator.
You should go over the arguments first. Get at least the most basic arguments done. For things you are still unsure you can just use a attribute set in the .__init__() method and turn it into a proper argument later on.
Depending on what you want to do you can work on the different levels of the API. You can either use what is there and combine it into something new or you can implements new things in the appropriate level.
Here are some examples:
Housing for some electronics¶
You can use the ElectronicsBox or the ClosedBox as a basis. Write some callbacks to place holes in the walls to allow accessing the ports of the electronics boards. Place some holes to screw spacers into the bottom to mount the PBC on.
NemaMount¶
This is a good non box example to look at.
Note that although it produces a cube like object it uses separate
variables (x
, y
, h
) for the different axis. Probably
because it started as a copy of another generator like ClosedBox
.
DisplayShelf¶
The DisplayShelf is completely made out of rectangularWalls(). It uses
a callback to place all the fingerHolesAt() right places on the sides.
While the use of the Boxes.py API is pretty straight forward the
calculations needed are a bit more tricky. You can use the debug
default param to check if you got things right when attempting
something like this yourself.
Note that the front walls and the shelfs form a 90° angle so they work with the default FingerJoints.
BinTray¶
-
class
boxes.generators.bintray.
BinTray
[source]¶ A Type tray variant to be used up right with sloped walls in front
The BinTray is based on the TypeTray generator:
TypeTray is an already pretty complicated generator.
BinTray replaces the now vertical front (former top) edges with a special purpose one that does add the triangles:
The hi
(height of inner walls) argument was removed although the
variable is still used internally - out of laziness.
To complete the bin the front walls are added. Follow up patches then switched the slots between the vertical and horizontal walls to have better support for the now bottoms of the bins. Another patch adds angled finger joints for connecting the front walls with the bottoms of the bins.
The TrafficLight generator uses a similar technique implementing its own Edge class. But it uses its own code to generate all the wall needed.
Stachel¶
Stachel allows mounting a monopod to a bass recorder. It is basically
just one part repeated with different parameters. It can’t really make
use of much of the Boxes.py library. It implements this one part
including the move
parameter and draws everything using the
.polyline()
method. This is pretty painful as lots of angles and
distances need to be calculated by hand.
For symmetric sections it passes the parameters to .polyline
twice
– first in normal order and then reversed to get the mirrored section.
This generator is beyond what Boxes.py is designed for. If you need something similar you may want to use another tool like OpenScad or a traditional CAD program.
All Box Generators¶
Generators are organized in several Groups