feat: integrate SimpleCADAPI 2.0.2 CAD workflows

This commit is contained in:
Jerry
2026-08-03 11:17:05 +08:00
parent b5738e9109
commit c3a0f269b7
481 changed files with 110229 additions and 12826 deletions
+30 -12
View File
@@ -10,14 +10,19 @@ The long-form schema reference remains [`../operation_graph_json_spec.md`](../op
import json
import simplecadapi as scad
with scad.GraphSession() as session:
body = scad.make_box_rsolid(10, 6, 2)
hole = scad.make_cylinder_rsolid(1, 4, bottom_face_center=(0, 0, -1))
@scad.model(graph_id="drilled_block")
def build_model():
body = scad.make_box_rsolid(width=10, height=6, depth=2)
hole = scad.make_cylinder_rsolid(
radius=1, height=4, bottom_face_center=(0, 0, -1)
)
result = scad.cut_rsolid(body, hole)
scad.capture_result(value=result)
return result
model_json = scad.export_model_json(session)
payload = json.loads(model_json)
rebuilt = scad.replay_model_json(model_json)
model = build_model()
payload = json.loads(model.model_json)
rebuilt = model.replay()
```
Inspect these fields:
@@ -29,6 +34,17 @@ Inspect these fields:
- `node["inputs"]`: upstream node ids used by replay.
- `payload["leaf_ids"]`: explicit final result node ids.
- `payload["expression_graph"]`: expression DAG used by expression-backed parameters.
- `payload["tolerance_graph"]`: dimension-chain requirements and validation evidence.
For new top-level models, `ModelResult.model_json` is the preferred artifact
accessor. Use `@scad.requires_session` for reusable builders and
`scad.capture_result(...)` when the final output should not be inferred from
all graph leaves. If a model invocation also needs durable CAD/viewer files,
pass `export_dir=...` to `@scad.model`; its captured geometry/product values
then produce one self-contained `<graph_id>.scene.zip`. It embeds
`model/model.json`, mapped project-relative Python sources, and the evaluated
render/selection assets. It does not create adjacent model/session JSON, STEP,
STL, or FCStd files. No files are written when `export_dir` is omitted.
## Important rule: source API is not always graph API
@@ -52,11 +68,13 @@ Many user-facing functions are convenience APIs. During an active `GraphSession`
- [Primitive and profile operations](primitives-and-profiles.md)
- [Features, booleans, transforms, patterns, and selectors](features-booleans-transforms.md)
- [Expressions and replay behavior](expressions-and-replay.md)
- [Physical units and dimension inference](../physical-units.md)
- [Dimension tolerance chains](../dimension-tolerance-chains.md)
## Example
## Examples
See [`../../../examples/07_serialization_operation_tree.py`](../../../examples/07_serialization_operation_tree.py). It intentionally exercises every canonical core operation and writes:
- `examples/out/serialization_operation_tree.model.json`
- `examples/out/serialization_operation_tree.summary.md`
- `examples/out/serialization_operation_tree.step`
The retained examples use the same model/session contract. See
[`../../../examples/08_constrained_sketch.py`](../../../examples/08_constrained_sketch.py)
for sketch promotion and replay, and
[`../../../examples/10_part_assembly.py`](../../../examples/10_part_assembly.py)
for product hierarchy and automatic artifact export.
@@ -15,14 +15,15 @@ This lets consumers choose between:
```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")
width = scad.var("width", 24.0, unit="mm", comment="plate width", tolerance=0.1)
height = scad.var("height", 12.0, unit="mm", comment="plate height", tolerance=0.1)
thickness = scad.var("thickness", 4.0, unit="mm", comment="plate thickness", tolerance=(-0.05, 0.1))
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)
session.require_tolerance(width + height, 0.2, tolerance_unit="mm", name="plate_envelope")
model_json = scad.export_model_json(session)
```
@@ -48,7 +49,9 @@ A node with expression-backed params may look like:
}
```
`params.distance` is the evaluated snapshot. `param_exprs.distance` says the value came from expression node `var_thickness`.
`params.distance` is the evaluated canonical snapshot. Unit-aware lengths are
stored in millimeters and angles in degrees. `param_exprs.distance` says the value
came from expression node `var_thickness` and preserves its declaration metadata.
For tuple/list params, `param_exprs` mirrors the shape of the parameter and uses `null` where no expression is present:
@@ -78,12 +81,24 @@ Consumers that want parameterization should:
Consumers that only want geometry can ignore `param_exprs` and `expression_graph`.
Variable nodes may contain `unit`, `tolerance`, and `tolerance_unit`. Registered
units use string symbols; custom units use `{symbol, dimension,
scale_to_canonical}` objects. Import reconstructs the expression graph and reruns
dimension inference rather than trusting external dimension claims.
Session/model payloads store derived-dimension requirements in `tolerance_graph`.
See [Physical Units](../physical-units.md) and [Dimension Tolerance
Chains](../dimension-tolerance-chains.md) for inference, propagation, and
validation semantics.
## 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.
Replay does validate stored tolerance requirements before rebuilding the nominal geometry. A failing tolerance chain blocks replay, but passing bounds do not cause replay to sample or regenerate limit geometry.
In practical terms:
```python
@@ -122,6 +122,40 @@ Replay effect:
2. Replay path wire from input 1.
3. Call `sweep_rsolid(profile, path, is_frenet=...)`.
## Twisted Sweep
Source:
```python
profile = scad.make_rectangle_rface(width=2.0, height=1.0)
solid = scad.twisted_sweep_rsolid(
profile=profile,
distance=8.0,
twist_angle=30.0,
)
```
Serialized node:
```json
{
"op": "make_twisted_sweep_rsolid",
"params": {
"axis": [0.0, 0.0, 1.0],
"origin": [0.0, 0.0, 0.0],
"distance": 8.0,
"twist_angle": 30.0,
"guide_radius": 1.0
},
"inputs": ["node_for_profile_face"],
"output_count": 1
}
```
Replay reconstructs the continuous auxiliary-spine rotation law from the
recorded parameters and invokes `twisted_sweep_rsolid(...)`. No sampled loft
sections are stored or inferred.
## Helical sweep macro lowering
Source: