Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
a8a84c6
editor: GUI-made features, a typed operation registry and the command…
NiJingzhe Nov 2, 2026
3171a01
editor: a blank notebook, the product cell with the first GUI feature…
NiJingzhe Nov 2, 2026
ff6c8a5
editor-web: a feature tree, a command bar and spec-generated paramete…
NiJingzhe Nov 2, 2026
1381ac8
editor: sketches with constraints as GUI features, and a solve endpoi…
NiJingzhe Nov 2, 2026
04dbb60
editor: assemblies as a GUI feature, with the connectors a part declares
NiJingzhe Nov 2, 2026
ad1e783
editor-web: a constrained sketch canvas
NiJingzhe Nov 2, 2026
a0fc3ea
editor: connectors of a GUI part, written into its product cell
NiJingzhe Nov 2, 2026
b1f6460
editor: a sketch can lie on any plane frame, and extrudes along its n…
NiJingzhe Nov 2, 2026
08d580e
sketch: trim_rsketch, extend_rsketch and diff_sketch_rsketchdiff as S…
NiJingzhe Nov 2, 2026
8069026
editor: sketch in the 3D viewport on a datum plane or a picked face; …
NiJingzhe Nov 2, 2026
b74eda7
editor-web: trim and extend tools, an assembly panel and part connectors
NiJingzhe Nov 2, 2026
a4ddd70
SDK: fillet_corner_rsketch, chamfer_corner_rsketch, project_rsketch a…
NiJingzhe Nov 2, 2026
f2aa06e
editor: the new SDK operations through the sketch document and the as…
NiJingzhe Nov 2, 2026
6ea7c02
editor-web: fillet and chamfer corner, project, an exploded view, and…
NiJingzhe Nov 2, 2026
c0908d2
editor: name clashes are a message, and a cell reported late is found…
NiJingzhe Nov 2, 2026
87ba477
editor-web: connector frames as real 3D objects, on shared scene-laye…
NiJingzhe Nov 3, 2026
f8c0a96
editor-web: sketches stay visible, pick a sketch to extrude, a contex…
NiJingzhe Nov 3, 2026
b19b54f
editor: a new solid says at once how it meets the body (join, cut, in…
NiJingzhe Nov 3, 2026
a7af887
editor-web: an operation chooser (join, cut, intersect) when a new so…
NiJingzhe Nov 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/skill/references/docs/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,9 @@ This index includes generated docs for the public SimpleCAD API surface, includi

## Advanced Features

- [chamfer_corner_rsketch](chamfer_corner_rsketch.md) *(from operators/sketch.py)* `top-level`
- [chamfer_rsolid](chamfer_rsolid.md) *(from operators/features.py)* `top-level`
- [fillet_corner_rsketch](fillet_corner_rsketch.md) *(from operators/sketch.py)* `top-level`
- [fillet_rsolid](fillet_rsolid.md) *(from operators/features.py)* `top-level`
- [helical_sweep_rsolid](helical_sweep_rsolid.md) *(from operators/features.py)* `top-level`
- [shell_rsolid](shell_rsolid.md) *(from operators/features.py)* `top-level`
Expand Down Expand Up @@ -380,8 +382,11 @@ This index includes generated docs for the public SimpleCAD API surface, includi
- [constrain_symmetric_rsketch](constrain_symmetric_rsketch.md) *(from operators/sketch.py)* `top-level`
- [constrain_tangent_rsketch](constrain_tangent_rsketch.md) *(from operators/sketch.py)* `top-level`
- [constrain_vertical_rsketch](constrain_vertical_rsketch.md) *(from operators/sketch.py)* `top-level`
- [diff_sketch_rsketchdiff](diff_sketch_rsketchdiff.md) *(from operators/sketch.py)* `top-level`
- [draft_rsolid](draft_rsolid.md) *(from operators/features.py)* `top-level`
- [explain_tag](explain_tag.md) *(from operators/selection.py)* `top-level`
- [explode_rassembly](explode_rassembly.md) *(from operators/product.py)* `top-level`
- [extend_rsketch](extend_rsketch.md) *(from operators/sketch.py)* `top-level`
- [fill_holes_rshell](fill_holes_rshell.md) *(from operators/geometry.py)* `top-level`
- [fit_point_grid_rface](fit_point_grid_rface.md) *(from operators/geometry.py)* `top-level`
- [free_boundaries_rwirelist](free_boundaries_rwirelist.md) *(from operators/geometry.py)* `top-level`
Expand All @@ -408,6 +413,7 @@ This index includes generated docs for the public SimpleCAD API surface, includi
- [placement_frame_mm](placement_frame_mm.md) *(from product/placement.py)* `submodule:product/placement`
- [placement_from_canonical](placement_from_canonical.md) *(from product/placement.py)* `submodule:product/placement`
- [placement_ticks](placement_ticks.md) *(from product/placement.py)* `submodule:product/placement`
- [project_rsketch](project_rsketch.md) *(from operators/sketch.py)* `top-level`
- [prop](prop.md) *(from ql.py)* `submodule:ql`
- [radial_pattern_rsolidlist](radial_pattern_rsolidlist.md) *(from operators/transform.py)* `top-level`
- [register_property_resolver](register_property_resolver.md) *(from ql.py)* `submodule:ql`
Expand All @@ -422,6 +428,7 @@ This index includes generated docs for the public SimpleCAD API surface, includi
- [source_topology](source_topology.md) *(from ql.py)* `submodule:ql`
- [tag](tag.md) *(from ql.py)* `submodule:ql`
- [transform_rsolid](transform_rsolid.md) *(from operators/transform.py)* `top-level`
- [trim_rsketch](trim_rsketch.md) *(from operators/sketch.py)* `top-level`
- [trim_surface_rface](trim_surface_rface.md) *(from operators/geometry.py)* `top-level`
- [twisted_sweep_rsolid](twisted_sweep_rsolid.md) *(from operators/features.py)* `top-level`
- [unground_component_rassembly](unground_component_rassembly.md) *(from operators/product.py)* `top-level`
Expand Down
21 changes: 21 additions & 0 deletions docs/skill/references/docs/api/chamfer_corner_rsketch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# chamfer_corner_rsketch

## API Definition

```python
def chamfer_corner_rsketch(sketch, a, b, distance)
```

*Source: operators/sketch.py*

## Import Surface

- top-level: `from simplecadapi import chamfer_corner_rsketch`

## Description

Cut the corner where lines ``a`` and ``b`` meet with a straight line ``distance`` from the corner along each line.

Same rules as fillet_corner_rsketch: the lines must share an end point, both are shortened, the corner point goes
away unless something else uses it. The new line ``l<n>`` joins the two points, with a driving length (its chord)
and a driving angle to ``a``. ``distance`` must be shorter than the shorter line (the message names it).
20 changes: 20 additions & 0 deletions docs/skill/references/docs/api/diff_sketch_rsketchdiff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# diff_sketch_rsketchdiff

## API Definition

```python
def diff_sketch_rsketchdiff(before, after)
```

*Source: operators/sketch.py*

## Import Surface

- top-level: `from simplecadapi import diff_sketch_rsketchdiff`

## Description

Report what an edit did: entity and constraint ids created, removed or changed between two sketches.

Returns a dict with ``created``, ``removed``, ``changed`` (entity ids, points included) and
``created_constraints``, ``removed_constraints``, ``changed_constraints``. Not recorded in the graph.
28 changes: 28 additions & 0 deletions docs/skill/references/docs/api/explode_rassembly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# explode_rassembly

## API Definition

```python
def explode_rassembly(assembly, factor, *, center=None, axis=None, directions=None, weights=None)
```

*Source: operators/product.py*

## Import Surface

- top-level: `from simplecadapi import explode_rassembly`

## Description

Return a display copy of an assembly with its components pushed apart for an exploded view.

Each top-level component moves by ``factor * weight * direction * size``: ``size`` is the diagonal of the box that
holds every component's bodies, ``weight`` is ``weights[component_id]`` (default 1; list a component with 0 to keep
it, for example the grounded base) and ``direction`` is ``directions[component_id]`` (normalised), else ``axis``
(signed by the side of the centre the component is on), else the vector from the centre to the component's own
centre (the centre of the box of its bodies). ``center`` overrides the assembly centre (the centre of the box of
all components). A component with no direction (it is at the centre, or on the centre plane of ``axis``) stays and
is listed in ``get_metadata("explode")``, together with factor, size, centre and every displacement. ``factor=0``
gives the same placements. Parts, constraints, grounding and public connectors are untouched and the constraints
are not re-solved: the result is a placement set for display, rendering and export, and solving it moves the
components back.
21 changes: 21 additions & 0 deletions docs/skill/references/docs/api/extend_rsketch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# extend_rsketch

