Update OCCT, Add GUI Tools, b123d Script Support - #198
Open
zalo wants to merge 84 commits into
Open
Conversation
- generate-occt-symbols.cjs scans worker sources for every oc.* reference and emits UsedOCCTSymbols.generated.js at build time; CascadeWorker.init verifies the list against the loaded WASM so renumbered Embind overload suffixes fail loudly at startup with symbol names - CascadeViewHandles routed gizmo write-back through window.monacoEditor, which is the raw Monaco instance and has no evaluateCode; use the app's EditorManager (setCode/evaluateCode) so drag-end updates re-evaluate Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CacheOp now tags every produced shape with .producingLine (the 1-based eval line, refreshed on cache hits too). combineAndRenderShapes builds face/edge-hash → sceneShape-index maps while scanning sceneShapes and returns a shapeLines array alongside the mesh; ShapeToMesh threads the maps through so each face/edge record carries a shape_index. The engine exposes it as meshData.shapeLines. This lets the viewport resolve any raycast pick to the top-level scene shape that owns it and to the editor line that produced that shape. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New viewport toolbar (Select/Box/Cylinder/Sphere/Fillet) with per-tool state machines under src/tools/. Every GUI operation emits JavaScript against the StandardLibrary vocabulary into Monaco (via executeEdits, preserving undo) and re-evaluates — the code IS the scene. - ToolManager: toolbar DOM, capture-phase pointer routing ahead of OrbitControls (controls disabled during drags, like HandleManager), ground-plane raycasting with integer-mm snapping, CAD↔three coordinate mapping (x, -z, y), collision-safe variable naming, code emission. - Box: drag footprint → drag height → click commits `let box1 = Translate([x, y, 0], Box(w, d, h));` - Cylinder/Sphere: click center → drag radius (→ drag height) → commit. - Fillet: click edges to multi-select (orange highlight, selection-aware repaint in the edge mesh), inline radius panel, Enter commits `shape = FilletEdges(shape, r, [indices]);` using exactly the per-shape edge indices the hover tooltip shows; bare-expression producing lines are rewritten to `let <var> = ...;` first. - Select: clicking a shape reveals + flashes its producing line, using the new shape_index/shapeLines mesh metadata (third vertex-color channel for faces, globalEdgeMetadata for edges). - EditorManager: insertCode/getLineContent/replaceLine/flashLine helpers. - CascadeAPI._tools exposes the ToolManager for tests/tooling. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
test/gui-tools.spec.js drives the tools through real synthetic PointerEvents on the viewport canvas (exercising the capture-phase routing and OrbitControls coordination): toolbar rendering + Escape, full Box drag flow with code emission and runCode round-trip, Fillet edge click → FilletEdges emission with the picked indices, and Select click → producing-line flash with shapeLines verification. playwright.config.js gains CS_TEST_PORT (8080 is occupied on this machine) and CS_TEST_HEADFUL (headless Chromium here cannot create a SwiftShader WebGL context; headful against Xvfb works). Defaults are unchanged. CLAUDE.md documents the GUI tools, their file map, the pick→line mechanism, and the new test env vars. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…trude
New toolbar entry (pencil icon) for a Fusion/SolidWorks-style sketch →
extrude workflow, unlike the drag-primitives:
- Clicks place grid-snapped polyline vertices with a live rubber-band
(segment length + angle label); Enter or clicking the first vertex
closes the profile (min 3 vertices).
- Line/Arc segment toggle in the inline panel plus L/A shortcuts. Arc
segments take two clicks (through-point, then end) and preview the
live three-point circular arc; closing from Arc mode emits a closing
ArcTo back to the start vertex.
- Escape is stage-level undo: a half-placed arc through-point first,
then vertices one at a time, then the whole sketch; the Tool base
class gains onEscape()/onKeyDown() and the ToolManager routes keys so
tools can consume Escape before it falls back to Select.
- Once closed, corner-vertex clicks toggle sketch fillets (vertex 0,
the Sketch start point, is refused per pitfall 5 — arc junction
vertices verified workable against ChFi2d), and an inline panel
commits as Extrude / Revolve / Face only. For Extrude, dragging
vertically inside the profile sets the height interactively (reuses
the Box height-drag ray logic; the numeric input reflects the drag)
with a live ExtrudeGeometry preview.
- Emits the StandardLibrary Sketch builder chain (~3 segment calls per
line), e.g.:
let profile1 = new Sketch([20, 5])
.LineTo([35, 5]).ArcTo([42, 12], [35, 20]).LineTo([20, 20]).Fillet(3)
.End(true).Face();
let part1 = Extrude(profile1, [0, 0, 15]);
- The sketch plane is a parameter (CAD origin + u/v basis) so
sketch-on-face can be added later; v1 uses the ground plane, which
maps 1:1 onto the default `new Sketch([u, v])` XY plane.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two new Playwright tests drive the tool through synthetic pointer/key events: a full profile (two lines, a two-click three-point arc, a corner fillet on vertex 3 with vertex 0 refused, close on the first vertex, height via the panel input, Apply) asserting the emitted `new Sketch(...).ArcTo(...).Fillet(...)` + `Extrude(...)` code and its runCode round-trip; and the Escape ladder (through-point → vertices → sketch → back to Select). Toolbar test updated for 6 tools. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- New PythonRuntime.js: lazy Brython bootstrap (fetch + indirect eval on the first Python evaluation; ~1.38MB, copied to dist by build.cjs) and guarded execution that converts Brython exceptions into JS Errors carrying the full Python traceback (user-editor line numbers). - New Build123dLite.js: algebra-mode build123d subset as an embedded Python source, registered as the importable module 'build123d' (Box/Cylinder/Sphere/Cone centered like build123d, +/-/& booleans, Pos/Rot Locations, fillet/chamfer with edges(indices=...) escape hatch, extrude/revolve, Rectangle/Circle profiles, volume/show). - CascadeWorker.evaluate branches on payload.language === 'python': async evaluation whose pending promise gates combineAndRenderShapes; the onmessage router now supports Promise-returning handlers. - CacheOp resolves Python source lines from Brython's frame chain (self.getPythonUserLine), so modelHistory line numbers and pick->line mapping keep working in Python mode. - CascadeEngine.evaluate passes `language` through to the worker; worker console.log stringification is now circular-safe. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- EditorManager.setMode('python') switches Monaco to its built-in
Python language and swaps starter code like the other modes;
evaluateCode passes language: 'python' through the engine.
- CascadeMain: PYTHON_STARTER_CODE (algebra-mode demo: centered Box
minus Cylinder, filleted, with a volume printout) and per-mode
saved-code handling for the new mode.
- Mode switcher gains a "Python (build123d)" option in both the
generated dist index.html and the dev index.html.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- ToolManager exposes codeLanguage/isPythonMode and per-language dispatch; Box/Cylinder/Sphere tools emit `name = Pos(cx, cy, cz) * Primitive(...)` in Python mode (build123d primitives are centered on the origin, so the emission math converts corner/base placement to center placement). - Fillet tool emits `var = fillet(var.edges(indices=[...]), r)` and rewrites bare Python expressions without the JS `let`. - The Sketch tool is JS-only for now: disabled (grayed + tooltip) in Python mode via EditorManager.setMode -> ToolManager.onLanguageChanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Box(x, y, z, centered=true) called Translate() inside its CacheOp closure, which scene-registers the translated shape, and then Box pushed the same shape again — every cache miss added the shape to the scene twice (latent in JS mode, exposed by build123d-lite's centered primitives). Deregister the nested Translate result like Text3D does. build123d-lite's show() now tests scene membership with Python 'is' (Brython compares the underlying JS objects); Embind shapes have no .ptr property and .indexOf() never matches across Brython wrappers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- test/python-mode.spec.js: starter script renders with real Python line numbers in history; union/difference volumes verified via print(volume(...)); Python runtime + syntax errors surface in getErrors() with tracebacks and user line numbers; the GUI Box tool emits `box1 = Pos(...) * Box(...)` that round-trips, and the Sketch tool is disabled in Python mode. - CLAUDE.md: "Python (build123d) Mode" section — Brython-in-worker architecture, subset coverage table, size note (~1.38MB lazy), known gaps (no context managers, no selectors beyond edges(), no stdlib imports, async console output). Full suite: 22 passed with --workers=1 (headful, port 8517). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lisions
- MeasureShape/BoundingBox: volume, area, unique face/edge counts and a
mesh-approximated bbox (fine triangulation of a deep copy — the WASM
build has no Bnd_Box binding). Used by build123d-lite's bounding_box()
and the b123d validation harness.
- WireFromSegments/MakeCompound/FilletFace2D: wire assembly from chained
line/arc3/bezier/spline segments, compound grouping, and 2D corner
fillets (BRepFilletAPI_MakeFillet2d) for the lite BuildLine/make_face
and sketch-vertex fillet paths.
- Per-entity introspection helpers (_edgeMidpoint/_edgeDirection/
_faceNormal/_faceUDir/_edgePointAt/...) exposed on self for the lite
Python selectors; _faceNormal now respects face orientation (outward
normals on solids).
- Fix a real cache bug: ComputeHash strips embind ptr fields, so raw
sub-shapes (selector edges/faces, compounds) all hashed to "{}" and
CacheOp could return a PREVIOUS op's result for a different sub-shape.
Selector entries and MakeCompound results now carry a stable
OCJS.HashCode identity.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Validates Python mode against REAL build123d 0.11.1: - collect.py: 129 candidate scripts from the build123d clone (examples/ plus the numbered general_examples[_algebra] docs snippets), viewer imports stripped, code inlined into manifest.json. - reference.py: runs each script natively (guarded subprocess, 60s timeout, pool of 4) and records volume/bbox/area/face/edge counts for every module-level shape or builder result, KEYED BY VARIABLE NAME. - run-lite.mjs: playwright driver that runs every script through CascadeAPI Python mode with a measurement footer (worker MeasureShape), classifies PASS (volume 0.5% rel, bbox 1e-3/axis) / MISMATCH / ERROR (bucketed by first missing feature) / TIMEOUT, and writes report.md with a sorted feature-gap frequency table. - run.sh entry point + README. Not part of the default playwright suite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…bjects/ops Grown from the algebra-only subset to a validated build123d 0.11.1 subset (50/126 upstream example scripts now produce matching geometry — see test/b123d-validation/report.md): - BuildPart/BuildSketch/BuildLine as plain context managers over a module-level builder stack (we own the implementation, so no inspect.currentframe tricks): nesting, mode=, multiple workplanes, pending faces (BuildSketch->extrude/revolve/loft), pending edges (BuildLine->make_face), pending path (BuildLine->sweep), Select.LAST. A pre-run reset guards against stacks leaked by aborted evaluations. - Full Location math in pure Python (rotation matrix + translation): Pos/Rot/Rotation (intrinsic XYZ, verified == build123d), 3-arg axis-angle Location, Plane with named planes, Plane(face) (UV-derived x_dir + orientation-aware normal), offset()/rotated(), Locations/ GridLocations/PolarLocations/HexLocations (context managers AND iterables), planes*shape and locs*shape algebra. - Objects with rotation=/align=/mode=: Box, Cylinder(arc_size), Sphere, Cone, Torus, Hole/CounterBoreHole/CounterSinkHole (conventions probed against real build123d: holes span +-depth), Rectangle(Rounded), Circle, Polygon, RegularPolygon, Trapezoid, Slots, BaseSketchObject/ BasePartObject; Line/Polyline/PolarLine/arcs (ThreePoint/Radius/ Sagitta/Center/Tangent/Jern)/Bezier/Spline (natural-cubic sampled — GeomAPI_Interpolate is not bound), curve @ u / % u. - Ops: extrude (dir/both/pending), revolve (arbitrary axis), loft, sweep (chained multi-segment paths), fillet/chamfer (3D edges + 2D sketch vertices via FilletFace2D), offset, mirror (any plane; segment-level inside BuildLine), split (big-box intersection), scale, add (2D objects become pending faces, like build123d). - ShapeList selectors: filter_by (Axis/GeomType/callable), filter_by_position, group_by, sort_by (incl. SortBy.RADIUS via 3-point circumradius), sort_by_distance; Edge/Face/Vertex wrappers. - Multi-tool subtraction fuses the tools first (matches build123d and avoids an OCCT sequential-cut robustness bug). - Stdlib shims registered as Brython modules: math (JS Math), copy, typing, functools, itertools, operator, logging — brython.js cannot import even its built-in math inside a module worker. - Unsupported features raise NotImplementedError with clear reasons (Text/fonts, Spline tangents, extrude until/taper, offset openings, joints, hull/project/thicken) — never fake geometry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- run-lite.mjs: unescape the console-panel's JSON-escaped measurement
line (quotes arrive backslash-escaped — parse failures looked like
'no measurement produced'); wait for worker idle between scripts so a
slow evaluation cannot poison the next one; retry once on Brython's
traceback-formatter crash ("reading 'substr'") which masks the real
Python error after long run sequences.
- reference.py + lite measurer: measure only face-carrying elements of
module-level lists and sort them by bbox center — edge/vertex selector
lists have implementation-defined order on both sides and produced
per-index comparison noise.
- reference.json regenerated (build123d 0.11.1); report.md committed:
50 PASS / 10 MISMATCH / 64 ERROR / 2 TIMEOUT of 126 scored scripts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… tests Each test runs an unmodified upstream build123d script (Apache-2.0, from the build123d repo docs/examples) through Python mode and asserts every module-level shape volume against the value REAL build123d 0.11.1 produced natively (0.5% relative tolerance, the harness PASS criterion). Coverage: builder basics, BuildLine+mirror+make_face, selector chains + Select.LAST + Hole, Locations(face)+PolarLocations+counter-holes, JernArc+@/%+sweep, Plane.rotated, split, algebra selectors, loft over placed sketches, and a real-world part (pillow block) with 2D vertex fillets and CounterBoreHoles. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the algebra-only coverage table with the validated subset (builders, locations, selectors, objects, ops, stdlib shims), current harness numbers (50/126 upstream scripts passing), the honest-gap list, and pointers to test/b123d-validation + the frozen regression tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Worker error strings carry literal "\n" sequences, so identical NotImplemented gaps were split into one row per script in the report's feature-gap table. Reclassified the committed report accordingly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
26.3MB wasm (5.3MB brotli, +0.87MB over the wire vs RC4) carrying the full build123d OCP import surface: 223 classes + auto-resolved base/Handle closure, OCAF/XCAF, STEPCAF, RWGltf, HLR, ShapeFix, Geom2d*, Bnd, BRepFeat, BRepProj. Full suite green (32/32) including frozen upstream build123d examples. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ement, splines with tangents, tapered extrude, thick-solid offsets - MeasureShape/BoundingBox now use BRepBndLib + Bnd_Box from the 8.0.1 build instead of fine-meshing a deep copy (dominant harness cost eliminated) - build123d Text implemented over the JS Text3D/opentype.js path (FreeSans + FreeSansBold vendored for metric parity with upstream's default font) - Spline(tangents=) via GeomAPI_Interpolate; extrude(taper=) via BRepFeat_MakeDPrism; offset(openings=) via BRepOffsetAPI_MakeThickSolid - assorted lite fixes from the validation loop (nested builder transfer WIP) Work by the validation agent (recovered after it hung mid-run); verified: full suite 32/32, full harness pass 78/126 matching native build123d. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…imeout Full 129-script pass: 141s single-page (was ~50+ min with mesh-based bounding boxes). Requires CS_TEST_HEADFUL=1 DISPLAY=:99 on this machine — headless Chromium cannot create WebGL contexts here; the earlier all-ERROR 'no measurement produced' run was exactly that footgun. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rness timing + headful requirement Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rsal
- HLRProject: hidden-line-removal projection (HLRBRep_Algo + HLRToShape +
BuildCurves3d) returning visible/hidden edge compounds — backs
build123d-lite's Shape.project_to_viewport.
- ScaleXYZ: non-uniform scaling via gp_GTrsf + BRepBuilderAPI_GTransform.
- ReverseFace: orientation flip with proper TopoDS_Face typing (used to
normalize make_face/Text faces to +Z oriented normals — mirrored or
wire-wound faces otherwise extrude/fuse the wrong way).
- Text2D: per-glyph layout with the worker's OWN kern-table parse (all
format-0 horizontal subtables — opentype.js reads only the first, and
FreeSans keeps pairs opentype misses), plus the Font_TextFormatter
kern(last,last) width quirk; OS/2 typo metrics for vertical alignment.
Verified: Text("123d", 10) matches native build123d's bbox/area exactly.
- CascadeWorker: font preloads awaited (no first-eval race); adds the
FreeSans family (Bold/Oblique/BoldOblique — what OCCT's font manager
resolves Arial styles to on Linux) and Liberation Sans.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…6 passing - Builder transfer gating replicates build123d exactly: a builder's result moves to the enclosing builder ONLY when both with-statements share a Python stack frame (via a Brython frame-identity helper) — fixes the BaseSketchObject double-add family. - extrude(until=Until.NEXT/LAST): boolean-trimmed candidate pieces against the target part (nearest void / drop-beyond-last), honest geometry only. - Face.offset (the face's plane offset), Curve.location_at + curve ^ t (tangent-oriented frames for sweep sections), Shape.translate/.wire(), ShapeList set-difference, settable .length for custom part classes. - Helix (exact parametric sampling -> spline fit), weighted Bezier (rational sampling), EllipticalCenterArc arc_size= signature, SlotArc and make_face from raw line/circle edges, bounding_box() operation, LineType enum, no-op ExportSVG, project_to_viewport via HLRProject. - add() into BuildLine contributes (optionally rotated) segment specs; make_face normalizes XY faces to +Z via ReverseFace. - scale() accepts non-uniform tuples (ScaleXYZ); pack() ported from build123d 0.11.1 (two-stage min/max sort) + exact-MT19937 random and timeit shims — packed_boxes reproduces the seeded reference bit-for-bit. - Python-mode evaluations clear the worker op cache: cross-evaluation cache-hash collisions produced observably wrong booleans. Validation: 85/126 upstream scripts PASS (was 78), 0 timeouts, full 129-script harness pass in ~45 s with 4 pages. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New coverage: heat_exchanger (HexLocations/SortBy.RADIUS), lego (Kind.INTERSECTION offsets), loft, packed_boxes (pack + seeded MT19937 + HLR), clock (2D vertex fillets + Text), ex23 (revolve), ex29 (bottle: make_face orientation + thick-solid openings), ex35 (slots + SlotArc), ex36 (extrude until=Until.NEXT), boxes_on_faces (Plane(face) UV x_dir). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nterpolate / PointsToBSplineSurface Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…aft/surface helpers
- WireFromSegments: new 'interp' segment kind (GeomAPI_Interpolate, exact
build123d Edge.make_spline semantics incl. end/per-point tangents and the
Scale flag) and opaque 'raw' TopoDS-edge passthrough; disjoint segment
runs now become separate wires in a compound instead of aborting MakeWire.
- New helpers: PipeShellSweep (BRepOffsetAPI_MakePipeShell with upstream's
trihedron/transition/binormal/aux-spine modes, multisection),
SurfaceFromPoints (interpolated rows + ThruSections skin — the
PointsToBSplineSurface handle type is unbound), DraftAngleFaces,
FaceWithHoles, ExportSTL (MEMFS), SolidsVolume, _faceOuterWire,
_sameShape, _edgeIsForward.
- _faceUDir now implements build123d's exact Plane(face) x_dir rule
(gp_Ax3 XDirection for elementary surfaces, raw D1(0.5,0.5) for bounded).
- ScaleUniform: baked gp_Trsf scale about a center — the legacy Scale()
encodes the factor in a TopLoc_Location, which downstream OCCT algorithms
handle inconsistently (root cause of the BaseSketchObject scale family).
- Text2D: hash the freshly built glyph face before the CacheOp'd Mirror —
un-hashed shapes hash as "{}", which made every text after the first
reuse the first text's glyph geometry.
- MeasureShape: COMPROMISE(volume-measure) — per-solid volume sum (8.0.1's
VolumeProperties picks up stray-face contributions on mixed compounds).
- CacheOp: normalize raw wasm exceptions (numbers) into descriptive Errors
— Brython cannot attach tracebacks to primitives, which masked every
kernel abort as "Cannot create property '__traceback__' on number".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ft/project, scipy shim — 110/126
Coverage 85 -> 110 PASS (4 MISMATCH, 12 ERROR, all honest). Every deliberate
deviation is marked with a grep-able COMPROMISE(<topic>) comment.
- Spline/Helix: exact GeomAPI_Interpolate ('interp' specs) replacing the
natural-cubic approximation — tangents=/tangent_scalars=/per-point/periodic
match upstream (unit tangent x scalar, Scale flag = scalars is None);
clears the ex12/vase/roller-coaster residuals. Segment transforms/reverse/
scale are kind-aware (interp tangents are scale-invariant under uniform
scaling — chord-length parametrization).
- sweep(): MakePipeShell with upstream's trihedron modes (is_frenet,
normal= fixed binormal + WithCorrection, binormal= aux spine), transition
mapping, multisection over outer wires; inner wires swept and cut.
Solid.extrude_linear_with_rotation via helix aux spine (twist_extrude).
- section(): finite-rect BRepAlgoAPI_Common like operations_part.section,
Mode.PRIVATE default; Face.inner_wires/outer_wire/make_rect;
Face(outer, [holes]); Wire(edges); scale(about=) + baked uniform scale.
- Joints as location algebra (COMPROMISE(joints) — no assembly tree/XCAF):
RigidJoint/RevoluteJoint/LinearJoint/CylindricalJoint/BallJoint with
upstream's exact relative_to math; connect_to repositions the other part;
shapes track a composed Location (locate/located absolute, .position
settable); copy.copy rebinds joints; builder joints transfer on exit.
- scipy shim: pure-Python Nelder-Mead minimize + bounded minimize_scalar
registered as scipy/scipy.optimize/scipy.spatial (dotted submodules
aliased around Brython's script-id sanitization); everything else raises.
DoubleTangentArc solves the same tangency root (scan+bisection over a
sampled/refined curve distance) and trims the over-extended target like
upstream's wire fixing does (maker_coin).
- make_hull: 2000 samples/edge, monotone-chain hull, line/circle boundary
runs reconstructed exactly as arcs; draft() via BRepOffsetAPI_DraftAngle;
project() (BuildPart pending-faces form) + Face/Shape.project_to_shape
through target shells; make_surface_from_array_of_points; Mesher (STL
into MEMFS, 3MF raises); real export_stl.
- Workplanes() context (shares the Locations fanout path — a plane basis IS
its Location); add() into BuildLine replicates at location contexts.
- Kernel-fault guard: fuses smaller than their largest input RAISE naming
the known OCCT 8.0.1 coplanar-BSpline fuse fault (ex34) — never
silently-wrong geometry.
- Edge.position_at/tangent_at/@/% orientation-aware, Axis(edge) raw-curve
(upstream parity); Edge.make_line/make_mid_way; ShapeList.__add__ keeps
the selector type; filter_by Axis tolerance in DEGREES like upstream;
Shape.__iter__; solids-only volume.
- Text2D metrics validated glyph-exact vs the reference (Latin); the logo
and extrude families now pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…(25 total) - run-lite.mjs recycles a page after a raw wasm kernel abort (the OCCT heap is corrupt afterwards; every later script on that page would fail with "memory access out of bounds"). - New probe.mjs: run one manifest script or a local .py snippet through the built app and dump raw measurements + errors (the debug loop used throughout this round). - Freeze maker_coin, handle_algebra, custom_sketch_objects_algebra, key_cap and stud_wall as regression tests (suite now 47, all green). - README/report/CLAUDE.md: coverage 110 PASS / 4 MISMATCH / 12 ERROR with per-bucket reasons; coverage TABLE refreshed (Text/Helix/until=/taper=/ non-uniform scale/EllipticalCenterArc and the new areas moved to Supported); new "Known compromises" index pointing at the grep-able COMPROMISE(<topic>) source markers; new "Roadmap (deliberately deferred)" section (XCAF assemblies; real-build123d-over-OCP-shim crossover). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e prose The test count line is mine (3 canonical tests + the mode/gesture specs added since). The rest of this hunk is the GUI tool gesture + default-mode documentation that was already sitting in the working tree describing c6efda4 / eaa8119; it got swept in by a whole-file `git add` and is committed as-is rather than risk clobbering a live edit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…xample mode The new page.mouse tests probed the camera badly: projecting fixed CAD points can land outside the canvas (the event then goes to another panel) and fixed screen offsets can run parallel to a projected axis, so one CAD coordinate never changes and the footprint stays degenerate. groundDragPoints() now asks the tool's own raycaster for two canvas points whose ground hits differ in both CAD x and y, and the assertions compare the emitted code against the dragged dimensions. Verified against the pre-fix tools (checkout + rebuild): both new tests fail, the six pre-existing gui-tools tests pass — the old synthetic-event tests could not have caught this. Everything Example loaded the app without selecting a mode, so it ran the JS gallery through the Python evaluator; it uses gotoAndReady() now. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…c5a23) 68c5a23 swept up a temporary working-tree checkout of the pre-fix tool files (made to prove the new page.mouse tests fail without the fix) and committed the revert of c6efda4 along with the docs. This re-applies c6efda4's Tool.js / BoxTool.js / CylinderTool.js / SphereTool.js exactly; CLAUDE.md and ToolManager.js were unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The lite port's cross-kernel harness caught the patch failing its own premise: reassembling a closed section loop into a Wire and reversing it canonicalized to the other seam of the loop, winding the other way. Reproducible on OCP 7.9.3 alone. Three causes, all the same species — a decision made on numerical noise before the decisive quantity is consulted (REPORT.md §3.3): 1. canonical()'s "already canonical" test compared form.start to a tolerance without wrapping it, so a band midpoint an epsilon below 1.0 (measured 1 - 8e-9) took the re-seam path. Now a circular distance, judged at the resolution the seam is defined to (the band width), and a closed shape that only needs its direction flipped keeps its topology. 2. _walk_loop ranked pieces by raw end-point distance before the tangent; at a seam both distances are noise (8.9e-16 for the piece heading the right way, 0.0 for the wrong one) so the loop was walked backwards. Now gap-closed-at-all -> tangent -> gap. 3. Extremal bands were discovered by thresholding samples and ranked by the minima of whichever samples fell inside them — both sampling-phase dependent, which is how a mirror-symmetric pair of bands resolved differently per frame. Now bands are the local minima of the sampled coordinate (plateaus collapsed), each reduced to its bisection-refined midpoint, and the midpoints ranked with coordinates quantised to the band width so a symmetric pair ties on y and z decides. The motivating loop (1, 2 or 4 Edges depending on the sphere's frame) now canonicalizes identically for 7 rotations x both traversals: 14/14, was 4/14. §2.2 of the rule statement is reworded to match (bands, midpoints, quantised ranking) — the same rule, phrased on quantities that are not functions of the sampling. Table 3 is regenerated: still 13/14 loops at exactly 0 mm cross-kernel with the 14th at 2.6e-5 mm, but the seams of the mirror-symmetric-band loops move to the arc the rule prescribes, so any recorded expectation of the old values needs refreshing. Tables 4 and 5 (arch path, joints) are unchanged. Counts on dev: tests/test_direct_api 1187 passed, 2 skipped (+18 new, was +15); rest of tests/ 1037 passed, 1 skipped — same as pristine. Diff regenerated (+1001/-61) and re-verified by applying it to a pristine dev tree. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…/ 95f86a4) 1. canonical()'s "already canonical" test is now a CIRCULAR distance judged at the resolution the seam is defined to (the band width, from the shape's bounding box) instead of form.start vs TOLERANCE/length — a band midpoint an epsilon below 1.0 is the same point as one above 0.0. A closed shape that only needs its direction flipped keeps its topology and curve types. 2. _walk_loop ranks (is-the-gap-closed, tangent, gap): the tangent is consulted BEFORE the raw noise-scale gap, so the two pieces meeting at a seam vertex no longer resolve by 1e-16 and walk the loop backwards. 3. Band discovery is now the LOCAL MINIMA of the sampled coordinate (plateaus collapsed to their middle sample, _local_minima), each bisection-refined to its midpoint (_band_midpoint), and the midpoints are ranked with the remaining coordinates QUANTISED to the band width (_quantise) — so a mirror-symmetric pair ties on y and z decides, instead of the sampling phase deciding. _cyclic_runs is gone. Plus one lite-side defect the fixes exposed: _reverse_1d flipped a Wire's orientation flag, but Curve._walk follows BRepTools_WireExplorer's edge order and ignores that flag (upstream's Wire.position_at honours it via _occt_param_at), so reversing a Wire silently did nothing to position_at — the same species of bug. A Wire is now rebuilt from its edges in reverse order, each reversed; a single-edge wire, which cannot express it, comes back as an Edge. Measured on the motivating loop (sphere(10) cut by cylinder(r5, x=6), reassembled into a Wire, over 6 sphere rotations x both traversals): 12/12 combinations now canonicalize to pos0 (1, 0, -9.9499) / pos25 (9.25, 3.7997, 0) / length 65.027 — the same values patched upstream produces on OCP 7.9.3. canonical() is now idempotent on those loops too (it returns self). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The three seam fixes intentionally move the canonical seam of loops whose extremal band comes in a mirror-symmetric pair (REPORT.md 3.3): the seam now lands on the arc the rule prescribes — the midpoint whose remaining coordinates are smallest once quantised to the band width — instead of on whichever arc the sampling phase favoured. - canonical-lite-reference.json regenerated from the patched upstream PR tree at 95f86a4 (PYTHONPATH=/tmp/b123d-pr/src); canonical-cross-kernel.json regenerated by its own harness. Cross-kernel: still 185 canonical measurements at 0.00e+0 mm, and sphere_cylinder_reassembled is now frame-consistent in BOTH kernels (was 2/2 off in both). - the harness's stale "known fragility" note is replaced: a frame-inconsistent case now means canonical() is not doing its job, which is the point of the check. - new freeze test: the reassembled sphere/cylinder loop over 6 sphere frames x BOTH traversals (12/12 must agree on pos0 (1, 0, -9.9499), pos25 (9.25, 3.7997, 0), length 65.027 — the mirror pair resolves to the negative z once y ties), plus the already-canonical identity check (canonical() returns the very same object for a canonical loop, circle and rectangle). - the arch and joints frozen values are UNCHANGED: the arch's extremal band is unique (x = -sqrt(1000 + 140 z) is most negative at the single point z = 10, y = 0), so nothing about its hand-computed assertions moves. Verified, not assumed. Harness: 119 PASS / 4 MISMATCH / 3 ERROR / 0 TIMEOUT, with the joints deltas byte-identical to the previous run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The app has no Help panel yet, so only the first line keeps the requested "see Help for the compatibility table" wording; the second reference now names build123d.readthedocs.io, which exists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First show() of an evaluation replaces the auto-added scene with exactly the shown shapes (2-D intermediates no longer leak into renders/exports); later show()/show_object() calls append — COMPROMISE(show-semantics) note explains the deviation from ocp_vscode's strict last-wins. Flag resets per evaluation in PythonRuntime. Starter's 'see Help' line now cites the coverage table URL (no Help UI exists yet). Suite 67/67; harness classification unchanged (119/4/3). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Creation/fillet commits are one-shot: commitCode() reactivates the Select tool so the camera is immediately usable after each placement. Escape behavior unchanged. All six tools route through commitCode, so this is the single switch point. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
User feedback: +1001/-61 was too large for what reads as "matching existing functions". Restructured into three patches that can be reviewed and merged separately, total +771/-29: | # | branch | targets | size | contents | |---|---|---|---|---| | 1 | sort-by-tie-break | dev | +70/-11 | ShapeList.sort_by(..., tie_break=True) | | 2 | canonical-free-edges-v2 | dev | +622/-4 | the rule + Mixin1D.canonical()/canonical_form() | | 3 | canonical-consumers | patch 2 | +79/-14 | Axis(edge, canonical=True), Edge.make_mid_way | 1 and 2 are independent of each other; 3 needs 2. Where the 230 lines went: * sort_by no longer needs its mechanical refactor. sorted() is stable, so making the *incoming* order geometric is enough to make ties geometric: +53/-9 in shape_core instead of +122/-45, one helper instead of two. The opt-in path pays a vertex+center key per object rather than keys for tied subsets only - a deliberate trade for a diff a reviewer can read in one screen. * Tests use pytest.mark.parametrize over the frame x traversal x shape-type matrices (upstream already does this in tests/test_direct_api), so 31 cases fit in 222 lines where 18 unittest methods took 231. * canonical.py lost _golden_min (a 40-iteration search replaced by a 6-line parabolic fit through three samples it already had - also faster), _cyclic_runs, _quantise, _dominant_axis, CanonicalForm.position, and the public lexicographic_key/loop_area_vector surface; docstrings trimmed to the house style of neighbouring topology modules. 310 -> 253 lines. REPORT.md §3.0 now states the line budget plainly, because the remaining size is the thing to judge: of canonical.py's 253 lines, 43 are header+license, ~54 are docstrings and 86 are executable - and those 86 are mostly conditioning (band midpoint + bisection ~34, local-minima/parabolic feature discovery ~22, quantised midpoint ranking ~8, winding ~10). §3.3 is the evidence that none of the three is decoration: the draft without them had a seam that moved with the sampling phase and the traversal direction. PR-DESCRIPTION.md is replaced by one description per branch, each arguing its own split. Counts, per branch, on OCP 7.9.3: tests/test_direct_api 1169 -> 1171 / 1200 / 1207 passed with 2 skipped and no failures; rest of tests/ 1037 passed, 1 skipped everywhere, same as pristine dev. All three diffs re-verified by applying them to a pristine dev tree with patch -p1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Kernel errors used to surface as "the OCCT kernel threw '6454200' (a raw wasm exception)". They now carry OCCT's own message, e.g. the slide_latch 2-D fillet failure reads "the OCCT kernel raised 'BRep_API: command not done'". The intended route (the fork's OCJS::getStandard_FailureData) is bound but UNCALLABLE in this build: embind refuses with "unbound types: St9exception" because Standard_Failure derives from std::exception, which is never registered. The module also exports no runtime helpers (no HEAPU8/getValue/ UTF8ToString), so CascadeWorker now keeps the wasm Memory via Emscripten's documented `instantiateWasm` hook and StandardUtils reads Standard_Failure's StringRef message directly - COMPROMISE(failure-decode). Wired into every worker error path (CacheOp, ShapeToMesh, console.error, PythonRuntime), and ShapeToMesh no longer TypeErrors while trying to set .message on a number. Corpus: collect.py now also gathers docs/*.py (objects_1d/2d/3d, tutorial_joints, slide_latch, ...), docs/objects/examples, docs/topology_selection/examples, the 13 Too Tall Toby challenge parts, and every docs .rst code-block plus a cumulative script per page. Since most .rst blocks are prose fragments, the manifest is built in two stages: collect -> manifest-all.json (382 candidates), reference -> reference-all.json, then `collect.py prune` keeps the 232 that natively produce geometry (222 scored). sanitize() is now statement-aware so multi-line write_svg(...) calls no longer leave a dangling paren, and reference.py gives each script __file__ plus symlinked STEP/asset siblings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…PI gaps Select bookkeeping was a BuildPart-only side channel that recorded every edge and face of the result: `vertices(Select.LAST)` ignored `select` entirely and returned ALL vertices. That is what made the docs' slide_latch die inside FilletFace2D - it filleted the slot arcs' tangent vertices, not the four rectangle corners upstream selects. `lasts` is now computed inside _combine for every builder and every operation with upstream's semantics (`post - pre` per sub-shape type, the combined objects for the builder's own type), so vertices/edges/faces/solids(Select.LAST) all agree with real build123d (4/6/6 and NEW=2 on the docs' Box+Cylinder case, verified against the venv). The difference is taken on geometry, since lite rewraps shapes and cannot rely on TopoDS identity surviving a boolean. Also added, all against upstream sources: - `new_edges(*objects, combined=)` + `Select.NEW` - upstream's BRepAlgoAPI_Cut of the combined shape's edge list by the operands' (StandardLibrary.NewEdges) - module-level `vertices()/edges()/wires()/faces()/solids()` context selectors - an `os` shim (path arithmetic only - no filesystem is faked) - `FilletPolyline`, `IntersectingLine`, `SlotCenterPoint`, `LengthMode` + `PolarLine(length_mode=, length=<limit shape>)`, `TangentArc( tangent_from_first=False)`, partial `Sphere` (BRepPrimAPI_MakeSphere angles), `Edge.find_intersection_points` (1-D form), `Edge.radius`/`is_interior`/ `find_tangent`/`make_circle`, `Curve.arc_center/radius`, `Axis(Location|Plane)`, `Shell.extrude`, `Compound.make_triad` (COMPROMISE(triad-labels): no singleline stroke font), and `_topo()` now accepts a Builder. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, docs build123d 0.11.1 gives every builder a FRESH location context on entry (Builder.__enter__ sets local_locations = LocationList([Location()])), so a Locations/GridLocations context wrapping a builder does not replicate what the builder constructs - verified in the venv (model._obj 141.372 = one cylinder, pp._obj 125.0 = one box, and even holes.sketch is local in 0.11.1). Lite fanned those out, so the docs' own "Locations around a builder" snippets built four rectangles instead of one; _ctx_locations now truncates the stack at the active builder's depth. Also: new_edges() maps its result back to the corresponding edges OF the combined shape so fillet() can consume it (this caught a maker_coin regression - raw cut edges have no per-shape index and leaked Brython objects into CacheOp's hash); _edges_by_parent now raises a readable error instead; filter_by/sort_by/group_by accept a class property object and filter_by accepts a Plane; Compound(builder.part.wrapped, joints=...) accepts a raw TopoDS shape. Harness on the broadened 232-script corpus: 158 -> 177 PASS, 11 MISMATCH, 34 ERROR, 10 SKIP, and every script that passed on the old 129-script corpus still passes. Thirteen landmark passes frozen in python-mode-examples.spec.js (32 -> 45): seven Too Tall Toby challenge parts with their own mass asserts, the new_edges / context-selector / is_interior / FilletPolyline doc blocks and both "Locations around a builder" cases. report.md's defaults-audit table root-causes every remaining non-PASS; README/CLAUDE.md coverage refreshed. The one unrelated failure in the suite was pre-existing: a2a1847 made commits return to the Select tool but did not update the Box-tool gesture test's activeTool expectation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
177 PASS / 11 MISMATCH / 34 ERROR / 10 SKIP, reproduced on a clean run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The eight topology-selection docs scripts that failed on missing selector surface now run; six PASS, two reach geometry comparison. Ported from build123d 0.11.1: - Shape.distance / distance_to / distance_to_with_closest_points / closest_points (BRepExtrema_DistShapeShape, newly bound helper _distShapeShape); ShapeList.sort_by_distance now sorts by that MINIMAL distance instead of centre distance, like upstream. - Face.position_at(u, v), Face.center_location, Face.is_circular_convex / is_circular_concave / _curvature_sign. gp_Cylinder/gp_Sphere/gp_Torus are unbound in this wasm build, so the reference distance comes from the second fundamental form instead — same sign, same magnitude (COMPROMISE(curvature-sign)). - Mixin1D.normal() (conic axis, else the fitted plane's normal), Edge.param_at_point, Wire.param_at_point (arc length along the wire in WireExplorer order), sort_by/group_by(<Edge|Wire>). - GroupBy keeps its keys: group(key) / group_for(shape); group_by passes non-numeric keys through unrounded. filter_by_position returns the survivors sorted along the axis. ShapeList.wires(). add(<Builder>) uses the builder's result (a Builder is not iterable upstream either). - fillet/chamfer take their target from the ACTIVE BUILDER like upstream and map each edge onto it geometrically, so edge pools assembled from several intermediate shapes work ([f.outer_wire().edges() for f in faces]). make_hull is now a statement-for-statement port of Wire.make_convex_hull (sample -> 2-D hull -> connecting lines + TRIMMED source edges) instead of the simplified polygon, which removes COMPROMISE(make-hull): the boundary arcs are the source arcs again. The old polyline boundary was also what made FilletEdges abort the wasm heap on examples/cast_bearing_unit — that script now PASSES, and so does docs-rst/tips/b01. _edgeParamAtPoint reports -1 rather than JS null for "not on this edge" (a null crosses into Brython as NullType, which cannot be compared). Harness: 177 -> 185 PASS, 34 -> 24 ERROR, 11 -> 13 MISMATCH, no regressions; playwright 80/80. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
It belongs with the build123d contribution, not CascadeStudio: now lives on zalo/build123d branch canonical-research (research/), alongside the three stacked PR branches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports the tractable half of the 1-D CONSTRAINED/analytic object bucket, all
against the reference venv's intermediate values first:
- BSpline / Edge.make_bspline — EXACT Geom_BSplineCurve from poles, a knot
sequence (repeats -> multiplicities) and optional weights, as a new
'bspline' segment kind (JS BSplineEdge).
- ParabolicCenterArc / HyperbolicCenterArc — gp_Parab / gp_Hypr trimmed by
GC_MakeArcOfParabola / GC_MakeArcOfHyperbola ('parab' / 'hypr' segment
kinds), including make_hyperbola's major>=minor swap with the matching
angle-range shift, and the LIMIT form of arc_size (build both half arcs,
trim each at its first intersection with the limit, keep the shorter).
- EllipticalStartArc — the start-point/tangent ellipse frame construction.
- BlendCurve — cubic (C1) / quintic (C2) Bezier control points from
derivative_at(order 1, 2), plus ContinuityLevel.
- Airfoil — NACA 4-digit/fractional sections (numpy-free port). Its point
dedup has to round to GEOM_KEY_DIGITS like Vector.__hash__ does, otherwise
the two trailing-edge points differ by 1.8e-17 and the periodic
interpolation dies in BSplCLib::Interpolate.
- Triangle — with the trianglesolver package's law-of-sines/cosines solver
ported (sss/sas/ssa/aaas), the solved a/b/c/A/B/C and edge_*/vertex_*.
- ArrowHead + HeadType (drafting), Mixin1D.derivative_at, curvature_comb,
Edge.trim by POINT, Edge.trim_to_other, Curve.trim, AngularDirection,
Sagitta and Tangency enums.
- 'raw' segments (opaque non-analytic edges) now survive a rigid transform:
the Location is applied to the edge itself instead of raising, which is
what a trimmed elliptical arc added into a BuildLine needs.
Harness: 185 -> 194 PASS, 24 -> 15 ERROR, no regressions. Still ERROR:
ConstrainedArcs / ConstrainedLines (2 scripts) — OCCT's Geom2dGcc solvers are
unbound in this wasm build.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pytest.approx, and three fidelity fixes found by triage New objects/ops (each a port of the 0.11.1 implementation): - Wedge (BRepPrimAPI_MakeWedge's min/max form) and ConvexPolyhedron (hull facets sewn into a solid) — docs/objects_3d now PASSES. - Text(path=, position_on_path=) places each glyph on the curve exactly like Compound.make_text's position_glyph — docs-rst/objects-text/b10 PASSES. - topo_distance_to: BFS over the parent's peer adjacency (Faces via an Edge, Edges/Wires via a Vertex, Shells/Solids via a Face), with sub-shapes identified geometrically — docs-rst/topology_selection/b12 PASSES. - A real pytest.approx shim (rel 1e-6 / abs 1e-12, sequences and dicts); every other pytest attribute raises. docs/spitfire_wing_gordon now gets past its import and builds the wing Gordon surface, which takes ~390 s in this wasm build and then returns a null surface, so it is a TIMEOUT rather than an ERROR now (recorded in the audit). Fidelity fixes, each root-caused against the reference venv: - position_at/param_at EXTRAPOLATE outside [0, 1] instead of clamping, like upstream. `line @ 2/3` parses as `(line @ 2) / 3`, and the topology-selection docs rely on it — docs-selectors/selectors_operators PASSES. - A full CenterArc is ONE closed circle edge, not two half arcs (new 'circle' segment kind). The edge count of a circle is observable: group_by(Edge.length) keys and make_hull's per-edge sampling both change with it. With this, the hull of the group_properties_with_keys profile is bit-identical to upstream (490.921953150644). - copy.copy(<Builder>) shallow-copies the builder instead of returning it, so `before_fillet = copy(part)` is the snapshot the docs use it as (the copies were all reporting the FINAL geometry) — group_properties_with_keys PASSES with before_fillet 9751.639 / after_fillet 9730.739, exactly upstream's. - _shape_key uses the face CENTROID for faces: CenterOfMass is a volume integral here and returned the same bbox corner for every face of a solid, which silently emptied faces(Select.LAST). - find_intersection_points also reports contact without a sign change (an end point sitting on the line), like the tolerance-based Geom2dAPI intersector. - Vector(()) is the origin, and intersect(Axis) on a 1-D shape returns the ShapeList of Vertex upstream returns. Harness: 194 -> 199 PASS, 15 -> 11 ERROR, 13 -> 11 MISMATCH, 1 TIMEOUT; playwright 80/80. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two mismatches root-caused to the same place, BuildSketch's own bookkeeping: - offset() on a 2-D target now offsets the outer wire by +amount and each inner wire by -amount and rebuilds the planar face (upstream's operations_generic.offset face branch) instead of running a 3-D MakeOffsetShape, which was thickening the sketch by +-amount in z. - _combine reproduces BuildSketch._add_to_context's "align sketch planar faces with Plane.XY" step: a face that is not coplanar with Plane.XY is expressed in its own plane's frame and dropped onto z = 0, and every face is then oriented +Z. The orientation half is what makes a mirrored face FUSE with the face it was mirrored from — coplanar faces with opposite normals are not the same domain, so `mirror(about=Plane.YZ)` inside a BuildSketch was leaving two half faces behind (heart_token's outline). docs/heart_token and docs/slide_latch now PASS. slide_latch closes the localization question the audit left open: 0.11.1 DOES localize a global face added inside a face-workplane BuildSketch, but only when the face is not already coplanar with Plane.XY, and it keeps the in-plane x/y offset. Harness: 199 -> 201 PASS, 11 -> 9 MISMATCH; playwright 80/80. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ne selectors - extrude(taper=) now follows Solid.extrude_taper's TWO algorithms instead of always using LocOpe_DPrism: the DPrism only applies to a positive taper along the profile normal with no holes, and everything else is a LOFT between the profile wires and their 2-D offsets (-length * tan(taper), Kind.INTERSECTION, inner wires flipped). A negative taper was 1% small; a bare taper=-10 rectangle now measures 2957.1391331767363, bit-identical to the reference — ttt/ttt-ppp0107 PASSES (its `zz`/`zz2` intermediates were this, not extrude(until=) as the audit guessed). - Builder selectors read the line built SO FAR inside a BuildLine (`side_line.vertices()` mid-context returned nothing, because _obj only exists after __exit__), and fillet() of wire-corner vertices now says what is actually missing (Wire.fillet_2d) instead of "no edges given". Harness: 201 -> 202 PASS, 9 -> 8 MISMATCH; playwright 80/80. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- report.md regenerated (202 PASS / 8 MISMATCH / 11 ERROR / 1 TIMEOUT / 10 SKIP) with a rewritten defaults-audit table: every non-PASS root-caused, including three corrections of earlier verdicts — cast_bearing_unit's "kernel fillet fault" was lite's simplified hull, ttt-ppp0107's small intermediates were a TAPERED extrude (not extrude(until=)), and `Draft` in docs/objects_2d is drafting's dimension-styling dataclass, not the draft-angle operation. - README coverage block, CLAUDE.md's Python-mode tables, honest-gaps paragraph and COMPROMISE index updated: make-hull is GONE (the hull is exact now) and curvature-sign / traversal-order are new. - test/python-mode-examples.spec.js: 45 -> 50 frozen scripts, adding Wedge/ConvexPolyhedron, Triangle, the parabolic/hyperbolic arcs, slide_latch and group_properties_with_keys. Default suite is 85 tests, all green. - The canonical-edges research moved to zalo/build123d branch canonical-research (research/); every reference to the old in-repo path now points there, and canonical-cross-kernel.mjs takes the reference JSON path from --reference / B123D_CANONICAL_REFERENCE. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e targets) OCCT's whole Geom2dGcc family — the 2-D geometric constraint solvers upstream's ConstrainedArcs/ConstrainedLines are built on — is declared in the .d.ts but NOT registered at runtime in this wasm build, and neither is GccEnt. The two cases the docs exercise are circles (and points) tangent to circles, which is closed-form circle geometry, so they are solved directly here with upstream's semantics kept intact: - the centre loci are circles of radius R ± r per GccEnt qualifier (Tangency.OUTSIDE = external contact, ENCLOSING = the solution contains the target, ENCLOSED = the reverse, UNQUALIFIED = both); - a solution is rejected when its contact point falls outside the target's TRIMMED parameter range (upstream's _param_in_trim — this is what reduces the egg-plant example's candidates to one arc per constraint); - both arcs between the two contact parameters are built, so Sagitta.SHORT/LONG/BOTH picks the same one upstream picks, and the user `selector` sees the same solution set. Verified against the reference's own intermediates: 8 solution arcs with lengths [2.8095, 2.8095, 3.0834, 3.0834, 3.4914, 3.4914, 6.6149, 6.6149] and the 4 common tangent lines with identical end points. docs/objects_1d_constrained and docs-rst/tutorial_constraints/b13 PASS. The center=/center_on=/three-tangency/oriented-line overloads still raise with that reason: their solution SETS feed a user selector, so guessing an enumeration would be guessing the answer. Harness: 202 -> 204 PASS, 11 -> 9 ERROR (report/audit/README/CLAUDE.md updated); playwright 85/85. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The CLAUDE.md index promises that every compromise is grep-able in the sources; traversal-order was documented but unmarked. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description TODO