Scripting
BearCAD's Lua API is a first-class front end: everything achievable in the GUI is achievable by scripting, and vice versa — one model, two front ends.
The interpreter is sandboxed: no filesystem/network access beyond the explicit document/import/export/screenshot operations the API exposes.
Namespace split
- The primary API is declarative modeling, OpenSCAD-style, at the top level:
bearcad.new,bearcad.rect,bearcad.extrude,bearcad.add_constraint,bearcad.parameter,bearcad.select, …. - All GUI manipulation — simulated mouse/keyboard, camera, tools, panes, the
palette — lives under
bearcad.ui.*:bearcad.ui.click,bearcad.ui.key,bearcad.ui.orbit,bearcad.ui.tool,bearcad.ui.screenshot, ….
Prefer the declarative API; reach for bearcad.ui.* only when the UI interaction itself
is the point.
-- Declarative (preferred): describe the geometry directly.
bearcad.new()
bearcad.rect{ width = 80, height = 50, name = "Main box" }
-- Simulated interaction (bearcad.ui.*): only when the interaction matters.
bearcad.ui.tool("rectangle")
bearcad.ui.click_ground(0, 0)
bearcad.ui.move_ground(80, 50)
bearcad.ui.key("enter")
Running a script
--script (or a bare .lua path) runs a script; --exit closes the app when it
finishes:
cargo run -- --script examples/rectangle.lua --exit
# equivalent:
cargo run -- examples/rectangle.lua --exit
Once installed as bearcad on your PATH (Help → Install "bearcad" Command in PATH,
or bearcad install-cli):
bearcad --script examples/rectangle.lua --exit
Both the desktop and browser apps run a script interactively through File → Load
Script…. The browser runs the full modeling API; the bearcad.ui.* simulation verbs run
in the desktop app.
Other flags:
--timeout <seconds>— force-exit (non-zero) if the app hasn't closed in time.--show-commands— echo GUI actions asbearcad.*calls on stdout. Help → Export Session Commands… does the same into a replayable.luafile.--tutorial <name>— start a tutorial on launch. The browser app takes it as a URL parameter:?tutorial=bracket.
Interactive REPL
bearcad --repl runs the same Lua API on stdin against the live app — the GUI stays
usable while you type:
$ bearcad --repl
bearcad> x = 15
bearcad> bearcad.rect{ width = x * 2, height = x }
bearcad> 1 + 2
3
bearcad> bearcad.save("drawing.bearcad")
Semantics match the standalone lua interpreter: globals persist between entries, bare
expressions echo their value, errors print and the session continues, multi-line
constructs buffer under a ...> prompt, and yielding calls (bearcad.ui.wait,
screenshots) work. Ctrl-D ends the session; with --exit it also closes the app.
--repl and --script are mutually exclusive. Piping works:
echo '...' | bearcad --repl --exit.
Import shorthand
bearcad.import() copies the top-level modeling functions into the global namespace
(bearcad.ui.* stays namespaced):
bearcad.import()
new()
rect{ width = 80, height = 50 }
Coroutines and waiting
Scripts run in a coroutine. Calls that wait for a frame or animation —
bearcad.ui.wait, bearcad.ui.wait_ms, bearcad.ui.screenshot, bearcad.ui.view —
yield until the next frame rather than blocking.
Gizmos
Viewport drag handles are scriptable — each gizmo is one scalar:
-- What gizmos does the current tool state expose?
for _, g in ipairs(bearcad.gizmos()) do
print(g.kind, g.name, g.value) -- e.g. "push_pull" "extrude" 7.0
end
bearcad.set_gizmo{ name = "extrude", value = 15 } -- set the depth outright
bearcad.drag_gizmo{ name = "extrude", by = 5 } -- nudge it (mirrors a drag delta)
Lengths are in millimetres, angles in radians. Gizmos today: "extrude",
"chamfer"/"fillet", "revolve", "offset" (construction plane), the Move tool's
"move_x"/"move_y"/"move_z", and "text_width" (a selected
wrapped text's box width).
Where to go next
- Declarative modeling — worked examples: sketch, draw, extrude, export.
- The
bearcad.ui.*namespace — camera, panes, the palette, synthetic input. - Point-level selection — selecting a single vertex, for scripted constraint authoring.
- First-person mode — walking, flying, and scale, from a script.