## API Definition

```python
def extend_rsketch(sketch, entity, end, *, to=None)
```

*Source: operators/sketch.py*

## Import Surface

- top-level: `from simplecadapi import extend_rsketch`

## Description

Lengthen a sketch line at ``end`` ('start' or 'end') until it meets ``to`` (default: the nearest entity ahead).

The sketch is solved first; the end point moves onto the other entity and is tied to it with a point_on
constraint. Refused when the end point is shared with another entity. The length dimension of the line, and
dimensions or fix on the moved point, no longer hold and are dropped.
27 changes: 27 additions & 0 deletions docs/skill/references/docs/api/fillet_corner_rsketch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# fillet_corner_rsketch

## API Definition

```python
def fillet_corner_rsketch(sketch, a, b, radius)
```

*Source: operators/sketch.py*

## Import Surface

- top-level: `from simplecadapi import fillet_corner_rsketch`

## Description

Round the corner where lines ``a`` and ``b`` meet with an arc of ``radius``.

The lines must share an end point (the corner). The sketch is solved first; both lines are shortened to the
tangent points and the arc (counter-clockwise from its start to its end around its centre) is tied to each line
with a tangent constraint and to ``radius`` with a driving radius dimension, so the result survives a re-solve.
The corner point goes away unless a constraint or another entity uses it (then it stays as the sharp corner and
the cut-off parts of the lines stay as construction lines). horizontal/vertical and the other constraints that
hold for a shorter line stay; lengths and other dimensions of the lines are dropped (see
diff_sketch_rsketchdiff). Refused: parallel lines, lines that share no point, a radius too large for the
shorter line (the message names the largest radius). New ids: points ``p<n>``, arc ``a<n>``, legs ``l<n>``,
constraints ``c_<kind>_<n>``.
28 changes: 28 additions & 0 deletions docs/skill/references/docs/api/project_rsketch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# project_rsketch

