Skip to content

release: 2.2.7 (dev into master) - #70

Merged
NiJingzhe merged 55 commits into
masterfrom
dev
Oct 30, 2026
Merged

NiJingzhe merged 55 commits into
masterfrom
dev

Conversation

@NiJingzhe

Copy link
Copy Markdown
Collaborator

Release 2.2.7, the first stable release of the C++ engine: dev into master, as for every release.

🤖 Generated with Claude Code

NiJingzhe and others added 30 commits October 26, 2026 11:06
The page used generated notes and the bare tag as its title. It now takes docs/updates/<version>.md (plus a
link to the Chinese notes) and is titled 'simplecadapi <version>', like every earlier release page.

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

2.2.7b1 requested a failure picture for booleans only. The blend diagnoses now name the edges to mark and the
operator asks for the render; the ql evidence provider (the C++ hook existed, nothing registered it) renders the
scope with matched faces orange and near misses purple. Highlighted faces use the standard material as the VTK path did.
Restores test_error_diagnostics_blend_ql.py and test_rearchitecture_2_0_kernel_builders.py, deleted when the engine was ported.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…cripts; drop the fem extra

shape.wrapped is the SDK's own binding, so OCP functions reject it and verifier scripts that did
BRepGProp.VolumeProperties_s(solid.wrapped, ...) stopped working with the C++ engine. simplecadapi.verify.to_ocp / from_ocp
bridge through OpenCASCADE's binary format; the verify extra installs OCP 7.9 without VTK. The SDK itself never imports OCP.
The skill gets references/discipline/verifier-scripts.md, whose code the tests execute.

The fem extra (gmsh, ccx2paraview[VTK]) is removed with the README steps that used it; vtk is out of the lock file.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ation, vectorised edge keys

A settle lists the cells in the call that publishes the preview (one call into the kernel, not three), the boot's
duplicate settle is dropped, the debounce is 0.12 s, parts are tessellated on a background thread while the next
cell computes (cached_mesh releases the GIL; a failure is recorded on the shape under the GIL), and the edge
position keys are computed with numpy (byte-identical).  Six-axis arm, last cell to preview event: ~0.83 s -> ~0.36 s.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The server sends the preview as one binary frame (u32 header length, JSON envelope, SCAPREV1 bytes) and a page that
joins late is given it on connect, so the page no longer asks for it over HTTP.  A model already on screen still
travels as the bare notice.  GET /api/preview/latest stays for agents and tools.

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

A part-family member restored from a cache whose lineage record named another graph failed with 'value contains graph
node ... not owned by active graph'.  The record is now checked against the id its cache belongs to (a mismatch runs
the cell again), a restored cell keeps its record, and a record is only saved under its own id.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The page sends a slider value when the drag pauses and again on release.  The second send reran every cell that
depends on a child notebook for a model that already had the value.  The server skips a value equal to the applied
one, except after a failed run (a retry).

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

When the values of a cell were cached but its lineage record was missing, setup dropped the cell from the active set
and returned a miss; the run then ended without saving anything, so the record stayed missing and the cell ran on
every call.  The attempt now becomes a miss for marimo and the cell stays active, so the run writes the entry and the
record and the next call restores it.

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

Running a child notebook is not free even when every cell is restored: cells load, the product is projected and
packaged.  The editor kernel is a long-lived process, so the product of the last run of each (notebook, id) is kept and
returned while the child's static digest and the overrides are unchanged.  One entry per (notebook, id) because the
registry holds the last run's nodes by graph id.  New OPEN_STROKE values on the six-axis arm: 6.4 s -> 4.2 s.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…running without the cache forever

marimo restores an entry that names a class which no longer exists as a stub.  The runner then ran every cell again
without the cache, which neither read nor wrote entries, so the same entries came back as stubs on every later run.
Only a stub whose reason is 'No module named ...' is treated this way: its entry is deleted and the run goes on with
the cache, which writes it again.  Any other stub keeps the old behaviour.

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

