Files

9.8 KiB

Spatial Lab

An agent-operated mathematical geometry workbench for designing intricate architectural structures before building the final environment in Blender. The editable source is a construction graph of curves, transported frames, sections, surfaces and transforms. The browser observes the current design; authoring happens through Python or CLI transactions.

Working loop: inspect → edit parameters or graph → observe → validate → export → update Blender → inspect again. A project remains editable throughout this loop, with persistent undo/redo. Nothing depends on reconstructing shapes from an image.

Run

Clone this repository with Git LFS to retrieve the Blender example:

git clone https://github.com/emil28092005/spatial-lab.git
cd spatial-lab
git lfs pull

Python 3.11+ and a browser with WebGL2 are required. There are no runtime Python dependencies, CDN assets or accounts to configure. From this directory:

./spatial-lab serve examples/braided_atrium.spatial.json --port 8767

Open http://127.0.0.1:8767. The launcher also works by absolute path from another working directory. python3 -m spatial_lab is equivalent when run from this directory. Optional installation: python3 -m pip install -e . provides the spatial-lab command.

The viewer automatically rebuilds on source changes. Drag to orbit, Shift-drag to pan, scroll to zoom. Use Top/Front/Side, Fit, Surface/Wire, node selection, Isolate and Section Z to inspect the structure. Viewer controls never edit geometry. Invalid source displays an error and labels the previous valid view as stale. The HTTP server binds only to localhost and exposes no write API.

Iterative authoring

./spatial-lab init out/my-study.json --example simple
./spatial-lab inspect out/my-study.json
./spatial-lab param out/my-study.json height 12
./spatial-lab undo out/my-study.json
./spatial-lab redo out/my-study.json
./spatial-lab build out/my-study.json --out out/my-study.bundle.json
./spatial-lab check out/my-study.bundle.json
./spatial-lab ops

Use init --example atrium for the larger demo. Existing files are never overwritten by init. Command output is JSON; validation failures return exit code 2.

For several related changes, write a JSON array and apply it with ./spatial-lab patch PROJECT changes.json:

[
  {"action":"set_parameter","name":"width","value":1.8},
  {"action":"patch_node","id":"braided-galleries","patch":{"params":{"count":2}}}
]

The entire graph must build successfully before any edit is saved. upsert_node adds/replaces an operation; patch_node merges its params and inputs; remove_node rejects a node with dependents unless cascade:true. node PROJECT node.json and remove PROJECT ID --cascade are CLI shortcuts. Persistent history retains 64 revisions beside the project in .spatial_lab/. Direct external JSON edits start a new undo chain. Keep source projects in Git for long-term history.

Python uses the same transaction layer:

from spatial_lab import Project

project = Project("examples/braided_atrium.spatial.json")
bundle = project.apply([
    {"action": "set_parameter", "name": "width", "value": 1.8},
])
project.travel("undo")

Construction vocabulary

All built-in operators currently use version 1. Node IDs remain stable during edits. Inputs reference other node IDs; graph order is resolved automatically. Set visible:false on intermediate nodes to avoid exporting duplicate construction geometry. Hidden nodes still evaluate and feed downstream operations.

Operator Inputs Main parameters
curve xyz: three expressions in t; domain (default [0,"tau"]); segments (128); closed (false); optional max_chord_error in metres
polyline points: 3D vectors; closed (false); do not repeat the first point
frames path up ([0,0,1]); twist_degrees (0); parallel transport with closed-loop seam correction
sweep frames profile: a simple closed 2D polygon in local X/Z; cap (true)
surface xyz in u,v; u_domain,v_domain ([0,1]); u_segments (32), v_segments (16); wrap_u,wrap_v (false); thickness (0)
loft sections: list of curves Corresponding closed contours with equal point counts; cap (true), requiring planar ends
transform source translate, rotate (XYZ Euler degrees), scale (positive scalar or 3D vector); curve or mesh input
repeat source: mesh count (2); translate,rotate,scale define a cumulative step; instances share geometry
merge sources: list of meshes Groups parts without performing a boolean union

Numeric fields accept arithmetic expressions using project parameters, pi, tau, e, trigonometry and basic math functions. Global parameters are constants: they cannot refer to one another. Formula evaluation uses a bounded arithmetic AST, not Python eval. Local frames use X=right, Y=tangent, Z=up. A closed frame loop requires twist to be a multiple of 360°. Positive-scale transforms preserve orientation. Cumulative repetition applies the step matrix repeatedly, so translation can rotate or scale between instances.

