first commit

This commit is contained in:
2026-07-22 13:48:46 +08:00
commit c87751c3dc
2820 changed files with 726976 additions and 0 deletions
@@ -0,0 +1,145 @@
# Expressions and Replay Behavior
SimpleCADAPI stores expression-backed parameters in two places:
1. `node.params`: numeric / JSON-compatible snapshot used by simple replay
2. `node.param_exprs`: references into the top-level `expression_graph`
This lets consumers choose between:
- pure geometric replay using only the numeric snapshots
- parameter-aware import using `param_exprs + expression_graph`
## Source example
```python
import simplecadapi as scad
width = scad.var("width", 24.0, comment="plate width")
height = scad.var("height", 12.0, comment="plate height")
thickness = scad.var("thickness", 4.0, comment="plate thickness")
with scad.GraphSession() as session:
plate = scad.make_box_rsolid(width, height, thickness)
rib = scad.make_box_rsolid(width / 4.0, height, thickness * 2.0)
part = scad.union_rsolid(plate, rib)
model_json = scad.export_model_json(session)
```
Because `make_box_rsolid(...)` lowers to profile + extrude nodes, the expressions appear on the lowered line/profile/extrude nodes rather than on a `make_box` node.
## Node-level JSON shape
A node with expression-backed params may look like:
```json
{
"op": "make_extrude_rsolid",
"params": {
"direction": [0.0, 0.0, 1.0],
"distance": 4.0
},
"param_exprs": {
"distance": {"expr_id": "var_thickness"}
},
"inputs": ["node_for_profile"],
"output_count": 1
}
```
`params.distance` is the evaluated snapshot. `param_exprs.distance` says the value came from expression node `var_thickness`.
For tuple/list params, `param_exprs` mirrors the shape of the parameter and uses `null` where no expression is present:
```json
{
"params": {
"start": [-12.0, -6.0, 0.0],
"end": [12.0, -6.0, 0.0]
},
"param_exprs": {
"start": [{"expr_id": "expr_a"}, {"expr_id": "expr_b"}, null],
"end": [{"expr_id": "expr_c"}, {"expr_id": "expr_b"}, null]
}
}
```
## Top-level expression graph
`payload["expression_graph"]` contains expression nodes for variables, constants, and arithmetic operations. The exact ids are stable within one exported payload but should not be treated as human-authored names.
Consumers that want parameterization should:
1. Build an expression table from `expression_graph.nodes`.
2. For each operation node, inspect `param_exprs`.
3. Replace or annotate corresponding numeric `params` entries with expression references.
4. Keep numeric `params` as fallback evaluated values.
Consumers that only want geometry can ignore `param_exprs` and `expression_graph`.
## Replay policy in current implementation
`replay_model_json(model_json)` currently uses the canonical low-level `graph` and the numeric values in `node.params`.
That means replay is deterministic with respect to the exported snapshot. It does not currently re-solve expressions with changed variable values.
In practical terms:
```python
width = scad.var("width", 24.0)
with scad.GraphSession() as session:
box = scad.make_box_rsolid(width, 10, 2)
payload = scad.export_model_json(session)
rebuilt = scad.replay_model_json(payload)
```
Replay rebuilds using width `24.0`, because that is the value stored in `params`.
## Expression metadata is still important
Even though replay uses snapshots today, `param_exprs` and `expression_graph` are important for external tools:
- FreeCAD or CAD translators can reconstruct spreadsheet bindings.
- UI tools can display which dimensions are driven by variables.
- Future parametric replay can use the same expression references.
- Diffs can distinguish numeric constants from expression-derived values.
## Leaf ids and replayed outputs
The top-level `leaf_ids` field determines which node outputs are returned by replay:
```json
{
"leaf_ids": ["node_final", "node_auxiliary"]
}
```
Replay behavior:
1. Execute every graph node in topological order.
2. Store each node output by `node_id`.
3. Return outputs for `leaf_ids` in order.
If an example creates many independent showcase shapes, `leaf_ids` may contain many node ids. This is expected: the graph is not required to have a single final part.
## Unsupported / lossy expression cases
- Python callables are not serialized as expressions.
- Some discrete selector data, topology refs, and counts are intentionally treated as JSON data rather than scalar expressions.
## Practical inspection snippet
```python
import json
payload = json.loads(model_json)
for node in payload["graph"]["nodes"]:
if node.get("param_exprs"):
print(node["node_id"], node["op"])
print(" params:", node["params"])
print(" param_exprs:", node["param_exprs"])
```
Use this to show the source-to-JSON relationship for expression-backed dimensions.