3D STL
Two entry points, one standard of output: a watertight STL, a parametric script that regenerates it, and a slicer table that makes the print succeed.
| Input | Route | Library |
|---|---|---|
| Dimensions, a described object, a sketch in words | Geometry route | scripts/partlib.py |
| A photo or picture file | Image route | scripts/imagelib.py |
Setup for the geometry route:
pip install trimesh shapely manifold3d --break-system-packages -q
The image route needs only Pillow and numpy — no mesh library, no booleans.
Geometry route
1. Pin down the dimensions before modelling
Most rework here comes from ambiguity, not from bad geometry. Restate the part in a short table (dimension → value → source) and mark anything you assumed. Watch specifically for:
- Outer size vs. mounting centres. "87 mm wide" and "87 mm between the screws" are different parts — they differ by one wall thickness. Ask.
- A missing third dimension. A profile plus a thickness is only two of three; choose a sensible depth, say so, and make it a flag.
- Fastener intent. "5 mm screws" can mean clearance holes for metal M5 screws, printed threaded pins, or a heat-set insert. Three different models.
- Existing hardware. If the part mates with something that already exists, its hole spacing and material thickness are constraints, not preferences.
Ask at most the one or two questions that change the geometry; assume the rest, state the assumption, and move.
2. Model with the library
scripts/partlib.py provides profile builders, extrude, threaded_rod,
threaded_pin, hex_nut, place, and finish. Import it rather than
re-deriving mesh math — the helical thread is easy to get subtly wrong.
scripts/example_handle.py is a complete worked part (bridge handle + integral
threaded pins + matching nut). Copy it as the skeleton for a new part.
Write the model as a parametric script with argparse flags, never hard-coded numbers. The user will ask for a variant within minutes, and the answer should be one command line rather than a new model. Hand back the script alongside the STL.
3. Common part recipes
| Part | Build from |
|---|---|
| Bridge handle, ⊓ bracket | bridge_profile + extrude |
| Flat plate with holes | shapely polygon with interior rings + extrude |
| Pin, stud, bolt | threaded_pin, then place onto its axis |
| Nut, threaded socket | hex_nut, or subtract threaded_rod(clearance=…) |
| Spacer, bushing | two concentric circles + extrude |
| Hook, L-bracket | polygon of the side view + extrude, flat on the bed |
| Enclosure | outer box minus inner box; lip with 0.15 mm clearance |
Anything not listed is still a polygon, an extrusion, and a boolean.
Image route
scripts/imagelib.py converts a picture into a height field and stitches an
explicit vertex-grid solid — top surface, flat bottom, four side walls. It uses
no boolean engine, which is exactly why it does not fall over on large or odd
images.
python3 imagelib.py photo.jpg --mode lithophane --width 100 \
--out stl_output/lithophane.stl
python3 imagelib.py logo.png --mode relief --width 80 --base 2 \
--out stl_output/relief.stl
| Mode | Mapping | Typical z |
|---|---|---|
relief | bright pixels rise | 0.6 → 4.0 mm, often over a --base plate |
lithophane | dark pixels get thick, so it glows when backlit | 0.8 → 3.0 mm, no base |
--ressets samples on the long edge (default 200). Resolution beyond about 0.4 mm per sample is wasted — the nozzle cannot print it, and the file balloons. Raise it only for large prints.- Contrast is auto-stretched on load; a flat, low-contrast photo produces a flat, disappointing print. Say so rather than printing it.
- Lithophanes want faces and high-contrast subjects. Landscapes and busy scenes turn to mud.
check()verifies that every edge appears exactly twice in opposite directions. Counting alone would pass a wall stitched backwards, which yields a "valid" STL that the slicer turns inside out. Run it, report it.
Finishing, for both routes
Verify first: finish() (geometry) and check() (image) report the bounding
box, watertightness and triangle count. Compare the bounding box against the
requested dimensions — it catches sign errors and unit slips instantly. Never
present a mesh that is not watertight.
For anything visually non-obvious — threads, curves, assemblies — render a quick matplotlib preview and look at it. Twenty seconds beats a six-hour print of the wrong shape.
Read references/design-rules.md before choosing orientation, thread pitch, or
clearances. It covers layer-direction strength, printable thread profiles, fit
tolerances, image-derived parts, and the failure modes worth naming.
The reply should contain:
- A dimensions table, including assumed values.
- Any decision made on the user's behalf, with its reason. If a requested feature is mechanically weak, build what they asked for and name the weakness with a concrete alternative — never silently substitute.
- A slicer table: layer height, walls, infill, supports (exactly where), material, and which face goes on the bed and why.
- The one-line command to regenerate with different numbers.
- Anything to confirm against existing hardware before printing.
Deliver STL files to /mnt/user-data/outputs/ on claude.ai, or to a
stl_output/ folder in the current working directory in Claude Code, and
present them together with the generator script. The scripts live in this
skill's scripts/ folder; call them by full path or copy them next to the
part script. On Windows use python instead of python3.
Multi-part assemblies
When parts mate (pin and nut, lid and box, hinge halves):
- generate both from shared parameters in one script — a clearance value living in two files will drift
- derive the female part from the same function as the male, offset by a clearance, so they cannot disagree
- state how many of each to print, and suggest a spare for the small one
- when a fit is uncertain, offer a tolerance test: print the small part at two clearances and keep the one that turns