Skip to content

editor: GUI-first modeling over SDK operations (features, sketches, assemblies) and new sketch/assembly operations - #72

Merged
NiJingzhe merged 19 commits into
devfrom
feat/gui-modeling
Nov 8, 2026
Merged

NiJingzhe merged 19 commits into
devfrom
feat/gui-modeling

Conversation

@NiJingzhe

Copy link
Copy Markdown
Collaborator

What this adds

A button-first way to model in sca edit, built on one rule: every capability is an SDK operation an agent can call in code; the editor is a UI over them.

SDK (C++, recorded, replayable, stubbed, documented, tested)

  • trim_rsketch, extend_rsketch, diff_sketch_rsketchdiff
  • fillet_corner_rsketch, chamfer_corner_rsketch, project_rsketch (relates sketches on different planes)
  • explode_rassembly (exploded view as an assembly operation, factor is a scad.var)
  • the new operations join the canonical core op set

Editor (Python)

  • a typed operation registry (24 operations) that renders ordinary cells (# ---- params ---- + # ---- feature ---- with a # gui: line); drift tests check every template against the live signatures
  • commands: add / edit / delete / undo / redo / set connectors, over POST /api/features; every edit carries the code it was made from, so a concurrent agent edit is refused (409), never overwritten
  • sketches as a document (24 constraint kinds), POST /api/sketch/solve, and POST /api/sketch/op which only calls the SDK edit operations; sketches can lie on any plane frame (a picked face)
  • assemblies as one feature rendered to params / scad.use / one layout cell (no cell carries the assembly through a chain); connectors of a part; exploded view
  • a new solid says at once how it meets the body (join / cut / intersect / new): a part holds one solid
  • sca feature ... and sca sketch ... for agents; sca new --kind blank
  • skill: editor-collaboration.md documents the # gui: rules and the commands

Editor web UI

  • floating command bar (categories, search, Enter), feature tree with undo/redo, spec-generated parameter dialogs, contextual action bar by selection
  • sketching in the 3D viewport on a datum plane or a picked face; sketches stay visible; pick a sketch, then extrude
  • Trim / Extend / Fillet / Chamfer / Project tools; assembly panel with connector frames as real 3D objects (reusable scene/layers and components/scene)
  • operation chooser (join / cut / intersect)

Also in this branch: the skill entry redesign is not here (already on dev); the C++ SDK operations are the first commits.

Tests

  • Python: 2358 passed, 46 skipped (full test/, the seven untracked old test files excluded as before)
  • editor-web: 256 vitest tests, typecheck and build clean
  • C++: doctest for the new operations (sketch 72 cases, product 49, semantic 76 on the author's build); clang-format 18.1.8 clean; stubs fresh; skillbuild --check OK
  • driven in a real browser (ego-browser) for each flow; keyboard events could not be sent by that tool, so key handling was tested with synthetic events

Known limits

  • Translators (FreeCAD/Fusion/SolidWorks) do not know the new sketch/assembly operations
  • project from model edges works in the SDK; the editor only wires sketch-to-sketch projection
  • scene nodes carry no component_id; the frontend matches the last segment of the node id
  • two sketch display systems overlap (scene/sketch-layer.ts and scene/layers/); to be unified

🤖 Generated with Claude Code

NiJingzhe and others added 19 commits November 3, 2026 01:20
…s over it

features.py is a hand-curated table of 24 operations (primitives, profiles, booleans, hole, fillet, chamfer, shell,
patterns, transforms) with typed slots and a template that renders the cells; a drift test checks every template against
the live signatures. gui_features.py adds, edits and deletes features through the editor's own cell commands (an edit
carries the code it was made from, so an agent's concurrent edit is never overwritten) and keeps an undo/redo history.
Routes: GET /api/features/specs, GET /api/features, POST /api/features.

Generated cells are ordinary cells (params cell + feature cell, selectors inline, a '# gui:' line to rebuild the form).
Primitives and extrudes carry readable face tags; a selection recipe tries the tags first and checks the count.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…, and the protocol in the skill

sca new --kind blank writes a part notebook with no cells; the first GUI feature adds the product cell that builds the Part
from it, and later features that consume that body move it along. editor-collaboration.md says what a GUI cell looks like,
that GUI commands never overwrite an edit made in between, and how the inline selection recipes behave.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…r dialogs

The Features tab lists the notebook's features (GUI-made ones editable, code ones marked), shows each one's errors, offers
undo/redo and a banner when the product does not build from the tip. Operations are added from a floating command bar
(category chips, keyword search, Up/Down/Enter, docked top or bottom, collapsible) fed by a command registry; each one opens a
non-modal dialog generated from the operation's typed slots, with viewport picks for faces and edges and a 'use picked face'
helper for holes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…nt for a canvas

sketch_doc.py is the document a 2D canvas edits (points, lines, circles, arcs, 24 constraint kinds, the profile that becomes
the face): validation (every id and number that reaches code is checked), the sketch cell it renders to (dimensions become
scad.var sliders in the params cell), and solve(): the SDK solver without recording anything, returning status, degrees of
freedom, solved points, diagnostics and the profiles (about 1.3 ms for a small sketch). POST /api/sketch/solve serves it.
features.py gets a face-producing 'sketch' operation and extrude/revolve/cut/boss from a sketch; the feature service keeps
solids and profiles apart (tip, open bodies, defaults, the plane normal a profile extrudes along).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
assembly_doc.py is the document a GUI edits (components with placements, the grounded one, fixed/revolute/prismatic/gear/belt/
rack-pinion constraints between connector frames): validation, and three cells it renders to (params sliders for placements and
joint drives, one scad.use per child, one layout cell that builds and solves the assembly in a single pass so no cell carries the
whole assembly through a chain). The feature service adds, edits, deletes and undoes the three cells together and keeps the
undo history valid when a cell is re-created. GET /api/assembly/parts lists the workspace's parts and, with ?path=, the
connectors a part declares.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An SVG 2D editor for the sketch document: select/move, point, line, circle, arc, construction, auto horizontal/vertical, pan and
zoom, snapping; a constraint toolbar that enables by the current selection; dimensions edited in place (double-click the label);
live solve at every drag step with a temporary fix on the dragged point; status, degrees of freedom and diagnostics; closed loops
found client-side and matched to the solver's profiles so the outer profile and the holes can be picked; plane selector. The
sketch feature dialog hosts it in a wide docked panel; profile slots are selects over the notebook's sketches and the
extrude direction follows the profile's plane normal.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
set_connectors replaces the list of connector frames of the part the product cell builds; the list lives in a marker line the GUI
reads back, each frame becomes an add_connector_rpart call, and the first product the GUI makes is a recorded make_part_rpart so
connectors can be added to it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ormal

The sketch plane is a name (XY, XZ, YZ) or a frame {origin, x_axis, y_axis} (the SDK already takes one), validated to be unit and
square; profiles report their normal vector, and the sketch-based operations take 'normal of the sketch plane' as a direction,
which is the default. This is what sketching on a picked face needs.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…DK operations

Trim removes the part of a line, circle or arc that contains a point, between the nearest crossings (or the ones named in
against); extend lengthens a line until it meets the next entity. Both work on the solved geometry, keep the sketch connected
(point_on ties, horizontal/vertical kept, dimensions that cannot hold dropped), are recorded in the operation graph and replay
from model JSON. diff_sketch_rsketchdiff reports what an edit created, removed and changed. C++ in src/cpp/src/sketch/edit.cpp,
bound in bind_sketch.cpp, with doctest cases, Python tests and API pages; the two operations join the canonical core op set.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…edits are the SDK's operations

Sketching happens in the viewport (the camera turns to the chosen plane or face, the model stays visible and orbitable, the
drawing is an SVG overlay projected through the live camera); the separate canvas panel is gone. The sketch plane can be a face
frame. Trim and extend are not editor features: sketch_doc.apply_edit builds the SDK Sketch from the document, calls
scad.<op>_rsketch and turns the result back; the command line (sca sketch op) and /api/sketch/op are the same thin bridge.
Cut/boss/extrude from a sketch accept 'against the normal'; profiles carry a plane label; an edit that removes parameters writes
the feature cell before the params cell so no cell reads a name that is gone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Trim (T) and Extend (E) in sketch mode call /api/sketch/op, which runs the SDK's trim_rsketch / extend_rsketch; the frontend only
builds the request, picks the nearer end and adopts the answer (undoable, errors shown, created entities flash). The Assembly tab
edits the assembly document (components from the workspace's parts, placements, grounding, constraints with connector pickers),
shows connector frames as gizmos in the viewport (clickable to fill a picker) and writes the part-side connectors from picked
faces. The feature state's undo count now follows the open notebook.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…nd explode_rassembly

fillet_corner/chamfer_corner round or bevel the corner of two lines that share a point (tangent and radius/length constraints keep
it defined); project_rsketch brings geometry from another sketch or from the edges of a shape into a sketch as fixed construction
geometry, which is how sketches in different planes are related; explode_rassembly returns an assembly with the component
placements displaced for an exploded view (factor, centre or axis, per-component directions and weights). C++, bound,
recorded, replayable, stubbed, documented and tested like trim/extend.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…sembly document

/api/sketch/op reaches fillet_corner, chamfer_corner and project (the source is another sketch feature of the notebook, named by
from_sketch); the assembly document can carry an exploded view: the product becomes scad.explode_rassembly of the solved assembly
and the factor is a slider variable.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… errors shown where they happen

Corner tools (R, H) pick a corner or two lines and ask for the radius or distance; Project (J) brings another sketch's geometry
in as muted fixed reference geometry; the Assembly panel gets an exploded-view section. All three send the request and adopt the
SDK's answer. A feature that fails to run keeps its dialog open with the error, and the next submit edits it instead of adding a
duplicate. The feature dialog docks below the Inspector header.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… by its code

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…r components

Connector frames were SVG lines whose screen directions were normalised to a fixed length, which destroyed foreshortening when the
camera rotated; they are now three.js objects in the viewer's scene (X red, Y green, Z blue with cone heads, Z longer, a
translucent contact plane), depth-tested with a faint ghost pass for the hidden ones, sized from the model and following the
component's node. The pieces are reusable: scene/layers (a layer host with picking, hover/selected state, apparent-size scaling,
ghost materials, anchoring to a node) and components/scene (SceneLabel anchored to a 3D point, AxisTriad, useSceneLayer, axis
colours). The first camera view now fits the whole bounding sphere with a margin.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…tual action bar, sketch on a face

Finished sketches are drawn in the viewport all the time (dim when an extrusion already uses them, an eye per sketch remembered
per notebook, hover and selection linked with the tree); sketches are selectable (a SKETCH selection mode, or armed from a form)
and the Extrude/Revolve/Cut/Boss forms take the selected sketch as their profile; 'Finish and extrude' finishes and opens the
extrude form with the new sketch. A contextual action bar shows the commands that fit the selection (a planar face: Sketch on this
face, Hole here, Shell; a sketch: Extrude, Revolve, Cut, Boss, Edit), driven by a when(selection) predicate on registry commands.
The plane chooser highlights faces on hover and refuses curved ones with the reason; the cut form shows the direction as an arrow
with a flip button. General pieces live in components/ (ActionBar, HintBanner, SelectionBar).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…tersect)

A part holds one solid, so the operations that make a new solid (primitives, extrusions, revolutions, lofts, sweeps, and the
sketch extrude/revolve) get an Operation slot and a Combine-with slot instead of leaving a second body beside the first: the
solid is built as a private tool and joined to, cut from or intersected with the body so far in the same cell, the feature's
role follows the operation, and the product follows. 'New body' starts the first body. Slots can be shown only for some values
of another slot (show_when), and the feature state defaults the operation to join when a body exists.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…lid is made

The Operation slot of a new solid is a segmented control (New body only when there is no body yet), the Combine-with slot shows as
a line when one body is open, slots can be shown only for some values of another slot, and the stand-alone cut/union/intersect and
sketch cut/boss commands are hidden unless several bodies are open: a selected sketch offers Extrude (join) and Extrude (cut) as
presets of the one Extrude dialog. The chooser and its keyboard logic are general components.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@NiJingzhe
NiJingzhe merged commit a7af887 into dev Nov 8, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant