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
@@ -0,0 +1,17 @@
# Dimension
## Class Definition
```python
class Dimension(length: int = 0, angle: int = 0)
```
*Source: units.py*
## Import Surface
- top-level: `from simplecadapi import Dimension`
## Description
Physical dimension represented by integer length and angle exponents.
@@ -0,0 +1,20 @@
# DimensionTolerance
## Class Definition
```python
class DimensionTolerance(lower_deviation: float, upper_deviation: float)
```
*Source: expr.py*
## Import Surface
- top-level: `from simplecadapi import DimensionTolerance`
## Description
Permitted lower and upper deviations from a nominal dimension.
Deviations are signed: the lower deviation must be less than or equal to
zero and the upper deviation must be greater than or equal to zero.
@@ -21,6 +21,7 @@ Current design goals:
- Translate only from the canonical low-level `graph` IR
- Preserve node metadata and graph lineage as FreeCAD custom properties
- Preserve `expression_graph` as explicit translator metadata
- Preserve dimension tolerances and tolerance-chain requirements as metadata
- Preserve exported assembly constraints as document metadata objects
- Keep assembly metadata from the full model payload alongside the IR-driven
geometry translation
@@ -16,16 +16,14 @@ class GraphSession(graph_id: Optional[str] = None)
Context manager that records CAD operations into a DAG.
with GraphSession() as session:
n1 = record_operation(
"make_line_redge", {"start": (0, 0, 0), "end": (1, 0, 0)}
)
n2 = record_operation(
"make_line_redge", {"start": (1, 0, 0), "end": (1, 1, 0)}
)
record_operation(
"make_wire_from_edges_rwire", {"edge_count": 2}, inputs=[n1, n2]
)
For new replayable model entry points, prefer `@scad.model`, which owns the
session and returns a `ModelResult`. Use `GraphSession` directly for lower-level
graph workflows. `result_node_ids` reports nodes selected by `capture_result`:
# Access the graph after the session
print(session.graph.topological_order())
```python
with GraphSession(graph_id="demo") as session:
body = make_box_rsolid(width=10.0, height=6.0, depth=2.0)
session.capture_result(value=body)
print(session.result_node_ids)
```
@@ -0,0 +1,39 @@
# ModelResult
## Class Definition
```python
@dataclass(frozen=True)
class ModelResult:
value: Any
session: GraphSession
result_node_ids: Tuple[str, ...]
model_json: str
session_json: str
artifact_paths: Mapping[str, Path] = field(default_factory=dict)
```
*Source: graph.py*
## Import Surface
- top-level: `from simplecadapi import ModelResult`
## Description
Immutable result returned by a function decorated with `@scad.model`.
It contains the ordinary Python return value, the completed graph session, the
explicit result node ids, the model/session JSON artifacts, and any paths
written by automatic artifact export.
```python
result = build_model()
rebuilt = result.replay()
```
`replay()` replays `result.model_json`. Use `result.value` for the application
return value and `result.result_node_ids` to inspect the captured graph outputs.
Use `result.export_artifacts(output_dir=...)` to write one self-contained
`<graph_id>.scene.zip` after the model runs. The package embeds model JSON,
mapped project-relative Python sources, and render/selection assets; automatic
export does not write adjacent model/session JSON, STEP, STL, or FCStd files.
@@ -65,14 +65,38 @@ This index includes generated docs for the public SimpleCAD API surface, includi
- [loft_rsolid](loft_rsolid.md) *(from operations.py)* `top-level`
- [revolve_rsolid](revolve_rsolid.md) *(from operations.py)* `top-level`
- [sweep_rsolid](sweep_rsolid.md) *(from operations.py)* `top-level`
- [twisted_sweep_rsolid](twisted_sweep_rsolid.md) *(from operations.py)* `top-level`
## Tagging and Selection
- [apply_tag](apply_tag.md) *(from operations.py)* `top-level`
- [apply_tag_rselection](apply_tag_rselection.md) *(from operations.py)* `top-level`
- [explain_tag](explain_tag.md) *(from operations.py)* `top-level`
- [list_tags](list_tags.md) *(from operations.py)* `top-level`
- [select_edges_by_tag](select_edges_by_tag.md) *(from operations.py)* `top-level`
- [select_faces_by_tag](select_faces_by_tag.md) *(from operations.py)* `top-level`
Creation-time topology-identity tags are supported by profile constructors and
native feature primitives through `tag_prefix`. QL selectors also support `shared_boundary(...)`,
`intersection(...)`, `incident_to(...)`, `incident_face_count(...)`, and
`solids()` for relation-aware Edge selection.
## Unified Tag Contract
Topology identity and user semantics share one public tag model. Every binding
is inspected with `list_tags(...)` and `explain_tag(...)` and queried with
`ql.tag(...)`; one topology object may carry several tags for different uses.
`tag_prefix="housing"` creates topology-identity tags such as
`housing.face.top` and `housing.solid`. These bindings carry `topology_name`
evidence and project only when kernel history proves exact correspondence.
Role parameters such as `top_face_tag`, `side_faces_tag`, and
`generated_faces_tag`, plus `result_tag`, create tags whose evidence identifies
the kernel-proven role or result. Their tag text alone does not establish
topology identity. There is one public tag parameter per target role, not a
generic role-to-tag mapping.
## Boolean Operations
- [cut_rsolid](cut_rsolid.md) *(from operations.py)* `top-level`
@@ -98,6 +122,10 @@ This index includes generated docs for the public SimpleCAD API surface, includi
## Modeling Graph and Replay
- [GraphSession](GraphSession.md) *(from graph.py)* `top-level`
- [ModelResult](ModelResult.md) *(from graph.py)* `top-level`
- [capture_result](capture_result.md) *(from graph.py)* `top-level`
- [model](model.md) *(from graph.py)* `top-level`
- [requires_session](requires_session.md) *(from graph.py)* `top-level`
- [export_graph_json](export_graph_json.md) *(from serializer.py)* `top-level`
- [export_model_json](export_model_json.md) *(from serializer.py)* `top-level`
- [export_session_json](export_session_json.md) *(from serializer.py)* `top-level`
@@ -111,12 +139,34 @@ This index includes generated docs for the public SimpleCAD API surface, includi
## Expressions and Parameters
- [Const](Const.md) *(from expr.py)* `top-level`
- [DimensionTolerance](DimensionTolerance.md) *(from expr.py)* `top-level`
- [Expr](Expr.md) *(from expr.py)* `top-level`
- [ExpressionGraph](ExpressionGraph.md) *(from expr.py)* `top-level`
- [ToleranceAnalysis](ToleranceAnalysis.md) *(from tolerance.py)* `top-level`
- [ToleranceAnalysisError](ToleranceAnalysisError.md) *(from tolerance.py)* `top-level`
- [ToleranceCheck](ToleranceCheck.md) *(from tolerance.py)* `top-level`
- [ToleranceContribution](ToleranceContribution.md) *(from tolerance.py)* `top-level`
- [ToleranceGraph](ToleranceGraph.md) *(from tolerance.py)* `top-level`
- [ToleranceReport](ToleranceReport.md) *(from tolerance.py)* `top-level`
- [ToleranceRequirement](ToleranceRequirement.md) *(from tolerance.py)* `top-level`
- [ToleranceValidationError](ToleranceValidationError.md) *(from tolerance.py)* `top-level`
- [Var](Var.md) *(from expr.py)* `top-level`
- [analyze_tolerance](analyze_tolerance.md) *(from tolerance.py)* `top-level`
- [check_tolerance](check_tolerance.md) *(from tolerance.py)* `top-level`
- [const](const_function.md) *(from expr.py)* `top-level`
- [var](var_function.md) *(from expr.py)* `top-level`
## Physical Units
- [Dimension](Dimension.md) *(from units.py)* `top-level`
- [Unit](Unit.md) *(from units.py)* `top-level`
- [UnitValidationError](UnitValidationError.md) *(from units.py)* `top-level`
- [canonical_unit_for_dimension](canonical_unit_for_dimension.md) *(from units.py)* `top-level`
- [convert_value](convert_value.md) *(from units.py)* `top-level`
- [expression_uses_units](expression_uses_units.md) *(from units.py)* `top-level`
- [get_unit](get_unit.md) *(from units.py)* `top-level`
- [infer_dimension](infer_dimension.md) *(from units.py)* `top-level`
## Types and Errors
- [SimpleCADError](SimpleCADError.md) *(from errors.py)* `top-level`
@@ -207,11 +257,15 @@ This index includes generated docs for the public SimpleCAD API surface, includi
- [meta](meta.md) *(from ql.py)* `submodule:ql`
- [not_](not_.md) *(from ql.py)* `submodule:ql`
- [or_](or_.md) *(from ql.py)* `submodule:ql`
- [output_role](output_role.md) *(from ql.py)* `submodule:ql`
- [place_component_rassembly](place_component_rassembly.md) *(from operations.py)* `top-level`
- [radial_pattern_rsolidlist](radial_pattern_rsolidlist.md) *(from operations.py)* `top-level`
- [render_screenshot_rpath](render_screenshot_rpath.md) *(from operations.py)* `top-level`
- [select](select.md) *(from ql.py)* `submodule:ql`
- [solids](solids.md) *(from ql.py)* `submodule:ql`
- [solve_assembly_constraints_rassembly](solve_assembly_constraints_rassembly.md) *(from operations.py)* `top-level`
- [source_binding](source_binding.md) *(from ql.py)* `submodule:ql`
- [source_topology](source_topology.md) *(from ql.py)* `submodule:ql`
- [tag](tag.md) *(from ql.py)* `submodule:ql`
- [unground_component_rassembly](unground_component_rassembly.md) *(from operations.py)* `top-level`
- [value](value.md) *(from ql.py)* `submodule:ql`
@@ -22,3 +22,10 @@ Use `make_sketch_rsketch(...)`, `add_point_rsketch(...)`,
profiles. Public sketch construction APIs are functional and return an
updated `Sketch` document. The legacy `curves` constructor remains only for
reading already-built wire/edge containers.
Entity IDs are creation-time local identifiers. Constrained profile promotion uses
the ordered promotion map to bind each entity to exactly one generated Edge,
creating `sketch.<sketch-name>.entity.<entity-id>` and
`sketch.<sketch-name>.profile.<profile-id>` tags with `topology_name` evidence.
Downstream features may project these tags only when their kernel history proves exact
correspondence.
@@ -0,0 +1,17 @@
# ToleranceAnalysis
## Class Definition
```python
class ToleranceAnalysis(target_expr_id: str, method: ToleranceMethod, nominal: float, lower_bound: float, upper_bound: float, lower_deviation: float, upper_deviation: float, dimension: Dimension | None = None, unit: Unit | None = None, contributions: Tuple[ToleranceContribution, ...] = ())
```
*Source: tolerance.py*
## Import Surface
- top-level: `from simplecadapi import ToleranceAnalysis`
## Description
Nominal value and propagated limits for an expression.
@@ -0,0 +1,17 @@
# ToleranceAnalysisError
## Class Definition
```python
class ToleranceAnalysisError
```
*Source: tolerance.py*
## Import Surface
- top-level: `from simplecadapi import ToleranceAnalysisError`
## Description
Raised when a tolerance chain cannot be propagated safely.
@@ -0,0 +1,17 @@
# ToleranceCheck
## Class Definition
```python
class ToleranceCheck(requirement: ToleranceRequirement, analysis: ToleranceAnalysis, passed: bool, lower_margin: float, upper_margin: float)
```
*Source: tolerance.py*
## Import Surface
- top-level: `from simplecadapi import ToleranceCheck`
## Description
Validation result for one tolerance requirement.
@@ -0,0 +1,17 @@
# ToleranceContribution
## Class Definition
```python
class ToleranceContribution(variable_expr_id: str, variable_name: str, nominal: float, source_tolerance: DimensionTolerance, sensitivity: float | None, lower_deviation: float, upper_deviation: float, source_unit: Unit | None = None)
```
*Source: tolerance.py*
## Import Surface
- top-level: `from simplecadapi import ToleranceContribution`
## Description
One source dimension's propagated contribution to a result.
@@ -0,0 +1,17 @@
# ToleranceGraph
## Class Definition
```python
class ToleranceGraph(expression_graph: ExpressionGraph)
```
*Source: tolerance.py*
## Import Surface
- top-level: `from simplecadapi import ToleranceGraph`
## Description
Tolerance requirements attached to one expression graph.
@@ -0,0 +1,17 @@
# ToleranceReport
## Class Definition
```python
class ToleranceReport(checks: Tuple[ToleranceCheck, ...] = ())
```
*Source: tolerance.py*
## Import Surface
- top-level: `from simplecadapi import ToleranceReport`
## Description
Validation report for every requirement in a tolerance graph.
@@ -0,0 +1,17 @@
# ToleranceRequirement
## Class Definition
```python
class ToleranceRequirement(requirement_id: str, target_expr_id: str, tolerance: DimensionTolerance, method: ToleranceMethod = 'worst_case', name: str = '', tolerance_unit: Unit | None = None, target_dimension: Dimension | None = None)
```
*Source: tolerance.py*
## Import Surface
- top-level: `from simplecadapi import ToleranceRequirement`
## Description
Permitted result deviations for one derived dimension.
@@ -0,0 +1,17 @@
# ToleranceValidationError
## Class Definition
```python
class ToleranceValidationError(report: 'ToleranceReport')
```
*Source: tolerance.py*
## Import Surface
- top-level: `from simplecadapi import ToleranceValidationError`
## Description
Raised when one or more declared tolerance requirements fail.
@@ -0,0 +1,20 @@
# Unit
## Class Definition
```python
class Unit(symbol: str, dimension: Dimension, scale_to_canonical: float)
```
*Source: units.py*
## Import Surface
- top-level: `from simplecadapi import Unit`
## Description
Named unit with a scale to SimpleCAD's canonical numeric units.
Custom units are supported and serialize their symbol, dimension, and scale.
Registered built-in units serialize as compact symbols.
@@ -0,0 +1,17 @@
# UnitValidationError
## Class Definition
```python
class UnitValidationError
```
*Source: units.py*
## Import Surface
- top-level: `from simplecadapi import UnitValidationError`
## Description
Raised when units or expression dimensions are physically inconsistent.
@@ -3,7 +3,7 @@
## Class Definition
```python
class Var(name: str, default: float, comment: str | None = None, expr_id: str = field(default_factory=lambda : _make_expr_id('var')))
class Var(name: str, default: float, comment: str | None = None, expr_id: str = field(default_factory=lambda : _make_expr_id('var')), tolerance: DimensionTolerance | None = None, unit: Unit | None = None, tolerance_unit: Unit | None = None)
```
*Source: expr.py*
@@ -14,4 +14,8 @@ class Var(name: str, default: float, comment: str | None = None, expr_id: str =
## Description
Named scalar parameter with a default fallback value.
Named scalar parameter with optional physical-unit and tolerance intent.
``default`` and ``tolerance`` remain in their declared units. Evaluation,
geometry parameters, and tolerance propagation convert them to SimpleCAD's
canonical CAD units: millimeters for length and degrees for angle.
@@ -14,4 +14,6 @@ def add_line_rsketch(sketch: Sketch, entity_id: str, start: Union[SketchRef, str
## Description
Add a named line entity and return an updated sketch document.
Add an identified line entity and return an updated sketch document. Its
`entity_id` becomes the local segment of the canonical topology-identity tag for
the corresponding promoted profile Edge.
@@ -0,0 +1,27 @@
# analyze_tolerance
## API Definition
```python
def analyze_tolerance(value: ScalarLike, *, method: ToleranceMethod = 'worst_case') -> ToleranceAnalysis
```
*Source: tolerance.py*
## Import Surface
- top-level: `from simplecadapi import analyze_tolerance`
## Description
Propagate source manufacturing tolerances through a scalar expression.
``worst_case`` returns guaranteed interval bounds. Affine chains are
dependency-aware, so repeated variables such as ``x - x`` cancel exactly;
nonlinear chains use conservative interval arithmetic. ``rss`` performs a
first-order root-sum-square calculation using analytic sensitivities.
Unit-aware variables are converted to canonical CAD units before
propagation. The returned analysis reports the inferred physical dimension
and canonical result unit. Every variable in the expression must declare a
source tolerance.
@@ -14,10 +14,13 @@ def apply_tag(shape: AnyShape, tag: str) -> AnyShape
## Description
Attach a normalized tag to a shape using the standard propagation policy.
Attach a normalized local user tag to a shape.
Tags must already be normalized lowercase tokens such as
``role.mounting_surface`` or ``group.fasteners``. Propagation is intentionally
not configurable from the public API; the default tag policy propagates
semantic role/anchor/group tags downward and keeps topology-specific tags
local.
`role.mounting_surface` or `group.fasteners`. The default topology policy is
`local` for every tag; token prefixes do not imply downward propagation.
Lineage visibility is limited to proven continuation and fragment witnesses.
`apply_tag(...)` preserves its historical in-place wrapper behavior. Use
`apply_tag_rselection(...)` when you need an independent semantic shape view,
explicit topology propagation, or a replayable multi-entity assignment.
@@ -0,0 +1,36 @@
# apply_tag_rselection
## API Definition
```python
def apply_tag_rselection(
scope: AnyShape,
targets: Union[ShapeSelector, Sequence[AnyShape]],
tag: str,
topology_propagation: str | TopologyPropagation = TopologyPropagation.LOCAL,
lineage_policy: str | LineagePolicy = LineagePolicy.CONTINUATION_FRAGMENT,
) -> AnyShape
```
*Source: operations.py*
## Import Surface
- top-level: `from simplecadapi import apply_tag_rselection`
## Description
Return an independent semantic view over the same geometry with one canonical
`TagBinding` attached to the selected entities. `targets` may be a serializable
QL `ShapeSelector` or a non-empty sequence of topology objects belonging to
`scope`.
Topology propagation defaults to `local`. Set `topology_propagation="downward"`
only when descendants should inherit the binding. Lineage defaults to proven
continuation and fragment derivations; it never makes lineage part of the
`effective` scope.
Inside `GraphSession`, the operation records the complete binding, target intent,
and selected-reference evidence. Replay re-resolves the target and checks that
the evidence has not drifted. The semantic node does not replace geometry-owned
topology references.
@@ -0,0 +1,19 @@
# canonical_unit_for_dimension
## API Definition
```python
def canonical_unit_for_dimension(dimension: Dimension) -> Unit
```
*Source: units.py*
## Import Surface
- top-level: `from simplecadapi import canonical_unit_for_dimension`
## Description
Return the canonical unit used by CAD and tolerance calculations.
Length, area, volume, and angle use ``mm``, ``mm^2``, ``mm^3``, and ``deg``.
@@ -0,0 +1,30 @@
# capture_result
## API Definition
```python
def capture_result(*, value: Any) -> Any
```
*Source: graph.py*
## Import Surface
- top-level: `from simplecadapi import capture_result`
## Description
Mark the graph nodes represented by `value` as explicit final outputs of the
active model session. The value is returned unchanged. Explicit capture keeps
intermediate/debug graph leaves out of `ModelResult.model_json`. Captured
geometry/product values are also the inputs to automatic artifact export when
the model decorator receives `export_dir=...`.
```python
@scad.model(graph_id="demo")
def build_demo():
body = scad.make_box_rsolid(width=10.0, height=4.0, depth=2.0)
return scad.capture_result(value=body)
```
Call it inside `@model` or another active `GraphSession`.
@@ -3,7 +3,14 @@
## API Definition
```python
def chamfer_rsolid(solid: Solid, edges: Union[Sequence[Edge], ShapeSelector], distance: ScalarLike) -> Solid
def chamfer_rsolid(
solid: Solid,
edges: Union[Sequence[Edge], ShapeSelector],
distance: ScalarLike,
*,
result_tag: Optional[str] = None,
generated_faces_tag: Optional[str] = None,
) -> Solid
```
*Source: operations.py*
@@ -14,4 +21,10 @@ def chamfer_rsolid(solid: Solid, edges: Union[Sequence[Edge], ShapeSelector], di
## Description
Apply chamfers to selected solid edges.
Apply chamfers to selected solid edges. `generated_faces_tag` targets every face
with the kernel-proven `chamfer.patch` role. OCC contour expansion is included
rather than treating only the seed edge as the feature boundary.
The operation fails if a requested patch role has no proven result. `result_tag`
tags the resulting solid, and graph recording lowers assignments to replayable
semantic nodes.
@@ -0,0 +1,22 @@
# check_tolerance
## API Definition
```python
def check_tolerance(value: ScalarLike, tolerance: ToleranceLike, *, method: ToleranceMethod = 'worst_case', name: str | None = None, tolerance_unit: UnitLike | None = None) -> ToleranceCheck
```
*Source: tolerance.py*
## Import Surface
- top-level: `from simplecadapi import check_tolerance`
## Description
Propagate and verify one Length or Angle requirement.
``tolerance_unit`` defaults to the target dimension's canonical unit. When
provided, it must be dimensionally compatible and is converted before the
comparison. Legacy unitless requirements remain supported when no unit is
supplied.
@@ -0,0 +1,17 @@
# convert_value
## API Definition
```python
def convert_value(value: int | float, from_unit: UnitLike, to_unit: UnitLike) -> float
```
*Source: units.py*
## Import Surface
- top-level: `from simplecadapi import convert_value`
## Description
Convert a finite numeric value between dimensionally compatible units.
@@ -3,7 +3,11 @@
## API Definition
```python
def cut_rsolid(*solids: Union[Solid, Sequence[Solid]], skip_non_intersecting: bool = True) -> Solid
def cut_rsolid(
*solids: Union[Solid, Sequence[Solid]],
skip_non_intersecting: bool = True,
tracking_policy: TrackingPolicy | str = TrackingPolicy.FULL,
) -> Solid
```
*Source: operations.py*
@@ -29,6 +33,10 @@ sequences, and returns a single `Solid`.
- **Description**: When True, tools with no meaningful intersection are ignored for interactive convenience. Graph replay records this flag and should use False for strict diagnostic workflows.
### tracking_policy
- **Description**: `TrackingPolicy.FULL` computes topology history and lineage. `TrackingPolicy.GRAPH` preserves the canonical cut node, parameters, inputs, result topology references, and replay while omitting `TopoDelta` and history-derived topology lineage. Intersection validation and `skip_non_intersecting` behavior are unchanged.
## Returns
Solid: The cut result solid.
@@ -0,0 +1,27 @@
# explain_tag
## API Definition
```python
def explain_tag(
shape: AnyShape,
tag: str,
scope: str | TagScope = TagScope.EFFECTIVE,
) -> List[Dict[str, Any]]
```
*Source: operations.py*
## Import Surface
- top-level: `from simplecadapi import explain_tag`
## Description
Return every visible canonical binding that produces `tag` in the requested
scope. Explanations preserve binding identity, producer, attachment, evidence,
and policy-allowed lineage witnesses, so equal tag tokens from different
producers remain distinguishable.
As with `list_tags(...)`, `effective` excludes lineage. A lineage explanation
requires complete topology-history coverage.
@@ -3,7 +3,12 @@
## API Definition
```python
def export_model_json(session: 'GraphSession', indent: int = 2) -> str
def export_model_json(
session: 'GraphSession',
indent: int = 2,
*,
result_node_ids: Optional[Sequence[str]] = None,
) -> str
```
*Source: serializer.py*
@@ -20,3 +25,8 @@ Current Phase 1 scope uses the active session as the container of:
- operation graph
- expression graph
- capabilities/schema metadata
When `result_node_ids` is omitted, explicitly captured session results are used
when available; otherwise export falls back to graph leaves. New top-level model
code normally reads `ModelResult.model_json` instead of calling this function
directly.
@@ -0,0 +1,17 @@
# expression_uses_units
## API Definition
```python
def expression_uses_units(value: 'ScalarLike') -> bool
```
*Source: units.py*
## Import Surface
- top-level: `from simplecadapi import expression_uses_units`
## Description
Return whether an expression contains an explicit unit declaration.
@@ -3,7 +3,17 @@
## API Definition
```python
def extrude_rsolid(profile: Union[Wire, Face], direction: Tuple[float, float, float], distance: ScalarLike) -> Solid
def extrude_rsolid(
profile: Union[Wire, Face],
direction: Tuple[float, float, float],
distance: ScalarLike,
*,
tag_prefix: Optional[str] = None,
result_tag: Optional[str] = None,
start_face_tag: Optional[str] = None,
end_face_tag: Optional[str] = None,
side_faces_tag: Optional[str] = None,
) -> Solid
```
*Source: operations.py*
@@ -14,4 +24,26 @@ def extrude_rsolid(profile: Union[Wire, Face], direction: Tuple[float, float, fl
## Description
Create a solid by extruding a profile.
Create a solid by extruding a profile. Kernel history assigns the output roles
`extrusion.start`, `extrusion.end`, and `extrusion.side`. Use the role tag
arguments to attach tags to those exact role sets.
Use creation-time profile topology tags (`tag_prefix` and `edge_tags`) when
durable feature Face/Edge identity is required. A tagged profile Edge yields a
corresponding `<tag_prefix>.face.side.<edge_tag>` when OCC proves the generated
side Face. Tagged Faces expose their effective tags to boundary Edge queries; use QL
`incident_to(..., distinct=True)` or `shared_boundary(...)` to disambiguate
an Edge by its two neighboring Faces.
Start and end roles require exactly one proven face. The side role requires one
or more proven faces and tags all of them. Missing, ambiguous, or unsupported
roles fail the whole operation instead of returning an untagged result.
`result_tag` attaches a local tag to the resulting solid. In a `GraphSession`,
all requested tags lower to replayable `apply_tag_rselection` semantic nodes;
they are not stored as geometry parameters.
Topology tags produced by `tag_prefix` remain directly queryable after
booleans when OCC provides a complete preserved or modified Face history. The
result binding retains the original semantic binding ID and source topology ID;
new boundary faces do not inherit the topology tag.
@@ -3,7 +3,14 @@
## API Definition
```python
def fillet_rsolid(solid: Solid, edges: Union[Sequence[Edge], ShapeSelector], radius: ScalarLike) -> Solid
def fillet_rsolid(
solid: Solid,
edges: Union[Sequence[Edge], ShapeSelector],
radius: ScalarLike,
*,
result_tag: Optional[str] = None,
generated_faces_tag: Optional[str] = None,
) -> Solid
```
*Source: operations.py*
@@ -14,4 +21,10 @@ def fillet_rsolid(solid: Solid, edges: Union[Sequence[Edge], ShapeSelector], rad
## Description
Apply fillets to selected solid edges.
Apply fillets to selected solid edges. `generated_faces_tag` targets every face
with the kernel-proven `fillet.patch` role. OCC contour expansion is included,
so the role is not limited to the original seed edge.
The operation fails if a requested patch role has no proven result. `result_tag`
tags the resulting solid. In a `GraphSession`, assignments are separate replayable
semantic nodes with asserted user provenance.
@@ -0,0 +1,17 @@
# get_unit
## API Definition
```python
def get_unit(value: UnitLike) -> Unit
```
*Source: units.py*
## Import Surface
- top-level: `from simplecadapi import get_unit`
## Description
Resolve a built-in unit name/alias or return an existing ``Unit``.
@@ -0,0 +1,21 @@
# infer_dimension
## API Definition
```python
def infer_dimension(value: 'ScalarLike') -> Dimension | None
```
*Source: units.py*
## Import Surface
- top-level: `from simplecadapi import infer_dimension`
## Description
Infer and validate an expression's result dimension.
``None`` means the expression uses only legacy variables without unit
declarations. Expressions that contain explicit units are validated
strictly and cannot mix in legacy variables.
@@ -3,7 +3,10 @@
## API Definition
```python
def list_tags(shape: AnyShape) -> List[str]
def list_tags(
shape: AnyShape,
scope: str | TagScope = TagScope.EFFECTIVE,
) -> List[str]
```
*Source: operations.py*
@@ -14,4 +17,12 @@ def list_tags(shape: AnyShape) -> List[str]
## Description
Return shape tags in deterministic sorted order.
Return shape tags in deterministic sorted order for one semantic scope.
- `local`: bindings attached directly to the entity.
- `inherited`: bindings visible through explicit downward topology propagation.
- `effective`: local plus inherited bindings. Lineage is not included.
- `lineage`: bindings visible through complete, policy-allowed topology history.
Lineage queries fail with a semantic capability error when complete topology
history is unavailable; they do not guess from geometry or enumeration order.
@@ -3,7 +3,17 @@
## API Definition
```python
def loft_rsolid(profiles: List[Wire], ruled: bool = False) -> Solid
def loft_rsolid(
profiles: List[Wire],
ruled: bool = False,
*,
tracking_policy: TrackingPolicy | str = TrackingPolicy.FULL,
tag_prefix: Optional[str] = None,
result_tag: Optional[str] = None,
start_face_tag: Optional[str] = None,
end_face_tag: Optional[str] = None,
side_faces_tag: Optional[str] = None,
) -> Solid
```
*Source: operations.py*
@@ -14,4 +24,13 @@ def loft_rsolid(profiles: List[Wire], ruled: bool = False) -> Solid
## Description
Create a solid by lofting multiple profiles.
Create a solid by lofting multiple profiles. Kernel history assigns
`loft.start`, `loft.end`, and `loft.side` roles. Start and end tags require one
proven face each; side tags apply to all proven side faces. `result_tag` targets
the solid. Recorded assignments are replayable semantic nodes.
`TrackingPolicy.FULL` is the default and preserves complete kernel topology
history. `TrackingPolicy.GRAPH` skips topology-history queries while still
recording and replaying the `make_loft_rsolid` graph node. In `GRAPH` mode,
`result_tag` remains available, but face-role tags and `tag_prefix` require
`FULL` tracking.
@@ -3,7 +3,21 @@
## API Definition
```python
def make_box_rsolid(width: ScalarLike, height: ScalarLike, depth: ScalarLike, bottom_face_center: Tuple[float, float, float] = (0, 0, 0)) -> Solid
def make_box_rsolid(
width: ScalarLike,
height: ScalarLike,
depth: ScalarLike,
bottom_face_center: Tuple[float, float, float] = (0, 0, 0),
*,
tag_prefix: Optional[str] = None,
result_tag: Optional[str] = None,
bottom_face_tag: Optional[str] = None,
top_face_tag: Optional[str] = None,
front_face_tag: Optional[str] = None,
back_face_tag: Optional[str] = None,
left_face_tag: Optional[str] = None,
right_face_tag: Optional[str] = None,
) -> Solid
```
*Source: operations.py*
@@ -14,4 +28,12 @@ def make_box_rsolid(width: ScalarLike, height: ScalarLike, depth: ScalarLike, bo
## Description
Create a box solid.
Create a native box with six exact kernel-backed Face roles: `box.bottom`,
`box.top`, `box.front`, `box.back`, `box.left`, and `box.right`.
`tag_prefix="housing"` creates `housing.solid` and corresponding
`housing.face.<role>` topology-identity tags. Use the face tag arguments for
role tags and `result_tag` for the Solid.
Box Edge roles are unsupported because the current OCP builder does not expose
equivalent direct Edge witnesses. Select an exact Edge from two tagged incident
Faces with QL `incident_to(..., distinct=True)` or `shared_boundary(...)`.
@@ -3,7 +3,13 @@
## API Definition
```python
def make_circle_redge(center: Tuple[float, float, float], radius: ScalarLike, normal: Tuple[float, float, float] = (0, 0, 1)) -> Edge
def make_circle_redge(
center: Tuple[float, float, float],
radius: ScalarLike,
normal: Tuple[float, float, float] = (0, 0, 1),
*,
tag_prefix: Optional[str] = None,
) -> Edge
```
*Source: operations.py*
@@ -14,4 +20,5 @@ def make_circle_redge(center: Tuple[float, float, float], radius: ScalarLike, no
## Description
Create a circular edge.
Create a circular edge. `tag_prefix` optionally creates the topology tag
`<tag_prefix>.edge`.
@@ -3,7 +3,14 @@
## API Definition
```python
def make_circle_rface(center: Tuple[float, float, float], radius: ScalarLike, normal: Tuple[float, float, float] = (0, 0, 1)) -> Face
def make_circle_rface(
center: Tuple[float, float, float],
radius: ScalarLike,
normal: Tuple[float, float, float] = (0, 0, 1),
*,
tag_prefix: Optional[str] = None,
edge_tag: Optional[str] = None,
) -> Face
```
*Source: operations.py*
@@ -14,4 +21,8 @@ def make_circle_rface(center: Tuple[float, float, float], radius: ScalarLike, no
## Description
Create a circular face.
Create a circular face. `tag_prefix` creates `<tag_prefix>.face`, while
`edge_tag` supplies the final segment of `<tag_prefix>.edge.<edge_tag>` for its
boundary Edge, or the complete Edge tag when `tag_prefix` is omitted. The Face
topology tag is visible to effective boundary-Edge QL queries without copying
arbitrary local Face tags to every Edge.
@@ -3,7 +3,14 @@
## API Definition
```python
def make_circle_rwire(center: Tuple[float, float, float], radius: ScalarLike, normal: Tuple[float, float, float] = (0, 0, 1)) -> Wire
def make_circle_rwire(
center: Tuple[float, float, float],
radius: ScalarLike,
normal: Tuple[float, float, float] = (0, 0, 1),
*,
tag_prefix: Optional[str] = None,
edge_tag: Optional[str] = None,
) -> Wire
```
*Source: operations.py*
@@ -14,4 +21,8 @@ def make_circle_rwire(center: Tuple[float, float, float], radius: ScalarLike, no
## Description
Create a circular wire.
Create a circular wire. `tag_prefix` creates `<tag_prefix>.wire`, while
`edge_tag` supplies the final segment of `<tag_prefix>.edge.<edge_tag>` for its
single circular Edge, or the complete Edge tag when `tag_prefix` is omitted.
These topology tags are preserved only where a downstream operation has
complete, kernel-proven correspondence.
@@ -3,7 +3,22 @@
## API Definition
```python
def make_cone_rsolid(bottom_radius: ScalarLike, height: ScalarLike, top_radius: ScalarLike = 0.0, bottom_face_center: Tuple[float, float, float] = (0, 0, 0), axis: Tuple[float, float, float] = (0, 0, 1)) -> Solid
def make_cone_rsolid(
bottom_radius: ScalarLike,
height: ScalarLike,
top_radius: ScalarLike = 0.0,
bottom_face_center: Tuple[float, float, float] = (0, 0, 0),
axis: Tuple[float, float, float] = (0, 0, 1),
*,
tag_prefix: Optional[str] = None,
result_tag: Optional[str] = None,
start_face_tag: Optional[str] = None,
end_face_tag: Optional[str] = None,
side_face_tag: Optional[str] = None,
start_edge_tag: Optional[str] = None,
end_edge_tag: Optional[str] = None,
seam_edge_tag: Optional[str] = None,
) -> Solid
```
*Source: operations.py*
@@ -14,4 +29,12 @@ def make_cone_rsolid(bottom_radius: ScalarLike, height: ScalarLike, top_radius:
## Description
Create a cone or truncated cone solid.
Create a native cone or frustum with direct kernel-backed `cone.start`,
`cone.end`, and `cone.side` Face roles and `cone.start_boundary`,
`cone.end_boundary`, and `cone.seam` Edge roles.
A pointed cone (`top_radius=0`) has no `cone.end` Face, so requesting that role
fails. Its `cone.end_boundary` is the kernel's degenerate apex Edge. A frustum
has all six roles. `tag_prefix="adapter"` creates corresponding
`adapter.face.*` and `adapter.edge.*` topology-identity tags; use the role tag
arguments for role assignments.
@@ -3,7 +3,21 @@
## API Definition
```python
def make_cylinder_rsolid(radius: ScalarLike, height: ScalarLike, bottom_face_center: Tuple[float, float, float] = (0, 0, 0), axis: Tuple[float, float, float] = (0, 0, 1)) -> Solid
def make_cylinder_rsolid(
radius: ScalarLike,
height: ScalarLike,
bottom_face_center: Tuple[float, float, float] = (0, 0, 0),
axis: Tuple[float, float, float] = (0, 0, 1),
*,
tag_prefix: Optional[str] = None,
result_tag: Optional[str] = None,
start_face_tag: Optional[str] = None,
end_face_tag: Optional[str] = None,
side_face_tag: Optional[str] = None,
start_edge_tag: Optional[str] = None,
end_edge_tag: Optional[str] = None,
seam_edge_tag: Optional[str] = None,
) -> Solid
```
*Source: operations.py*
@@ -14,4 +28,9 @@ def make_cylinder_rsolid(radius: ScalarLike, height: ScalarLike, bottom_face_cen
## Description
Create a cylinder solid.
Create a native cylinder with kernel-backed topology tags. `tag_prefix="shaft"` produces
`shaft.face.start`, `shaft.face.end`, `shaft.face.side`, and corresponding
`shaft.edge.start`, `shaft.edge.end`, and `shaft.edge.seam` tags. Face tags
are inherited by their boundary Edges. Role tags use the native
`cylinder.*` Face/Edge roles. Use QL `incident_to(..., distinct=True)` or
`shared_boundary(...)` to select an Edge from its two neighboring tagged Faces.
@@ -14,4 +14,12 @@ def make_face_from_sketch_rface(sketch: Sketch, profile: int | str = 0, *, requi
## Description
Promote a sketch profile to a concrete face, solving internally.
Promote a sketch profile to a concrete face, solving internally. The Face and
each boundary Edge receive canonical tags with `topology_name` evidence from the
exact Sketch promotion map. For a Sketch with `name="rect"`, profile `bottom`,
and entity ID `right`, the tags are `sketch.rect.profile.bottom` and
`sketch.rect.entity.right`.
The bindings replay from the Sketch payload and promotion parameters. Legacy
compatibility tags such as `sketch_entity.right` remain separate from the
canonical topology-identity tags.
@@ -3,7 +3,12 @@
## API Definition
```python
def make_face_from_wire_rface(wire: Wire, normal: Tuple[float, float, float] = (0, 0, 1)) -> Face
def make_face_from_wire_rface(
wire: Wire,
normal: Tuple[float, float, float] = (0, 0, 1),
*,
tag_prefix: Optional[str] = None,
) -> Face
```
*Source: operations.py*
@@ -14,4 +19,6 @@ def make_face_from_wire_rface(wire: Wire, normal: Tuple[float, float, float] = (
## Description
Create a face from a closed wire.
Create a face from a closed wire. Existing proven Edge topology tags on the
wire are copied to corresponding Face boundary Edges. `tag_prefix` optionally
adds the Face tag `<tag_prefix>.face`.
@@ -3,7 +3,12 @@
## API Definition
```python
def make_line_redge(start: Tuple[ScalarLike, ScalarLike, ScalarLike], end: Tuple[ScalarLike, ScalarLike, ScalarLike]) -> Edge
def make_line_redge(
start: Tuple[ScalarLike, ScalarLike, ScalarLike],
end: Tuple[ScalarLike, ScalarLike, ScalarLike],
*,
tag_prefix: Optional[str] = None,
) -> Edge
```
*Source: operations.py*
@@ -14,4 +19,7 @@ def make_line_redge(start: Tuple[ScalarLike, ScalarLike, ScalarLike], end: Tuple
## Description
Create a straight edge between two points.
Create a straight edge between two points. When `tag_prefix` is provided, the
edge receives the topology tag `<tag_prefix>.edge`. Downstream profile and
feature operations may preserve that tag when kernel history proves the
correspondence.
@@ -3,7 +3,15 @@
## API Definition
```python
def make_rectangle_rface(width: ScalarLike, height: ScalarLike, center: Tuple[ScalarLike, ScalarLike, ScalarLike] = (0, 0, 0), normal: Tuple[ScalarLike, ScalarLike, ScalarLike] = (0, 0, 1)) -> Face
def make_rectangle_rface(
width: ScalarLike,
height: ScalarLike,
center: Tuple[ScalarLike, ScalarLike, ScalarLike] = (0, 0, 0),
normal: Tuple[ScalarLike, ScalarLike, ScalarLike] = (0, 0, 1),
*,
tag_prefix: Optional[str] = None,
edge_tags: Optional[Sequence[str]] = None,
) -> Face
```
*Source: operations.py*
@@ -14,4 +22,9 @@ def make_rectangle_rface(width: ScalarLike, height: ScalarLike, center: Tuple[Sc
## Description
Create a rectangular face.
Create a rectangular face. `tag_prefix` creates `<tag_prefix>.face`, and
`edge_tags` supplies one tag for each of its four boundary Edges. With
`tag_prefix`, each is a local segment under `<tag_prefix>.edge`; without it,
each is a complete Edge tag. These topology tags can be projected to proven
feature Faces and queried with the same `list_tags(...)` and `ql.tag(...)`
surfaces as other tags.
@@ -3,7 +3,15 @@
## API Definition
```python
def make_rectangle_rwire(width: ScalarLike, height: ScalarLike, center: Tuple[ScalarLike, ScalarLike, ScalarLike] = (0, 0, 0), normal: Tuple[ScalarLike, ScalarLike, ScalarLike] = (0, 0, 1)) -> Wire
def make_rectangle_rwire(
width: ScalarLike,
height: ScalarLike,
center: Tuple[ScalarLike, ScalarLike, ScalarLike] = (0, 0, 0),
normal: Tuple[ScalarLike, ScalarLike, ScalarLike] = (0, 0, 1),
*,
tag_prefix: Optional[str] = None,
edge_tags: Optional[Sequence[str]] = None,
) -> Wire
```
*Source: operations.py*
@@ -14,4 +22,9 @@ def make_rectangle_rwire(width: ScalarLike, height: ScalarLike, center: Tuple[Sc
## Description
Create a rectangular wire.
Create a rectangular wire. `tag_prefix` creates `<tag_prefix>.wire`.
`edge_tags` must contain one tag for each generated profile Edge, in kernel
construction order. With `tag_prefix`, each value is the local segment of
`<tag_prefix>.edge.<edge_tag>`; without it, each value is the complete Edge tag.
These topology tags are stable anchors for operations such as `extrude_rsolid`
when correspondence is proven.
@@ -17,4 +17,5 @@ def make_sketch_rsketch(name: Optional[str] = None, *, plane: Any = 'XY', sketch
Create an empty declarative sketch document.
Use this API, not concrete edge/wire constructors, when the intent is to
build a sketch profile with constraints.
build a sketch profile with constraints. The sketch name and explicit entity
IDs are stable local identifiers used by constrained profile promotion.
@@ -3,7 +3,7 @@
## API Definition
```python
def make_wire_from_edges_rwire(edges: List[Edge]) -> Wire
def make_wire_from_edges_rwire(edges: List[Edge], *, tag_prefix: Optional[str] = None) -> Wire
```
*Source: operations.py*
@@ -14,4 +14,6 @@ def make_wire_from_edges_rwire(edges: List[Edge]) -> Wire
## Description
Create a wire from a list of connected edges.
Create a wire from a list of connected edges. Existing proven Edge topology
tags are preserved by exact topology identity; `tag_prefix` optionally adds
`<tag_prefix>.wire` to the resulting wire.
@@ -14,4 +14,10 @@ def make_wire_from_sketch_rwire(sketch: Sketch, profile: int | str = 0, *, requi
## Description
Promote a sketch profile to a concrete wire, solving internally.
Promote a sketch profile to a concrete wire, solving internally. The promotion
map preserves exact ordered entity-to-Edge correspondence and creates tags with
`topology_name` evidence, such as `sketch.rect.profile.bottom` and
`sketch.rect.entity.right`.
Promotion fails when the generated Edge count does not match the promotion map;
the SDK does not infer identity from geometry or enumeration heuristics.
@@ -0,0 +1,44 @@
# model
## API Definition
```python
def model(
func=None,
*,
graph_id: Optional[str] = None,
export_dir: Optional[str | Path] = None,
) -> Callable
```
*Source: graph.py*
## Import Surface
- top-level: `from simplecadapi import model`
## Description
Decorate the single top-level entry point of a replayable model. Each invocation
creates and owns exactly one `GraphSession`, activates it while the function
runs, captures model/session JSON in memory, and returns a `ModelResult`. When
`export_dir` is provided, explicitly captured geometry/product values produce
one `<graph_id>.scene.zip` in that directory. The package embeds model JSON,
mapped project-relative Python source files, and render/selection assets; it
does not create adjacent model/session JSON, STEP, STL, or FCStd files.
```python
@scad.model(graph_id="bracket")
def build_bracket():
body = scad.make_box_rsolid(width=20.0, height=10.0, depth=3.0)
scad.capture_result(value=body)
return body
```
Use `result.artifact_paths["scene"]` to locate the package, or call
`result.export_artifacts(output_dir=...)` after a model has run without an
export directory. The explicit `export_dir` opt-in avoids unexpected filesystem
writes for library callers and tests.
Do not nest `@model` functions or create another `GraphSession` inside a model
function. Use `@requires_session` for child builders.
@@ -0,0 +1,23 @@
# output_role
## API Definition
```python
def output_role(role_name: str) -> SerializablePredicate
```
*Source: ql.py*
## Import Surface
- submodule: `from simplecadapi import ql`
## Description
Return a serializable predicate matching a kernel-proven operation output role
in `metadata["track"]`. Role matching never falls back to face order, geometry,
or flat tags. Use it with a typed selector, for example:
```python
end = ql.faces().where(ql.output_role(role_name="extrusion.end")).exactly(1)
```
@@ -3,7 +3,7 @@
## API Definition
```python
def render_screenshot_rpath(shapes: Union[Solid, Sequence[Solid]], output_path: str, highlight_tags: Optional[Sequence[str]] = None, tag_labels: Optional[Dict[str, str]] = None, image_size: Tuple[int, int] = (1400, 900), view: Union[Tuple[float, float], str] = 'auto', show_axes: bool = True, show_legend: bool = True, zoom: float = 4.0) -> str
def render_screenshot_rpath(shapes: Union[Solid, Sequence[Solid]], output_path: str, highlight_tags: Optional[Sequence[str]] = None, tag_labels: Optional[Dict[str, str]] = None, image_size: Tuple[int, int] = (1400, 900), view: Union[Tuple[float, float], str] = 'auto', show_axes: bool = True, show_legend: bool = True, zoom: float = 4.0, show_callouts: bool = True) -> str
```
*Source: operations.py*
@@ -14,4 +14,6 @@ def render_screenshot_rpath(shapes: Union[Solid, Sequence[Solid]], output_path:
## Description
Render a screenshot of shapes and save it to a file.
Render a screenshot of shapes and save it to a file. Set `show_callouts=False`
to retain highlighted material colors and the legend without placing tag labels
over the model.
@@ -0,0 +1,22 @@
# requires_session
## API Definition
```python
def requires_session(func=None) -> Callable
```
*Source: graph.py*
## Import Surface
- top-level: `from simplecadapi import requires_session`
## Description
Decorate a reusable graph-producing builder that must run inside the caller's
active `GraphSession`. It reuses the session owned by the enclosing `@model`
function and validates returned graph values against that session.
Calling a `@requires_session` builder without an active session raises
`RuntimeError`. Builders must not create their own `GraphSession`.
@@ -3,7 +3,18 @@
## API Definition
```python
def revolve_rsolid(profile: Union[Wire, Face], axis: Tuple[float, float, float] = (0, 0, 1), angle: ScalarLike = 360, origin: Tuple[float, float, float] = (0, 0, 0)) -> Solid
def revolve_rsolid(
profile: Union[Wire, Face],
axis: Tuple[float, float, float] = (0, 0, 1),
angle: ScalarLike = 360,
origin: Tuple[float, float, float] = (0, 0, 0),
*,
tag_prefix: Optional[str] = None,
result_tag: Optional[str] = None,
start_face_tag: Optional[str] = None,
end_face_tag: Optional[str] = None,
side_faces_tag: Optional[str] = None,
) -> Solid
```
*Source: operations.py*
@@ -14,4 +25,10 @@ def revolve_rsolid(profile: Union[Wire, Face], axis: Tuple[float, float, float]
## Description
Create a solid by revolving a profile around an axis.
Create a solid by revolving a profile around an axis. Kernel history assigns
`revolution.start`, `revolution.end`, and `revolution.side` output roles.
Start and end roles require exactly one proven face; side tags apply to every
proven side face. A full 360-degree revolve normally has no separate start or end
face, so requesting either cap tag raises a capability error rather than guessing.
`result_tag` tags the resulting solid. Role tags are replayable semantic nodes.
@@ -15,3 +15,15 @@ def select(items: Iterable[Any]) -> Query
## Description
Start a QL query over a shape collection or selector scope.
For topology-aware selection, use `ql.faces()`, `ql.edges()`, `ql.wires()`,
`ql.vertices()`, or `ql.solids()`. `ShapeSelector.intersection(other)` forms a
serializable set intersection. `selector.shared_boundary(other,
to_kind="edge")` intersects the boundaries of two selectors. Edge selectors
also support `incident_to(face_selector, ..., distinct=True)` and
`incident_face_count(exactly=2)` to select edges by exact incident Face
witnesses and reject open or non-manifold edges.
These selectors resolve by topology identity, not enumeration order, area,
normal, or position heuristics. Their `to_dict()` payloads can be restored with
`ql.selector_from_dict(...)`.
@@ -3,7 +3,11 @@
## API Definition
```python
def select_edges_by_tag(shape: Union[Face, Solid], tag: str) -> List[Edge]
def select_edges_by_tag(
shape: Union[Face, Solid],
tag: str,
scope: str | TagScope = TagScope.EFFECTIVE,
) -> List[Edge]
```
*Source: operations.py*
@@ -14,4 +18,6 @@ def select_edges_by_tag(shape: Union[Face, Solid], tag: str) -> List[Edge]
## Description
Select edges by tag.
Select edges by an exact normalized tag in the requested semantic scope.
`effective` does not include lineage; request `scope="lineage"` explicitly when
selection depends on complete topology-history evidence.
@@ -3,7 +3,11 @@
## API Definition
```python
def select_faces_by_tag(solid: Solid, tag: str) -> List[Face]
def select_faces_by_tag(
solid: Solid,
tag: str,
scope: str | TagScope = TagScope.EFFECTIVE,
) -> List[Face]
```
*Source: operations.py*
@@ -14,4 +18,6 @@ def select_faces_by_tag(solid: Solid, tag: str) -> List[Face]
## Description
Select faces by tag.
Select faces by an exact normalized tag in the requested semantic scope.
`effective` does not include lineage; request `scope="lineage"` explicitly when
selection depends on complete topology-history evidence.
@@ -3,7 +3,17 @@
## API Definition
```python
def shell_rsolid(solid: Solid, faces_to_remove: Union[Sequence[Face], ShapeSelector], thickness: ScalarLike) -> Solid
def shell_rsolid(
solid: Solid,
faces_to_remove: Union[Sequence[Face], ShapeSelector],
thickness: ScalarLike,
*,
result_tag: Optional[str] = None,
body_faces_tag: Optional[str] = None,
offset_faces_tag: Optional[str] = None,
closing_faces_tag: Optional[str] = None,
wall_edges_tag: Optional[str] = None,
) -> Solid
```
*Source: operations.py*
@@ -14,4 +24,16 @@ def shell_rsolid(solid: Solid, faces_to_remove: Union[Sequence[Face], ShapeSelec
## Description
Shell a solid to create a hollow part.
Shell a solid to create a hollow part. The operation can expose these exact
kernel roles:
- `shell.body_face`: surviving or modified source body faces.
- `shell.offset_face`: generated offset faces.
- `shell.closing_descendant`: descendants of removed closing faces.
- `shell.wall`: generated closing-boundary edges.
The named arguments map directly to those roles. A role is available only when
OCC provides a complete witness;
requesting an unavailable role fails instead of deriving one from enumeration or
geometry. `result_tag` tags the resulting solid. Recorded assignments replay as
semantic nodes and preserve face versus edge target kinds.
@@ -0,0 +1,19 @@
# solids
## API Definition
```python
def solids() -> ShapeSelector
```
*Source: ql.py*
## Import Surface
- submodule: `from simplecadapi import ql`
## Description
Create a serializable selector over Solid topology. Solid selectors can be
traversed to boundary Edges and combined with `intersection(...)` or
`shared_boundary(...)` to find topology common to two named Solid selectors.
@@ -0,0 +1,19 @@
# source_binding
## API Definition
```python
def source_binding(binding_id: str) -> SerializablePredicate
```
*Source: ql.py*
## Import Surface
- submodule: `from simplecadapi import ql`
## Description
Match a local projected `TagBinding` whose topology-change evidence preserves the
exact source `binding_id`. Objects without canonical local binding evidence raise
an unsupported-query capability error instead of consulting flat tags.
@@ -0,0 +1,19 @@
# source_topology
## API Definition
```python
def source_topology(topo_id: str) -> SerializablePredicate
```
*Source: ql.py*
## Import Surface
- submodule: `from simplecadapi import ql`
## Description
Match a local projected `TagBinding` by the exact source topology identity stored
in its kernel-history evidence. This predicate queries source-preserving evidence;
it does not infer ancestry from geometry.
@@ -3,7 +3,17 @@
## API Definition
```python
def sweep_rsolid(profile: Face, path: Wire, is_frenet: bool = False) -> Solid
def sweep_rsolid(
profile: Face,
path: Wire,
is_frenet: bool = False,
*,
tag_prefix: Optional[str] = None,
result_tag: Optional[str] = None,
start_face_tag: Optional[str] = None,
end_face_tag: Optional[str] = None,
side_faces_tag: Optional[str] = None,
) -> Solid
```
*Source: operations.py*
@@ -14,4 +24,10 @@ def sweep_rsolid(profile: Face, path: Wire, is_frenet: bool = False) -> Solid
## Description
Create a solid by sweeping a profile along a path.
Create a solid by sweeping a profile along a path. Kernel history assigns
`sweep.start`, `sweep.end`, and `sweep.side` roles. Start and end tags require one
proven face each; side tags apply to all proven side faces. `result_tag` targets
the solid, and recorded assignments are replayable semantic nodes.
Profiles with inner wires are rejected because the current PipeShell operation
receives only the outer wire; silently dropping profile holes is not allowed.
@@ -0,0 +1,46 @@
# twisted_sweep_rsolid
## API Definition
```python
def twisted_sweep_rsolid(
profile: Face,
distance: ScalarLike,
twist_angle: ScalarLike,
axis: Tuple[float, float, float] = (0.0, 0.0, 1.0),
origin: Tuple[float, float, float] = (0.0, 0.0, 0.0),
*,
guide_radius: ScalarLike = 1.0,
tag_prefix: Optional[str] = None,
result_tag: Optional[str] = None,
start_face_tag: Optional[str] = None,
end_face_tag: Optional[str] = None,
side_faces_tag: Optional[str] = None,
) -> Solid
```
*Source: operations.py*
## Import Surface
- top-level: `from simplecadapi import twisted_sweep_rsolid`
## Description
Sweep a planar profile along a straight axis while rotating it linearly by the
signed total `twist_angle` in degrees. The profile must lie at the sweep start,
be planar, and be normal to `axis`.
The OCP implementation uses a one-edge straight spine and a one-edge
cylindrical auxiliary spine. This normally creates one continuous side face per
profile edge rather than splitting every side at intermediate loft sections.
Profiles with inner wires are rejected.
Kernel history assigns `twisted_sweep.start`, `twisted_sweep.end`, and
`twisted_sweep.side` roles. The operation records one canonical
`make_twisted_sweep_rsolid` graph node containing `axis`, `origin`, `distance`,
`twist_angle`, and `guide_radius`; strict replay invokes the same public
operation with the recorded parameters.
`guide_radius` controls only the auxiliary orientation guide and must be a
positive finite value. It does not set the swept profile radius.
@@ -3,7 +3,13 @@
## API Definition
```python
def union_rsolid(*solids: Union[Solid, Sequence[Solid]], clean: bool = True, glue: bool = _DEFAULT_UNION_GLUE, tol: Optional[float] = None) -> Solid
def union_rsolid(
*solids: Union[Solid, Sequence[Solid]],
clean: bool = True,
glue: bool = _DEFAULT_UNION_GLUE,
tol: Optional[float] = None,
tracking_policy: TrackingPolicy | str = TrackingPolicy.FULL,
) -> Solid
```
*Source: operations.py*
@@ -40,6 +46,10 @@ returning multiple pieces.
- **Type**: `Optional fuzzy-boolean tolerance used by the OCC union kernel. When`
- **Description**: omitted, SimpleCAD chooses a conservative scale-aware tolerance.
### tracking_policy
- **Description**: `TrackingPolicy.FULL` computes topology history and lineage. `TrackingPolicy.GRAPH` preserves the canonical union node, parameters, inputs, result topology references, and replay while omitting `TopoDelta` and history-derived topology lineage. Geometry options `clean`, `glue`, and `tol` are unchanged.
## Returns
Solid: The merged union result.
@@ -3,7 +3,7 @@
## API Definition
```python
def var(name: str, default: int | float, comment: str | None = None) -> Var
def var(name: str, default: int | float, comment: str | None = None, tolerance: ToleranceLike | None = None, *, unit: UnitLike | None = None, tolerance_unit: UnitLike | None = None) -> Var
```
*Source: expr.py*
@@ -14,4 +14,13 @@ def var(name: str, default: int | float, comment: str | None = None) -> Var
## Description
Create a named variable node for v2 expression-driven parameters.
Create a physical or legacy scalar variable.
``tolerance=0.1`` declares a symmetric ``+/-0.1`` tolerance. Use a
``(lower_deviation, upper_deviation)`` pair for an asymmetric tolerance.
``tolerance_unit`` defaults to ``unit`` when a nominal unit is declared.
Values are converted to canonical CAD units only when evaluated, so the
declaration and serialized expression node preserve the user's units.
Variables without ``unit`` retain legacy unitless behavior. A unit-aware
expression cannot mix declared-unit variables with legacy variables.
@@ -33,7 +33,7 @@ SimpleCADAPI 的顶层 API 选择是正确的:它没有把用户拖进传统 C
- historical scalar-field/SDF implementation, now removed from the active public/support surface
- `docs/core/serialization/`
- `docs/core/operation_graph_json_spec.md`
- `examples/07_serialization_operation_tree.py`
- retained replayable examples under `examples/`
- `test/` 与 `tests/`
## 顶层 API 评价
@@ -46,7 +46,7 @@ SimpleCADAPI 的顶层 API 选择是正确的:它没有把用户拖进传统 C
- `__init__.py` 明确导出建模函数、核心类型、graph/session、serializer、expression 和 `ql` 子模块,公开面可见性好。参考:`src/simplecadapi/__init__.py`。
- 大多数核心创建函数遵守返回类型后缀,例如 `make_point_rvertex -> Vertex`、`make_line_redge -> Edge`、`make_circle_rwire -> Wire`、`make_box_rsolid -> Solid`。参考:`src/simplecadapi/operations.py:691`、`src/simplecadapi/operations.py:723`、`src/simplecadapi/operations.py:1465`。
- composite convenience API 在 `GraphSession` 内降低为 canonical low-level graph,这个设计非常正确。用户调用 `make_box_rsolid`,图里记录 rectangle/profile/extrude,这是 CAD DSL 和 replay IR 分层的正确方式。参考:`src/simplecadapi/operations.py:1480`、`examples/07_serialization_operation_tree.py:52`、`examples/07_serialization_operation_tree.py:272`。
- composite convenience API 在 `GraphSession` 内降低为 canonical low-level graph,这个设计非常正确。用户调用 `make_box_rsolid`,图里记录 rectangle/profile/extrude,这是 CAD DSL 和 replay IR 分层的正确方式。参考:`src/simplecadapi/operations.py` 与 `test/test_serialization.py`。
- `union_rsolid(...)` 明确返回单个 `Solid`,失败就报错,不默默返回多个实体。这对机械 CAD 是正确的默认语义。参考:`src/simplecadapi/operations.py:2856`、`src/simplecadapi/operations.py:2876`。
- `GraphSession` + `export_model_json` + `replay_model_json` 的主线是对的。它把脚本建模提升为可交换、可 replay 的模型记录。参考:`src/simplecadapi/graph.py:44`、`src/simplecadapi/serializer.py:416`、`src/simplecadapi/serializer.py:732`。
- `ql` 被放到子模块而非全部塞进顶层,是正确的边界意识。参考:`src/simplecadapi/__init__.py`、`docs/api/README.md:5`。
@@ -154,7 +154,7 @@ QL 是架构上最应该继续投资的部分。
- `OperationGraph` 是 DAG,节点保存 op、params、param_exprs、inputs、context、semantic_delta、topo_delta、tags。结构完整。参考:`src/simplecadapi/topology.py:318`、`src/simplecadapi/topology.py:476`。
- `export_model_json` 输出 graph、leaf_ids、expression_graph、frame_graph、registries、delta logs、canonical contract。方向很对。参考:`src/simplecadapi/serializer.py:627`。
- `_assert_graph_is_canonical` 在 export model 前拒绝非 canonical ops,这是保持 IR 干净的正确动作。参考:`src/simplecadapi/serializer.py:246`、`src/simplecadapi/serializer.py:624`。
- `examples/07_serialization_operation_tree.py` 非常有价值。它把 source API 到 operation tree 的映射讲清楚,是 SDK 最该保留和扩展的示例。参考:`examples/07_serialization_operation_tree.py:1`、`examples/07_serialization_operation_tree.py:217`。
- `test/test_serialization.py` 系统覆盖 source API 到 operation tree 的映射,是该契约的主要回归入口。
严重问题:
@@ -164,7 +164,7 @@ QL 是架构上最应该继续投资的部分。
- graph schema version 是 `1.0`,model schema 是 `2.0-draft`,canonical contract 是 `2.0-final-state`。这三个版本信号混在一起,会让外部消费者不知道哪个才是稳定承诺。参考:`src/simplecadapi/topology.py:19`、`src/simplecadapi/serializer.py:221`、`src/simplecadapi/serializer.py:628`。
- node.context 和 frame_graph 被记录,但 replay 不恢复 frame/workplane;所有 ops 按当前 ambient world context 执行。参考:`src/simplecadapi/graph.py:132`、`src/simplecadapi/serializer.py:991`。
- `leaf_ids` 自动来自 graph leaves,而不是用户显式声明的 final outputs。debug/intermediate 独立节点会成为 replay 输出。参考:`src/simplecadapi/serializer.py:624`、`src/simplecadapi/topology.py:563`。
- expression replay 是 numeric snapshot,不是 parametric replay。这个可以接受,但必须在 SDK 主文档里反复强调。参考:`examples/07_serialization_operation_tree.py:40`、`examples/07_serialization_operation_tree.py:287`。
- expression replay 是 numeric snapshot,不是 parametric replay。这个可以接受,但必须在 SDK 主文档里反复强调。参考:`test/test_serialization.py`。
建议:replay 应该默认 strict。所有 missing input、missing param、unknown op、leaf missing output、selection cardinality mismatch 都应该 hard failure。需要宽松模式时显式 `strict=False`。
@@ -62,7 +62,7 @@ SimpleWorkplane ← local modeling context
- **Shape-first API**: users work with `Vertex`, `Edge`, `Wire`, `Face`, and `Solid`, not graph nodes.
- **Functional modeling style**: public operations return new geometry values, e.g. `make_box_rsolid(...)`, `cut_rsolid(...)`, `fillet_rsolid(...)`.
- **OCP-native runtime**: geometry construction, topology traversal, properties, booleans, transforms, and export use OCP/OpenCascade helpers.
- **Replayable graph workflows**: `GraphSession` can record a canonical low-level operation graph and `export_model_json()` can serialize it for `replay_model_json()`.
- **Replayable graph workflows**: `@scad.model` owns one `GraphSession` and returns a `ModelResult`; `@scad.requires_session` composes child builders, and `scad.capture_result()` selects canonical output nodes for replay and export.
- **Tags and metadata**: tags are useful for lightweight semantics; structured numeric facts should be stored in metadata such as `metadata["geo"]`.
- **Indexed topology access**: use plural methods such as `get_edges()` and `get_faces()` for enumeration, and pass an index to the same getter, such as `get_edges(index)` or `get_faces(index)`, for intentional indexed picks that should become graph selection nodes.
@@ -74,11 +74,14 @@ import simplecadapi as scad
with scad.SimpleWorkplane(origin=(0, 0, 0)):
box = scad.make_box_rsolid(width=5, height=3, depth=2)
scad.apply_tag(box, "role.bracket")
scad.apply_tag(shape=box, tag="role.bracket")
box.set_metadata("material", "6061-T6")
box.auto_tag_faces("box")
top_faces = [face for face in box.get_faces() if "face.top" in scad.list_tags(face)]
top_faces = [
face for face in box.get_faces()
if "face.top" in scad.list_tags(shape=face)
]
print(len(top_faces))
```
@@ -87,13 +90,18 @@ print(len(top_faces))
```python
import simplecadapi as scad
with scad.GraphSession() as session:
body = scad.make_box_rsolid(10, 10, 4)
hole = scad.make_cylinder_rsolid(1.5, 8, bottom_face_center=(0, 0, -2))
@scad.model(graph_id="drilled_block")
def build_model():
body = scad.make_box_rsolid(width=10, height=10, depth=4)
hole = scad.make_cylinder_rsolid(
radius=1.5, height=8, bottom_face_center=(0, 0, -2)
)
part = scad.cut_rsolid(body, hole)
scad.capture_result(value=part)
return part
payload = scad.export_model_json(session)
rebuilt = scad.replay_model_json(payload)
result = build_model()
rebuilt = result.replay()
print(len(rebuilt))
```
@@ -0,0 +1,56 @@
# Dimension Tolerance Chains
SimpleCADAPI can attach manufacturing tolerances to declared dimension variables, propagate them through the expression DAG, and verify derived dimensions against design requirements.
This feature is separate from sketch-solver residual tolerances, boolean fuzzy tolerances, mesh resolution, and geometric fitting tolerances.
## Core Workflow
```python
import simplecadapi as scad
housing = scad.var("housing", 100.0, unit="mm", tolerance=0.15)
bearing = scad.var("bearing", 2.0, unit="cm", tolerance=(-0.04, 0.05), tolerance_unit="mm")
spacer = scad.var("spacer", 79.4, unit="mm", tolerance=0.05)
clearance = housing - bearing - spacer
worst_case = scad.analyze_tolerance(clearance, method="worst_case")
rss = scad.analyze_tolerance(clearance, method="rss")
with scad.GraphSession() as session:
body = scad.make_box_rsolid(housing, 10.0, 10.0)
session.require_tolerance(
clearance,
(-0.25, 0.24),
method="worst_case",
name="axial_clearance",
tolerance_unit="mm",
)
report = session.validate_tolerances(raise_on_failure=True)
model_json = scad.export_model_json(session)
```
## Rules
- A scalar tolerance is symmetric: `0.1` means `-0.1/+0.1`.
- A two-value sequence contains signed `(lower_deviation, upper_deviation)` values.
- Every variable in a tolerance chain must declare a finite tolerance.
- `tolerance_unit` defaults to `unit`; different nominal/tolerance units must
have the same dimension.
- Length and angle propagate in canonical `mm` and `deg` units.
- Expression dimensions are inferred and validated before propagation.
- Area and volume can be analyzed, while requirements currently accept final
Length and Angle results only.
- Tolerance sources are identified by `expr_id`, not by variable name.
- `worst_case` uses exact dependency-aware affine propagation and conservative nonlinear interval propagation.
- `rss` uses analytic first-order sensitivities, assumes distinct variables are independent, and combines repeated occurrences of the same variable before RSS.
- Both methods reject a chain that is undefined anywhere inside its declared source intervals.
- Export, import, replay, and FreeCAD translation validate stored requirements automatically.
- Legacy model JSON without `tolerance_graph` imports with an empty graph.
- Nominal replay continues to use numeric operation snapshots; tolerance analysis does not regenerate worst-case geometry.
- Import recomputes dimensions and rejects tampered unit or target-dimension data.
Use `check_tolerance()` for a standalone non-raising check. Use `GraphSession.require_tolerance()` when the requirement must be serialized and enforced.
See the source-checkout guide at `docs/core/dimension-tolerance-chains.md` for propagation details, failure conditions, JSON shapes, and FreeCAD behavior.
@@ -117,7 +117,7 @@ source API 名字不等于 canonical graph op 名字。Composite source API 可
```json
{
"schema_version": "2.0",
"producer_version": "2.0.1b1",
"producer_version": "2.0.2",
"capabilities": {
"selection_ref_strategies": true,
"geo_select_nodes": true,
@@ -334,7 +334,7 @@ source API 名字不等于 canonical graph op 名字。Composite source API 可
- `make_*_face` 与 `make_face_from_wire` -> `Sketch`
- `make_*_edge` / `make_*_wire` -> `Profile`
- `make_extrude_rsolid` / `make_revolve_rsolid` / `make_loft_rsolid` / `make_sweep_rsolid` / `make_fillet_rsolid` / `make_chamfer_rsolid` / `make_shell_rsolid` / `make_cut_rsolid` / `make_union_rsolid` / `make_intersect_rsolid` -> `Feature`
- `make_extrude_rsolid` / `make_revolve_rsolid` / `make_loft_rsolid` / `make_sweep_rsolid` / `make_twisted_sweep_rsolid` / `make_fillet_rsolid` / `make_chamfer_rsolid` / `make_shell_rsolid` / `make_cut_rsolid` / `make_union_rsolid` / `make_intersect_rsolid` -> `Feature`
## 7. Topology Delta Schema
@@ -936,6 +936,7 @@ New canonical profile nodes use the `make_*_r*` names listed in `canonical_contr
- `make_revolve_rsolid`
- `make_loft_rsolid`
- `make_sweep_rsolid`
- `make_twisted_sweep_rsolid`
- `make_translate_rshape`
- `make_rotate_rshape`
- `make_mirror_rshape`
@@ -1296,6 +1297,23 @@ Important:
| --- | --- | --- |
| `is_frenet` | `bool` | sweep orientation mode |
#### `make_twisted_sweep_rsolid`
- Inputs: 1 profile `Face`
- Outputs: 1 `Solid`
- Params:
| Key | Type | Meaning |
| --- | --- | --- |
| `axis` | 3-number array | sweep and rotation-axis direction in caller coordinates |
| `origin` | 3-number array | sweep start and a point on the rotation axis |
| `distance` | positive number | axial sweep distance |
| `twist_angle` | number | signed total rotation in degrees |
| `guide_radius` | positive number | internal auxiliary-spine radius |
Replay rebuilds the auxiliary spine deterministically inside the operation. It
does not infer section counts or lower to transient loft nodes.
### 14.6 Boolean Ops
#### `make_union_rsolid`
@@ -0,0 +1,45 @@
# Physical Units And Dimension Inference
Declare physical meaning on variables:
```python
width = scad.var(
"width",
1.0,
unit="in",
tolerance=0.1,
tolerance_unit="mm",
)
height = scad.var("height", 40.0, unit="mm", tolerance=0.2)
diagonal = scad.sqrt(width**2 + height**2)
```
## Canonical Rules
- Length, area, volume, and angle evaluate in `mm`, `mm^2`, `mm^3`, and `deg`.
- Declaration values remain available on `Var.default` and `Var.tolerance`.
- `tolerance_unit` defaults to `unit` and must have the same dimension.
- Bindings use the variable's declaration unit.
- Numeric constants are dimensionless coefficients; in addition/subtraction they
are contextual offsets in the canonical result unit.
## Inference Rules
- Addition and subtraction require matching dimensions.
- Multiplication/division add/subtract dimension exponents.
- Constant integer powers multiply exponents.
- Square root requires every exponent to be even.
- `sin`, `cos`, and `tan` require Angle and return Dimensionless.
- `asin`, `acos`, and `atan` require Dimensionless and return Angle.
- `atan2` requires matching input dimensions and returns Angle.
- Unit-declared variables cannot mix with legacy variables without units.
Built-in symbols are `1`, `%`, `mm`, `cm`, `m`, `in`, `ft`, their square/cubic
forms, `deg`, and `rad`. Use `get_unit()`, `convert_value()`, and
`infer_dimension()` for explicit inspection.
Area and volume can be inferred and analyzed. Persisted manufacturing requirements
currently require a final Length or Angle result.
Read the source-checkout `docs/core/physical-units.md` for custom-unit JSON,
complete operation tables, failure conditions, and legacy behavior.
@@ -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:
@@ -30,6 +35,16 @@ Inspect these fields:
- `payload["leaf_ids"]`: explicit final result node ids.
- `payload["expression_graph"]`: expression DAG used by expression-backed parameters.
For new top-level models, `ModelResult.model_json` is the preferred artifact
accessor. Use `@scad.requires_session` for reusable builders and
`scad.capture_result(...)` to keep intermediate graph leaves out of the model
outputs. 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 JSON, mapped
project-relative Python sources, and 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
Many user-facing functions are convenience APIs. During an active `GraphSession`, they lower to canonical low-level nodes:
@@ -53,10 +68,10 @@ Many user-facing functions are convenience APIs. During an active `GraphSession`
- [Features, booleans, transforms, patterns, and selectors](features-booleans-transforms.md)
- [Expressions and replay behavior](expressions-and-replay.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.
@@ -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:
@@ -2,21 +2,24 @@
## Overview
`TaggedMixin` is the internal tag and metadata storage mixin used by `Vertex`, `Edge`, `Wire`, `Face`, and `Solid`. It owns the shared `_tags`, `_metadata`, and `_runtime` stores for topology wrappers.
`TaggedMixin` is the internal semantic binding and metadata mixin used by topology wrappers. Canonical tag ownership is source-preserving `TagBinding` data. `_tags` is only an effective-scope compatibility cache; user code must not treat it as writable truth.
User code should not call member tag mutators. The public tag API is functional:
- `apply_tag(shape, tag)` attaches one normalized tag.
- `list_tags(shape)` returns tags in deterministic sorted order.
- `apply_tag_rselection(scope, targets, tag, ...)` returns an independent semantic view and exposes explicit propagation policies.
- `list_tags(shape, scope=...)` returns tags in deterministic sorted order.
- `explain_tag(shape, tag, scope=...)` preserves binding and producer evidence.
- `select_faces_by_tag(...)`, `select_edges_by_tag(...)`, and QL predicates such as `ql.tag("role.*")` provide selection/query helpers.
## Tagging Mental Model
- Tags are normalized lowercase dot-separated semantic tokens.
- Examples: `role.mounting_surface`, `anchor.datum.primary`, `group.fasteners`, `face.top`, `edge.boundary`, `wire.outer`, `solid.boolean.cut`.
- `apply_tag(shape, tag)` does not expose propagation controls.
- The standard policy propagates `role.*`, `anchor.*`, `group.*`, and a few legacy bare semantic tags downward.
- Topology-specific tags such as `face.*`, `edge.*`, `wire.*`, `vertex.*`, and `solid.*` stay local.
- New user assignments default to local topology propagation regardless of prefix.
- Downward inheritance is explicit and computed dynamically; bindings are not copied into every child.
- `effective` means local plus inherited and does not include lineage.
- `lineage` requires complete topology history and only follows derivations allowed by the binding policy.
- Numeric dimensions, measurements, and rich descriptive payloads belong in metadata, not tags.
- Geometry builders store structured geometry facts under `metadata["geo"]`.
@@ -34,16 +37,29 @@ top_faces = [face for face in box.get_faces() if "face.top" in scad.list_tags(fa
print(len(top_faces))
```
## Propagation Example
## Explicit Propagation Example
```python
import simplecadapi as scad
body = scad.make_box_rsolid(10, 10, 2)
scad.apply_tag(body, "role.mounting_plate")
body = scad.make_box_rsolid(width=10, height=10, depth=2)
tagged = scad.apply_tag_rselection(
scope=body,
targets=[body],
tag="role.mounting_plate",
topology_propagation=scad.TopologyPropagation.DOWNWARD,
)
face_hits = [face for face in body.get_faces() if "role.mounting_plate" in scad.list_tags(face)]
edge_hits = [edge for edge in body.get_edges() if "role.mounting_plate" in scad.list_tags(edge)]
face_hits = scad.select_faces_by_tag(
solid=tagged,
tag="role.mounting_plate",
scope=scad.TagScope.INHERITED,
)
edge_hits = scad.select_edges_by_tag(
shape=tagged,
tag="role.mounting_plate",
scope=scad.TagScope.INHERITED,
)
print(len(face_hits), len(edge_hits))
```
@@ -55,7 +71,13 @@ Primitives and modeling operations may attach normalized tags automatically:
- Primitive tags such as `geom.primitive.box`, `geom.primitive.cylinder`, and `geom.primitive.sphere`.
- Face tags from `auto_tag_faces(...)`, such as `face.top`, `face.bottom`, `face.side`, and `face.surface`.
- Wire tags such as `wire.outer` and `wire.inner`.
- Operation/tracking tags such as `solid.boolean.cut`, `op.cut.modified`, or `op.extrude.generated`.
- Operation-level categorical tags such as `solid.boolean.cut` may remain local annotations.
Operation events and source roles are not tags. Proven `preserved`, `modified`,
or `generated` events and `body`/`tool` origins live in typed
`metadata["track"]`. Query them with `ql.operation_event(...)` and
`ql.origin_role(...)`. Missing correspondence remains `coverage="partial"` and
`status="unknown"`; it is never promoted to `generated` by default.
## Metadata Methods
@@ -84,7 +106,7 @@ scad.apply_tag(body, "role.mounting_plate")
body.auto_tag_faces("box")
top_faces = Q.select(body.get_faces()).where(Q.tag("face.top")).all()
role_faces = Q.select(body.get_faces()).where(Q.tag("role.*")).all()
role_faces = Q.select(body.get_faces()).where(Q.tag("role.*", scope="effective")).all()
print(len(top_faces), len(role_faces))
```
@@ -4,12 +4,12 @@ This index includes generated docs for standard part factory functions. Use thes
## Import Surfaces
- Recommended package-level module export: `import simplecadapi as scad`, then call functions through submodules such as `scad.std.gear.<function>(...)` and `scad.std.bearing.<function>(...)`.
- Recommended package-level module export: `import simplecadapi as scad`, then call functions through submodules such as `scad.std.gear.<function>(...)`, `scad.std.fastener.<function>(...)`, and `scad.std.bearing.<function>(...)`.
- Direct submodule import is also supported, for example `from simplecadapi.std.gear import make_spur_gear_rsolid` or `from simplecadapi.std.bearing import make_ball_bearing_rassembly`.
## Usage Guidance
- Prefer standard-library factories for standard bearings, gears, ring gears, and racks before hand-modeling profiles with core geometry APIs.
- Prefer standard-library factories for standard bearings, fasteners, spur gears, straight bevel gears, ring gears, and racks before hand-modeling profiles with core geometry APIs.
- Standard parts return normal SimpleCAD shapes or product assemblies, so they can be transformed, tagged, assembled, exported, or combined with core geometry operations.
- Switch to core geometry APIs only when the requested standard part needs substantial custom geometry beyond the factory parameters.
@@ -23,6 +23,19 @@ This index includes generated docs for standard part factory functions. Use thes
- [make_herringbone_gear_rsolid](make_herringbone_gear_rsolid.md) *(from std/gear.py)* `stdlib`
- [make_spur_gear_rsolid](make_spur_gear_rsolid.md) *(from std/gear.py)* `stdlib`
## Bevel Gears
- [make_straight_bevel_gear_rsolid](make_straight_bevel_gear_rsolid.md) *(from std/gear.py)* `stdlib`
## Roller Chain
- [make_roller_chain_sprocket_rsolid](make_roller_chain_sprocket_rsolid.md) *(from std/chain.py)* `stdlib`
## Fasteners
- [make_bolt_rsolid](make_bolt_rsolid.md) *(from std/fastener.py)* `stdlib`
- [make_nut_rsolid](make_nut_rsolid.md) *(from std/fastener.py)* `stdlib`
## Internal Ring Gears
- [make_helical_ring_gear_rsolid](make_helical_ring_gear_rsolid.md) *(from std/gear.py)* `stdlib`
@@ -0,0 +1,41 @@
# make_bolt_rsolid
## API Definition
```python
def make_bolt_rsolid(
diameter: float,
length: float,
head_style: str = "hex",
thread_style: str = "auto",
thread_detail: str = "modeled",
thread_form: str = "v",
thread_pitch: Optional[float] = None,
thread_depth: Optional[float] = None,
thread_length: Optional[float] = None,
head_width: Optional[float] = None,
head_height: Optional[float] = None,
drive_style: str = "none",
drive_size: Optional[float] = None,
drive_depth: Optional[float] = None,
underhead_fillet_radius: Optional[float] = None,
) -> Solid
```
*Source: std/fastener.py*
## Import Surface
- standard library: `import simplecadapi as scad` then `scad.std.fastener.make_bolt_rsolid(...)`; direct submodule import: `from simplecadapi.std.fastener import make_bolt_rsolid`
## Description
Create a parameterized bolt along `+Z`, with the head underside on `Z=0`. Head styles are `hex`, `square`, `cylindrical`, `button`, and `countersunk`. Drive styles are `none`, `slot`, `cross`, and `hex_socket`.
Thread styles are `auto`, `full`, `partial`, and `none`. `auto` selects full thread for `length <= 3 * diameter`; otherwise it uses the ISO-style piecewise partial-thread length. The default `thread_detail="modeled"` creates a visible replayable helical `v` or `trapezoidal` thread. Set `thread_detail="cosmetic"` explicitly for a smooth shank with thread intent only in metadata. For partial threads, `thread_length` is measured back from the tip at `Z=length`.
For metric coarse-series diameters, the factory derives the default pitch from the standard series and records `d2 = d - 0.6495P` and `d1 = d - 1.0825P` in metadata. Hex-head defaults use catalog-like `S` and `k` values where available. The default underhead fillet is `0.06d`; `underhead_fillet_radius` can override it. The metadata also reports a minimum recommended mating-hole chamfer equal to the fillet radius.
Modeled threads may rotate the helical seam to an equivalent kernel-stable phase. The selected phase is recorded as `thread_phase_degrees` in `std.fastener.bolt` metadata and does not change thread dimensions or handedness.
Default dimensions are useful parametric proportions, not a claim of compliance with a specific ISO, DIN, ASME, or supplier catalog. Set catalog dimensions explicitly for released hardware.
@@ -16,10 +16,9 @@ def make_helical_gear_rsolid(n_teeth: int, module: float, pressure_angle: float
Create an involute helical gear.
Non-zero helix angles are modeled as small-step ruled lofts through rotated
copies of one profile. The small angular step keeps closed-wire section
correspondence stable while ruled faces avoid smooth loft bulging in STEP
exports.
Non-zero helix angles use a continuous twisted sweep along the gear axis. An
auxiliary-spine rotation law preserves the profile while generating one
continuous side face per profile edge instead of one face per loft interval.
## Parameters
@@ -16,10 +16,8 @@ def make_herringbone_gear_rsolid(n_teeth: int, module: float, pressure_angle: fl
Create an involute herringbone (double-helical) gear.
Each half is modeled as a small-step ruled loft through rotated copies of
one profile, with a shared center section forming the herringbone ridge.
This keeps closed-wire section correspondence stable while avoiding smooth
loft bulging in STEP exports.
Each half uses a continuous twisted sweep with opposite handedness. The two
halves share the rotated center profile and are fused into one solid.
## Parameters
@@ -0,0 +1,35 @@
# make_nut_rsolid
## API Definition
```python
def make_nut_rsolid(
diameter: float,
width: float,
height: float,
nut_style: str = "hex",
hole_style: str = "through",
thread_detail: str = "modeled",
thread_form: str = "v",
thread_pitch: Optional[float] = None,
thread_depth: Optional[float] = None,
hole_depth: Optional[float] = None,
knurl_count: int = 24,
) -> Solid
```
*Source: std/fastener.py*
## Import Surface
- standard library: `import simplecadapi as scad` then `scad.std.fastener.make_nut_rsolid(...)`; direct submodule import: `from simplecadapi.std.fastener import make_nut_rsolid`
## Description
Create a parameterized nut along `+Z`, with its bottom face on `Z=0`. Nut styles are `hex`, `square`, `round`, and `knurled`. Hole styles are `through` and `blind`; a blind hole opens from the top face and uses `hole_depth` as its axial depth.
The default `thread_detail="modeled"` creates replayable internal `v` or `trapezoidal` helical teeth. Set `thread_detail="cosmetic"` explicitly for a smooth major-diameter hole with thread intent only in metadata. For metric coarse-series diameters, the factory derives the default pitch and records `d2 = d - 0.6495P` and `d1 = d - 1.0825P` in metadata. `knurl_count` controls the lobe count of the printable knurled approximation.
Modeled threads may rotate the helical seam to an equivalent kernel-stable phase. The selected phase is recorded as `thread_phase_degrees` in `std.fastener.nut` metadata and does not change thread dimensions or handedness.
Default thread proportions are useful for parametric models, not a substitute for catalog tolerances, thread class, lead-in, prevailing-torque features, or manufacturing checks.
@@ -0,0 +1,19 @@
# make_roller_chain_sprocket_rsolid
## API Definition
```python
def make_roller_chain_sprocket_rsolid(n_teeth: int, chain_pitch: float, roller_diameter: float, sprocket_thickness: float, *, bore_radius: float = 0.0, roller_clearance: float = 0.15) -> Solid
```
*Source: std/chain.py*
## Import Surface
- standard library: `import simplecadapi as scad` then `scad.std.chain.make_roller_chain_sprocket_rsolid(...)`; direct submodule import: `from simplecadapi.std.chain import make_roller_chain_sprocket_rsolid`
## Description
Create a roller-chain sprocket from tooth count, chain pitch, roller diameter, and tooth-plate thickness. The pitch radius follows the regular pitch polygon. Circular roller seats are cut at every pitch point and opened through the engineering outside-diameter envelope.
The result preserves assembly-level engagement dimensions. Manufacturing release still requires the selected chain standard's permitted tooth-form range, tooth width, hub, material, heat treatment, runout, and supplier checks.
@@ -0,0 +1,30 @@
# make_straight_bevel_gear_rsolid
## API Definition
```python
def make_straight_bevel_gear_rsolid(n_teeth: int, module: float, pitch_angle: float = 45.0, pressure_angle: float = 20.0, face_width: float = 8.0, *, addendum_factor: float = 1.0, clearance_factor: float = 0.25, backlash: float = 0.0) -> Solid
```
*Source: std/gear.py*
## Import Surface
- standard library: `import simplecadapi as scad` then `scad.std.gear.make_straight_bevel_gear_rsolid(...)`; direct submodule import: `from simplecadapi.std.gear import make_straight_bevel_gear_rsolid`
## Description
Create a straight bevel gear with standard metric tooth proportions. The large-end transverse section uses an analytic involute profile. A similar small-end section is placed on the pitch cone and connected with ruled straight tooth surfaces.
The returned solid contains nominal tooth geometry. Releasing a mating pair still requires mounting-distance, contact-pattern, backlash, material, heat-treatment, and strength checks.
## Parameters
- `n_teeth`: Number of teeth, at least 3.
- `module`: Large-end transverse module in millimetres.
- `pitch_angle`: Pitch-cone angle in degrees, greater than 0 and less than 90.
- `pressure_angle`: Transverse pressure angle in degrees.
- `face_width`: Tooth face width along the pitch-cone generator in millimetres; must be smaller than the outer pitch-cone distance.
- `addendum_factor`: Large-end tooth addendum divided by module.
- `clearance_factor`: Root clearance beyond the addendum divided by module.
- `backlash`: Large-end circumferential tooth-thickness reduction at the pitch circle in millimetres.