# Quick Links
- [Installation / Usage](#installation)
- [Shell Command Scripts](#shell-command-scripts)
- [gridfinitybox](#gridfinitybox)
- [gridfinitybase](#gridfinitybase)
- [ruggedbox](#ruggedbox)
- [Classes](#classes)
- [GridfinityBaseplate](#gridfinitybaseplate)
- [GridfinityBox](#gridfinitybox-1)
- [GridfinityDrawerSpacer](#gridfinitydrawerspacer)
- [GridfinityRuggedBox](#gridfinityruggedbox)
- [GridfinityObject](#gridfinityobject)
- [References](#references)
## Installation
**cq-gridfinity** has the following installation dependencies:
- [CadQuery](https://github.com/CadQuery/cadquery)
- [cq-kit](https://github.com/michaelgale/cq-kit)
Assuming these dependencie are installed, you can install **cq-gridfinity** using a [PyPI package](https://pypi.org/project/cqgridfinity/) as follows:
```bash
$ pip install cqgridfinity
```
Alternatively, the **cq-gridfinity** package can be installed directly from the source code:
```bash
$ git clone https://github.com/michaelgale/cq-gridfinity.git
$ cd cq-gridfinity
$ pip install .
```
## Development with VS Code Dev Container
This project includes a development container configuration that provides a consistent development environment with all required dependencies pre-installed.
### Prerequisites
1. Install [Docker Desktop](https://www.docker.com/products/docker-desktop/)
2. Install [Visual Studio Code](https://code.visualstudio.com/)
3. Install the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) in VS Code
### Getting Started
1. Clone this repository:
```bash
git clone https://github.com/michaelgale/cq-gridfinity.git
cd cq-gridfinity
```
2. Open the project in VS Code:
```bash
code .
```
3. When VS Code detects the dev container configuration, it will prompt you to "Reopen in Container". Click this prompt, or:
- Press `F1` or `Ctrl+Shift+P` (Cmd+Shift+P on Mac)
- Type "Dev Containers: Reopen in Container" and select it
4. Wait for the container to build (this may take a few minutes the first time)
### What's Included
The development container comes with:
- Python 3.12
- CadQuery 2.4
- All required dependencies (pytest, black, flake8, etc.)
- A pre-configured environment for CAD development
### Troubleshooting
If you encounter issues:
1. Ensure Docker is running
2. Try rebuilding the container:
- Press `F1` or `Ctrl+Shift+P`
- Select "Dev Containers: Rebuild Container"
## Basic Usage
After installation, the package can imported:
```shell
$ python
>>> import cqgridfinity
>>> cqgridfinity.__version__
```
An example of the package can be seen below:
```python
from cqgridfinity import *
# make a simple box
box = GridfinityBox(3, 2, 5, holes=True, no_lip=False, scoops=True, labels=True)
box.save_stl_file()
# Output a STL file of box:
# gf_box_3x2x5_holes_scoops_labels.stl
```
# Shell Command Scripts
- [gridfinitybox](#gridfinitybox)
- [gridfinitybase](#gridfinitybase)
- [ruggedbox](#ruggedbox)
This package can be used to make your own python scripts to generate Gridfinity objects. This gives the flexibility to customize the object and combine with other code to add custom cutouts, add text labels, etc.
However, for simple generation of standard objects such as baseplates and boxes, console scripts can be used for quick command line usage. These console scripts are installed automatically into the path of your python environment and should be accessible from your terminal shell.
## `gridfinitybox`
Make a customized/parameterized Gridfinity compatible box with many optional features.
```
usage: gridfinitybox [-h] [-m] [-u] [-n] [-s] [-l] [-e] [-d] [-r RATIO] [-ld LENGTHDIV] [-wd WIDTHDIV] [-wt WALL]
[-f FORMAT] [-o OUTPUT]
length width height
Make a customized/parameterized Gridfinity compatible box with many optional features.
positional arguments:
length Box length in U (1U = 42 mm)
width Box width in U (1U = 42 mm)
height Box height in U (1U = 7 mm)
options:
-h, --help show this help message and exit
-m, --magnetholes Add bottom magnet/mounting holes
-u, --unsupported Add bottom magnet holes with 3D printer friendly strips without support
-n, --nolip Do not add mating lip to the top perimeter
-s, --scoops Add finger scoops against each length-wise back wall
-l, --labels Add label strips against each length-wise front wall
-e, --ecolite Make economy / lite style box with no elevated floor
-d, --solid Make solid (filled) box for customized storage
-r RATIO, --ratio RATIO
Solid box fill ratio 0.0 = minimum, 1.0 = full height
-ld LENGTHDIV, --lengthdiv LENGTHDIV
Split box length-wise with specified number of divider walls
-wd WIDTHDIV, --widthdiv WIDTHDIV
Split box width-wise with specified number of divider walls
-wt WALL, --wall WALL
Wall thickness (default=1 mm)
-f FORMAT, --format FORMAT
Output file format (STEP, STL, SVG) default=STEP
-o OUTPUT, --output OUTPUT
Output filename (inferred output file format with extension)
```
Examples:
```shell
# 2x3x5 box with magnet holes saved to STL file with default filename:
$ gridfinitybox 2 3 5 -m -f stl
# gf_box_2x3x5_holes.stl
# 1x3x4 box with scoops, label strip, 3 internal partitions and specified name:
$ gridfinitybox 1 3 4 -s -l -ld 3 -o MyBox.step
# MyBox.step
# Solid 3x3x3 box with 50% fill, unsupported magnet holes and no top lip:
$ gridfinitybox 3 3 3 -d -r 0.5 -u -n
# gf_box_3x3x3_basic_holes_solid.step
# Lite style box 3x2x3 with label strip, partitions, output to default SVG file:
$ gridfinitybox 3 2 3 -e -l -ld 2 -f svg
# gf_box_lite_3x2x3_div2_labels.svg
```
## `gridfinitybase`
Make a customized/parameterized Gridfinity compatible simple baseplate.
```
usage: gridfinitybase [-h] [-f FORMAT] [-s] [-d DEPTH] [-hd HOLEDIAM] [-hc CSKDIAM] [-ca CSKANGLE] [-o OUTPUT]
length width
Make a customized/parameterized Gridfinity compatible simple baseplate.
positional arguments:
length Box length in U (1U = 42 mm)
width Box width in U (1U = 42 mm)
options:
-h, --help show this help message and exit
-f FORMAT, --format FORMAT
Output file format (STEP, STL, SVG) default=STEP
-s, --screws Add screw mounting tabs to the corners (adds +5 mm to depth)
-d DEPTH, --depth DEPTH
Extrude extended depth under baseplate by this amount
-hd HOLEDIAM, --holediam HOLEDIAM
Corner mounting screw hole diameter (default=5)
-hc CSKDIAM, --cskdiam CSKDIAM
Corner mounting screw countersink diameter (default=10)
-ca CSKANGLE, --cskangle CSKANGLE
Corner mounting screw countersink angle (deg) (default=82)
-o OUTPUT, --output OUTPUT
Output filename (inferred output file format with extension)
```
Examples:
```shell
# 7 x 4 baseplate with screw corners to default STL file:
$ gridfinitybase 7 4 -s -f stl
# gf_baseplate_7x4x5.0_screwtabs.stl
```
## `ruggedbox`
Make a parameterized rugged storage box compatible with gridfinity. This box is based on the [superb design by Pred on Printables](https://www.printables.com/model/543553-gridfinity-storage-box-by-pred-now-parametric). This implementation makes a few improvements and additions to Pred's design in addition to making almost all of the box's features optional and tunable. Using either the `ruggedbox` console script or the `GridfinityRuggedBox` class, you can make vast variety of different boxes of various sizes and features. By default, almost all of the boxes features are enabled, but by using the desired command line options you can customize your desired feature set.
```
usage: ruggedbox [-h] [+l] [-l] [+p] [-p] [+w] [-w] [-wt WINDOWTHICKNESS] [+a] [-a] [+c] [-c] [+s] [-s] [+v] [-v]
[+e] [-e] [+b] [-b] [-r] [+r] [-f FORMAT] [-o OUTPUT] [-gb] [-gl] [-ga] [-gh] [-ge] [-gn] [-gt]
[-gw]
length width height
Make a customized/parameterized Gridfinity compatible rugged box enclosure.
The minimum box size is 3U x 3U x 4U.
positional arguments:
length Box length in U (1U = 42 mm)
width Box width in U (1U = 42 mm)
height Box height in U (1U = 7 mm)
options:
-h, --help show this help message and exit
+l, --label Add label window across the front wall
-l, --nolabel Remove label window across the front wall
+p, --lidbaseplate Add baseplate to top of the lid
-p, --nolidbaseplate Smooth/plain lid
+w, --lidwindow Add window slot to the lid
-w, --nolidwindow Do not add window slot to the lid
-wt WINDOWTHICKNESS, --windowthickness WINDOWTHICKNESS
Thickness of lid windows (mm)
+a, --handle Add front handle
-a, --nohandle No front handle
+c, --clasps Add clasps to the left and right side walls
-c, --noclasps No clasps on the left and right side walls
+s, --stackable Add stackable mating features to top and bottom
-s, --notstackable Non-stackable box
+v, --veegroove Add v-cut grooves to side walls
-v, --noveegroove No v-cut grooves (plain) side walls
+e, --sidehandle Add handles to side walls
-e, --nosidehandle No handles on side walls
+b, --backfeet Add standing feet to back wall
-b, --nobackfeet No standing feet added to back wall
-r, --normalstyle Make normal style box
+r, --ribstyle Make rib style box with exposed vertical ribs
-f FORMAT, --format FORMAT
Output file format (STEP, STL, SVG) default=STEP
-o OUTPUT, --output OUTPUT
Output filename (inferred output file format with extension)
-gb, --box Generate box
-gl, --lid Generate lid
-ga, --acc Generate accessory components
-gh, --hinge Generate hinge element
-ge, --genlabel Generate label panel insert
-gn, --genhandle Generate front handle
-gt, --genlatch Generate latch component
-gw, --genwindow Generate lid window component
example usage:
5 x 4 x 6 rugged box shell and lid saved to STL files:
$ ruggedbox 5 4 6 --box --lid -f stl
```
Examples:
5 x 4 x 6 rugged box component saved to STL file:
```shell
$ ruggedbox 5 4 6 -gb -f stl
____ _ ____
| _ \ _ _ __ _ __ _ ___ __| | __ ) _____ __
| |_) | | | |/ _` |/ _` |/ _ \/ _` | _ \ / _ \ \/ /
| _ <| |_| | (_| | (_| | __/ (_| | |_) | (_) > <
|_| \_\\__,_|\__, |\__, |\___|\__,_|____/ \___/_/\_\
|___/ |___/
Version: 0.5.7
Gridfinity rugged box: 5U x 4U x 6U
Exterior dim: 230.0 mm x 188.0 mm x 55.0 mm
Interior dim: 210.0 mm x 168.0 mm x 45.8 mm
Internal volume: 1.616 L
Wall Vgrooves : Y
Front Handle : Y
Stackable : Y
Side Clasps : Y
Lid Baseplate : Y
Inside Baseplate : Y
Side Handles : Y
Front Label : Y
Back Feet : Y
Rib Style : N
Lid Window : N
Rendering box...
Component generated and saved as gf_ruggedbox_5x4x6_body_fr-hl_sd-hc_stack_lidbp.stl in STL format
$
```
```shell
# same 5 x 4 x 6 rugged box with the lid saved to STL file:
$ ruggedbox 5 4 6 --lid -f stl
# gf_ruggedbox_5x4x6_lid_fr-hl_sd-hc_stack_lidbp.stl
# 5 x 5 x 9 rugged box, smooth lid, non-stackable, and no handle; full assembly saved to STEP file
$ ruggedbox 5 5 9 --nohandle --nolidbaseplate --notstackable
# gf_ruggedbox_5x5x9_fr-l_sd-hc.step
# Render the box, lid, and hinge for a 5x4x6 rugged box all at once:
$ ruggedbox 5 4 6 --box --lid --hinge
# gf_ruggedbox_5x4x6_fr-hl_sd-hc_stack_lidbp.step
# gf_ruggedbox_5x4x6_lid_fr-hl_sd-hc_stack_lidbp.step
# gf_ruggedbox_5x4x6_hinge_fr-hl_sd-hc_stack_lidbp.step
# Then render the latches and handle components for the same box:
$ ruggedbox 5 4 6 --acc
# gf_ruggedbox_5x4x6_acc_fr-hl_sd-hc_stack_lidbp.step
# Or render individual components as STL files with your preferred name:
$ ruggedbox 5 4 6 --genhandle --genlatch -o orange.stl
# orange_handle.stl
# orange_latch.stl
```
# Classes
- [GridfinityBaseplate](#gridfinitybaseplate)
- [GridfinityBox](#gridfinitybox-1)
- [GridfinityDrawerSpacer](#gridfinitydrawerspacer)
- [GridfinityRuggedBox](#gridfinityruggedbox)
- [GridfinityObject](#gridfinityobject)
## `GridfinityBaseplate`
Gridfinity baseplates can be made with the `GridfinityBaseplate` class. The baseplate style is the basic style initially proposed by Zach Freedman. Therefore, it does not have magnet or mounting holes. An example usage is as follows:
```python
# Create 4 x 3 baseplate
baseplate = GridfinityBaseplate(4, 3)
baseplate.save_step_file()
# gf_baseplate_4x3.step
```
Baseplates can be rendered with extra depth to make a taller overall baseplate using the `ext_depth` attribute. Furthermore, mounting screws corner tabs can be added to the baseplate with the `corner_screws` attribute. A baseplate with this feature is shown below.
### Optional Keyword Arguments
```python
ext_depth = 0 # extended depth to extrude below baseplate
straight_bottom = False # add/remove 0.8 mm chamfer on bottom of baseplate
corner_screws = False # add corner mounting screw tabs
corner_tab_size = 21 # size of screw mounting tab (mm)
csk_hole = 5.0 # hole diameter of countersink mounting screw (mm)
csk_diam = 10.0 # countersink diameter (mm)
csk_angle = 82 # countersink angle (deg)
```
## `GridfinityBox`
Gridfinity boxes with many optional features can be created with the `GridfinityBox` class. As a minimum, this class is initialized with basic 3D unit dimensions for length, width, and height. The length and width are multiples of 42 mm Gridfinity intervals and height represents multiples of 7 mm.
### Simple Box
```python
# Create a simple 3 x 2 box, 5U high
box = GridfinityBox(3, 2, 5)
box.save_step_file()
# Output a STEP file of box named:
# gf_box_3x2x5.step
```
### Lite Style Box
"Lite" style boxes are simplified for faster 3D printing with less material. They remove the continuous floor at 7.2 mm and the box becomes a homogenous 1 mm thick walled shell. "lite" style boxes can include labels and dividers; however, the number of dividers must correspond to the same bottom partition ridges, i.e. `length_div` must be `length_u - 1` and `width_div` must be `width_u - 1`. "lite" style cannot be combined with solid boxes, finger scoops, or magnet holes.
```python
# Create a "lite" style 3 x 2 box, 5U high
box = GridfinityBox(3, 2, 5, lite_style=True)
box.save_step_file()
# Output a STEP file of box named:
# gf_box_lite_3x2x5.step
```
### Magnet Holes
```python
# add magnet holes to the box
box = GridfinityBox(3, 2, 5, holes=True)
box.save_step_file()
# gf_box_3x2x5_holes.step
```
The `unsupported_holes` attribute can specify either regular holes or modified/unsupported holes which are more suitable for 3D-printing. These modified holes include thin filler strips which allow the slicer to avoid using supports to render the underside holes.
```python
# add magnet holes to the box
box = GridfinityBox(1, 1, 5, holes=True, unsupported_holes=True)
box.save_step_file()
# gf_box_1x1x5_holes.step
```
### Simple Box with No Top Lip
```python
# remove top mounting lip
box = GridfinityBox(3, 2, 5, no_lip=True)
box.save_step_file()
# gf_box_3x2x5_basic.step
```
### Scoops and Labels
```python
# add finger scoops and label top flange
box = GridfinityBox(3, 2, 5, scoops=True, labels=True)
box.save_step_file()
# gf_box_3x2x5_scoops_labels.step
```
### Dividing Walls
```python
# add dividing walls
box = GridfinityBox(3, 2, 5, length_div=2, width_div=1, scoops=True, labels=True)
box.save_step_file()
# gf_box_3x2x5_div2x1_scoops_labels.step
```
### Solid Box
```python
# make a partially solid box
box = GridfinityBox(3, 2, 5, solid=True, solid_ratio=0.7)
box.save_step_file()
# gf_box_3x2x5_solid.step
```
### Optional Keyword Arguments
```python
length_div=0 # add dividing walls along length
width_div=0 # add dividing walls along width
holes=False # add magnet holes to bottom
unsupported_holes=False # 3D-printer friendly hole style requiring no supports
no_lip=False # remove top mating lip feature
scoops=False # add finger scoops
scoop_rad=11 # radius of optional interior scoops
labels=False # add a label flange to the top
label_width=12 # width of the label strip
label_height=10 # thickness height of label overhang
label_lip_height=0.8 # thickness of label vertical lip
lite_style=False # make a "lite" version of box without elevated floor
solid=False # make a solid box
solid_ratio=1.0 # ratio of solid height range 0.0 to 1.0 (max height)
wall_th=1.0 # wall thickness (0.5-2.5 mm)
fillet_interior=True # enable/disable internal fillet edges
```
## `GridfinityDrawerSpacer`
The `GridfinityDrawerSpacer` class can be used to make spacer components to fit a drawer with any arbitrary dimensions. Initialize with specified width and depth of the drawer (in mm) and the best fit of integer gridfinity baseplate units is computed. Rarely, integer multiples of 42 mm gridfinity baseplates fit perfectly inside a drawer; therefore, spacers are required to secure the baseplate snuggly inside the drawer. Spacers consist of 4x identical corner sections, 2x spacers for the left and right sides and 2x spacers for the front and back edges.
If the computed spacer width falls below a configurable threshold (default 4 mm), then no spacer component is made in that dimension. The spacer components are made by default with interlocking "jigsaw" type features to assist with assembly and to secure the spacers within the drawer. Also, alignment arrows (default but optional) are placed on the components to indicate the installation orientation in the direction of the drawer movement.
```python
# make drawer spacers for Craftsman tool chest drawer 23" wide x 19" deep
spacer = GridfinityDrawerSpacer(582, 481, verbose=True)
# Best fit for 582.00 x 481.00 mm is 13U x 11U
# with 18.00 mm margin each side and 9.50 mm margin front and back
# Corner spacers : 4U wide x 3U deep
# Front/back spacers : 5U wide x 9.25 mm +0.25 mm tolerance
# Left/right spacers : 5U deep x 17.75 mm +0.25 mm tolerance
```
A full set of components (optionally including a full baseplate) can be rendered with the `render_full_set()` method. This method is mostly used to verify the fit and placement of the spacers.
Normally, the `render_half_set()` method used to render half of the components compactly arranged conveniently for 3D printing. This set can be printed twice to make a full set for a single drawer.
### Optional Keyword Arguments
```python
thickness=GR_BASE_HEIGHT # thickness of spacers, default=5 mm
chamf_rad=1.0 # chamfer radius of spacer top/bottom edges
show_arrows=True # show orientation arrows indicating drawer in/out direction
align_features=True # add "jigsaw" interlocking feautures
align_tol=0.15 # tolerance of the interlocking joint
align_min=8 # minimum spacer width for adding interlocking feature
min_margin=4 # minimum size to make a spacer, nothing is made for smaller gaps
tolerance=GR_TOL # overall tolerance for spacer components, default=0.5 mm
```
### Example with IKEA ALEX narrow drawer
An example use case to make a set of spacer components for a typical IKEA narrow ALEX drawer is as follows:
```python
spacers = GridfinityDrawerSpacer(INCHES(11.5), INCHES(20.5), verbose=True)
spacers.render_full_set(include_baseplate=True)
spacers.save_step_file("ikea_alex_full_set.step")
# make a half set for 3D printing
spacers.render_half_set()
spacers.save_stl_file("ikea_alex_half_set.stl")
```
## `GridfinityRuggedBox`
The `GridfinityRuggedBox` class can be used to make gridfinity compatible rugged storage boxes. This box is based on the [superb design by Pred on Printables](https://www.printables.com/model/543553-gridfinity-storage-box-by-pred-now-parametric).
The **cq-gridfinity** derivative version of Pred's box is completely parameterized and generated completely with code in the `GridfinityRuggedBox` class. This lets you render the most minimalist box configuration with no added features up to a full-featured box as shown below:
The desired box size and features are specified with keyword arguments/attributes such as the ones illustrated below:
A alternative "rib style" rugged box is also available. This adds vertical rib stiffeners around the perimeter of the box and it is recommended to disable the side handles to allow for ribs to be generated on all sides.
Lastly, the lid baseplate can be substituted with a lid window which makes the contents of the box visible. The window consists of a seperately prepared 1 mm thick transparent acrylic sheet cut to the required dimensions. These dimensions can be queried with the `lid_window_size()` method or will be printed to the console when using the `ruggedbox` shell script.
After the lid has been printed the process to install the lid window is as follows:
1. Cut the lid window to the required dimensions. It is recommended to chamfer or round off the leading edge corners with a file prior to insertion.
2. Slide the window into the lid starting from the back and along the tapered window groove slot around the inside perimeter of the lid.
3. The window should be inserted just past the retention slots for the hinges.
4. Secure the lid with 3x M2 screws along the back of the lid. Carefully drill 2.5 mm clearance holes into the window in situ prior to installation of the screws. Alternatively, the lid can be secured with a few drops of super glue along the rear edge.
5. Install the lid hinges. The hinges must be installed last since they act as a physical retainer along the back edge of the window.
The lid window should nominally be 1 mm thick; however if it necessary to use a different thickness material, the `window_th` attribute can be set. It recommended to keep the window thickness in the range of 0.8 to 1.6 mm.
The rugged box can be rendered either as a complete assembly or as individual components. This is useful for making individual asset files for 3D printing. The render methods include the `render_assembly()` method as shown above for the complete assembly, as well as individual render methods summarized below:
`render()` - renders just the main box body shell:
`render_lid()` - renders the lid:
`render_accessories()` - renders the accessory component elements as a group in the quantities required for the desired box:
Lastly, each individual component has an individual render method.
- `render_hinge()`
- `render_latch()`
- `render_label()`
- `render_handle()`
### Optional Keyword Arguments
```python
lid_height = 10 # lid height (should be multiple of 10 mm for stacking)
wall_vgrooves = True # enable horizontal v-grooves to body shell
front_handle = True # enable front handle
stackable = True # add mating stackable features
side_clasps = True # add extra side latching clasps
lid_baseplate = True # enable top/lid baseplate
inside_baseplate = True # enable interior baseplate
side_handles = True # enable side handles to box
front_label = True # enable front label panel
label_length = None # length of front label panel, None=auto size
label_height = None # height of front label panel, None=auto size
label_th = GR_LABEL_TH # thickness of label panel, default=0.5 mm
back_feet = True # add rear back feet matching hinges to allow the stand box vertically
hinge_width = GR_HINGE_SZ # Size of hinge, default=32 mm
hinge_bolted = False # printed or bolted hinge construction
box_color = cq.Color(0.25, 0.25, 0.25) # colors for the assembly STEP file
lid_color = cq.Color(0.25, 0.5, 0.75)
handle_color = cq.Color(0.75, 0.5, 0.25)
latch_color = cq.Color(0.75, 0.5, 0.25)
hinge_color = cq.Color(0.75, 0.5, 0.25)
label_color = cq.Color(0.7, 0.7, 0.7)
```
## `GridfinityObject`
The `GridfinityObject` is the base class for `GridfinityBox`, `GridfinityBaseplate`, etc. It has several useful methods and attributes including:
### File export and naming
`obj.filename(self, prefix=None, path=None)` returns a filename string with descriptive attributes such as the object size and enabled features.
```python
box = GridfinityBox(3, 2, 5, holes=True)
box.filename()
# gf_box_3x2x5_holes
box.filename(prefix="MyBox")
# MyBox_3x2x5_holes
box.filename(path="./outputfiles")
# ./outputfiles/gf_box_3x2x5_holes
box2 = GridfinityBox(4, 3, 3, holes=True, length_div=2, width_div=1)
box2.filename()
# gf_box_4x3x3_holes_div2x1
```
```python
# Export object to STEP, STL, or SVG file
obj.save_step_file(filename=None, path=None, prefix=None)
obj.save_stl_file(filename=None, path=None, prefix=None)
obj.save_svg_file(filename=None, path=None, prefix=None)
```
The automatic filename assignment is aware of the last object generated with a particular class's render method. Therefore, you can call any render method and then call any of the `save_step_file`, `save_stl_file`, `save_svg_file` methods and the filename will adapt to the last object rendered. For example:
```python
b1 = GridfinityRuggedBox(5, 4, 6)
b1.render_accessories()
b1.save_step_file()
# saved as "gf_ruggedbox_5x4x6_acc_fr-hl_sd-hc_stack_lidbp.step"
b1.render_handle()
b1.save_stl_file()
# saved as "gf_ruggedbox_5x4x6_handle_fr-hl_sd-hc_stack_lidbp.stl"
b1.render_hinge()
b1.save_svg_file(path="./mystuff")
# saved as "./mystuff/gf_ruggedbox_5x4x6_hinge_fr-hl_sd-hc_stack_lidbp.svg"
b1.render_assembly()
b1.save_step_file()
# saved as "gf_ruggedbox_5x4x6_assembly_fr-hl_sd-hc_stack_lidbp.step"
```
### Useful properties
```obj.cq_obj``` returns a rendered CadQuery Workplane object
```obj.length``` returns length in mm
```obj.width``` returns width in mm
```obj.height``` returns height in mm
```obj.top_ref_height``` returns the height of the top surface of a solid box or the floor height of an empty box. This can be useful for making custom boxes with cutouts since the reference height can be used to orient the cutting solid to the correct height.
# To-do
- add more example scripts
- improve documentation
# Releases
- v.0.1.0 - First release on PyPI
- v.0.1.1 - Fixed release
- v.0.2.0 - Added new "lite" style box
- v.0.2.1 - Added new unsupported magnet hole types
- v.0.2.2 - Added SVG export and integrated STL exporter
- v.0.2.3 - Updated to python build tools to make distribution
- v.0.3.0 - Added console generator scripts: `gridfinitybox` and `gridfinitybase`
- v.0.4.0 - Added `GridfinityRuggedBox` class and `ruggedbox` console script. Various other improvements.
- v.0.4.1 - Fixed docstring in `__init__.py`
- v.0.4.2 - Improved script automatic renaming
- v.0.4.3 - Fixed regression bug with using multilevel extrusion functions from cq-kit
- v.0.4.4 - IMPORTANT FIX: generated geometry breaks using CadQuery v.2.4+ due to changes in CadQuery's `extrude` method. This version should work with any CQ version since it detects which CQ extrusion implementation is used at runtime.
- v.0.4.5 - IMPORTANT FIX: fixes error in v.0.4.4 for extrusion angle
- v.0.5.0 - Improved rugged box to make viable boxes down to 3U x 3U x 4U
- v.0.5.1 - Increased the resolution of the gridfinity extruded base profile
- v.0.5.2 - Adjusted geometry of box/bin floor/lip heights to exactly 7.00 mm intervals
- v.0.5.3 - Removed a potential namespace collision for computing the height of boxes
- v.0.5.4 - Optimized the geometry of the baseplate top height
- v.0.5.5 - Added underside bin clearance and variable wall thickness interior radiusing
- v.0.5.6 - Added adjustable magnet hole diameter to box. Prevent drawer spacers being rendered which fall below minimum size
- v.0.5.7 - Added scoops to lite-style boxes. Added new "rib style" rugged box. Added a lid window feature to the rugged box.
# References
- [Zach Freedman's YouTube Channel](https://www.youtube.com/c/ZackFreedman)
- [The video that started it all!](https://youtu.be/ra_9zU-mnl8?si=EOT1LFV65VZfiepi)
- [Gridfinity Documentation repo](https://github.com/Stu142/Gridfinity-Documentation)
- [Gridfinity Unofficial wiki](https://gridfinity.xyz)
- Catalogs
- [gridfinity-catalog](https://github.com/jeffbarr/gridfinity-catalog)
- [Master Collection on Printables](https://www.printables.com/model/242711-gridfinity-master-collection)
- Software/Tools
- [Online Gridfinity Creator](https://gridfinity.bouwens.co)
- [Gridfinity rebuilt OpenSCAD library](https://github.com/kennetek/gridfinity-rebuilt-openscad)
- [Gridfinity Fusion360 generator plugin](https://github.com/Le0Michine/FusionGridfinityGenerator)
- [FreeCAD Gridfinity Parametric Files (on Printables)](https://www.printables.com/@Stu142_524934/collections/969910)
- [Gridfinity eco (low-cost Gridfinity resources)](https://github.com/jrymk/gridfinity-eco)
- [Another CadQuery based Gridfinity script](https://github.com/kmeisthax/gridfinity-cadquery)
- Videos
- [Zach Freedman's follow-up Jul 2022](https://youtu.be/Bd4NnHvTRAY?si=rvgb9geXnq83mhOv)
- [Zach Freedman's follow-up Dec 2022](https://youtu.be/7FCwMq-rVsY?si=tdqAe8MthGjfWEbR)
- [The Next Layer tips video](https://youtu.be/KtbKwAuwv9s?si=1hYPjOvqf8tb5NO9)
## Authors
**cq-gridfinity** was written by [Michael Gale](https://github.com/michaelgale)
================================================
FILE: cqgridfinity/__init__.py
================================================
"""cqgridfinity - A python library to make Gridfinity compatible objects with CadQuery."""
import os
# fmt: off
__project__ = 'cqgridfinity'
__version__ = '0.5.7'
# fmt: on
VERSION = __project__ + "-" + __version__
script_dir = os.path.dirname(__file__)
from .constants import *
from .gf_obj import GridfinityObject
from .gf_baseplate import GridfinityBaseplate
from .gf_box import GridfinityBox, GridfinitySolidBox
from .gf_drawer import GridfinityDrawerSpacer
from .gf_ruggedbox import GridfinityRuggedBox
================================================
FILE: cqgridfinity/constants.py
================================================
#! /usr/bin/env python3
#
# Copyright (C) 2023 Michael Gale
# This file is part of the cq-gridfinity python module.
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Globally useful constants representing Gridfinity geometry
from math import sqrt
SQRT2 = sqrt(2)
EPS = 1e-5
M2_DIAM = 1.8
M2_CLR_DIAM = 2.5
M3_DIAM = 3
M3_CLR_DIAM = 3.5
M3_CB_DIAM = 5.5
M3_CB_DEPTH = 3.5
GRU = 42
GRU2 = GRU / 2
GRHU = 7
GRU_CUT = 42.2 # base extrusion width
GR_WALL = 1.0 # nominal exterior wall thickness
GR_DIV_WALL = 1.2 # width of dividing walls
GR_TOL = 0.5 # nominal tolerance
GR_RAD = 4 # nominal exterior filleting radius
GR_BASE_CLR = 0.25 # clearance above the nominal base height
GR_BASE_HEIGHT = 4.75 # nominal base height
# baseplate extrusion profile
GR_BASE_CHAMF_H = 0.98994949 / SQRT2
GR_STR_H = 1.8
GR_BASE_TOP_CHAMF = GR_BASE_HEIGHT - GR_BASE_CHAMF_H - GR_STR_H
GR_BASE_PROFILE = (
(GR_BASE_TOP_CHAMF * SQRT2, 45),
GR_STR_H,
(GR_BASE_CHAMF_H * SQRT2, 45),
)
GR_STR_BASE_PROFILE = (
(GR_BASE_TOP_CHAMF * SQRT2, 45),
GR_STR_H + GR_BASE_CHAMF_H,
)
GR_BOT_H = 7 # bin nominal floor height
GR_FILLET = 1.1 # inside filleting radius
GR_FLOOR = GR_BOT_H - GR_BASE_HEIGHT # floor offset
# box/bin extrusion profile
GR_BOX_CHAMF_H = 1.1313708 / SQRT2
GR_BOX_TOP_CHAMF = GR_BASE_HEIGHT - GR_BOX_CHAMF_H - GR_STR_H + GR_BASE_CLR
GR_BOX_PROFILE = (
(GR_BOX_TOP_CHAMF * SQRT2, 45),
GR_STR_H,
(GR_BOX_CHAMF_H * SQRT2, 45),
)
# bin mating lip extrusion profile
GR_UNDER_H = 1.6
GR_TOPSIDE_H = 1.2
GR_LIP_PROFILE = (
(GR_UNDER_H * SQRT2, 45),
GR_TOPSIDE_H,
(0.7 * SQRT2, -45),
1.8,
(1.3 * SQRT2, -45),
)
GR_LIP_H = 0
for h in GR_LIP_PROFILE:
if isinstance(h, tuple):
GR_LIP_H += h[0] / SQRT2
else:
GR_LIP_H += h
GR_NO_PROFILE = (GR_LIP_H,)
# bottom hole nominal dimensions
GR_HOLE_D = 6.5
GR_HOLE_H = 2.4
GR_BOLT_D = 3.0
GR_BOLT_H = 3.6 + GR_HOLE_H
GR_HOLE_DIST = 26 / 2
GR_HOLE_SLICE = 0.25
# Rugged Box constant parameters
GR_RBOX_WALL = 2.5
GR_RBOX_FLOOR = 1.2
GR_RBOX_CWALL = 10.0
GR_RBOX_CORNER_W = 56
GR_RBOX_BACK_L = 66
GR_RBOX_FRONT_L = 56
GR_RBOX_RAD = 3.745
GR_RBOX_CRAD = 14
GR_RBOX_CHAN_W = 20
GR_RBOX_CHAN_D = GR_RBOX_CWALL - GR_RBOX_WALL
GR_RBOX_VCUT_D = 1
GR_CLASP_SLIDE_D = 39
GR_CLASP_SLIDE_W = 4
GR_RIB_W = 2
GR_RIB_L = 5
GR_RIB_GAP = 1
GR_RIB_H = 3.5
GR_RIB_SEP = 4
GR_RIB_CTR = 10
GR_REG_L = 5
GR_REG_W = 2.5
GR_REG_H = 2.5
GR_REG_R0 = 10.75
GR_REG_R1 = 8.25
GR_BREG_R0 = GR_REG_R0 + 0.25
GR_BREG_R1 = GR_REG_R1 - 0.25
GR_HANDLE_L1 = 12
GR_HANDLE_L2 = 28
GR_HANDLE_H = 7.5
GR_HANDLE_W = 5
GR_HANDLE_SEP = 12.5
GR_HANDLE_OFS = 61.5
GR_HANDLE_SZ = 30
GR_HANDLE_TH = 7
GR_HANDLE_RAD = 11
GR_LID_HANDLE_W = 70
GR_SIDE_HANDLE_W = 60
GR_HINGE_SZ = 32
GR_HINGE_D = 3
GR_HINGE_W1 = 5.5
GR_HINGE_H1 = 2.7
GR_HINGE_W2 = 2.1
GR_HINGE_H2 = 9
GR_HINGE_CTR = 30.625
GR_HINGE_W3 = 2
GR_HINGE_SEP = 1
GR_HINGE_OFFS = 2.65
GR_HINGE_SKEW = 0.15
GR_HINGE_RAD = 3.5
GR_HINGE_TOL = 0.4
GR_HEX_H = 3
GR_HEX_W = 4
GR_HEX_D = 1.3
GR_LID_WINDOW_H = 6.5
GR_LABEL_SLOT_TH = 2.5
GR_LABEL_TH = 0.8
GR_LABEL_H = 31
GR_LATCH_L = 32.5
GR_LATCH_W = 19.6
GR_LATCH_H = 7
GR_LATCH_IW = 14.75
GR_LATCH_IL = 5.2
================================================
FILE: cqgridfinity/gf_baseplate.py
================================================
#! /usr/bin/env python3
#
# Copyright (C) 2023 Michael Gale
# This file is part of the cq-gridfinity python module.
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Gridfinity Baseplates
import cadquery as cq
from cqgridfinity import *
from cqkit.cq_helpers import (
rounded_rect_sketch,
composite_from_pts,
rotate_x,
recentre,
)
from cqkit import VerticalEdgeSelector, HasZCoordinateSelector
class GridfinityBaseplate(GridfinityObject):
"""Gridfinity Baseplate
This class represents a basic Gridfinity baseplate object. This baseplate
more or less conforms to the original simple baseplate released by
Zach Freedman. As such, it does not include features such as mounting
holes, magnet holes, weight slots, etc.
length_u - length in U (42 mm / U)
width_u - width in U (42 mm / U)
ext_depth - extrude bottom face by an extra amount in mm
straight_bottom - remove bottom chamfer and replace with straight side
corner_screws - add countersink mounting screws to the inside corners
corner_tab_size - size of mounting screw corner tabs
csk_hole - mounting screw hole diameter
csk_diam - mounting screw countersink diameter
csk_angle - mounting screw countersink angle
"""
def __init__(self, length_u, width_u, **kwargs):
super().__init__()
self.length_u = length_u
self.width_u = width_u
self.ext_depth = 0
self.straight_bottom = False
self.corner_screws = False
self.corner_tab_size = 21
self.csk_hole = 5.0
self.csk_diam = 10.0
self.csk_angle = 82
for k, v in kwargs.items():
if k in self.__dict__ and v is not None:
self.__dict__[k] = v
if self.corner_screws:
self.ext_depth = max(self.ext_depth, 5.0)
def _corner_pts(self):
oxy = self.corner_tab_size / 2
return [
(i * (self.length / 2 - oxy), j * (self.width / 2 - oxy), 0)
for i in (-1, 1)
for j in (-1, 1)
]
def render(self):
profile = GR_BASE_PROFILE if not self.straight_bottom else GR_STR_BASE_PROFILE
if self.ext_depth > 0:
profile = [*profile, self.ext_depth]
rc = self.extrude_profile(
rounded_rect_sketch(GRU_CUT, GRU_CUT, GR_RAD), profile
)
rc = rotate_x(rc, 180).translate((GRU2, GRU2, GR_BASE_HEIGHT + self.ext_depth))
rc = recentre(composite_from_pts(rc, self.grid_centres), "XY")
r = (
cq.Workplane("XY")
.rect(self.length, self.width)
.extrude(GR_BASE_HEIGHT + self.ext_depth)
.edges("|Z")
.fillet(GR_RAD)
.faces(">Z")
.cut(rc)
)
if self.corner_screws:
rs = cq.Sketch().rect(self.corner_tab_size, self.corner_tab_size)
rs = cq.Workplane("XY").placeSketch(rs).extrude(self.ext_depth)
rs = rs.faces(">Z").cskHole(
self.csk_hole, cskDiameter=self.csk_diam, cskAngle=self.csk_angle
)
r = r.union(recentre(composite_from_pts(rs, self._corner_pts()), "XY"))
bs = VerticalEdgeSelector(self.ext_depth) & HasZCoordinateSelector(0)
r = r.edges(bs).fillet(GR_RAD)
return r
================================================
FILE: cqgridfinity/gf_box.py
================================================
#! /usr/bin/env python3
#
# Copyright (C) 2023 Michael Gale
# This file is part of the cq-gridfinity python module.
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Gridfinity Boxes
import math
import cadquery as cq
from cqkit import HasZCoordinateSelector, VerticalEdgeSelector, FlatEdgeSelector
from cqkit.cq_helpers import rounded_rect_sketch, composite_from_pts
from cqgridfinity import *
class GridfinityBox(GridfinityObject):
"""Gridfinity Box
This class represents a Gridfinity compatible box module. As a minimum,
this class is initialized with basic 3D unit dimensions for length,
width, and height. length and width are multiples of 42 mm Gridfinity
intervals and height represents multiples of 7 mm.
Many box features can be enabled with attributes provided either as
keywords or direct dotted access. These attributes include:
- solid : renders the box without an interior, i.e. a solid block. This
is useful for making custom Gridfinity modules by subtracting
out shapes from the solid interior. Normally, the box is
rendered solid up to its maximum size; however, the
solid_ratio attribute can specify a solid fill of between
0.0 to 1.0, i.e. 0 to 100% fill.
- holes : adds bottom mounting holes for magnets or screws
- scoops : adds a radiused bottom edge to the interior to help fetch
parts from the box
- labels : adds a flat flange along each compartment for adding a label
- no_lip : removes the contoured lip on the top module used for stacking
- length_div, width_div : subdivides the box into sub-compartments in
length and/or width.
- lite_style : render box as an economical shell without elevated floor
- unsupported_holes : render bottom holes as 3D printer friendly versions
which can be printed without supports
- label_width : width of top label ledge face overhang
- label_height : height of label ledge overhang
- scoop_rad : radius of the bottom scoop feature
- wall_th : wall thickness
- hole_diam : magnet/counterbore bolt hole diameter
"""
def __init__(self, length_u, width_u, height_u, **kwargs):
super().__init__()
self.length_u = length_u
self.width_u = width_u
self.height_u = height_u
self.length_div = 0
self.width_div = 0
self.scoops = False
self.labels = False
self.solid = False
self.holes = False
self.no_lip = False
self.solid_ratio = 1.0
self.lite_style = False
self.unsupported_holes = False
self.label_width = 12 # width of the label strip
self.label_height = 10 # thickness of label overhang
self.label_lip_height = 0.8 # thickness of label vertical lip
self.scoop_rad = 14 # radius of optional interior scoops
self.fillet_interior = True
self.wall_th = GR_WALL
self.hole_diam = GR_HOLE_D # magnet/bolt hole diameter
for k, v in kwargs.items():
if k in self.__dict__:
self.__dict__[k] = v
self._int_shell = None
self._ext_shell = None
def __str__(self):
s = []
s.append(
"Gridfinity Box %dU x %dU x %dU (%.2f x %.2f x %.2f mm)"
% (
self.length_u,
self.width_u,
self.height_u,
self.length - GR_TOL,
self.width - GR_TOL,
self.height,
)
)
sl = "no mating top lip" if self.no_lip else "with mating top lip"
ss = "Lite style box " if self.lite_style else ""
s.append(" %sWall thickness: %.2f mm %s" % (ss, self.wall_th, sl))
s.append(
" Floor height : %.2f mm Inside height: %.2f mm Top reference height: %.2f mm"
% (self.floor_h + GR_BASE_HEIGHT, self.int_height, self.top_ref_height)
)
if self.solid:
s.append(" Solid filled box with fill ratio %.2f" % (self.solid_ratio))
if self.holes:
s.append(" Bottom mounting holes with %.2f mm diameter" % (self.hole_diam))
if self.unsupported_holes:
s.append(" Holes are 3D printer friendly and can be unsupported")
if self.scoops:
s.append(" Lengthwise scoops with %.2f mm radius" % (self.scoop_rad))
if self.labels:
s.append(
" Lengthwise label shelf %.2f mm wide with %.2f mm overhang"
% (self.label_width, self.label_height)
)
if self.length_div:
xl = (self.inner_l - GR_DIV_WALL * (self.length_div)) / (
self.length_div + 1
)
s.append(
" %dx lengthwise divisions for %.2f mm compartment lengths"
% (self.length_div, xl)
)
if self.width_div:
yl = (self.inner_w - GR_DIV_WALL * (self.width_div)) / (self.width_div + 1)
s.append(
" %dx widthwise divisions for %.2f mm compartment widths"
% (self.width_div, yl)
)
s.append(" Auto filename: %s" % (self.filename()))
return "\n".join(s)
def render(self):
"""Returns a CadQuery Workplane object representing this Gridfinity box."""
self._int_shell = None
if self.lite_style:
# just force the dividers to the desired quantity in both dimensions
# rather than raise a exception
if self.length_div:
self.length_div = self.length_u - 1
if self.width_div:
self.width_div = self.width_u - 1
if self.solid:
raise ValueError(
"Cannot select both solid and lite box styles together"
)
if self.holes:
raise ValueError(
"Cannot select both holes and lite box styles together"
)
if self.wall_th > 1.5:
raise ValueError(
"Wall thickness cannot exceed 1.5 mm for lite box style"
)
if self.wall_th > 2.5:
raise ValueError("Wall thickness cannot exceed 2.5 mm")
if self.wall_th < 0.5:
raise ValueError("Wall thickness must be at least 0.5 mm")
r = self.render_shell()
rd = self.render_dividers()
rs = self.render_scoops()
rl = self.render_labels()
for e in (rd, rl, rs):
if e is not None:
r = r.union(e)
if not self.solid and self.fillet_interior:
heights = [GR_FLOOR]
if self.labels:
heights.append(self.safe_label_height(backwall=True, from_bottom=True))
heights.append(self.safe_label_height(backwall=False, from_bottom=True))
bs = (
HasZCoordinateSelector(heights, min_points=1, tolerance=0.5)
+ VerticalEdgeSelector(">5")
- HasZCoordinateSelector("<%.2f" % (self.floor_h))
)
if self.lite_style and self.scoops:
bs = bs - HasZCoordinateSelector("<=%.2f" % (self.floor_h))
bs = bs - VerticalEdgeSelector()
r = self.safe_fillet(r, bs, self.safe_fillet_rad)
if self.lite_style and not self.has_dividers:
bs = FlatEdgeSelector(self.floor_h)
if self.wall_th < 1.2:
r = self.safe_fillet(r, bs, 0.5)
elif self.wall_th < 1.25:
r = self.safe_fillet(r, bs, 0.25)
if not self.labels and self.has_dividers:
bs = VerticalEdgeSelector(
GR_TOPSIDE_H, tolerance=0.05
) & HasZCoordinateSelector(GRHU * self.height_u - GR_BASE_HEIGHT)
r = self.safe_fillet(r, bs, GR_TOPSIDE_H - EPS)
if self.holes:
r = self.render_holes(r)
r = r.translate((-self.half_l, -self.half_w, GR_BASE_HEIGHT))
if self.unsupported_holes:
r = self.render_hole_fillers(r)
return r
@property
def top_ref_height(self):
"""The height of the top surface of a solid box or the floor
height of an empty box."""
if self.solid:
return self.max_height * self.solid_ratio + GR_BOT_H
if self.lite_style:
return self.floor_h
return GR_BOT_H
@property
def bin_height(self):
return self.height - GR_BASE_HEIGHT
def safe_label_height(self, backwall=False, from_bottom=False):
lw = self.label_width
if backwall:
lw += self.lip_width
lh = self.label_height * (lw / self.label_width)
yl = self.max_height - self.label_height + self.wall_th
if backwall:
yl -= self.lip_width
if yl < 0:
lh = self.max_height - 1.5 * GR_FILLET - 0.1
elif yl < 1.5 * GR_FILLET:
lh -= 1.5 * GR_FILLET - yl + 0.1
if from_bottom:
ws = math.sin(math.atan2(self.label_height, self.label_width))
if backwall:
lh = self.max_height + GR_FLOOR - lh + ws * self.wall_th
else:
lh = self.max_height + GR_FLOOR - lh + ws * GR_DIV_WALL
return lh
@property
def has_dividers(self):
return self.length_div > 0 or self.width_div > 0
@property
def interior_solid(self):
if self._int_shell is not None:
return self._int_shell
self._int_shell = self.render_interior()
return self._int_shell
def render_interior(self, force_solid=False):
"""Renders the interior cutting solid of the box."""
wall_u = self.wall_th - GR_WALL
wall_h = self.int_height + wall_u
under_h = ((GR_UNDER_H - wall_u) * SQRT2, 45)
profile = GR_NO_PROFILE if self.no_lip else [under_h, *GR_LIP_PROFILE[1:]]
profile = [wall_h, *profile]
if self.int_height < 0:
profile = [self.height - GR_BOT_H]
rci = self.extrude_profile(
rounded_rect_sketch(*self.inner_dim, self.inner_rad), profile
)
rci = rci.translate((*self.half_dim, self.floor_h))
if self.solid or force_solid:
hs = self.max_height * self.solid_ratio
ri = rounded_rect_sketch(*self.inner_dim, self.inner_rad)
rf = cq.Workplane("XY").placeSketch(ri).extrude(hs)
rf = rf.translate((*self.half_dim, self.floor_h))
rci = rci.cut(rf)
if self.scoops and not self.no_lip and not self.lite_style:
rf = (
cq.Workplane("XY")
.rect(self.inner_l, 2 * self.under_h)
.extrude(self.max_height)
.translate((self.half_l, -self.half_in, self.floor_h))
)
rci = rci.cut(rf)
if self.lite_style:
r = composite_from_pts(self.base_interior(), self.grid_centres)
rci = rci.union(r)
return rci
def solid_shell(self):
"""Returns a completely solid box object useful for intersecting with other solids."""
if self._ext_shell is not None:
return self._ext_shell
r = self.render_shell(as_solid=True)
self._ext_shell = r.cut(self.render_interior(force_solid=True))
return self._ext_shell
def mask_with_obj(self, obj):
"""Intersects a solid object with this box."""
return obj.intersect(self.solid_shell())
def base_interior(self):
profile = [GR_BASE_HEIGHT, *GR_BOX_PROFILE]
zo = GR_BASE_HEIGHT + GR_BASE_CLR
if self.int_height < 0:
h = self.bin_height - GR_BASE_HEIGHT
profile = [h, *profile]
zo += h
r = self.extrude_profile(
rounded_rect_sketch(GRU - GR_TOL, GRU - GR_TOL, self.outer_rad),
profile,
)
rx = r.faces("Parameters:
|