Files
cadSet/SimpleCADAPI/skills/simplecadapi/references/MODELING_WORKFLOWS.md

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.model for a top-level replayable entry point. It owns one GraphSession and returns a ModelResult; use @scad.requires_session for 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) and get_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_json as the interchange boundary for new model entry points. Use export_model_json(session=...) for lower-level direct sessions.
  • Declare directly controlled dimensions with var(..., unit=..., tolerance=..., tolerance_unit=...), and attach derived requirements with GraphSession.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, or replay_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.md before hand-modeling a standard mechanical part.
  • Use references/docs/stdlib/<function_name>.md for 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), or get_vertices(index) for intentional indexed picks in examples.
  • Attach local semantic tags with apply_tag(shape=..., tag=...). Use apply_tag_rselection(...) for selector targets, explicit downward inheritance, or immutable semantic branches.
  • Inspect with list_tags(shape=..., scope=...) and explain_tag(...); effective excludes lineage.
  • 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: one roles require exactly one proven target, and many roles 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_tag targets the one result Solid. Role tags target Faces except for shell.wall, which targets Edges.
  • tag_prefix creates topology-identity tags such as body.face.side.right; role-specific tag parameters and result_tag create tags such as role.mounting_surface and part.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, and box.right Face roles. It does not expose Edge roles; use tagged incident Faces with QL incident_to(...) or shared_boundary(...) for exact Box Edge selection.
  • Cone exposes cone.start, optional cone.end, and cone.side Face roles plus cone.start_boundary, cone.end_boundary, and cone.seam Edge 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_face set.
  • 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_id when constructing a constrained Sketch.
  • Promote through make_wire_from_sketch_rwire(...) or make_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") or explain_tag(...); the evidence contains topology_name and SketchPromotionMap.
  • A promoted profile receives sketch.<sketch-name>.profile.<profile-id> and each boundary Edge receives sketch.<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 and intersect_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_case for a guaranteed conservative envelope.
  • Use rss only 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 mm and deg values in CAD operations.
  • Read references/docs/core/physical-units.md for unit and inference rules.
  • Read references/docs/core/dimension-tolerance-chains.md for complete rules.