A model's parts are identified by the content of their geometry.  The kernel posts only the parts the server does not
hold (a 409 asks for the whole preview), the server gives each page only the parts that page lacks (a page that joins
late or asks with preview.resync gets the whole model), and the page builds geometry only for new parts and reuses the
rest.  A joint angle on the six-axis arm now moves 115 KB instead of 6.26 MB: the post takes 17 ms instead of 285-610 ms,
the page handles the frame in 2-5 ms with no long task, and from the last cell to the model on screen takes 0.36-0.41 s
instead of 1.3-3.2 s.  The signature that files the model is taken beside the build, the server replies to the kernel's
post before filing the model and writing the snapshot, and SCA_EDITOR_TRACE=1 prints how long each step of a settle takes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ow what a cell reads

marimo unpickles an entry while it answers the cache lookup (about a second per assembly of the six-axis arm), so the
kernel asks its memory right after the cell's key is hashed and before the disk, and hands out the same objects when it
holds the entry (SDK value types and plain data only).  The saved parameter values in a cell's digest now follow what
the cell reads instead of reaching every cell below the cell that defines them, so one joint angle no longer changes the
digest of the cells that build the parts.  Six-axis arm: a new joint angle 5.7-6.5 s -> 2.3-2.7 s, a seen one 5.5 s -> 0.6 s.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
With the verify extra installed, wrapped returns an OCP TopoDS_Shape of the class of its kind (as CadQuery's does), so the
OCCT code an agent writes for verifier scripts runs as written.  It is a copy made through BinTools; without OCP it stays
the SDK's own OCCT shape, and SCA_WRAPPED=native|ocp chooses.  The SDK's own layers take _native_wrapped, and the
inspect.brep queries, the STEP export and the kernel geometry helpers accept either kind of shape.

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

set_preview raised on a stored model that left out parts, which ended the kernel boot (the page said 'starting the kernel'
for ever).  A stored model must be whole: restoring one that is not is a no-op, and remember_model does not file one.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- use(): two notebooks run under one graph id replace each other's memo entry (the registry holds one run's nodes
  per graph id; the older entry handed out a product whose nodes were gone).
- Editor: a post that is not a readable preview is answered 400, not 409; the server's model is one value replaced
  atomically; the signing of a publish is handed out once, so a stray second post cannot be filed under it.
- Cell memory: set_metadata/auto_tag_faces change a value in place, so an entry holding a value they changed is
  dropped (it was handed out changed); the marimo hook checks the signature it re-implements and says so when it
  cannot; the length reads under the lock.
- Handled failures print a [sca] line (preview warm-up, lineage record, cache entry delete, preview frame, signing,
  damaged kept model) instead of passing silently.
- Tests: a damaged preview, bytes that are no preview, the single model value, the signing handed out once, a
  moved and turned solid keeps its location and face orientation through .wrapped, saved values in the digests of a
  real notebook, the marimo hook, a value changed in place.
- Docs: docs/environment.md lists every SCA_* switch; CHANGELOG states the one-time rebuild of notebooks with saved
  parameters, the copy semantics of .wrapped, the memory limit.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… tests that do not apply

The file was deleted by the C++ port; its 5 tests pass on native (one is in the known-failures list as an internal).
DEVIATIONS.md lists the old tests of the removed VTK render pipeline and of the py-slvs default, each with the reason.

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

2.2.6 drew one picture for each (the failing edge as an orange line; the matched faces orange and the near misses
purple). The native port attached none, and the old test file that asserts them was one of those the port deleted.

- BlendDiagnosis carries the edges and the caption of the six probes (the old ones); raise_blend_failure asks for
  the picture through DiagnosticImage (new field highlight_edges), the provider call gets the edges.
- evidence.py draws it, and the ql picture through the existing ql evidence hook, on a clone of the scope: the
  user's shape is not tagged.
