This application generates STL or STEP files of configurable Gridfinity compatible components. For example, for the standard divider bin you can specify width, length, height, number of compartments (in both directions) and whether or not you want a stacking lip, magnet holes, screw holes, curved scoop surface and/or one or more label tabs. Here are some of the possible bins you can create in this way:
The total number of possible combinations with those options is beyond one million, which is why the 3D models are dynamically created and not pre-calculated.
There are currently a few components available, listed below. Other components are in the works.
- Baseplate: Basic baseplate without screws, magnets or weighting.
- Divider bin: Standard divider bin very similar to Zack's original design.
- Light bin: A light version of the normal Gridfinity bin that saves plastic and offers more room. This means there is no room for magnets and/or screws
- Solid bin: A completely filled solid Gridfinity bin which can be used as a starting point for custom bins
- Holey bin: A solid bin with a grid of holes of user-defined shape, size and depth. Includes option for a keepout-area around each hole
If you don't know how to run your own server (or simply don't want to), there should be an instance of the generator running at https://gridfinity.bouwens.co. No uptime guarantees, but it has been running since early 2023 and seems stable enough, even with the occasional spike in load.
The generator runs as a web-application in a docker container. To run your own instance, perform the following steps from the command line:
- Download and unzip the code or clone the repository:
git clone https://github.com/jeroen94704/gridfinitycreator - cd into the source directory
- Build the image and start the server:
./deploy.sh(may need to prefix this with 'sudo')
Now you can access the application by opening a browser and navigating to :5000, e.g.
http://192.168.1.100:5000/
Notes:
./deploy.shbuilds the image itself;./build.shis only needed if you want to build the image without starting it.- The compose file attaches the container to an external docker network called
proxy(used by the reverse proxy set-up below)../deploy.shcreates that network if it does not exist yet. - Logs are kept in
./data/gridfinitycreator/logs. To keep them elsewhere, setDATA_ROOTin.env.container. - Your local settings live in
.env.container, which./deploy.shcreates from.env.container.templateon a first start. It is not kept in git, so it is yours to edit. (If you update an existing checkout that still tracked it, move your copy aside beforegit pulland put it back after.) - The server runs as an ordinary user (uid 1000), so the log directory has to be writable by that user.
./deploy.shcreates it for you, owned by you; if your user is not uid 1000 it tells you how to hand the directory over. If the server cannot write there it still starts, and logs to a temporary directory (lost when the container is) with a warning indocker logs. - The scripts use
docker compose(the Compose plugin) when it is installed and fall back to the olderdocker-compose.
Everything has a default that works for a small private network, so none of this is required. The settings are environment variables, which you can add under environment: in the compose file.
| Variable | Default | Meaning |
|---|---|---|
FLASK_PORT |
5000 |
Port the server listens on |
GFG_BIND |
0.0.0.0 (127.0.0.1 in debug mode) |
Address the server listens on |
GFG_SECRET_KEY |
random at each start | Key used to sign sessions and form tokens. Set it to keep it fixed across restarts; a page left open across a restart is otherwise told to reload |
GFG_TMP_DIR |
/tmpfiles in the image |
Where generated files are written until they are downloaded (the compose files mount a RAM disk here) |
GFG_LOG_DIR |
/logs in the image |
Where the log files go |
GFG_MAX_JOBS |
2 |
Models built at the same time |
GFG_MAX_QUEUE |
4 |
Further requests allowed to wait for a turn; anything beyond that is told the server is busy (HTTP 503) |
GFG_QUEUE_TIMEOUT |
120 |
Seconds a waiting request is kept waiting |
GFG_THREADS |
max jobs + queue + 4 | Server threads (never fewer than max jobs + queue + 1, so the page stays responsive while models are building) |
GFG_BUILD_TIMEOUT |
300 |
Seconds a model may take to build before it is stopped |
GFG_MAX_DIVIDER_SEGMENTS |
300 |
Largest divider-bin layout allowed, in divider wall segments (about 12 x 12 compartments) |
GFG_MAX_LIGHT_DIVIDER_SEGMENTS |
150 |
The same limit for the light bin, which does more work per segment |
GFG_MAX_HOLES |
600 |
Most holes a holey bin may have |
GFG_TRUSTED_PROXIES |
none | Reverse proxies whose X-Forwarded-For, -Proto and -Host headers are believed: addresses or networks, separated by commas (for example 172.18.0.0/16). Leave it empty when visitors connect directly. See Reverse proxy |
GFG_PROXY_HOPS |
1 |
How many proxies a request passes through before it reaches the server (only matters with GFG_TRUSTED_PROXIES) |
GFG_HSTS_MAX_AGE |
none | Seconds for which browsers are told to insist on https for this host (Strict-Transport-Security). Not sent unless set, and only over https. See Security |
GFG_LOG_CLIENT_IP |
true |
Set to false (or 0, no, off) to leave visitors' addresses out of the log. See Security |
If you expose an instance to the internet, lower the limits (GFG_MAX_QUEUE, GFG_QUEUE_TIMEOUT, GFG_BUILD_TIMEOUT and the three GFG_MAX_* limits) so one visitor cannot keep the server busy for long.
The container runs as an ordinary user (uid 1000) on a read-only filesystem with no extra privileges and no Linux capabilities. It writes only to two size-limited RAM disks (/tmpfiles for the generated files and /tmp) and to the /logs volume. The compose files set this up, and CI starts the image exactly like that and uses it.
Every response carries security headers. The main one is a Content-Security-Policy that lets the page run scripts and load styles, fonts, images and data from the server itself only, so a script that got into the page by mistake would not be run. That is why the page has no inline scripts or styles, and why anything from another host (a font, an analytics script, an embed) does not load until the policy in security_headers.py is widened for it. The other headers keep the page from being framed, stop browsers guessing file types, and switch off camera, microphone and location access.
Strict-Transport-Security is not sent by default: a browser that has been told keeps refusing plain http for the host until the time is up, which locks you out of an instance whose https is not working yet, or one that is only meant for a private network. To send it, set GFG_HSTS_MAX_AGE to a number of seconds (for example 15552000, 180 days). It is only sent over https, and behind a reverse proxy only when GFG_TRUSTED_PROXIES is set, so that the server knows the visitor used https. If your proxy already sends it, leave the setting alone.
The server keeps one log, access.log in the log directory, which rotates (the current file and ten older ones, up to 1 MB each) so it never grows beyond about 11 MB. It records every model that is generated, with the visitor's address. Where addresses count as personal data, set GFG_LOG_CLIENT_IP=false and the log records - instead. Behind a reverse proxy the address is the visitor's only if GFG_TRUSTED_PROXIES is set (see Reverse proxy).
Models are built in separate processes so that the web page stays responsive while they are being generated. An idle instance uses roughly 1 GB of memory (the web server and a warm copy of CadQuery that new builds start from); each model being built at the same time needs a few hundred MB more, more for the largest ones. Allow about 2 GB for the default settings, and fewer concurrent builds (GFG_MAX_JOBS) on a smaller machine. The compose files cap the container at 3 GB; set GFG_MEM_LIMIT (for example GFG_MEM_LIMIT=4g in .env.container) to change that. A normal bin takes about a second to build; the largest allowed ones take about a minute.
If you want to deploy this app with Portainer, use the Git repository and a stack that points to the simplified compose file.
- Repository URL:
https://github.com/NoSQLKnowHow/gridfinitycreator.git - Repository reference:
feature/config-library(ormainfor the main branch) - Compose path:
docker-compose.portainer.yml
This stack file needs no environment file and no external proxy network.
The server runs as uid 1000, so the gridfinitycreator_logs volume has to belong to that user. A volume created fresh starts out that way. One created by an earlier version of the image (which ran as root) belongs to root; the server then logs to a temporary directory instead and says so in the container's log. To hand the volume over:
docker run --rm -v gridfinitycreator_logs:/logs alpine chown -R 1000:1000 /logs
If your Portainer UI only shows a single repository reference field, use https://github.com/NoSQLKnowHow/gridfinitycreator.git#feature/config-library.
The deploy script results in the server running in production mode using the Waitress WSGI server. This is good for performance, but if you want to debug the code, start the server using the "./debug.sh" script instead of "./deploy.sh". This will make the server start itself using the built-in Flask server, which has convenient debugging features.
The debug server includes an interactive debugger that can run code on the machine, so it is only published on the host's own loopback address (http://127.0.0.1:5001/). Do not publish it on a network interface.
Run the tests with pip install cadquery==2.8.0 -r requirements-dev.txt followed by pytest. They drive the real application, including real CadQuery geometry, so CadQuery must be installed (the version the image uses); the whole suite takes about a minute. If Node.js is installed they also run the unit tests of the browser-side scripts (tests/js).
Dependencies are pinned to the versions the tests run against (requirements.txt; CadQuery in the Dockerfile, where conda installs it, and in .github/workflows/ci.yml; a test checks that they agree). Dependabot proposes updates to the pins, the base image and the CI actions.
CI (.github/workflows/ci.yml) lints for real bugs (ruff.toml), runs the tests, then builds the image, runs the tests inside it, starts it hardened as described above and uses it (tools/smoke_test.py, which you can also point at any running instance: python tools/smoke_test.py http://host:5000).
Because I use Traefik myself I included the Traefik labels I use in the docker-compose file. If you want to use Traefik, uncomment them and comment out the "ports" section. You will also need to fill in your domain in the .env.container file.
A proxy that ends the visitor's HTTPS connection tells the server who the visitor was and that they used https in X-Forwarded-* headers. The server believes those headers only from the proxies you name in GFG_TRUSTED_PROXIES: anyone who can reach the server directly can send the same headers, and would otherwise be able to choose the address that ends up in the log. Headers from any other peer are discarded, and X-Forwarded-Prefix is never used (the server does not run under a path prefix). Without the setting the server sees the proxy's address, logs it for every visitor, and does not mark its cookies Secure.
Name the proxy by its address, or by the network it is on (docker network inspect proxy shows the subnet of the proxy network the compose file uses, for example GFG_TRUSTED_PROXIES=172.18.0.0/16). With a chain of proxies (a CDN in front of Traefik, say), list the one that talks to the server and set GFG_PROXY_HOPS to the length of the chain. A typo in either setting stops the server from starting, with the reason in docker logs.
I have no experience with other reverse proxy methods (Apache, nginx, Helm, etc), so if anyone creates instructions for setting up GridfinityCreator with any of those I'd happily accept the pull-request.
Kirk Kirkconnell 🤔 |
wug-ge 🐛 |
Chase Bolt 🐛 |
Daniel Seifert 🐛 💻 |
If you find this project useful a small donation is much appreciated (but by no means required or expected): https://ko-fi.com/jeroen94704
GridfinityCreator © 2023 by Jeroen Bouwens is licensed under CC BY-NC-SA 4.0. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/