11 KiB
Modeling Workflows
Modeling Mental Model
- Model the part as a sequence of intentional operations, not as one opaque final shape.
- Use the standard parts library first when a requested standard component is available and does not need complex custom geometry changes.
- Start from profiles and reference geometry, then create solids with features such as extrude, revolve, loft, and sweep.
- Use booleans and detail features after the base form is clear: cut openings, union intended merged bodies, then apply fillets, chamfers, or shell operations.
- Use
@scad.modelfor a top-level replayable entry point. It owns oneGraphSessionand returns aModelResult; use@scad.requires_sessionfor child builders. - Use QL for grounding and selection. Query the facts you need, such as face normals, centers, areas, edge lengths, curve types, and tags.
- Use indexed child-geometry getters such as
get_edges(index)andget_faces(index)when an indexed topology pick is intentional. - Use semantic tags for design intent and anchors. Keep numeric measurements and geometry facts in metadata or model JSON payloads.
- Treat
ModelResult.model_jsonas the interchange boundary for new model entry points. Useexport_model_json(session=...)for lower-level direct sessions. - Declare directly controlled dimensions with
var(..., unit=..., tolerance=..., tolerance_unit=...), and attach derived requirements withGraphSession.require_tolerance(...)when manufacturing variation matters. - Validate incrementally: after each major step, print small QL-derived facts such as selected face count, top face center, edge count, volume, or replay result count.
1) Capture a replayable modeling flow
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()
payload = result.model_json
rebuilt = result.replay()
2) Import and use in Python
import simplecadapi as scad
3) Keep replay payloads as the interchange boundary
- Prefer
export_model_json()output instead of hand-written payloads. - Use
ModelResult.replay()for a model invocation, orreplay_model_json(json_str=...)when consuming standalone model JSON. - Use
import_model_json()when consuming previously exported payloads.
4) Use standard parts when they fit
import simplecadapi as scad
gear = scad.std.gear.make_spur_gear_rsolid(
n_teeth=24,
module=1.5,
gear_height=8.0,
)
rack = scad.std.gear.make_spur_rack_rsolid(module=1.5, n_teeth=18)
bearing = scad.std.bearing.make_ball_bearing_rassembly(
bore_diameter=8.0,
outer_diameter=22.0,
bearing_width=7.0,
ball_diameter=3.5,
)
- Read
references/docs/stdlib/README.mdbefore hand-modeling a standard mechanical part. - Use
references/docs/stdlib/<function_name>.mdfor exact standard-library signatures. - Continue with core geometry APIs when the standard part requires substantial custom geometry beyond the provided parameters.
5) QL-grounded feature workflow
import simplecadapi as scad
from simplecadapi import ql
@scad.model(graph_id="swept_profile")
def build_model():
profile = scad.make_circle_rface(center=(0, 0, 0), radius=1.0)
body = scad.extrude_rsolid(
profile=profile,
direction=(0, 0, 1),
distance=4.0,
end_face_tag="role.sweep_profile",
result_tag="part.body",
)
end_face = (
ql.faces()
.where(ql.output_role(role_name="extrusion.end"))
.exactly(1)
.resolve(body)[0]
)
print("end face center", end_face.get_center())
path = scad.make_segment_rwire(start=(0, 0, 4), end=(0, 0, 8))
swept = scad.sweep_rsolid(profile=end_face, path=path)
scad.capture_result(value=swept)
return swept
result = build_model()
rebuilt = result.replay()
print("rebuilt", len(rebuilt))
6) Selection and tag discipline
- Prefer QL selectors for semantic/geometric feature input selection.
- Use
get_edges(index),get_faces(index),get_wires(index), orget_vertices(index)for intentional indexed picks in examples. - Attach local semantic tags with
apply_tag(shape=..., tag=...). Useapply_tag_rselection(...)for selector targets, explicit downward inheritance, or immutable semantic branches. - Inspect with
list_tags(shape=..., scope=...)andexplain_tag(...);effectiveexcludeslineage. - Use tags for topology identity, intent, roles, anchors, and groups.
- Store dimensions, positions, measured geometry, operation events, source roles, and descriptive payloads in metadata or model JSON, not in tags.
- Keep QL result prints concise: selected count, centers, normals, areas, lengths, or tags.
7) Kernel-proven feature output roles
- Feature output roles are typed tracking evidence, not user tag strings. Query them with
ql.output_role(role_name=...). - The feature APIs expose one named tag argument per output role. Every requested role is strict:
oneroles require exactly one proven target, andmanyroles require at least one and tag the full proven set. - There is no generic role-to-tag mapping. Unknown or unavailable roles and non-normalized tags fail before returning geometry.
result_tagtargets the one result Solid. Role tags target Faces except forshell.wall, which targets Edges.tag_prefixcreates topology-identity tags such asbody.face.side.right; role-specific tag parameters andresult_tagcreate tags such asrole.mounting_surfaceandpart.body. They share one tag API, while their evidence and projection policies differ. Tag text alone never establishes topology identity.- Box exposes the exact
box.bottom,box.top,box.front,box.back,box.left, andbox.rightFace roles. It does not expose Edge roles; use tagged incident Faces with QLincident_to(...)orshared_boundary(...)for exact Box Edge selection. - Cone exposes
cone.start, optionalcone.end, andcone.sideFace roles pluscone.start_boundary,cone.end_boundary, andcone.seamEdge roles. A pointed cone has no end cap Face, but its end boundary is retained as the kernel's degenerate apex Edge. - A 360-degree revolve has no distinct start/end caps. Requesting those tags fails rather than inventing cap roles.
- Shell role availability follows actual OCC evidence. For example, an operation may prove offset faces, closing descendants, and wall edges without proving a
shell.body_faceset. - Sweep currently rejects a profile with inner wires rather than silently sweeping only its outer wire.
body = scad.extrude_rsolid(
profile=profile,
direction=(0, 0, 1),
distance=10.0,
start_face_tag="anchor.base",
end_face_tag="role.mounting_surface",
side_faces_tag="group.outer_walls",
result_tag="part.body",
)
mounting_face = (
ql.faces()
.where(ql.output_role(role_name="extrusion.end"))
.exactly(1)
.resolve(body)[0]
)
print(scad.list_tags(shape=mounting_face, scope="local"))
In a GraphSession, each requested assignment becomes a canonical
apply_tag_rselection semantic node. The feature node remains geometry-only, and
replay validates both the recomputed role set and the exact selected refs.
8) Replay-safe source projection
When a feature supports source projection, keep the returned semantic view in the feature's input chain:
profile = scad.make_rectangle_rface(width=5.0, height=3.0)
source_edge = profile.get_edges(0)
profile = scad.apply_tag_rselection(
scope=profile,
targets=[source_edge],
tag="role.source_edge",
)
source_edge = scad.select_edges_by_tag(
shape=profile,
tag="role.source_edge",
scope="local",
)[0]
source_binding_id = scad.explain_tag(
shape=source_edge,
tag="role.source_edge",
scope="local",
)[0]["binding_id"]
body = scad.extrude_rsolid(
profile=profile,
direction=(0, 0, 1),
distance=2.0,
)
projected_face = (
ql.faces()
.where(ql.source_binding(binding_id=source_binding_id))
.exactly(1)
.resolve(body)[0]
)
print(scad.list_tags(shape=projected_face, scope="local"))
The projected binding preserves the exact source binding ID, source topology ID,
target topology ID, operation, role, and evidence method. ql.source_binding(...)
and ql.source_topology(...) inspect this local evidence. They do not search tag
text or infer ancestry from geometry. Calling apply_tag_rselection(...) but then
passing the original profile to the feature creates a detached semantic branch and
does not authorize hidden coupling.
9) Constrained Sketch topology tags
- Give every profile entity a stable explicit
entity_idwhen constructing a constrained Sketch. - Promote through
make_wire_from_sketch_rwire(...)ormake_face_from_sketch_rface(...); the promotion map is the exact entity-to-Edge correspondence. - Inspect canonical topology-identity tags with
ql.tag("sketch.rect.entity.right")orexplain_tag(...); the evidence containstopology_nameandSketchPromotionMap. - A promoted profile receives
sketch.<sketch-name>.profile.<profile-id>and each boundary Edge receivessketch.<sketch-name>.entity.<entity-id>. - If the generated Edge count differs from the promotion map, fail rather than guessing correspondence.
- A feature can project a Sketch entity topology tag only when kernel history proves one source Edge to one result Face.
10) Boolean and body discipline
- Use
union_rsolid(...)when multiple solids should become one integrated body. - Ensure bodies that should union into one solid have real geometric overlap or embedding.
- Use
cut_rsolid(...)for subtractive features andintersect_rsolid(...)for common-volume workflows. - Validate body count and volume after major boolean operations.
11) Dimension tolerance chains
width = scad.var("width", 10.0, unit="mm", tolerance=0.1)
gap = scad.var("gap", 0.5, unit="mm", tolerance=(-0.05, 0.1))
overall = width + gap
with scad.GraphSession() as session:
body = scad.make_box_rsolid(width, 2.0, 1.0)
session.require_tolerance(overall, (-0.15, 0.2), tolerance_unit="mm", name="overall")
report = session.validate_tolerances(raise_on_failure=True)
- Use
worst_casefor a guaranteed conservative envelope. - Use
rssonly when distinct source dimensions can be treated as independent. - Every source variable in a chain must have a declared tolerance.
- Unit-aware expressions infer dimensions automatically and cannot mix legacy variables without units.
- Length and angle use canonical
mmanddegvalues in CAD operations. - Read
references/docs/core/physical-units.mdfor unit and inference rules. - Read
references/docs/core/dimension-tolerance-chains.mdfor complete rules.