feat: integrate SimpleCADAPI 2.0.2 CAD workflows
This commit is contained in:
@@ -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,15 @@ 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 when composing
|
||||
or testing lower-level graph workflows. `result_node_ids` reports the graph
|
||||
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,64 @@
|
||||
# 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 keeps the ordinary Python return value together with the owned session and
|
||||
the durable graph artifacts produced for that model invocation.
|
||||
|
||||
## Attributes
|
||||
|
||||
- `value`: The value returned by the model function. It may be a shape, a
|
||||
product assembly, or a tuple containing application-level reports and a
|
||||
captured preview.
|
||||
- `session`: The completed `GraphSession` that recorded the model.
|
||||
- `result_node_ids`: Explicitly captured graph node ids used as model outputs.
|
||||
- `model_json`: Canonical low-level operation graph JSON containing the captured
|
||||
result leaves.
|
||||
- `session_json`: Session JSON containing the complete session state.
|
||||
- `artifact_paths`: Files written by automatic export. It contains the `scene`
|
||||
key when captured geometry or product values produced a Scene ZIP.
|
||||
|
||||
## Artifact Export
|
||||
|
||||
```python
|
||||
result = build_model()
|
||||
exported = result.export_artifacts(output_dir="examples/out/bracket")
|
||||
print(exported.artifact_paths["scene"])
|
||||
```
|
||||
|
||||
The same export runs automatically when `@scad.model(export_dir=...)` is used.
|
||||
Only values explicitly passed to `capture_result(...)` are considered final
|
||||
geometry/product outputs. Automatic export writes one self-contained
|
||||
`<graph_id>.scene.zip` containing the model JSON, mapped Python sources, and
|
||||
render/selection assets; it does not write adjacent model/session JSON, STEP,
|
||||
STL, or FCStd files. Without an export directory, no files are written.
|
||||
|
||||
## Replay
|
||||
|
||||
```python
|
||||
result = build_model()
|
||||
rebuilt = result.replay()
|
||||
```
|
||||
|
||||
`replay()` is equivalent to replaying `result.model_json`. Pass
|
||||
`strict=False` only when intentionally relaxing replay validation.
|
||||
@@ -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,12 @@ 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, not geometry guesses. During
|
||||
`make_wire_from_sketch_rwire(...)` or `make_face_from_sketch_rface(...)`, the
|
||||
promotion map binds each ordered profile entity to exactly one generated Edge.
|
||||
The canonical topology-identity tags are
|
||||
`sketch.<sketch-name>.entity.<entity-id>` and
|
||||
`sketch.<sketch-name>.profile.<profile-id>`, with `topology_name` evidence.
|
||||
Downstream features can project those tags only when their kernel history
|
||||
proves one-source/one-target 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. During
|
||||
profile promotion, `entity_id` becomes the local segment of the canonical
|
||||
topology-identity tag for the corresponding 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,34 @@
|
||||
# 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 the explicit final outputs of the
|
||||
active model session. The value is returned unchanged, so it can be used inline
|
||||
or assigned back to a local variable.
|
||||
|
||||
```python
|
||||
@scad.model(graph_id="multi_output_demo")
|
||||
def build_model():
|
||||
primary = scad.make_box_rsolid(width=10.0, height=4.0, depth=2.0)
|
||||
secondary = scad.make_cylinder_rsolid(radius=2.0, height=5.0)
|
||||
return scad.capture_result(value=(primary, secondary))
|
||||
```
|
||||
|
||||
Explicit capture prevents unrelated intermediate graph leaves from becoming
|
||||
model outputs. When the enclosing `@model` uses `export_dir=...`, captured
|
||||
geometry and product values become roots in the single self-contained Scene ZIP.
|
||||
The package embeds model JSON and mapped Python sources; automatic export does
|
||||
not create adjacent model/session JSON, STEP, STL, or FCStd files. It must be
|
||||
called 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,27 @@ 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 semantic role
|
||||
tag arguments to attach user tags to those exact role sets.
|
||||
|
||||
When `tag_prefix` is supplied, profile Edge tags are recommended at creation
|
||||
time through the profile API (`edge_tags` and `tag_prefix`). The feature
|
||||
produces `<tag_prefix>.face.start`, `<tag_prefix>.face.end`, and one
|
||||
`<tag_prefix>.face.side.<profile_edge_tag>` for each kernel-proven profile Edge.
|
||||
The cap Face tags are inherited by their boundary Edges, so an exact Edge
|
||||
can be selected from two tagged neighboring Faces with QL `incident_to` or
|
||||
`shared_boundary` rather than an enumeration index.
|
||||
|
||||
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,19 @@ def make_box_rsolid(width: ScalarLike, height: ScalarLike, depth: ScalarLike, bo
|
||||
|
||||
## Description
|
||||
|
||||
Create a box solid.
|
||||
Create a native box solid. The exact kernel-backed Face roles are `box.bottom`,
|
||||
`box.top`, `box.front`, `box.back`, `box.left`, and `box.right`. Each role has
|
||||
exactly one Face.
|
||||
|
||||
The face tag arguments attach tags to those roles. `result_tag` targets the
|
||||
result Solid. `tag_prefix="housing"` creates the topology tags
|
||||
`housing.solid`, `housing.face.bottom`,
|
||||
`housing.face.top`, `housing.face.front`, `housing.face.back`,
|
||||
`housing.face.left`, and `housing.face.right`.
|
||||
|
||||
The roles and topology tags come directly from OCC Box Face witnesses, not Face
|
||||
enumeration or geometric classification. The current OCP Box builder does not
|
||||
expose equivalent direct Edge witnesses, so Box Edge output roles are
|
||||
unsupported. Select an exact Edge from two tagged incident Faces with
|
||||
`Q.edges().incident_to(face_a, face_b, distinct=True)` or
|
||||
`face_a.shared_boundary(face_b)`.
|
||||
|
||||
@@ -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,19 @@ 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. The kernel-backed Face roles are `cone.start`,
|
||||
`cone.end`, and `cone.side`. The Edge roles are `cone.start_boundary`,
|
||||
`cone.end_boundary`, and `cone.seam`.
|
||||
|
||||
For a pointed cone (`top_radius=0`), no top cap exists, so `cone.end` is absent
|
||||
and requesting `end_face_tag` fails. `cone.end_boundary` remains available as
|
||||
the kernel's degenerate apex Edge. A frustum (`top_radius>0`) has all six roles.
|
||||
|
||||
The role tag arguments attach tags to exact roles. `result_tag` targets the
|
||||
Solid. `tag_prefix="adapter"` creates `adapter.solid`, the
|
||||
Face tags `adapter.face.start`, `adapter.face.end` when present, and
|
||||
`adapter.face.side`, plus `adapter.edge.start`, `adapter.edge.end`, and
|
||||
`adapter.edge.seam`.
|
||||
|
||||
All roles and topology tags use direct OCC Cone witnesses. They are not inferred from
|
||||
topology enumeration, size, normal, or position.
|
||||
|
||||
@@ -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,21 @@ def make_cylinder_rsolid(radius: ScalarLike, height: ScalarLike, bottom_face_cen
|
||||
|
||||
## Description
|
||||
|
||||
Create a cylinder solid.
|
||||
Create a native cylinder solid. The kernel-backed output roles are:
|
||||
|
||||
- `cylinder.start`, `cylinder.end`, and `cylinder.side` for Faces.
|
||||
- `cylinder.start_boundary`, `cylinder.end_boundary`, and `cylinder.seam` for Edges.
|
||||
|
||||
The role tag arguments attach tags to those exact roles.
|
||||
`tag_prefix="shaft"` creates the topology tag prefix `shaft`: the cap and
|
||||
lateral Faces receive `shaft.face.start`, `shaft.face.end`, and
|
||||
`shaft.face.side`; their boundary Edges inherit the corresponding Face tags.
|
||||
The three native Edge roles additionally receive `shaft.edge.start`,
|
||||
`shaft.edge.end`, and `shaft.edge.seam`.
|
||||
|
||||
Topology tag prefixes must be supplied while creating the profile or feature. The implementation
|
||||
uses OCC primitive witnesses and exact incident topology, not face or edge
|
||||
enumeration, area, normal, or position heuristics. Use QL relation/set queries
|
||||
to disambiguate an Edge shared by two tagged Faces, for example
|
||||
`Q.edges().incident_to(face_a, face_b, distinct=True)` or
|
||||
`face_a.shared_boundary(face_b)`.
|
||||
|
||||
@@ -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 promoted
|
||||
Face receives the canonical profile topology-identity tag and each boundary
|
||||
Edge receives the exact Sketch entity tag from the promotion map. For example,
|
||||
a Sketch with `name="rect"` and profile `bottom` produces `sketch.rect.profile.bottom` and
|
||||
`sketch.rect.entity.bottom`.
|
||||
|
||||
These are creation-time tags with `topology_name` evidence backed by the solved Sketch
|
||||
promotion map. They replay from the Sketch payload and promotion parameters;
|
||||
ordinary compatibility tags such as `sketch_entity.bottom` remain separate.
|
||||
|
||||
@@ -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,11 @@ 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 the exact ordered `entity_id` to Edge correspondence. A Sketch
|
||||
with `name="rect"` and profile `bottom` produces the tag
|
||||
`sketch.rect.profile.bottom`; entity ID `right` produces
|
||||
`sketch.rect.entity.right` with `topology_name` evidence.
|
||||
|
||||
Promotion fails if the kernel returns a different Edge count, because the SDK
|
||||
does not guess correspondence from Edge order, geometry, or measurements.
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# 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/model.json`, mapped project-relative Python source files under
|
||||
`sources/`, and the GLB/entity assets required for rendering and selection. It
|
||||
does not create adjacent model/session JSON, STEP, STL, or FCStd files.
|
||||
|
||||
```python
|
||||
import simplecadapi as scad
|
||||
|
||||
@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
|
||||
|
||||
result = build_bracket()
|
||||
print(result.result_node_ids)
|
||||
```
|
||||
|
||||
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,28 @@
|
||||
# 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`. The decorator reuses the session owned by the enclosing
|
||||
`@model` function and validates that returned graph values belong to it.
|
||||
|
||||
```python
|
||||
@scad.requires_session
|
||||
def make_bracket_body():
|
||||
return scad.make_box_rsolid(width=20.0, height=10.0, depth=3.0)
|
||||
```
|
||||
|
||||
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,12 @@ 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.
|
||||
The semantic role tag arguments attach tags to the proven cap or side Faces.
|
||||
`result_tag` tags the resulting Solid. These assignments 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`。
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
# Translator Backend Contract
|
||||
|
||||
This contract defines the package structure and dependency boundaries for every
|
||||
SimpleCAD translator backend.
|
||||
|
||||
## Backend Naming
|
||||
|
||||
Backend packages live under `simplecadapi.translator` and use the name
|
||||
`<backend>_translator`, where `<backend>` is a stable lowercase backend ID.
|
||||
|
||||
Examples:
|
||||
|
||||
- `freecad_translator`
|
||||
- `openscad_translator`
|
||||
- `onshape_translator`
|
||||
|
||||
Backends are exported explicitly from `simplecadapi.translator`. Importing a
|
||||
backend package must not require the target CAD runtime to be installed.
|
||||
|
||||
## Required Files
|
||||
|
||||
Every backend package contains:
|
||||
|
||||
| File | Responsibility |
|
||||
| --- | --- |
|
||||
| `__init__.py` | Defines the complete public backend surface through `__all__`. |
|
||||
| `api.py` | Contains public convenience functions and user-facing error boundaries. |
|
||||
| `translator.py` | Contains the `<Backend>Translator` implementation. |
|
||||
| `capabilities.py` | Declares backend targets and support for every canonical operation. |
|
||||
|
||||
The translator class must inherit `BaseTranslator`. `capabilities.py` exports
|
||||
`BACKEND_NAME`, `CAPABILITIES`, and `OP_SUPPORT`. The operation support map must
|
||||
contain exactly the canonical operation set. Unsupported operations require a
|
||||
non-empty reason.
|
||||
|
||||
## Conditional Files
|
||||
|
||||
Use these standard names when the corresponding responsibility exists:
|
||||
|
||||
| File or directory | Responsibility |
|
||||
| --- | --- |
|
||||
| `exporter.py` | File output, external process or remote API execution, and output validation. |
|
||||
| `context.py` | State owned by one translation invocation. |
|
||||
| `analysis.py` | Backend-specific graph analysis and lowering decisions. |
|
||||
| `codegen.py` | Pure target-code formatting and literal helpers. |
|
||||
| `emitters/` | Canonical operation emitters and their single registry. |
|
||||
| `runtime/` | Source fragments embedded into an artifact for execution in the target runtime. |
|
||||
|
||||
An `emitters/registry.py` file is the only operation-to-emitter mapping. Runtime
|
||||
fragments must remain valid Python source, must not execute target APIs when the
|
||||
SimpleCAD package is imported, and must be assembled in one declared order.
|
||||
|
||||
## Translation And Export
|
||||
|
||||
Translation is an in-memory operation. It consumes canonical model JSON or an
|
||||
already imported canonical payload and returns a `TranslationArtifact`.
|
||||
|
||||
Export is an effectful operation. It may write files, invoke an executable, or
|
||||
call a remote API. Those effects belong in `exporter.py`, not in the translator
|
||||
or emitters.
|
||||
|
||||
Public naming follows these forms:
|
||||
|
||||
- `translate_model_json_to_<artifact>` for in-memory conversion.
|
||||
- `export_model_json_to_<format>` for file or external-runtime output.
|
||||
|
||||
Existing public names may remain as compatibility aliases.
|
||||
|
||||
## Dependencies
|
||||
|
||||
The allowed dependency direction is:
|
||||
|
||||
```text
|
||||
backend/__init__.py -> api.py, translator.py, capabilities.py
|
||||
api.py -> translator.py, exporter.py
|
||||
translator.py -> context.py, analysis.py, emitters/, runtime/
|
||||
emitters/ -> context and pure code-generation helpers
|
||||
exporter.py -> shared types/errors and external execution
|
||||
```
|
||||
|
||||
The following reverse dependencies are prohibited:
|
||||
|
||||
- Translator or emitters importing `api.py`.
|
||||
- Translator importing `exporter.py`.
|
||||
- Emitters importing the public translator class.
|
||||
- Runtime fragments importing the backend package.
|
||||
- Capabilities importing translator implementation modules.
|
||||
|
||||
## Compatibility
|
||||
|
||||
The generated artifact and its persisted target metadata are treated as a
|
||||
compatibility boundary. Pure module moves must not rename generated runtime
|
||||
helpers, registries, target object properties, or public import paths. Behavior
|
||||
fixes are made separately from structural migrations.
|
||||
|
||||
## Verification
|
||||
|
||||
The shared backend contract test verifies required files, public exports,
|
||||
translator inheritance, backend naming, and canonical operation coverage.
|
||||
Backend tests additionally verify generated artifact syntax and, where the
|
||||
target runtime is available, real exported files.
|
||||
@@ -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,256 @@
|
||||
# Dimension Tolerance Chains
|
||||
|
||||
SimpleCADAPI can attach units and manufacturing tolerances to declared dimension
|
||||
variables, infer dimensions through the expression DAG, propagate source
|
||||
variation, 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. A dimension tolerance describes permitted manufacturing variation around a nominal design value; it never changes a CAD operation's numerical robustness settings.
|
||||
|
||||
## Declare Source Dimensions
|
||||
|
||||
Every variable that participates in a tolerance chain must declare a tolerance:
|
||||
|
||||
```python
|
||||
import simplecadapi as scad
|
||||
|
||||
width = scad.var("width", 10.0, unit="mm", tolerance=0.1)
|
||||
shaft = scad.var(
|
||||
"shaft",
|
||||
0.315,
|
||||
unit="in",
|
||||
tolerance=(-0.05, 0.0),
|
||||
tolerance_unit="mm",
|
||||
)
|
||||
```
|
||||
|
||||
A scalar tolerance is symmetric. `tolerance=0.1` means `-0.1/+0.1`
|
||||
around the nominal value. A two-value sequence contains signed
|
||||
`(lower_deviation, upper_deviation)` values. The lower deviation must be at most
|
||||
zero, the upper deviation must be at least zero, and all values must be finite.
|
||||
|
||||
`tolerance_unit` defaults to `unit`. It may differ from the nominal unit, but the
|
||||
dimensions must match. Geometry and tolerance propagation use canonical
|
||||
millimeters for length and degrees for angle. Declaration-space values remain on
|
||||
the `Var` for display and serialization.
|
||||
|
||||
The same values can be represented explicitly:
|
||||
|
||||
```python
|
||||
tolerance = scad.DimensionTolerance(
|
||||
lower_deviation=-0.05,
|
||||
upper_deviation=0.0,
|
||||
)
|
||||
shaft = scad.var("shaft", 8.0, unit="mm", tolerance=tolerance)
|
||||
```
|
||||
|
||||
Tolerance identity follows `expr_id`, not the human-readable variable name. Two variables with the same name remain separate tolerance sources.
|
||||
|
||||
## Propagate A Chain
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
result = scad.analyze_tolerance(clearance, method="worst_case")
|
||||
|
||||
print(result.nominal)
|
||||
print(result.lower_bound, result.upper_bound)
|
||||
print(result.lower_deviation, result.upper_deviation)
|
||||
for contribution in result.contributions:
|
||||
print(contribution.variable_name, contribution.lower_deviation, contribution.upper_deviation)
|
||||
```
|
||||
|
||||
`ToleranceAnalysis` contains absolute bounds, deviations from nominal, inferred
|
||||
`dimension`, canonical `unit`, and one contribution record per source variable.
|
||||
Each contribution reports its nominal and source tolerance in canonical units.
|
||||
|
||||
The unit system validates the expression before propagation. This permits
|
||||
physically meaningful nonlinear chains such as:
|
||||
|
||||
```python
|
||||
width = scad.var("width", 30.0, unit="mm", tolerance=0.1)
|
||||
height = scad.var("height", 40.0, unit="mm", tolerance=0.2)
|
||||
diagonal = scad.sqrt(width**2 + height**2)
|
||||
|
||||
analysis = scad.analyze_tolerance(diagonal)
|
||||
assert analysis.dimension == scad.LENGTH
|
||||
assert analysis.unit == scad.MM
|
||||
```
|
||||
|
||||
Area and volume expressions can be inferred and analyzed. Persisted manufacturing
|
||||
requirements currently accept final Length and Angle results only.
|
||||
|
||||
## Propagation Methods
|
||||
|
||||
### Worst Case
|
||||
|
||||
`method="worst_case"` is the default and the safety-oriented validation method.
|
||||
|
||||
- Affine chains preserve variable identity and combine coefficients exactly. Repeated use is not treated as an independent source, so `x - x` has zero propagated tolerance.
|
||||
- Nonlinear chains use conservative interval propagation.
|
||||
- Multiplication and division consider all endpoint sign combinations.
|
||||
- Integer, negative, fractional, and varying powers validate their mathematical domains.
|
||||
- `sin` and `cos` include interior extrema; `tan` rejects intervals crossing a discontinuity.
|
||||
- `sqrt`, `asin`, and `acos` validate the entire input interval.
|
||||
- Division rejects denominator intervals containing zero.
|
||||
- `atan2` rejects tolerance regions containing its undefined origin.
|
||||
|
||||
Conservative interval propagation can intentionally overestimate a strongly correlated nonlinear expression. It must not underestimate a safety bound.
|
||||
|
||||
### RSS
|
||||
|
||||
`method="rss"` uses first-order analytic sensitivities and root-sum-square combination:
|
||||
|
||||
```python
|
||||
result = scad.analyze_tolerance(clearance, method="rss")
|
||||
```
|
||||
|
||||
Distinct variables are assumed independent. Repeated occurrences of the same variable are merged before RSS, so `x - x` still has zero sensitivity and `x + x` has twice the sensitivity of `x`.
|
||||
|
||||
RSS validates the full declared tolerance interval before calculating the first-order estimate. A nominal point cannot hide a division singularity, trigonometric discontinuity, or invalid function domain elsewhere in the source range.
|
||||
|
||||
Covariance matrices, correlation groups, probability distributions, and Monte Carlo analysis are not represented by the current API. Use `worst_case` when the independence assumption is unavailable or when a guaranteed envelope is required.
|
||||
|
||||
## Check One Requirement
|
||||
|
||||
`check_tolerance()` returns a result without raising when the derived tolerance exceeds the permitted deviations:
|
||||
|
||||
```python
|
||||
check = scad.check_tolerance(
|
||||
clearance,
|
||||
tolerance=(-0.25, 0.24),
|
||||
method="worst_case",
|
||||
name="axial_clearance",
|
||||
tolerance_unit="mm",
|
||||
)
|
||||
|
||||
print(check.passed)
|
||||
print(check.lower_margin, check.upper_margin)
|
||||
```
|
||||
|
||||
A non-negative lower and upper margin means the requirement passes, subject to a
|
||||
small floating-point comparison epsilon. Requirement deviations are converted
|
||||
from `tolerance_unit` to the target's canonical unit before comparison.
|
||||
|
||||
## Session Requirements And Automatic Validation
|
||||
|
||||
Use `GraphSession.require_tolerance()` for design requirements that must travel with the model:
|
||||
|
||||
```python
|
||||
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",
|
||||
requirement_id="req.axial_clearance",
|
||||
tolerance_unit="mm",
|
||||
)
|
||||
|
||||
report = session.validate_tolerances(raise_on_failure=True)
|
||||
model_json = scad.export_model_json(session)
|
||||
```
|
||||
|
||||
Automatic validation occurs when:
|
||||
|
||||
1. `validate_tolerances(raise_on_failure=True)` is called.
|
||||
2. A session or model JSON payload is exported.
|
||||
3. A model JSON payload is imported or replayed.
|
||||
4. A model is translated to FreeCAD through the model importer.
|
||||
|
||||
A failed requirement raises `ToleranceValidationError` at the tolerance layer. Model import, export, and replay expose it through the existing structured `SimpleCADError` harness where applicable.
|
||||
|
||||
Declaring a requirement validates that the chain is complete and mathematically defined, but it still records a failing requirement so callers can inspect its margins. Export and replay are the enforcement boundaries.
|
||||
|
||||
## Serialization
|
||||
|
||||
Variable tolerances are stored on variable nodes in `expression_graph`:
|
||||
|
||||
```json
|
||||
{
|
||||
"expr_id": "var_width",
|
||||
"kind": "var",
|
||||
"name": "width",
|
||||
"default": 1.0,
|
||||
"unit": "in",
|
||||
"tolerance": {
|
||||
"lower_deviation": -0.1,
|
||||
"upper_deviation": 0.1
|
||||
},
|
||||
"tolerance_unit": "mm"
|
||||
}
|
||||
```
|
||||
|
||||
Design requirements and their latest validation evidence are stored in the top-level `tolerance_graph`:
|
||||
|
||||
```json
|
||||
{
|
||||
"requirements": [
|
||||
{
|
||||
"requirement_id": "req.axial_clearance",
|
||||
"target_expr_id": "expr_clearance",
|
||||
"tolerance": {
|
||||
"lower_deviation": -0.25,
|
||||
"upper_deviation": 0.24
|
||||
},
|
||||
"method": "worst_case",
|
||||
"name": "axial_clearance",
|
||||
"tolerance_unit": "mm",
|
||||
"target_dimension": {
|
||||
"length": 1,
|
||||
"angle": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"validation": {
|
||||
"passed": true,
|
||||
"checks": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Validation evidence is recomputed during import; serialized evidence is not
|
||||
trusted as an authority. The target expression dimension is inferred again and
|
||||
must match `target_dimension`; `tolerance_unit` must have the same dimension.
|
||||
Payloads created before units or `tolerance_graph` existed remain valid as legacy
|
||||
unitless expressions and import with an empty tolerance graph when it is absent.
|
||||
|
||||
Nominal geometry replay still uses the numeric snapshots in operation-node `params`. Tolerance validation does not sample or regenerate worst-case geometry.
|
||||
|
||||
## FreeCAD Translation
|
||||
|
||||
FreeCAD translation keeps the full tolerance graph as document metadata. The
|
||||
`SimpleCADExpressions` spreadsheet stores lower/upper deviations in columns E/F,
|
||||
nominal unit in G, tolerance unit in H, and inferred dimension in I. Spreadsheet
|
||||
values and formulas use canonical CAD values so inch/radian declarations remain
|
||||
consistent with operation-node snapshots. The translator preserves tolerance
|
||||
intent but does not convert it into FreeCAD geometric-tolerance objects or
|
||||
statistical solvers.
|
||||
|
||||
## Failure Conditions
|
||||
|
||||
Tolerance analysis fails explicitly for:
|
||||
|
||||
- a source variable without a declared tolerance
|
||||
- non-finite nominal values or deviations
|
||||
- malformed signed lower/upper deviations
|
||||
- unknown, malformed, non-finite, incompatible, overflowing, or underflowing units
|
||||
- addition/subtraction or `atan2` with incompatible dimensions
|
||||
- invalid powers, square roots, or trigonometric dimensions
|
||||
- mixing unit-declared and legacy variables in one expression
|
||||
- a requirement unit or persisted target dimension that disagrees with the target
|
||||
- an Area, Volume, Dimensionless, or compound-dimension requirement target
|
||||
- duplicate requirement IDs
|
||||
- unknown or dangling expression references
|
||||
- malformed, cyclic, duplicate-ID, or unsupported expression nodes
|
||||
- an undefined expression anywhere in the declared tolerance interval
|
||||
- an RSS derivative at a non-differentiable nominal point
|
||||
- an unsupported propagation method
|
||||
|
||||
See [Physical Units And Dimension Inference](physical-units.md) for the complete
|
||||
unit registry, dimension algebra, custom-unit payload, and legacy 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,
|
||||
@@ -128,7 +128,8 @@ source API 名字不等于 canonical graph op 名字。Composite source API 可
|
||||
"topology_delta_summary": false,
|
||||
"assembly_graph": false,
|
||||
"scalar_field_graph": false,
|
||||
"expression_graph": true
|
||||
"expression_graph": true,
|
||||
"dimension_tolerances": true
|
||||
},
|
||||
"graph_id": "graph_xxxxxxxx",
|
||||
"nodes": [...],
|
||||
@@ -163,6 +164,7 @@ source API 名字不等于 canonical graph op 名字。Composite source API 可
|
||||
| `assembly_graph` | `bool` | 当前 graph JSON 本身不承载 assembly graph |
|
||||
| `scalar_field_graph` | `bool` | 当前为 `false`;SDF / scalar field graph 暂时不在支持范围内 |
|
||||
| `expression_graph` | `bool` | session/model payload 支持 expression graph |
|
||||
| `dimension_tolerances` | `bool` | session/model payload supports variable tolerances and a tolerance requirement graph |
|
||||
|
||||
## 5. Operation Node Schema
|
||||
|
||||
@@ -334,7 +336,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
|
||||
|
||||
@@ -667,7 +669,13 @@ outer_edges = Q.faces().where(Q.tag("face.top")).boundary("wire").where(Q.tag("w
|
||||
"expr_id": "var_119b16e4",
|
||||
"kind": "var",
|
||||
"name": "r",
|
||||
"default": 2.0
|
||||
"default": 2.0,
|
||||
"unit": "mm",
|
||||
"tolerance": {
|
||||
"lower_deviation": -0.1,
|
||||
"upper_deviation": 0.2
|
||||
},
|
||||
"tolerance_unit": "mm"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -692,7 +700,13 @@ outer_edges = Q.faces().where(Q.tag("face.top")).boundary("wire").where(Q.tag("w
|
||||
"expr_id": "var_xxx",
|
||||
"kind": "var",
|
||||
"name": "radius",
|
||||
"default": 2.0
|
||||
"default": 2.0,
|
||||
"unit": "mm",
|
||||
"tolerance": {
|
||||
"lower_deviation": -0.05,
|
||||
"upper_deviation": 0.1
|
||||
},
|
||||
"tolerance_unit": "mm"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -722,6 +736,82 @@ outer_edges = Q.faces().where(Q.tag("face.top")).boundary("wire").where(Q.tag("w
|
||||
- `cos`
|
||||
- `tan`
|
||||
- `sqrt`
|
||||
- `acos`
|
||||
- `asin`
|
||||
- `atan`
|
||||
- `atan2`
|
||||
|
||||
### 10.3 Unit And Dimension Semantics
|
||||
|
||||
Variable nodes may include:
|
||||
|
||||
| Field | Type | Required | Meaning |
|
||||
| --- | --- | --- | --- |
|
||||
| `unit` | `string | unit object` | no | nominal declaration unit |
|
||||
| `tolerance` | `object` | no | signed deviations in `tolerance_unit` |
|
||||
| `tolerance_unit` | `string | unit object` | no | source tolerance unit; defaults to `unit` in Python declarations |
|
||||
|
||||
Built-in units serialize as symbols such as `mm`, `in`, `deg`, or `rad`. Custom
|
||||
units serialize in full:
|
||||
|
||||
```json
|
||||
{
|
||||
"symbol": "thou",
|
||||
"dimension": {"length": 1, "angle": 0},
|
||||
"scale_to_canonical": 0.0254
|
||||
}
|
||||
```
|
||||
|
||||
Dimensions contain required integer `length` and `angle` exponents. Canonical
|
||||
numeric values used by operation-node `params` and tolerance analysis are `mm`,
|
||||
`mm^2`, `mm^3`, `deg`, or `1` for the named dimensions.
|
||||
|
||||
Import rebuilds the complete DAG and reruns dimension inference. Addition and
|
||||
subtraction require matching dimensions; multiplication/division combine
|
||||
exponents; dimensioned powers require supported constant exponents; square root
|
||||
requires even exponents; and trigonometric operations enforce Angle/Dimensionless
|
||||
inputs. A graph cannot mix unit-declared variables with legacy variables lacking
|
||||
units. Unitless legacy graphs remain accepted.
|
||||
|
||||
### 10.4 Dimension Tolerance Graph
|
||||
|
||||
Variable `tolerance` values are signed deviations from `default`. A scalar source dimension must use `lower_deviation <= 0 <= upper_deviation`.
|
||||
|
||||
Session/model payloads may include a sibling `tolerance_graph`:
|
||||
|
||||
```json
|
||||
{
|
||||
"requirements": [
|
||||
{
|
||||
"requirement_id": "req.clearance",
|
||||
"target_expr_id": "expr_clearance",
|
||||
"tolerance": {
|
||||
"lower_deviation": -0.2,
|
||||
"upper_deviation": 0.3
|
||||
},
|
||||
"method": "worst_case",
|
||||
"name": "clearance",
|
||||
"tolerance_unit": "mm",
|
||||
"target_dimension": {
|
||||
"length": 1,
|
||||
"angle": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"validation": {
|
||||
"passed": true,
|
||||
"checks": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Supported methods are `worst_case` and `rss`. Unit-aware requirements must target
|
||||
Length or Angle. `tolerance_unit` must match the inferred target dimension and is
|
||||
converted to the canonical unit before comparison. Importers recompute validation
|
||||
from `expression_graph`, compare the inferred result to `target_dimension`, and do
|
||||
not trust serialized `validation` evidence. Missing `tolerance_graph` is treated as
|
||||
an empty graph for backward compatibility. Legacy requirements may omit both unit
|
||||
fields when their target expression is unitless.
|
||||
|
||||
## 11. Frame Graph Schema
|
||||
|
||||
@@ -765,6 +855,7 @@ outer_edges = Q.faces().where(Q.tag("face.top")).boundary("wire").where(Q.tag("w
|
||||
"canonical_contract": {...},
|
||||
"graph": {...},
|
||||
"expression_graph": {...},
|
||||
"tolerance_graph": {...},
|
||||
"frame_graph": {...},
|
||||
"geometry_registry": [...],
|
||||
"semantic_entity_registry": [...],
|
||||
@@ -784,6 +875,7 @@ outer_edges = Q.faces().where(Q.tag("face.top")).boundary("wire").where(Q.tag("w
|
||||
| `graph` | `graph object` | yes | canonical low-level graph and only source of truth |
|
||||
| `leaf_ids` | `array<string>` | yes | explicit result set for multi-output graph replay/export |
|
||||
| `expression_graph` | `object` | yes | expression DAG |
|
||||
| `tolerance_graph` | `object` | no | dimension-chain requirements and validation evidence; defaults to an empty graph |
|
||||
| `frame_graph` | `object` | yes | frame snapshots |
|
||||
| `geometry_registry` | `array<object>` | yes | output geometry registry |
|
||||
| `semantic_entity_registry` | `array<object>` | yes | semantic entity registry |
|
||||
@@ -936,6 +1028,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 +1389,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,202 @@
|
||||
# Physical Units And Dimension Inference
|
||||
|
||||
SimpleCADAPI attaches physical meaning at `Var` declarations and infers the
|
||||
dimension of every derived scalar expression. This catches invalid formulas before
|
||||
they reach geometry, tolerance analysis, model export, replay, or FreeCAD
|
||||
translation.
|
||||
|
||||
## Canonical CAD Units
|
||||
|
||||
Declaration values are preserved on each `Var`, but evaluation converts them to a
|
||||
single CAD coordinate system:
|
||||
|
||||
| Dimension | Canonical unit |
|
||||
| --- | --- |
|
||||
| Dimensionless | `1` |
|
||||
| Length | `mm` |
|
||||
| Area | `mm^2` |
|
||||
| Volume | `mm^3` |
|
||||
| Angle | `deg` |
|
||||
|
||||
Degrees are canonical because existing SimpleCAD rotation and angular APIs use
|
||||
degrees. Trigonometric evaluation converts to radians internally and converts
|
||||
inverse-trigonometric results back to degrees.
|
||||
|
||||
```python
|
||||
import math
|
||||
import simplecadapi as scad
|
||||
|
||||
width = scad.var("width", 1.0, unit="in")
|
||||
angle = scad.var("angle", math.pi / 2, unit="rad")
|
||||
|
||||
assert width.default == 1.0
|
||||
assert width.evaluate() == 25.4
|
||||
assert angle.evaluate() == 90.0
|
||||
assert math.isclose(scad.sin(angle).evaluate(), 1.0)
|
||||
```
|
||||
|
||||
Bindings use the variable's declaration unit. `width.evaluate({"width": 2.0})`
|
||||
therefore returns `50.8` millimeters for an inch-declared variable.
|
||||
|
||||
## Declaring Units And Tolerances
|
||||
|
||||
```python
|
||||
width = scad.var(
|
||||
"width",
|
||||
1.0,
|
||||
unit="in",
|
||||
tolerance=(-0.1, 0.2),
|
||||
tolerance_unit="mm",
|
||||
)
|
||||
```
|
||||
|
||||
- `default` is in `unit`.
|
||||
- `tolerance` is in `tolerance_unit`.
|
||||
- `tolerance_unit` defaults to `unit` when a tolerance is present.
|
||||
- Nominal and tolerance units may differ, but their dimensions must match.
|
||||
- A `tolerance_unit` requires both `unit` and `tolerance`.
|
||||
- Values must be finite and representable after canonical conversion.
|
||||
|
||||
`width.default` and `width.tolerance` preserve declaration-space values.
|
||||
`width.canonical_default` and `width.canonical_tolerance` expose values used by
|
||||
geometry and tolerance propagation.
|
||||
|
||||
## Built-In Units
|
||||
|
||||
| Dimension | Symbols |
|
||||
| --- | --- |
|
||||
| Dimensionless | `1`, `%` |
|
||||
| Length | `mm`, `cm`, `m`, `in`, `ft` |
|
||||
| Area | `mm^2`, `cm^2`, `m^2`, `in^2`, `ft^2` |
|
||||
| Volume | `mm^3`, `cm^3`, `m^3`, `in^3`, `ft^3` |
|
||||
| Angle | `deg`, `rad` |
|
||||
|
||||
Common singular/plural names are accepted by `get_unit()`, including
|
||||
`millimeters`, `inches`, `feet`, `degrees`, `radians`, `square feet`, and
|
||||
`cubic inches`. `ml` aliases `cm^3`.
|
||||
|
||||
Use constants such as `MM`, `INCH`, `DEGREE`, `RADIAN`, `LENGTH`, and `ANGLE`, or
|
||||
resolve strings:
|
||||
|
||||
```python
|
||||
assert scad.get_unit("inch") == scad.INCH
|
||||
assert scad.convert_value(1.0, "in", "mm") == 25.4
|
||||
assert math.isclose(scad.convert_value(180.0, "deg", "rad"), math.pi)
|
||||
```
|
||||
|
||||
Incompatible conversion raises `UnitValidationError`.
|
||||
|
||||
## Dimension Algebra
|
||||
|
||||
`Dimension` stores integer exponents for length and angle. Named dimensions are:
|
||||
|
||||
- `DIMENSIONLESS = Dimension()`
|
||||
- `LENGTH = Dimension(length=1)`
|
||||
- `AREA = Dimension(length=2)`
|
||||
- `VOLUME = Dimension(length=3)`
|
||||
- `ANGLE = Dimension(angle=1)`
|
||||
|
||||
`infer_dimension(expression)` applies these rules:
|
||||
|
||||
| Operation | Rule |
|
||||
| --- | --- |
|
||||
| `a + b`, `a - b` | dimensions must match |
|
||||
| `a * b` | add dimension exponents |
|
||||
| `a / b` | subtract dimension exponents |
|
||||
| `a ** n` | multiply exponents by constant integer `n` |
|
||||
| `sqrt(a)` or `a ** 0.5` | every exponent must be even |
|
||||
| unary `-a`, `abs(a)` | preserve dimension |
|
||||
| `sin`, `cos`, `tan` | input must be Angle; result is Dimensionless |
|
||||
| `asin`, `acos`, `atan` | input must be Dimensionless; result is Angle |
|
||||
| `atan2(y, x)` | inputs must have the same dimension; result is Angle |
|
||||
|
||||
Arbitrary and varying powers are permitted for dimensionless bases. A dimensioned
|
||||
base requires a constant integer exponent, except `0.5` is accepted when all base
|
||||
exponents are even.
|
||||
|
||||
```python
|
||||
width = scad.var("width", 30.0, unit="mm")
|
||||
height = scad.var("height", 40.0, unit="mm")
|
||||
|
||||
area = width * height
|
||||
diagonal = scad.sqrt(width**2 + height**2)
|
||||
|
||||
assert scad.infer_dimension(area) == scad.AREA
|
||||
assert scad.infer_dimension(diagonal) == scad.LENGTH
|
||||
assert diagonal.evaluate() == 50.0
|
||||
```
|
||||
|
||||
## Numeric Constants
|
||||
|
||||
Numeric literals are dimensionless coefficients in multiplication and division.
|
||||
For addition and subtraction, a literal adopts the other operand's dimension as a
|
||||
contextual offset:
|
||||
|
||||
```python
|
||||
length = scad.var("length", 10.0, unit="mm")
|
||||
assert scad.infer_dimension(length * 2.0) == scad.LENGTH
|
||||
assert scad.infer_dimension(length + 2.0) == scad.LENGTH
|
||||
```
|
||||
|
||||
The literal is already expressed in the canonical result unit. `length + 2.0`
|
||||
therefore means two millimeters, not two units of `length.unit`. Prefer explicit
|
||||
variables when declaration-unit intent must be retained.
|
||||
|
||||
## Legacy Unitless Expressions
|
||||
|
||||
Variables without `unit` retain the previous behavior:
|
||||
|
||||
- `infer_dimension()` returns `None`.
|
||||
- Trigonometric inputs and results use radians.
|
||||
- Existing arbitrary expression and tolerance behavior remains available.
|
||||
- A legacy variable cannot be mixed with a unit-declared variable in one
|
||||
expression because no safe physical meaning can be inferred.
|
||||
|
||||
Pure numeric constant expressions infer `Dimensionless`.
|
||||
|
||||
## Custom Units
|
||||
|
||||
Custom linear-scale units use the same canonical system:
|
||||
|
||||
```python
|
||||
thou = scad.Unit("thou", scad.LENGTH, 0.0254)
|
||||
width = scad.var("width", 1000.0, unit=thou)
|
||||
assert width.evaluate() == 25.4
|
||||
```
|
||||
|
||||
Built-in units serialize as symbols. Custom units serialize a definition:
|
||||
|
||||
```json
|
||||
{
|
||||
"symbol": "thou",
|
||||
"dimension": {"length": 1, "angle": 0},
|
||||
"scale_to_canonical": 0.0254
|
||||
}
|
||||
```
|
||||
|
||||
Units are scale-only. Offset units such as Celsius/Fahrenheit are not represented.
|
||||
|
||||
## Validation Boundaries
|
||||
|
||||
Unit and dimension validation runs when:
|
||||
|
||||
1. A `Var`, `Dimension`, or `Unit` is created.
|
||||
2. An expression is directly evaluated.
|
||||
3. An expression is registered or imported through `ExpressionGraph`.
|
||||
4. A tolerance is analyzed or a requirement is declared.
|
||||
5. Session/model JSON is imported, exported, replayed, or translated.
|
||||
|
||||
Malformed units, cyclic graphs, duplicate expression IDs, mixed legacy/typed
|
||||
variables, incompatible dimensions, invalid roots, and invalid trigonometric inputs
|
||||
are rejected before graph mutation or geometry replay.
|
||||
|
||||
## Manufacturing Requirement Scope
|
||||
|
||||
Area, volume, inverse length, and other compound dimensions can be inferred and
|
||||
analyzed. Manufacturing requirements created by `check_tolerance()` or
|
||||
`GraphSession.require_tolerance()` currently accept final `Length` and `Angle`
|
||||
results only. This prevents an area or volume variation from being presented as a
|
||||
linear dimension requirement without explicit engineering semantics.
|
||||
|
||||
See [Dimension Tolerance Chains](dimension-tolerance-chains.md) for propagation,
|
||||
RSS assumptions, enforcement boundaries, and serialized requirement fields.
|
||||
@@ -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:
|
||||
|
||||
@@ -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))
|
||||
```
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Engineering Guides
|
||||
|
||||
- [STEP BREP 逆向工程方法](step-brep-reverse-engineering.md):从 STEP 检查、解析几何指纹、特征树推断、候选迭代,到几何点集、BREP 拓扑、参数历史和模型回放的完整工作流。
|
||||
|
||||
对应的可运行模块和案例:
|
||||
|
||||
```text
|
||||
src/simplecadapi/inverse_engineer/brep/
|
||||
examples/out/xzby_reverse/README.md
|
||||
examples/out/xzby_reverse/reverse_model.py
|
||||
```
|
||||
@@ -0,0 +1,49 @@
|
||||
# STEP BREP 逆向工程方法
|
||||
|
||||
该方法论是 SimpleCAD skill 的任务专用参考,规范版本位于:
|
||||
|
||||
```text
|
||||
skills/simplecadapi/references/inverse_engineer/brep-reverse-engineering.md
|
||||
```
|
||||
|
||||
正式检查工具位于:
|
||||
|
||||
```text
|
||||
src/simplecadapi/inverse_engineer/brep/
|
||||
```
|
||||
|
||||
Python API:
|
||||
|
||||
```python
|
||||
from simplecadapi.inverse_engineer import brep
|
||||
|
||||
report = brep.inspect_step(path="part.step")
|
||||
comparison = brep.compare_steps(target_path="target.step", candidate_path="candidate.step")
|
||||
brep.render_step_views(step_path="candidate.step", output_path="candidate-views.png")
|
||||
brep.compare_step_slices(
|
||||
target_path="target.step",
|
||||
candidate_path="candidate.step",
|
||||
output_path="slice-overlay.png",
|
||||
)
|
||||
```
|
||||
|
||||
渲染和图片截面功能需要可选依赖:
|
||||
|
||||
```bash
|
||||
uv sync --extra inverse-engineer
|
||||
```
|
||||
|
||||
CLI:
|
||||
|
||||
```bash
|
||||
uv run simplecad-brep inspect part.step -o part-report.json
|
||||
uv run simplecad-brep compare target.step candidate.step -o comparison.json
|
||||
uv run simplecad-brep render candidate.step candidate-views.png
|
||||
uv run simplecad-brep slices target.step candidate.step slice-overlay.png
|
||||
```
|
||||
|
||||
具体逆向案例见:
|
||||
|
||||
```text
|
||||
examples/out/xzby_reverse/README.md
|
||||
```
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user