- test_error_diagnostics_blend_ql.py is restored (13 tests). 12 pass; test_ql_evidence_highlight_is_visible is
  listed as open: the picture is right but the soft renderer's shading leaves 302 sampled orange pixels where the
  test asks for more than 400.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…fused posts, front end)

- A face or edge query that matches nothing draws the plain solid again (it returned no picture).
- use() memo: a key kept again under another graph id, and eviction, no longer leave a stale owner.
- A refused preview post names what the server said; a picture that cannot be drawn is said on stderr.
- Editor: a client that left while a frame was sent is not registered again; the damaged-model message is accurate.
- Front end: a kept part is a copy, not a view that pins the frame it came in; a dropped frame is warned.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
One mechanical pass (clang-format 18.1.8, Google base, 120 columns, T& x, includes untouched). Nothing but whitespace and
line breaks moves; tools/check_format.sh lists what would change and CI runs it.

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

- format job (clang-format 18.1.8, no OCCT needed); sanitize job (ASan+UBSan, ctest, LSan suppression file);
  advisory clang-tidy on changed files; occt-guard audit (tools/check_occt_guard.py, non-blocking until clean).
- tests/python/ref.py: SCAD_GOLDEN_STRICT=1 (set in CI) makes a missing golden an error and refuses SCAD_REGOLDEN.
- src/cpp/tests/python now runs in CI (it was not in pytest testpaths).
- tools/gen_stubs.sh generates the .pyi of the extension; CI fails when the checked-in stubs are stale.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… OCCT guard audit is clean and strict

- The four py::register_exception_translator calls (common, reactive graph, reactive units, raw OCCT shim) become
  handlers of one translator that asks them most specific first (ExceptionPriority), so the result no longer depends
  on the order the areas are bound in (UnitValidationError is an Error, StaleLineageError a ValueError).
- check_occt_guard.py accepts operator_scope and a one-line claim that the one caller runs the helper inside
  occt_guard; the 7 reported sites were helpers of guarded operators and now say so. CI runs it with --strict.
- Tests: Standard_Failure, UnitValidationError and StaleLineageError each keep their own Python class.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…he agent notes are retired

- CONTRIBUTING.md: build, the test layers, golden policy, format, error, GIL, thread-safety and parity rules.
- docs/READING_ORDER.md and docs/LANDING_PLAN.md (+ tools/landing_plan.py): how to read the tree, and 10 PRs that
  account for every file of the port (a plan: the stacked branches were not created or built one by one).
- README.md replaced; ARCHITECTURE.md and NATIVE_PROGRESS.md corrected; AGENTS.md and HANDOFF.md removed (what was
  still true moved into the human docs); PORTING_STATUS.md marked as a stale snapshot.
- tests/python/ref.py no longer hard-codes the maintainer's home: SCAD_REF_PYTHON, else the checkout next to this one.

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

include/scad/core/tolerance.hpp holds 38 constants, each named for what it is a tolerance for, with a how-to-choose
comment and a table. 116 sites in 56 files use them; every constant has exactly the value of the literal it replaces,
so nothing computes differently (ctest and the golden parity tests unchanged). About 110 literals stay where they
are on purpose: OCCT mirror bindings, solver constants that already have local names, relative-times-floor
combinations, raster/render epsilons.

Six ambiguous sites are listed in the CHANGELOG-free note of the commit for the owner:
extrude_until.cpp:68 (1e-8 sine test), :193 (extent below 1e-12 uses kDegenerateNorm), probes.cpp:207,228,
bind_surface.cpp:75 (interpolation default 1e-5 vs kSplineInterpolation 1e-6), tracking.hpp:148 (tracked_shell tol),
BuildCurves3d at 1e-6 (kernel/curves.cpp:177) and 1e-7 (tracking_lofts.cpp:254).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…perationGraph::from_value, validate_manifest, decode_artifact, make_bolt_rsolid, raise_sketch_solve_failure, tracked_twisted_sweep