Curves have explicit sampling resolution. estimated_chord_error_m measures deviation at interval midpoints; it is an estimate, not a certified maximum or adaptive subdivision. Inspect it and increase segments when needed. Thin shells offset sampled normals; large offsets can intersect themselves.

Exact Blender handoff

./spatial-lab build examples/braided_atrium.spatial.json --out out/braided_atrium.bundle.json
blender -b --factory-startup --python-exit-code 1 --python blender/bridge.py -- \
  --bundle out/braided_atrium.bundle.json \
  --save out/Braided_Atrium.blend --report out/blender_verification.json

For a live Blender session (including through Blender MCP), load the bridge once, select the intended dedicated scene, then repeat import_bundle after rebuilding:

import importlib.util
from pathlib import Path

lab = Path("/absolute/path/to/spatial-lab")
spec = importlib.util.spec_from_file_location("spatial_bridge", lab / "blender/bridge.py")
bridge = importlib.util.module_from_spec(spec)
spec.loader.exec_module(bridge)
scene = bridge.create_scene("Spatial study")  # Reuse this scene on subsequent imports.
bridge.import_bundle(lab / "out/braided_atrium.bundle.json", scene)
report = bridge.verify_bundle(lab / "out/braided_atrium.bundle.json", scene)
assert report["passed"], report

The importer updates objects by stable ID and prunes only obsolete objects tagged with this project ID in the supplied scene. Existing object identity, modifiers and object-level material assignments survive updates. It replaces the owned base mesh and transforms: do final base-mesh editing on a separate copy or after the mathematical design is finished. Per-face authored material assignments are not preserved. Other scene objects are untouched. Imported guides are wire meshes and frame empties. Scene unit scale must be 1.

The JSON contract is documented in FORMAT.md. Vertices, triangle indices and transformation matrices are transferred directly. Verification compares base-mesh coordinates in local and world space, exact connectivity/winding, IDs and matrices; artist modifiers are excluded. The supplied demo was tested in Blender 5.2.2 LTS with maximum world-coordinate error below 0.000004 m. No manual reconstruction or random regeneration occurs during import.

Ready-made artifacts: Blender scene, technical bundle, verification report. Retrieve the .blend with Git LFS when cloning the repository.

Extend

examples/wave_extension.py demonstrates a composable curve-deformation operator:

./spatial-lab --plugin examples/wave_extension.py ops

Register a function with @register_operator(name, version=1, inputs=(...)). It receives (context, params, resolved_inputs) and returns a curve, frames or mesh entity. Use context.number, vec, count and expression to evaluate bounded numeric parameters. See built-ins in spatial_lab/operators.py for entity shapes. Load the same plugin for every authoring/build/viewer command using that operator. Blender can import the already evaluated bundle without the plugin. Plugins are explicitly trusted local Python code; project JSON cannot auto-load them.

Validation and current limits

python3 -m unittest discover -s tests -v
node --check spatial_lab/web/app.js
blender -b --factory-startup --python-exit-code 1 --python tests/blender_roundtrip.py

The Blender integration test changes geometry, removes an instance, checks artist-work preservation, undoes the transaction and verifies the restored bundle. It writes a report, bundle and .blend into out/. Kernel tests include analytical volumes, frame orthogonality, closed seams, concave caps, graph dependencies, safe expressions, deterministic hashes and transactional history. HTTP tests cover live updates and error handling.

Version 0.1 checks triangle degeneracy, edge incidence/orientation, expected boundaries, finite coordinates and transfer parity. It does not solve constraints, detect 3D self-intersections/collisions, prove walkability, perform solid booleans or implement portals/non-Euclidean runtime space. The braided atrium is a geometric study, not a finished playable level or cinematic scene. Those remain subsequent design and Blender/engine stages. Engine-specific adapters, UVs, textures and production detailing are deliberately outside the current mathematical handoff.

Runtime limits include 256 nodes, 4,096 exported objects, 200,000 vertices per mesh and 500,000 unique vertices per bundle. Large compositions should be split into projects. Design notes describe the architecture and viewer choices.