## API Definition

```python
def project_rsketch(sketch, source, *, entities=None, construction=True)
```

*Source: operators/sketch.py*

## Import Surface

- top-level: `from simplecadapi import project_rsketch`

## Description

Bring geometry from another sketch, or from the edges of shapes, into the sketch as reference geometry.

``source`` is a Sketch (any plane; it is solved first), a Solid / Face / Shell / Wire / Edge / Compound or a list
of shapes (what ``ql.edges().resolve(shape)`` returns). Points go through the plane frames: u = (P - origin) .
x_axis, v = (P - origin) . y_axis. Points, lines, circles and arcs in a parallel plane are copied (an arc keeps its
counter-clockwise sense); a circle or arc seen edge on becomes a line, a line along the sketch normal a point, an
oblique full circle an ellipse; an oblique arc, and edges that are not lines or circles, are refused by name.
``entities`` limits the source sketch entities (default: all lines, circles, arcs and free points; ellipses and
B-splines are skipped and listed in ``get_metadata("last_edit")``); it is not used with shapes. The copies are
``construction`` (default True) and fixed at their projected coordinates. Ids are ``proj_<source sketch id>_<n>``
(``proj_shape_<n>`` for shapes); ``get_metadata("projections")`` lists {source, entity, projected} and projecting
the same source again reuses the entities already there.
23 changes: 23 additions & 0 deletions docs/skill/references/docs/api/trim_rsketch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# trim_rsketch

## API Definition

```python
def trim_rsketch(sketch, entity, at, *, against=None)
```

*Source: operators/sketch.py*

## Import Surface

- top-level: `from simplecadapi import trim_rsketch`

## Description

Remove the part of a line, circle or arc that contains ``at``, between the nearest cuts.