Behaviour is unchanged: each function became an outline over named helpers with
the same statement order, throws and OCCT call order; lookup table in
entity_type_of left as is (commented). Verified with the 14 ctest targets,
the 471 Python tests in tests/python and the old brep_region, render,
fastener, sketch diagnostics, graph and serialization suites.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…cene, make_ball_bearing_rassembly, glb_bytes, assign_tag, resolve_recorded_delta, solve_assembly_constraints, ExpressionGraph::from_value, loft diagnosis, Session::recompute

Behaviour is unchanged: statement order, throws, recorded params and OCCT
call order are kept; helpers only carry code out of the long functions.
Verified with the 14 ctest targets, the 471 Python tests in tests/python
and the old stdlib, product, assembly, tagging, expr_graph, cache, editor,
ql, notebook and diagnostics suites.

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

cardinality_failure, ShapeSelector::from_value, build_gear_profile_sketch,
apply_tracking_tags, Shape::to_string, assemble_solution, Sketch::add_bspline,
make_cylindrical_surface_rface, geo_selector_score, make_geo_exact_selector.
Behaviour is unchanged: validation and OCCT call order are kept; float sums
keep their operation order. Verified with the 14 ctest targets, the 471
Python tests in tests/python and the old ql, gear, sketch, surface,
tagging, serialization and inspect suites.

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

Behaviour is unchanged: the accessor/bufferView JSON and the input gathering
moved into named helpers with the same statement order. Verified with the 14
ctest targets, the 471 Python tests in tests/python and the old mesh export,
notebook, editor, cache and expr_graph suites.

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

pybind11-stubgen 2.5.5 over simplecadapi._cpp._native (py.typed was already shipped, so type checkers saw an untyped
module). CI regenerates them and fails when they are stale.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
pybind11-stubgen emits the re-export imports in set order; gen_stubs.sh sorts each run of import lines, so --check
(run in CI) is stable: three consecutive checks after generating are fresh.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ize and stub jobs are advisory; dead nested workflows removed

- gen_stubs.sh: the interpreter word-splits (CI sets PYTHON="uv run --no-sync python"); the stubs of the test-only
  modules and two unresolvable builtins imports are left out.
- ci.yml: the ASan/UBSan job and the stub-freshness step are continue-on-error until each has run green once.
- src/cpp/.github/workflows (never read by GitHub) is deleted; CONTRIBUTING/READING_ORDER/LANDING_PLAN corrected.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
NiJingzhe and others added 25 commits October 29, 2026 20:22
… branch

#66 implements the same pictures as this branch's d54d0d0 and also shades highlighted faces with the standard
material, which makes test_ql_evidence_highlight_is_visible pass (it was listed as open here). Resolution: #66's
blend diagnosis, blends.cpp, diagnostics.hpp and evidence.py win; this branch's DiagnosticImage.highlight_edges field
and provider argument are dropped (unused); raster.cpp keeps this branch's split draw_panel with #66's shade() change
re-applied; the open entry of the ql test is removed; evidence.py compares native shapes (_native_wrapped) because
.wrapped is an OCP shape here, and says on stderr when a 2D picture cannot be drawn.

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

