In just a few lines of code, generate printable enclosures for your projects that can resist >100 kg/220 lbs (*see Strength test).
The part shown in the Features and Strength test sections below weights 68g and would cost about 1.36€ in plastic (assuming a price of 20€/kg), but could be halved for a weaker prototyping version.
While the builder currently meets my needs, the project isn't battle-tested yet, as I've only used it for one ongoing project. If you encounter any problems or have features you'd like to see, please feel free to contribute or create an issue. Thanks!
- Parts library: includes jacks, barrel plugs, USBs, and more (22 parts across 16 categories so far—see List of built-in parts).
- Easy to extend with your own parts (e.g. the specific model of SPST you're using).
- Create custom parts from existing STEP files (converted from STL if necessary).
- Screwable lid: supports heat set inserts and regular printed threads.
- Layout builder: offers parametric lines and grids of parts.
- STL export: generates ready-to-print files.
Below, you can find the code used to generate an enclosure, the 3D preview, and the printed result (the seamlines [Z-seam] and generate print quality can be improved by tweaking your printer and slicer settings):
A more complex enclosure using the Layout builder to align parts: (WIP, part of my soon-to-be released modular multi-effect guitar pedal)
- Install
- Examples (with screenshots)
- 01 – Panel with buttons
- 02 – Set default part types and parameters
- 03 – Panel's optional parameters
- 04 – Enclosure with buttons
- 05 – Enclosure: export STLs
- 06 – Enclosure: optional parameters
- 06.5 – Enclosure: custom screws placement
- 07 – Layout builder: simple line
- 08 – Layout builder: fixed-width line
- 09 – Layout builder: fixed-width line with median part centred at 0,0
- 10 – Layout builder: grid of parts
- 11 – Layout builder: combining groups
- 12 – Text (cut and extruded)
- 13 – Support for parts
- 14 – Holders for Raspberry Pi and protoboard
- 15 – Add a new Part
- 16 – Add a new Part from a STL/STEP file
- 17 – All parts
- Available parts
- Strength test
- API Reference
- Enclosure
- PartsFactory
- LayoutElement
- LayoutGroup (layout builder)
- Panel
- Part
$ python setup.py install
Note: if you already have the required dependencies (see Manual install), this is not required to run the examples.
This will install cq_enclosure_builder
on your system, as well as the required libraries (CadQuery and cq_warehouse.).
You'll also need to copy a file to your cq_warehouse install folder, as this project uses a custom type of screw. See flat_head_parameters.csv
in the section below. (In future versions, the default type of screw used will be change, to reduce complexity; see issue #5.)
First, you'll need to install CadQuery.
Then, install the library used to generate screws: cq_warehouse.
Finally, copy the custom screw used by the project in cq_warehouse's install folder:'
# Or wherever your cq_warehouse install folder is
$ cp src/cq_enclosure_builder/screws/flat_head_parameters.csv ~/miniconda3/lib/python3.10/site-packages/cq_warehouse/flat_head_parameters.csv
There are a few available UIs you can choose from, including:
- jupyter-cadquery: more features and flexibility, but requires a working Jupyter Notebook/Lab (highly recommended if you decide to design your own parts).
- cq-editor: the default UI.
To run the examples, just open them with the UI you've picked, and they should work right away. If I missed a required dependency, please let me know!
You can explore the provided examples. If you're using jupyter-cadquery, check out 00_all_examples.ipynb which groups all examples into a single Notebook.
For other UIs, navigate to the examples directory and open the individual .py
files with your chosen UI.
A single Panel (side of an enclosure) with two buttons.
Almost the same as above, but shows how PartsFactory's set_default_types
and set_default_parameters
can help you make your code more readable, by removing the need to repeat the part_type
(e.g. with specific model of SPST you're using for your project), and removing the need to repeat common parameters such as enclosure_wall_thickness
.
02_default_part_type_and_parameters.py
Shows optional parameters for the Panel class (see API Reference). Here, we've updated the panel colour, the part colour, and the panel's transparency.
A simple Enclosure with an 'exploded' view.
Enclosure's export_printables
will generate ready-to-print STLs files for each distinct part of your project. Most enclosures will typically require only to parts to print: the main section (box) and a lid, while others will require additional prints (such as brackets to hold a specific type of screen in this example).
Shows optional parameters for the Enclosure class. Here, we've updated a few parameters, but the most visibles in the screenshot below is that we've removed the fillet (rounded corners) on the top and bottom of the enclosure.
We've also changed the project's name, which generated printables STLs with a customisd name.
06_enclosure_optional_params.py
Certainly! Here's a revised version of your description:
By default, the Enclosure includes four screws at its corners. However, you can customize this setup. In the following example, we added two M2 screws with printed threads, complementing the default four M3 heat set inserts.
06_5_enclosure_custom_screws.py
Using LayoutGroup to build a simple line of three components. See optional parameters to center the line on (0,0), add margin between each element, etc.
07_layout_builder_simple_line.py
Line of N components taking all the available space. Here, we're aligning the parts by their 'external footprint': the outward-facing section of the components, such as the hole of a USB or the knobs of potentiometers.
In the screenshot below, the beige rectangle represents the PCB onto which the USB A connector is mounted, which is larger than the USB port itself. Conversely, for the button and encoder, the cap and knob are more prominent than their respective base components.
08_layout_builder_fixed_width_line.py
In this fixed-width line layout, we aim to position the median element (in this instance, the second out of three) directly at the coordinate (0,0)
. By default, this positioning might not be achieved if the footprint of the element(s) to its left differs from that of the element(s) to its right. Though will make the spacing slightly uneven, it's useful when we want to force an element at (0,0).
09_layout_buider_fixed_width_line_centre_at_0_0.py
Create a grid of any component (6.35mm jacks in this case). You can define the number of rows and columns, as well as the spacing between them.
10_layout_builder_grid_of_parts.py
You can combine multiple LayoutGroup (and LayoutElement instances. In this example, we've evenly spaced two grids of jacks (6.35mm and 3.5mm) along with a single USB A on the panel.
11_layout_builder_combining_groups.py
You can add extruded and cut text (e.g. for your project's name and version).
For parts under heavy stress, such as the switch on a guitar pedal, you can add a support pillar underneath if there's available space.
Screws to hold your PCBs in place.
For the protobard, the screws can be positioned arbitrarily (grid coordinates).
14_holders_for_pi_and_protoboard.py
See Adding a new Part for more details.
In this example, I've made a model using Tinkercad, which I exported as a STL. Fusion 360 then allowed me to convert it to STEP. (You can also use online converters, though I've had some format issues with these.)
After creating a new Part and filling a few fields (size, etc.), you'll be able to add it to your enclosure.
Show all available parts.
For a detailed list, scroll down to List of built-in parts.
Note: the default caps/knobs are shown, but there's a dozen available, and more can be added by taking four simple measurements.
For many parts, I've provided purchase links within the docstrings of the Python code, so you can obtain the exact item I used for modeling them.
- jack: 6.35mm PJ-612A, 3.5mm PJ-392
- button: SPST PBS-24B-4, DPDT PBS-24-212SP, PBS 11-A, PBS-110
- encoder: EC11
- potentiometer: WH148
- usb_a: 3.0 cltgxdd
- usb_c: ChengHaoRan E
- screen: HDMI 5 inch JRP5015, DSI 5 inch CFsunbird
- air_vent: basic rectangular
- banana: 4mm
- barrel_plug: DC-022B
- rca: N1030
- support: pyramid, skirt
- toggle: MTS-103
- midi: SD-50SN
- holder: RPi 4B, Protoboard
- text: default
See example 15 above.
Each part belongs to a category, such as button
, encoder
, screen
, and so forth. (New categories can be added on the fly.)
- Copy the file __template.py to a location in your project.
- For contributors: place the file into the appropriate sub-package of cq_enclosure_builder/parts.
- (Optional) Use the
@register_part("<category>", "<part_type>")
decorator.<category>
should match the sub-folder if contributing. Otherwise, it can be any valid Python identifier.<part_type>
represents the reference for your component (e.g.PBS 11-A
).
- If your part's category doesn't exist, no problem! A new build method—
build_<your new category>
—will be automatically added to the PartsFactory.- For contributors: simply create a new sub-package in the previously mentioned folder.
- You'll now be able to use your part as follows:
from cq_enclosure_builder import PartsFactory as pf
my_part = pf.build_<category>(part_type="<part_type>")
Note 1: set your part as default for this category with PartsFactory's set_default_types
.
Note 2: the @register_part
decorator is optional but allows for integration with PartsFactory and its built-in caching. Without it, instantiate your object as usual, like MyPart()
.
CadQuery only supports importing STEP files. If you're working with an STL, you can easily convert them to STEP using Fusion 360, or online converters (though I've had some format issues with these).
Once you have a STEP file, follow the steps (ah!) shown in example 16.
The enclosures should be strong enough for most common needs, including guitar pedals (which experience regular stomping!)
I conducted two tests using this Silk PLA. Standard PLA should be somewhat stronger, while PLA+ will significantly deform before it snaps. For thorough research on what makes a print strong, see CNC Kitchen on YouTube.
- in the first test, the enclosure was constructed without any support beneath the SPST;
- for the second test, a small 'pillar' was added underneath the SPST (see example 13 above), making it very solid (if you don't plan to stand on top of it).
The code used to generate the STLs for these tests can be found here.
Note: smart use of supports can make the print extremely strong in regular use [stomping on the button], and while it should resist some drops and other types of hits, at the end of the day, (including some drops), it's still just plastic!
With support, can resist >100 kg ('unlimited', as there's no floating part) | Without support, resists 20-35 kg before the first crack... | ...but likely won't break right away |
---|---|---|
class: Enclosure
Method or Value Name | Parameters | Description |
---|---|---|
__init__ |
|
|
add_part_to_face -> None |
|
|
assemble -> None |
|
Needs to be called before calling export_printables or using the assembly . |
export_printables -> None |
none | Export one STL per printable. By default, one for the lid and for the box . Some parts can require additional prints; any element added to Part's additional_printables will also be exported. |
add_screw -> None |
|
Used to add more screws than the four corner scresw that can be added automatically using __init__ 's add_corner_lid_screws . See example 6.5. |
(value) assembly : cq.Assembly |
N/A | Contains a displayable assembly with the panels (incl. parts), frame, lid screws, and lid support. |
(value) debug : cq.Assembly |
N/A | Debug elements: footprints, holes, panels masks, printables, and other debug elements added by the parts (Part's debug_objects ). |
(value) assembly_with_debug : cq.Assembly |
N/A | Assembly containing the two previous assemblies. |
(value) all_printables_assembly : cq.Assembly |
N/A | Contains all the printables models, which can be exported with export_printables . |
class: PartsFactory
Method Name | Parameters | Description |
---|---|---|
build -> Part |
|
|
build_<category_name> -> Part |
same as above (without category_name ) |
Dynamically generated for each new category registered with @register_part . |
list_categories -> List[str] |
N/A | List all the categories registered in the factory, e.g. ["encoder", "midi", ...] . |
list_types_for_category -> List[str] |
|
List all the types available for a given category (for instance, various types of USB C connectors). |
list_types_of_<category_name> -> List[str] |
N/A | Same as above, without needing to provide category_name as parameter. Dynamically generated. |
set_default_types -> None |
|
Set the default part for any category, so you don't have to repeat part_type="<type>" each time you're building a part. |
set_default_parameters -> None |
|
For instance, if setting a default value for enclosure_wall_thickness , it won't have to be repeated explicitly each time you're building a part. Can be overridden. |
set_defaults -> None |
|
Sets both set_default_types and set_default_parameters at once. |
class: LayoutElement
Method Name | Parameters | Description |
---|---|---|
__init__ |
|
|
move_to -> Self |
|
|
translate -> Self |
|
|
get_pos -> Tuple[float, float] |
none | Get the relative pos (centre is 0,0). |
get_abs_pos -> Tuple[float, float] |
|
Get the absolute based on the size of the panel. |
set_inside_footprint_x -> None |
|
Override the real footprint of the element. See example 9 for use case. |
set_outside_footprint_x -> None |
|
see above |
set_footprints_x -> None |
|
see above Set both inside and outside at once. |
set_inside_footprint_y -> None |
|
Override the real footprint of the element. See example 9 for use case. |
set_outside_footprint_y -> None |
|
see above |
set_footprints_y -> None |
|
see above Set both inside and outside at once. |
class: LayoutGroup (layout builder), inherits LayoutElement
Method Name | Parameters | Description |
---|---|---|
__init__ |
not needed | |
move_to -> Self |
|
Same as LayoutElement. Set the pos of the entire group. |
translate -> Self |
|
Same as LayoutElement. Translate all the elements of the group. |
get_pos -> Tuple[float, float] |
none | Get the relative pos (centre is 0,0). |
get_elements -> List[ LayoutElement] |
none | Returns all the elements of the group. If there's nested groups, it flatten them by recurisvely calling get_elements . |
(static) line_of_parts -> LayoutGroup |
margin: float (default: 5 ): the spacing between each element. |
Return a simple group where each part is next to each other in a line. |
(static) line_of_elements -> LayoutGroup |
line_of_parts . |
The element variant is useful to combine groups. |
(static) fixed_width_line_of_parts -> LayoutGroup |
|
Return a line of element spaced equally, taking a set amount of space. |
(static) fixed_width_line_of_elements -> LayoutGroup |
fixed_width_line_of_parts (same order—elements is the second parameter). |
The element variant is useful to combine groups. |
(static) line_of_parts -> LayoutGroup |
margin: float (default: 5 ): the spacing between each element. |
Return a simple group where each part is next to each other in a line. |
(static) grid_of_part -> LayoutGroup |
|
Return a grid of rows x cols made up the same Part. |
class: Panel
Method Name | Parameters | Description |
---|---|---|
__init__ |
|
|
add -> None |
|
|
assemble -> None |
none | Should be called before using the values below, otherwise you won't like it. |
(value) panel : cq.Workplane |
N/A | The panel 'wall' and all its parts. |
(value) mask : cq.Workplane |
N/A | A solid box of the size (size.width, size.length, size.wall_thickness) |
(value) debug_assemblies : Dict[str, Union[Dict, cq.Workplane]] |
N/A | Assemblies: footprint_in , footprint_out , hole , other (from the debug_objects field of the panel's part), combined . |
class: Part
Method Name | Parameters | Description |
---|---|---|
__init__ |
none | No-arg constructors; sets the variables below to their default values. |
(value) part : cq.Workplane |
N/A | The part that will be added to the panel. |
(value) assembly_parts : List[ AssemblyPart] (default: None ) |
AssemblyPart consists of three fields: workplane: cq.Workplane , name: str , and color: cq.Color . It's utilized to visually distinguish sub-parts, like coloring screws differently. If AssemblyPart is present, it will be used for display; otherwise, the part field will be used. Even if they are equivalent, the part should still be set. Refer to the comments in Part for more details. |
|
(value) mask : cq.Workplane |
N/A | Will be cut from the panel, should likely be the same width/length as the part . |
(value) size : PartSize |
N/A | PartSize consists of three fields: width: float , length: float , and thickness: float . |
(value) additional_printables: Dict[str, Tuple[float, float], cq.Workplane] |
N/A | Workplanes in this map will be export when calling Enclosure's export_printables'. Dict of (, , )`. |
(value) inside_footprint: Tuple[float, float] |
N/A | Needed by the layout builder (LayoutGroup) to position elements. |
(value) inside_footprint_thickness: float |
N/A | Currently, only used when wanting to create a "pyramid" support to sit underneath another part, for added strength. |
(value) inside_footprint_offset: Tuple[float, float] |
N/A | The distance from the centre-point of the inside_footprint to the coordinate (0,0) . This is used by the layout builder. |
(value) outside_footprint: Tuple[float, float] |
N/A | Needed by the layout builder (LayoutGroup) to position elements. |
(value) outside_footprint_thickness: float |
N/A | Unused at the moment; required field nonetheless. |
(value) outside_footprint_offset: Tuple[float, float] (default: (0,0) ) |
N/A | The current design assumes the centre-point of the outside_footprint is at the coordinate (0,0) . If this isn't the case for your specific part (for instance, if the center of your USB connector's hole isn't exactly at (0,0) ), the layout builder may not function optimally. |
(value) debug_objects : DebugObjects |
N/A | Used to obtain visual insights about your part. For example, it can display how much an SPST is sticking out from an enclosure, including its caps. |
class: DebugObjects
Value Name | Parameters | Description |
---|---|---|
__init__ |
none | |
(value) footprint : DebugObjects.Footprint |
N/A | Sub-classes has two fields, inside: cq.Workplane and outside: cq.Workplane (default for both: None ). It is used to show the actual space taken by the part. If you're creating a new part, you likely want to set these fields if relevant. |
(value) hole : cq.Workplane (default: None ) |
N/A | Visual representation of the hole in the enclosure (e.g. for a USB port, it will simply be the size of the connector). |
(value) others : Dict[str, cq.Workplane] |
N.A | Shown in the Panel's and Enclosure's debug assemblies. |
If there's any problem, feel free to open a PR, or create an issue; thanks!