A cut is a crossing with another non-construction entity (``against`` limits which ones) or an end of the
entity. ``at`` is a point (x, y) in sketch coordinates or a point id. Lines and arcs keep the pieces on either
side, a circle keeps the arc left over (it needs two cuts), an entity nothing crosses is removed whole. Cut
points are tied to the cutter with point_on; line pieces inherit horizontal/vertical; constraints that name
the removed entity are removed. Use diff_sketch_rsketchdiff(before, after) to see what changed.
45 changes: 43 additions & 2 deletions docs/skill/references/docs/core/operation_graph_json_spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -910,8 +910,10 @@ fields when their target expression is unitless.
- 多输出 graph 的最终结果集由 `leaf_ids` 显式声明
- replay 默认 strict;permissive 仅通过 API 参数显式 opt-in
- importer 仍识别冻结的 `2.0` contract,但 `2.0` graph 不得使用
`load_brep_region_rshell`、`load_brep_region_rsolid` 或
`make_solid_from_shell_rsolid`。
`load_brep_region_rshell`、`load_brep_region_rsolid`、
`make_solid_from_shell_rsolid`、`trim_rsketch`、`extend_rsketch`、
`fillet_corner_rsketch`、`chamfer_corner_rsketch`、`project_rsketch` 或
`explode_rassembly`。
- 为兼容早期 `2.0` model payload,importer 将缺失的
`canonical_contract` 视为最小 legacy `{"contract_version": "2.0"}`;
新导出的 model payload 仍必须显式包含完整 contract。
Expand Down Expand Up @@ -1012,6 +1014,11 @@ New canonical profile nodes use the `make_*_r*` names listed in `canonical_contr
- `add_circle_rsketch`
- `add_arc_rsketch`
- `add_bspline_rsketch`
- `trim_rsketch`
- `extend_rsketch`
- `fillet_corner_rsketch`
- `chamfer_corner_rsketch`
- `project_rsketch`
- `make_constrain_coincident_rsketch`
- `make_constrain_point_on_rsketch`
- `make_constrain_horizontal_rsketch`
Expand Down Expand Up @@ -1309,6 +1316,40 @@ Notes:
- Inputs: 1 `Sketch`
- Outputs: 1 updated `Sketch`
- Params: `sketch_id`, `entity_id`, `start`, `end`, `control_points`, `degree`, `knots`, `multiplicities`, `weights`, `periodic`, `construction`
#### `trim_rsketch`

- Inputs: 1 `Sketch`
- Outputs: 1 updated `Sketch`
- Params: `sketch_id`, `entity`, `at`, `against`
- `at` is `[x, y]` in sketch coordinates or a point id; `against` is a list of entity ids or `null` (every non-construction entity). The edit is made on the solved geometry, so replay is deterministic.

#### `extend_rsketch`

- Inputs: 1 `Sketch`
- Outputs: 1 updated `Sketch`
- Params: `sketch_id`, `entity`, `end` (`start` or `end`), `to` (entity id or `null`)

#### `fillet_corner_rsketch` / `chamfer_corner_rsketch`

- Inputs: 1 `Sketch`
- Outputs: 1 updated `Sketch`
- Params: `sketch_id`, `a`, `b` (line ids), `radius` (fillet) or `distance` (chamfer)
- The edit is made on the solved geometry and picks its own ids (`p<n>`, `a<n>`, `l<n>`, `c_<kind>_<n>`), so replay is deterministic. `radius` / `distance` are recorded as symbolic parameters when they were `Var` / `Expr`.

#### `project_rsketch`

- Inputs: the target `Sketch` and either the source `Sketch` (`source_kind: "sketch"`: inputs `[target, source]`) or the projected shapes (`source_kind: "shapes"`: the shape inputs, then the target `Sketch`)
- Outputs: 1 updated `Sketch`
- Params: `sketch_id`, `source_kind`, `source` (source sketch id, or the number of shapes), `entities` (source entity ids or `null`; sketch sources only), `construction`
- Projected ids are `proj_<source sketch id>_<n>` (`proj_shape_<n>` for shapes); the target sketch's `projections` metadata lists `{source, entity, projected}`.

#### `explode_rassembly`

- Inputs: 1 `Assembly`
- Outputs: 1 `Assembly` (a display copy: only component placements differ)
- Params: `assembly_id`, `factor`, `center`, `axis` (3-vectors or `null`), `directions` (component id to 3-vector), `weights` (component id to number)
- `factor` is recorded as a symbolic parameter when it was a `Var` / `Expr`. Constraints are not re-solved; solving the result moves the components back.

#### `make_constrain_*_rsketch`

- Inputs: 1 `Sketch`
Expand Down
25 changes: 25 additions & 0 deletions docs/skill/references/domains/assembly-and-product.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,35 @@ composing notebooks.
`NotebookConfig`, `sca run` CLI.
- Product build: `part`, `assemble`, `file_input`, `PartBuildResult`,
`AssemblyBuildResult`.
- Exploded view: `explode_rassembly(assembly, factor, *, center, axis,
directions, weights)` returns a display copy whose component placements are
pushed apart (see "Exploded view" below).
- Packages: `capture`, `CaptureResult`, `ProductPackage`,
`build_product_package`, `read_product_package`, `load_product_package`,
`validate_product_package`.

## Exploded view

`explode_rassembly(assembly, factor, ...)` is a pure function of the assembly:
each top-level component moves by `factor * weight * direction * size`, where
`size` is the diagonal of the box that holds all component bodies, so
`factor=1` is a clear explosion and `factor=0` gives the same placements.
`direction` is `directions[id]`, else `axis` (signed by the side of the centre),
else from the assembly centre (`center`, default the centre of the bodies' box)
to the component's own centre. A component with no direction (at the centre)
stays and is listed in `get_metadata("explode")`; to keep a component, such as
the grounded base, put it in `weights` as `0` (nothing is special-cased).

```python
view = scad.explode_rassembly(assembly, 1.0, weights={"base": 0.0})
view = scad.explode_rassembly(assembly, 0.6, axis=(0, 0, 1), directions={"cover": (1, 0, 0)})
```

The result is for display, rendering and export of an exploded view: parts,
constraints, grounding and public connectors are unchanged and the constraints
are not re-solved, so solving the result moves the components back. It is
recorded in the graph and replays from model JSON.

## Validation gates

- Placement semantics: moving a component changes placement only; the part's
Expand Down
54 changes: 54 additions & 0 deletions docs/skill/references/domains/editor-collaboration.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,60 @@ Errors there are the same errors `sca snapshot` reports per cell. A module that
does not compile is reported as `setup failed` with the file and line; fix that
file first, since every cell is blocked until it compiles.

## Features the person made with buttons

The editor also has a Features tab and an operation palette (box, cylinder, extrude, hole, fillet, patterns ...). A click
writes two ordinary cells, so you read and edit them like any other:

```python
# ---- params: hole ----
HOLE_DIAMETER = scad.var(name="hole_diameter", default=6.0, unit="mm", comment="Diameter") # ui: slider(...)