verify.to_ocp / from_ocp now delegate to the bridge of this branch (_glue.bridge), which also backs shape.wrapped when
OCP is installed: one conversion, one place. The verifier-scripts skill reference and the CHANGELOG say that
shape.wrapped is an OCP shape with the extra (PR #67 described it as the SDK's own binding). CI: the verifier-bridge
step is kept next to the C++ parity and stub steps.

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
ref.py defaulted to <repo parent>/SimpleCADAPI/.venv; in a CI checkout (<work>/SimpleCADAPI/SimpleCADAPI) that is the
repository itself, so the SDK under test became its own 2.2.6 reference and test_the_reference_sdk_accepts_our_snapshot
failed on every Python version. The default is now absent in that case (the test skips), as before.

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

PR #67's test_the_sdk_wrapped_shape_is_not_an_ocp_shape asserted that OCP rejects shape.wrapped; with OCP installed
wrapped is now an OCP shape (a deliberate change of this branch). The original assertion is kept for the SDK's own
binding (SCA_WRAPPED=native), and a new test pins the new default: OCP measures solid.wrapped.

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

- cpp-tests (advisory until green once): the per-area doctest executables without a sanitizer. No required job ran
  them; it also tells whether the Linux difference of the default hex bolt (56 faces / 117 edges where macOS builds
  54 / 116) comes from the sanitizer build or from the platform.
- clang-tidy: configured without the Python layer, so the bindings are not in its compile database (Python.h and
  pybind11 not found); it now runs on src/cpp/src/*.cpp only.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…UBSan skips OCCT's own misaligned TopoDS read

- test_fastener: the modeled-thread topology depends on the platform's OCCT build. 2.2.6.post2 (OCP) gives 54 faces /
  116 edges on macOS arm64 and 56 / 117 on Linux x86-64 (measured in a linux/amd64 container; volume equal to 1e-8), and
  this engine gives the same on each. The doctest asserts the value 2.2.6 gives on the platform (Linux x86-64: 56 / 117;
  otherwise the macOS one) instead of failing on Linux.
- tools/ubsan.supp: the alignment reports of the sanitizer job are all inside OCCT's mesher (BRepMesh_* ->
  TopoDS::Face); only that check, only for OCCT's TopoDS.hxx, is suppressed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…CT's headers

The first run reported TopoDS.hxx, the next TopoDS_Shape.hxx: one misaligned TopoDS_Shape read inside OCCT's mesher.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The first run with the alignment check suppressed left only 112 bytes in 4 allocations (TPrsStd_DriverTable::Get and
TCollection_AsciiString allocated in static initialisation), with every doctest case of the io area passing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
All three passed on Linux (14/14 doctest executables in both builds; the checked-in stubs, generated on macOS, are
reproduced). clang-tidy stays advisory: it fails on compile errors only, never on its warnings.

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

Behaviour is unchanged: each empty catch body now assigns/returns the value the code
already ended up with (nullopt, 0.0 term, skipped selector keys), and the caught type
is narrowed where the guarded call is known to throw only that type (Standard_Failure,
KernelError, scad::Exception, ValueError).  Sites whose type could not be proven
(ql loop_role/geomclosed, geo_selection shape_geom_type) keep std::exception and say so.
Session::~Session drops a try/catch around the noexcept stop().

Checked by: ninja build, ctest 14/14, src/cpp/tests/python 471 passed/1 skipped,
and the whole old test/ suite (no failures).  clang-tidy itself could not be run locally.

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

Behaviour is unchanged for valid inputs.  Accessors that an invariant guarantees engaged
(gear radii of a coupling, anchor geometry_ref/placement, Property key path, gear root
fillets, B-spline fit best, last_solve_, ...) go through the new scad::require_engaged
(core/engaged.hpp), which throws RuntimeError "internal invariant violated: ..." instead
of undefined behaviour.  Other sites bind the optional once (accessor results, at[i]
selector ends, residual maps) so the test dominates the dereference.  No site was found
where an empty optional is reachable from valid input.

Checked by: ninja build, ctest 14/14, src/cpp/tests/python 471 passed/1 skipped, the old
test/ suite (no failures), g++-16 -fsyntax-only on all 28 touched .cpp files.
clang-tidy itself could not be run locally.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…t_edges argument in the PR #66 merge)

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

clang-tidy on the previous commit left one site of 70: reservation.value() on the line after if (reservation), which its
dataflow no longer follows across the calls in between. Same helper as the other 69 sites; behaviour unchanged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…s the property absent, as in 2.2.6

2.2.6 wrote `except Exception: return MISSING` around the OCP calls (ql._geometry_property); OCP's Standard_Failure is an
Exception there. The C++ port caught std::exception, which OCCT's exceptions do not derive from, so an OCCT failure in
shape_geom_type (reactive/geo_selection.cpp) and in geom.closed (ql/property.cpp: is_wire_closed / shell_is_closed,
which are not under occt_guard) escaped instead of yielding no value. Both now catch Standard_Failure explicitly.

DEVIATIONS.md records (with a test) the other difference found in the same review of a real agent session: a
scad.var expression used as a ql predicate value is evaluated here and matched nothing in 2.2.6.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2.2.6 gave them symbolic arithmetic only; int(), round(), comparisons, max/min/sorted, range(n), [0] * n, lst[n - 1],
//, %, f"{r:.2f}" raised, and r == 5.0 / r in [5.0] answered False for a Var whose value is 5.0 (an agent lost ten
minutes in a scan-to-CAD run to a ql predicate with a Var-derived value that matched nothing).

Arithmetic stays an Expr (the formula); decisions read the value the expression has now (the value float(x) gives).
Two expressions compare by formula, an expression and a number by value; numpy scalars are accepted as operands.
Unchanged: bool(var) raises (an old test pins it), json.dumps(var) raises. There is no __array__ on purpose: marimo keys
an object that has one by its value alone, so a changed name, unit, tolerance or comment would no longer redo the cells
that read it (caught by test_cell_cache_store). __rmul__ hands list / str repetition back to Python.

A matrix test runs each usage with a number, a Var and an Expr and asserts the same answer. DEVIATIONS.md lists what
changed against 2.2.6; the notebook-runtime guide tells a model how to use a scad.var.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…t a list of function names

apply_tag, set_metadata and auto_tag_faces change the shape they are given and return that same object. The memory
watched two of them by name and missed apply_tag, so after a cell tagged a shape it had received, the editor handed out
the producing cell's object with the tag on it: edit the tag in an annotate block and the shape kept the old tag too,
where a cold run has only the new one.

Every root of shapes now counts its writes (Registry::revision, bumped in the one write door Shape::writable_state and in
the removals that bypass it; AnyShape._semantic_revision). A memory entry records the counts of the shapes in its values
(a Part is its body, an Assembly its components) and is dropped when they moved. Reading a shape and consuming it in an
operator (cut, union, fillet, translate, make_part) do not count: the six-axis arm still recomputes only the two cells
that depend on the changed joint and restores the rest from memory.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…a Var, and why

A new discipline file with the rules, the reason for each and the error you get when it is broken; wired into SKILL.md,
the single-part and assembly workflows, the feature tree convention and the runtime guide.

Written from what the agents actually did (26 sessions in Langfuse): float(NAME) around every Var (119 times), and the
template's NameError when sca cell put replaces the params cell while the base cell still reads RADIUS (6 sessions).
Every statement about Var was checked against the SDK: units convert (2 in = 50.8 mm), mixing length and angle in an
expression or unit and unitless variables raises when evaluated, but a Var is not checked against the parameter it is
passed to; the package definition does not record the parameters, so the file does not claim it.

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
perf(editor)+C++ landing: incremental preview, cell memory, shape.wrapped as OCP, formatted/documented C++ tree
Version in pyproject.toml, the project entry of uv.lock and SKILL.md; the CHANGELOG heading; docs/updates/2.2.7 (EN and
zh-CN); the update notes of both READMEs replaced with this version's.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…is there, not when the type stubs are

The validation workers (build123dftc, cadqueryftc) put the checkout's src on their subprocess's path only when it is a
built tree, because it would shadow the installed wheel with Python files that have no compiled module. They looked for
glob('_native*'), which the new type stub directory src/simplecadapi/_cpp/_native/ matches: in the release workflow's wheel
tests (the wheel installed, the checkout not built) six tests failed with 'simplecadapi needs its compiled C++ module'.
wheel_smoke.py asserted the same glob, which the stubs would have satisfied without the extension.

All three now require a file. A test pins the rule (stubs alone: not built; .so / .pyd: built).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…evelopment build, false in CI's editable install)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
fix: a checkout is a built source tree only when the compiled module is there
@NiJingzhe
NiJingzhe merged commit e9200de into master Oct 30, 2026
22 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