The live-update loop spun forever and ignored window close, so the viewport window couldn't be dismissed. Now it detects closure (close flag via q/Escape key events, plotter._closed, or render_window going away) and breaks the loop, then calls plotter.close() to tear down cleanly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| scripts | ||
| src/woodshop | ||
| tests | ||
| .gitignore | ||
| CLAUDE.md | ||
| README.md | ||
| pyproject.toml | ||
README.md
WoodShop
Voice-driven conversational 3D woodworking & furniture modeler.
Talk to it like the Star Trek holodeck and watch furniture build itself:
"Place a 6 foot 2x4, sand it, then attach a 2 foot 2x4 at 90 degrees, 10 inches from the end."
"Build a coffee table: a four foot by two foot frame from 2x4s, with four legs 18 inches tall standing at the corners."
Each board is real dimensional lumber (a 2x4 is modeled at its true 1.5″ × 3.5″), so the result is buildable — export to STEP (CAD/CNC) or STL (3D print), and get a cut list with board-feet and a shopping estimate.
How it works
WoodShop reuses the existing CmdForge tool ecosystem for everything that isn't woodworking-specific, so no wheels are reinvented:
woodshop-talk ── the conversational loop
│ dictate ............. speech → text (CmdForge tool)
│ pa-load-tools ....... wood-* → Claude schemas (CmdForge tool)
│ claude -p ........... interpret → tool calls (provider)
│ pa-execute-tool ..... dispatch each wood-* (CmdForge tool)
│ read-aloud .......... speak confirmation (CmdForge tool)
▼
scene.json ← single source of truth (parts, joints, selection, undo)
▲ │ writes
│ reads/mutates ▼
wood-* CmdForge tools woodshop-view
(place/join/stand/move/...) live pyvista 3D, watches scene.json
The wood-* tools are thin wrappers over the woodshop CLI, so the modeling
logic lives in one place and the tools double as the LLM's documented command
vocabulary.
Installation
python -m venv .venv && source .venv/bin/activate
pip install -e ".[viewer,dev]" # 'viewer' pulls build123d + pyvista
python scripts/gen_wood_tools.py # register the wood-* CmdForge tools
Usage
woodshop-view & # live 3D window (watches the scene)
woodshop-talk # type commands; --voice to speak them
woodshop-talk --once "build a workbench top from five 2x6 boards 6 feet long"
Or drive it directly from the CLI:
woodshop place 2x4 "6 ft" # place a board
woodshop stand # stand it up (a leg)
woodshop join p2 --to p1 --angle 90 --offset "10 in"
woodshop rename "front-left leg"
woodshop cutlist # bill of materials
woodshop export table.step # STEP / STL export
woodshop save "coffee table" # named projects
woodshop open "coffee table"
Run woodshop --help for the full command list (place, join, stand, lay,
rotate, move, trim, copy, rename, sand, delete, undo, clear, status, cutlist,
export, save, open, projects).
The active scene lives at $WOODSHOP_SCENE or
~/.local/share/woodshop/scene.json; named projects in
~/.local/share/woodshop/projects/.
Development
pytest # 41 tests
Key modules:
| Module | Role |
|---|---|
scene.py |
Part/Joint/Scene model, operations, undo, persistence |
lumber.py |
nominal → actual dimensional lumber table |
units.py |
parse "6 ft" / "3 ft 6 in" / "-2 ft" → inches |
cli.py |
the woodshop command |
geometry.py |
build123d solids + STL/STEP export |
cutlist.py |
cut list, board-feet, shopping estimate |
viewer.py |
live pyvista 3D viewport (woodshop-view) |
driver.py |
conversational loop (woodshop-talk) |
scripts/gen_wood_tools.py |
(re)generate the wood-* CmdForge tools |
Known limitations
- Joins are butt joints (B's end sits flush against A's face, not its centerline); joinery cuts (mortise/tenon, lap, pocket holes) aren't modeled yet.
- Command interpretation latency is ~7–13s per utterance (one
claude -pcall).
License
MIT