# ---- feature: hole (subtract) ----
# gui: {"spec":"hole","v":1,"values":{...}}
...one operation that makes the new name `hole`...
```

- The `# gui:` line is how the form is rebuilt. A cell without it is code and the form never rewrites it. To take a
GUI feature over as code, delete the `# gui:` line (or ask the person to); to change a number, edit the `# params:` cell
or the form, not the call.
- GUI commands carry the code they were made from: if you edited the cell in between, the command is refused (409), it
does not overwrite you. The same holds for the person's undo of a GUI command.
- Selections in a GUI cell are written inline, resolved against the body the cell receives: the face tags the box, cylinder
or extrude was given (`<slug>.top`, `<slug>.side` ... they survive booleans, not fillets or chamfers) first, the picked
geometry second, with the count checked (`.exactly(n)`). If the model moved so that nothing matches, the cell fails and
the feature turns red: ask the person to pick again, never widen the selector until it matches.
- A blank notebook (`sca new NAME --kind blank`) has no cells; the first feature adds the product cell
(`<name> = scad.Part(...)`), and later features that consume the body it builds from move it along.
- Unattended: the buttons are not available; write the cells yourself (see `discipline/notebook-cell-design.md`).

## The same operations from the command line

An agent does not click: `sca feature` and `sca sketch` do what the buttons do, with the same checks, as JSON in and JSON out.

```bash
sca feature specs [SPEC] # operations and their typed slots (lengths mm, angles deg, bodies by name)
sca feature ls # features, bodies, tip, product, defaults
sca feature add box --name base --values '{"width": 40, "height": 30, "depth": 20}'
sca feature add fillet --values '{"radius": 2}' --selections '{"edges": [{"kind": "edge", "properties": {...}, "geometry": {...}}]}'
sca feature edit base --values '{"width": 50}' # a GUI feature; a code feature is refused
sca feature rm base | sca feature undo | sca feature redo
sca sketch ls | get CELL | put CELL -f doc.json | solve CELL
sca sketch op CELL trim --args '{"entity": "h", "at": [8, 0]}' [--dry-run]
sca sketch op CELL extend --args '{"entity": "h", "end": "end"}'
```

- The edits are the SDK's own operations, not editor features: `sca sketch op CELL trim ...` calls `scad.trim_rsketch` on the
sketch the document builds and writes the result back, so the same edit in a notebook cell is
`_sketch = scad.trim_rsketch(sketch=_sketch, entity="h", at=(8.0, 0.0))`; `scad.diff_sketch_rsketchdiff(before=..., after=...)`
says what changed (`created`, `removed`, `changed` entities and `created_constraints`, `removed_constraints`,
`changed_constraints`). `trim` cuts an entity at the crossings around the point `at` on the solved geometry and ties each cut
point to the entity that made it (`point_on`); a piece keeps the horizontal/vertical constraint of its line; dimensions that
no longer hold are removed (they appear in `removed_constraints`). New ids are the lowest free `p<n>`, `l<n>`, `a<n>` and
`c_<kind>_<n>`. `--dry-run` prints without writing.
- Errors name ids (`there is no entity 'x'`, `nothing is ahead of the end of h`); fix the call, do not retry it unchanged.
- Everything a command writes is an ordinary cell; `sca cell` still works on it (see the `# gui:` rules above).

## Rules of thumb

- Re-read the cell before editing (`sca cell get`, or the file): the person edits and
Expand Down
Loading
Loading