diff --git a/.gitignore b/.gitignore index 7fc8b691..f3f5cd4e 100644 --- a/.gitignore +++ b/.gitignore @@ -118,3 +118,7 @@ test-results/ # Agent test screenshots agent-test-*.png chess-pawn*.png + +# Vendored Pyodide core distribution (experimental Python runtime, fetched by +# packages/cascade-core/scripts/fetch-pyodide.cjs) +vendor/ diff --git a/CLAUDE.md b/CLAUDE.md index 5b2cf8de..64ad5c07 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,7 +11,7 @@ compiled to WebAssembly via Emscripten. The 3D viewport uses Three.js with a mat ```bash npm run build # builds cascade-core then cascade-studio npx http-server ./packages/cascade-studio/dist -p 8080 -c-1 --silent -npx playwright test # 12 tests, ~40s +npx playwright test # 85 tests (incl. 50 frozen build123d example scripts) ``` ## Architecture (Monorepo) @@ -69,10 +69,352 @@ Four methods — that's it: **NEVER** use `browser_take_screenshot` (captures full page UI, not the 3D model) or `browser_run_code` (use `setCameraAngle` instead). +## GUI Modeling Tools + +LeapShape-style tools in the viewport toolbar (top-left overlay). **Every GUI operation +emits JavaScript into the Monaco editor — the code IS the scene.** Committing a tool +action appends a snippet (via `executeEdits`, so Monaco undo works) and re-evaluates. + +**Tools**: Select (default), Box, Cylinder, Sphere, Sketch, Fillet. One active at a +time; Escape cancels the current interaction, then returns to Select; committing a tool action also returns to Select (creation tools are one-shot — reactivate from the toolbar to place another). OrbitControls +are disabled while a creation drag is in progress (like HandleManager's gizmo drags). + +**Gestures — every numeric stage accepts BOTH** (`Tool.stageDown`/`stageUp`): +press-drag-release, and click-move-click. A release with the stage's dimension still +zero is **non-destructive** (the stage stays armed; Escape is the only way to throw +away an in-progress solid). This was a real bug: the height stage used to `cancel()` +on any pointerdown while the height was 0, so pressing to drag the height destroyed +the whole box/cylinder — the second drag always failed with a real mouse, while the +synthetic-PointerEvent tests only exercised move-then-click and passed. Regression +tests drive Playwright's `page.mouse` (real CDP input), not `dispatchEvent`. + +- **Box**: pointerdown on the ground plane (snapped to integer mm) → size the footprint + → lock it → size the height → commits `let box1 = Translate([x,y,0], Box(w,d,h));` +- **Cylinder**: from the center → size the radius → lock it → size the height → commits +- **Sphere**: from the center → size the radius → commits +- **Sketch**: stateful multi-click profile drawing (Fusion-style sketch → extrude). + Clicks place grid-snapped vertices with a rubber-band preview (length/angle label); + the Line/Arc toggle (or `L`/`A` keys) picks the segment type — Arc segments take two + clicks (through-point, then end) and preview the live three-point arc. Escape is + vertex-level undo (a half-placed arc through-point is its own undo step); Enter or + clicking the first vertex closes (min 3 vertices; closing works from Arc mode too). + Once closed, corner-vertex clicks toggle sketch fillets (vertex 0, the Sketch start + point, can't be filleted — pitfall 5), and an inline panel commits as + **Extrude / Revolve / Face only**; for Extrude, dragging vertically inside the + profile sets the height interactively (the input reflects the drag). Emits the + Sketch builder chain, 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 on the tool (CAD origin + u/v basis in + `SketchTool.plane`) to make sketch-on-face feasible later; v1 always uses the + ground plane (XY at z=0), which maps 1:1 onto the default `new Sketch([u,v])` plane. +- **Fillet**: click edges to multi-select (orange highlight), set radius in the inline + panel, Enter/Apply commits `shapeVar = FilletEdges(shapeVar, r, [indices]);`. The edge + indices are exactly the per-shape indices the hover tooltip shows. If the producing + line is a bare expression (`Box(10,10,10);`), it is rewritten to `let box1 = ...` first. +- **Select**: clicking a shape reveals + flashes the editor line that produced it. + +**File map** (`packages/cascade-studio/src/tools/`): +- `ToolManager.js` — toolbar DOM, capture-phase pointer routing (fires before + OrbitControls), raycast/snap/CAD↔three helpers, variable naming, code emission, + Escape/keyboard routing (tools can consume Escape for stage-level undo) +- `Tool.js` — base class; `SelectTool.js`, `BoxTool.js`, `CylinderTool.js`, + `SphereTool.js`, `SketchTool.js`, `FilletTool.js` — per-tool state machines + +**Pick → line mapping**: `CacheOp` (StandardUtils.js) tags every produced shape with +`.producingLine`; `combineAndRenderShapes` (CascadeWorker.js) builds face/edge-hash → +sceneShape-index maps plus a `shapeLines` array that flow through ShapeToMesh into the +mesh payload (`face.shape_index`, `edge.shape_index`, `meshData.shapeLines`). The +viewport stores the shape index in the third vertex-color channel (faces) and in +`globalEdgeMetadata` (edges); `viewport.getPickInfo(intersect)` + `getShapeLine(i)` +resolve a click to an editor line. + +**Coordinates**: three.js scene is Y-up, CAD is Z-up. CAD `[x,y,z]` ↔ three `(x, z, -y)` +(see `ToolManager.cadToThree/threeToCad`, same mapping as CascadeViewHandles.js). + +**Testing hooks**: `CascadeAPI._tools` exposes the ToolManager. `test/gui-tools.spec.js` +drives tools two ways: synthetic PointerEvents on the canvas (fast, but they cannot +reproduce gesture bugs) and `page.mouse.down/move/up`, which is real CDP input — use the +latter for anything gesture-shaped. Fresh loads are Python mode now, so the JS-emission +specs call `CascadeAPI.setMode('cascadestudio')` in their `gotoAndReady` helper. + +## Python (build123d) Mode + +The **default** editor language mode (alongside `'cascadestudio'` and `'openscad'`): +users write **build123d algebra-mode** Python that evaluates in the existing CAD worker. +A parameter-less load opens `PYTHON_STARTER_CODE` (a parametric flanged bearing mount: +`Box` + `filter_by(Axis.Z)` fillet, fused boss, bore, `GridLocations` bolt holes, a +`Rot`'d set screw, `group_by(Axis.Z)[-1]` rim fillet) — see "URL Encoding & Mode +Defaults" for how each entry point picks its mode. + +**Architecture — Brython in the worker (NOT Pyodide — now a measured choice, +see `test/b123d-validation/runtime-comparison.md`)**: +- `packages/cascade-core/src/worker/PythonRuntime.js` lazily bootstraps Brython on the + FIRST Python evaluation: brython.js (~1.38 MB raw / ~300 KB gz, copied to dist by the + cascade-core build) is fetched as text and indirect-eval'd in the worker global scope + (module workers lack importScripts; brython.js is strict-mode, so its `__BRYTHON__`/`$B` + are exported onto `globalThis` from inside the eval'd text). JS mode pays zero cost. +- `packages/cascade-core/src/worker/Build123dLite.js` embeds the **build123d-lite** + Python source (a JS template string — beware: it must contain no backticks or `${`). + It is registered as the importable module `build123d` via + `__BRYTHON__.runPythonSource(src, 'build123d')`; user code runs as module `'main'`, + so user line numbers map 1:1 to editor lines (nothing is prepended). +- Python accesses the worker's standard library via `from browser import self as w` — + e.g. `w.Box(...)`, `w.Union([...])`. The JS functions own all sceneShapes bookkeeping, + so wrapped shapes are never double-added. (Brython wrappers defeat `indexOf` identity; + use Python `is` to compare shapes across the boundary.) +- `CascadeWorker.evaluate` branches on `payload.language === 'python'`: the evaluation + becomes async (Brython bootstrap) and its pending promise gates + `combineAndRenderShapes`; the worker's onmessage router supports Promise-returning + handlers. `resetWorking`/`modelHistory` still fire in the same order as JS mode. +- Python errors throw a JS Error whose message is `Python \n` + (extracted via `$B.error_trace(exc)`); it surfaces through the usual worker → + `window.onerror` → `CascadeAPI.getErrors()` path. NOTE: worker logs/errors post + asynchronously — tests must poll for console content, not sample right after runCode. +- **Line mapping works in Python mode**: `CacheOp` calls `self.getPythonUserLine()` + (walks Brython's frame chain to the innermost `'main'` frame, `frame.$lineno`) instead + of parsing JS eval stack frames. History steps, Select-pick → line flash, and the + Fillet tool's variable resolution all work on Python lines. +- **`?pyruntime=pyodide`** (or `localStorage['cascade-py-runtime']`) swaps the + interpreter for real CPython 3.14 on wasm — same Build123dLite.js source, + `packages/cascade-core/src/worker/PyodideRuntime.js`, needs + `node packages/cascade-core/scripts/fetch-pyodide.cjs` (gitignored `vendor/pyodide/`, + copied to dist only when present). It is a validated drop-in (identical 204/222 + classification, identical mismatch magnitudes) and is NOT the default: it costs 23x + the download and ~3x the boot for no user-visible win. Numbers, the interop-seam + notes and the recommendation live in `test/b123d-validation/runtime-comparison.md`; + the flag is covered by `test/py-runtime.spec.js` and benchmarked by + `test/b123d-validation/bench-runtime.mjs`. `CS_PY_RUNTIME=pyodide` switches + run-lite.mjs/probe.mjs over. + +**build123d-lite coverage** (vs real build123d 0.11.1 — validated by running +EVERY runnable script in the upstream `examples/` and `docs/` trees through both, +see `test/b123d-validation/`: the examples, the docs' own `.py` scripts, the 13 +Too Tall Toby challenge parts (mass asserts kept) and every docs `.rst` +code-block; currently **204/222 scripts PASS** (volume within 0.5%, bbox within +1e-3/axis), 8 MISMATCH, 9 ERROR, 1 TIMEOUT, 10 SKIP (real build123d fails +natively) — full breakdown with per-script reasons AND a hand-maintained +root-cause/defaults audit of every non-PASS in the committed +`test/b123d-validation/report.md`. A full 232-script harness pass takes ~150 s +with `--pages 4` (~5 min single-page); the harness MUST run with +`CS_TEST_HEADFUL=1 DISPLAY=:99` on this machine (headless Chromium has no WebGL, +which manifests as every script reporting "no measurement produced"). Debug a +single script with `test/b123d-validation/probe.mjs` (prints raw measurements + +errors). Since the GeomAPI-array binding round: Spline/Helix interpolate EXACTLY +(GeomAPI_Interpolate, incl. tangents=/tangent_scalars=/per-point tangents/periodic), +sweep uses MakePipeShell with upstream's trihedron/transition modes (incl. +multisection, normal=, binormal=), section()/make_hull/draft/project (BuildPart +form) work, joints are live (RigidJoint/RevoluteJoint/LinearJoint/ +CylindricalJoint/BallJoint with connect_to), scipy.optimize.minimize is shimmed +(pure-Python Nelder-Mead) with DoubleTangentArc on top, and Mesher writes STL +into MEMFS. Since the surface/text-parity round: +make_surface_from_array_of_points is EXACT (GeomAPI_PointsToBSplineSurface via +the fork's `Handle_Geom_BSplineSurface.AsGeomSurface` — never call `.get()` on +the surface handle, wrap with `BRepBuilderAPI_MakeFace_8(hs, tol)`), Text +topology matches upstream (one face per disjoint outer contour — i/j dots are +separate faces; counters stay holes; +Z oriented normals per glyph face), +`position_at`/`tangent_at` use arc-length fractions (GCPnts_AbscissaPoint, +upstream's `_occt_param_at`), thicken/Solid.thicken work (reconstructed +BRepOffset walls), Solid/Face.extrude/revolve/make_loft/make_sphere/ +make_cylinder classmethods, Compound.make_text, Shell(faces)+Solid(shell) +sewing, Vertex(...) point forms, find_intersection_points + +Shape.project_faces (text-on-path projection), and scipy.spatial.ConvexHull is +served by the worker's bundled quickhull3d. The known OCCT 8.0.1 wasm fuse +fault (coplanar BSpline-edged contact faces DROP an operand) is now detected +by volume and RECOVERED from the correct General-Fuse partition instead of +raising. Since the freeform-surface round: `Face.make_gordon_surface` builds +real curve-network Gordon surfaces (a JS port of ocp_gordon — GordonSurface.js +— realized through a scored least-squares refit, see +COMPROMISE(gordon-surface-realization)), `Face.wrap`/`Shape.wrap_faces` +conform flat Edges/Wires/Faces onto a curved surface along a path, +`Face.make_surface` fills a non-planar boundary (BRepOffsetAPI_MakeFilling), +`Face.location_at`/`normal_at` give surface frames at normalized u/v or a 3D +point, `Wire`/`Edge.project_to_shape` project along a direction or from a +cone apex (BRepProj_Projection), and `offset(side=Side.LEFT/RIGHT)` does +one-sided offsets of OPEN lines. `Face(wire)` is now planar-only like +upstream, `make_face` cleans its result like upstream's `_add_to_context`, +and `Trapezoid`'s obtuse-side-angle case matches upstream. Since the +selectors/1-D-solver round: `make_hull` is a statement-for-statement port of +`Wire.make_convex_hull` (trimmed source arcs, no polyline approximation — which +also closed two supposed "kernel fillet faults"), the topology-selection +property surface is complete (`Face.center_location`/`position_at`/ +`is_circular_convex`/`is_circular_concave`, `Mixin1D.normal`, +`Edge`/`Wire.param_at_point`, `Shape.distance`/`distance_to`/`closest_points` +via BRepExtrema, `sort_by()`, `topo_distance_to`, +`GroupBy.group(key)`), the analytic 1-D objects are in (`BSpline`, +`ParabolicCenterArc`/`HyperbolicCenterArc` incl. limit `arc_size`, +`EllipticalStartArc`, `BlendCurve`, `Airfoil`, `Triangle`, `derivative_at`, +`curvature_comb`, `trim` by point, `trim_to_other`), `Wedge`, +`ConvexPolyhedron`, `Text(path=)` and `ArrowHead` exist, and five fidelity +defaults were corrected: `position_at` EXTRAPOLATES outside [0, 1] (`line @ 2/3` +parses as `(line @ 2) / 3`), a full `CenterArc` is ONE closed edge, +`copy.copy()` snapshots, `extrude(taper=)` follows both of upstream's +algorithms, and `BuildSketch` localizes + orients (+Z) every incoming face. +Since the OCCT-binding round: OCCT's whole **`Geom2dGcc` 2-D constraint-solver +family is real here** (the fork's binding files were failing to compile on one +enum-out-param method — see its CHANGELOG), so `ConstrainedArcs`/ +`ConstrainedLines` are a statement-for-statement port of upstream's +`topology/constrained_lines.py` across ALL overloads; `Wire.fillet_2d` (1-D +corner fillets on `ChFi2d_FilletAlgo`) and `make_brake_formed` landed with it; +`full_round` works on a real pure-Python 2-D `scipy.spatial.Voronoi` +(Bowyer-Watson circumcentres, vertex-set-identical to scipy); `import_step` +reads assets handed to the worker up front (`CascadeAPI.loadExternalFiles`); +`gp_Cylinder`/`gp_Sphere`/`gp_Torus` and `Extrema_ExtAlgo` retired the +`curvature-sign` and `point-projection` compromises; and joints gained +`symbol`, survival through `Shape.moved`/`Compound(joints=)`, +`Shape.show_topology` and `Compound.do_children_intersect`: + +| Area | Supported | Not supported | +|---|---|---| +| Builders | `with BuildPart/BuildSketch/BuildLine(...)` as plain context managers over a module-level stack (nesting, `mode=`, multiple workplanes, pending faces/edges/path), `Mode.ADD/SUBTRACT/INTERSECT/REPLACE/PRIVATE`, `add()` (incl. Locations-context replication into BuildLine), `Select.LAST`/`Select.NEW` for vertices/edges/faces/solids (upstream's `post - pre` bookkeeping; a builder gets a FRESH location context on entry, so an enclosing `Locations` never replicates its result), `Workplanes()` (shares the Locations fanout path — a plane basis IS its Location) | — | +| 3D objects | `Box`, `Cylinder` (incl. `arc_size`), `Sphere`, `Cone`, `Torus`, `Wedge`, `ConvexPolyhedron`, `Hole`, `CounterBoreHole`, `CounterSinkHole` — all with `rotation=`/`align=`/`mode=`; partial `Sphere(r, a1, a2, a3)`; `Solid.extrude_linear_with_rotation` | partial cones | +| 2D objects | `Rectangle`, `RectangleRounded`, `Circle`, `Ellipse`, `Polygon`, `RegularPolygon`, `Triangle` (ported trianglesolver), `Trapezoid`, `SlotOverall`, `SlotCenterToCenter`, `SlotCenterPoint`, `SlotArc`, `Text` (opentype.js/FreeSans, FreeType-parity kerning, incl. `path=`/`position_on_path=`), `ArrowHead`/`HeadType`, `BaseSketchObject`/`BasePartObject` subclassing, `Face(outer_wire, [hole_wires])`, `Face.make_rect`, `Face.make_surface_from_array_of_points`, `Face.radius`/`Face.axis_of_rotation` | `Text(font_path=)`, the rest of `drafting` (`Draft`, `ExtensionLine`, `DimensionLine`, `TechnicalDrawing`) | +| 1D objects | `Line`, `Polyline`, `PolarLine` (incl. `length_mode=` and a limit shape as `length=`), `FilletPolyline`, `IntersectingLine`, `ThreePointArc`, `RadiusArc`, `SagittaArc`, `CenterArc` (a full one is ONE closed edge, like upstream), `TangentArc`, `JernArc`, `Bezier` (incl. weights), `Spline` (EXACT GeomAPI_Interpolate incl. `tangents=`/`tangent_scalars=`/per-point/periodic), `BSpline` (EXACT poles/knots/multiplicities/weights), `DoubleTangentArc`, `BlendCurve` (C0/C1/C2), `Helix`, `EllipticalCenterArc`, `EllipticalStartArc`, `ParabolicCenterArc`/`HyperbolicCenterArc` (incl. the LIMIT form of `arc_size`), `Airfoil` (NACA 4-digit), `ConstrainedArcs`/`ConstrainedLines` (ALL upstream overloads on OCCT's real Geom2dGcc solvers: `radius=`, `center_on=`, three-tangency, `center=`, `radius=`+`center_on=`, two-tangent lines, tangent+point, oriented lines — Tangency qualifiers, trim-range rejection and Sagitta selection), `curve @ u / % u / ^ u` (incl. multi-edge curves and EXTRAPOLATION outside [0, 1]), `Edge.make_line/make_circle/make_mid_way/make_spline/make_bspline/param_at/param_at_point/trim (by point)/trim_to_other`, `derivative_at`, `curvature_comb`, `Mixin1D.normal`, `Wire(edges)`, `Wire.order_edges`/`is_closed`/`param_at_point`, `Edge.arc_center`/`radius`/`is_interior`/`find_tangent`/`find_intersection_points` | conical `Helix` | +| Ops | `extrude` (dir/both/`taper=` — both of upstream's algorithms: LocOpe_DPrism for a positive taper along the normal, otherwise the offset loft — /`until=Until.NEXT/LAST`), `revolve` (arbitrary Axis), `loft`, `sweep` (MakePipeShell: `is_frenet`, `transition=`, `normal=`, `binormal=`, `multisection=True`), `fillet`/`chamfer` (3D edges), `fillet` (2D sketch vertices), `offset` (2D + solid, `openings=`, Kind.ARC/INTERSECTION), `mirror`, `split` (Keep.TOP/BOTTOM), `scale` (uniform about location + non-uniform gp_GTrsf; spec-level inside BuildLine), `make_face`, `make_hull`, `section()`, `draft()`, `project()` (BuildPart pending-faces form), `project_to_shape`, `project_to_viewport` (HLR), `project_faces` (path-on-shape), `find_intersection_points`, `thicken` (see COMPROMISE(thicken)), `bounding_box()`, `pack()`, `offset` (2-D FACE offsets follow upstream's outer/inner-wire branch), `offset(side=Side.LEFT/RIGHT, closed=)` on open lines, `Face.wrap`/`Shape.wrap_faces`, `Face.make_surface`, `Face.make_gordon_surface`, `Face.location_at`/`normal_at`, `Wire`/`Edge.project_to_shape`, `Wire.offset_2d`, `Wire.fillet_2d` (1-D corner fillets, ChFi2d_FilletAlgo), `make_brake_formed`, `full_round`, `edges_to_wires` | `offset(min_edge_length=)` (no `fix_degenerate_edges`), `split(Keep.BOTH)`, screen-projection `project()` forms | +| Locations | full `Location` (matrix-based; 1/2/3-arg incl. axis-angle), `.position/.orientation/.x_axis/.y_axis/.z_axis`, `Axis(Location)`/`Axis(Plane)`, `Pos`, `Rot`/`Rotation`, `Plane` (named planes, `Plane(face)` with the exact gp_Ax3/D1 x_dir rule, `offset()`, `rotated()`), `Locations`, `GridLocations`, `PolarLocations`, `HexLocations`, `Workplanes` (context managers AND iterables, `append()`), `planes * shape`, `locs * shape`; shapes track a composed `.location` (`locate()/located()` are absolute; `.position` settable) | `Location.orientation` edge cases | +| Joints | `RigidJoint`, `RevoluteJoint`, `LinearJoint`, `CylindricalJoint`, `BallJoint` — upstream's exact relative-location algebra; `connect_to` repositions the other part; `copy.copy` AND `Shape.moved` rebind joints; `Compound(joints=)` reparents them; builder-scoped joints transfer to the part on exit; `Joint.symbol`, `Shape.show_topology`, `Compound.do_children_intersect`, `shape.parent = ` | assembly structure / XCAF (roadmap) | +| Selectors | `.edges()/.faces()/.vertices()/.solids()/.wires()` as ShapeLists (a Builder's selectors read the shape built SO FAR, which matters inside a BuildLine) (plus the module-level `edges()`/`vertices()`/… context getters and `Select.ALL/LAST/NEW` on every builder, `new_edges(*objects, combined=)`) with `filter_by` (Axis with DEGREES tolerance/GeomType/Plane/callable/class property), `filter_by_position`, `group_by`, `sort_by` (Axis/SortBy/class property incl. RADIUS, opt-in geometric `tie_break=`), `sort_by()` (parameter along that shape), `sort_by_distance` (MINIMAL distance), `topo_distance_to`, `GroupBy.group(key)`/`group_for`, slicing, `+` keeps ShapeList; `Face.center_location`/`position_at`/`is_circular_convex`/`is_circular_concave`, `Shape.distance`/`distance_to`/`closest_points` (BRepExtrema); Edge `position_at/tangent_at/@/%` are orientation-aware, `Axis(edge)` raw-curve like upstream | — | +| Canonical edges | `canonical()`/`canonical_form()` on Edge/Wire, `canonical_form(sampler, length, closed)`, `lexicographic_key`, `loop_area_vector`, `CanonicalForm`, `CANONICAL_SAMPLES`/`CANONICAL_BAND`, `Axis(edge, canonical=True)`, `Edge.reversed()`; `Edge.make_mid_way` canonicalizes its references (default-on) and `sort_by(..., tie_break=True)` breaks ties geometrically (opt-in) — defaults exactly as in the patch | automatic merging of C0-continuous free edges (out of scope upstream too — reassemble with `edges_to_wires` first) | +| Algebra | `+ - &` (incl. lists; multi-tool cuts fuse tools first; fuse guarded against the known 8.0.1 drop fault), `Part()/Sketch()/Curve()` empty starters, `Compound(children=)`, `copy.copy`, `Shape.__iter__` | — | +| Measure | `volume/area/length` (volume = per-solid sum), `center()`, `bounding_box()` (exact Bnd_Box), `.wrapped`, `.is_forward` | mass properties | +| Stdlib | `math`, `copy` (incl. `copy.copy()` snapshots), `typing`, `functools`, `itertools`, `operator`, `logging`, `random`/`timeit` (CPython-exact), `os` (PATH ARITHMETIC ONLY — `os.path.join/dirname/abspath/...`, `getcwd`; no filesystem is faked, `os.path.exists` is always False), `scipy.optimize.minimize`/`minimize_scalar` (pure-Python Nelder-Mead / bounded golden-section), `scipy.spatial.ConvexHull` (3-D, bundled quickhull3d), `scipy.spatial.Voronoi` (2-D, Bowyer-Watson Delaunay circumcentres — `.vertices` only, verified vertex-set-identical to scipy on full_round's inputs), `pytest.approx` (real, documented tolerances) | `numpy`, `sympy`, the rest of `pytest`, 2-D `ConvexHull`, Voronoi ridges/regions (raise loudly), everything else | +| Export/import | `Mesher` (STL into worker MEMFS), `export_stl` (MEMFS), `import_step` (assets handed to the worker up front — `CascadeAPI.loadExternalFiles({name: text})`; resolved by base name) | 3MF (no lib3mf — raises), `export_step/gltf` (no-ops), `ExportDXF`, `import_stl`/`import_svg` | + +**Known honest gaps** (kept as ERRORs rather than fake geometry — see the +defaults-audit table in report.md for per-script root causes and +upstream-vs-lite defaults comparisons): the `drafting` module beyond +`ArrowHead` (`Draft`, `ExtensionLine`, `DimensionLine`, `TechnicalDrawing` — +`Draft` in docs/objects_2d is the dimension-styling dataclass, NOT the +draft-angle operation, which lite has had for rounds; the port is ~450 code +lines whose accuracy rides entirely on `Compound.make_text` glyph metrics, +since `Text(...).bounding_box().size.X` feeds every arrow position and +`DimensionLine`'s 3-candidate label placement), `sympy`, 3MF export (no lib3mf +in this wasm build — dual_color_3mf builds all six of its shapes correctly and +then fails on `Mesher.write`), `fix_degenerate_edges`/`offset(min_edge_length=)` +and `split(Keep.BOTH)`. +`docs/spitfire_wing_gordon` is a TIMEOUT, not a gap: it runs (real +`pytest.approx` shim) and spends ~390 s building the wing's Gordon surface +before returning a null surface — the cost/robustness of +COMPROMISE(gordon-surface-realization) at that scale. `examples/heat_exchanger` +sits right at the 60 s harness budget (~55 s on an idle machine) and times out +when the four harness pages contend; it passes on its own. +Two scripts die on KNOWN OCCT 8.0.1 wasm kernel faults with byte-identical +defaults to upstream: the truck-body `FilletEdges` (toy_truck) and the +coplanar-BSpline fuse operand drop that even the General-Fuse rebuild cannot +recover (ttt-ppp0110). NOTE for the record: the OTHER "kernel fillet fault", +cast_bearing_unit, turned out to be a LITE bug — a simplified convex hull +handing hundreds of micro-edges to BRepFilletAPI — and now passes, together +with docs-rst/tips/b01. +The remaining MISMATCHes are traversal/orientation history plus two +single-shape residuals: COMPROMISE(edge-orientation) for joints x2, projection +x2 and docs-selectors/sort_axis (sub-edge FORWARD/REVERSED flags and hence +`Axis(edge)` differ from OCP 7.x over identical curve geometry), +COMPROMISE(traversal-order) for docs-selectors/filter_all_edges_circle (the +script keeps the LAST of a mirror-symmetric face pair) and docs-rst/tips/b04 (a +`sort_by(Axis.Z)` over local sketch vertices that is a COMPLETE tie, so the +kernel's enumeration decides), COMPROMISE(triad-labels) for docs/objects_1d, +`m6_screw` alone in docs/tutorial_joints (a `CylindricalJoint` hole frame), and +`l1`/`l2` alone in ttt-23-02-02-sm_hanger (a BuildLine on a non-XY workplane +leaves its module-level line variables in LOCAL coordinates in lite, and the +harness compares the last binding of a reused name). Canonical free edges +(below) shrank two of joints' three residuals (pin_arm 8.16 -> 2.69 mm, +slider_arm 11.80 -> 9.11 mm) without changing any classification; closing the +rest needs the examples to opt into `sort_by(..., tie_break=True)` / +`Axis(edge, canonical=True)`, which is opt-in upstream too. + +**Known compromises** (each marked in source with a grep-able +`COMPROMISE()` comment — `grep -rn "COMPROMISE(" packages/` is the +authoritative list): +- `scipy-shim` — pure-Python Nelder-Mead/golden-section instead of scipy; all other scipy APIs raise. +- `joints` — location algebra on lite shapes; no assembly tree/XCAF (roadmap), no joint symbols. +- `mesher` — STL only, written into the worker's in-memory Emscripten FS (no lib3mf, no disk). +- `double-tangent-arc` — scan+bisection root solve over a sampled/refined curve distance; trims the over-extended target segment where upstream relies on wire fixing. +- `kernel-guard` — fuse results smaller than the largest input are rebuilt from the (correct) General-Fuse partition; the partition keeps internal contact faces, so selectors see the contact topology. +- `sweep` — trihedron/transition calls match upstream exactly; residual MakePipeShell numeric differences are kernel-version. +- `helix` — exact interpolation through dense samples with analytic tangents (no surface-curve segment type). +- `edge-orientation` — sub-edge FORWARD/REVERSED can differ from OCP 7.x over identical curve geometry; Axis(edge)-based measuring lands at the other end (joints x2, sort_axis) and closed intersection-curve paths traverse the opposite way (projection x2). +- `traversal-order` — where a script keeps whichever of two SYMMETRIC results the kernel enumerated last (filter_all_edges_circle) or resolves a completely TIED `sort_by` (tips/b04), the answer follows OCCT's traversal of lite's construction, not OCP 7.x's of upstream's. `sort_by(..., tie_break=True)` makes it deterministic, and is opt-in upstream too. +- `thicken` — upstream's BRepOffset_MakeOffset Thickening mode is unbound; the same offset shell is built via MakeThickSolidByJoin and the missing side walls are reconstructed as ruled lofts + sewing. +- `project` — only the BuildPart pending-faces form; projected pending planes use the reversed projection direction (validated on maker_coin). +- `text` — bundled FreeSans only; non-Latin glyph metrics may differ from other Arial substitutes. +- `raw-segments` — non-line/circle edges ride through wires as opaque TopoDS edges (exact, but not transformable at spec level). +- `volume-measure` — volume is summed per solid (8.0.1's VolumeProperties picks up stray-face contributions on mixed compounds). +- `gordon-*` (GordonSurface.js) — the ocp_gordon port: curve/curve intersections via GeomAPI_ExtremaCurveCurve, a JS Geom2dAPI_Interpolate reimplementation, conic→non-rational approximation, and `gordon-surface-realization` (the exact tensor-product surface cannot be built as a Geom_BSplineSurface in this wasm build, so it is refit from a dense sample grid with a C2 least-squares approximation scored against the exact surface's area — bracelet's tip lands within 0.02%). +- `projection-sort` / `projected-edge-split` — projected wires are ordered by centre of mass (upstream uses the half-arc-length point), and a projected curve that this kernel splits where it grazes the surface boundary is re-concatenated (build123d's clean() leaves the B-spline-concat flag off). +- `edges-to-wires` — ShapeAnalysis_FreeBounds::ConnectEdgesToWires needs the unbound TopTools_HSequenceOfShape, so edges are chained on their endpoints and each group is ordered by ShapeFix_Wire. +- `failure-decode` — OCCT's C++ exceptions arrive in JS as raw pointer numbers. The fork binds `OCJS::getStandard_FailureData` for exactly this, but it is UNCALLABLE here ("unbound types: St9exception" — `Standard_Failure` derives from `std::exception`, which the build never registers) and no runtime helpers (`HEAPU8`/`getValue`/`UTF8ToString`) are exported, so CascadeWorker keeps the wasm `Memory` via Emscripten's `instantiateWasm` hook and StandardUtils reads `Standard_Failure`'s `StringRef` message out of it directly. Users see e.g. "the OCCT kernel raised 'BRep_API: command not done'" instead of "threw '6454200'". +- `new-edges-partial` — `new_edges()` maps its result back to the corresponding edges OF the combined shape (so it can be filleted like upstream's maker_coin does); an edge that is only PARTLY new has no counterpart and is returned as bare geometry. +- `triad-labels` — `Compound.make_triad` draws the axes and arrow heads exactly, but not upstream's X/Y/Z labels: those need the `singleline` STROKE font, and this build ships only the outline font FreeSans. + +**Roadmap (deliberately deferred)**: +- XCAF-based assemblies: real part identities, STEP hierarchy/names/colors, a + viewport assembly tree, and upgrading joints from location algebra to real + assembly constraints. +- Real-build123d-over-OCP-shim crossover: revisit once the remaining gap is + dominated by semantics-replication effort; current blockers are numpy in + geometry.py and the sheer OCP binding surface. + +**Canonical free edges**: lite implements the upstream *canonical free-edge +parametrization* proposal (research record + patch in +`docs/upstream-canonical-edges/`). A free edge — one produced by a section, +projection or boolean rather than drawn — inherits the seam, direction and +parameter range the kernel found convenient, and those depend on the parametric +frames of the operand surfaces, so `position_at(0)` / `Axis(edge)` move when a +geometrically identical solid is re-framed. `edge.canonical()` returns the same +geometry traversed from a geometry-defined start: open shapes from the +lexicographically smaller end; closed shapes from the arc-length midpoint of the +extremal band `{x ≤ x_min + 1e-6·bbox}`, where the candidate bands are the +**local minima** of the sampled coordinate (plateaus collapsed), each +bisection-refined to its midpoint, and the midpoints are ranked with the +remaining coordinates **quantised to the band width** so a mirror-symmetric pair +ties on `y` and `z` decides (x→y→z fall-through for flat loops); winding CCW +about the dominant axis of the loop's area vector. **Defaults match the patch exactly**: +`canonical()`, `Axis(edge, canonical=True)` and `sort_by(..., tie_break=True)` +are opt-in (the default sort stays a plain stable sort, so chained +`sort_by(SortBy.RADIUS).sort_by(Axis.Z)` keeps working), while +`Edge.make_mid_way`'s canonicalization is unconditional. The `tie_break` key is +the shape's vertex positions sorted+rounded to 6 digits, with `center()` as a +second stage, computed only inside a tie group. Re-seaming a closed loop needs +one substitution, not a compromise: `GeomConvert_CompCurveToBSplineCurve` is +unusable here (`Convert_ParameterisationType` is an unbound Embind type), so +`ConcatEdgesToEdge` (StandardLibrary.js) does the concatenation itself — +exactly, converting analytic conics through the rational-quadratic construction +and degree-raising with OCCT's `IncreaseDegree`. Verified against PATCHED +upstream build123d on OCP 7.9.3: 185 canonical measurements agree to +**0.00e+0 mm** while the raw seams differ +(`test/b123d-validation/canonical-cross-kernel.mjs`, reference generated by +`docs/upstream-canonical-edges/experiments/lite_cross_kernel.py`). Frozen in +`test/python-mode-canonical.spec.js`. + +**GUI tools in Python mode**: Box/Cylinder/Sphere emit `name = Pos(cx, cy, cz) * +Primitive(...)` — since build123d primitives are centered, the emission converts the +dragged corner/base placement into the shape's center. Fillet emits +`var = fillet(var.edges(indices=[...]), r)`. The **Sketch tool stays JS-only** (it emits +a `new Sketch(...)` builder chain): in Python mode its toolbar button is grayed +(`cs-tool-disabled`) and its tooltip says "not available in Python mode yet (switch to +CascadeStudio JS mode)"; `ToolManager.activate('sketch')` refuses with a console error. +See `test/python-mode.spec.js`. + +**Validation against real build123d**: `test/b123d-validation/` (see its README) +runs the upstream build123d examples through BOTH real build123d 0.11.1 (native +venv) and Python mode, comparing per-variable volume/bbox. Re-run it whenever +Build123dLite.js changes. FIFTY representative passing scripts are frozen +as regression tests in `test/python-mode-examples.spec.js` (part of the default +suite) with volumes/bboxes hardcoded from the native run — the newest five cover +`Wedge`/`ConvexPolyhedron`, `Triangle`, the parabolic/hyperbolic arcs, +slide_latch (sketch-face alignment + `Select.LAST` vertices) and +group_properties_with_keys (builder copy snapshots + the exact convex hull + +`GroupBy.group`). + ## Playwright Testing WebGL requires `--use-gl=angle --use-angle=swiftshader` in playwright.config.js launch args. +Environment overrides (for machines where the defaults don't work): +- `CS_TEST_PORT=8517` — test server port (default 8080; use when 8080 is occupied) +- `CS_TEST_HEADFUL=1 DISPLAY=:99` — run headful against an X server (use when headless + Chromium cannot create a SwiftShader WebGL context, as on this machine) + ```javascript await page.goto('http://localhost:8080'); await page.waitForFunction(() => window.CascadeAPI?.isReady()); @@ -291,16 +633,37 @@ await page.evaluate(() => CascadeAPI.showFinalResult()); - Generates `dist/index.html` - **Output**: `packages/cascade-studio/dist/` -## URL Encoding +## URL Encoding & Mode Defaults + +Projects can be shared via URL: `?code=&gui=&mode=python` + +- `code` / `gui`: `encodeURIComponent(btoa(deflateSync(text)))` (using fflate). + Decoding: `inflateSync(atob(decodeURIComponent(encoded)))` (compatible with master's + RawDeflate). A missing/malformed `gui` is tolerated. +- `mode`: **plain, human-readable** — one of `CascadeStudioApp.MODES` + (`cascadestudio` | `openscad` | `python`). Written by the save-to-URL path + (F5 / Ctrl+S → `EditorManager.evaluateCode(true)`). + +Mode resolution (`CascadeStudioApp.initialize`, tested in `test/modes-and-urls.spec.js`): -Projects can be shared via URL: `?code=&gui=` +| Load | Mode | +|-----------------------------------|---------------------------------------------| +| no params, no project | `python` (`CascadeStudioApp.DEFAULT_MODE`) | +| `?code=…` **without** `&mode=` | `cascadestudio` — legacy links predate mode serialization and must not be captured by the Python default | +| `?mode=…` (with or without code) | that mode (unknown value → default) | +| saved project `_cascadeState.mode`| that mode (legacy project files → `cascadestudio`) | -Encoding: `encodeURIComponent(btoa(deflateSync(text)))` (using fflate) -Decoding: `inflateSync(atob(decodeURIComponent(encoded)))` (compatible with master's RawDeflate) +Starters live on the app class (`STARTER_CODE`, `OPENSCAD_STARTER_CODE`, +`PYTHON_STARTER_CODE`, dispatched by `CascadeStudioApp.starterCode(mode)`); all three +must evaluate with zero errors. `saveProject()` serializes `mode` alongside the code. ## Key Dependencies -- **opencascade.js**: Custom fork of OCCT 8.0.0 RC4 compiled with emsdk 4.0.23 +- **opencascade.js**: Custom fork of OCCT 8.0.1 compiled with emsdk 4.0.23 + (branch `cascadestudio-v3-occt801` of the fork checkout; the build pipeline + and the reason each hand-registered symbol exists are in its CHANGELOG.md. + The worker cross-checks `USED_OCCT_SYMBOLS` against the module at startup, so + a renumbered overload or a silently-dropped binding fails loudly.) - See `node_modules/opencascade.js/CLAUDE.md` for build details - **Three.js r170**: 3D rendering (matcap material, OrbitControls) - `THREE.ColorManagement.enabled = false` for legacy rendering diff --git a/index.html b/index.html index 234eb6b9..e75d8443 100644 --- a/index.html +++ b/index.html @@ -73,6 +73,7 @@

diff --git a/package-lock.json b/package-lock.json index b6a90803..f851ad24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -551,6 +551,12 @@ "node": ">= 0.8" } }, + "node_modules/brython": { + "version": "3.14.3", + "resolved": "https://registry.npmjs.org/brython/-/brython-3.14.3.tgz", + "integrity": "sha512-d06vY02XaJEexv6knEqNd6ZYpBguPXI4JrCcoBchJOaoy1yf3anmDrU7TWbY1Gyq/9BxG2mJ+X1GgTaFusT1Vg==", + "license": "BSD-3-Clause" + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -641,7 +647,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -835,6 +840,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-plane-normal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-plane-normal/-/get-plane-normal-1.0.0.tgz", + "integrity": "sha512-MUtCvFQCerkHvH97PyZ6IzUzxrJbkie7CA8oB/065l6Ui7UUq3JwiEMzXRasY0AotKmlj8/RieDDwvs9jLr+Sw==", + "license": "MIT", + "dependencies": { + "gl-vec3": "^1.0.3" + } + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -849,6 +863,41 @@ "node": ">= 0.4" } }, + "node_modules/gl-mat3": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gl-mat3/-/gl-mat3-1.0.0.tgz", + "integrity": "sha512-obeEq9y7xaDoVkwMGJNL1upwpYlPJiXJFhREaNytMqUdfHKHNna9HvImmLV8F8Ys6QOYwPPddptZNoiiec/XOg==", + "license": "zlib" + }, + "node_modules/gl-mat4": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gl-mat4/-/gl-mat4-1.2.0.tgz", + "integrity": "sha512-sT5C0pwB1/e9G9AvAoLsoaJtbMGjfd/jfxo8jMCKqYYEnjZuFvqV5rehqar0538EmssjdDeiEWnKyBSTw7quoA==", + "license": "Zlib" + }, + "node_modules/gl-quat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gl-quat/-/gl-quat-1.0.0.tgz", + "integrity": "sha512-Pv9yvjJgQN85EbE79S+DF50ujxDkyjfYHIyXJcCRiimU1UxMY7vEHbVkj0IWLFaDndhfZT9vVOyfdMobLlrJsQ==", + "license": "Zlib", + "dependencies": { + "gl-mat3": "^1.0.0", + "gl-vec3": "^1.0.3", + "gl-vec4": "^1.0.0" + } + }, + "node_modules/gl-vec3": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gl-vec3/-/gl-vec3-1.2.0.tgz", + "integrity": "sha512-ynW7j5ZshRTHXG5UPC5u6G52cBCNx53LbNlq2HQ1bAosZbO4wOCEBufXS7KGiKL1slMgy4j/77Ikt+vMznouLg==", + "license": "zlib" + }, + "node_modules/gl-vec4": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gl-vec4/-/gl-vec4-1.0.1.tgz", + "integrity": "sha512-/gx5zzIy75JXzke4yuwcbvK+COWf8UJbVCUPvhfsYVw1GVey4Eextk/0H0ctXnOICruNK7+GS4ILQzEQcHcPEg==", + "license": "Zlib" + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -1022,11 +1071,19 @@ "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==", "license": "MIT" }, + "node_modules/monotone-convex-hull-2d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/monotone-convex-hull-2d/-/monotone-convex-hull-2d-1.0.1.tgz", + "integrity": "sha512-ixQ3qdXTVHvR7eAoOjKY8kGxl9YjOFtzi7qOjwmFFPfBqZHVOjUFOBy/Dk9dusamRSPJe9ggyfSypRbs0Bl8BA==", + "license": "MIT", + "dependencies": { + "robust-orientation": "^1.1.3" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/object-inspect": { @@ -1044,7 +1101,7 @@ }, "node_modules/opencascade.js": { "version": "2.0.0-cascadestudio", - "resolved": "git+ssh://git@github.com/zalo/opencascade.js.git#03c26253880a1a561a03bfd1588475a56bc8eef0", + "resolved": "git+ssh://git@github.com/zalo/opencascade.js.git#6e0eba7aa0dff0456e41eae2d58a90458640c542", "license": "LGPL-2.1-only" }, "node_modules/opener": { @@ -1113,6 +1170,15 @@ "node": ">=18" } }, + "node_modules/point-line-distance": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/point-line-distance/-/point-line-distance-1.0.1.tgz", + "integrity": "sha512-20FaXAqnX4IISI9PRPddGZWjLxS6PmkODBEuKz6m7bYqbVSQvScD5VUAbe+ziCBOg3eoK4h7huinvcPzOUEdXw==", + "license": "MIT", + "dependencies": { + "gl-vec3": "^1.0.3" + } + }, "node_modules/portfinder": { "version": "1.0.38", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.38.tgz", @@ -1149,6 +1215,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quickhull3d": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/quickhull3d/-/quickhull3d-3.1.2.tgz", + "integrity": "sha512-PEoALuuLYI4pdvzH9K+PG8y1xZ9q6a350ahjKfezWHGyaOTuYK+lCCyBIFMOA1bnoTMFeQANlP6R2PmhjoUKQw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "get-plane-normal": "^1.0.0", + "gl-mat4": "^1.2.0", + "gl-quat": "^1.0.0", + "gl-vec4": "^1.0.1", + "monotone-convex-hull-2d": "^1.0.1", + "point-line-distance": "^1.0.0" + } + }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -1156,6 +1237,40 @@ "dev": true, "license": "MIT" }, + "node_modules/robust-orientation": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/robust-orientation/-/robust-orientation-1.2.1.tgz", + "integrity": "sha512-FuTptgKwY6iNuU15nrIJDLjXzCChWB+T4AvksRtwPS/WZ3HuP1CElCm1t+OBfgQKfWbtZIawip+61k7+buRKAg==", + "license": "MIT", + "dependencies": { + "robust-scale": "^1.0.2", + "robust-subtract": "^1.0.0", + "robust-sum": "^1.0.0", + "two-product": "^1.0.2" + } + }, + "node_modules/robust-scale": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/robust-scale/-/robust-scale-1.0.2.tgz", + "integrity": "sha512-jBR91a/vomMAzazwpsPTPeuTPPmWBacwA+WYGNKcRGSh6xweuQ2ZbjRZ4v792/bZOhRKXRiQH0F48AvuajY0tQ==", + "license": "MIT", + "dependencies": { + "two-product": "^1.0.2", + "two-sum": "^1.0.0" + } + }, + "node_modules/robust-subtract": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/robust-subtract/-/robust-subtract-1.0.0.tgz", + "integrity": "sha512-xhKUno+Rl+trmxAIVwjQMiVdpF5llxytozXJOdoT4eTIqmqsndQqFb1A0oiW3sZGlhMRhOi6pAD4MF1YYW6o/A==", + "license": "MIT" + }, + "node_modules/robust-sum": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/robust-sum/-/robust-sum-1.0.0.tgz", + "integrity": "sha512-AvLExwpaqUqD1uwLU6MwzzfRdaI6VEZsyvQ3IAQ0ZJ08v1H+DTyqskrf2ZJyh0BDduFVLN7H04Zmc+qTiahhAw==", + "license": "MIT" + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -1293,6 +1408,18 @@ "url": "https://github.com/sponsors/cocopon" } }, + "node_modules/two-product": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/two-product/-/two-product-1.0.2.tgz", + "integrity": "sha512-vOyrqmeYvzjToVM08iU52OFocWT6eB/I5LUWYnxeAPGXAhAxXYU/Yr/R2uY5/5n4bvJQL9AQulIuxpIsMoT8XQ==", + "license": "MIT" + }, + "node_modules/two-sum": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/two-sum/-/two-sum-1.0.0.tgz", + "integrity": "sha512-phP48e8AawgsNUjEY2WvoIWqdie8PoiDZGxTDv70LDr01uX5wLEQbOgSP7Z/B6+SW5oLtbe8qaYX2fKJs3CGTw==", + "license": "MIT" + }, "node_modules/union": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", @@ -1330,10 +1457,12 @@ "version": "2.0.6", "license": "MIT", "dependencies": { - "opencascade.js": "github:zalo/opencascade.js#cascadestudio-v2", + "brython": "^3.14.3", + "opencascade.js": "github:zalo/opencascade.js#cascadestudio-v3-occt801", "openscad-parser": "^0.6.3", "opentype.js": "^1.3.4", - "potpack": "^2.0.0" + "potpack": "^2.0.0", + "quickhull3d": "^3.1.2" } }, "packages/cascade-studio": { diff --git a/packages/cascade-core/fonts/FreeSans.ttf b/packages/cascade-core/fonts/FreeSans.ttf new file mode 100644 index 00000000..1d53e2fa Binary files /dev/null and b/packages/cascade-core/fonts/FreeSans.ttf differ diff --git a/packages/cascade-core/fonts/FreeSansBold.ttf b/packages/cascade-core/fonts/FreeSansBold.ttf new file mode 100644 index 00000000..58e62647 Binary files /dev/null and b/packages/cascade-core/fonts/FreeSansBold.ttf differ diff --git a/packages/cascade-core/fonts/FreeSansBoldOblique.ttf b/packages/cascade-core/fonts/FreeSansBoldOblique.ttf new file mode 100644 index 00000000..73c96c5b Binary files /dev/null and b/packages/cascade-core/fonts/FreeSansBoldOblique.ttf differ diff --git a/packages/cascade-core/fonts/FreeSansOblique.ttf b/packages/cascade-core/fonts/FreeSansOblique.ttf new file mode 100644 index 00000000..a09fb137 Binary files /dev/null and b/packages/cascade-core/fonts/FreeSansOblique.ttf differ diff --git a/packages/cascade-core/fonts/LiberationSans-Regular.ttf b/packages/cascade-core/fonts/LiberationSans-Regular.ttf new file mode 100644 index 00000000..7769c411 Binary files /dev/null and b/packages/cascade-core/fonts/LiberationSans-Regular.ttf differ diff --git a/packages/cascade-core/package.json b/packages/cascade-core/package.json index 553c1f2a..7ed8f395 100644 --- a/packages/cascade-core/package.json +++ b/packages/cascade-core/package.json @@ -35,9 +35,11 @@ "types/" ], "dependencies": { - "opencascade.js": "github:zalo/opencascade.js#cascadestudio-v2", + "brython": "^3.14.3", + "opencascade.js": "github:zalo/opencascade.js#cascadestudio-v3-occt801", "openscad-parser": "^0.6.3", "opentype.js": "^1.3.4", - "potpack": "^2.0.0" + "potpack": "^2.0.0", + "quickhull3d": "^3.1.2" } } diff --git a/packages/cascade-core/scripts/build.cjs b/packages/cascade-core/scripts/build.cjs index 26305a7e..8c560fda 100644 --- a/packages/cascade-core/scripts/build.cjs +++ b/packages/cascade-core/scripts/build.cjs @@ -17,7 +17,8 @@ if (fs.existsSync(distDir)) { } fs.mkdirSync(distDir, { recursive: true }); -// 1. Bundle the worker entry point +// 1. Regenerate the oc.* symbol manifest, then bundle the worker entry point +require('./generate-occt-symbols.cjs'); console.log('[cascade-core] Bundling worker...'); execFileSync(npx, [ 'esbuild', @@ -38,7 +39,28 @@ if (fs.existsSync(wasmSrc)) { fs.copyFileSync(wasmSrc, path.join(distDir, 'cascadestudio.wasm')); } -// 3. Copy fonts to dist/fonts/ +// 3. Copy Brython (lazy-loaded by the worker for Python/build123d mode) +console.log('[cascade-core] Copying Brython...'); +const brythonSrc = path.join(monoRoot, 'node_modules', 'brython', 'brython.js'); +if (fs.existsSync(brythonSrc)) { + fs.copyFileSync(brythonSrc, path.join(distDir, 'brython.js')); +} + +// 3b. Copy the Pyodide core distribution, IF it has been vendored +// (`node packages/cascade-core/scripts/fetch-pyodide.cjs`). Optional by +// design: Pyodide is the experimental `?pyruntime=pyodide` alternative to +// Brython, ~13.5 MB, off by default and absent from a plain checkout. +const pyodideSrc = path.join(monoRoot, 'vendor', 'pyodide'); +if (fs.existsSync(pyodideSrc)) { + console.log('[cascade-core] Copying Pyodide (experimental runtime)...'); + const pyodideDist = path.join(distDir, 'pyodide'); + fs.mkdirSync(pyodideDist, { recursive: true }); + for (const file of fs.readdirSync(pyodideSrc)) { + fs.copyFileSync(path.join(pyodideSrc, file), path.join(pyodideDist, file)); + } +} + +// 4. Copy fonts to dist/fonts/ console.log('[cascade-core] Copying fonts...'); const fontsDir = path.join(pkgRoot, 'fonts'); const distFontsDir = path.join(distDir, 'fonts'); diff --git a/packages/cascade-core/scripts/fetch-pyodide.cjs b/packages/cascade-core/scripts/fetch-pyodide.cjs new file mode 100644 index 00000000..3b87a31f --- /dev/null +++ b/packages/cascade-core/scripts/fetch-pyodide.cjs @@ -0,0 +1,62 @@ +/** + * Fetch the Pyodide **core** distribution into `vendor/pyodide/` (gitignored). + * + * Pyodide is the EXPERIMENTAL alternative Python runtime for build123d-lite + * (`?pyruntime=pyodide`); Brython remains the default. The core tarball is the + * smallest official bundle — the interpreter plus the Python stdlib, with no + * packages (no numpy/scipy) — which is the only variant that is even in the + * same conversation as Brython on size. + * + * It is NOT an npm dependency on purpose: ~13.5 MB of wasm/zip has no business + * in node_modules (or in git) for an experiment that is off by default. The + * build copies `vendor/pyodide/` into dist only when it exists, so a checkout + * without it builds and runs exactly as before. + * + * node packages/cascade-core/scripts/fetch-pyodide.cjs [version] + */ +const fs = require('fs'); +const path = require('path'); +const { execFileSync } = require('child_process'); + +const VERSION = process.argv[2] || '314.0.4'; +const monoRoot = path.join(__dirname, '..', '..', '..'); +const vendorDir = path.join(monoRoot, 'vendor', 'pyodide'); + +// Only what loadPyodide actually fetches at runtime; the tarball also carries +// CLI entry points and .d.ts files that would just bloat dist. +const RUNTIME_FILES = [ + 'pyodide.mjs', + 'pyodide.asm.mjs', + 'pyodide.asm.wasm', + 'python_stdlib.zip', + 'pyodide-lock.json', +]; + +if (RUNTIME_FILES.every((f) => fs.existsSync(path.join(vendorDir, f)))) { + console.log('[pyodide] already present in ' + vendorDir); + process.exit(0); +} + +const url = 'https://github.com/pyodide/pyodide/releases/download/' + + VERSION + '/pyodide-core-' + VERSION + '.tar.bz2'; +const tmp = path.join(monoRoot, 'vendor', 'pyodide-core.tar.bz2'); +fs.mkdirSync(path.join(monoRoot, 'vendor'), { recursive: true }); +console.log('[pyodide] downloading ' + url); +execFileSync('curl', ['-sL', url, '-o', tmp], { stdio: 'inherit' }); + +const extractDir = path.join(monoRoot, 'vendor', '.pyodide-extract'); +fs.rmSync(extractDir, { recursive: true, force: true }); +fs.mkdirSync(extractDir, { recursive: true }); +execFileSync('tar', ['xjf', tmp, '-C', extractDir], { stdio: 'inherit' }); + +fs.mkdirSync(vendorDir, { recursive: true }); +for (const file of RUNTIME_FILES) { + fs.copyFileSync(path.join(extractDir, 'pyodide', file), path.join(vendorDir, file)); +} +fs.rmSync(extractDir, { recursive: true, force: true }); +fs.rmSync(tmp, { force: true }); + +let total = 0; +for (const file of RUNTIME_FILES) { total += fs.statSync(path.join(vendorDir, file)).size; } +console.log('[pyodide] ' + VERSION + ' ready in ' + vendorDir + + ' (' + (total / 1048576).toFixed(1) + ' MB raw)'); diff --git a/packages/cascade-core/scripts/generate-occt-symbols.cjs b/packages/cascade-core/scripts/generate-occt-symbols.cjs new file mode 100644 index 00000000..121f7d36 --- /dev/null +++ b/packages/cascade-core/scripts/generate-occt-symbols.cjs @@ -0,0 +1,35 @@ +/** + * Scans the worker sources for every `oc.` reference and writes + * UsedOCCTSymbols.generated.js. The worker verifies this list against the + * loaded WASM at startup. + * + * Why: the Embind binding generator derives numbered overload suffixes + * (e.g. BRepBuilderAPI_MakeEdge_24) from each class's overload set. A new + * OCCT version can silently renumber them, which previously surfaced only + * as cryptic "not a constructor" errors deep inside user evaluations. + */ +const fs = require('fs'); +const path = require('path'); + +const workerDir = path.join(__dirname, '..', 'src', 'worker'); +const outFile = path.join(workerDir, 'UsedOCCTSymbols.generated.js'); + +const symbols = new Set(); +for (const file of fs.readdirSync(workerDir)) { + if (!file.endsWith('.js') || file.endsWith('.generated.js')) { continue; } + const src = fs.readFileSync(path.join(workerDir, file), 'utf8'); + for (const m of src.matchAll(/\boc\.([A-Za-z_][A-Za-z0-9_]*)/g)) { + symbols.add(m[1]); + } +} + +const sorted = [...symbols].sort(); +const banner = + '// GENERATED by scripts/generate-occt-symbols.cjs — do not edit.\n' + + '// Every oc.* symbol referenced by the worker sources. Verified against\n' + + '// the loaded OpenCascade module at startup (see CascadeWorker.init).\n'; +fs.writeFileSync( + outFile, + banner + 'export const USED_OCCT_SYMBOLS = ' + JSON.stringify(sorted, null, 2) + ';\n' +); +console.log(`[cascade-core] ${sorted.length} oc.* symbols -> ${path.relative(process.cwd(), outFile)}`); diff --git a/packages/cascade-core/src/engine/CascadeEngine.js b/packages/cascade-core/src/engine/CascadeEngine.js index eabdc3d1..a5070da7 100644 --- a/packages/cascade-core/src/engine/CascadeEngine.js +++ b/packages/cascade-core/src/engine/CascadeEngine.js @@ -58,8 +58,12 @@ class CascadeEngine { /** Evaluate CAD code and return mesh data. * Fires intermediate events (log, addSlider, etc.) in real-time. + * `language` selects the worker runtime ('cascadestudio' default; + * 'python' evaluates build123d-lite code via Brython). `pyRuntime` + * ('brython' default | 'pyodide') picks the Python interpreter for that + * path — see PyodideRuntime.js. * Returns: { meshData: { faces, edges }, sceneOptions, logs, errors } */ - async evaluate(code, { guiState = {}, maxDeviation, sceneOptions } = {}) { + async evaluate(code, { guiState = {}, maxDeviation, sceneOptions, language, pyRuntime } = {}) { if (!this._ready) throw new Error('CascadeEngine not initialized. Call init() first.'); this._working = true; @@ -67,7 +71,9 @@ class CascadeEngine { // Send evaluation command (fire-and-forget — worker processes asynchronously) this._messageBus.send('Evaluate', { code, - GUIState: guiState + GUIState: guiState, + language, + pyRuntime }); // Request meshing — this returns a Promise that resolves with [facesAndEdges, sceneOptions] @@ -83,9 +89,9 @@ class CascadeEngine { if (!result) return { meshData: null, sceneOptions: {} }; - const [[faces, edges], resultSceneOptions] = result; + const [[faces, edges], resultSceneOptions, shapeLines] = result; return { - meshData: { faces, edges }, + meshData: { faces, edges, shapeLines: shapeLines || [] }, sceneOptions: resultSceneOptions || {} }; } @@ -98,6 +104,13 @@ class CascadeEngine { }); } + /** Worker-side memory footprint: { pyRuntime, jsHeapUsed, jsHeapTotal, + * occtWasm, pythonWasm, bootTiming }. Everything Python costs lives in + * the worker, so the page's own numbers say nothing about it. */ + async memoryStats() { + return this._messageBus.request('memoryStats', {}, 15000); + } + /** Export the current shape as STEP text. */ async exportSTEP() { return this._messageBus.request('saveShapeSTEP'); @@ -108,6 +121,13 @@ class CascadeEngine { this._messageBus.send('loadFiles', files); } + /** Import external files and RESOLVE once the worker has done so, with the + * names it actually imported. Python mode's `import_step()` needs the + * asset to be in the worker BEFORE the evaluation runs. */ + loadExternalFilesAwaited(dict) { + return this._messageBus.request('loadPrexistingExternalFiles', dict, 120000); + } + /** Load pre-existing external files (from saved project state). */ loadPrexistingExternalFiles(dict) { this._messageBus.send('loadPrexistingExternalFiles', dict); diff --git a/packages/cascade-core/src/worker/Build123dLite.js b/packages/cascade-core/src/worker/Build123dLite.js new file mode 100644 index 00000000..cf90be02 --- /dev/null +++ b/packages/cascade-core/src/worker/Build123dLite.js @@ -0,0 +1,9546 @@ +// Build123dLite.js - the "build123d-lite" Python library source (cascade-core) +// +// A subset of build123d (https://build123d.readthedocs.io) implemented on top +// of the CascadeStudio standard library that the CAD worker exposes on `self` +// (Box, Union, WireFromSegments, MeasureShape, ...). PythonRuntime.js +// registers this source as the importable Brython module `build123d`, so user +// scripts start with `from build123d import *`. Both ALGEBRA mode +// (Pos(...) * Box(...) - Cylinder(...)) and BUILDER mode +// (with BuildPart() as bp: ...) are supported; builders are plain context +// managers over a module-level stack (no inspect.currentframe tricks needed — +// we own this implementation). +// +// The source is embedded as a JS template string (rather than a .py asset +// copied to dist) so the worker bundle needs no extra fetch or dev/build +// dual-path handling beyond brython.js itself, and so the library is always +// version-locked to the worker code that consumes it. +// IMPORTANT: the Python source must contain no backticks and no "${". +// +// Honest-subset notes (differences from real build123d) — verified against +// build123d 0.11.1 via test/b123d-validation: +// * Rotation(x,y,z) is intrinsic-XYZ (matrix Rx*Ry*Rz), matching build123d. +// * align= offsets are computed from the object's own bounding box (mesh +// approximated for non-analytic shapes), like build123d. +// * Spline()/Edge.make_spline INTERPOLATE exactly (GeomAPI_Interpolate, +// incl. tangents=/tangent_scalars=/per-point tangents/periodic). +// * Unsupported (raise NotImplementedError rather than fake geometry): +// the drafting module beyond ArrowHead, partial cones, +// split(keep=Keep.BOTH), offset(min_edge_length=) (no +// fix_degenerate_edges), Kind.TANGENT offsets, 3MF export, imports. +// * Boolean results are cleaned with ShapeUpgrade_UnifySameDomain (the +// standard library always does); face/edge COUNTS can therefore differ +// from build123d even when the geometry (volume/bbox) matches. +// * Every remaining deliberate deviation is marked in source with a +// grep-able COMPROMISE() comment; CLAUDE.md indexes them. + +export const BUILD123D_LITE_PY = ` +# build123d-lite: a subset of build123d (algebra + builder mode) for +# CascadeStudio. Runs under Brython inside the CAD worker; every CAD op +# delegates to the CascadeStudio standard library exposed as JS globals. +# Those JS functions own all sceneShapes bookkeeping. +from browser import self as w +import math + +MM = 1.0 +CM = 10.0 +M = 1000.0 +IN = 25.4 +FT = 304.8 +THOU = 0.0254 +# mass units (build123d build_common): grams, used by the Too Tall Toby +# challenge scripts to convert a volume into a mass check +G = 1.0 +KG = 1000.0 +LB = 453.59237 + +_TOL = 1e-9 +_TOL_1E6 = 1e-6 # build123d's TOLERANCE + + +# ---------------------------------------------------------------- enums --- + +class Mode: + ADD = 'ADD' + SUBTRACT = 'SUBTRACT' + INTERSECT = 'INTERSECT' + REPLACE = 'REPLACE' + PRIVATE = 'PRIVATE' + + +class Align: + MIN = 'MIN' + CENTER = 'CENTER' + MAX = 'MAX' + NONE = None + + +class Intrinsic: + """Order to apply INTRINSIC rotations by axis (build123d Intrinsic; each + rotation is about the already-rotated frame).""" + XYZ = 'XYZ' + XZY = 'XZY' + YZX = 'YZX' + YXZ = 'YXZ' + ZXY = 'ZXY' + ZYX = 'ZYX' + XYX = 'XYX' + XZX = 'XZX' + YZY = 'YZY' + YXY = 'YXY' + ZXZ = 'ZXZ' + ZYZ = 'ZYZ' + + +class Extrinsic: + """Order to apply EXTRINSIC rotations by axis (build123d Extrinsic; every + rotation is about the FIXED frame).""" + XYZ = 'xXYZ' + XZY = 'xXZY' + YZX = 'xYZX' + YXZ = 'xYXZ' + ZXY = 'xZXY' + ZYX = 'xZYX' + XYX = 'xXYX' + XZX = 'xXZX' + YZY = 'xYZY' + YXY = 'xYXY' + ZXZ = 'xZXZ' + ZYZ = 'xZYZ' + + +class Keep: + TOP = 'TOP' + BOTTOM = 'BOTTOM' + BOTH = 'BOTH' + ALL = 'ALL' + INSIDE = 'INSIDE' + OUTSIDE = 'OUTSIDE' + + +class Unit: + """Standard units (build123d's Unit enum) — Mesher(unit=...).""" + MC = 'MC' + MM = 'MM' + CM = 'CM' + M = 'M' + IN = 'IN' + FT = 'FT' + + +class Until: + NEXT = 'NEXT' + LAST = 'LAST' + PREVIOUS = 'PREVIOUS' + FIRST = 'FIRST' + + +class Kind: + ARC = 'ARC' + INTERSECTION = 'INTERSECTION' + TANGENT = 'TANGENT' + + +class Select: + ALL = 'ALL' + LAST = 'LAST' + NEW = 'NEW' + + +class CenterOf: + GEOMETRY = 'GEOMETRY' + MASS = 'MASS' + BOUNDING_BOX = 'BOUNDING_BOX' + + +class Transition: + TRANSFORMED = 'TRANSFORMED' + ROUND = 'ROUND' + RIGHT = 'RIGHT' + + +class LengthMode: + """How PolarLine's length argument is measured (build123d LengthMode).""" + DIAGONAL = 'DIAGONAL' + HORIZONTAL = 'HORIZONTAL' + VERTICAL = 'VERTICAL' + + +class Side: + LEFT = 'LEFT' + RIGHT = 'RIGHT' + BOTH = 'BOTH' + + +class AngularDirection: + CLOCKWISE = 'CLOCKWISE' + COUNTER_CLOCKWISE = 'COUNTER_CLOCKWISE' + + +class ContinuityLevel: + """How smoothly a blend joins its neighbours (build123d + ContinuityLevel): position only, tangent, or curvature.""" + C0 = 0 + C1 = 1 + C2 = 2 + + +class Sagitta: + """Which of the two arcs between the tangency points a constrained-arc + solution contributes (build123d Sagitta — the values ARE the indices into + the length-sorted pair).""" + SHORT = 0 + LONG = -1 + BOTH = 1 + + +class Tangency: + """Where the solution lies relative to a tangency argument (build123d + Tangency, GccEnt's qualifiers).""" + UNQUALIFIED = 'UNQUALIFIED' + ENCLOSING = 'ENCLOSING' + ENCLOSED = 'ENCLOSED' + OUTSIDE = 'OUTSIDE' + + +class SortBy: + LENGTH = 'LENGTH' + AREA = 'AREA' + VOLUME = 'VOLUME' + RADIUS = 'RADIUS' + DISTANCE = 'DISTANCE' + + +class GeomType: + # values are the strings the worker's introspection helpers return + LINE = ('Line',) + CIRCLE = ('Circle',) + ELLIPSE = ('Ellipse',) + HYPERBOLA = ('Hyperbola',) + PARABOLA = ('Parabola',) + BEZIER = ('BezierCurve', 'BezierSurface') + BSPLINE = ('BSplineCurve', 'BSplineSurface') + PLANE = ('Plane',) + CYLINDER = ('Cylinder',) + CONE = ('Cone',) + SPHERE = ('Sphere',) + TORUS = ('Torus',) + OTHER = ('Other',) + + +class LineType: + CONTINUOUS = 'CONTINUOUS' + CENTER = 'CENTER' + DASHED = 'DASHED' + DOT = 'DOT' + HIDDEN = 'HIDDEN' + PHANTOM = 'PHANTOM' + BORDER = 'BORDER' + DASHDOT = 'DASHDOT' + DIVIDE = 'DIVIDE' + ISO_DASH = 'ISO_DASH' + ISO_DASH_SPACE = 'ISO_DASH_SPACE' + ISO_LONG_DASH_DOT = 'ISO_LONG_DASH_DOT' + ISO_LONG_DASH_DOUBLE_DOT = 'ISO_LONG_DASH_DOUBLE_DOT' + ISO_LONG_DASH_TRIPLE_DOT = 'ISO_LONG_DASH_TRIPLE_DOT' + ISO_DOT = 'ISO_DOT' + ISO_LONG_DASH_SHORT_DASH = 'ISO_LONG_DASH_SHORT_DASH' + ISO_LONG_DASH_DOUBLE_SHORT_DASH = 'ISO_LONG_DASH_DOUBLE_SHORT_DASH' + ISO_DASH_DOT = 'ISO_DASH_DOT' + ISO_DOUBLE_DASH_DOT = 'ISO_DOUBLE_DASH_DOT' + ISO_DASH_DOUBLE_DOT = 'ISO_DASH_DOUBLE_DOT' + ISO_DOUBLE_DASH_DOUBLE_DOT = 'ISO_DOUBLE_DASH_DOUBLE_DOT' + ISO_DASH_TRIPLE_DOT = 'ISO_DASH_TRIPLE_DOT' + ISO_DOUBLE_DASH_TRIPLE_DOT = 'ISO_DOUBLE_DASH_TRIPLE_DOT' + + +class FontStyle: + REGULAR = 'REGULAR' + BOLD = 'BOLD' + ITALIC = 'ITALIC' + BOLDITALIC = 'BOLDITALIC' + + +# Type aliases build123d exports for annotations (used in signatures of +# user subclasses like the PlatonicSolid example); the actual accepted +# values are whatever the receiving function converts. +VectorLike = tuple +RotationLike = tuple + + +# -------------------------------------------------------- vector algebra --- + +def _num(x): + return float(x) + + +class Vector: + """3D vector with build123d-style .X/.Y/.Z properties.""" + + def __init__(self, *args): + if len(args) == 0: + self._v = (0.0, 0.0, 0.0) + elif len(args) == 1: + a = args[0] + if isinstance(a, Vector): + self._v = a._v + else: + t = tuple(a) + if len(t) == 0: + # Vector(()) is the origin upstream too (0 * (x, y, z) is + # how the docs write a conditional offset) + self._v = (0.0, 0.0, 0.0) + elif len(t) == 1: + self._v = (_num(t[0]), 0.0, 0.0) + elif len(t) == 2: + self._v = (_num(t[0]), _num(t[1]), 0.0) + else: + self._v = (_num(t[0]), _num(t[1]), _num(t[2])) + elif len(args) == 2: + self._v = (_num(args[0]), _num(args[1]), 0.0) + else: + self._v = (_num(args[0]), _num(args[1]), _num(args[2])) + + @property + def X(self): + return self._v[0] + + @property + def Y(self): + return self._v[1] + + @property + def Z(self): + return self._v[2] + + def __iter__(self): + return iter(self._v) + + def __getitem__(self, i): + return self._v[i] + + def __len__(self): + return 3 + + def __add__(self, o): + o = Vector(o) + return Vector(self._v[0] + o._v[0], self._v[1] + o._v[1], self._v[2] + o._v[2]) + + __radd__ = __add__ + + def __sub__(self, o): + o = Vector(o) + return Vector(self._v[0] - o._v[0], self._v[1] - o._v[1], self._v[2] - o._v[2]) + + def __rsub__(self, o): + return Vector(o).__sub__(self) + + def __neg__(self): + return Vector(-self._v[0], -self._v[1], -self._v[2]) + + def __mul__(self, s): + s = _num(s) + return Vector(self._v[0] * s, self._v[1] * s, self._v[2] * s) + + __rmul__ = __mul__ + + def __truediv__(self, s): + s = _num(s) + return Vector(self._v[0] / s, self._v[1] / s, self._v[2] / s) + + def __eq__(self, o): + try: + o = Vector(o) + except Exception: + return NotImplemented + return (abs(self._v[0] - o._v[0]) < 1e-12 and + abs(self._v[1] - o._v[1]) < 1e-12 and + abs(self._v[2] - o._v[2]) < 1e-12) + + def dot(self, o): + o = Vector(o) + return (self._v[0] * o._v[0] + self._v[1] * o._v[1] + self._v[2] * o._v[2]) + + def cross(self, o): + o = Vector(o) + a, b = self._v, o._v + return Vector(a[1] * b[2] - a[2] * b[1], + a[2] * b[0] - a[0] * b[2], + a[0] * b[1] - a[1] * b[0]) + + @property + def length(self): + return math.sqrt(self.dot(self)) + + def normalized(self): + ln = self.length + if ln < 1e-14: + raise ValueError('cannot normalize a zero-length Vector') + return self / ln + + def get_signed_angle(self, vec, normal=None): + """Signed angle in DEGREES between this vector and vec about normal + (default -Z, like build123d): atan2((Va x Vb) . Vn, Va . Vb).""" + n = Vector(0, 0, -1) if normal is None else Vector(normal) + b = Vector(vec) + reference = self.cross(b).dot(n) + scale = self.length * b.length * n.length + if abs(reference) <= 1e-12 * max(scale, _TOL_1E6): + # OCCT's gp_Vec::AngleWithRef falls back to the UNSIGNED angle when + # the cross product has no component along the reference (the two + # vectors are parallel, or the plane they span is perpendicular to + # it), so antiparallel is +180, never -180. Python's atan2 would + # return -180 for a negative zero and silently flip every + # comparison built on this (offset_2d's Side.LEFT/RIGHT pick). + return math.degrees(math.acos( + max(-1.0, min(1.0, self.normalized().dot(b.normalized()))))) + return math.degrees(math.atan2(reference, self.dot(b))) + + def reverse(self): + return -self + + def rotate(self, axis, angle): + """This vector rotated angle degrees about the given Axis DIRECTION + (the joint math only ever rotates direction vectors).""" + R = _axis_angle_mat(tuple(axis.direction), angle) + return Vector(_mat_vec(R, self._v)) + + def to_tuple(self): + return self._v + + def __repr__(self): + return 'Vector' + repr(self._v) + + +def _v3(a): + """Coerce anything point-like to a plain 3-tuple of floats.""" + if isinstance(a, Vector): + return a._v + t = tuple(a) + if len(t) == 0: + return (0.0, 0.0, 0.0) + if len(t) == 1: + return (_num(t[0]), 0.0, 0.0) + if len(t) == 2: + return (_num(t[0]), _num(t[1]), 0.0) + return (_num(t[0]), _num(t[1]), _num(t[2])) + + +# ---------------------------------------- canonical free-edge parametrization +# Free edges and wires - the ones that come out of intersections, sections, +# projections and boolean operations rather than being drawn by the user - +# carry a start point ("seam"), a traversal direction and a parameter range +# that the CAD kernel picked for its own convenience. Those choices are +# IMPLEMENTATION DEFINED: they depend on the parametric frames of the surfaces +# that produced the curve (which meridian is u = 0), on the seed point of the +# surface/surface walking algorithm and on the order in which the boolean +# assembler happened to visit the faces of the result. Two geometrically +# identical solids therefore produce section edges with different seams and +# different directions, and anything measured from position_at(0) or +# Axis(edge) silently moves with them. +# +# This is a port of build123d's proposed topology/canonical.py - same names, +# same defaults, same tie-break conventions; see +# the canonical-edges research record (zalo/build123d branch canonical-research, +# research/) for the full write-up and the upstream +# patch. Pure geometry: the only primitive needed is "give me the point at arc +# length d", which is why it can be driven by a polyline and checked against a +# second CAD kernel. + +# Number of arc length samples used to search for the canonical seam. The +# search resolves near-extremal arcs down to length / (SAMPLES / 2). +CANONICAL_SAMPLES = 512 + +# Relative size of the "lexicographically extremal" band, as a fraction of the +# bounding box diagonal of the loop. Making the band a finite width (instead of +# hunting for the extremum itself) is what makes the seam well conditioned: the +# band edges are transversal crossings, so they are located to full precision, +# and the midpoint of the band cancels the leading curvature term. +CANONICAL_BAND = 1e-6 + + +class CanonicalForm: + """Canonical traversal of a 1D shape. + + start: arc length distance, measured along the shape's current + (orientation aware) parametrization, of the canonical start point. Always + 0.0 for open shapes. + sign: +1 if the shape's current direction is canonical, -1 if it must be + traversed backwards. + closed: whether the shape was treated as a closed loop. + + (build123d's is a NamedTuple; this one is iterable and compares equal to + the equivalent tuple so the two behave alike.)""" + + def __init__(self, start, sign, closed): + self.start = start + self.sign = sign + self.closed = closed + + def position(self, position): + """Map a canonical normalized position to the shape's own normalized + position, so that shape.position_at(form.position(u)) walks the shape + canonically.""" + if self.closed: + return (self.start + self.sign * position) % 1.0 + return position if self.sign > 0 else 1.0 - position + + def __iter__(self): + return iter((self.start, self.sign, self.closed)) + + def __len__(self): + return 3 + + def __getitem__(self, i): + return (self.start, self.sign, self.closed)[i] + + def __eq__(self, other): + try: + return tuple(self) == tuple(other) + except TypeError: + return NotImplemented + + def __repr__(self): + return ('CanonicalForm(start=' + repr(self.start) + ', sign=' + + repr(self.sign) + ', closed=' + repr(self.closed) + ')') + + +def _quantise(value, resolution): + """value snapped to a multiple of resolution. + + Comparisons of "is this coordinate smaller?" are only meaningful above the + geometric tolerance; quantising makes near-equal values tie EXACTLY so the + next coordinate can decide.""" + return int(math.floor(value / resolution + 0.5)) + + +def _coordinate(point, index): + """The index-th coordinate of a Vector.""" + return (point.X, point.Y, point.Z)[index] + + +def lexicographic_key(point): + """The (x, y, z) sort key used by every canonical comparison.""" + if isinstance(point, Vector): + return (point.X, point.Y, point.Z) + x, y, z = point + return (x, y, z) + + +def loop_area_vector(points): + """Vector area (Newell) of a closed polyline: 1/2 sum (p_i - c) x (p_i+1 - c). + + Its direction is the loop's winding axis (exact for planar loops, the + least-squares normal for non planar ones) and its length is the enclosed + area, so it doubles as a degeneracy measure.""" + count = len(points) + center = Vector(sum(p.X for p in points) / count, + sum(p.Y for p in points) / count, + sum(p.Z for p in points) / count) + area = Vector(0, 0, 0) + for i in range(count): + first = points[i] - center + second = points[(i + 1) % count] - center + area = area + first.cross(second) + return area * 0.5 + + +def _dominant_axis(area): + """Index of the axis the loop winds about, preferring X, then Y, then Z on + exact ties (a tie means the loop's plane bisects two axes, where no + geometric rule can do better than a documented convention).""" + magnitudes = (abs(area.X), abs(area.Y), abs(area.Z)) + best = 0 + for index in (1, 2): + if magnitudes[index] > magnitudes[best]: + best = index + return best + + +def _golden_min(function, low, high, iterations=40): + """(location, value) of the minimum of a unimodal function on [low, high]. + + The VALUE of a smooth minimum is well conditioned; its location is not (an + error d in the location only changes the value by O(d^2)), so the location + is used as nothing more than a seed for the band search below.""" + inv_phi = 0.6180339887498949 + b_low, b_high = low, high + x_1 = b_high - inv_phi * (b_high - b_low) + x_2 = b_low + inv_phi * (b_high - b_low) + f_1, f_2 = function(x_1), function(x_2) + for _ in range(iterations): + if f_1 <= f_2: + b_high, x_2, f_2 = x_2, x_1, f_1 + x_1 = b_high - inv_phi * (b_high - b_low) + f_1 = function(x_1) + else: + b_low, x_1, f_1 = x_1, x_2, f_2 + x_2 = b_low + inv_phi * (b_high - b_low) + f_2 = function(x_2) + return (x_1, f_1) if f_1 <= f_2 else (x_2, f_2) + + +def _bisect_level(function, inside, outside, level): + """Distance where function crosses level, bracketed by a point below the + level and a point above it. A transversal crossing, hence full + precision.""" + low, high = inside, outside + for _ in range(60): + mid = 0.5 * (low + high) + if function(mid) <= level: + low = mid + else: + high = mid + return 0.5 * (low + high) + + +def _local_minima(values): + """One representative index per local minimum of a cyclic sample list. + + Plateaus (a straight extremal side, say) collapse to their middle sample, so + the number of candidates stays proportional to the number of FEATURES, not + to the number of samples.""" + count = len(values) + if count == 0: + return [] + if all(value == values[0] for value in values): + return [0] + minima = [] + for index in range(count): + if not values[index] < values[index - 1]: + continue # not a strict descent into index + end = index + while values[(end + 1) % count] == values[index] and end - index < count: + end = end + 1 + if values[(end + 1) % count] > values[index]: + minima.append(((index + end) // 2) % count) + return minima + + +def _band_midpoint(value, inside, level, step, samples, length): + """Arc-length midpoint of the value <= level band that contains inside. + + The band edges are transversal crossings of level, so bisection finds them + to full precision, and their midpoint cancels the leading curvature term of + the extremum inside the band. Neither the midpoint nor the width depends on + where the samples happened to fall.""" + backward = inside + for _ in range(samples): + if value(backward - step) > level: + break + backward = backward - step + forward = inside + for _ in range(samples): + if value(forward + step) > level: + break + forward = forward + step + band_start = _bisect_level(value, backward, backward - step, level) + band_end = _bisect_level(value, forward, forward + step, level) + return (band_start + 0.5 * ((band_end - band_start) % length)) % length + + +def canonical_form(sampler, length, closed, samples=CANONICAL_SAMPLES, + band=CANONICAL_BAND): + """Canonical traversal of a curve given an arc length sampler. + + sampler(distance) -> Vector, distance in [0, length]; length is the total + arc length; closed says whether sampler(0) == sampler(length). + + Open shapes are traversed from the lexicographically smaller of their two + end points. Closed shapes start at the midpoint of the lexicographically + extremal band and wind counter-clockwise about the dominant axis of their + area vector.""" + if length <= _TOL_1E6: + return CanonicalForm(0.0, 1, closed) + + if not closed: + start, end = sampler(0.0), sampler(length) + sign = 1 if lexicographic_key(start) <= lexicographic_key(end) else -1 + return CanonicalForm(0.0, sign, False) + + step = length / samples + points = [sampler(index * step) for index in range(samples)] + + # ---- direction: wind counter-clockwise about the dominant winding axis + area = loop_area_vector(points) + axis = _dominant_axis(area) + sign = 1 + if abs(_coordinate(area, axis)) > _TOL_1E6 * _TOL_1E6: + sign = 1 if _coordinate(area, axis) > 0 else -1 + + # ---- seam: midpoint of the lexicographically extremal band + diagonal = max(max(_coordinate(p, i) for p in points) - + min(_coordinate(p, i) for p in points) for i in range(3)) + tolerance_band = max(band * max(diagonal, _TOL_1E6), _TOL_1E6 * 1e-3) + + seam = 0.0 + for coordinate in (0, 1, 2): + + def value(distance, coordinate=coordinate): + return _coordinate(sampler(distance % length), coordinate) + + values = [_coordinate(p, coordinate) for p in points] + + # Every local minimum of the sampled coordinate is a candidate feature; + # refining each one's VALUE (its location is ill conditioned, its value + # is not) says which of them are extremal to within the band. Looking + # only at samples below a threshold would miss a band whose samples all + # sit just above it. + refined = [] + for index in _local_minima(values): + location, minimum = _golden_min(value, (index - 1) * step, + (index + 1) * step) + if values[index] < minimum: + location, minimum = index * step, values[index] + refined.append((minimum, location)) + if not refined: + continue + + level = min(minimum for minimum, _ in refined) + tolerance_band + if all(sample_value <= level for sample_value in values): + continue # loop is flat in this coordinate: fall through to the next + + # Each extremal band is reduced to its own midpoint, and the bands are + # then ranked by THOSE POINTS in the remaining coordinates. Comparing the + # minima of whichever samples fell inside a band would make the choice + # depend on the sampling phase. Coordinates are quantised to the band + # width so that two candidates whose y agree to within tolerance tie on y + # and let z decide, instead of the last bits of a mirror-symmetric pair + # of minima picking the winner. + others = [other for other in (0, 1, 2) if other != coordinate] + candidates = [] + for minimum, location in refined: + if minimum > level: + continue + midpoint = _band_midpoint(value, location, level, step, samples, length) + point = sampler(midpoint) + candidates.append((tuple(_quantise(_coordinate(point, other), + tolerance_band) + for other in others), midpoint)) + if not candidates: + continue + + # A surviving tie means the loop is symmetric about this band to within + # tolerance, where no geometric rule can choose - the smallest midpoint + # distance wins, which is stable for a given input. + seam = min(candidates)[1] + break + + return CanonicalForm(seam / length, sign, True) + + +# build123d's one_d.py imports the rule under this alias so the Mixin1D method +# of the same name can shadow it +_canonical_form = canonical_form + + +# 3x3 matrices as tuples of row-tuples +_MAT_I = ((1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)) + + +def _mat_mul(A, B): + return tuple(tuple(sum(A[i][k] * B[k][j] for k in range(3)) + for j in range(3)) for i in range(3)) + + +def _mat_vec(A, v): + return tuple(A[i][0] * v[0] + A[i][1] * v[1] + A[i][2] * v[2] + for i in range(3)) + + +def _mat_is_identity(A): + for i in range(3): + for j in range(3): + if abs(A[i][j] - (1.0 if i == j else 0.0)) > 1e-12: + return False + return True + + +def _rot_mat(rx, ry, rz): + """Intrinsic-XYZ rotation matrix Rx*Ry*Rz (matches build123d Rotation).""" + cx, sx = math.cos(math.radians(rx)), math.sin(math.radians(rx)) + cy, sy = math.cos(math.radians(ry)), math.sin(math.radians(ry)) + cz, sz = math.cos(math.radians(rz)), math.sin(math.radians(rz)) + Rx = ((1.0, 0.0, 0.0), (0.0, cx, -sx), (0.0, sx, cx)) + Ry = ((cy, 0.0, sy), (0.0, 1.0, 0.0), (-sy, 0.0, cy)) + Rz = ((cz, -sz, 0.0), (sz, cz, 0.0), (0.0, 0.0, 1.0)) + return _mat_mul(_mat_mul(Rx, Ry), Rz) + + +def _euler_mat(angles, order, intrinsic=True): + """Rotation matrix for an arbitrary Euler sequence (build123d's + Intrinsic/Extrinsic orders, which map onto gp_EulerSequence). Intrinsic + applies each rotation about the ALREADY-ROTATED frame, i.e. the matrices + multiply left-to-right; extrinsic multiplies right-to-left about the fixed + frame.""" + axes = {'X': lambda a: ((1.0, 0.0, 0.0), + (0.0, math.cos(a), -math.sin(a)), + (0.0, math.sin(a), math.cos(a))), + 'Y': lambda a: ((math.cos(a), 0.0, math.sin(a)), + (0.0, 1.0, 0.0), + (-math.sin(a), 0.0, math.cos(a))), + 'Z': lambda a: ((math.cos(a), -math.sin(a), 0.0), + (math.sin(a), math.cos(a), 0.0), + (0.0, 0.0, 1.0))} + R = _MAT_I + letters = list(order) + values = [math.radians(a) for a in angles] + if not intrinsic: + letters = list(reversed(letters)) + values = list(reversed(values)) + for letter, value in zip(letters, values): + R = _mat_mul(R, axes[letter](value)) + return R + + +def _axis_angle_mat(axis, degrees): + """Rotation matrix from axis + angle (Rodrigues).""" + x, y, z = Vector(axis).normalized() + a = math.radians(degrees) + c, s = math.cos(a), math.sin(a) + C = 1.0 - c + return ((c + x * x * C, x * y * C - z * s, x * z * C + y * s), + (y * x * C + z * s, c + y * y * C, y * z * C - x * s), + (z * x * C - y * s, z * y * C + x * s, c + z * z * C)) + + +def _mat_axis_angle(R): + """Rotation matrix -> (axis list, angle degrees); robust near 0 and 180.""" + tr = R[0][0] + R[1][1] + R[2][2] + c = max(-1.0, min(1.0, (tr - 1.0) / 2.0)) + angle = math.acos(c) + if angle < 1e-10: + return [0.0, 0.0, 1.0], 0.0 + if abs(angle - math.pi) < 1e-7: + # axis from the largest diagonal element of (R + I) / 2 + xx = (R[0][0] + 1.0) / 2.0 + yy = (R[1][1] + 1.0) / 2.0 + zz = (R[2][2] + 1.0) / 2.0 + if xx >= yy and xx >= zz: + x = math.sqrt(max(xx, 0.0)) + axis = [x, R[0][1] / (2.0 * x), R[0][2] / (2.0 * x)] + elif yy >= zz: + y = math.sqrt(max(yy, 0.0)) + axis = [R[0][1] / (2.0 * y), y, R[1][2] / (2.0 * y)] + else: + z = math.sqrt(max(zz, 0.0)) + axis = [R[0][2] / (2.0 * z), R[1][2] / (2.0 * z), z] + return axis, 180.0 + s = 2.0 * math.sin(angle) + axis = [(R[2][1] - R[1][2]) / s, + (R[0][2] - R[2][0]) / s, + (R[1][0] - R[0][1]) / s] + return axis, math.degrees(angle) + + +# ------------------------------------------------------------- Location --- + +class Location: + """Rigid placement: rotation matrix + translation. Composes with *. + Right operand applies first: (Pos(0,0,5) * Rot(Z=45)) * shape rotates, + then translates — like build123d.""" + + def __init__(self, *args): + self._R = _MAT_I + self._t = (0.0, 0.0, 0.0) + if len(args) == 0: + return + if len(args) == 1: + a = args[0] + if isinstance(a, Location): + self._R, self._t = a._R, a._t + elif isinstance(a, Plane): + loc = a.location + self._R, self._t = loc._R, loc._t + else: + self._t = _v3(a) + elif len(args) == 2: + self._t = _v3(args[0]) + r = args[1] + if isinstance(r, (int, float)): + self._R = _rot_mat(0.0, 0.0, r) + else: + r = _v3(r) + self._R = _rot_mat(r[0], r[1], r[2]) + elif len(args) == 3: + if isinstance(args[2], str): + # Location(position, (rx, ry, rz), Intrinsic/Extrinsic order) + # - build123d's Euler-sequence form (gp_EulerSequence) + order = args[2] + intrinsic = not order.startswith('x') + self._t = _v3(args[0]) + self._R = _euler_mat(_v3(args[1]), + order[1:] if not intrinsic else order, + intrinsic) + else: + # Location(position, rotation_axis, angle_degrees) + self._t = _v3(args[0]) + self._R = _axis_angle_mat(args[1], args[2]) + else: + raise TypeError('Location: unsupported arguments') + + @classmethod + def _make(cls, R, t): + loc = cls.__new__(cls) + loc._R = R + loc._t = tuple(t) + return loc + + @property + def position(self): + return Vector(self._t) + + @property + def orientation(self): + # Euler intrinsic-XYZ extraction (inverse of _rot_mat) + R = self._R + sy = R[0][2] + sy = max(-1.0, min(1.0, sy)) + ry = math.asin(sy) + if abs(abs(sy) - 1.0) > 1e-9: + rx = math.atan2(-R[1][2], R[2][2]) + rz = math.atan2(-R[0][1], R[0][0]) + else: + rx = math.atan2(R[1][0], R[1][1]) + rz = 0.0 + return Vector(math.degrees(rx), math.degrees(ry), math.degrees(rz)) + + @property + def x_axis(self): + """Axis along this location's local X (build123d Location.x_axis).""" + R = self._R + return Axis(self._t, (R[0][0], R[1][0], R[2][0])) + + @property + def y_axis(self): + R = self._R + return Axis(self._t, (R[0][1], R[1][1], R[2][1])) + + @property + def z_axis(self): + R = self._R + return Axis(self._t, (R[0][2], R[1][2], R[2][2])) + + def inverse(self): + Rt = tuple(tuple(self._R[j][i] for j in range(3)) for i in range(3)) + t = _mat_vec(Rt, self._t) + return Location._make(Rt, (-t[0], -t[1], -t[2])) + + def _transform_point(self, p): + q = _mat_vec(self._R, _v3(p)) + return (q[0] + self._t[0], q[1] + self._t[1], q[2] + self._t[2]) + + def _apply_topo(self, topo): + if not _mat_is_identity(self._R): + axis, angle = _mat_axis_angle(self._R) + if angle != 0.0: + topo = w.Rotate(axis, angle, topo) + if abs(self._t[0]) > _TOL or abs(self._t[1]) > _TOL or abs(self._t[2]) > _TOL: + topo = w.Translate([self._t[0], self._t[1], self._t[2]], topo) + return topo + + def __mul__(self, other): + if isinstance(other, Location): + R = _mat_mul(self._R, other._R) + t0 = _mat_vec(self._R, other._t) + return Location._make(R, (t0[0] + self._t[0], t0[1] + self._t[1], + t0[2] + self._t[2])) + if isinstance(other, Plane): + return Plane._from_location(Location(self) * other.location) + if isinstance(other, Shape): + if other.topo is None: + return other + moved = _wrap_like(other, self._apply_topo(other.topo)) + moved._loc = self * other.location + # build123d's Shape.moved deep-copies the shape, so its JOINTS come + # along rebound to the copy (copy_attributes_to); their frames are + # relative to the parent, so they follow the move automatically. + if other.joints: + moved.joints = {k: j._lite_rebind(moved) + for k, j in other.joints.items()} + if isinstance(moved, Curve) and moved._specs: + # keep segment data consistent with the moved geometry so + # make_face()/sweep() can still chain the result exactly + fn_dir = lambda d: _mat_vec(self._R, d) + try: + moved._specs = [_seg_transform(s, self._transform_point, + fn_dir, self) + for s in moved._specs] + except NotImplementedError: + # opaque 'raw' segments cannot be re-derived; the moved + # TOPO is still exact, only spec-level chaining is lost + moved._specs = [] + return moved + if isinstance(other, (list, tuple, ShapeList)): + return ShapeList([self * s for s in other]) + return NotImplemented + + def __neg__(self): + """Flip the orientation without moving the origin (build123d -loc: + Location(-Plane(self)), i.e. z and y reversed, x kept).""" + return Location(-Plane(self)) + + def __repr__(self): + return ('Location(t=' + repr(self._t) + ', R=' + repr(self._R) + ')') + + +class Pos(Location): + def __init__(self, *args, **kwargs): + if kwargs: + x = kwargs.get('X', args[0] if len(args) > 0 else 0.0) + y = kwargs.get('Y', args[1] if len(args) > 1 else 0.0) + z = kwargs.get('Z', args[2] if len(args) > 2 else 0.0) + Location.__init__(self, (x, y, z)) + elif len(args) == 1 and not isinstance(args[0], (int, float)): + Location.__init__(self, args[0]) + else: + x = args[0] if len(args) > 0 else 0.0 + y = args[1] if len(args) > 1 else 0.0 + z = args[2] if len(args) > 2 else 0.0 + Location.__init__(self, (x, y, z)) + + +class Rotation(Location): + def __init__(self, X=0, Y=0, Z=0, **kwargs): + if len(kwargs) > 0: + X = kwargs.get('X', X) + Y = kwargs.get('Y', Y) + Z = kwargs.get('Z', Z) + Location.__init__(self) + self._R = _rot_mat(X, Y, Z) + + +Rot = Rotation + + +# ----------------------------------------------------------------- Axis --- + +class Axis: + def __init__(self, origin=(0, 0, 0), direction=(0, 0, 1), canonical=False): + # Axis(edge): origin at the start, direction along the tangent. + # (duck-typed on .topo — the Shape class is defined later in this + # module, and Axis.X/Y/Z are created at module load) + # Axis(edge, canonical=True): origin & direction from the edge's + # CANONICAL traversal instead of from the underlying curve's first + # parameter (build123d's opt-in; default False keeps the pre-0.12 + # behaviour, which also disagrees with edge.position_at(0) whenever the + # edge is REVERSED). + # Axis(location) / Axis(plane): the frame's origin and its z axis + # (build123d Axis(Location) - used to build joint axes from holes) + # Duck-typed: Axis.X/Y/Z are constructed at module load, BEFORE the + # Location and Plane classes exist, so isinstance() cannot be used. + if hasattr(origin, '_R') and hasattr(origin, 'position'): # Location + self.position = Vector(origin.position) + self.direction = Vector(origin.z_axis.direction).normalized() + return + if hasattr(origin, 'z_dir') and hasattr(origin, 'origin'): # Plane + self.position = Vector(origin.origin) + self.direction = Vector(origin.z_dir).normalized() + return + if hasattr(origin, 'topo') and origin.topo is not None: + edge = origin + if origin.topo.ShapeType().value != 6: + edge = origin.edges()[0] + if canonical: + canonical_edge = edge.canonical() + self.position = canonical_edge.position_at(0) + self.direction = canonical_edge.tangent_at(0).normalized() + return + p = w._edgePointAt(edge.topo, 0.0) + t = w._edgeTangentAt(edge.topo, 0.0) + self.position = Vector(tuple(p)) + self.direction = Vector(tuple(t)).normalized() + return + self.position = Vector(origin) + self.direction = Vector(direction).normalized() + + @property + def origin(self): + return self.position + + @property + def location(self): + """Location whose z axis is this axis (build123d Axis.location).""" + return Plane(self.position, z_dir=self.direction).location + + def located(self, loc): + """This axis placed by loc (build123d Axis.located).""" + p = loc._transform_point(tuple(self.position)) + d = _mat_vec(loc._R, tuple(self.direction)) + return Axis(p, d) + + def is_parallel(self, other, angular_tolerance=1e-5): + # tolerance in DEGREES, like build123d's Axis.is_parallel + d = min(1.0, abs(self.direction.dot(other.direction))) + return math.acos(d) <= math.radians(angular_tolerance) or \ + d > (1.0 - 1e-9) + + def is_normal(self, other, angular_tolerance=1e-5): + d = min(1.0, abs(self.direction.dot(other.direction))) + return abs(math.degrees(math.acos(d)) - 90.0) <= angular_tolerance + + def is_opposite(self, other, angular_tolerance=1e-5): + d = max(-1.0, min(1.0, self.direction.dot(other.direction))) + return abs(math.degrees(math.acos(d)) - 180.0) <= \ + max(angular_tolerance, 1e-4) + + def __neg__(self): + return Axis(self.position, -self.direction) + + def reverse(self): + return -self + + def __repr__(self): + return ('Axis(' + repr(tuple(self.position)) + ', ' + + repr(tuple(self.direction)) + ')') + + +Axis.X = Axis((0, 0, 0), (1, 0, 0)) +Axis.Y = Axis((0, 0, 0), (0, 1, 0)) +Axis.Z = Axis((0, 0, 0), (0, 0, 1)) + + +# ---------------------------------------------------------------- Plane --- + +def _default_x_dir(z): + """OCCT gp_Ax2's deterministic X direction for a normal (build123d uses + gp_Ax3, same rule).""" + a, b, c = z + aa, bb, cc = abs(a), abs(b), abs(c) + if bb <= aa and bb <= cc: + if aa > cc: + x = (-c, 0.0, a) + else: + x = (c, 0.0, -a) + elif aa <= bb and aa <= cc: + if bb > cc: + x = (0.0, -c, b) + else: + x = (0.0, c, -b) + else: + if aa > bb: + x = (-b, a, 0.0) + else: + x = (b, -a, 0.0) + ln = math.sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]) + return (x[0] / ln, x[1] / ln, x[2] / ln) + + +def _ortho_x_dir(z_dir, x_dir): + """The x direction gp_Ax3(origin, z, x) actually adopts: x projected into + the plane normal to z, normalized. build123d hands a possibly + NON-perpendicular x_dir straight to gp_Ax3 (Plane.__init__), which + orthogonalizes it — e.g. Face.location_at(point, x_dir=(1, 0, 0)) on a + curved surface.""" + z = Vector(z_dir).normalized() + x = Vector(x_dir) + x = x - z * x.dot(z) + ln = x.length + if ln < 1e-12: + raise ValueError('x_dir must not be parallel to z_dir') + return x / ln + + +class Plane: + def __init__(self, origin=(0, 0, 0), x_dir=None, z_dir=(0, 0, 1)): + if isinstance(origin, Plane): + p = origin + self.origin = p.origin + self.x_dir, self.y_dir, self.z_dir = p.x_dir, p.y_dir, p.z_dir + return + if isinstance(origin, Location): + loc = origin + R = loc._R + self.origin = Vector(loc._t) + self.x_dir = Vector(R[0][0], R[1][0], R[2][0]) + self.y_dir = Vector(R[0][1], R[1][1], R[2][1]) + self.z_dir = Vector(R[0][2], R[1][2], R[2][2]) + return + if isinstance(origin, Face): + face = origin + n = w._faceNormal(face.topo) + c = w._faceCentroid(face.topo) + self.origin = Vector(c[0], c[1], c[2]) + self.z_dir = Vector(n[0], n[1], n[2]).normalized() + # build123d derives x_dir from the face's UV axes + u = w._faceUDir(face.topo) + if u is not None: + self.x_dir = _ortho_x_dir(self.z_dir, tuple(u)) + else: + self.x_dir = Vector(_default_x_dir(tuple(self.z_dir))) + self.y_dir = self.z_dir.cross(self.x_dir) + return + self.origin = Vector(origin) + self.z_dir = Vector(z_dir).normalized() + if x_dir is None: + self.x_dir = Vector(_default_x_dir(tuple(self.z_dir))) + else: + self.x_dir = _ortho_x_dir(self.z_dir, x_dir) + self.y_dir = self.z_dir.cross(self.x_dir) + + @classmethod + def _from_location(cls, loc): + return cls(loc) + + @property + def location(self): + x, y, z = tuple(self.x_dir), tuple(self.y_dir), tuple(self.z_dir) + R = ((x[0], y[0], z[0]), (x[1], y[1], z[1]), (x[2], y[2], z[2])) + return Location._make(R, tuple(self.origin)) + + def offset(self, amount): + return Plane(self.origin + self.z_dir * amount, self.x_dir, self.z_dir) + + def rotated(self, rotation=(0, 0, 0)): + """Plane with axes rotated (intrinsic XYZ, degrees) in the GLOBAL + frame about the plane origin — matches build123d.""" + r = _v3(rotation) + R = _rot_mat(r[0], r[1], r[2]) + return Plane(self.origin, + Vector(_mat_vec(R, tuple(self.x_dir))), + Vector(_mat_vec(R, tuple(self.z_dir)))) + + def shift_origin(self, new_origin): + return Plane(Vector(new_origin), self.x_dir, self.z_dir) + + def from_local_coords(self, pt): + """A point given in this plane's local frame, in world coordinates + (build123d Plane.from_local_coords).""" + v = Vector(pt) + return (self.origin + self.x_dir * v.X + self.y_dir * v.Y + + self.z_dir * v.Z) + + def to_local_coords(self, pt): + """The world point expressed in this plane's local frame.""" + d = Vector(pt) - self.origin + return Vector(d.dot(self.x_dir), d.dot(self.y_dir), d.dot(self.z_dir)) + + def __mul__(self, other): + if isinstance(other, Location): + return self.location * other + return self.location * other + + def __neg__(self): + return Plane(self.origin, self.x_dir, -self.z_dir) + + def __repr__(self): + return ('Plane(o=' + repr(tuple(self.origin)) + ', x=' + + repr(tuple(self.x_dir)) + ', z=' + repr(tuple(self.z_dir)) + ')') + + +# (Plane.XY etc. are assigned after the shape classes exist — Plane.__init__ +# dispatches on Face, which is defined below.) + + +# --------------------------------------------------------------- shapes --- + +def _topo(obj): + """Unwrap a Shape (or accept a raw TopoDS shape) to the JS shape object.""" + # A Builder stands in for its result everywhere build123d takes a Shape + # (upstream Shape methods accept builders through the same coercion). + if isinstance(obj, Builder): + obj = obj._obj + if obj is None: + raise ValueError('this builder has no result yet') + if isinstance(obj, Shape): + if obj.topo is None: + raise ValueError('this ' + type(obj).__name__ + ' is empty') + return obj.topo + if hasattr(obj, 'ShapeType'): + return obj + raise TypeError('expected a build123d-lite Shape, got ' + repr(obj)) + + +def _wrap_like(obj, topo): + cls = type(obj) if isinstance(obj, Shape) else Part + if cls is Edge: + cls = Curve + elif cls is Face: + cls = Sketch + elif cls is Vertex: + cls = Part + res = cls.__new__(cls) + Shape.__init__(res, topo) + if isinstance(res, Curve): + res._specs = list(getattr(obj, '_specs', []) or []) + return res + + +def _solid_volume(t): + """Sum of per-solid volumes (0 for shapes without solids) — matches + build123d's volume on compounds and sidesteps the meaningless partial + integrals VolumeProperties gives open faces.""" + return w.SolidsVolume(t) + + +def _tolist(objs): + if objs is None: + return [] + if isinstance(objs, (Shape, Builder)): + # a Builder stands in for its result and is NOT iterable upstream + # either (build123d's add() coerces obj._obj per element) + return [objs] + return list(objs) + + +class Shape: + """A shape wrapping a raw OCCT TopoDS shape (self.topo, may be None for + empty algebra starters like Part()). Supports build123d algebra.""" + + def __init__(self, topo=None): + # graceful promotions like build123d: Shape(list_of_shapes) makes a + # compound, Shape(other_shape) adopts its geometry + if isinstance(topo, (list, tuple, ShapeList)): + topos = [_topo(s) for s in topo + if not (isinstance(s, Shape) and s.topo is None)] + if len(topos) == 0: + topo = None + elif len(topos) == 1: + topo = topos[0] + else: + topo = w.MakeCompound(topos) + elif isinstance(topo, Shape): + topo = topo.topo + self.topo = topo + self.label = '' + self.color = None + self.children = [] + self._parent = None + # build123d tracks a top-level Location on every shape; lite bakes + # transforms into geometry but keeps the equivalent composed Location + # here so joints / locate() / .position can reason about frames. + self._loc = None # None = identity + self.joints = {} + + @property + def wrapped(self): + """The underlying raw (JS/OCCT) shape — build123d compat.""" + return self.topo + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + """Attaching a shape to a parent ADDS it to the parent's children - + build123d's assembly tree is anytree, where setting .parent is how a + node joins the tree (tutorial_joints does exactly this with the M6 + screw). COMPROMISE(joints) still holds: there is no anytree, only the + parent/children links that Compound walks.""" + if self._parent is not None and self in self._parent.children: + self._parent.children.remove(self) + self._parent = value + if value is not None and self not in value.children: + value.children.append(self) + + # --- location bookkeeping (baked geometry + tracked frame) --- + @property + def location(self): + return self._loc if self._loc is not None else Location() + + @location.setter + def location(self, value): + self.locate(value) + + @property + def position(self): + return self.location.position + + @position.setter + def position(self, value): + cur = self.location + delta = Vector(value) - cur.position + if self.topo is not None: + self.topo = w.Translate([delta.X, delta.Y, delta.Z], self.topo) + self._loc = Location._make(cur._R, tuple(Vector(value))) + + @property + def orientation(self): + return self.location.orientation + + # --- boolean algebra --- + def __add__(self, other): + others = [o for o in _tolist(other) if not (isinstance(o, Shape) and o.topo is None)] + topos = [_topo(o) for o in others] + if self.topo is not None: + topos.insert(0, self.topo) + if len(topos) == 0: + return _wrap_like(self, None) + if len(topos) == 1: + return _wrap_like(self, topos[0]) + vols = [_solid_volume(t) for t in topos] + fused = w.Union(topos) + # COMPROMISE(kernel-guard): this OCCT 8.0.1 wasm build has a known + # kernel fault where BooleanFuse SILENTLY DROPS an operand when + # coplanar faces meet along BSpline edges. The JS Union now detects + # the drop and rebuilds the union from the General-Fuse partition + # (whose split phase is unaffected); if even that fails, a valid + # fuse can never be smaller than its largest input, so RAISE here + # (never return silently-wrong geometry). + if max(vols) > 1e-6: + rv = _solid_volume(fused) + if rv < max(vols) * 0.999 - 1e-9: + raise RuntimeError( + 'KNOWN OCCT 8.0.1 wasm kernel fault: fuse dropped an ' + 'operand (result volume ' + repr(rv) + ' < largest ' + 'input ' + repr(max(vols)) + ') and the General-Fuse ' + 'rebuild could not recover it. This build\\'s ' + 'BooleanFuse mishandles coplanar BSpline-edged contact ' + 'faces; offset or restructure the touching geometry.') + return _wrap_like(self, fused) + + def __iter__(self): + """Iterate contained shapes like build123d Compound iteration: + solids for 3D content, else faces, else edges.""" + if self.topo is None: + return iter(()) + sol = self.solids() + if len(sol) > 0: + return iter(sol) + fac = self.faces() + if len(fac) > 0: + return iter(fac) + return iter(self.edges()) + + def __sub__(self, other): + others = [o for o in _tolist(other) if not (isinstance(o, Shape) and o.topo is None)] + if self.topo is None: + raise ValueError('cannot subtract from an empty shape') + if len(others) == 0: + return _wrap_like(self, self.topo) + tools = [_topo(o) for o in others] + if len(tools) > 1: + # fuse the tools first: ONE boolean cut, like build123d — the + # sequential per-tool cuts are also less robust in this OCCT + tools = [w.Union(tools)] + return _wrap_like(self, w.Difference(self.topo, tools)) + + def __and__(self, other): + others = _tolist(other) + topos = [self.topo] + [_topo(o) for o in others] + return _wrap_like(self, w.Intersection(topos)) + + def __neg__(self): + """Reversed-orientation copy (build123d's Mixin2D.__neg__: + TopoDS_Shape::Complemented). Defined on the base class because lite + re-wraps transformed faces as Sketch (see _wrap_like), so -face and + -sketch must both work; Face overrides it to stay a Face.""" + if self.topo is None: + raise ValueError('Invalid Shape') + return _wrap_like(self, w.ReverseShape(self.topo, True)) + + def __rmul__(self, other): + # [Plane(f) for f in ...] * shape -> copies placed at each plane + if isinstance(other, (list, tuple)) and all( + isinstance(p, (Location, Plane)) for p in other): + return ShapeList([p * self for p in other]) + return NotImplemented + + def fuse(self, *others): + return self.__add__(list(others)) + + def cut(self, *others): + return self.__sub__(list(others)) + + def intersect(self, *others): + # intersect(Axis) on a 1-D shape is the POINT intersection upstream + # returns as a ShapeList of Vertex (Mixin1D._intersect), not a boolean + if len(others) == 1 and isinstance(others[0], Axis) and \ + isinstance(self, Curve): + return ShapeList([Vertex(tuple(p)) for p in + self.find_intersection_points(others[0])]) + return self.__and__(list(others)) + + # --- selectors --- + def edges(self, indices=None): + """All (unique) edges as a ShapeList of Edge. indices=[...] is the + CascadeStudio escape hatch used by the GUI Fillet tool.""" + if self.topo is None: + return ShapeList() + sel = w.Edges(self.topo) + idxs = list(sel.indices()) + raws = list(sel.edges()) + out = ShapeList() + for i in range(len(idxs)): + if indices is None or idxs[i] in indices: + out.append(Edge(raws[i], parent=self, index=idxs[i])) + return out + + def faces(self): + if self.topo is None: + return ShapeList() + sel = w.Faces(self.topo) + idxs = list(sel.indices()) + raws = list(sel.faces()) + out = ShapeList() + for i in range(len(idxs)): + out.append(Face(raws[i], parent=self, index=idxs[i])) + return out + + def vertices(self): + if self.topo is None: + return ShapeList() + out = ShapeList() + seen = [] + def _cb(vtx): + p = w._vertexPoint(vtx) + key = (round(p[0], 9), round(p[1], 9), round(p[2], 9)) + if key not in seen: + seen.append(key) + out.append(Vertex(vtx, parent=self)) + w.ForEachVertex(self.topo, _cb) + return out + + def solids(self): + if self.topo is None: + return ShapeList() + out = ShapeList() + def _cb(i, s): + out.append(Part(s)) + w.ForEachSolid(self.topo, _cb) + return out + + def wires(self): + if self.topo is None: + return ShapeList() + out = ShapeList() + def _cb(i, s): + out.append(Curve(s)) + w.ForEachWire(self.topo, _cb) + return out + + def face(self): + fs = self.faces() + return fs[0] if len(fs) > 0 else None + + def edge(self): + es = self.edges() + return es[0] if len(es) > 0 else None + + def wire(self): + ws = self.wires() + return ws[0] if len(ws) > 0 else None + + def solid(self): + ss = self.solids() + return ss[0] if len(ss) > 0 else None + + # --- measurement --- + @property + def volume(self): + if self.topo is None: + return 0.0 + return w.SolidsVolume(self.topo) + + @property + def area(self): + if self.topo is None: + return 0.0 + return w.SurfaceArea(self.topo) + + @property + def length(self): + if getattr(self, '_length_attr', None) is not None: + return self._length_attr + if self.topo is None: + return 0.0 + return w.EdgeLength(self.topo) + + @length.setter + def length(self, value): + self._length_attr = value + + def center(self, center_of=CenterOf.MASS): + if center_of == CenterOf.BOUNDING_BOX: + return self.bounding_box().center() + return Vector(tuple(w.CenterOfMass(self.topo))) + + # --- minimal distance (BRepExtrema_DistShapeShape, like build123d) --- + def distance_to_with_closest_points(self, other): + """(distance, point on self, point on other) for the MINIMAL distance + between two shapes (build123d + Shape.distance_to_with_closest_points). other may be a point.""" + if self.topo is None: + raise ValueError('Cannot calculate distance to or from an empty ' + 'shape') + if isinstance(other, (Shape, Builder)): + target = _topo(other) + else: + target = w.PointVertex(list(_v3(other))) + res = w._distShapeShape(self.topo, target) + if not res: + raise RuntimeError('the distance between these shapes could not ' + 'be computed') + r = list(res) + return (r[0], Vector(tuple(r[1])), Vector(tuple(r[2]))) + + def distance_to(self, other): + """Minimal distance to another shape or point (build123d + Shape.distance_to).""" + return self.distance_to_with_closest_points(other)[0] + + def distance(self, other): + """Minimal distance between two shapes (build123d Shape.distance).""" + if not isinstance(other, (Shape, Builder)): + raise ValueError('Cannot calculate distance to or from an empty ' + 'shape') + return self.distance_to_with_closest_points(other)[0] + + def closest_points(self, other): + """The two points where the distance between the shapes is minimal + (build123d Shape.closest_points).""" + return self.distance_to_with_closest_points(other)[1:3] + + def bounding_box(self, tolerance=None): + return BoundBox(list(w.BoundingBox(self.topo))) + + # --- placement --- + def moved(self, loc): + return loc * self + + def located(self, loc): + """Copy at the ABSOLUTE location loc (build123d semantics): the + tracked location is replaced, so the delta loc * current⁻¹ is what + gets applied to the baked geometry.""" + delta = loc * self.location.inverse() + placed = delta * self + placed._loc = Location(loc) + return placed + + def move(self, loc): + moved = loc * self + self.topo = moved.topo + self._loc = moved._loc + # the segment specs travel with the geometry: mirror()/make_face() + # rebuild from them, so stale specs would silently un-place the shape + if isinstance(self, Curve): + self._specs = moved._specs + return self + + def locate(self, loc): + placed = self.located(loc) + self.topo = placed.topo + self._loc = placed._loc + if isinstance(self, Curve): + self._specs = placed._specs + return self + + def rotate(self, axis, angle): + topo = self.topo + o = tuple(axis.position) + shift = (abs(o[0]) > _TOL or abs(o[1]) > _TOL or abs(o[2]) > _TOL) + if shift: + topo = w.Translate([-o[0], -o[1], -o[2]], topo) + topo = w.Rotate(list(axis.direction), angle, topo) + if shift: + topo = w.Translate([o[0], o[1], o[2]], topo) + return _wrap_like(self, topo) + + def translate(self, v): + return Pos(Vector(v)) * self + + def scale(self, factor, about=None): + c = _v3(about) if about is not None else tuple(self.location.position) + if not isinstance(factor, (int, float)): + f = tuple(factor) + return scale(self, f, about=c, mode=Mode.PRIVATE) + return _wrap_like(self, w.ScaleUniform(self.topo, factor, list(c))) + + def mirror(self, mirror_plane=None): + return mirror(self, about=mirror_plane or Plane.XZ, mode=Mode.PRIVATE) + + def project_to_shape(self, target, direction): + """Delegate to Face.project_to_shape for every face of this shape + (build123d defines projection per shape class; sketches project + their faces).""" + out = ShapeList() + for f in self.faces(): + out.extend(Face(f.topo).project_to_shape(target, direction)) + return out + + def find_intersection_points(self, other, tolerance=1e-6): + """(point, unit surface normal) pairs where the Axis crosses this + shape's surface, sorted along the axis — + BRepIntCurveSurface_Inter, like build123d.""" + hits = w.IntersectLineShape(self.topo, list(other.position), + list(other.direction), tolerance) + return [(Vector(tuple(h[0])), Vector(tuple(h[1]))) for h in hits] + + def project_faces(self, faces, path, start=0): + """Project faces onto this shape following a path on the shape — + upstream Shape.project_faces: each face is positioned on the + surface-normal plane at its path position and projected inward.""" + path_length = path.length + shape_center = self.center() + if isinstance(faces, Shape): + faces = faces.faces() + faces = [f for f in faces] + first_face_min_x = faces[0].bounding_box().min[0] + projected = ShapeList() + for face in faces: + bbox = face.bounding_box() + face_center_x = (bbox.min[0] + bbox.max[0]) / 2.0 + u = start + (face_center_x - first_face_min_x) / path_length + path_position = path.position_at(u) + path_tangent = path.tangent_at(u) + axis = Axis(path_position, shape_center - path_position) + surface_point, surface_normal = \ + self.find_intersection_points(axis)[0] + pl = Plane(origin=surface_point, x_dir=path_tangent, + z_dir=surface_normal) + projection_face = pl * face.moved( + Location((-face_center_x, 0, 0))) + projected.append(Face(projection_face.topo).project_to_shape( + self, surface_normal * -1)[0]) + return projected + + def project_to_viewport(self, viewport_origin, viewport_up=(0, 0, 1), + look_at=None): + """Hidden-line projection (HLRBRep): returns (visible, hidden) + edge compounds like build123d.""" + vo = Vector(viewport_origin) + target = Vector(look_at) if look_at is not None else \ + self.bounding_box().center() + view_dir = (target - vo).normalized() + vis, hid = w.HLRProject(self.topo, list(view_dir)) + return (Curve(vis).edges(), Curve(hid).edges()) + + def show_topology(self, limit_class='Vertex', show_center=None): + """Tree rendering of the internal structure (build123d + Shape.show_topology). This is a DIAGNOSTIC string - no geometry rides + on it - so it reproduces upstream's shape (labels, box-drawing prefix, + centre or Location per node) without promising byte parity of the + pointer values upstream prints.""" + order = ['Compound', 'Solid', 'Shell', 'Face', 'Wire', 'Edge', + 'Vertex'] + getters = {'Solid': 'solids', 'Shell': 'shells', 'Face': 'faces', + 'Wire': 'wires', 'Edge': 'edges', 'Vertex': 'vertices'} + if limit_class in order: + limit = order.index(limit_class) + else: + limit = len(order) - 1 + lines = [] + + def describe(shape, label): + name = type(shape).__name__ + use_center = show_center + if use_center is None: + use_center = not shape.children + where = None + if use_center: + try: + c = shape.center() + where = 'Center(' + str(c.X) + ', ' + str(c.Y) + ', ' + \ + str(c.Z) + ')' + except Exception: + where = None + if where is None: + where = 'Location(' + str(shape.location) + ')' + prefix = '' + if label: + prefix = label + ' ' + return prefix + name + ' at ' + where + + def children_of(shape): + kids = [k for k in shape.children if isinstance(k, Shape)] + if kids: + return kids + name = type(shape).__name__ + if name in order: + start = order.index(name) + 1 + else: + start = 1 + for level in order[start:limit + 1]: + getter = getters.get(level) + if getter is None: + continue + try: + kids = list(getattr(shape, getter)()) + except Exception: + kids = [] + if kids: + return kids + return [] + + def walk(shape, label, prefix, is_last, is_root, depth=0): + if depth > 8 or len(lines) > 5000: + return # guard: diagnostics, not geometry + if is_root: + root_label = '' + if label: + root_label = label + ' is the root' + lines.append(describe(shape, root_label)) + child_prefix = '' + else: + branch = '\u251c\u2500\u2500 ' + if is_last: + branch = '\u2514\u2500\u2500 ' + lines.append(prefix + branch + describe(shape, label)) + if is_last: + child_prefix = prefix + ' ' + else: + child_prefix = prefix + '\u2502 ' + kids = children_of(shape) + for i, kid in enumerate(kids): + if kid is shape: + continue + walk(kid, getattr(kid, 'label', ''), child_prefix, + i == len(kids) - 1, False, depth + 1) + + walk(self, self.label, '', True, True) + return '\\n'.join(lines) + + def do_children_intersect(self, include_parent=False, tolerance=1e-5): + """Do any of this assembly's children overlap (build123d + Compound.do_children_intersect)? Same algorithm: a pre-order walk of + the tree, a bounding-box pre-filter, then a real Intersection whose + solid volume must exceed the tolerance.""" + nodes = [] + + def preorder(shape, depth=0): + if depth > 8: + return + nodes.append(shape) + for kid in shape.children: + if isinstance(kid, Shape) and kid is not shape: + preorder(kid, depth + 1) + + preorder(self) + if not include_parent: + nodes.pop(0) + boxes = [n.bounding_box() for n in nodes] + for i in range(len(nodes)): + for j in range(i + 1, len(nodes)): + a, b = boxes[i], boxes[j] + if (a.max.X < b.min.X or b.max.X < a.min.X or + a.max.Y < b.min.Y or b.max.Y < a.min.Y or + a.max.Z < b.min.Z or b.max.Z < a.min.Z): + continue + try: + common = nodes[i].intersect(nodes[j]) + except Exception: + common = None + if common is None or common.topo is None: + continue + volume = sum([s.volume for s in common.solids()]) + if volume > tolerance: + return (True, (nodes[i], nodes[j]), volume) + return (False, (None, None), 0.0) + + def is_valid(self): + return self.topo is not None + + def clean(self): + return self + + def _lite_copy(self): + c = _wrap_like(self, self.topo) + c._loc = self._loc + c.label = self.label + # copy.copy in build123d preserves joints REPARENTED to the copy + # (shape_core.copy_attributes_to) + if self.joints: + c.joints = {k: j._lite_rebind(c) for k, j in self.joints.items()} + c.children = list(self.children) + return c + + def __copy__(self): + return self._lite_copy() + + def __deepcopy__(self, memo=None): + return self._lite_copy() + + +class Part(Shape): + def __init__(self, topo=None): + # Solid(Shell(faces)) sews the faces into a closed solid, and + # Solid(other_shape) adopts its geometry — like build123d. + if isinstance(topo, Shape): + fl = getattr(topo, '_face_shapes', None) \ + if isinstance(topo, Shell) else None + if fl: + topo = w.SewSolidFromFaces([_topo(f) for f in fl]) + else: + topo = topo.topo + Shape.__init__(self, topo) + + @classmethod + def extrude(cls, obj, direction): + """Extrude a Face into a Solid (build123d Solid.extrude).""" + d = Vector(direction) + return cls(w.Extrude(_topo(obj), [d.X, d.Y, d.Z], True)) + + @classmethod + def make_sphere(cls, radius, plane=None, angle1=-90, angle2=90, + angle3=360): + """A sphere solid (full spheres only, like the examples use).""" + if angle1 != -90 or angle2 != 90 or angle3 != 360: + raise NotImplementedError( + 'partial spheres are not supported in build123d-lite') + s = cls(w.Sphere(radius)) + if plane is not None: + s = plane * s + s._loc = None # the plane is BAKED (upstream keeps identity) + return s + + @classmethod + def make_cylinder(cls, radius, height, plane=None, angle=360): + """A cylinder solid with its base on the given plane's origin, + extending along the plane normal (build123d Solid.make_cylinder).""" + if angle != 360: + raise NotImplementedError( + 'partial cylinders are not supported in build123d-lite') + s = cls(w.Cylinder(radius, height, False)) + if plane is not None: + s = plane * s + s._loc = None # the plane is BAKED (upstream keeps identity) + return s + + @classmethod + def make_loft(cls, objs, ruled=False): + """Loft through wires (build123d Solid.make_loft).""" + wires = [] + for o in objs: + t = _topo(o) + wires.append(t if t.ShapeType().value == 5 + else w.GetWire(t, 0, True)) + return cls(w.Loft(wires)) + + @classmethod + def revolve(cls, section, angle=360, axis=None, inner_wires=None): + """Revolve a Face/Wire section about an Axis + (build123d Solid.revolve).""" + if axis is None: + axis = Axis.Z + sec = section if isinstance(section, Face) or not inner_wires \ + else Face(section, list(inner_wires)) + o = tuple(axis.position) + d = list(axis.direction) + topo = _topo(sec) + shift = (abs(o[0]) > _TOL or abs(o[1]) > _TOL or abs(o[2]) > _TOL) + if shift: + topo = w.Translate([-o[0], -o[1], -o[2]], topo) + topo = w.Revolve(topo, angle, d) + if shift: + topo = w.Translate([o[0], o[1], o[2]], topo) + return cls(topo) + + @classmethod + def thicken(cls, surface, depth, normal_override=None): + """Thicken a Face/Shell into a Solid along its normals — the exact + BRepOffset construction of build123d's Solid.thicken (full offset + shell, GeomAbs_Intersection join).""" + f = surface + d = float(depth) + if normal_override is not None and isinstance(f, Face): + n = f.normal_at() + if n.dot(Vector(normal_override).normalized()) < 0: + d = -d + return cls(w.ThickenSolid(_topo(f), d)) + + @classmethod + def extrude_linear_with_rotation(cls, section, center=(0, 0, 0), + normal=(0, 0, 1), angle=0, + inner_wires=None): + """Twisted prism: sweep along a straight spine with a helical + auxiliary spine — the exact MakePipeShell construction of + build123d's Solid.extrude_linear_with_rotation.""" + c = _v3(center) + nvec = Vector(normal) + h = nvec.length + spine = w.WireFromSegments([('line', [list(c), + list(Vector(c) + nvec)])]) + pitch = 360.0 / angle * h + hel = Helix(pitch, h, 1, center=c, + direction=tuple(nvec.normalized()), mode=Mode.PRIVATE) + aux = w.WireFromSegments(_chain_segments(hel._specs)) + if isinstance(section, Face): + outer = w._faceOuterWire(section.topo) + inner = [x.topo for x in section.inner_wires()] + else: + outer = _topo(section) + inner = [_topo(x) for x in _tolist(inner_wires)] + solid = w.PipeShellSweep([outer], spine, False, '', [], aux, False) + if inner: + tools = [w.PipeShellSweep([iw], spine, False, '', [], aux, False) + for iw in inner] + solid = w.Difference(solid, tools) + return cls(solid) + + +class Sketch(Shape): + pass + + +class Curve(Shape): + # A single-segment Curve (what the 1-D object constructors return) answers + # the circular-arc queries of its one edge, like build123d's Mixin1D. + @property + def arc_center(self): + return _single_edge_of(self).arc_center + + @property + def radius(self): + # the 1-D constructors (JernArc) record their defining radius; other + # curves read it off their single circular edge + if getattr(self, '_radius_attr', None) is not None: + return self._radius_attr + return _single_edge_of(self).radius + + @radius.setter + def radius(self, value): + self._radius_attr = value + + def find_intersection_points(self, other, tolerance=1e-6): + return _single_edge_of(self).find_intersection_points(other, tolerance) + + def normal(self): + """Normal of a PLANAR curve (build123d Mixin1D.normal): the conic's own + axis direction for a circle/ellipse, otherwise the normal of the plane + the curve lies in. + + The general branch substitutes for BRepLib_FindSurface: its Surface() + comes back as an unbound handle in this build, so the plane is fitted + from sampled points instead (exact for a genuinely planar curve, and + the deviation of the samples from the fit is what decides whether the + curve IS planar — upstream raises the same ValueError when it is not).""" + edges = self.edges() if not isinstance(self, Edge) else [self] + if not edges: + raise ValueError("Can't find normal of empty edge/wire") + axis_dir = w._edgeArcNormal(_topo(edges[0])) + if axis_dir and len(edges) == 1: + return Vector(tuple(axis_dir)).normalized() + pts = [] + for e in edges: + for i in range(5): + pts.append(Vector(tuple(w._edgePointAt(_topo(e), i / 4.0)))) + centroid = Vector(0, 0, 0) + for p in pts: + centroid = centroid + p + centroid = centroid * (1.0 / len(pts)) + normal, best = None, 0.0 + for i in range(len(pts)): + for j in range(i + 1, len(pts)): + cross = (pts[i] - centroid).cross(pts[j] - centroid) + if cross.length > best: + normal, best = cross, cross.length + if normal is None or best <= _TOL_1E6: + raise ValueError('Normal not defined') + normal = normal.normalized() + span = max([(p - centroid).length for p in pts]) + for p in pts: + if abs((p - centroid).dot(normal)) > _TOL_1E6 * max(1.0, span): + raise ValueError('Normal not defined') + return normal + + def __init__(self, topo=None, specs=None): + if isinstance(topo, (list, tuple, ShapeList)): + # Wire(edges) / Curve(edges): one chained wire from the edges + sp = [] + for it in topo: + if isinstance(it, Curve) and it._specs: + sp.extend(it._specs) + elif isinstance(it, Shape): + sp.extend(_specs_from_topo_edges(it)) + else: + raise TypeError('Curve/Wire from a list expects edges') + chained = _chain_segments(sp) + Shape.__init__(self, w.WireFromSegments(chained)) + self._specs = chained + return + Shape.__init__(self, topo) + self._specs = list(specs) if specs else [] + + def __add__(self, other): + # curves concatenate their segment specs so make_face()/sweep() can + # chain them exactly; the topo union still happens for display + others = [o for o in _tolist(other) if not (isinstance(o, Shape) and o.topo is None)] + specs = list(self._specs) + for o in others: + if isinstance(o, Curve): + specs.extend(o._specs) + topos = [o.topo for o in others if o.topo is not None] + if self.topo is not None: + topos.insert(0, self.topo) + if len(topos) == 0: + return Curve(None, specs) + topo = topos[0] if len(topos) == 1 else w.MakeCompound(topos) + return Curve(topo, specs) + + def _edge_chain(self): + """This curve's edges in CONNECTION order: BRepTools_WireExplorer for + a real wire (what build123d's BRepAdaptor_CompCurve follows), TopExp + storage order for anything else.""" + if self.topo is not None and self.topo.ShapeType().value == 5: + return list(w.OrderedEdges(self.topo)) + return list(w.Edges(self.topo).edges()) + + def _chain_flips(self, es, ends): + """Which of the chained edges have to be traversed BACKWARDS to run + head-to-tail (see _walk for why the first edge is special-cased).""" + flips = [False] * len(es) + if len(es) > 1 and self.topo.ShapeType().value == 5: + a0, a1 = ends[0] + b0, b1 = ends[1] + if min(math.dist(a0, b0), math.dist(a0, b1)) < \ + min(math.dist(a1, b0), math.dist(a1, b1)): + flips[0] = True + for i in range(1, len(es)): + prev_end = ends[i - 1][0] if flips[i - 1] else ends[i - 1][1] + d_fwd = math.dist(prev_end, ends[i][0]) + d_rev = math.dist(prev_end, ends[i][1]) + flips[i] = d_rev < d_fwd + return flips + + def param_at_point(self, point): + """Normalized position (0..1) of point along this wire: the arc + length from the wire's start to the point, over the wire's total length + (build123d Wire.param_at_point, which walks the wire in + BRepTools_WireExplorer order accumulating edge lengths).""" + es = self._edge_chain() + pt = list(_v3(point)) + if len(es) == 1: + return Edge(es[0]).param_at_point(pt) + lens = [w._edgeLength(e) for e in es] + ends = [(tuple(w._edgePointAt(e, 0.0)), tuple(w._edgePointAt(e, 1.0))) + for e in es] + flips = self._chain_flips(es, ends) + total = sum(lens) + best, best_dist = None, None + acc = 0.0 + for i, e in enumerate(es): + d = w._edgeDistanceToPoint(e, pt) + if best_dist is None or d < best_dist: + u = w._edgeParamAtPoint(e, pt) + if u < 0.0: # not ON this edge — snap to the nearer end + u = 0.0 if math.dist(tuple(w._edgePointAt(e, 0.0)), + tuple(pt)) < \ + math.dist(tuple(w._edgePointAt(e, 1.0)), tuple(pt)) \ + else 1.0 + if flips[i]: + u = 1.0 - u + best, best_dist = (acc + u * lens[i]), d + acc += lens[i] + if best_dist is None or best_dist > _TOL_1E6: + raise ValueError('point ' + repr(tuple(pt)) + ' is ' + + repr(best_dist) + ' from this wire') + return best / total if total > 0 else 0.0 + + def _walk(self, u, tangent): + """Evaluate position/tangent at length-fraction u along the (possibly + multi-edge) curve, orienting each edge to chain head-to-tail.""" + es = self._edge_chain() + if len(es) == 1: + fn = w._edgeTangentAt if tangent else w._edgePointAt + return Vector(tuple(fn(es[0], float(u)))) + lens = [w._edgeLength(e) for e in es] + ends = [(tuple(w._edgePointAt(e, 0.0)), tuple(w._edgePointAt(e, 1.0))) + for e in es] + # orient edges into a chain (WireFromSegments adds them in order, + # but individual edges may run tip-to-tail reversed). In a real WIRE + # the edges above are in CONNECTION order, so the only ambiguity left + # is the first edge's raw parametrization direction — flip it when its + # start (not its end) is what touches the second edge. (For a COMPOUND + # of edges the order itself is arbitrary, and the greedy chaining + # starting from edge 0 as-is is what matches build123d's + # BRepAdaptor_CompCurve there.) + flips = self._chain_flips(es, ends) + total = sum(lens) + target = max(0.0, min(1.0, float(u))) * total + acc = 0.0 + for i, e in enumerate(es): + if target <= acc + lens[i] + 1e-12 or i == len(es) - 1: + v = (target - acc) / lens[i] if lens[i] > 0 else 0.0 + if flips[i]: + v = 1.0 - v + fn = w._edgeTangentAt if tangent else w._edgePointAt + res = Vector(tuple(fn(e, v))) + if tangent and flips[i]: + res = -res + return res + acc += lens[i] + raise ValueError('curve evaluation failed') + + def __matmul__(self, u): # curve @ u -> position + return self._walk(u, False) + + def __mod__(self, u): # curve % u -> tangent + return self._walk(u, True) + + def position_at(self, u): + """Point at length-fraction u along the wire (build123d + Mixin1D.position_at; Edge overrides it to be orientation-aware).""" + return self._walk(u, False) + + def tangent_at(self, u=0.5): + return self._walk(u, True) + + def location_at(self, u, x_dir=None): + """Location at length-fraction u: origin on the curve, z along the + tangent (build123d convention for sweep section placement).""" + pos = self._walk(u, False) + tan = self._walk(u, True) + if x_dir is not None: + pl = Plane(pos, x_dir=Vector(x_dir), z_dir=tan) + else: + pl = Plane(pos, z_dir=tan) + return pl.location + + def __xor__(self, u): # curve ^ u -> location + return self.location_at(u) + + def _to_param(self, value): + """A float position stays as it is; a point becomes its normalized + position along this shape (build123d Mixin1D._to_param).""" + if isinstance(value, (int, float)): + return float(value) + return self.param_at_point(value) + + def derivative_at(self, position, order=2): + """The order-th derivative of the underlying curve at the normalized + position (build123d Mixin1D.derivative_at). NOT normalized: the + magnitude is the curve's natural speed, which is what BlendCurve's + tangent_scalars scale. Odd orders follow the shape's orientation.""" + u = self._to_param(position) + es = self._edge_chain() + if len(es) == 1: + # like position_at, a REVERSED edge is traversed the other way + # (upstream's _occt_param_at maps u -> 1 - u before evaluating) + forward = bool(w._edgeIsForward(es[0])) + edge, local_u, flipped = es[0], (u if forward else 1.0 - u), False + else: + lens = [w._edgeLength(e) for e in es] + ends = [(tuple(w._edgePointAt(e, 0.0)), + tuple(w._edgePointAt(e, 1.0))) for e in es] + flips = self._chain_flips(es, ends) + total = sum(lens) + target = max(0.0, min(1.0, u)) * total + acc = 0.0 + edge, local_u, flipped = es[-1], 1.0, flips[-1] + for i, e in enumerate(es): + if target <= acc + lens[i] + 1e-12 or i == len(es) - 1: + local_u = (target - acc) / lens[i] if lens[i] > 0 else 0.0 + if flips[i]: + local_u = 1.0 - local_u + edge, flipped = e, flips[i] + break + acc += lens[i] + d = Vector(tuple(w._edgeDerivativeAt(edge, float(local_u), int(order)))) + reverse = flipped if len(es) > 1 else not bool(w._edgeIsForward(es[0])) + if order % 2 == 1 and reverse: + d = -d + return d + + def trim(self, start, end): + """A new Edge keeping only the section between two normalized + positions, which may be given as POINTS on the curve (build123d + Edge.trim).""" + return _single_edge_of(self).trim(start, end) + + def curvature_comb(self, count=100, max_tooth_size=None): + """The curvature comb of a planar (XY) curve: short line Edges erected + along the left normal, their length proportional to the signed + curvature (build123d Mixin1D.curvature_comb, ported statement for + statement).""" + closed = bool(self.is_closed) if hasattr(self, 'is_closed') else False + # numpy's linspace(0, 1, count, endpoint=not closed) + if closed: + u_values = [i / count for i in range(count)] + else: + u_values = [i / (count - 1) for i in range(count)] if count > 1 \ + else [0.0] + kappas, tangents = [], [] + for u in u_values: + tangent = self.derivative_at(u, 1) + curvature = self.derivative_at(u, 2) + tangents.append(tangent) + cross = tangent.cross(curvature) + kappa = cross.length / (tangent.length ** 3 + _TOL_1E6) + kappas.append(kappa if cross.Z >= 0 else -kappa) + max_kappa_size = max([_TOL_1E6] + [abs(k) for k in kappas]) + curve_size = max(tuple(self.bounding_box().size)) + tooth = max_tooth_size if max_tooth_size is not None else curve_size / 10 + scale_factor = tooth / max_kappa_size + out = ShapeList() + for i in range(len(u_values)): + length = scale_factor * kappas[i] + if abs(length) < _TOL_1E6: + continue + pnt = self._walk(u_values[i], False) + kappa_dir = tangents[i].normalized().cross(Vector(0, 0, 1)) + out.append(Edge.make_line(pnt, pnt + kappa_dir * length)) + return out + + def reversed(self): + """A copy of this Edge/Wire with the opposite orientation + (build123d Edge.reversed - the OCCT orientation flag, not a rebuild).""" + return _reverse_1d(self) + + def canonical(self): + """This shape with a CANONICAL parametrization: the same geometry, but + with a start point and a traversal direction determined by the geometry + alone instead of by the CAD kernel's construction history + (build123d Mixin1D.canonical - see the canonical_form() rule above). + + Free edges - the ones produced by cut/intersect/section/project_to_shape + rather than drawn by the user - inherit the seam, direction and + parameter range the kernel found convenient, so two geometrically + identical solids can yield section edges that start in different places + and run in opposite directions. Anything measured from position_at(0), + tangent_at or Axis(edge) then moves with them. + + Open shapes keep their type; a closed shape that has to be re-seamed + comes back as a single Edge, because a closed Wire has no distinguished + start point for position_at to key off.""" + form = self.canonical_form() + + if not form.closed: + return self if form.sign > 0 else _reverse_1d(self) + + # The seam is a position on a loop, so "is it already at the start?" is a + # question about the CIRCULAR distance: a band midpoint that lands an + # epsilon BELOW 1.0 is the same point as one an epsilon above 0.0. The + # comparison is made at the resolution the seam is actually defined to - + # the width of the extremal band - because asking for more precision than + # that would re-seam a shape by a few nanometres, over and over. + box = self.bounding_box() + diagonal = max(box.size.X, box.size.Y, box.size.Z) + relative_tolerance = (max(_TOL_1E6, CANONICAL_BAND * diagonal) / + max(self.length, _TOL_1E6)) + wrapped_start = form.start % 1.0 + if min(wrapped_start, 1.0 - wrapped_start) <= relative_tolerance: + # Already seamed here: at most the direction needs flipping, which + # keeps the original topology and curve types. + return self if form.sign > 0 else _reverse_1d(self) + + seam = self.position_at(form.start) + direction = self.tangent_at(form.start) * form.sign + ordered = _walk_loop(_split_1d_at_point(self, seam), seam, direction) + return _concatenate_edges(ordered) + + def canonical_form(self, samples=CANONICAL_SAMPLES): + """The canonical start position (normalized) and direction sign of this + shape, without rebuilding it (build123d Mixin1D.canonical_form).""" + length = self.length + if length <= _TOL_1E6: + return CanonicalForm(0.0, 1, False) + closed = (self.position_at(0) - self.position_at(1)).length <= _TOL_1E6 + + def sampler(distance): + return self.position_at(min(max(distance / length, 0.0), 1.0)) + + return _canonical_form(sampler, length, closed, samples=samples) + + def project_to_shape(self, target_object, direction=None, center=None): + """Project this wire onto the surfaces of a shape, either along a + direction or conically from a center point (pass exactly one) — + BRepProj_Projection, like build123d's Wire.project_to_shape. One or + more wires come back, nearest projection first.""" + if (direction is None) == (center is None): + raise ValueError('Provide exactly one of direction or center') + d = list(Vector(direction).normalized()) if direction is not None \ + else None + c = list(Vector(center)) if center is not None else None + out = w.ProjectWireOnShape(_topo(self), _topo(target_object), d, c) + return ShapeList([Curve(t) for t in out]) + + @property + def is_closed(self): + """Whether the wire/edge closes on itself (BRep_Tool::IsClosed).""" + if self.topo is None: + return False + return bool(w._wireIsClosed(self.topo)) + + def offset_2d(self, distance, kind=Kind.ARC, side=Side.BOTH, closed=True): + """2D offset of this planar wire (build123d Wire.offset_2d). + side=LEFT/RIGHT keeps only one side of the offset of an OPEN wire + (the end caps and the other side are dropped); closed=True then joins + that side back to the original line to make a closed region.""" + if kind == Kind.TANGENT: + raise NotImplementedError('Kind.TANGENT offsets are not supported ' + 'in build123d-lite') + join = 'intersection' if kind == Kind.INTERSECTION else 'arc' + line = self + edges = line.edges() + if len(edges) == 1: + # BRepOffsetAPI_MakeOffset mishandles a single-edge wire, so split + # it in half first (exactly build123d's workaround) + halves = [edges[0].trim(0.0, 0.5), edges[0].trim(0.5, 1.0)] + src = w.WireFromEdgesFixed([_topo(h) for h in halves], 1e-7) + else: + src = _topo(line) + offset_topo = w.OffsetPlanarWire(src, distance, join) + if offset_topo is None: + raise RuntimeError('2D offset produced no wire') + offset_wire = Curve(offset_topo) + if side == Side.BOTH: + oes = offset_wire.edges() + return oes[0] if len(oes) == 1 else offset_wire + + # drop the semicircular end caps, then keep the side asked for + endpoints = (line.position_at(0), line.position_at(1)) + + def _is_end_cap(e): + if e.geom_type != GeomType.CIRCLE: + return False + c = e.arc_center + for pt in endpoints: + if (c - pt).length < _TOL_1E6: + return True + return False + + sides = edges_to_wires(offset_wire.edges().filter_by(_is_end_cap, + reverse=True)) + if len(sides) != 2: + raise RuntimeError('one-sided offset expected two offset sides, ' + 'got ' + repr(len(sides))) + tan0 = line.tangent_at(0) + angles = [tan0.get_signed_angle(wr.position_at(0.5) - endpoints[0]) + for wr in sides] + if side == Side.LEFT: + offset_wire = sides[int(angles[0] > angles[1])] + else: + offset_wire = sides[int(angles[0] <= angles[1])] + + if closed: + self0, self1 = endpoints + end0 = offset_wire.position_at(0) + end1 = offset_wire.position_at(1) + if (self0 - end0).length - abs(distance) <= _TOL_1E6: + edge0 = Edge.make_line(self0, end0) + edge1 = Edge.make_line(self1, end1) + else: + edge0 = Edge.make_line(self0, end1) + edge1 = Edge.make_line(self1, end0) + joined = list(line.edges()) + list(offset_wire.edges()) + \ + [edge0, edge1] + offset_wire = Curve(w.WireFromEdgesFixed( + [_topo(e) for e in joined], _TOL_1E6)) + + oes = offset_wire.edges() + return oes[0] if len(oes) == 1 else offset_wire + + def order_edges(self): + """The edges in CONNECTION order (build123d Wire.order_edges — + BRepTools_WireExplorer, not TopExp's storage order).""" + return ShapeList([Edge(t) for t in w.OrderedEdges(self.topo)]) + + @property + def start_point(self): + return self @ 0 + + @property + def end_point(self): + return self @ 1 + + @classmethod + def make_polygon(cls, pts, close=True): + """Closed polygonal wire through pts (build123d Wire.make_polygon).""" + return Polyline(*[tuple(_v3(p)) for p in pts], close=close, + mode=Mode.PRIVATE) + + +Solid = Part +def _wire_combine(cls, wires, tol=1e-9): + """Group edges/wires into the largest possible wires (build123d + Wire.combine): the same connectivity grouping edges_to_wires does, which is + what ShapeAnalysis_FreeBounds::ConnectEdgesToWires computes upstream.""" + edges = [] + for item in _tolist(wires): + edges.extend(item.edges() if not isinstance(item, Edge) else [item]) + return edges_to_wires(edges, max(tol, 1e-9)) + + +Curve.combine = classmethod(_wire_combine) + +Wire = Curve + + +class Compound(Shape): + @classmethod + def make_triad(cls, axes_scale): + """The coordinate-system triad symbol (build123d Compound.make_triad): + three axis lines with spline arrow heads. + + COMPROMISE(triad-labels): upstream also draws 'X'/'Y'/'Z' with the + 'singleline' STROKE font, which this build does not ship (only the + outline font FreeSans), so the labels are omitted. The triad is a + viewer symbol, never part of a modelled part.""" + s = float(axes_scale) + parts = [Edge.make_line((0, 0, 0), (s, 0, 0)), + Edge.make_line((0, 0, 0), (0, s, 0)), + Edge.make_line((0, 0, 0), (0, 0, s))] + arrow_arc = Edge.make_spline([(0, 0, 0), (-s / 20, s / 30, 0)], + [(-1, 0, 0), (-1, 1.5, 0)]) + arrow = Curve([arrow_arc, arrow_arc.mirror(Plane.XZ)]) + parts.append(Pos(s, 0, 0) * arrow) + parts.append(Pos(0, s, 0) * (arrow.rotate(Axis.Z, 90))) + parts.append(Pos(0, 0, s) * (arrow.rotate(Axis.Y, -90))) + return Curve(parts) + + def __init__(self, children=None, label='', **kwargs): + # Compound(shape.wrapped) / Compound(topods): a single raw TopoDS shape + # (build123d's Shape(obj) form, used by Compound subclasses that call + # super().__init__(builder.part.wrapped, ...) - tutorial_joints' Hinge) + if children is not None and not isinstance(children, Shape) and \ + hasattr(children, 'ShapeType'): + children = [children] + topos = [_topo(c) for c in _tolist(children) if not (isinstance(c, Shape) and c.topo is None)] + topo = None + if len(topos) == 1: + topo = topos[0] + elif len(topos) > 1: + topo = w.MakeCompound(topos) + Shape.__init__(self, topo) + self.label = label + self.children = _tolist(children) + # Compound(..., joints=) — build123d's Compound.__init__ adopts a + # joint dict and REPARENTS every joint onto the new compound. This is + # how a Compound subclass built from a builder keeps the joints the + # builder collected (tutorial_joints' Hinge). + joints = kwargs.get('joints') + if joints: + self.joints = {k: j._lite_rebind(self) for k, j in joints.items()} + + @classmethod + def make_text(cls, txt, font_size, font='Arial', font_path=None, + font_style=None, + text_align=('center', 'center'), # TextAlign values + align=None, position_on_path=0.0, text_path=None): + """2D text as a compound of faces (build123d Compound.make_text). + Like upstream, align defaults to None: only the Font_TextFormatter + (advance-based) text_align applies, NOT bbox alignment.""" + if text_path is not None: + raise NotImplementedError( + 'Compound.make_text(text_path=) is not supported in ' + 'build123d-lite') + t = Text(txt, font_size, font=font, font_path=font_path, + font_style=font_style if font_style is not None + else FontStyle.REGULAR, + text_align=text_align, align=align, mode=Mode.PRIVATE) + res = cls.__new__(cls) + Shape.__init__(res, t.topo) + return res + + +def _single_edge_of(curve): + """The one edge of a single-segment Curve (build123d's 1-D objects return + Curves whose circular-arc properties read through to that edge).""" + es = curve.edges() + if len(es) != 1: + raise ValueError('this property is only defined for a single edge, ' + 'this curve has ' + str(len(es))) + return es[0] + + +class Edge(Curve): + def __init__(self, topo, parent=None, index=None): + Curve.__init__(self, topo) + self.parent = parent + self.index = index + + @property + def geom_type(self): + t = w._edgeCurveType(self.topo) + for name in ('LINE', 'CIRCLE', 'ELLIPSE', 'HYPERBOLA', 'PARABOLA', + 'BEZIER', 'BSPLINE', 'OTHER'): + if t in getattr(GeomType, name): + return getattr(GeomType, name) + return GeomType.OTHER + + @property + def length(self): + return w._edgeLength(self.topo) + + def center(self, center_of=CenterOf.GEOMETRY): + return Vector(tuple(w._edgeMidpoint(self.topo))) + + def _edge_topo(self): + return self.topo + + @property + def is_forward(self): + return bool(w._edgeIsForward(self.topo)) + + def find_intersection_points(self, other, tolerance=1e-6): + """Points where this 2-D edge crosses an Axis or another Edge + (build123d Edge.find_intersection_points - the 1-D form, distinct from + Shape's ray/surface intersection). + + Upstream lifts both curves onto their common plane and calls + Geom2dAPI_InterCurveCurve; that needs BRep_Tool::CurveOnPlane, which is + unbound here, so the crossing is solved on the signed distance to the + other curve's line: sample the arc-length parametrization, then bisect + every sign change. Exact to tolerance for the analytic line/arc cases + the constructors use.""" + if hasattr(other, 'position') and hasattr(other, 'direction'): + base = Vector(other.position) + d = Vector(other.direction).normalized() + else: + o = other.edges()[0] if not isinstance(other, Edge) else other + base = o.position_at(0) + d = (o.position_at(1) - base).normalized() + # signed perpendicular offset of the sampled point from the line, + # in the plane spanned by d and the sampling normal + def offset(u): + q = Vector(tuple(w._edgePointAt(self.topo, float(u)))) + r = q - base + along = r.dot(d) + perp = r - d * along + # sign from the 2-D cross product about z (planar edges) + sign = 1.0 if (d.X * r.Y - d.Y * r.X) >= 0 else -1.0 + return sign * perp.length + + n = 512 + roots = [] + prev_u, prev = 0.0, offset(0.0) + if abs(prev) < tolerance: + roots.append(0.0) + for i in range(1, n + 1): + u = i / n + cur = offset(u) + if abs(cur) <= tolerance: + # contact WITHOUT a sign change: an end point sitting on the + # line, or a tangency. Upstream's Geom2dAPI_InterCurveCurve is + # tolerance-based and reports these, so the sampled search has + # to as well (the wing example's trailing edge ends exactly on + # the axis it is measured against). + if not any(abs(u - r) < 1e-9 for r in roots): + roots.append(u) + if (prev <= 0.0 <= cur) or (cur <= 0.0 <= prev): + lo, hi, flo = prev_u, u, prev + for _ in range(60): + mid = (lo + hi) / 2.0 + fm = offset(mid) + if (flo <= 0.0) == (fm <= 0.0): + lo, flo = mid, fm + else: + hi = mid + root = (lo + hi) / 2.0 + if not any(abs(root - r) < 1e-9 for r in roots): + roots.append(root) + prev_u, prev = u, cur + out = ShapeList() + for r in roots: + p = Vector(tuple(w._edgePointAt(self.topo, float(r)))) + # reject near-misses: the point must really lie on the line + rel = p - base + if (rel - d * rel.dot(d)).length <= max(tolerance, 1e-6) * 100: + out.append(p) + return out + + @property + def radius(self): + """Radius of a circular edge (build123d Edge.radius).""" + r = w._edgeArcRadius(self.topo) + if r is None: + raise ValueError('radius is only defined for circles') + return r + + @property + def is_interior(self): + """True when this edge lies between two faces of the SAME body rather + than on its outer boundary (build123d Edge.is_interior): offset both + adjoining faces outward by length/100 and see whether they still + intersect in an edge.""" + return bool(w.EdgeIsInterior(self.topo, _topo(self.parent) + if self.parent is not None else None)) + + def find_tangent(self, angle): + """The normalized parameters at which this edge's tangent is at 'angle' + degrees to the local x axis (build123d Edge.find_tangent).""" + tangent = math.tan(math.radians(angle)) + out = [] + # upstream solves the 2-D tangent condition on the curve; lite scans + # the arc-length parametrization and bisects each sign change + def f(u): + t = w._edgeTangentAt(self.topo, float(u)) + if abs(t[0]) < 1e-12: + return None + return t[1] / t[0] - tangent + n = 512 + prev_u, prev = 0.0, f(0.0) + for i in range(1, n + 1): + u = i / n + cur = f(u) + if prev is not None and cur is not None and \ + ((prev <= 0.0 <= cur) or (cur <= 0.0 <= prev)): + lo, hi, flo = prev_u, u, prev + for _ in range(60): + mid = (lo + hi) / 2.0 + fm = f(mid) + if fm is None: + break + if (flo <= 0.0) == (fm <= 0.0): + lo, flo = mid, fm + else: + hi = mid + root = (lo + hi) / 2.0 + if not any(abs(root - r) < 1e-6 for r in out): + out.append(root) + prev_u, prev = u, cur + return out + + @property + def arc_center(self): + """Center of a circular/elliptical edge (build123d Edge.arc_center).""" + c = w._edgeArcCenter(self.topo) + if c is None: + raise ValueError('arc_center is only defined for circles and ' + 'ellipses') + return Vector(tuple(c)) + + def position_at(self, u): + # orientation-aware like build123d (Axis(edge) stays raw-curve). + # COMPROMISE(edge-orientation): which end of a selector edge is + # FORWARD depends on the kernel's construction history, and OCCT + # 8.0.1 (wasm) does not always orient sub-edges the way OCP 7.x + # does — scripts that measure along Axis(edge) of a selected edge + # (the joints examples) can legitimately land at the opposite end. + uu = u if self.is_forward else 1.0 - u + return Vector(tuple(w._edgePointAt(self.topo, float(uu)))) + + def tangent_at(self, u=0.5): + uu = u if self.is_forward else 1.0 - u + t = Vector(tuple(w._edgeTangentAt(self.topo, float(uu)))) + return t if self.is_forward else -t + + def __matmul__(self, u): + return self.position_at(u) + + def __mod__(self, u): + return self.tangent_at(u) + + def project_to_shape(self, target_object, direction=None, center=None): + """The projected EDGES of this edge on a shape (build123d + Edge.project_to_shape flattens the projected wires to edges).""" + wires = Curve.project_to_shape(self, target_object, direction, center) + return wires.edges() + + def param_at(self, position=0.5): + """The raw OCCT curve parameter at the normalized ARC-LENGTH position + (build123d Edge.param_at; positions outside [0, 1] extrapolate).""" + return w._edgeParam(self.topo, float(position)) + + def param_at_point(self, point): + """Normalized parameter (0..1) of the point on this edge closest to + point (build123d Edge.param_at_point: vertex snap, then + GeomAPI_ProjectPointOnCurve validated by re-evaluation, then a bounded + numeric search — all three inside _edgeParamAtPoint).""" + u = w._edgeParamAtPoint(self.topo, list(_v3(point))) + if u < 0.0: + raise ValueError('point ' + repr(tuple(_v3(point))) + + ' is not on this edge') + return u + + def trim(self, start, end): + """A new edge keeping only the section between two normalized + arc-length positions, each of which may be given as a POINT on the + edge instead (build123d Edge.trim).""" + start_u = self._to_param(start) + end_u = self._to_param(end) + trimmed = Edge(w.TrimEdge(self.topo, float(min(start_u, end_u)), + float(max(start_u, end_u)))) + # keep the requested direction (upstream rebuilds it reversed) + start_point = self.position_at(start_u) + same_start = (trimmed.position_at(0) - start_point).length < _TOL_1E6 + same_direction = self.tangent_at(start_u).dot( + trimmed.tangent_at(0)) > 1 - _TOL_1E6 + if same_start and same_direction: + return trimmed + return _reverse_1d(trimmed) + + def trim_to_other(self, other): + """The SHORTEST piece of this edge trimmed at its intersections with + other, or None when they do not intersect (build123d + Edge.trim_to_other).""" + points = self.find_intersection_points(other) + if not points: + return None + trims = ShapeList([self.trim(0.0, p) for p in points]) + return trims.sort_by(Edge.length)[0] + + def _extend_spline(self, at_start, surface_face, extension_factor=0.1): + """A copy of this B-spline edge extended past one end by + extension_factor of its length and snapped back onto the surface + (build123d Edge._extend_spline).""" + if self.geom_type != GeomType.BSPLINE: + raise TypeError('_extend_spline only works with splines') + topo = w.ExtendSplineOnFace(self.topo, bool(at_start), + _topo(surface_face), + float(extension_factor)) + if topo is None: + raise RuntimeError('Failed to snap extended edge to surface') + return Edge(topo) + + @classmethod + def make_spline(cls, points, tangents=None, periodic=False, + parameters=None, scale=True, tol=1e-6): + """Edge interpolating the points EXACTLY (GeomAPI_Interpolate, like + build123d's Edge.make_spline). tangents are either the two end + tangents or one per point.""" + pts = [list(_v3(p)) for p in points] + tans = [list(_v3(t)) for t in tangents] if tangents else [] + return cls(w.InterpolatedEdge(pts, tans, bool(periodic), bool(scale))) + + @classmethod + def make_circle(cls, radius, plane=None, start_angle=360.0, + end_angle=360.0, angular_direction=None): + """Full circle or circular arc edge (build123d Edge.make_circle). + A full circle is the default (start_angle == end_angle).""" + if plane is None: + plane = Plane.XY + topo = w.CircularEdge(float(radius), float(start_angle), + float(end_angle), + list(plane.origin), list(plane.z_dir), + list(plane.x_dir)) + return cls(topo) + + @classmethod + def make_three_point_arc(cls, p1, p2, p3): + """Circular arc through three points (build123d + Edge.make_three_point_arc / GC_MakeArcOfCircle).""" + wire = w.WireFromSegments([('arc3', [list(_v3(p1)), list(_v3(p2)), + list(_v3(p3))])]) + return Curve(wire).edges()[0] + + def split(self, plane, keep=None): + """The part of this edge on the +z_dir side of a plane (build123d + Mixin1D.split, Keep.TOP default). Returns None when nothing is left.""" + pieces = [] + u = _edge_plane_crossing(self, plane) + if u is None: + side = plane.to_local_coords(self.position_at(0.5)).Z + return self if side >= -_TOL_1E6 else None + for a, b in ((0.0, u), (u, 1.0)): + if b - a <= _TOL_1E6: + continue + piece = self.trim(a, b) + mid = plane.to_local_coords(piece.position_at(0.5)).Z + if mid >= -_TOL_1E6: + pieces.append(piece) + if not pieces: + return None + return pieces[0] if len(pieces) == 1 else pieces + + @classmethod + def make_line(cls, p1, p2): + """Linear edge between two points (build123d Edge.make_line).""" + seg = ('line', [list(_v3(p1)), list(_v3(p2))]) + wire = w.WireFromSegments([seg]) + edges = list(w.Edges(wire).edges()) + return cls(edges[0]) + + @classmethod + def make_mid_way(cls, first, second, middle=0.5): + """Linear edge a fractional distance between two edges + (build123d Edge.make_mid_way, flip-aware). + + The direction and start point of the two reference edges are incidental + - a section edge starts wherever the kernel's intersector happened to + seam it - so the ends are paired up CANONICALLY instead of from the + construction history (the is_opposite() flip below is kept for + reference edges that are not parallel).""" + first, second = first.canonical(), second.canonical() + flip = Axis(first, canonical=True).is_opposite( + Axis(second, canonical=True)) + pnts = [] + for i in (0.0, 1.0): + a = first.position_at(i) + b = second.position_at(1.0 - i if flip else i) + pnts.append(a + (b - a) * middle) + return cls.make_line(pnts[0], pnts[1]) + + +# --------------------------------- canonical 1D shape rebuilding helpers --- +# build123d's one_d.py module-level helpers behind Mixin1D.canonical(). + +def _reverse_1d(shape): + """A copy of an Edge or Wire that is traversed in the opposite direction. + + An Edge only needs its orientation flag flipped, which Edge.position_at + honours. A Wire needs more: lite's Curve._walk follows + BRepTools_WireExplorer's edge order and ignores the wire's own orientation + flag (upstream's Wire.position_at goes through _occt_param_at and does + honour it), so flipping the flag alone would silently leave position_at + unchanged - which is exactly the kind of noise-level failure the canonical + rule exists to remove. Rebuilding the wire from its edges in REVERSE order, + each individually reversed, is the same geometry with a genuinely reversed + traversal. A single-edge wire cannot express it at all, so it comes back as + an Edge (which can).""" + if isinstance(shape, Edge): + return Edge(w.ReverseEdgeOrWire(_topo(shape))) + edges = list(shape.order_edges()) + if len(edges) == 1: + return Edge(w.ReverseEdgeOrWire(edges[0].topo)) + return Curve(w.WireFromEdgesFixed( + [w.ReverseEdgeOrWire(e.topo) for e in edges[::-1]], _TOL_1E6)) + + +def _edge_plane_crossing(edge, plane, samples=64): + """The normalized parameter at which an edge crosses a plane (the sign of + the LOCAL z flips), refined by bisection; None when it never crosses.""" + def height(u): + return plane.to_local_coords(edge.position_at(u)).Z + prev_u, prev_h = 0.0, height(0.0) + for i in range(1, samples + 1): + u = i / samples + h = height(u) + if (prev_h < 0.0) != (h < 0.0): + lo, hi = prev_u, u + for _ in range(60): + mid = (lo + hi) / 2.0 + if (height(lo) < 0.0) != (height(mid) < 0.0): + hi = mid + else: + lo = mid + return (lo + hi) / 2.0 + prev_u, prev_h = u, h + return None + + +def _split_1d_at_point(shape, point): + """The Edges of shape, with the one that contains point split there.""" + pieces = [] + pt = list(point) + for edge in shape.edges(): + ends_at_point = min((edge.position_at(0) - point).length, + (edge.position_at(1) - point).length) + if ends_at_point > _TOL_1E6 and \ + w._edgeDistanceToPoint(edge.topo, pt) <= _TOL_1E6: + parameter = w._edgeParamAtPoint(edge.topo, pt) + if parameter >= 0.0 and \ + _TOL_1E6 < parameter * edge.length < edge.length - _TOL_1E6: + pieces.append(edge.trim(0.0, parameter)) + pieces.append(edge.trim(parameter, 1.0)) + continue + pieces.append(edge) + return pieces + + +def _walk_loop(pieces, start, direction): + """Order and orient pieces into a chain that leaves start heading along + direction, purely by matching end points.""" + remaining = list(pieces) + # the requested start comes from a sampled parameter, so allow a gap that + # scales with the size of the loop + gap_tolerance = max(_TOL_1E6, 1e-6 * sum(piece.length for piece in pieces)) + ordered = [] + position, heading = start, direction + while len(remaining) > 0: + best, best_score, flip = None, None, False + for candidate in remaining: + for reverse in (False, True): + # scoring a reversed candidate without building it: its + # position_at(0) is the candidate's position_at(1) and its + # tangent_at(0) is the negated tangent_at(1) + if reverse: + gap = (candidate.position_at(1) - position).length + heading_score = candidate.tangent_at(1).dot(heading) + else: + gap = (candidate.position_at(0) - position).length + heading_score = -candidate.tangent_at(0).dot(heading) + # Rank on whether the gap is closed at all, then on the tangent, + # and only then on the gap itself. Comparing raw gaps first would + # let 1e-16 noise decide between two pieces that meet at the same + # vertex - and at the seam of a loop those two pieces head in + # opposite directions, so the loop could be walked backwards. + score = (gap > gap_tolerance, heading_score, gap) + if best_score is None or score < best_score: + best, best_score, flip = candidate, score, reverse + if best is None or best_score[0]: + return pieces # not a connected chain - keep the input order + edge = _reverse_1d(best) if flip else best + ordered.append(edge) + remaining = [p for p in remaining if p is not best] + position, heading = edge.position_at(1), edge.tangent_at(1) + return ordered + + +def _concatenate_edges(edges): + """A single Edge whose curve is the concatenation of edges, in order. + + Used to give a re-seamed closed loop an unambiguous start point: a closed + TopoDS_Wire carries no distinguished first edge, while an Edge's curve + parametrization does.""" + return Edge(w.ConcatEdgesToEdge([_topo(e) for e in edges])) + + +class Face(Shape): + def __init__(self, topo, parent=None, index=None): + # Face(outer_wire, [hole_wires]) like build123d + inner = None + if isinstance(parent, (list, tuple, ShapeList)) and \ + all(isinstance(x, (Curve, Edge)) for x in parent): + inner = list(parent) + parent = None + if isinstance(topo, (Curve, Edge)): + outer = _topo(topo) + if outer.ShapeType().value != 5: + outer = w.GetWire(outer, 0, True) + if inner: + def _wire_of(x): + tw = _topo(x) + return tw if tw.ShapeType().value == 5 \ + else w.GetWire(tw, 0, True) + topo = w.FaceWithHoles(outer, [_wire_of(x) for x in inner]) + else: + # build123d's Face(wire) is always PLANAR (OnlyPlane=True) + topo = w.MakeFace(outer, True, True) + Shape.__init__(self, topo) + self.parent = parent + self.index = index + + @classmethod + def make_gordon_surface(cls, profiles, guides, tolerance=0.0003): + # Gordon curve-network surface interpolation. Upstream delegates to + # the external ocp_gordon package; here the algorithm is a JS port + # (GordonSurface.js, COMPROMISE notes in its header) exposed as + # w.GordonSurfaceFace. Profiles/guides: Edge/Curve/Wire or points + # (only first/last entries may be points, matching upstream). + def conv(item): + if isinstance(item, Shape): + return _topo(item) + v = Vector(item) + return [v.X, v.Y, v.Z] + p = [conv(i) for i in profiles] + g = [conv(i) for i in guides] + return cls(w.GordonSurfaceFace(p, g, tolerance)) + + @property + def geom_type(self): + t = w._faceSurfaceType(self.topo) + for name in ('PLANE', 'CYLINDER', 'CONE', 'SPHERE', 'TORUS', + 'BEZIER', 'BSPLINE', 'OTHER'): + if t in getattr(GeomType, name): + return getattr(GeomType, name) + return GeomType.OTHER + + @property + def area(self): + return w._faceArea(self.topo) + + def center(self, center_of=CenterOf.GEOMETRY): + if center_of == CenterOf.BOUNDING_BOX: + return self.bounding_box().center() + return Vector(tuple(w._faceCentroid(self.topo))) + + def _surface_params(self, surface_point, u, v): + """RAW (u, v) surface parameters for build123d's two overloads: a + 3D point projected onto the surface, or NORMALIZED u/v mapped into + the face's UV bounds.""" + if surface_point is None: + b = tuple(w._faceUVBounds(self.topo)) + return (b[0] + u * (b[1] - b[0]), b[2] + v * (b[3] - b[2])) + uv = w._faceParamsAtPoint(self.topo, list(Vector(surface_point))) + if uv is None: + raise ValueError('could not project the point onto this surface') + return (uv[0], uv[1]) + + def _surface_args(self, args, kwargs, extra=()): + """Shared argument parsing for normal_at/location_at: either + (surface_point) or (u, v), defaulting to the face center.""" + surface_point, u, v = None, -1.0, -1.0 + if args: + if isinstance(args[0], (Vector, tuple, list)): + surface_point = args[0] + elif isinstance(args[0], (int, float)): + u = args[0] + if len(args) == 2 and isinstance(args[1], (int, float)): + v = args[1] + allowed = ('surface_point', 'u', 'v') + tuple(extra) + unknown = [k for k in kwargs if k not in allowed] + if unknown: + raise ValueError('Unexpected argument(s) ' + ', '.join(unknown)) + surface_point = kwargs.get('surface_point', surface_point) + u = kwargs.get('u', u) + v = kwargs.get('v', v) + if surface_point is None and u < 0 and v < 0: + u, v = 0.5, 0.5 + elif surface_point is None and (u < 0 or v < 0): + raise ValueError('Both u & v values must be specified') + return (surface_point, u, v) + + def normal_at(self, *args, **kwargs): + """Unit surface normal, at the face center by default, or at a 3D + surface_point / normalized (u, v) — build123d Face.normal_at.""" + if not args and not kwargs: + # the whole-face mid-parameter normal (identical evaluation, but + # this path is what every other lite call site already uses) + return Vector(tuple(w._faceNormal(self.topo))) + surface_point, u, v = self._surface_args(args, kwargs) + u_val, v_val = self._surface_params(surface_point, u, v) + return Vector(tuple(w._faceNormalAt(self.topo, u_val, v_val))) + + def location_at(self, *args, **kwargs): + """location_at(u, v, *, x_dir=None) | location_at(surface_point, *, + x_dir=None): the placement (origin + orientation) on this surface. + z is the surface normal (dU x dV), x defaults to the U tangent — + build123d Face.location_at. Defaults to the face center (0.5, 0.5).""" + surface_point, u, v = self._surface_args(args, kwargs, ('x_dir',)) + user_x_dir = kwargs.get('x_dir', None) + u_val, v_val = self._surface_params(surface_point, u, v) + d = w._faceD1(self.topo, u_val, v_val) + origin = Vector(tuple(d[0])) + du, dv = Vector(tuple(d[1])), Vector(tuple(d[2])) + z_dir = du.cross(dv).normalized() + x_dir = Vector(user_x_dir) if user_x_dir is not None else du + return Location(Plane(origin=origin, x_dir=x_dir, z_dir=z_dir)) + + def position_at(self, u, v): + """Point on the face at NORMALIZED (u, v) surface parameters + (build123d Face.position_at).""" + u_val, v_val = self._surface_params(None, u, v) + return Vector(tuple(w._faceD1(self.topo, u_val, v_val)[0])) + + @property + def center_location(self): + """Location at the centre of the face (build123d + Face.center_location): the (0.5, 0.5) surface point with the surface + normal as z.""" + origin = self.position_at(0.5, 0.5) + return Plane(origin=origin, z_dir=self.normal_at(origin)).location + + @property + def _curvature_sign(self): + """Signed reference distance between the face's centre and its + underlying geometry's reference point — positive convex, negative + concave, 0.0 for surfaces that are not a cylinder/sphere/torus + (build123d Face._curvature_sign; StandardLibrary._faceCurvatureSign + reads the surface's own gp_Cylinder/gp_Sphere/gp_Torus reference, which + the fork binds as of the Geom2dGcc round).""" + return w._faceCurvatureSign(self.topo) + + @property + def is_circular_convex(self): + """Is this cylinder/sphere/torus face convex relative to its own + geometry (build123d Face.is_circular_convex).""" + return self._curvature_sign > _TOL_1E6 + + @property + def is_circular_concave(self): + """Is this cylinder/sphere/torus face concave relative to its own + geometry (build123d Face.is_circular_concave).""" + return self._curvature_sign < -_TOL_1E6 + + def offset(self, amount): + """The face's plane offset by amount (build123d Face.offset).""" + return Plane(self).offset(amount) + + @property + def radius(self): + """Radius of a cylindrical or spherical face, else None (build123d + Face.radius, read off the surface's own gp_Cylinder/gp_Sphere).""" + return w._faceRadius(self.topo) + + @property + def axis_of_rotation(self): + """Rotational axis of a cone/cylinder/sphere/torus/revolution face, + else None (build123d Face.axis_of_rotation).""" + ax = w._faceAxisOfRotation(self.topo) + if ax is None: + return None + origin, direction = list(ax[0]), list(ax[1]) + return Axis(tuple(origin), tuple(direction)) + + def outer_wire(self): + """The face's outer boundary wire (BRepTools::OuterWire).""" + return Curve(w._faceOuterWire(self.topo)) + + def inner_wires(self): + """Hole wires: every wire of the face except the outer one.""" + outer = w._faceOuterWire(self.topo) + out = ShapeList() + + def _cb(i, wire): + if not w._sameShape(wire, outer): + out.append(Curve(wire)) + w.ForEachWire(self.topo, _cb) + return out + + def project_to_shape(self, target, direction): + """Project this face onto target along direction: extrude the + face by the combined bbox diagonal and intersect with the target + (BRepAlgoAPI_Common) — exactly build123d's Face.project_to_shape. + Returns faces ordered by distance along the projection axis.""" + d = Vector(direction).normalized() + bb1 = list(w.BoundingBox(self.topo)) + bb2 = list(w.BoundingBox(_topo(target))) + lo = [min(bb1[i], bb2[i]) for i in range(3)] + hi = [max(bb1[i + 3], bb2[i + 3]) for i in range(3)] + diag = math.sqrt(sum((hi[i] - lo[i]) ** 2 for i in range(3))) + prism = w.Extrude(self.topo, [d[0] * diag, d[1] * diag, d[2] * diag], + True) + # like build123d: intersect the prism with the target's SHELLS (its + # boundary surface), so the result is surface pieces on the target + # — front AND back — never the prism's own side walls + shells = [] + + def _shell_cb(i, sh): + shells.append(sh) + w.ForEachShell(_topo(target), _shell_cb) + if not shells: + shells = [_topo(target)] + pieces = [] + for sh in shells: + common = w.Intersection([prism, sh], True, 1e-7, True) + pieces.extend(Shape(common).faces()) + origin = self.center() + + def _dist(f): + c = w._faceCentroid(f.topo) + return ((c[0] - origin.X) * d[0] + (c[1] - origin.Y) * d[1] + + (c[2] - origin.Z) * d[2]) + return ShapeList(sorted([Face(f.topo) for f in pieces], key=_dist)) + + @classmethod + def make_surface(cls, exterior, surface_points=None, interior_wires=None): + """A potentially NON-planar face bounded by exterior (a wire or + edges), pulled towards surface_points and holed by interior_wires — + the exact BRepOffsetAPI_MakeFilling construction of build123d's + Face.make_surface.""" + if isinstance(exterior, Shape): + edges = [e.topo for e in exterior.edges()] + else: + edges = [_topo(e) for e in exterior] + pts = [list(Vector(p)) for p in (surface_points or [])] + holes = [_topo(x) for x in (interior_wires or [])] + topo = w.FillingFace(edges, pts, holes) + if topo is None: + raise RuntimeError('non planar face is invalid') + return cls(topo) + + # --- wrapping flat geometry onto this surface (build123d Face.wrap) --- + + def _intersect_surface_normal(self, point, direction, target_center): + """(point, unit normal) of the closest crossing of the axis + (point, direction) with this surface — the inner helper of + build123d's Face._wrap_edge.""" + hits = self.find_intersection_points(Axis(point, direction)) + if not hits: + raise RuntimeError('wrapping over surface boundary, try a ' + 'different surface_loc') + best, best_d = hits[0], (hits[0][0] - point).length + for h in hits[1:]: + d = (h[0] - point).length + if d < best_d: + best, best_d = h, d + return best + + def _wrap_edge(self, planar_edge, surface_loc, snap_to_face=True, + tolerance=0.001): + """Wrap one flat edge onto this surface: march along the edge in the + local surface frame, casting each step back onto the surface, refining + the subdivision until the wrapped length matches — build123d's + Face._wrap_edge.""" + if self.topo is None: + raise ValueError('cannot wrap around an empty face') + target_center = self.center(CenterOf.BOUNDING_BOX) + surface_x_direction = surface_loc.x_axis.direction + planar_edge_length = planar_edge.length + + def find_point_on_surface(current_point, normal, relative_position): + local_plane = Plane(origin=current_point, + x_dir=surface_x_direction, z_dir=normal) + world_point = local_plane.from_local_coords(relative_position) + return self._intersect_surface_normal( + world_point, world_point - target_center, target_center) + + if planar_edge.position_at(0).length > tolerance: + # the edge does not start at the surface location: wrap a + # construction line to find where it does + to_start_edge = Edge.make_line((0, 0, 0), planar_edge @ 0) + wrapped_to_start = self._wrap_edge(to_start_edge, surface_loc, + True, tolerance) + start_pnt = wrapped_to_start @ 1 + start_normal = self._intersect_surface_normal( + start_pnt, start_pnt - target_center, target_center)[1] + else: + start_pnt = surface_loc.position + start_normal = surface_loc.z_axis.direction + + closed = planar_edge.is_closed + subdivisions = 3 + loop_count = 0 + length_error = 1e308 + wrapped_edge = None + while length_error > tolerance and loop_count < 10: + points = [start_pnt] + current_point, current_normal = start_pnt, start_normal + for div in range(1, subdivisions + (0 if closed else 1)): + prev = planar_edge.position_at((div - 1) / subdivisions) + curr = planar_edge.position_at(div / subdivisions) + current_point, current_normal = find_point_on_surface( + current_point, current_normal, curr - prev) + points.append(current_point) + wrapped_edge = Edge.make_spline(points, periodic=closed) + length_error = abs(planar_edge_length - wrapped_edge.length) + subdivisions *= 2 + loop_count += 1 + + if length_error > tolerance: + raise RuntimeError('Length error of ' + repr(length_error) + + ' exceeds tolerance ' + repr(tolerance)) + if not snap_to_face: + return wrapped_edge + snapped = w.ProjectEdgeOnFace(_topo(wrapped_edge), self.topo) + if snapped is None: + raise RuntimeError('Projection failed, try setting snap_to_face ' + 'to False.') + return Edge(snapped) + + def _wrap_wire(self, planar_wire, surface_loc, tolerance=0.001, + extension_factor=0.1): + """Wrap a flat wire onto this surface edge by edge, then close the + junction the distortion opens between the first and last edge — + build123d's Face._wrap_wire.""" + surface_point = surface_loc.position + surface_x_direction = surface_loc.x_axis.direction + + planar_edges = planar_wire.order_edges() + if len(planar_edges) == 1: + return Curve([self._wrap_edge(planar_edges[0], surface_loc, True, + tolerance)]) + + wrapped_edges = [] + first_start_point = None + + if planar_edges[0].position_at(0) == Vector(0, 0, 0): + edge_surface_point = surface_point + planar_edge_end_point = Vector(0, 0, 0) + else: + construction_line = Edge.make_line( + (0, 0, 0), planar_edges[0].position_at(0)) + wrapped_construction_line = self._wrap_edge( + construction_line, surface_loc, True, tolerance) + edge_surface_point = wrapped_construction_line.position_at(1) + planar_edge_end_point = planar_edges[0].position_at(0) + edge_surface_location = Location(Plane( + origin=edge_surface_point, x_dir=surface_x_direction, + z_dir=self.normal_at(edge_surface_point))) + + for planar_edge in planar_edges: + # re-wrap as an Edge: _wrap_like turns a transformed Edge into a + # Curve, and Curve.position_at is not orientation-aware, so a + # REVERSED edge of the wire would march from the wrong end + local_planar_edge = Edge(_topo( + planar_edge.translate(-planar_edge_end_point))) + wrapped_edge = self._wrap_edge(local_planar_edge, + edge_surface_location, True, + tolerance) + edge_surface_point = wrapped_edge.position_at(1) + edge_surface_location = Location(Plane( + origin=edge_surface_point, x_dir=surface_x_direction, + z_dir=self.normal_at(edge_surface_point))) + planar_edge_end_point = planar_edge.position_at(1) + if first_start_point is None: + first_start_point = wrapped_edge.position_at(0) + wrapped_edges.append(wrapped_edge) + + if not planar_wire.is_closed: + return Curve(wrapped_edges) + + # extend the first and last wrapped edge so that they cross, then trim + # both at the crossing + first_edge = wrapped_edges[0]._extend_spline(True, self, + extension_factor) + last_edge = wrapped_edges[-1]._extend_spline(False, self, + extension_factor) + params = w.ExtremaEdgeParams(_topo(first_edge), _topo(last_edge)) + if params is None: + raise RuntimeError('Extended first/last edges do not intersect; ' + 'increase extension.') + param_first, param_last = params[0], params[1] + + u_start_first = first_edge.param_at(0) + u_end_first = first_edge.param_at(1) + new_start = (param_first - u_start_first) / (u_end_first - u_start_first) + trimmed_first = first_edge.trim(new_start, 1.0) + + u_start_last = last_edge.param_at(0) + u_end_last = last_edge.param_at(1) + new_end = (param_last - u_start_last) / (u_end_last - u_start_last) + trimmed_last = last_edge.trim(0.0, new_end) + + wrapped_edges[0] = trimmed_first + wrapped_edges[-1] = trimmed_last + + closing_error = (trimmed_first.position_at(0) - + trimmed_last.position_at(1)).length + wire = w.WireFromEdgesFixed([_topo(e) for e in wrapped_edges], + 2 * closing_error) + return Curve(wire) + + def _wrap_face(self, planar_face, surface_loc, tolerance=0.001, + extension_factor=0.1): + """Wrap a flat face onto this surface (build123d Face._wrap_face).""" + wrapped_perimeter = self._wrap_wire(planar_face.outer_wire(), + surface_loc, tolerance, + extension_factor) + wrapped_holes = [self._wrap_wire(iw, surface_loc, tolerance, + extension_factor) + for iw in planar_face.inner_wires()] + wrapped_face = Face.make_surface( + wrapped_perimeter, surface_points=[surface_loc.position], + interior_wires=wrapped_holes) + # flip the wrapped face if it ended up facing away from the surface + surface_normal = surface_loc.z_axis.direction + wrapped_normal = wrapped_face.normal_at(surface_loc.position) + if surface_normal.dot(wrapped_normal) < 0: + wrapped_face = -wrapped_face + return wrapped_face + + def wrap(self, planar_shape, surface_loc, tolerance=0.001, + extension_factor=0.1): + """Wrap a flat Edge/Wire/Face (drawn on Plane.XY) onto this surface + starting at surface_loc (build123d Face.wrap).""" + if isinstance(planar_shape, Edge): + return self._wrap_edge(planar_shape, surface_loc, True, tolerance) + if isinstance(planar_shape, (Face, Sketch)): + return self._wrap_face(planar_shape, surface_loc, tolerance, + extension_factor) + if isinstance(planar_shape, Curve): + return self._wrap_wire(planar_shape, surface_loc, tolerance, + extension_factor) + raise TypeError('planar_shape must be an Edge, Wire or Face') + + def wrap_faces(self, faces, path, start=0.0): + """Wrap flat faces onto this surface, spaced along a path that lies on + it: each face keeps its relative X position, mapped to arc length + along the path (build123d Shape.wrap_faces).""" + path_length = path.length + face_list = [f for f in faces] + first_face_min_x = face_list[0].bounding_box().min[0] + wrapped = ShapeList() + for face in face_list: + bbox = face.bounding_box() + face_center_x = (bbox.min[0] + bbox.max[0]) / 2.0 + delta_x = face_center_x - first_face_min_x + relative_position = start + delta_x / path_length + path_position = path.position_at(relative_position) + surface_location = Location(Plane( + origin=path_position, + x_dir=path.tangent_at(relative_position), + z_dir=self.normal_at(path_position))) + face.position = face.position - Vector(delta_x, 0, 0) + wrapped.append(self._wrap_face(face, surface_location)) + return wrapped + + @classmethod + def make_surface_from_array_of_points(cls, points, tol=1e-2, + smoothing=None, min_deg=1, + max_deg=3): + """Approximate a BSpline surface through a 2D grid of points — + upstream's exact GeomAPI_PointsToBSplineSurface 2-D least-squares + fit (outer index = V, inner = U).""" + pts = [[list(_v3(p)) for p in row] for row in points] + # [] = no smoothing (None does not survive CacheOp's JSON hashing) + smooth = list(smoothing) if smoothing is not None else [] + topo = w.SurfaceFromPoints(pts, tol, min_deg, max_deg, smooth) + if topo is None: + raise ValueError('B-spline surface approximation failed') + return cls(topo) + + def __neg__(self): + """The same face with reversed orientation (build123d -face).""" + return Face(w.ReverseFace(self.topo, True)) + + @classmethod + def extrude(cls, obj, direction): + """Extrude an Edge into a Face (build123d Face.extrude). Extruding a + one-edge WIRE gives a shell here, so unwrap it to the single face + build123d would have produced.""" + d = Vector(direction) + topo = w.Extrude(_topo(obj), [d.X, d.Y, d.Z], True) + return cls(w.AsSingleFace(topo, True)) + + @classmethod + def revolve(cls, profile, angle=360, axis=None): + """Revolve an Edge/Wire profile into a Face of revolution + (build123d Face.revolve).""" + if axis is None: + axis = Axis.Z + o = tuple(axis.position) + d = list(axis.direction) + topo = _topo(profile) + shift = (abs(o[0]) > _TOL or abs(o[1]) > _TOL or abs(o[2]) > _TOL) + if shift: + topo = w.Translate([-o[0], -o[1], -o[2]], topo) + topo = w.Revolve(topo, angle, d) + if shift: + topo = w.Translate([o[0], o[1], o[2]], topo) + return cls(topo) + + @classmethod + def make_rect(cls, width, height, plane=None): + """A width x height rectangle face on the given plane (Plane.XY).""" + topo = w.Polygon([[-width / 2.0, -height / 2.0, 0], + [width / 2.0, -height / 2.0, 0], + [width / 2.0, height / 2.0, 0], + [-width / 2.0, height / 2.0, 0]]) + face = cls(topo) + if plane is not None: + face = cls((plane.location * face).topo) + return face + + +# lite re-wraps a TRANSFORMED Face as a Sketch (the algebra-mode 2D +# convention, see _wrap_like), so a Sketch very often really is one face: +# share Face's surface-geometry methods with it. Explicit assignment rather +# than making Sketch a Face subclass, because several call sites dispatch on +# isinstance(x, Face) (Face(wire) promotion, revolve, thicken, ...). +Sketch._surface_args = Face._surface_args +Sketch._surface_params = Face._surface_params +Sketch.normal_at = Face.normal_at +Sketch.location_at = Face.location_at +Sketch.outer_wire = Face.outer_wire +Sketch.inner_wires = Face.inner_wires +Sketch._intersect_surface_normal = Face._intersect_surface_normal +Sketch._wrap_edge = Face._wrap_edge +Sketch._wrap_wire = Face._wrap_wire +Sketch._wrap_face = Face._wrap_face +Sketch.wrap = Face.wrap +Sketch.wrap_faces = Face.wrap_faces + + +class Shell(Shape): + """A shell — only the build123d forms the examples use: Shell(faces) + collects faces (Solid(Shell(faces)) then sews them into a closed + solid via BRepBuilderAPI_Sewing + ShapeFix_Solid), Shell(shape) + adopts the shape's faces.""" + + _face_shapes = None # default for instances made via _wrap_like + + @classmethod + def extrude(cls, obj, direction): + """A wire/edge swept along a direction into an open SHELL + (build123d Shell.extrude).""" + d = _v3(direction) + topo = w.Extrude(_topo(obj), [d[0], d[1], d[2]]) + shell = cls.__new__(cls) + Shape.__init__(shell, topo) + shell._face_shapes = None + return shell + + def __init__(self, faces=None): + if faces is None: + Shape.__init__(self, None) + self._face_shapes = [] + return + if isinstance(faces, Shape): + Shape.__init__(self, faces.topo) + self._face_shapes = list(faces.faces()) + return + fl = [f for f in _tolist(faces)] + self._face_shapes = fl + if len(fl) == 0: + topo = None + elif len(fl) == 1: + topo = _topo(fl[0]) + else: + topo = w.MakeCompound([_topo(f) for f in fl]) + Shape.__init__(self, topo) + + +class Vertex(Shape): + def __init__(self, topo=None, *args, parent=None): + # Vertex(Vector) / Vertex(x, y, z) / Vertex((x, y, z)) like build123d + if topo is None or isinstance(topo, (int, float)) or \ + isinstance(topo, (Vector, tuple, list)): + if topo is None: + pt = (0.0, 0.0, 0.0) + elif isinstance(topo, (int, float)): + pt = (float(topo),) + tuple(float(a) for a in args) + \ + (0.0, 0.0) + pt = pt[:3] + else: + pt = tuple(Vector(topo)) + topo = w.PointVertex(list(pt)) + Shape.__init__(self, topo) + self.parent = parent + p = w._vertexPoint(topo) + self.X, self.Y, self.Z = p[0], p[1], p[2] + + def center(self, center_of=CenterOf.GEOMETRY): + return Vector(self.X, self.Y, self.Z) + + def to_tuple(self): + return (self.X, self.Y, self.Z) + + def __iter__(self): + return iter((self.X, self.Y, self.Z)) + + +class BoundBox: + def __init__(self, six): + if six is None: + six = [0.0] * 6 + self.min = Vector(six[0], six[1], six[2]) + self.max = Vector(six[3], six[4], six[5]) + + @property + def size(self): + return self.max - self.min + + def center(self): + return (self.min + self.max) * 0.5 + + @property + def diagonal(self): + return (self.max - self.min).length + + def __repr__(self): + return 'BoundBox(' + repr(tuple(self.min)) + ', ' + repr(tuple(self.max)) + ')' + + +Plane.XY = Plane((0, 0, 0), (1, 0, 0), (0, 0, 1)) +Plane.XZ = Plane((0, 0, 0), (1, 0, 0), (0, -1, 0)) +Plane.YZ = Plane((0, 0, 0), (0, 1, 0), (1, 0, 0)) +Plane.YX = Plane((0, 0, 0), (0, 1, 0), (0, 0, -1)) +Plane.ZX = Plane((0, 0, 0), (0, 0, 1), (0, 1, 0)) +Plane.ZY = Plane((0, 0, 0), (0, 0, 1), (-1, 0, 0)) +Plane.front = Plane.XZ +Plane.top = Plane.XY + + +# ------------------------------------------------------------ ShapeList --- + +def _entity_center(s): + if isinstance(s, Edge): + return w._edgeMidpoint(s.topo) + if isinstance(s, Face): + return w._faceCentroid(s.topo) + if isinstance(s, Vertex): + return (s.X, s.Y, s.Z) + return tuple(w.CenterOfMass(_topo(s))) + + +def _axis_value(s, axis): + c = _entity_center(s) + o = tuple(axis.position) + d = tuple(axis.direction) + return ((c[0] - o[0]) * d[0] + (c[1] - o[1]) * d[1] + (c[2] - o[2]) * d[2]) + + +def _entity_radius(s): + """Radius of a circular edge from three sampled points (no gp_Circ + binding in the WASM build).""" + if not isinstance(s, Edge): + raise TypeError('SortBy.RADIUS only supports edges in build123d-lite') + p0 = _v3(w._edgePointAt(s.topo, 0.0)) + p1 = _v3(w._edgePointAt(s.topo, 1.0 / 3.0)) + p2 = _v3(w._edgePointAt(s.topo, 2.0 / 3.0)) + u = tuple(p1[k] - p0[k] for k in range(3)) + v = tuple(p2[k] - p0[k] for k in range(3)) + a = math.sqrt(sum((p1[k] - p2[k]) ** 2 for k in range(3))) + b = math.sqrt(sum(v[k] ** 2 for k in range(3))) + c = math.sqrt(sum(u[k] ** 2 for k in range(3))) + cr = (u[1] * v[2] - u[2] * v[1], u[2] * v[0] - u[0] * v[2], + u[0] * v[1] - u[1] * v[0]) + area2 = math.sqrt(sum(cr[k] ** 2 for k in range(3))) + if area2 < 1e-12: + return float('inf') # straight line + return a * b * c / (2.0 * area2) + + +_TOL_DIGITS = 6 # abs(log10(build123d's TOLERANCE)) + + +def _canonical_sort_key(shape): + """Deterministic, purely geometric ordering key (build123d's + _canonical_sort_key): the shape's VERTEX positions, sorted and rounded to + TOL_DIGITS - no bounding box, no curve evaluation, so it is cheap enough to + compute inside a sort. Used to break ties in + ShapeList.sort_by(tie_break=True), where the only alternative is the order + the objects arrived in (the CAD kernel's traversal order). + + Vector members are handled explicitly rather than by catching + AttributeError, so an API change surfaces as a failure instead of as a + silently disabled tie break.""" + if isinstance(shape, Vector): # ShapeList also holds plain Vectors + return tuple(round(c, _TOL_DIGITS) for c in (shape.X, shape.Y, shape.Z)) + if shape.topo is None: + return () + points = sorted([(round(v.X, _TOL_DIGITS), round(v.Y, _TOL_DIGITS), + round(v.Z, _TOL_DIGITS)) for v in shape.vertices()]) + return tuple(c for point in points for c in point) + + +def _canonical_center_key(shape): + """Second stage tie break, for shapes whose vertices coincide (two arcs + spanning the same end points, say). Only reached when the vertex key above + leaves a tie (build123d's _canonical_center_key).""" + if isinstance(shape, Vector): + return () + try: + center = shape.center() + except Exception: + return () + return tuple(round(c, _TOL_DIGITS) + for c in (center.X, center.Y, center.Z)) + + +def _sort_key_fn(key): + if isinstance(key, Axis): + return lambda s: _axis_value(s, key) + if isinstance(key, Curve) and getattr(key, 'topo', None) is not None: + # sort_by(): the parameter, along that 1-D shape, of the + # point closest to each object's centre (build123d's + # u_of_closest_center -> closest_points + param_at_point) + def along(s): + pnt = key.closest_points(s.center())[0] + return key.param_at_point(pnt) + return along + if key == SortBy.LENGTH: + return lambda s: s.length + if key == SortBy.AREA: + return lambda s: s.area + if key == SortBy.VOLUME: + return lambda s: s.volume + if key == SortBy.RADIUS: + return _entity_radius + if key == SortBy.DISTANCE: + return lambda s: Vector(_entity_center(s)).length + if callable(key): + return key + if isinstance(key, property): + # sort_by(Face.area) / group_by(Edge.length): a class PROPERTY object + # is called on each shape (build123d's documented selector form) + return lambda s: key.fget(s) + raise TypeError('unsupported sort/group key: ' + repr(key)) + + +def topo_distance_to(other): + """A sort_by/group_by key function giving the TOPOLOGICAL distance to the + reference shape(s) (build123d's topo_distance_to): 0 for the references + themselves, 1 for their direct neighbours, and so on, measured over the + full topology of their shared parent. Adjacency is sharing a lower-order + sub-shape, exactly as upstream defines it — Faces via an Edge, Edges/Wires + via a Vertex, Shells/Solids via a Face — with the sub-shapes identified + geometrically (lite re-wraps every shape, so there is no TopoDS identity to + hash).""" + sources = [other] if isinstance(other, Shape) else list(other) + if not sources: + raise ValueError('Cannot measure topological distance to an empty ' + 'object') + kind_lut = [(Vertex, 'vertex'), (Face, 'face'), (Edge, 'edge'), + (Shell, 'shell'), (Part, 'solid'), (Curve, 'wire')] + peer_kind = None + for cls, name in kind_lut: + if isinstance(sources[0], cls): + peer_kind = name + break + if peer_kind is None: + raise ValueError('Topological distance is not supported for ' + + type(sources[0]).__name__) + for s in sources: + if not isinstance(s, type(sources[0])): + raise ValueError('Topological distance requires shapes of the ' + 'same type') + parent = getattr(sources[0], 'parent', None) + if parent is None or parent.topo is None: + raise ValueError('Topological distance requires shapes with a ' + 'topo_parent') + connector = {'vertex': 'edge', 'edge': 'vertex', 'wire': 'vertex', + 'face': 'edge', 'shell': 'face', 'solid': 'face'}[peer_kind] + peers = {'vertex': parent.vertices, 'edge': parent.edges, + 'wire': parent.wires, 'face': parent.faces, + 'shell': parent.faces, 'solid': parent.solids}[peer_kind]() + + def sub_keys(shape): + if connector == 'vertex': + return [_shape_key(v, 'vertex') for v in shape.vertices()] + if connector == 'edge': + return [_shape_key(e, 'edge') for e in shape.edges()] + return [_shape_key(f, 'face') for f in shape.faces()] + peer_keys = [_shape_key(p, peer_kind) for p in peers] + peer_subs = [sub_keys(p) for p in peers] + # adjacency: peers that share at least one connector sub-shape + by_sub = {} + for i, subs in enumerate(peer_subs): + for key in subs: + by_sub.setdefault(key, []).append(i) + neighbours = [set() for _ in peers] + for key in by_sub: + group = by_sub[key] + for i in group: + for j in group: + if i != j: + neighbours[i].add(j) + # breadth-first search out of the reference shapes + distances = {} + frontier = [] + for s in sources: + key = _shape_key(s, peer_kind) + if key in peer_keys: + i = peer_keys.index(key) + if i not in distances: + distances[i] = 0 + frontier.append(i) + step = 0 + while frontier: + step += 1 + nxt = [] + for i in frontier: + for j in neighbours[i]: + if j not in distances: + distances[j] = step + nxt.append(j) + frontier = nxt + + def key_f(shape): + key = _shape_key(shape, peer_kind) + if key not in peer_keys: + return float('inf') + return distances.get(peer_keys.index(key), float('inf')) + return key_f + + +def _group_key_fn(key, tol_digits=6): + """group_by's key function: the sort key ROUNDED to tol_digits, with + non-numeric keys passed through unchanged (build123d's group_by wraps + every branch in try: round(val) / except TypeError: val).""" + fn = _sort_key_fn(key) + + def rounded(s): + val = fn(s) + if isinstance(val, bool) or not isinstance(val, (int, float)): + return val + return round(val, tol_digits) + return rounded + + +def _is_parallel(s, axis, tolerance=1e-5): + """Parallelism like build123d's Axis.is_parallel: tolerance is the + ANGULAR tolerance in radians (gp_Ax1::IsParallel).""" + d = Vector(axis.direction) + if isinstance(s, Edge): + ed = w._edgeDirection(s.topo) + # JS null crosses Brython as NullType (not None) — test truthiness + if not ed: + return False + v = Vector(tuple(ed)).normalized() + elif isinstance(s, Face): + v = Vector(tuple(w._faceNormal(s.topo))) + else: + return False + dot = min(1.0, abs(v.dot(d))) + # build123d's angular_tolerance is in DEGREES (geometry.py multiplies + # by pi/180); keep the old 1e-4 dot slack as a floor so near-parallel + # edges from wasm boolean noise still match + return math.acos(dot) <= math.radians(tolerance) or dot > (1.0 - 1e-4) + + +class ShapeList(list): + def __add__(self, other): + # plain list.__add__ would decay to a list, losing the selectors + # (built element-wise: Brython's unbound list.__add__ returns + # NotImplemented for subclass receivers) + out = ShapeList(self) + out.extend(other) + return out + + def __radd__(self, other): + out = ShapeList(other) + out.extend(self) + return out + + def filter_by(self, f, reverse=False, tolerance=1e-5): + if isinstance(f, Axis): + pred = lambda s: _is_parallel(s, f, tolerance) + elif isinstance(f, tuple): # a GeomType member + def pred(s): + try: + return s.geom_type == f + except Exception: + return False + elif hasattr(f, 'z_dir') and hasattr(f, 'origin'): + # filter_by(Plane): shapes lying IN that plane (build123d's + # Plane filter - contains() on every vertex) + def pred(s): + try: + for v in s.vertices(): + d = (Vector(v.to_tuple()) - Vector(f.origin)).dot( + Vector(f.z_dir)) + if abs(d) > tolerance: + return False + return True + except Exception: + return False + elif callable(f): + pred = f + elif isinstance(f, property): + # filter_by(Face.is_planar): a class PROPERTY used as a predicate + pred = lambda s: bool(f.fget(s)) + else: + raise TypeError('filter_by: unsupported filter ' + repr(f)) + out = ShapeList([s for s in self if bool(pred(s)) != bool(reverse)]) + return out + + def filter_by_position(self, axis, minimum, maximum, inclusive=(True, True)): + out = ShapeList() + for s in self: + v = _axis_value(s, axis) + lo = v >= minimum if inclusive[0] else v > minimum + hi = v <= maximum if inclusive[1] else v < maximum + if lo and hi: + out.append(s) + # build123d returns the survivors SORTED along the same axis + return out.sort_by(axis) + + def sort_by(self, key=Axis.Z, reverse=False, tie_break=False): + """Sort by the given criterion (build123d ShapeList.sort_by). + + tie_break=False (the default) keeps Python's stable sort exactly, so + ties carry the incoming order - which is itself a useful contract for + CHAINED sorts (sort_by(SortBy.RADIUS).sort_by(Axis.Z) keeps the radius + order inside each equal-Z group). COMPROMISE(traversal-order): that + incoming order is the CAD kernel's traversal of lite's construction, + so a script that resolves a COMPLETE tie this way (or keeps whichever + of two symmetric results came last) can land on the other candidate + than it does on OCP 7.x - which is exactly the ambiguity the canonical + rule below exists to remove, opt-in on both sides. tie_break=True instead resolves ties + with _canonical_sort_key, so identical geometry always sorts identically + rather than in the kernel's traversal order (see Curve.canonical). Like + upstream, the geometric key is computed only for objects inside a tie + group, and the center-based second stage only where the vertex key ties + too.""" + fn = _sort_key_fn(key) + decorated = [(fn(s), s) for s in self] + if tie_break: + # keys are computed only for the objects that actually tie, and the + # cheap one (vertex positions) almost always settles it + for tie_break_key in (_canonical_sort_key, _canonical_center_key): + try: + tied = {} + for k, _ in decorated: + tied[k] = tied.get(k, 0) + 1 + except TypeError: # unhashable keys from a custom callable + break + if all([count == 1 for count in tied.values()]): + break + decorated = [((k, tie_break_key(s) if tied[k] > 1 else ()), s) + for k, s in decorated] + decorated = sorted(decorated, key=lambda pair: pair[0], reverse=reverse) + return ShapeList([s for _, s in decorated]) + + def sort_by_distance(self, other, reverse=False): + """Sort by the MINIMAL distance between each shape and other + (build123d ShapeList.sort_by_distance -> Shape.distance_to).""" + return ShapeList(sorted(self, key=lambda s: s.distance_to(other), + reverse=reverse)) + + def wires(self): + out = ShapeList() + for s in self: + out.extend(s.wires()) + return out + + def group_by(self, key=Axis.Z, reverse=False, tol_digits=6): + fn = _group_key_fn(key, tol_digits) + ordered = sorted(self, key=fn, reverse=reverse) + groups = [] + keys = [] + last = None + for s in ordered: + v = fn(s) + if last is None or v != last: + groups.append(ShapeList()) + keys.append(v) + last = v + groups[-1].append(s) + return GroupBy(groups, keys, fn) + + def __sub__(self, other): + removed = list(other) + return ShapeList([s for s in self + if not any(s is o or (s.topo is not None and + s.topo is o.topo) + for o in removed)]) + + @property + def first(self): + return self[0] + + @property + def last(self): + return self[-1] + + def __getitem__(self, i): + r = list.__getitem__(self, i) + if isinstance(i, slice): + return ShapeList(r) + return r + + def __gt__(self, key): + return self.sort_by(key) + + def __lt__(self, key): + return self.sort_by(key, reverse=True) + + def edges(self): + out = ShapeList() + for s in self: + if isinstance(s, Edge): + out.append(s) + else: + out.extend(s.edges()) + return out + + def faces(self): + out = ShapeList() + for s in self: + if isinstance(s, Face): + out.append(s) + else: + out.extend(s.faces()) + return out + + def vertices(self): + out = ShapeList() + for s in self: + out.extend(s.vertices()) + return out + + def solids(self): + out = ShapeList() + for s in self: + out.extend(s.solids()) + return out + + def _single(self, kind, items): + if len(items) != 1: + raise ValueError('Expected exactly one ' + kind + ', found ' + + str(len(items))) + return items[0] + + def edge(self): + return self._single('edge', self.edges()) + + def face(self): + return self._single('face', self.faces()) + + def wire(self): + return self._single('wire', self.wires()) + + def vertex(self): + return self._single('vertex', self.vertices()) + + def solid(self): + return self._single('solid', self.solids()) + + +class GroupBy: + """The result of ShapeList.group_by: groups reachable by INDEX or by KEY + (build123d's GroupBy — group(key) is what the topology-selection docs + use, e.g. length_groups.group(6)).""" + + def __init__(self, groups, keys=None, key_f=None): + self.groups = groups + self.key_to_group_index = [(k, i) for i, k in enumerate(keys or [])] + self.key_f = key_f + + def __getitem__(self, i): + return self.groups[i] + + def __iter__(self): + return iter(self.groups) + + def __len__(self): + return len(self.groups) + + def group(self, key): + """The group whose key equals key (build123d GroupBy.group).""" + for k, i in self.key_to_group_index: + if key == k: + return self.groups[i] + raise KeyError(key) + + def group_for(self, shape): + """The group the given shape belongs to (build123d + GroupBy.group_for).""" + if self.key_f is None: + raise KeyError(shape) + return self.group(self.key_f(shape)) + + +# ------------------------------------------------------------- builders --- + +_builders = [] +_loc_stack = [] + + +def _reset_state(): + """Clear the builder/location context stacks. Called by the worker + before every user evaluation: the build123d module instance persists + across runs, so a previous run that died inside a with-block (e.g. a + JS-level abort that skipped Python unwinding) must not leak its stack + into the next run.""" + del _builders[:] + del _loc_stack[:] + + +def _active_builder(cls=None): + if not _builders: + return None + b = _builders[-1] + if cls is not None and not isinstance(b, cls): + return None + return b + + +def _ctx_locations(): + """The active local locations, in the CURRENT builder's scope only. + + build123d 0.11.1 gives every builder a fresh location context on entry + (build_common Builder.__enter__ sets local_locations = LocationList( + [Location()])), so a Locations context wrapping a builder does NOT + replicate what the builder constructs - the builder always builds locally. + Truncating at the builder's own stack depth reproduces that exactly; before + this, an enclosing GridLocations fanned out objects created inside a nested + BuildSketch (key_concepts_builder's documented "Locations around a builder" + case built four rectangles instead of one).""" + builder = _active_builder() + start = builder._loc_depth if (builder is not None and + builder._loc_depth is not None) else 0 + locs = [Location()] + for ctx in _loc_stack[start:]: + locs = [a * b for a in locs for b in ctx.locations] + return locs + + +def _sub_shapes_of(shape, kind): + """The shape's vertices/edges/faces/solids for a Select bookkeeping kind.""" + if shape is None or getattr(shape, 'topo', None) is None: + return [] + if kind == 'vertex': + return list(shape.vertices()) + if kind == 'edge': + return list(shape.edges()) + if kind == 'face': + return list(shape.faces()) + return list(shape.solids()) + + +def _shape_key(shape, kind): + """Geometric identity of a sub-shape, for 'post - pre' set arithmetic. + + build123d compares TopoDS identity (which survives a boolean for untouched + sub-shapes); lite rewraps every shape, so identity is taken from geometry + instead: position for a vertex, midpoint+length for an edge, + center+area for a face, center+volume for a solid.""" + topo = shape.topo + if kind == 'vertex': + p = w._vertexPoint(topo) + return (round(p[0], 6), round(p[1], 6), round(p[2], 6)) + if kind == 'edge': + c = w._edgeMidpoint(topo) + return (round(c[0], 6), round(c[1], 6), round(c[2], 6), + round(w._edgeLength(topo), 6)) + if kind == 'face' or kind == 'shell': + # CenterOfMass is a VOLUME integral in this build and degenerates to + # the bounding-box corner on an open shape, so every face of a solid + # got the same key — the face centroid is the honest identity + c = tuple(w._faceCentroid(topo)) if kind == 'face' else \ + tuple(w.CenterOfMass(topo)) + return (round(c[0], 6), round(c[1], 6), round(c[2], 6), + round(w.SurfaceArea(topo), 6)) + c = tuple(w.CenterOfMass(topo)) + return (round(c[0], 6), round(c[1], 6), round(c[2], 6), + round(w.SolidsVolume(topo), 6)) + + +def new_edges(*objects, combined=None): + """build123d's new_edges(): the edges of 'combined' that none of 'objects' + contributed - i.e. the edges the combining operation created + (topology/utils.py). Used by 'builder.edges(Select.NEW)'.""" + if combined is None: + raise ValueError('new_edges() requires combined=') + topos = [] + for o in objects: + if isinstance(o, Builder): + o = o._obj + if o is not None and getattr(o, 'topo', None) is not None: + topos.append(o.topo) + if isinstance(combined, Builder): + combined = combined._obj + if combined is None or combined.topo is None: + return ShapeList() + # Return the CORRESPONDING edges of 'combined' (same parent + per-shape + # index), so the result can be handed straight to fillet()/chamfer() the + # way upstream's maker_coin does. The cut result is geometry only: it + # carries no index, and its edges have fresh TopoDS handles. + own = combined.edges() + keyed = {} + for e in own: + c = w._edgeMidpoint(e.topo) + keyed[(round(c[0], 6), round(c[1], 6), round(c[2], 6), + round(w._edgeLength(e.topo), 6))] = e + out = ShapeList() + for raw in w.NewEdges(combined.topo, topos): + c = w._edgeMidpoint(raw) + key = (round(c[0], 6), round(c[1], 6), round(c[2], 6), + round(w._edgeLength(raw), 6)) + match = keyed.get(key) + # COMPROMISE(new-edges-partial): an edge that is only PARTLY new comes + # back as a trimmed piece with no counterpart in 'combined'; it is + # returned as bare geometry (usable for measuring, not for fillet()). + out.append(match if match is not None else Edge(raw, parent=combined)) + return out + + +def _context_selector(name): + """build123d's module-level selector getters (build_common's + __gen_context_component_getter): 'edges()' inside a builder context is + '.edges()'.""" + def getter(select=Select.ALL): + builder = _active_builder() + if builder is None: + raise RuntimeError(name + '() requires a Builder context to be in ' + 'scope') + return getattr(builder, name)(select) + return getter + + +def _align_sketch_faces(obj): + """build123d's BuildSketch._add_to_context step 'Align sketch planar faces + with Plane.XY': a face that is NOT coplanar with Plane.XY is expressed in + its own plane's local frame and dropped onto z = 0, and every face is then + oriented +Z (an up-side-down face is negated). Without the orientation + half, a MIRRORED face never fuses with the face it was mirrored from + (coplanar faces with opposite normals are not the same domain) and a + BuildSketch mirror leaves two half faces behind.""" + faces = obj.faces() + if not faces: + return obj + aligned = [] + changed = False + for face in faces: + normal = face.normal_at() + coplanar = abs(normal.Z) > 1.0 - _TOL_1E6 and \ + abs(face.center().Z) <= _TOL_1E6 + if not coplanar: + try: + plane = Plane(origin=(0, 0, 0), x_dir=(1, 0, 0), z_dir=normal) + except Exception: + plane = Plane(origin=(0, 0, 0), z_dir=normal) + # take the transformed FACE back out of the result (a transformed + # shape is a generic TopoDS_Shape, which the face helpers reject) + face = (plane.location.inverse() * face).faces()[0] + face = (Pos(0, 0, -face.center().Z) * face).faces()[0] + changed = True + if face.normal_at().Z <= 0: + face = -face + changed = True + aligned.append(face) + if not changed: + return obj + topos = [_topo(f) for f in aligned] + return Sketch(topos[0] if len(topos) == 1 else w.MakeCompound(topos)) + + +def _combine(builder, obj, mode, warn_cls=None): + """Merge obj into builder._obj per mode. Returns the CREATED object.""" + if builder is None or mode == Mode.PRIVATE: + return obj + if isinstance(builder, BuildSketch) and obj is not None and \ + getattr(obj, 'topo', None) is not None: + obj = _align_sketch_faces(obj) + before = builder._obj + pre = builder._sub_shape_lists() + if mode == Mode.REPLACE or builder._obj is None or builder._obj.topo is None: + if mode == Mode.SUBTRACT: + raise ValueError('Mode.SUBTRACT with nothing to subtract from') + if mode == Mode.INTERSECT: + raise ValueError('Mode.INTERSECT with nothing to intersect') + builder._obj = builder._wrap(obj.topo) + elif mode == Mode.ADD: + builder._obj = builder._wrap((builder._obj + obj).topo) + elif mode == Mode.SUBTRACT: + builder._obj = builder._wrap((builder._obj - obj).topo) + elif mode == Mode.INTERSECT: + builder._obj = builder._wrap((builder._obj & obj).topo) + else: + raise ValueError('unsupported mode ' + repr(mode)) + builder._record_lasts(pre, obj, before) + return obj + + +class Builder: + _shape_cls = Part + _tag = 'part' + + def __init__(self, *workplanes, mode=Mode.ADD): + planes = [] + for wp in workplanes: + if isinstance(wp, Plane): + planes.append(wp) + elif isinstance(wp, Location): + planes.append(Plane(wp)) + elif isinstance(wp, Face): + planes.append(Plane(wp)) + else: + raise TypeError('workplane must be a Plane/Location/Face') + self.workplanes = planes or [Plane.XY] + self.mode = mode + self._obj = None + self._loc_depth = None + self.pending_faces = [] # [Face/Sketch] for BuildPart + self.pending_face_planes = [] # parallel [Plane] (build123d layout) + self.joints = {} # joints created with to_part=None + self.pending_edge_specs = [] # segment specs for BuildSketch + + def _wrap(self, topo): + return self._shape_cls(topo) + + def _lite_copy(self): + """copy.copy() — a SHALLOW copy of the builder, exactly like + upstream's: the copy keeps a reference to the result object as it is + NOW, and every later operation rebinds the original's _obj, so the copy + is the snapshot the docs use it as (before_fillet = copy(part)).""" + clone = self.__class__.__new__(self.__class__) + for key in list(self.__dict__.keys()): + clone.__dict__[key] = self.__dict__[key] + return clone + + def __enter__(self): + self._loc_depth = len(_loc_stack) + # build123d only transfers a builder's result to the enclosing + # builder when both with-statements share a stack frame (an inner + # builder inside e.g. a BaseSketchObject subclass __init__ must NOT + # auto-combine — the object machinery adds it instead) + self._python_frame = w._pythonCallerFrame() + enclosing = _builders[-1] if _builders else None + if enclosing is not None and \ + enclosing._python_frame is self._python_frame: + self._parent = enclosing + else: + self._parent = None + _builders.append(self) + return self + + def __exit__(self, exc_type, exc, tb): + del _loc_stack[self._loc_depth:] + _builders.pop() + if exc_type is not None: + return False + self._finalize(self._parent) + # transfer joints created in this context onto the result shape + # (build123d BuildPart._exit_extras) + if self.joints and self._obj is not None: + self._obj.joints = self.joints + for j in self.joints.values(): + j.parent = self._obj + return False + + def _finalize(self, parent): + if parent is not None and self._obj is not None and self._obj.topo is not None: + _combine(parent, self._obj, self.mode) + + @property + def location(self): + """The result shape's location (build123d BuildPart.location).""" + return self._obj.location if self._obj is not None else Location() + + def locate(self, loc): + if self._obj is None: + raise ValueError('builder has no result to locate') + return self._obj.locate(loc) + + # ---------------------------------------------------------------- # + # Select.LAST / Select.NEW bookkeeping (build123d Builder.lasts) + # + # Upstream records, per operation, 'post - pre' over the builder's + # sub-shapes — except for the builder's OWN shape type, which is just the + # objects that were combined in (build_common._add_to_context). The set + # difference relies on TopoDS identity surviving a boolean; lite wraps + # every shape in a fresh handle, so the difference is taken on GEOMETRY + # (vertex position / edge midpoint+length / face center+area / solid + # center+volume, rounded to 6 digits), which answers the same question. + # ---------------------------------------------------------------- # + _SELECT_KINDS = ('vertex', 'edge', 'face', 'solid') + # the shape type each builder itself produces (build123d Builder._shape) + _core_kind = 'solid' + + def _sub_shape_lists(self): + """Current vertices/edges/faces/solids. Topology traversal only — the + measurements that turn these into keys are deferred until a + Select.LAST/NEW query actually asks for them.""" + o = self._obj + if o is None or o.topo is None: + return dict((k, []) for k in Builder._SELECT_KINDS) + return {'vertex': list(o.vertices()), 'edge': list(o.edges()), + 'face': list(o.faces()), 'solid': list(o.solids())} + + def _record_lasts(self, pre, created, before): + self._lasts_pre = pre + self._lasts_post = self._sub_shape_lists() + self._lasts_created = created + self._lasts_before = before + self._lasts_cache = {} + + def _lasts(self, kind): + if not hasattr(self, '_lasts_post'): + return ShapeList() + if kind in self._lasts_cache: + return ShapeList(self._lasts_cache[kind]) + if kind == self._core_kind: + created = self._lasts_created + out = ShapeList(_sub_shapes_of(created, kind)) if created is not None \ + else ShapeList() + else: + seen = set(_shape_key(s, kind) for s in self._lasts_pre[kind]) + out = ShapeList([s for s in self._lasts_post[kind] + if _shape_key(s, kind) not in seen]) + self._lasts_cache[kind] = list(out) + return out + + @property + def new_edges(self): + """Edges that the last operation CREATED (build123d Builder.new_edges): + the combined result's edges cut by the operands' edges.""" + if self._obj is None or not hasattr(self, '_lasts_created'): + return ShapeList() + originals = [] + if self._lasts_before is not None and self._lasts_before.topo is not None: + originals.append(self._lasts_before) + if self._lasts_created is not None: + originals.append(self._lasts_created) + return new_edges(*originals, combined=self._obj) + + def _selection_shape(self): + """The shape the selectors read. BuildLine overrides it so that + mid-context selectors (side_line.vertices() inside the with-block, which + the sheet-metal examples fillet) see the line built SO FAR - _obj only + exists after __exit__.""" + return self._obj + + # selector passthroughs (builder.edges() etc.) + def edges(self, select=Select.ALL): + if select == Select.LAST: + return self._lasts('edge') + if select == Select.NEW: + return self.new_edges + shape = self._selection_shape() + return shape.edges() if shape is not None else ShapeList() + + def wires(self, select=Select.ALL): + if select == Select.LAST: + return ShapeList(edges_to_wires(self._lasts('edge'))) + if select == Select.NEW: + raise ValueError('Select.NEW only valid for edges') + shape = self._selection_shape() + return shape.wires() if shape is not None else ShapeList() + + def face(self): + return self._obj.face() if self._obj else None + + def wire(self): + return self._obj.wire() if self._obj else None + + def edge(self): + return self._obj.edge() if self._obj else None + + def faces(self, select=Select.ALL): + if select == Select.LAST: + return self._lasts('face') + if select == Select.NEW: + raise ValueError('Select.NEW only valid for edges') + shape = self._selection_shape() + return shape.faces() if shape is not None else ShapeList() + + def vertices(self, select=Select.ALL): + if select == Select.LAST: + return self._lasts('vertex') + if select == Select.NEW: + raise ValueError('Select.NEW only valid for edges') + shape = self._selection_shape() + return shape.vertices() if shape is not None else ShapeList() + + def solids(self, select=Select.ALL): + if select == Select.LAST: + return self._lasts('solid') + if select == Select.NEW: + raise ValueError('Select.NEW only valid for edges') + shape = self._selection_shape() + return shape.solids() if shape is not None else ShapeList() + + +class BuildPart(Builder): + _shape_cls = Part + _tag = 'part' + _core_kind = 'solid' + + @property + def part(self): + return self._obj + + @property + def _snapshot(self): + return self._obj + + +class BuildSketch(Builder): + _shape_cls = Sketch + _tag = 'sketch' + _core_kind = 'face' + + @property + def sketch(self): + """The sketch PLACED on the workplane (unlike _obj, which is local — + matching build123d's sketch_local semantics).""" + if self._obj is None or self._obj.topo is None: + return self._obj + placed = [wp.location * self._obj for wp in self.workplanes] + if len(placed) == 1: + return placed[0] + return placed[0] + placed[1:] + + @property + def sketch_local(self): + return self._obj + + def _finalize(self, parent): + if self._obj is None or self._obj.topo is None: + return + if isinstance(parent, BuildPart): + for wp in self.workplanes: + placed = wp.location * self._obj + parent.pending_faces.append(placed) + parent.pending_face_planes.append(wp) + elif parent is not None: + _combine(parent, self.sketch, self.mode) + + +class BuildLine(Builder): + _shape_cls = Curve + _tag = 'line' + _core_kind = 'edge' + + def __init__(self, *workplanes, mode=Mode.ADD): + Builder.__init__(self, *workplanes, mode=mode) + self._specs = [] + + def _selection_shape(self): + return self._obj if self._obj is not None else self.line + + @property + def line(self): + if self._obj is not None: + return self._obj + if not self._specs: + return None + # mid-context access (e.g. mirror(bl.line, ...)): build from the + # accumulated local segments + return Curve(w.WireFromSegments(_chain_segments(self._specs)), + list(self._specs)) + + def _finalize(self, parent): + # transform local specs by this builder's workplane + wp = self.workplanes[0] + loc = wp.location + fn_dir = lambda d: _mat_vec(loc._R, d) + specs = [_seg_transform(s, loc._transform_point, fn_dir, loc) + for s in self._specs] + if specs: + self._obj = Curve(w.WireFromSegments(_chain_segments(specs)), specs) + if isinstance(parent, BuildSketch): + parent.pending_edge_specs.extend(specs) + elif isinstance(parent, BuildPart): + # a BuildLine directly inside BuildPart provides the sweep() path + parent.pending_path = self._obj + elif parent is not None and self._obj is not None: + _combine(parent, self._obj, self.mode) + + +# Selectors that read the builder in scope: 'edges()' == '.edges()' +# (build123d exports these alongside the methods, and the docs use them). +vertices = _context_selector('vertices') +edges = _context_selector('edges') +wires = _context_selector('wires') +faces = _context_selector('faces') +solids = _context_selector('solids') + + +# ------------------------------------------------- location contexts ----- + +class LocationList: + def __init__(self, locations): + self.locations = list(locations) + + def __enter__(self): + _loc_stack.append(self) + return self + + def __exit__(self, exc_type, exc, tb): + _loc_stack.pop() + return False + + def __iter__(self): + return iter(self.locations) + + def __getitem__(self, i): + return self.locations[i] + + def __len__(self): + return len(self.locations) + + @property + def local_locations(self): + return list(self.locations) + + def append(self, loc): + """Location * GridLocations(...) products are mutable lists in + build123d — stud_wall appends extra studs to one.""" + self.locations.append(loc) + + def extend(self, locs): + self.locations.extend(locs) + + def __mul__(self, other): + # PolarLocations(...) * shape -> copies at every location (algebra) + if isinstance(other, Shape): + return ShapeList([loc * other for loc in self.locations]) + if isinstance(other, (list, tuple, ShapeList)): + return ShapeList([loc * s for loc in self.locations for s in other]) + return NotImplemented + + def __rmul__(self, other): + # Location * GridLocations(...) -> composed location list + if isinstance(other, Location): + return LocationList([other * loc for loc in self.locations]) + if isinstance(other, Plane): + return LocationList([other.location * loc for loc in self.locations]) + return NotImplemented + + +class Locations(LocationList): + def __init__(self, *pts): + locs = [] + for p in pts: + if isinstance(p, Location): + locs.append(p) + elif isinstance(p, Plane): + locs.append(p.location) + elif isinstance(p, Face): + locs.append(Plane(p).location) + elif isinstance(p, Vertex): + locs.append(Pos(p.X, p.Y, p.Z)) + elif isinstance(p, Axis): + pl = Plane(p.position, z_dir=p.direction) + locs.append(pl.location) + else: + locs.append(Pos(Vector(p))) + LocationList.__init__(self, locs) + + +class GridLocations(LocationList): + def __init__(self, x_spacing, y_spacing, x_count, y_count, + align=(Align.CENTER, Align.CENTER)): + x_count, y_count = int(x_count), int(y_count) + align = _norm_align(align, 2) + ox = _grid_offset(align[0], x_spacing * (x_count - 1)) + oy = _grid_offset(align[1], y_spacing * (y_count - 1)) + locs = [] + # build123d iterates x in the outer loop (y varies fastest) + for i in range(x_count): + for j in range(y_count): + locs.append(Pos(i * x_spacing + ox, j * y_spacing + oy, 0)) + LocationList.__init__(self, locs) + + +def _grid_offset(a, extent): + if a == Align.CENTER: + return -extent / 2.0 + if a == Align.MAX: + return -extent + return 0.0 + + +class HexLocations(LocationList): + """Hex-packed circle centers (touching circles of the given radius): + columns 2*apothem apart, rows 2*radius apart, odd columns offset by + radius; the whole grid is centered like build123d.""" + + def __init__(self, radius, x_count, y_count, align=(Align.CENTER, Align.CENTER)): + x_count, y_count = int(x_count), int(y_count) + apothem = radius * math.cos(math.radians(30)) + pts = [] + for i in range(x_count): + for k in range(y_count): + x = (i - (x_count - 1) / 2.0) * 2.0 * apothem + y = (k - (y_count - 1) / 2.0) * 2.0 * radius + \ + (i % 2) * radius - radius / 2.0 + pts.append((x, y)) + # center the grid (build123d aligns on the bounding box) + align = _norm_align(align, 2) + xs = [p[0] for p in pts] + ys = [p[1] for p in pts] + sh = _align_shift(align, (min(xs), min(ys), 0), (max(xs), max(ys), 0)) + LocationList.__init__(self, [Pos(p[0] + sh[0], p[1] + sh[1], 0) + for p in pts]) + + +class PolarLocations(LocationList): + def __init__(self, radius, count, start_angle=0.0, angular_range=360.0, + rotate=True): + count = int(count) + locs = [] + step = angular_range / count + for i in range(count): + a = start_angle + i * step + loc = Pos(radius * math.cos(math.radians(a)), + radius * math.sin(math.radians(a)), 0) + if rotate: + loc = loc * Rot(0, 0, a) + locs.append(loc) + LocationList.__init__(self, locs) + + +class Workplanes(LocationList): + """with Workplanes(*planes): — fan objects out over full plane bases. + The location-fanout stack (Locations/GridLocations/PolarLocations) + always carries complete Locations, and a Plane's basis IS its + location (rotation + origin), so Workplanes shares that exact code + path: every object created inside is replicated onto each plane with + the plane's orientation applied — 0.11.1 semantics.""" + + def __init__(self, *objs): + locs = [] + for o in objs: + if isinstance(o, Plane): + locs.append(o.location) + elif isinstance(o, Location): + locs.append(Location(o)) + elif isinstance(o, Face): + locs.append(Plane(o).location) + else: + raise TypeError('Workplanes expects Planes, Faces or ' + 'Locations') + LocationList.__init__(self, locs or [Location()]) + + +# ----------------------------------------------------- object creation --- + +def _norm_align(align, n): + if align is None: + return (None,) * n + if isinstance(align, str): + return (align,) * n + return tuple(align) + + +def _align_shift(align, bbox_min, bbox_max): + shift = [] + for i in range(3): + a = align[i] if i < len(align) else None + if a == Align.MIN: + shift.append(-bbox_min[i]) + elif a == Align.CENTER: + shift.append(-(bbox_min[i] + bbox_max[i]) / 2.0) + elif a == Align.MAX: + shift.append(-bbox_max[i]) + else: + shift.append(0.0) + return shift + + +def _create_object(cls, topo_maker, analytic_bbox, rotation3, align, mode, + builder_cls): + """Shared creation pipeline: align (own bbox) -> rotate -> replicate at + workplane x location-context products -> combine into the builder.""" + builder = _active_builder(builder_cls) + if builder is None and _builders: + raise RuntimeError('a ' + cls.__name__ + ' object cannot be created ' + 'directly inside a ' + type(_builders[-1]).__name__ + + ' context') + ctx_locs = _ctx_locations() + if builder is not None and builder_cls is BuildPart: + planes = builder.workplanes + else: + planes = [Plane.XY] + + rot = None + if rotation3 is not None: + if isinstance(rotation3, (int, float)): + rotation3 = (0.0, 0.0, rotation3) + r = tuple(rotation3) + if r[0] or r[1] or r[2]: + rot = Rotation(r[0], r[1], r[2]) + + results = [] + for pl in planes: + for loc in ctx_locs: + topo = topo_maker() + if align is not None and any(a is not None for a in align): + if analytic_bbox is not None: + bmin, bmax = analytic_bbox + else: + bb = list(w.BoundingBox(topo)) + bmin, bmax = bb[0:3], bb[3:6] + sh = _align_shift(align, bmin, bmax) + if sh[0] or sh[1] or sh[2]: + topo = w.Translate(sh, topo) + shape = cls.__new__(cls) + Shape.__init__(shape, topo) + if rot is not None: + shape = rot * shape + full = pl.location * loc + shape = full * shape + results.append(shape) + + obj = results[0] if len(results) == 1 else results[0] + results[1:] + # Select.LAST/NEW bookkeeping happens inside _combine for every operation. + return _combine(builder, obj, mode) + + +# ------------------------------------------------------- 3D primitives --- + +def Box(length, width, height, rotation=(0, 0, 0), + align=(Align.CENTER, Align.CENTER, Align.CENTER), mode=Mode.ADD): + align = _norm_align(align, 3) + bbox = ((-length / 2.0, -width / 2.0, -height / 2.0), + (length / 2.0, width / 2.0, height / 2.0)) + return _create_object(Part, lambda: w.Box(length, width, height, True), + bbox, rotation, align, mode, BuildPart) + + +def Cylinder(radius, height, arc_size=360, rotation=(0, 0, 0), + align=(Align.CENTER, Align.CENTER, Align.CENTER), mode=Mode.ADD): + align = _norm_align(align, 3) + if arc_size >= 360: + bbox = ((-radius, -radius, -height / 2.0), (radius, radius, height / 2.0)) + maker = lambda: w.Cylinder(radius, height, True) + else: + bbox = None # measured from the pie's own bounds, like build123d + + def maker(): + prof = w.Polygon([[0, 0, -height / 2.0], [radius, 0, -height / 2.0], + [radius, 0, height / 2.0], [0, 0, height / 2.0]]) + return w.Revolve(prof, arc_size, [0, 0, 1]) + return _create_object(Part, maker, bbox, rotation, align, mode, BuildPart) + + +def Sphere(radius, arc_size1=-90, arc_size2=90, arc_size3=360, + rotation=(0, 0, 0), + align=(Align.CENTER, Align.CENTER, Align.CENTER), mode=Mode.ADD): + align = _norm_align(align, 3) + if arc_size1 == -90 and arc_size2 == 90 and arc_size3 == 360: + bbox = ((-radius, -radius, -radius), (radius, radius, radius)) + return _create_object(Part, lambda: w.Sphere(radius), bbox, rotation, + align, mode, BuildPart) + # partial sphere: BRepPrimAPI_MakeSphere's two latitude angles and the + # longitude sweep, exactly build123d's Solid.make_sphere arguments. The + # bounding box is measured (a spherical wedge has no simple analytic box). + def maker(): + return w.PartialSphere(radius, arc_size1, arc_size2, arc_size3) + return _create_object(Part, maker, None, rotation, align, mode, BuildPart) + + +def Cone(bottom_radius, top_radius, height, arc_size=360, rotation=(0, 0, 0), + align=(Align.CENTER, Align.CENTER, Align.CENTER), mode=Mode.ADD): + if arc_size < 360: + raise NotImplementedError('partial cones are not supported in build123d-lite') + align = _norm_align(align, 3) + r = max(bottom_radius, top_radius) + bbox = ((-r, -r, -height / 2.0), (r, r, height / 2.0)) + + def maker(): + return w.Translate([0, 0, -height / 2.0], + w.Cone(bottom_radius, top_radius, height)) + return _create_object(Part, maker, bbox, rotation, align, mode, BuildPart) + + +def Torus(major_radius, minor_radius, rotation=(0, 0, 0), + align=(Align.CENTER, Align.CENTER, Align.CENTER), mode=Mode.ADD): + align = _norm_align(align, 3) + r = major_radius + minor_radius + bbox = ((-r, -r, -minor_radius), (r, r, minor_radius)) + + def maker(): + prof = w.Circle(minor_radius, False) + prof = w.Rotate([1, 0, 0], 90, prof) + prof = w.Translate([major_radius, 0, 0], prof) + return w.Revolve(prof, 360, [0, 0, 1]) + return _create_object(Part, maker, bbox, rotation, align, mode, BuildPart) + + +def ConvexPolyhedron(points, rotation=(0, 0, 0), align=Align.NONE, + mode=Mode.ADD): + """Part Object: the convex hull of the given points as a solid + (build123d ConvexPolyhedron): every hull facet becomes a polygonal Face, + which are then sewn into a Shell and solidified.""" + pnts = [tuple(_v3(p)) for p in points] + # the same quickhull3d the scipy shim's ConvexHull uses (upstream reads + # scipy's .simplices here) + faces = [] + for facet in w.ConvexHull3D([list(p) for p in pnts]): + corners = [pnts[int(i)] for i in facet] + faces.append(Face(Curve([Edge.make_line(corners[i], corners[ + (i + 1) % len(corners)]) for i in range(len(corners))]))) + solid = Part(w.SewSolidFromFaces([f.topo for f in faces])) + maker = lambda: solid.topo + align3 = _norm_align(align, 3) if align is not None else None + return _create_object(Part, maker, None, rotation, align3, mode, BuildPart) + + +def Wedge(xsize, ysize, zsize, xmin, zmin, xmax, zmax, rotation=(0, 0, 0), + align=(Align.CENTER, Align.CENTER, Align.CENTER), mode=Mode.ADD): + """Part Object: a wedge whose near face is xsize by zsize and whose far + face spans xmin..xmax by zmin..zmax, ysize deep (build123d Wedge -> + Solid.make_wedge -> BRepPrimAPI_MakeWedge's min/max form).""" + if any([v <= 0 for v in (xsize, ysize, zsize)]): + raise ValueError('xsize, ysize & zsize must all be greater than zero') + align = _norm_align(align, 3) + bbox = ((0.0, 0.0, 0.0), (xsize, ysize, zsize)) + maker = lambda: w.WedgeMinMax(xsize, ysize, zsize, xmin, zmin, xmax, zmax) + return _create_object(Part, maker, bbox, rotation, align, mode, BuildPart) + + +def _part_maxdim(builder): + if builder is None or builder._obj is None or builder._obj.topo is None: + raise ValueError('Hole requires an existing part in a BuildPart context') + bb = list(w.BoundingBox(builder._obj.topo, 0.01)) + return max(bb[3] - bb[0], bb[4] - bb[1], bb[5] - bb[2]) + + +def _hole_length(depth): + """Half-length of a hole cylinder: given depth, or 'through everything'. + build123d holes are CENTERED on the location and span +-depth.""" + if depth is not None: + return depth + return 2.0 * _part_maxdim(_active_builder(BuildPart)) + + +def Hole(radius, depth=None, mode=Mode.SUBTRACT): + ln = _hole_length(depth) + # build123d: cylinder of height 2*depth centered at the location + maker = lambda: w.Cylinder(radius, 2.0 * ln, True) + return _create_object(Part, maker, None, None, None, mode, BuildPart) + + +def CounterBoreHole(radius, counter_bore_radius, counter_bore_depth, + depth=None, mode=Mode.SUBTRACT): + ln = _hole_length(depth) + + def maker(): + # hole spans -ln..+ln; the counterbore spans -cb_depth..+ln + hole = w.Cylinder(radius, 2.0 * ln, True) + bore = w.Translate([0, 0, -counter_bore_depth], + w.Cylinder(counter_bore_radius, + counter_bore_depth + ln, False)) + return w.Union([bore, hole]) + return _create_object(Part, maker, None, None, None, mode, BuildPart) + + +def CounterSinkHole(radius, counter_sink_radius, depth=None, + counter_sink_angle=82, mode=Mode.SUBTRACT): + ln = _hole_length(depth) + sink_depth = ((counter_sink_radius - radius) / + math.tan(math.radians(counter_sink_angle / 2.0))) + + def maker(): + # hole spans -ln..+ln; countersink cone flares from the hole radius + # at -sink_depth to counter_sink_radius at z=0, then continues as a + # cylinder of that radius up to +ln (through the space above) + hole = w.Cylinder(radius, 2.0 * ln, True) + cone = w.Translate([0, 0, -sink_depth], + w.Cone(radius, counter_sink_radius, sink_depth)) + cap = w.Cylinder(counter_sink_radius, ln, False) + return w.Union([cone, cap, hole]) + return _create_object(Part, maker, None, None, None, mode, BuildPart) + + +# ------------------------------------------------------- 2D primitives --- + +class BasePartObject(Part): + """Base for user-defined part objects: places an existing Part with + rotation/align/mode like the built-in primitives (build123d compat).""" + + def __init__(self, part, rotation=(0, 0, 0), + align=None, mode=Mode.ADD): + topo = _topo(part) + align3 = _norm_align(align, 3) if align is not None else None + created = _create_object(Part, lambda: topo, None, rotation, align3, + mode, BuildPart) + Shape.__init__(self, created.topo) + + +class BaseSketchObject(Sketch): + """Base for user-defined sketch objects (build123d compat).""" + + def __init__(self, obj, rotation=0, align=None, mode=Mode.ADD): + topo = _topo(obj) + created = _sketch_object(lambda: topo, None, rotation, align, mode) + Shape.__init__(self, created.topo) + + +def _sketch_object(topo_maker, analytic_bbox, rotation, align, mode): + align3 = None + if align is not None: + a2 = _norm_align(align, 2) + align3 = (a2[0], a2[1], None) + bbox3 = None + if analytic_bbox is not None and align3 is not None: + (x0, y0), (x1, y1) = analytic_bbox + bbox3 = ((x0, y0, 0.0), (x1, y1, 0.0)) + rot3 = None + if rotation: + rot3 = (0.0, 0.0, rotation) + return _create_object(Sketch, topo_maker, bbox3, rot3, align3, mode, + BuildSketch) + + +def Rectangle(width, height, rotation=0, + align=(Align.CENTER, Align.CENTER), mode=Mode.ADD): + x, y = width / 2.0, height / 2.0 + maker = lambda: w.Polygon([[-x, -y, 0], [x, -y, 0], [x, y, 0], [-x, y, 0]]) + return _sketch_object(maker, ((-x, -y), (x, y)), rotation, align, mode) + + +def Circle(radius, align=(Align.CENTER, Align.CENTER), mode=Mode.ADD): + maker = lambda: w.Circle(radius, False) + return _sketch_object(maker, ((-radius, -radius), (radius, radius)), + 0, align, mode) + + +def Ellipse(*args, **kwargs): + raise NotImplementedError('Ellipse is not supported in build123d-lite ' + '(no ellipse curve binding)') + + +def Polygon(*pts, rotation=0, align=None, mode=Mode.ADD): + if len(pts) == 1 and not isinstance(pts[0], (Vector,)) and \ + hasattr(pts[0], '__len__') and len(pts[0]) > 0 and \ + hasattr(pts[0][0], '__len__'): + pts = tuple(pts[0]) + p3 = [_v3(p) for p in pts] + xs = [p[0] for p in p3] + ys = [p[1] for p in p3] + maker = lambda: w.Polygon([[p[0], p[1], 0] for p in p3]) + return _sketch_object(maker, ((min(xs), min(ys)), (max(xs), max(ys))), + rotation, align, mode) + + +def RegularPolygon(radius, side_count, major_radius=True, rotation=0, + align=(Align.CENTER, Align.CENTER), mode=Mode.ADD): + r = radius if major_radius else radius / math.cos(math.pi / side_count) + pts = [] + for i in range(side_count): + a = 2.0 * math.pi * i / side_count + pts.append([r * math.cos(a), r * math.sin(a), 0]) + maker = lambda: w.Polygon(pts) + # build123d aligns RegularPolygon on its circumcircle (+-r), not its bbox + return _sketch_object(maker, ((-r, -r), (r, r)), rotation, align, mode) + + +def RectangleRounded(width, height, radius, rotation=0, + align=(Align.CENTER, Align.CENTER), mode=Mode.ADD): + if radius <= 0 or radius >= min(width, height) / 2.0: + raise ValueError('RectangleRounded: invalid corner radius') + x, y, r = width / 2.0, height / 2.0, radius + k = r * (1.0 - math.sqrt(0.5)) # arc midpoint inset at 45 degrees + segs = [ + ['line', [[-x + r, -y, 0], [x - r, -y, 0]]], + ['arc3', [[x - r, -y, 0], [x - k, -y + k, 0], [x, -y + r, 0]]], + ['line', [[x, -y + r, 0], [x, y - r, 0]]], + ['arc3', [[x, y - r, 0], [x - k, y - k, 0], [x - r, y, 0]]], + ['line', [[x - r, y, 0], [-x + r, y, 0]]], + ['arc3', [[-x + r, y, 0], [-x + k, y - k, 0], [-x, y - r, 0]]], + ['line', [[-x, y - r, 0], [-x, -y + r, 0]]], + ['arc3', [[-x, -y + r, 0], [-x + k, -y + k, 0], [-x + r, -y, 0]]], + ] + maker = lambda: w.MakeFace(w.WireFromSegments(segs)) + return _sketch_object(maker, ((-x, -y), (x, y)), rotation, align, mode) + + +def SlotOverall(width, height, rotation=0, + align=(Align.CENTER, Align.CENTER), mode=Mode.ADD): + r = height / 2.0 + c = width / 2.0 - r # arc centers at +-c + if c <= 0: + raise ValueError('SlotOverall: width must exceed height') + segs = [ + ['line', [[-c, -r, 0], [c, -r, 0]]], + ['arc3', [[c, -r, 0], [c + r, 0, 0], [c, r, 0]]], + ['line', [[c, r, 0], [-c, r, 0]]], + ['arc3', [[-c, r, 0], [-c - r, 0, 0], [-c, -r, 0]]], + ] + maker = lambda: w.MakeFace(w.WireFromSegments(segs)) + return _sketch_object(maker, ((-width / 2.0, -r), (width / 2.0, r)), + rotation, align, mode) + + +def SlotCenterToCenter(center_separation, height, rotation=0, + align=(Align.CENTER, Align.CENTER), mode=Mode.ADD): + return SlotOverall(center_separation + height, height, rotation=rotation, + align=align, mode=mode) + + +def SlotCenterPoint(center, point, height, rotation=0, + align=(Align.CENTER, Align.CENTER), mode=Mode.ADD): + """Slot defined by its center and the center of ONE end arc, symmetric + about the center (build123d SlotCenterPoint).""" + c = Vector(center) + p = Vector(point) + half = p - c + if half.length <= 0: + raise ValueError('Distance between center and point must be greater ' + 'than 0 Got: distance = ' + repr(half.length)) + # a SlotOverall of the same length/height, rotated onto the half-line and + # translated to the center - the identical geometry upstream sews together + angle = math.degrees(math.atan2(half.Y, half.X)) + slot = SlotOverall(2 * half.length + height, height, + rotation=rotation + angle, align=align, + mode=Mode.PRIVATE) + placed = Pos(c.X, c.Y, c.Z) * slot + return _combine(_active_builder(BuildSketch), placed, mode) + + +def Trapezoid(width, height, left_side_angle, right_side_angle=None, + rotation=0, align=(Align.CENTER, Align.CENTER), mode=Mode.ADD): + if right_side_angle is None: + right_side_angle = left_side_angle + y = height / 2.0 + red_l = 0.0 if left_side_angle == 90 else \ + height / math.tan(math.radians(left_side_angle)) + red_r = 0.0 if right_side_angle == 90 else \ + height / math.tan(math.radians(right_side_angle)) + top_l = top_r = bot_l = bot_r = width / 2.0 + # build123d narrows the TOP for an acute side angle but widens the BOTTOM + # for an obtuse one (negative reduction), so 'width' is always the width + # of the wider of the two edges + if red_l > 0: + top_l -= red_l + else: + bot_l += red_l + if red_r > 0: + top_r -= red_r + else: + bot_r += red_r + if bot_l + bot_r < 0: + raise ValueError('Trapezoid bottom invalid - change angles') + if top_l + top_r < 0: + raise ValueError('Trapezoid top invalid - change angles') + pts = [[-bot_l, -y, 0], [bot_r, -y, 0], [top_r, y, 0], [-top_l, y, 0]] + xs = [p[0] for p in pts] + maker = lambda: w.Polygon(pts) + return _sketch_object(maker, ((min(xs), -y), (max(xs), y)), rotation, + align, mode) + + +class HeadType: + STRAIGHT = 'STRAIGHT' + CURVED = 'CURVED' + FILLETED = 'FILLETED' + + +def ArrowHead(size, head_type=HeadType.CURVED, rotation=0, mode=Mode.ADD): + """Sketch Object: an arrow head, tip at the origin pointing +X + (build123d's drafting.ArrowHead, same construction).""" + if head_type == HeadType.STRAIGHT: + return Polygon((-size, size / 3), (-size, -size / 3), (0, 0), + align=None, rotation=rotation, mode=mode) + if head_type not in (HeadType.CURVED, HeadType.FILLETED): + raise ValueError('unknown arrow HeadType ' + repr(head_type)) + with BuildSketch() as arrow_head: + with BuildLine(): + side = TangentArc((0, 0), (-size, size / 3), + tangent=(-size, size / 6)) + Line(side @ 1, (-7 * size / 8, 0)) + mirror(about=Plane.XZ) + make_face() + if head_type == HeadType.FILLETED: + fillet(arrow_head.vertices().filter_by_position( + Axis.X, -2 * size, -size / 5), radius=size / 20) + return add(arrow_head.sketch, rotation=rotation, mode=mode) \ + if _active_builder() is not None else arrow_head.sketch + + +# --------------------------------------------------------------- Triangle --- +# build123d's Triangle solves the triangle with the trianglesolver package +# (Steven Byrnes, Apache-2.0-compatible MIT); its law-of-sines/cosines solver +# is small enough to port outright, which is what these four helpers are. + +def _tri_aaas(D, E, F, f): + return (f * math.sin(D) / math.sin(F), f * math.sin(E) / math.sin(F), f, + D, E, F) + + +def _tri_sss(d, e, f): + if not (d + e > f and e + f > d and f + d > e): + raise ValueError('no such triangle') + F = math.acos((d ** 2 + e ** 2 - f ** 2) / (2 * d * e)) + E = math.acos((d ** 2 + f ** 2 - e ** 2) / (2 * d * f)) + return (d, e, f, math.pi - F - E, E, F) + + +def _tri_sas(d, e, F): + return _tri_sss(d, e, math.sqrt(d ** 2 + e ** 2 - 2 * d * e * math.cos(F))) + + +def _tri_ssa(d, e, D, ssa_flag): + sin_e = math.sin(D) * e / d + if abs(sin_e - 1.0) < 1e-9: + E = math.pi / 2 + else: + if sin_e >= 1.0: + raise ValueError('no such triangle') + e_acute = math.asin(sin_e) + e_obtuse = math.pi - e_acute + acute_ok = 0 < (math.pi - D - e_acute) < math.pi + obtuse_ok = 0 < (math.pi - D - e_obtuse) < math.pi + if ssa_flag == 'acute': + if not acute_ok: + raise ValueError('no such triangle') + E = e_acute + elif ssa_flag == 'obtuse': + if not obtuse_ok: + raise ValueError('no such triangle') + E = e_obtuse + else: + if acute_ok and obtuse_ok: + raise ValueError('Two different triangles fit this ' + 'description') + if not acute_ok and not obtuse_ok: + raise ValueError('No such triangle') + E = e_acute if acute_ok else e_obtuse + F = math.pi - D - E + e_, f_, d_, E_, F_, D_ = _tri_aaas(E, F, D, d) + return (d_, e_, f_, D_, E_, F_) + + +def _tri_solve(a=None, b=None, c=None, A=None, B=None, C=None, + ssa_flag='forbid'): + """trianglesolver.solve, ported: give any three of the six and get all + six back (angles in RADIANS).""" + given = [x for x in (a, b, c, A, B, C) if x is not None] + if len(given) != 3: + raise ValueError('Must provide exactly 3 inputs') + sides = [x for x in (a, b, c) if x is not None] + if not sides: + raise ValueError('Must provide at least 1 side length') + if len(sides) == 3: + return _tri_sss(a, b, c) + if len(sides) == 2: + if a is not None and A is not None and b is not None: + return _tri_ssa(a, b, A, ssa_flag) + if a is not None and A is not None and c is not None: + a, c, b, A, C, B = _tri_ssa(a, c, A, ssa_flag) + return (a, b, c, A, B, C) + if b is not None and B is not None and a is not None: + b, a, c, B, A, C = _tri_ssa(b, a, B, ssa_flag) + return (a, b, c, A, B, C) + if b is not None and B is not None and c is not None: + b, c, a, B, C, A = _tri_ssa(b, c, B, ssa_flag) + return (a, b, c, A, B, C) + if c is not None and C is not None and a is not None: + c, a, b, C, A, B = _tri_ssa(c, a, C, ssa_flag) + return (a, b, c, A, B, C) + if c is not None and C is not None and b is not None: + c, b, a, C, B, A = _tri_ssa(c, b, C, ssa_flag) + return (a, b, c, A, B, C) + if a is not None and b is not None and C is not None: + return _tri_sas(a, b, C) + if b is not None and c is not None and A is not None: + b, c, a, B, C, A = _tri_sas(b, c, A) + return (a, b, c, A, B, C) + if c is not None and a is not None and B is not None: + c, a, b, C, A, B = _tri_sas(c, a, B) + return (a, b, c, A, B, C) + raise ValueError('unsupported triangle specification') + if A is None: + A = math.pi - B - C + elif B is None: + B = math.pi - A - C + else: + C = math.pi - A - B + if not (A > 0 and B > 0 and C > 0): + raise ValueError('no such triangle') + if c is not None: + return _tri_aaas(A, B, C, c) + if a is not None: + b, c, a, B, C, A = _tri_aaas(B, C, A, a) + return (a, b, c, A, B, C) + c, a, b, C, A, B = _tri_aaas(C, A, B, b) + return (a, b, c, A, B, C) + + +def Triangle(a=None, b=None, c=None, A=None, B=None, C=None, align=None, + rotation=0, mode=Mode.ADD): + """Sketch Object: a triangle from one side length and any two other sides + or interior angles (build123d Triangle). Side 'a' is the bottom, 'b' the + right, going counter-clockwise; angle 'X' is opposite side 'x'. The result + carries the solved a/b/c/A/B/C, the three edges and the three vertices.""" + if [v is None for v in (a, b, c)].count(True) == 3 or \ + [v is None for v in (a, b, c, A, B, C)].count(True) != 3: + raise ValueError('One length and two other values must be provided') + ar, br, cr, Ar, Br, Cr = _tri_solve( + a, b, c, + math.radians(A) if A is not None else None, + math.radians(B) if B is not None else None, + math.radians(C) if C is not None else None) + apex = Vector(cr, 0, 0).rotate(Axis.Z, math.degrees(Br)) + pts = [(0.0, 0.0, 0.0), (ar, 0.0, 0.0), (apex.X, apex.Y, 0.0)] + cx = sum([p[0] for p in pts]) / 3.0 + cy = sum([p[1] for p in pts]) / 3.0 + pts = [[p[0] - cx, p[1] - cy, 0.0] for p in pts] + xs = [p[0] for p in pts] + ys = [p[1] for p in pts] + maker = lambda: w.Polygon(pts) + obj = _sketch_object(maker, ((min(xs), min(ys)), (max(xs), max(ys))), + rotation, align, mode) + obj.a, obj.b, obj.c = ar, br, cr + obj.A, obj.B, obj.C = math.degrees(Ar), math.degrees(Br), math.degrees(Cr) + obj.edge_a = obj.edges().filter_by( + lambda e: abs(e.length - ar) < _TOL_1E6)[0] + obj.edge_b = obj.edges().filter_by( + lambda e: abs(e.length - br) < _TOL_1E6 and + not (abs(e.length - obj.edge_a.length) < _TOL_1E6 and + (e.center() - obj.edge_a.center()).length < _TOL_1E6))[0] + obj.edge_c = obj.edges().filter_by( + lambda e: all([(e.center() - other.center()).length > _TOL_1E6 + for other in (obj.edge_a, obj.edge_b)]))[0] + + def _common_vertex(e1, e2): + for v1 in e1.vertices(): + for v2 in e2.vertices(): + if (Vector(v1.to_tuple()) - Vector(v2.to_tuple())).length < \ + _TOL_1E6: + return v1 + raise ValueError('these edges share no vertex') + obj.vertex_A = _common_vertex(obj.edge_b, obj.edge_c) + obj.vertex_B = _common_vertex(obj.edge_a, obj.edge_c) + obj.vertex_C = _common_vertex(obj.edge_a, obj.edge_b) + return obj + + +class TextAlign: + LEFT = 'left' + CENTER = 'center' + RIGHT = 'right' + BOTTOM = 'bottom' + TOP = 'top' + + +def Text(txt, font_size, font='Arial', font_path=None, + font_style=FontStyle.REGULAR, + text_align=(TextAlign.CENTER, TextAlign.CENTER), align=None, + path=None, position_on_path=0.0, single_line_width=None, + rotation=0.0, mode=Mode.ADD): + """Text rendered from bundled FreeSans outlines with FreeType-parity + kerning (matches what the reference build123d resolves 'Arial' to on + this machine for Latin text). COMPROMISE(text): only the bundled + FreeSans faces exist — other font names fall back with a warning, + font_path raises, and non-Latin glyph METRICS (e.g. Greek) can + differ from other Arial substitutes. + + path= places each glyph on a curve like upstream's position_glyph: the + glyph's bottom-centre advances the relative position along the path, and + the glyph is rotated by the signed angle between +X and the path tangent + there.""" + if font_path is not None: + raise NotImplementedError('Text font_path= is not supported in ' + 'build123d-lite (fonts are bundled)') + fname = {FontStyle.REGULAR: 'FreeSans', + FontStyle.BOLD: 'FreeSansBold', + FontStyle.ITALIC: 'FreeSansOblique', + FontStyle.BOLDITALIC: 'FreeSansBoldOblique'}.get(font_style) + if fname is None: + raise NotImplementedError('unsupported font_style in build123d-lite') + if font not in ('Arial', 'FreeSans'): + print('build123d-lite: font ' + repr(font) + + ' is not bundled; using FreeSans (what OCCT resolves Arial to)') + maker = lambda: w.Text2D(txt, float(font_size), fname, + text_align[0], text_align[1]) + if path is None: + return _sketch_object(maker, None, rotation, align, mode) + # Text on a path: upstream splits the flat text into its TOP LEVEL shapes + # (one per glyph) and repositions each of them (Compound.make_text's + # position_glyph). + flat = Sketch(maker()) + path_length = path.length + placed = [] + for glyph in flat.faces(): + bbox = glyph.bounding_box() + bottom_center_x = (bbox.min.X + bbox.max.X) / 2.0 + relative = position_on_path + bottom_center_x / path_length + tangent = path.tangent_at(relative) + wire_angle = Vector(1, 0, 0).get_signed_angle(tangent) + wire_position = path.position_at(relative) + shift = wire_position - Vector(bottom_center_x, 0, 0) + moved = Pos(shift.X, shift.Y, shift.Z) * glyph + placed.append(moved.rotate(Axis(wire_position, (0, 0, 1)), + -wire_angle)) + result = Sketch(w.MakeCompound([_topo(g) for g in placed])) + builder = _active_builder() + if rotation: + result = Rotation(0, 0, rotation) * result + return _combine(builder, result, mode) if builder is not None else result + + +# ---------------------------------------------------------- 1D objects --- + +def _seg_pts(seg): + return seg[1] + + +def _seg_params(seg): + return seg[2] if len(seg) > 2 else None + + +def _seg_make(kind, pts, params=None): + if params is None: + return [kind, [list(p) for p in pts]] + return [kind, [list(p) for p in pts], params] + + +def _seg_reverse(seg): + params = _seg_params(seg) + if seg[0] == 'circle' and params is not None: + n = _v3(params[1]) + params = [list(params[0]), [-n[0], -n[1], -n[2]], list(params[2]), + params[3]] + if seg[0] in ('parab', 'hypr') and params is not None: + # same parameter interval, opposite sense (GC_MakeArcOf*'s Sense flag) + params = list(params[:6]) + [not params[6]] + if seg[0] == 'interp' and params is not None: + tans = params[0] + if tans: + tans = [([-t[0], -t[1], -t[2]] if t else []) + for t in reversed(tans)] + params = [tans] + list(params[1:]) + return _seg_make(seg[0], list(reversed(_seg_pts(seg))), params) + + +def _seg_transform(seg, fn_point, fn_dir, loc=None): + """Rigid-transform a segment: points via fn_point, directions via fn_dir. + Kind-aware params: earc carries [center, xdir, normal, ...], interp + carries [tangents, periodic, scale], raw carries an untransformable + TopoDS edge.""" + pts = [fn_point(_v3(p)) for p in _seg_pts(seg)] + params = _seg_params(seg) + if params is not None: + if seg[0] == 'circle': + # [center, normal, xdir, radius] + params = [list(fn_point(_v3(params[0]))), list(fn_dir(_v3(params[1]))), + list(fn_dir(_v3(params[2]))), params[3]] + elif seg[0] in ('earc', 'parab', 'hypr'): + # [center/origin, xdir, normal, ...sizes and angles] + params = [list(fn_point(_v3(params[0]))), list(fn_dir(_v3(params[1]))), + list(fn_dir(_v3(params[2])))] + list(params[3:]) + elif seg[0] == 'bspline': + # only the POLES move; knots/mults/degree/weights are invariant + params = [[list(fn_point(_v3(p))) for p in params[0]]] + \ + list(params[1:]) + elif seg[0] == 'interp': + tans = params[0] + if tans: + tans = [(list(fn_dir(_v3(t))) if t else []) + for t in tans] + params = [tans] + list(params[1:]) + elif seg[0] == 'raw': + # COMPROMISE(raw-segments): edges that are not lines/circles ride + # through wires as opaque TopoDS edges. A rigid transform given as + # a Location can still be applied to the edge itself (exact); an + # arbitrary point/direction mapping cannot, and the caller falls + # back to transforming the baked topo and dropping specs. + if loc is None: + raise NotImplementedError('cannot transform an opaque edge ' + 'segment in build123d-lite') + params = [_topo(loc * Edge(params[0]))] + return _seg_make(seg[0], pts, params) + + +def _seg_scale(seg, k): + """Uniformly scale a segment about the origin by factor k.""" + pts = [[p[0] * k, p[1] * k, p[2] * k] for p in + [_v3(p) for p in _seg_pts(seg)]] + params = _seg_params(seg) + if params is not None: + if seg[0] == 'earc': + c = _v3(params[0]) + params = [[c[0] * k, c[1] * k, c[2] * k], list(params[1]), + list(params[2]), params[3] * k, params[4] * k] + \ + list(params[5:]) + elif seg[0] == 'circle': + cc = _v3(params[0]) + params = [[cc[0] * k, cc[1] * k, cc[2] * k], list(params[1]), + list(params[2]), params[3] * k] + elif seg[0] in ('parab', 'hypr'): + # a conic's PARAMETER range does not scale with its size (a + # parabola's U is the y offset, a hyperbola's is a hyperbolic + # angle), so scaling one would need the trim range recomputed — + # refuse rather than return the wrong arc + raise NotImplementedError('cannot scale a parabolic/hyperbolic ' + 'arc in build123d-lite') + elif seg[0] == 'bspline': + params = [[[p[0] * k, p[1] * k, p[2] * k] for p in + [_v3(q) for q in params[0]]]] + list(params[1:]) + elif seg[0] == 'interp': + # tangents stay UNCHANGED: GeomAPI_Interpolate parametrizes by + # chord length, so scaling the points by k scales the parameter + # range by k too — dP/dt is scale-invariant and the curve + # scales self-similarly with the original tangent magnitudes + pass + elif seg[0] == 'raw': + raise NotImplementedError('cannot scale an opaque edge segment ' + 'in build123d-lite') + return _seg_make(seg[0], pts, params) + + +def _chain_segments(specs): + """Greedy-chain segments end-to-start (reversing where needed) so OCCT's + MakeWire accepts them in order. Specs: (kind, [pts...][, params]).""" + if not specs: + return [] + remaining = [_seg_make(s[0], [tuple(p) for p in _seg_pts(s)], _seg_params(s)) + for s in specs] + ordered = [remaining.pop(0)] + + def s_start(s): + return _seg_pts(s)[0] + + def s_end(s): + return _seg_pts(s)[-1] + + def close(a, b): + return (abs(a[0] - b[0]) < 1e-6 and abs(a[1] - b[1]) < 1e-6 and + abs(a[2] - b[2]) < 1e-6) + + while remaining: + tail = s_end(ordered[-1]) + head = s_start(ordered[0]) + found = False + for i, seg in enumerate(remaining): + if close(s_start(seg), tail): + ordered.append(remaining.pop(i)) + found = True + break + if close(s_end(seg), tail): + ordered.append(_seg_reverse(remaining.pop(i))) + found = True + break + if close(s_end(seg), head): + ordered.insert(0, remaining.pop(i)) + found = True + break + if close(s_start(seg), head): + ordered.insert(0, _seg_reverse(remaining.pop(i))) + found = True + break + if not found: + # disconnected: append remaining as-is and let OCCT complain + ordered.extend(remaining) + break + return ordered + + +def _line_object(specs, mode=Mode.ADD): + """Register specs with the active BuildLine (if any) and return a Curve.""" + builder = _active_builder(BuildLine) + curve = Curve(w.WireFromSegments(_chain_segments(specs)), specs) + if builder is not None and mode != Mode.PRIVATE: + builder._specs.extend(specs) + return curve + + +def Line(*pts, mode=Mode.ADD): + if len(pts) == 1: + pts = tuple(pts[0]) + if len(pts) != 2: + raise ValueError('Line requires exactly two points') + return _line_object([('line', [_v3(pts[0]), _v3(pts[1])])], mode) + + +def Polyline(*pts, close=False, mode=Mode.ADD): + if len(pts) == 1 and hasattr(pts[0], '__len__') and \ + hasattr(pts[0][0], '__len__'): + pts = tuple(pts[0]) + p3 = [_v3(p) for p in pts] + if close and p3[0] != p3[-1]: + p3.append(p3[0]) + specs = [('line', [p3[i], p3[i + 1]]) for i in range(len(p3) - 1)] + return _line_object(specs, mode) + + +def ThreePointArc(*pts, mode=Mode.ADD): + if len(pts) == 1: + pts = tuple(pts[0]) + if len(pts) != 3: + raise ValueError('ThreePointArc requires three points') + return _line_object([('arc3', [_v3(pts[0]), _v3(pts[1]), _v3(pts[2])])], mode) + + +def _arc_mid_from_sagitta(p1, p2, sagitta): + mx, my = (p1[0] + p2[0]) / 2.0, (p1[1] + p2[1]) / 2.0 + dx, dy = p2[0] - p1[0], p2[1] - p1[1] + ln = math.hypot(dx, dy) + if ln < 1e-12: + raise ValueError('arc endpoints coincide') + nx, ny = -dy / ln, dx / ln # left normal of p1->p2 + return (mx + nx * sagitta, my + ny * sagitta, p1[2]) + + +def SagittaArc(start, end, sagitta, mode=Mode.ADD): + p1, p2 = _v3(start), _v3(end) + mid = _arc_mid_from_sagitta(p1, p2, sagitta) + return _line_object([('arc3', [p1, list(mid), p2])], mode) + + +def RadiusArc(start, end, radius, short_sagitta=True, mode=Mode.ADD): + p1, p2 = _v3(start), _v3(end) + c = math.hypot(p2[0] - p1[0], p2[1] - p1[1]) + r = abs(radius) + if r < c / 2.0 - 1e-9: + raise ValueError('RadiusArc: radius is smaller than half the chord') + h = math.sqrt(max(r * r - (c / 2.0) ** 2, 0.0)) + sag = (r - h) if short_sagitta else (r + h) + if radius < 0: + sag = -sag + return SagittaArc(start, end, sag, mode=mode) + + +def CenterArc(center, radius, start_angle, arc_size, mode=Mode.ADD): + c = _v3(center) + + def at(a): + return [c[0] + radius * math.cos(math.radians(a)), + c[1] + radius * math.sin(math.radians(a)), c[2]] + if abs(arc_size) >= 360: + # ONE closed circle edge, like upstream — not two half arcs. The edge + # COUNT of a full circle is observable: group_by(Edge.length) keys and + # anything that samples per edge (make_hull) change with it. + xdir = [math.cos(math.radians(start_angle)), + math.sin(math.radians(start_angle)), 0.0] + specs = [('circle', [at(start_angle), at(start_angle + 360)], + [list(c), [0.0, 0.0, 1.0], xdir, float(radius)])] + else: + specs = [('arc3', [at(start_angle), at(start_angle + arc_size / 2.0), + at(start_angle + arc_size)])] + return _line_object(specs, mode) + + +def TangentArc(*pts, tangent, tangent_from_first=True, mode=Mode.ADD): + if len(pts) == 1: + pts = tuple(pts[0]) + if len(pts) != 2: + raise ValueError('TangentArc requires two points') + if not tangent_from_first: + # upstream applies the tangent to the LAST point instead, which builds + # the same circle traversed the other way (objects_curve TangentArc) + pts = (pts[1], pts[0]) + p1, p2 = _v3(pts[0]), _v3(pts[1]) + t = _v3(tangent) + tln = math.hypot(t[0], t[1]) + tx, ty = t[0] / tln, t[1] / tln + nx, ny = -ty, tx # left normal of the tangent + dx, dy = p2[0] - p1[0], p2[1] - p1[1] + denom = 2.0 * (dx * nx + dy * ny) + if abs(denom) < 1e-12: + return Line(pts[0], pts[1], mode=mode) + r = (dx * dx + dy * dy) / denom # signed radius along the left normal + cx, cy = p1[0] + nx * r, p1[1] + ny * r + a1 = math.atan2(p1[1] - cy, p1[0] - cx) + a2 = math.atan2(p2[1] - cy, p2[0] - cx) + ccw = r > 0 # tangent matches CCW travel when center is on the left + if ccw: + while a2 <= a1: + a2 += 2.0 * math.pi + else: + while a2 >= a1: + a2 -= 2.0 * math.pi + amid = (a1 + a2) / 2.0 + rad = abs(r) + mid = [cx + rad * math.cos(amid), cy + rad * math.sin(amid), p1[2]] + return _line_object([('arc3', [p1, mid, p2])], mode) + + +def JernArc(start, tangent, radius, arc_size, mode=Mode.ADD): + p1 = _v3(start) + t = _v3(tangent) + tln = math.hypot(t[0], t[1]) + tx, ty = t[0] / tln, t[1] / tln + # positive arc_size turns left: center on the left normal, CCW sweep + side = 1.0 if arc_size >= 0 else -1.0 + nx, ny = -ty * side, tx * side + cx, cy = p1[0] + nx * radius, p1[1] + ny * radius + a1 = math.atan2(p1[1] - cy, p1[0] - cx) + a2 = a1 + side * math.radians(abs(arc_size)) + amid = (a1 + a2) / 2.0 + + def at(a): + return [cx + radius * math.cos(a), cy + radius * math.sin(a), p1[2]] + arc = _line_object([('arc3', [list(p1), at(amid), at(a2)])], mode) + # upstream's JernArc records its defining parameters on the object + arc.radius = radius + arc.center_point = Vector((cx, cy, p1[2])) + return arc + + +def _sample_curve(obj, per_edge=256): + """[(point3, edge, u)] samples along every edge of a curve/edge — + the pure-Python side of curve-distance queries (one JS call per + sample, cached by callers).""" + edges = [obj] if isinstance(obj, Edge) else obj.edges() + out = [] + for e in edges: + for i in range(per_edge + 1): + u = i / per_edge + q = w._edgePointAt(e.topo, u) + out.append(((q[0], q[1], q[2]), e, u)) + return out + + +def _closest_on_curve(samples, p): + """(distance, point, edge, u) of the curve point closest to p: coarse + scan over the cached samples, then golden-section refinement on the + winning edge's parameter (near-exact for smooth curves).""" + best_i = 0 + best_d = None + for i, (q, e, u) in enumerate(samples): + d = ((q[0] - p[0]) ** 2 + (q[1] - p[1]) ** 2 + (q[2] - p[2]) ** 2) + if best_d is None or d < best_d: + best_d = d + best_i = i + q0, e0, u0 = samples[best_i] + step = 1.0 if len(samples) < 2 else abs( + samples[1][2] - samples[0][2]) or 1.0 / 256 + a = max(0.0, u0 - step) + b = min(1.0, u0 + step) + + def f(u): + q = w._edgePointAt(e0.topo, u) + return ((q[0] - p[0]) ** 2 + (q[1] - p[1]) ** 2 + (q[2] - p[2]) ** 2) + phi = 0.6180339887498949 + c = b - phi * (b - a) + d_ = a + phi * (b - a) + fc, fd = f(c), f(d_) + for _i in range(48): + if fc < fd: + b, d_, fd = d_, c, fc + c = b - phi * (b - a) + fc = f(c) + else: + a, c, fc = c, d_, fd + d_ = a + phi * (b - a) + fd = f(d_) + u = (a + b) / 2.0 + q = w._edgePointAt(e0.topo, u) + dist = math.sqrt((q[0] - p[0]) ** 2 + (q[1] - p[1]) ** 2 + + (q[2] - p[2]) ** 2) + return dist, tuple(q), e0, u + + +def DoubleTangentArc(pnt, tangent, other, keep=Keep.TOP, mode=Mode.ADD): + """Arc tangent to a point/tangent pair AND to another curve. + COMPROMISE(double-tangent-arc): upstream solves radius with + scipy.optimize.minimize (Nelder-Mead) over an exact BRepExtrema + distance; lite finds the same root of dist(center(r)) - r by scan + + bisection over a sampled-then-refined curve distance. The tangency + point (hence the arc) matches upstream to well below harness + tolerance; candidate ORDER follows upstream's [90, -90] sweep about + the flipped common plane.""" + if keep not in (Keep.TOP, Keep.BOTTOM): + raise ValueError('Only the TOP or BOTTOM options are supported') + arc_pt = _v3(pnt) + t = Vector(tangent).normalized() + # BuildLine geometry is local-XY planar; upstream flips the derived + # common plane, making the rotation axis -Z + axis_dir = (0.0, 0.0, -1.0) + samples = _sample_curve(other, 512) + bb = other.bounding_box() + mins = [min(bb.min[i], arc_pt[i]) for i in range(3)] + maxs = [max(bb.max[i], arc_pt[i]) for i in range(3)] + max_size = 10 * math.sqrt(sum((maxs[i] - mins[i]) ** 2 for i in range(3))) + + accepted = [] + for ang in (90.0, -90.0): + bis = tuple(t.rotate(Axis((0, 0, 0), axis_dir), ang)) + + def g(r): + c = (arc_pt[0] + bis[0] * r, arc_pt[1] + bis[1] * r, + arc_pt[2] + bis[2] * r) + return _closest_on_curve(samples, c)[0] - r + + # first (smallest-r) root: sign-change scan + bisection — the + # solution Nelder-Mead from x0=0 walks into + n = 400 + prev_r, prev_v = 1e-9, g(1e-9) + root = None + for i in range(1, n + 1): + r = max_size * i / n + v = g(r) + if v == 0.0 or (prev_v > 0) != (v > 0): + a, b, fa = prev_r, r, prev_v + for _j in range(60): + m = (a + b) / 2.0 + fm = g(m) + if (fa > 0) != (fm > 0): + b = m + else: + a, fa = m, fm + root = (a + b) / 2.0 + break + prev_r, prev_v = r, v + if root is None: + continue + center = (arc_pt[0] + bis[0] * root, arc_pt[1] + bis[1] * root, + arc_pt[2] + bis[2] * root) + dist, p1, e1, u1 = _closest_on_curve(samples, center) + if abs(dist - root) > 1e-4: + continue + # tangency: the other curve's tangent must be perpendicular to the + # radial direction at the touch point (build123d checks the circle + # tangent is parallel within 0.05 rad) + ot = w._edgeTangentAt(e1.topo, u1) + radial = (p1[0] - center[0], p1[1] - center[1], p1[2] - center[2]) + rl = math.sqrt(sum(v * v for v in radial)) or 1.0 + cosang = abs(sum(ot[k] * radial[k] for k in range(3))) / rl + if cosang > 0.05: + continue + accepted.append((center, p1, e1, u1)) + if not accepted: + raise RuntimeError('No double tangent arcs found') + chosen = accepted[0] if keep == Keep.TOP else accepted[-1] + _c, p1, e1, u1 = chosen + # COMPROMISE(double-tangent-arc): upstream leaves the tangent target + # over-extended ("beyond the intersection") and relies on the face + # builder's wire fixing to trim it; lite trims the target's segment in + # the active BuildLine at the tangency point instead — the resulting + # FACE is identical, but the target curve's dangling tail is dropped. + builder = _active_builder(BuildLine) + if builder is not None and isinstance(other, Curve) and other._specs: + for i, sg in enumerate(builder._specs): + if any(sg is s2 for s2 in other._specs) and sg[0] == 'arc3': + s0 = _v3(w._edgePointAt(e1.topo, 0.0)) + seg0 = _v3(_seg_pts(sg)[0]) + if max(abs(s0[k] - seg0[k]) for k in range(3)) < 1e-6: + mid = w._edgePointAt(e1.topo, u1 / 2.0) + builder._specs[i] = _seg_make( + 'arc3', [list(seg0), [mid[0], mid[1], mid[2]], + list(p1)]) + break + return TangentArc(tuple(arc_pt), p1, tangent=tuple(t), mode=mode) + + +def PolarLine(start, length, angle=None, direction=None, + length_mode=LengthMode.DIAGONAL, mode=Mode.ADD, **kwargs): + """Line from a point at an angle, ending after 'length' or AT a limit + shape (build123d PolarLine).""" + p1 = _v3(start) + if direction is not None: + d = Vector(direction).normalized() + angle = math.degrees(math.atan2(d.Y, d.X)) + elif angle is not None: + a = math.radians(angle) + d = Vector((math.cos(a), math.sin(a), 0.0)) + else: + raise ValueError('PolarLine requires angle= or direction=') + + if isinstance(length, (int, float)): + # length_mode measures the DIAGONAL (default), or the horizontal / + # vertical projection of the line - upstream divides by cos/sin + if length_mode == LengthMode.HORIZONTAL: + scale = abs(length / math.cos(math.radians(angle))) + elif length_mode == LengthMode.VERTICAL: + scale = abs(length / math.sin(math.radians(angle))) + else: + scale = length + p2 = [p1[0] + scale * d.X, p1[1] + scale * d.Y, p1[2] + scale * d.Z] + return _line_object([('line', [p1, p2])], mode) + + # length is a LIMIT SHAPE: run the ray out and stop at the first contact + # in front of the start point (build123d trims a long edge to the limit) + target = length._obj if isinstance(length, Builder) else length + if not isinstance(target, Shape): + raise NotImplementedError( + 'PolarLine length limits are supported for shapes only in ' + 'build123d-lite, not ' + type(length).__name__) + axis = Axis(p1, tuple(d)) + best = None + contact = None + for e in target.edges(): + for h in e.find_intersection_points(axis): + v = Vector(tuple(h)) + along = (v - Vector(tuple(p1))).dot(d) + if along > _TOL and (best is None or along < best): + best, contact = along, v + if contact is None: + raise ValueError("Polar line doesn't intersect length limit " + + repr(length)) + return _line_object([('line', [p1, list(contact)])], mode) + + +def _dist3(a, b): + return math.sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2 + + (a[2] - b[2]) ** 2) + + +def _unit3(v): + n = math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]) + if n < 1e-15: + return [0.0, 0.0, 0.0] + return [v[0] / n, v[1] / n, v[2] / n] + + +def FilletPolyline(*pts, radius, close=False, mode=Mode.ADD): + """Polyline whose corners are filleted to a radius (build123d + FilletPolyline). A radius of 0 leaves that corner sharp. + + Upstream builds each corner fillet with Face.fillet_2d; a fillet between + two straight segments is the analytic tangent arc, so lite constructs it + directly (identical geometry, and it keeps the result a spec-level Curve + that mirror()/make_face() can still transform).""" + if len(pts) == 1 and hasattr(pts[0], '__len__') and \ + hasattr(pts[0][0], '__len__'): + pts = tuple(pts[0]) + points = [list(_v3(p)) for p in pts] + # a user-closed polyline (last == first) is treated as close=True + if len(points) > 1 and _dist3(points[0], points[-1]) < _TOL: + close = True + points.pop() + if len(points) < 2: + raise ValueError('FilletPolyline requires two or more pts') + + n = len(points) + if isinstance(radius, (int, float)): + radius_list = [float(radius)] * n + radius_at = lambda i: radius_list[i] + else: + radius_list = [float(r) for r in radius] + expected = n - (0 if close else 2) + if len(radius_list) != expected: + raise ValueError('radius list length (' + str(len(radius_list)) + + ') must match angle count (' + str(expected) + ')') + radius_at = lambda i: radius_list[i - (0 if close else 1)] + for r in radius_list: + if r < 0: + raise ValueError('radius ' + repr(r) + ' must be non-negative') + + corners = range(n) if close else range(1, n - 1) + segs = [] + cursor = list(points[0]) + for i in corners: + p = points[i] + prev = points[(i - 1) % n] + nxt = points[(i + 1) % n] + r = radius_at(i) + u = _unit3([p[0] - prev[0], p[1] - prev[1], p[2] - prev[2]]) + v = _unit3([nxt[0] - p[0], nxt[1] - p[1], nxt[2] - p[2]]) + cosang = -(u[0] * v[0] + u[1] * v[1] + u[2] * v[2]) + cosang = max(-1.0, min(1.0, cosang)) + interior = math.acos(cosang) + if r == 0 or interior < 1e-9 or abs(interior - math.pi) < 1e-9: + continue # sharp corner (or collinear: nothing to fillet) + t = r / math.tan(interior / 2.0) + a = [p[k] - u[k] * t for k in range(3)] + b = [p[k] + v[k] * t for k in range(3)] + # the arc centre lies along the interior bisector + bis = _unit3([v[k] - u[k] for k in range(3)]) + dist = r / math.sin(interior / 2.0) + centre = [p[k] + bis[k] * dist for k in range(3)] + chord_mid = [(a[k] + b[k]) / 2.0 for k in range(3)] + out = _unit3([chord_mid[k] - centre[k] for k in range(3)]) + arc_mid = [centre[k] + out[k] * r for k in range(3)] + if _dist3(cursor, a) > _TOL: + segs.append(('line', [list(cursor), list(a)])) + segs.append(('arc3', [list(a), arc_mid, list(b)])) + cursor = b + end = points[0] if close else points[-1] + if _dist3(cursor, end) > _TOL: + segs.append(('line', [list(cursor), list(end)])) + if not segs: + raise ValueError('FilletPolyline produced no segments') + return _line_object(segs, mode) + + +def IntersectingLine(start, direction, other, mode=Mode.ADD): + """Line from a point in a direction, ending at the NEAREST intersection + with another curve (build123d IntersectingLine).""" + p1 = _v3(start) + d = Vector(direction).normalized() + axis = Axis(p1, tuple(d)) + target = other._obj if isinstance(other, Builder) else other + hits = [h for e in target.edges() for h in e.find_intersection_points(axis)] + if not hits: + raise ValueError('No intersections found') + # upstream takes the intersection CLOSEST to start (unsigned distance) + best = None + contact = None + for h in hits: + v = Vector(tuple(h)) + dist = (Vector(tuple(p1)) - v).length + if best is None or dist < best: + best, contact = dist, v + p2 = [p1[0] + d.X * best, p1[1] + d.Y * best, p1[2] + d.Z * best] + return _line_object([('line', [p1, p2])], mode) + + +def Bezier(*cpts, weights=None, mode=Mode.ADD): + if len(cpts) == 1 and hasattr(cpts[0], '__len__') and \ + hasattr(cpts[0][0], '__len__'): + cpts = tuple(cpts[0]) + pts = [_v3(p) for p in cpts] + if weights is None: + return _line_object([('bezier', pts)], mode) + # rational Bezier: the WASM build lacks the weighted Geom_BezierCurve + # array types, so sample the exact rational curve densely and fit + if len(weights) != len(pts): + raise ValueError('Bezier: weights must match control points') + n = len(pts) - 1 + binom = [1] * (n + 1) + for i in range(1, n + 1): + binom[i] = binom[i - 1] * (n - i + 1) // i + + def at(t): + num = [0.0, 0.0, 0.0] + den = 0.0 + for i in range(n + 1): + b = binom[i] * (t ** i) * ((1 - t) ** (n - i)) * weights[i] + den += b + for k in range(3): + num[k] += b * pts[i][k] + return [num[0] / den, num[1] / den, num[2] / den] + dense = [at(i / 64.0) for i in range(65)] + return _line_object([('spline', dense)], mode) + + +def Spline(*pts, tangents=None, tangent_scalars=None, periodic=False, + mode=Mode.ADD): + """Exact interpolation through the points — GeomAPI_Interpolate via the + 'interp' segment kind, replicating build123d's Spline/Edge.make_spline: + tangents are unit-normalized then multiplied by their scalar (default + 1.0); OCC's Scale flag is True exactly when tangent_scalars is None.""" + if len(pts) == 1 and hasattr(pts[0], '__len__') and \ + hasattr(pts[0][0], '__len__'): + pts = tuple(pts[0]) + p3 = [list(_v3(p)) for p in pts] + # NOTE: [] (not None) encodes "no tangents" — Brython None objects break + # the worker's CacheOp JSON hashing when nested in argument structures + tans = [] + scale_flag = tangent_scalars is None + if tangents is not None: + tg = [(Vector(t).normalized() if t is not None else None) + for t in tangents] + if tangent_scalars is None: + sc = [1.0] * len(tg) + else: + sc = list(tangent_scalars) + # build123d zips tangents with scalars (extra tangents are dropped) + tans = [(list(t * s) if t is not None else []) + for t, s in zip(tg, sc)] + if len(tans) != 2 and len(tans) != len(p3): + raise ValueError('Spline: provide 2 end tangents or one per point') + return _line_object([('interp', p3, [tans, bool(periodic), scale_flag])], + mode) + + +def EllipticalCenterArc(center, x_radius, y_radius, start_angle=0.0, + arc_size=90.0, rotation=0.0, angular_direction=None, + mode=Mode.ADD): + if not isinstance(arc_size, (int, float)): + raise NotImplementedError('EllipticalCenterArc arc limits (Shape/' + 'Axis/...) are not supported in build123d-lite') + c = _v3(center) + if arc_size >= 0: + end_angle = start_angle + arc_size + else: + # negative size sweeps clockwise: same point set as the CCW arc + # from (start + arc_size) to start + start_angle, end_angle = start_angle + arc_size, start_angle + rot = math.radians(rotation) + xd = (math.cos(rot), math.sin(rot), 0.0) + + def at(a): + ar = math.radians(a) + lx, ly = x_radius * math.cos(ar), y_radius * math.sin(ar) + return [c[0] + lx * xd[0] - ly * xd[1], c[1] + lx * xd[1] + ly * xd[0], c[2]] + # gp_Elips requires major >= minor: swap axes when y_radius dominates + if x_radius >= y_radius: + major, minor, a0, a1 = x_radius, y_radius, start_angle, end_angle + xdir = list(xd) + else: + major, minor = y_radius, x_radius + a0, a1 = start_angle - 90.0, end_angle - 90.0 + xdir = [-xd[1], xd[0], 0.0] + spec = ('earc', [at(start_angle), at(end_angle)], + [list(c), xdir, [0.0, 0.0, 1.0], major, minor, a0, a1]) + return _line_object([spec], mode) + + +# ----------------------------------------------- conic & spline 1-D objects +# build123d's analytic 1-D objects that are not circular arcs: exact conics +# (gp_Parab / gp_Hypr / gp_Elips through GC_MakeArcOf*) and exact B-splines +# (Geom_BSplineCurve from poles + knots). All of them build a LOCAL segment +# spec, which BuildLine transforms by its workplane on exit like every other +# 1-D object. + +def _rot_z(v, degrees_): + """Rotate a 3-vector about +Z (the workplane normal for 1-D objects).""" + a = math.radians(degrees_) + ca, sa = math.cos(a), math.sin(a) + return [v[0] * ca - v[1] * sa, v[0] * sa + v[1] * ca, v[2]] + + +def _parabola_point(origin, xdir, ydir, focal, u): + """OCCT gp_Parab parametrization: P(U) = O + U^2/(4 f) X + U Y.""" + k = u * u / (4.0 * focal) + return [origin[i] + k * xdir[i] + u * ydir[i] for i in range(3)] + + +def _hyperbola_point(origin, xdir, ydir, major, minor, u): + """OCCT gp_Hypr parametrization: P(U) = O + a cosh(U) X + b sinh(U) Y.""" + ch, sh = math.cosh(u), math.sinh(u) + return [origin[i] + major * ch * xdir[i] + minor * sh * ydir[i] + for i in range(3)] + + +def _conic_arc_spec(kind, center, xdir, normal, sizes, a1_deg, a2_deg, sense): + """A 'parab'/'hypr' segment spec, with the chaining end points evaluated + from the same parametric equation the kernel will use.""" + ydir = list(Vector(tuple(normal)).cross(Vector(tuple(xdir)))) + u1, u2 = math.radians(a1_deg), math.radians(a2_deg) + if kind == 'parab': + p0 = _parabola_point(center, xdir, ydir, sizes, u1) + p1 = _parabola_point(center, xdir, ydir, sizes, u2) + else: + p0 = _hyperbola_point(center, xdir, ydir, sizes[0], sizes[1], u1) + p1 = _hyperbola_point(center, xdir, ydir, sizes[0], sizes[1], u2) + if not sense: + p0, p1 = p1, p0 + return (kind, [p0, p1], + [list(center), list(xdir), list(normal), sizes, a1_deg, a2_deg, + bool(sense)]) + + +def _arc_limit_curve(specs, arc_limit): + """build123d's numeric-or-limit arc_size: build the half arc both ways, + trim each at its first intersection with the limit and keep the shorter + (ParabolicCenterArc / HyperbolicCenterArc).""" + full = Curve(w.WireFromSegments(_chain_segments(specs)), specs) + edge = _single_edge_of(full) + candidates = ShapeList() + for candidate in (edge, _reverse_1d(edge)): + trimmed = candidate.trim_to_other(arc_limit) + if trimmed is not None: + candidates.append(trimmed) + if not candidates: + raise ValueError('the arc does not intersect the arc limit ' + + repr(arc_limit)) + return candidates.sort_by(Edge.length)[0] + + +def ParabolicCenterArc(vertex, focal_length, start_angle=0.0, end_angle=None, + arc_size=90.0, rotation=0.0, angular_direction=None, + mode=Mode.ADD): + """Parabolic arc about a vertex point (build123d ParabolicCenterArc): + gp_Parab(plane, focal_length) trimmed by GC_MakeArcOfParabola between the + two given "angles" (upstream converts them to radians and passes them as + the curve parameters).""" + c = _v3(vertex) + xdir = _rot_z([1.0, 0.0, 0.0], rotation) + normal = [0.0, 0.0, 1.0] + if end_angle is not None or angular_direction is not None: + if not isinstance(arc_size, (int, float)): + raise ValueError('ParabolicCenterArc limit arc_size cannot be ' + 'combined with end_angle / angular_direction') + end_a = end_angle if end_angle is not None else start_angle + arc_size + sense = angular_direction != AngularDirection.CLOCKWISE + spec = _conic_arc_spec('parab', c, xdir, normal, float(focal_length), + start_angle, end_a, sense) + return _line_object([spec], mode) + if isinstance(arc_size, (int, float)): + spec = _conic_arc_spec('parab', c, xdir, normal, float(focal_length), + start_angle, start_angle + arc_size, + arc_size >= 0) + return _line_object([spec], mode) + spec = _conic_arc_spec('parab', c, xdir, normal, float(focal_length), + start_angle, start_angle + 180.0, True) + trimmed = _arc_limit_curve([spec], arc_size) + return _line_object(_specs_from_topo_edges(trimmed), mode) + + +def HyperbolicCenterArc(center, x_radius, y_radius, start_angle=0.0, + end_angle=None, arc_size=90.0, rotation=0.0, + angular_direction=None, mode=Mode.ADD): + """Hyperbolic arc about a center point (build123d HyperbolicCenterArc): + gp_Hypr trimmed by GC_MakeArcOfHyperbola. gp_Hypr needs major >= minor, so + a taller-than-wide hyperbola is built rotated by 90 degrees with its angle + range shifted to match, exactly like Edge.make_hyperbola.""" + c = _v3(center) + normal = [0.0, 0.0, 1.0] + if y_radius > x_radius: + major, minor, correction = y_radius, x_radius, 90.0 + else: + major, minor, correction = x_radius, y_radius, 0.0 + xdir = _rot_z([1.0, 0.0, 0.0], rotation + correction) + sizes = [float(major), float(minor)] + if end_angle is not None or angular_direction is not None: + if not isinstance(arc_size, (int, float)): + raise ValueError('HyperbolicCenterArc limit arc_size cannot be ' + 'combined with end_angle / angular_direction') + end_a = end_angle if end_angle is not None else start_angle + arc_size + sense = angular_direction != AngularDirection.CLOCKWISE + spec = _conic_arc_spec('hypr', c, xdir, normal, sizes, + start_angle - correction, end_a - correction, + sense) + return _line_object([spec], mode) + if isinstance(arc_size, (int, float)): + spec = _conic_arc_spec('hypr', c, xdir, normal, sizes, + start_angle - correction, + start_angle + arc_size - correction, + arc_size >= 0) + return _line_object([spec], mode) + spec = _conic_arc_spec('hypr', c, xdir, normal, sizes, + start_angle - correction, + start_angle + 180.0 - correction, True) + trimmed = _arc_limit_curve([spec], arc_size) + return _line_object(_specs_from_topo_edges(trimmed), mode) + + +def EllipticalStartArc(start_pnt, start_tangent, x_radius, y_radius, arc_size, + start_angle=None, major_axis_dir=None, mode=Mode.ADD): + """Elliptical arc from a start point + tangent (build123d + EllipticalStartArc): the ellipse frame is derived from the tangent, then + the arc is the ordinary EllipticalCenterArc of that frame.""" + start = Vector(tuple(_v3(start_pnt))) + normal = Vector(0, 0, 1) + + def proj(v): + return v - normal * v.dot(normal) + tangent = proj(Vector(tuple(_v3(start_tangent)))) + if start_angle is not None: + rad = math.radians(start_angle) + pln_tangent = tangent.normalized() + a_radius = -x_radius * math.sin(rad) + b_radius = y_radius * math.cos(rad) + x_dir = (pln_tangent * a_radius - + normal.cross(pln_tangent) * b_radius) * \ + (1.0 / (a_radius * a_radius + b_radius * b_radius)) + pln_x_dir = x_dir.normalized() + elif major_axis_dir is not None: + pln_x_dir = proj(Vector(tuple(_v3(major_axis_dir)))).normalized() + pln_y_dir = normal.cross(pln_x_dir) + start_angle = math.degrees(math.atan2( + -(tangent.dot(pln_x_dir) / x_radius), + (tangent.dot(pln_y_dir) / y_radius))) + rad = math.radians(start_angle) + else: + raise ValueError('Either start_angle or major_axis_dir must be ' + 'provided') + pln_y_dir = normal.cross(pln_x_dir) + origin = start - pln_x_dir * (x_radius * math.cos(rad)) - \ + pln_y_dir * (y_radius * math.sin(rad)) + rotation = math.degrees(math.atan2(pln_x_dir.Y, pln_x_dir.X)) + return EllipticalCenterArc(origin, x_radius, y_radius, + start_angle=start_angle, arc_size=arc_size, + rotation=rotation, mode=mode) + + +# ------------------------------------------- constrained arcs and lines --- +# build123d's ConstrainedArcs/ConstrainedLines are thin wrappers over OCCT's +# 2-D geometric constraint solvers (Geom2dGcc_Circ2d2TanRad, _Circ2d2TanOn, +# _Circ2d3Tan, _Circ2dTanCen, _Circ2dTanOnRad, _Lin2d2Tan, _Lin2dTanObl driven +# through Geom2dGcc_QualifiedCurve). That whole family used to be missing from +# this wasm build - the .d.ts declared it but the module exposed nothing, +# because ONE method (WhichQualifier, which returns GccEnt_Position through +# non-const references Embind cannot bind) failed the compile of every binding +# file in the package. The fork now filters that method, so the solvers are +# real here and the calls below are a statement-for-statement port of +# build123d 0.11.1's topology/constrained_lines.py; the kernel side lives in +# StandardLibrary.js (ConstrainedArcs2D / ConstrainedLines2D). + + +def _tangency_pair(arg): + """Normalize one tangency argument to the {edge|point, qualifier} spec the + kernel helper takes (upstream's _as_gcc_arg input side): a Vertex or a + plain point is upstream's Geom2d_CartesianPoint argument, an Edge/Curve is + a Geom2dGcc_QualifiedCurve, and an Axis is the infinite line through it.""" + qualifier = Tangency.UNQUALIFIED + if isinstance(arg, tuple) and len(arg) == 2 and \ + not isinstance(arg[0], (int, float)): + arg, qualifier = arg[0], arg[1] + if isinstance(arg, Axis): + # upstream passes an Axis through Edge as well: a long line segment + # through the axis is the same qualified curve for the solvers, whose + # tangency parameter is then checked against the segment's range + big = 1e4 + p0 = Vector(arg.position) - Vector(arg.direction) * big + p1 = Vector(arg.position) + Vector(arg.direction) * big + arg = Edge.make_line(p0, p1) + if isinstance(arg, Vertex): + return {'point': list(arg.to_tuple())[:2]} + if isinstance(arg, (Curve, Edge)) and getattr(arg, 'topo', None) is not None: + edge = arg if isinstance(arg, Edge) else _single_edge_of(arg) + return {'edge': edge.topo, 'qualifier': qualifier} + if isinstance(arg, (Wire, Shape)) and getattr(arg, 'topo', None) is not None: + return {'edge': arg.edges()[0].topo, 'qualifier': qualifier} + v = _v3(arg) + return {'point': [v[0], v[1]]} + + +def _constrained_curve(topo_edges, selector, mode): + """Apply the user's selector and hand the result to the BuildLine, like + build123d's BaseCurveObject does.""" + edges = ShapeList([Edge(t) for t in topo_edges]) + selected = selector(edges) if selector is not None else edges + if selected is None: + raise ValueError('selector must return an Edge or list of Edges, not ' + 'None') + if isinstance(selected, (Edge, Curve)): + selected = [selected] + if not selected: + raise ValueError('selector must return an Edge or list of Edges, not ' + 'None') + specs = [] + for edge in selected: + specs.extend(_specs_from_topo_edges(edge)) + return _line_object(specs, mode) + + +def _sagitta_index(sagitta): + if sagitta == Sagitta.BOTH: + return 1 + if sagitta == Sagitta.LONG: + return -1 + return 0 + + +def ConstrainedArcs(*args, radius=None, center=None, center_on=None, + sagitta=Sagitta.SHORT, selector=None, mode=Mode.ADD): + """Circular arc(s) constrained by tangency to other geometry (build123d + ConstrainedArcs). All five upstream overloads are supported, each on the + OCCT solver upstream uses: + + (t1, t2, radius=) Geom2dGcc_Circ2d2TanRad + (t1, t2, center_on=) Geom2dGcc_Circ2d2TanOn + (t1, t2, t3) Geom2dGcc_Circ2d3Tan + (t1, center=) Geom2dGcc_Circ2dTanCen (full circles) + (t1, radius=, center_on=) Geom2dGcc_Circ2dTanOnRad (full circles) + """ + if not args: + raise ValueError('ConstrainedArcs requires at least one tangency') + opts = {'sagitta': _sagitta_index(sagitta)} + if center is not None: + if len(args) != 1: + raise ValueError('ConstrainedArcs(center=) takes one tangency') + c = _v3(center) + opts['center'] = [c[0], c[1]] + elif center_on is not None: + on = center_on[0] if isinstance(center_on, tuple) else center_on + if isinstance(on, Axis): + big = 1e4 + on = Edge.make_line(Vector(on.position) - Vector(on.direction) * big, + Vector(on.position) + Vector(on.direction) * big) + if getattr(on, 'topo', None) is None: + raise TypeError('center_on must be an Edge, Wire or Axis') + opts['centerOn'] = on.topo if isinstance(on, Edge) else on.edges()[0].topo + if len(args) == 1: + if radius is None: + raise ValueError('ConstrainedArcs(center_on=) with one ' + 'tangency also needs radius=') + opts['radius'] = float(radius) + elif len(args) == 3: + pass # three-tangency solver + else: + if radius is None: + raise ValueError('ConstrainedArcs requires radius=, center=, ' + 'center_on= or three tangencies') + if radius <= 0: + raise ValueError('radius must be > 0.0') + opts['radius'] = float(radius) + specs = [_tangency_pair(a) for a in args] + return _constrained_curve(w.ConstrainedArcs2D(specs, opts), selector, mode) + + +def ConstrainedLines(*args, angle=None, direction=None, selector=None, + mode=Mode.ADD): + """Line(s) constrained by tangency (build123d ConstrainedLines): + + (t1, t2) Geom2dGcc_Lin2d2Tan (t2 may be a point) + (t1, axis, angle=|direction=) Geom2dGcc_Lin2dTanObl + """ + if len(args) != 2: + raise ValueError('ConstrainedLines takes exactly two arguments') + if angle is not None or direction is not None: + reference = args[1] + if not isinstance(reference, Axis): + raise TypeError('the oriented form of ConstrainedLines needs an ' + 'Axis as its second argument') + if abs(abs(Vector(reference.direction).Z) - 1) < _TOL_1E6: + raise ValueError("reference Axis can't be perpendicular to " + 'Plane.XY') + if angle is None: + d = _v3(direction) + ref_angle = math.atan2(Vector(reference.direction).Y, + Vector(reference.direction).X) + angle_rad = math.atan2(d[1], d[0]) - ref_angle + else: + angle_rad = math.radians(angle) + opts = {'angle': angle_rad, + 'axis': {'position': [Vector(reference.position).X, + Vector(reference.position).Y], + 'direction': [Vector(reference.direction).X, + Vector(reference.direction).Y]}} + edges = w.ConstrainedLines2D([_tangency_pair(args[0])], opts) + else: + specs = [_tangency_pair(a) for a in args] + edges = w.ConstrainedLines2D(specs, {}) + return _constrained_curve(edges, selector, mode) + + +def BSpline(control_points, knots, degree, weights=None, periodic=False, + mode=Mode.ADD): + """An EXACT B-spline edge from poles, a knot sequence and a degree + (build123d BSpline / Edge.make_bspline): repeated knot values become knot + multiplicities, weights make it rational.""" + knot_list = [float(k) for k in knots] + if not knot_list: + raise ValueError('B-spline requires at least one knot') + poles = [list(_v3(p)) for p in control_points] + unique_knots = [knot_list[0]] + mults = [1] + for knot in knot_list[1:]: + if abs(knot - unique_knots[-1]) <= _TOL_1E6: + mults[-1] += 1 + else: + unique_knots.append(knot) + mults.append(1) + weight_list = [float(x) for x in weights] if weights else [] + params = [poles, unique_knots, mults, int(degree), weight_list, + bool(periodic)] + topo = w.BSplineEdge(poles, unique_knots, mults, int(degree), weight_list, + bool(periodic)) + p0 = list(w._edgePointAt(topo, 0.0)) + p1 = list(w._edgePointAt(topo, 1.0)) + return _line_object([('bspline', [p0, p1], params)], mode) + + +def Airfoil(airfoil_code, n_points=50, finite_te=False, mode=Mode.ADD): + """A NACA 4-digit (or fractional) airfoil section as a closed line + (build123d Airfoil): cosine-spaced chord stations, the standard thickness + distribution and camber line, interpolated as one periodic spline.""" + s = str(airfoil_code).replace('NACA', '').strip() + if '.' in s: + int_part, frac_part = s.split('.', 1) + m = int(int_part[0]) / 100.0 + p = int(int_part[1]) / 10.0 + t = float(('%02d' % int(int_part[2:])) + '.' + frac_part) / 100.0 + else: + m = int(s[0]) / 100.0 + p = int(s[1]) / 10.0 + t = int(s[2:]) / 100.0 + xs = [(1 - math.cos(math.pi * i / (n_points - 1))) / 2.0 + for i in range(n_points)] + a0, a1, a2, a3 = 0.2969, -0.1260, -0.3516, 0.2843 + a4 = -0.1015 if finite_te else -0.1036 + yt = [5 * t * (a0 * math.sqrt(x) + a1 * x + a2 * x ** 2 + a3 * x ** 3 + + a4 * x ** 4) for x in xs] + yc, dyc = [], [] + for x in xs: + if m == 0 or p == 0 or p == 1: + yc.append(0.0) + dyc.append(0.0) + elif x < p: + yc.append(m / p ** 2 * (2 * p * x - x * x)) + dyc.append(2 * m / p ** 2 * (p - x)) + else: + yc.append(m / (1 - p) ** 2 * ((1 - 2 * p) + 2 * p * x - x * x)) + dyc.append(2 * m / (1 - p) ** 2 * (p - x)) + theta = [math.atan(d) for d in dyc] + upper = [(xs[i] - yt[i] * math.sin(theta[i]), + yc[i] + yt[i] * math.cos(theta[i]), 0.0) + for i in range(n_points)] + lower = [(xs[i] + yt[i] * math.sin(theta[i]), + yc[i] - yt[i] * math.cos(theta[i]), 0.0) + for i in range(n_points)] + ordered = upper[::-1] + lower + # dict.fromkeys over build123d Vectors: identity is the position ROUNDED + # to GEOM_KEY_DIGITS (Vector.__hash__), which is what collapses the two + # trailing-edge points (1, +-1.8e-17) into one — without that the + # periodic interpolation is handed a 3.6e-17 closing gap and OCCT's + # BSplCLib::Interpolate fails + unique, seen = [], [] + for pnt in ordered: + key = (round(pnt[0], 5), round(pnt[1], 5), round(pnt[2], 5)) + if key not in seen: + seen.append(key) + unique.append(pnt) + specs = [('interp', [list(pnt) for pnt in unique], + [[], not finite_te, True])] + if finite_te: + specs.append(('line', [list(unique[-1]), list(unique[0])])) + return _line_object(specs, mode) + + +def BlendCurve(curve0, curve1, continuity=ContinuityLevel.C2, end_points=None, + tangent_scalars=None, mode=Mode.ADD): + """A Bezier transition between two curves that matches position, tangent + (C1, cubic) and curvature (C2, quintic) at the join — build123d + BlendCurve's control-point construction, verbatim.""" + tan_scalars = (1.0, 1.0) if tangent_scalars is None else tuple(tangent_scalars) + if len(tan_scalars) != 2: + raise ValueError('tangent_scalars must be a (start, end) pair') + curves = (curve0, curve1) + if end_points is None: + best, end_pnts = None, None + for v0 in curve0.vertices(): + for v1 in curve1.vertices(): + d = (Vector(v0.to_tuple()) - Vector(v1.to_tuple())).length + if best is None or d < best: + best = d + end_pnts = (v0.to_tuple(), v1.to_tuple()) + else: + end_pnts = tuple(end_points) + end_params = [0, 0] + for i in range(2): + given = Vector(tuple(_v3(end_pnts[i]))) + if (given - curves[i].position_at(0)).length < _TOL_1E6: + end_params[i] = 0 + elif (given - curves[i].position_at(1)).length < _TOL_1E6: + end_params[i] = 1 + else: + raise ValueError('end_points must be at either the start or end ' + 'of a curve') + start_pos = curve0.position_at(end_params[0]) + end_pos = curve1.position_at(end_params[1]) + start_deriv = curve0.derivative_at(end_params[0], 1) * tan_scalars[0] + end_deriv = curve1.derivative_at(end_params[1], 1) * tan_scalars[1] + if continuity == ContinuityLevel.C0: + return Line(start_pos, end_pos, mode=mode) + if continuity == ContinuityLevel.C1: + cntl_pnts = [start_pos, start_pos + start_deriv * (1.0 / 3.0), + end_pos - end_deriv * (1.0 / 3.0), end_pos] + else: + start_curv = curve0.derivative_at(end_params[0], 2) + end_curv = curve1.derivative_at(end_params[1], 2) + cntl_pnts = [start_pos, + start_pos + start_deriv * 0.2, + start_pos + start_deriv * 0.4 + start_curv * 0.05, + end_pos - end_deriv * 0.4 + end_curv * 0.05, + end_pos - end_deriv * 0.2, + end_pos] + return Bezier(*cntl_pnts, mode=mode) + + +def Ellipse(x_radius, y_radius, rotation=0, align=(Align.CENTER, Align.CENTER), + mode=Mode.ADD): + """Elliptical sketch face (gp_Elips + GC_MakeArcOfEllipse).""" + if x_radius >= y_radius: + major, minor, xdir = x_radius, y_radius, [1.0, 0.0, 0.0] + p0 = [x_radius, 0, 0] + else: + major, minor, xdir = y_radius, x_radius, [0.0, 1.0, 0.0] + p0 = [0, y_radius, 0] + segs = [('earc', [p0, [-p0[0], -p0[1], 0]], + [[0.0, 0.0, 0.0], xdir, [0.0, 0.0, 1.0], major, minor, 0.0, 180.0]), + ('earc', [[-p0[0], -p0[1], 0], p0], + [[0.0, 0.0, 0.0], xdir, [0.0, 0.0, 1.0], major, minor, 180.0, 360.0])] + maker = lambda: w.MakeFace(w.WireFromSegments(_chain_segments(segs))) + return _sketch_object(maker, ((-x_radius, -y_radius), (x_radius, y_radius)), + rotation, align, mode) + + +def Helix(pitch, height, radius, center=(0, 0, 0), direction=(0, 0, 1), + cone_angle=0, lefthand=False, mode=Mode.ADD): + """Helical curve. COMPROMISE(helix): the exact Geom helix (a curve on a + cylindrical surface) cannot be expressed in segment specs on this WASM + build, so the helix is interpolated (GeomAPI_Interpolate) through dense + parametric samples with analytic per-point tangents — within ~1e-6 of + the true helix, far below harness tolerance.""" + if cone_angle: + raise NotImplementedError('conical Helix is not supported in ' + 'build123d-lite') + turns = height / pitch + n = max(16, int(64 * turns)) + sgn = -1.0 if lefthand else 1.0 + pts = [] + tans = [] + for i in range(n + 1): + a = sgn * 2.0 * math.pi * turns * i / n + pts.append((radius * math.cos(a), radius * math.sin(a), + height * i / n)) + # d/da of the parametric form (direction only; scale=True) + t = Vector(-radius * math.sin(a) * sgn, radius * math.cos(a) * sgn, + pitch / (2.0 * math.pi)).normalized() + tans.append(list(t)) + loc = Plane(Vector(center), z_dir=Vector(direction)).location + fn_dir = lambda d: _mat_vec(loc._R, d) + spec = _seg_transform(('interp', pts, [tans, False, True]), + loc._transform_point, fn_dir) + return _line_object([spec], mode) + + +def edges_to_wires(edges, tol=1e-6): + """Group connected edges into wires (build123d's edges_to_wires). + COMPROMISE(edges-to-wires): ShapeAnalysis_FreeBounds::ConnectEdgesToWires + needs TopTools_HSequenceOfShape, which this wasm build does not bind, so + the chaining is done here on edge endpoints — same grouping, and the + ordering inside each wire is then fixed by ShapeFix_Wire.""" + remaining = [e for e in edges] + wires = ShapeList() + while remaining: + chain = [remaining.pop(0)] + start = chain[0].position_at(0) + end = chain[0].position_at(1) + grew = True + while grew: + grew = False + for i in range(len(remaining)): + p0 = remaining[i].position_at(0) + p1 = remaining[i].position_at(1) + if (p0 - end).length <= tol or (p1 - end).length <= tol: + end = p1 if (p0 - end).length <= tol else p0 + chain.append(remaining.pop(i)) + grew = True + break + if (p1 - start).length <= tol or (p0 - start).length <= tol: + start = p0 if (p1 - start).length <= tol else p1 + chain.insert(0, remaining.pop(i)) + grew = True + break + wires.append(Curve(w.WireFromEdgesFixed([_topo(e) for e in chain], + tol))) + return wires + + +def _specs_from_topo_edges(shape): + """Reconstruct segment specs from raw edges: lines and circular arcs + analytically; anything else (BSplines, ellipses, ...) as an opaque 'raw' + segment that passes the TopoDS edge through exactly (chainable into + wires, but not transformable).""" + specs = [] + for e in shape.edges() if not isinstance(shape, Edge) else [shape]: + t = w._edgeCurveType(e.topo) + p0 = list(w._edgePointAt(e.topo, 0.0)) + p1 = list(w._edgePointAt(e.topo, 1.0)) + if t == 'Line': + specs.append(('line', [p0, p1])) + elif t == 'Circle' and (abs(p0[0] - p1[0]) > _TOL_1E6 or + abs(p0[1] - p1[1]) > _TOL_1E6 or + abs(p0[2] - p1[2]) > _TOL_1E6): + pm = list(w._edgePointAt(e.topo, 0.5)) + specs.append(('arc3', [p0, pm, p1])) + else: + # closed circles (start == end) have no three-point form, and + # anything that is not a line/arc (BSpline, ellipse, ...) rides + # through as an opaque segment carrying the TopoDS edge itself + specs.append(('raw', [p0, p1], [e.topo])) + return specs + + +def SlotArc(arc, height, rotation=0, mode=Mode.ADD): + """Slot along an arc path: BRepOffsetAPI_MakeOffset on the open wire + yields both offset sides plus round end caps — build123d's SlotArc.""" + if isinstance(arc, Curve) and arc._specs: + specs = arc._specs + elif isinstance(arc, (Curve, Edge)): + specs = _specs_from_topo_edges(arc) + else: + raise NotImplementedError('SlotArc requires a curve/edge') + wire = w.WireFromSegments(_chain_segments(specs)) + closed = w.OffsetWire(wire, height / 2.0) + face = w.MakeFace(closed) + return _sketch_object(lambda: face, None, rotation, None, mode) + + +# ------------------------------------------------------------ operations --- + +def _pending_or_given(to_extrude): + """Resolve extrude/revolve/loft profiles: explicit shape(s) or the + enclosing BuildPart's pending sketch faces. Returns [(face_topo, plane)].""" + builder = _active_builder(BuildPart) + profiles = [] + if to_extrude is None: + if builder is None or not builder.pending_faces: + raise ValueError('no sketch profile: pass a shape or create one ' + 'with BuildSketch inside BuildPart') + for shape, plane in zip(builder.pending_faces, + builder.pending_face_planes): + for f in shape.faces(): + profiles.append((f.topo, plane)) + builder.pending_faces = [] + builder.pending_face_planes = [] + else: + for s in _tolist(to_extrude): + for f in s.faces(): + n = w._faceNormal(f.topo) + c = w._faceCentroid(f.topo) + profiles.append((f.topo, Plane(origin=tuple(c), z_dir=tuple(n)))) + return profiles + + +def extrude(to_extrude=None, amount=None, dir=None, until=None, target=None, + both=False, taper=0.0, clean=True, mode=Mode.ADD): + if until is not None: + if until not in (Until.NEXT, Until.LAST): + raise NotImplementedError('extrude until=' + str(until) + + ' is not supported in build123d-lite') + builder = _active_builder(BuildPart) + body = target if target is not None else \ + (builder._obj if builder is not None else None) + if body is None or body.topo is None: + raise ValueError('extrude(until=...) requires a target part') + profiles = _pending_or_given(to_extrude) + bb = list(w.BoundingBox(body.topo, 0.01)) + ln = 3.0 * max(bb[3] - bb[0], bb[4] - bb[1], bb[5] - bb[2]) + results = [] + for (face, plane) in profiles: + d = tuple(plane.z_dir) + candidate = Part(w.Extrude(face, [d[0] * ln, d[1] * ln, d[2] * ln])) + # pieces of the candidate OUTSIDE the body, ordered along dir + outside = Part(w.Difference(candidate.topo, [body.topo], + True, 1e-7, True)) + pieces = outside.solids() + + def proj(s): + sb = list(w.BoundingBox(s.topo, 0.01)) + return (min(sb[0] * d[0], sb[3] * d[0]) + + min(sb[1] * d[1], sb[4] * d[1]) + + min(sb[2] * d[2], sb[5] * d[2])) + pieces = sorted(pieces, key=proj) + if until == Until.NEXT: + # first void between the sketch plane and the body + results.append(pieces[0]) + else: + # everything up to the body's LAST surface: drop the piece + # that extends to the candidate's far end + results.append(candidate - pieces[-1]) + obj = results[0] if len(results) == 1 else results[0] + results[1:] + return _combine(builder, obj, mode) + if amount is None and isinstance(to_extrude, (int, float)): + amount = to_extrude + to_extrude = None + if amount is None: + raise ValueError('extrude requires amount=') + if taper and (both or dir is not None): + raise NotImplementedError('extrude taper with both=/dir= is not ' + 'supported in build123d-lite') + builder = _active_builder(BuildPart) + profiles = _pending_or_given(to_extrude) + results = [] + for (face, plane) in profiles: + if taper: + # build123d's Solid.extrude_taper uses TWO algorithms: LocOpe_DPrism + # only when the extrusion runs along the profile normal with a + # POSITIVE taper and no holes, otherwise a LOFT between the profile + # wires and their 2-D offsets (offset = -length * tan(taper), + # Kind.INTERSECTION), with the inner wires' taper flipped. + profile = Face(face) + inner = profile.inner_wires() + if taper > 0 and not inner and plane.z_dir.Z > 0: + solid = w.TaperExtrude(face, amount, taper) + results.append(Part(solid)) + continue + offset_amt = -abs(amount) * math.tan(math.radians(taper)) + base = Plane(profile) + shift = Pos(base.z_dir.X * amount, base.z_dir.Y * amount, + base.z_dir.Z * amount) + solids = [] + for i, wire in enumerate([profile.outer_wire()] + list(inner)): + flip = -1.0 if i > 0 else 1.0 + local = base.location.inverse() * wire + local_taper = Curve(_topo(local)).offset_2d( + flip * offset_amt, kind=Kind.INTERSECTION) + taper_wire = shift * (base.location * Curve(_topo(local_taper))) + solids.append(Part(w.Loft([_topo(wire), _topo(taper_wire)], + False))) + solid = solids[0] if len(solids) == 1 else \ + (solids[0] - solids[1:]) + results.append(Part(_topo(solid))) + continue + d = tuple(Vector(dir).normalized()) if dir is not None else tuple(plane.z_dir) + vec = [d[0] * amount, d[1] * amount, d[2] * amount] + if both: + f2 = w.Translate([-vec[0], -vec[1], -vec[2]], face, True) + solid = w.Extrude(f2, [2 * vec[0], 2 * vec[1], 2 * vec[2]]) + else: + solid = w.Extrude(face, vec) + results.append(Part(solid)) + obj = results[0] if len(results) == 1 else results[0] + results[1:] + return _combine(builder, obj, mode) + + +def revolve(profiles=None, axis=Axis.Z, revolution_arc=360, clean=True, + mode=Mode.ADD): + builder = _active_builder(BuildPart) + profs = _pending_or_given(profiles) + o = tuple(axis.position) + d = list(axis.direction) + shift = (abs(o[0]) > _TOL or abs(o[1]) > _TOL or abs(o[2]) > _TOL) + results = [] + for (face, plane) in profs: + topo = face + if shift: + topo = w.Translate([-o[0], -o[1], -o[2]], topo) + topo = w.Revolve(topo, revolution_arc, d) + if shift: + topo = w.Translate([o[0], o[1], o[2]], topo) + results.append(Part(topo)) + obj = results[0] if len(results) == 1 else results[0] + results[1:] + return _combine(builder, obj, mode) + + +def make_brake_formed(thickness, station_widths, line=None, side=Side.LEFT, + kind=Kind.ARC, clean=True, mode=Mode.ADD): + """Sheet-metal brake forming (build123d make_brake_formed) - a + statement-for-statement port of operations_part.make_brake_formed. + + The outline is offset by the sheet thickness to get the SECTION, a station + edge is paired to every vertex of the line (the offset vertex exactly + thickness away), each station edge is extruded by its width along the + section plane's normal, and consecutive station faces are swept along the + matching segment of the line and fused.""" + builder = _active_builder(BuildPart) + if line is None: + # upstream reads BuildPart.pending_edges_as_wire; in lite a BuildLine + # directly inside a BuildPart leaves its result in pending_path + line = getattr(builder, 'pending_path', None) if builder else None + if line is None: + raise ValueError('A line must be provided') + builder.pending_path = None + elif isinstance(line, Curve) and len(line.edges()) == 0: + raise ValueError('A line must be provided') + offset_line = line.offset_2d(distance=thickness, kind=kind, side=side, + closed=True) + offset_vertices = offset_line.vertices() + try: + plane = Plane(Face(offset_line)) + except Exception: + raise ValueError('line not suitable - probably straight') + + line_vertices = line.vertices() + if isinstance(station_widths, (int, float)): + widths = [float(station_widths)] * len(line_vertices) + else: + widths = [float(x) for x in station_widths] + if len(widths) != len(line_vertices): + raise ValueError('widths must either be a single number or an ' + 'iterable with a length of the # vertices in line (' + + str(len(line_vertices)) + ')') + + station_edges = ShapeList() + for vertex in line_vertices: + base = Vector(vertex.to_tuple()) + others = offset_vertices.sort_by_distance(base) + for other in others[1:]: + if abs((base - Vector(other.to_tuple())).length - thickness) < 1e-2: + station_edges.append(Edge.make_line(base, + Vector(other.to_tuple()))) + break + station_edges = station_edges.sort_by(line) + + z = Vector(plane.z_dir) + station_faces = [Face.extrude(e, z * width) + for e, width in zip(station_edges, widths)] + sweep_paths = line.edges().sort_by(line) + sections = [] + for i in range(len(station_faces) - 1): + # MakePipeShell needs a WIRE spine; each sweep path here is a single + # edge of the outline + path_topo = w.WireFromEdgesFixed([_topo(sweep_paths[i])]) + sections.append(Part(w.PipeShellSweep( + [w._faceOuterWire(station_faces[i].topo), + w._faceOuterWire(station_faces[i + 1].topo)], + path_topo, False, '', [], 0, True))) + if len(sections) > 1: + solid = sections[0] + for extra in sections[1:]: + solid = solid.fuse(extra) + else: + solid = sections[0] + return _combine(builder, Part(solid.topo), mode) + + +def loft(sections=None, ruled=False, clean=True, mode=Mode.ADD): + if ruled: + raise NotImplementedError('loft(ruled=True) is not supported in ' + 'build123d-lite') + builder = _active_builder(BuildPart) + profs = _pending_or_given(sections) + wires = [] + for (face, plane) in profs: + wires.append(w.GetWire(face)) + solid = w.Loft(wires) + return _combine(builder, Part(solid), mode) + + +def sweep(sections=None, path=None, multisection=False, is_frenet=False, + transition=Transition.TRANSFORMED, normal=None, binormal=None, + clean=True, mode=Mode.ADD): + """Sweep profile faces along a path with BRepOffsetAPI_MakePipeShell, + matching build123d's Solid.sweep / Solid.sweep_multi trihedron and + transition usage (SetMode(is_frenet); normal= -> fixed-binormal gp_Ax2 + with WithCorrection; binormal= wire -> auxiliary spine; multisection + never sets a transition mode and uses each face's OUTER wire only). + COMPROMISE(sweep): the calls match upstream exactly, but MakePipeShell + surfaces differ numerically between OCCT 8.0.1 (this wasm) and OCP 7.x + (~0.02% volume on the multisection handle example).""" + builder = _active_builder(BuildPart) + if path is None and builder is not None: + path = getattr(builder, 'pending_path', None) + if path is None: + raise ValueError('sweep requires path= (or a BuildLine inside the ' + 'BuildPart)') + if isinstance(path, Curve) and path._specs: + # multi-segment curves: build ONE chained wire (their topo may be a + # compound of separate wires, of which GetWire would take only one) + path_topo = w.WireFromSegments(_chain_segments(path._specs)) + else: + path_topo = _topo(path) + if not hasattr(path_topo, 'ShapeType') or path_topo.ShapeType().value != 5: + path_topo = w.GetWire(path_topo, 0, True) + tmap = {Transition.TRANSFORMED: 'transformed', Transition.ROUND: 'round', + Transition.RIGHT: 'right'} + trans = tmap.get(transition, 'transformed') + # 0/[]/'' stand in for "absent" — Brython None breaks CacheOp hashing + binormal_vec = [] + aux_spine = 0 + if binormal is not None: + if isinstance(binormal, Curve) and binormal._specs: + aux_spine = w.WireFromSegments(_chain_segments(binormal._specs)) + else: + aux_spine = _topo(binormal) + elif normal is not None: + binormal_vec = list(Vector(normal)) + profs = _pending_or_given(sections) + if multisection: + wires = [w._faceOuterWire(face) for (face, plane) in profs] + solid = w.PipeShellSweep(wires, path_topo, is_frenet, '', + binormal_vec, aux_spine, True) + return _combine(builder, Part(solid), mode) + results = [] + for (face, plane) in profs: + outer = w._faceOuterWire(face) + inner = [c.topo for c in Face(face).inner_wires()] + solid = w.PipeShellSweep([outer], path_topo, is_frenet, trans, + binormal_vec, aux_spine, True) + if inner: + tools = [w.PipeShellSweep([iw], path_topo, is_frenet, trans, + binormal_vec, aux_spine, True) + for iw in inner] + solid = w.Difference(solid, tools) + results.append(Part(solid)) + obj = results[0] if len(results) == 1 else results[0] + results[1:] + return _combine(builder, obj, mode) + + +def _edges_by_parent(objects): + """(target shape, per-shape edge indices) for fillet/chamfer. + + build123d takes the target from the ACTIVE BUILDER (operations_generic's + target = context._obj) and hands the raw TopoDS edges to + BRepFilletAPI, which matches them by identity — so an edge pool assembled + from several intermediate shapes ([f.outer_wire().edges() for f in + faces], the topology-selection docs' group_hole_area) is perfectly legal + as long as every edge IS an edge of the target. Lite re-wraps shapes, so + the same question is answered geometrically: each edge is mapped onto the + target's edge with the same midpoint and length.""" + edges = [] + for o in _tolist(objects): + if isinstance(o, Edge): + edges.append(o) + elif isinstance(o, (ShapeList, list, tuple)): + for e in o: + if isinstance(e, Edge): + edges.append(e) + elif isinstance(e, (ShapeList, list, tuple)): + edges.extend([x for x in e if isinstance(x, Edge)]) + if not edges: + raise ValueError('no edges given (use shape.edges() selectors)') + builder = _active_builder() + target = builder._obj if (builder is not None and + builder._obj is not None and + builder._obj.topo is not None) else None + if target is None: + target = edges[0].parent + for e in edges: + if e.parent is not target: + raise ValueError('all edges must belong to the same shape') + if target is None or target.topo is None: + raise ValueError('these edges have no parent shape, so they cannot be ' + 'filleted/chamfered (select them with shape.edges() ' + '/ builder.edges(...))') + keyed = {} + for e in target.edges(): + keyed[_shape_key(e, 'edge')] = e.index + indices = [] + for e in edges: + if e.parent is target and e.index is not None: + indices.append(e.index) + continue + idx = keyed.get(_shape_key(e, 'edge')) + if idx is None: + raise ValueError('one of these edges is not an edge of the shape ' + 'being filleted/chamfered (select them with ' + 'shape.edges() / builder.edges(...))') + indices.append(idx) + return target, indices + + +def _wire_common_plane(line): + """The plane a planar wire lies in (build123d Mixin1D.common_plane), with + its origin at the wire's start. The normal comes from Newell's method over + the sampled polyline, which is exact for a planar loop and stable for the + open lines fillet() works on.""" + pts = [] + for edge in line.edges(): + for i in range(5): + pts.append(edge.position_at(i / 4.0)) + nx = ny = nz = 0.0 + for i in range(len(pts)): + a = pts[i] + b = pts[(i + 1) % len(pts)] + nx += (a.Y - b.Y) * (a.Z + b.Z) + ny += (a.Z - b.Z) * (a.X + b.X) + nz += (a.X - b.X) * (a.Y + b.Y) + normal = Vector(nx, ny, nz) + if normal.length <= _TOL_1E6: + # a straight (degenerate) outline - any plane containing it will do + direction = (pts[-1] - pts[0]).normalized() + helper_v = Vector(0, 0, 1) + if abs(direction.dot(helper_v)) > 0.9: + helper_v = Vector(1, 0, 0) + normal = direction.cross(helper_v) + return Plane(origin=line.position_at(0), z_dir=normal.normalized()) + + +def _wire_fillet_corner(edges, index, vertex, radius): + """Fillet ONE corner of a connection-ordered edge list, returning the new + list (build123d's _fillet_wire_corner + _splice_wire_fillet_corner). + + The solver is upstream's primary one, ChFi2d_FilletAlgo, which the fork now + binds; upstream's Geom2dGcc_Circ2d2TanRad fallback is used when ChFi2d + finds no result on this corner (the same two-tangent-arc construction that + backs ConstrainedArcs).""" + e0, e1 = edges[index[0]], edges[index[1]] + point = [vertex.X, vertex.Y, vertex.Z] + solved = w.FilletWireCorner(e0.topo, e1.topo, point, radius) + if solved is not None: + arc = Edge(solved[0]) + trimmed = [Edge(solved[1]), Edge(solved[2])] + else: + # upstream's fallback: every arc of the given radius tangent to both + # edges, nearest the corner, then each edge trimmed at its contact + arcs = ShapeList([Edge(t) for t in w.ConstrainedArcs2D( + [{'edge': e0.topo, 'qualifier': Tangency.UNQUALIFIED}, + {'edge': e1.topo, 'qualifier': Tangency.UNQUALIFIED}], + {'radius': radius, 'sagitta': 1})]) + if not arcs: + raise ValueError('Fillet algorithm failed for ' + str(point) + + ' with radius ' + str(radius)) + arc = arcs.sort_by_distance(Vector(point))[0] + trimmed = [] + for e in (e0, e1): + contact = arc.vertices().sort_by_distance(e)[0] + pieces = _split_1d_at_point(e, Vector(contact.to_tuple())) + far = [v for v in e.vertices() + if (Vector(v.to_tuple()) - Vector(point)).length > _TOL_1E6] + keep = None + for piece in pieces: + for v in piece.vertices(): + for f in far: + if (Vector(v.to_tuple()) - + Vector(f.to_tuple())).length <= _TOL_1E6: + keep = piece + trimmed.append(keep if keep is not None else e) + + out = list(edges) + out[index[0]] = trimmed[0] + out[index[1]] = trimmed[1] + n = len(out) + if index[1] == (index[0] + 1) % n: + insert_at = index[0] + 1 + else: + insert_at = index[1] + 1 + out.insert(insert_at, arc) + return out + + +def _wire_fillet_2d(line, vertices, radius): + """The 1-D corner fillet of an open (or closed) planar wire - build123d's + Wire.fillet_2d, driven from the fillet() operation's 1-D branch. + + Upstream filters the wire's END vertices out in fillet() (they have only + one incident edge), fillets the remaining corners ONE AT A TIME, and + rebuilds the wire from the connection-ordered edge list with the fillet arc + spliced between the two trimmed edges.""" + # Upstream forces the wire onto Plane.XY for the fillet (ChFi2d and the + # Geom2dGcc solvers are 2-D) and maps the result back afterwards. + plane = _wire_common_plane(line) + to_local = plane.location.inverse() + local_line = to_local * line + local_points = [to_local._transform_point(v.to_tuple()) for v in vertices] + edges = ShapeList([Edge(t) for t in w.OrderedEdges(local_line.topo)]) + start = local_line.position_at(0) + end = local_line.position_at(1) + closed = (start - end).length <= _TOL_1E6 + for point in local_points: + v = Vector(point) + if not closed and ((v - start).length <= _TOL_1E6 or + (v - end).length <= _TOL_1E6): + continue # an end vertex cannot be filleted + touching = [] + for i, e in enumerate(edges): + for ev in e.vertices(): + if (Vector(ev.to_tuple()) - v).length <= _TOL_1E6: + touching.append(i) + break + if len(touching) != 2: + raise ValueError('Vertex must connect exactly two edges: ' + + str(v)) + edges = _wire_fillet_corner(edges, touching, v, radius) + result = plane.location * Curve(w.WireFromOrderedEdges( + [e.topo for e in edges])) + # keep the wire's DIRECTION (upstream re-reverses when is_forward flips): + # offset_2d's Side.LEFT/RIGHT is measured against the traversal direction, + # so a flipped result would offset to the other side + original_start = line.position_at(0) + if ((result.position_at(0) - original_start).length > + (result.position_at(1) - original_start).length): + result = _reverse_1d(result) + builder = _active_builder() + if builder is not None: + if isinstance(builder, BuildLine): + builder._specs = _specs_from_topo_edges(result) + builder._obj = result + elif (builder._obj is not None and + (builder._obj is line or builder._obj.topo is line.topo)): + builder._obj = result + return result + + +def _vertex_op_2d(objs, radius, opname): + """2D fillet of sketch corner vertices (BRepFilletAPI_MakeFillet2d).""" + if opname != 'fillet': + raise NotImplementedError('2D vertex chamfers are not supported in ' + 'build123d-lite') + verts = [o for o in objs if isinstance(o, Vertex)] + parent = verts[0].parent + for v in verts: + if v.parent is not parent: + raise ValueError('all vertices must belong to the same sketch') + if parent is None or parent.topo is None: + raise ValueError('fillet: vertices have no parent sketch') + faces = parent.faces() + if len(faces) == 0: + return _wire_fillet_2d(parent, verts, radius) + if len(faces) != 1: + raise NotImplementedError('2D vertex fillets on multi-face sketches ' + 'are not supported in build123d-lite') + pts = [[v.X, v.Y, v.Z] for v in verts] + result = _wrap_like(parent, w.FilletFace2D(faces[0].topo, radius, pts)) + builder = _active_builder() + if builder is not None and builder._obj is not None and \ + (builder._obj is parent or builder._obj.topo is parent.topo): + builder._obj = result + return result + + +def _edge_op(objects, jsfunc, value, opname): + objs = _tolist(objects) + if any(isinstance(o, Vertex) for o in objs): + return _vertex_op_2d(objs, value, opname) + parent, indices = _edges_by_parent(objs) + if parent is None or parent.topo is None: + raise ValueError(opname + ': edges have no parent shape') + ptopo = parent.topo + result = _wrap_like(parent, jsfunc(ptopo, value, indices)) + builder = _active_builder() + if builder is not None and builder._obj is not None and \ + (builder._obj is parent or builder._obj.topo is ptopo): + builder._obj = result + return result + + +def fillet(objects, radius): + return _edge_op(objects, w.FilletEdges, radius, 'fillet') + + +def chamfer(objects, length, length2=None, angle=None): + if length2 is not None or angle is not None: + raise NotImplementedError('asymmetric chamfers are not supported in ' + 'build123d-lite') + return _edge_op(objects, w.ChamferEdges, length, 'chamfer') + + +def offset(objects=None, amount=0, openings=None, kind=Kind.ARC, + side=Side.BOTH, closed=True, min_edge_length=None, + mode=Mode.REPLACE): + if kind == Kind.TANGENT: + raise NotImplementedError('Kind.TANGENT offsets are not supported in ' + 'build123d-lite') + if min_edge_length is not None: + raise NotImplementedError('offset(min_edge_length=) is not supported ' + 'in build123d-lite') + join = 'intersection' if kind == Kind.INTERSECTION else 'arc' + builder = _active_builder() + targets = _tolist(objects) if objects is not None else \ + ([builder._obj] if builder is not None and builder._obj is not None else []) + if not targets and objects is None and isinstance(builder, BuildLine): + # inside BuildLine the line lives in the pending segment specs + line = builder.line + targets = [line] if line is not None else [] + if not targets: + raise ValueError('offset: nothing to offset') + if side != Side.BOTH: + # one-sided offset of an OPEN line (build123d's Wire.offset_2d with + # side=): keep one offset side, optionally closed back onto the line + if len(targets) != 1 or not isinstance(targets[0], (Curve, Edge)): + raise ValueError('offset(side=...) applies to a single line') + src = targets[0] + if isinstance(src, Edge): + src = Curve([src]) + result = src.offset_2d(amount, kind=kind, side=side, closed=closed) + specs = _specs_from_topo_edges(result) + curve = Curve(w.WireFromSegments(_chain_segments(specs)), specs) + if isinstance(builder, BuildLine): + if mode == Mode.REPLACE: + builder._specs = list(specs) + elif mode == Mode.ADD: + builder._specs.extend(specs) + elif mode != Mode.PRIVATE: + raise ValueError('offset: unsupported mode ' + repr(mode) + + ' inside BuildLine') + return curve + return _combine(builder, curve, mode) + if openings is not None: + # hollow the solid, removing the opening faces (MakeThickSolid — + # the same operation build123d performs) + if len(targets) != 1: + raise ValueError('offset(openings=...) expects a single target') + target = targets[0] + faces = [o.topo for o in _tolist(openings)] + result = _wrap_like(target, + w.ThickSolidOffset(_topo(target), faces, amount, 1e-4)) + return _combine(builder, result, mode) + def _is_2d(t): + return t.topo is not None and len(t.solids()) == 0 and \ + len(t.faces()) > 0 + + if all([_is_2d(t) for t in targets]): + # 2-D offset of FACES: upstream offsets the outer wire by +amount and + # every inner wire by -amount, rebuilds the planar face and subtracts + # the (possibly overshooting) inner faces — operations_generic.offset's + # face branch. A 3-D MakeOffsetShape here would thicken the sketch. + new_faces = [] + for t in targets: + for face in t.faces(): + outer = face.outer_wire().offset_2d(amount, kind=kind) + inner_wires = [] + for hole in face.inner_wires(): + try: + inner_wires.append(hole.offset_2d(-amount, kind=kind)) + except Exception: + pass + new_face = Face(outer) + if (new_face.normal_at() - face.normal_at()).length > 0.001: + new_face = -new_face + if inner_wires: + new_face = new_face - [Face(iw) for iw in inner_wires] + new_faces.append(new_face) + obj = Sketch(w.MakeCompound([_topo(f) for f in new_faces])) \ + if len(new_faces) > 1 else Sketch(_topo(new_faces[0])) + return _combine(builder, obj, mode) + + results = [] + for t in targets: + results.append(_wrap_like(t, w.Offset(_topo(t), amount, 1e-4, False, join))) + obj = results[0] if len(results) == 1 else results[0] + results[1:] + return _combine(builder, obj, mode) + + +def _mirror_point(p, o, n): + d = 2.0 * ((p[0] - o[0]) * n[0] + (p[1] - o[1]) * n[1] + (p[2] - o[2]) * n[2]) + return (p[0] - d * n[0], p[1] - d * n[1], p[2] - d * n[2]) + + +def mirror(objects=None, about=Plane.XZ, mode=Mode.ADD): + builder = _active_builder() + # Inside BuildLine (or on spec-carrying curves) mirror at the SEGMENT + # level so make_face()/sweep() can still chain the result exactly. + if isinstance(builder, BuildLine) or ( + objects is not None and + all(isinstance(t, Curve) and t._specs for t in _tolist(objects))): + if objects is None: + src = list(builder._specs) + else: + src = [] + for t in _tolist(objects): + if not (isinstance(t, Curve) and t._specs): + raise NotImplementedError('mirror inside BuildLine needs ' + 'segment-based curves') + src.extend(t._specs) + o = tuple(about.origin) + n = tuple(about.z_dir) + + def _mirror_dir(d): + dd = 2.0 * (d[0] * n[0] + d[1] * n[1] + d[2] * n[2]) + return (d[0] - dd * n[0], d[1] - dd * n[1], d[2] - dd * n[2]) + + def _mirror_seg(s): + m = _seg_transform(s, lambda p: _mirror_point(p, o, n), _mirror_dir) + params = _seg_params(m) + if s[0] == 'earc' and params is not None: + # reflecting xdir AND normal keeps the frame right-handed but + # maps ellipse angle a -> -a: swap and negate the arc range + params[5], params[6] = -params[6], -params[5] + return m + specs = [_mirror_seg(s) for s in src] + curve = Curve(w.WireFromSegments(_chain_segments(specs)), specs) + if isinstance(builder, BuildLine) and mode == Mode.ADD: + builder._specs.extend(specs) + return curve + targets = _tolist(objects) if objects is not None else \ + ([builder._obj] if builder is not None and builder._obj is not None else []) + if not targets: + raise ValueError('mirror: nothing to mirror') + o = tuple(about.origin) + n = list(about.z_dir) + shift = (abs(o[0]) > _TOL or abs(o[1]) > _TOL or abs(o[2]) > _TOL) + results = [] + for t in targets: + topo = _topo(t) + if shift: + topo = w.Translate([-o[0], -o[1], -o[2]], topo, True) + topo = w.Mirror(n, topo) + else: + topo = w.Mirror(n, topo, True) + if shift: + topo = w.Translate([o[0], o[1], o[2]], topo) + results.append(_wrap_like(t, topo)) + obj = results[0] if len(results) == 1 else results[0] + results[1:] + return _combine(builder, obj, mode) + + +def split(objects=None, bisect_by=Plane.XZ, keep=Keep.TOP, mode=Mode.REPLACE): + if keep not in (Keep.TOP, Keep.BOTTOM): + raise NotImplementedError('split keep=' + str(keep) + + ' is not supported in build123d-lite') + builder = _active_builder() + targets = _tolist(objects) if objects is not None else \ + ([builder._obj] if builder is not None and builder._obj is not None else []) + if not targets: + raise ValueError('split: nothing to split') + results = [] + for t in targets: + topo = _topo(t) + bb = list(w.BoundingBox(topo, 0.01)) + size = 4.0 * max(bb[3] - bb[0], bb[4] - bb[1], bb[5] - bb[2], + abs(bb[0]), abs(bb[3]), abs(bb[1]), abs(bb[4]), + abs(bb[2]), abs(bb[5]), 1.0) + half = w.Box(size, size, size, True) + zshift = size / 2.0 if keep == Keep.TOP else -size / 2.0 + half = w.Translate([0, 0, zshift], half) + half_shape = bisect_by.location * Part(half) + results.append(_wrap_like(t, w.Intersection([topo, half_shape.topo]))) + obj = results[0] if len(results) == 1 else results[0] + results[1:] + return _combine(builder, obj, mode) + + +def scale(objects=None, by=1, about=None, mode=Mode.REPLACE): + factors = None + if not isinstance(by, (int, float)): + f = tuple(by) + if len(f) == 2: + f = (f[0], f[1], 1.0) + if abs(f[0] - f[1]) > 1e-9 or abs(f[0] - f[2]) > 1e-9: + factors = [f[0], f[1], f[2]] # gp_GTrsf non-uniform scale + else: + by = f[0] + builder = _active_builder() + if objects is None and isinstance(builder, BuildLine) and factors is None: + # scale the accumulated line segments in place (spec-level, so + # make_face() after scale() still chains exactly) + builder._specs = [_seg_scale(s, by) for s in builder._specs] + return builder.line + targets = _tolist(objects) if objects is not None else \ + ([builder._obj] if builder is not None and builder._obj is not None else []) + + def _scaled(t): + topo = _topo(t) + # build123d scales about about= or the shape's location position + c = _v3(about) if about is not None else tuple(t.location.position) \ + if isinstance(t, Shape) else (0.0, 0.0, 0.0) + if factors is not None: + shift = (abs(c[0]) > _TOL or abs(c[1]) > _TOL or + abs(c[2]) > _TOL) + if shift: + topo = w.Translate([-c[0], -c[1], -c[2]], topo, True) + topo = w.ScaleXYZ(factors, topo) + if shift: + topo = w.Translate([c[0], c[1], c[2]], topo) + else: + topo = w.ScaleUniform(topo, by, list(c)) + return _wrap_like(t, topo) + results = [_scaled(t) for t in targets] + obj = results[0] if len(results) == 1 else results[0] + results[1:] + return _combine(builder, obj, mode) + + +def add(objects, rotation=None, clean=True, mode=Mode.ADD): + builder = _active_builder() + if builder is None: + raise ValueError('add() requires an active builder context') + objs = _tolist(objects) + # build123d's add() replaces each Builder argument with its result and + # drops the ones that have none yet (operations_generic.add's object_iter) + objs = [(o._obj if isinstance(o, Builder) else o) for o in objs + if not (isinstance(o, Builder) and o._obj is None)] + # curves added to a BuildLine contribute their segments (optionally + # rotated) so make_face()/sweep() keep exact geometry + if isinstance(builder, BuildLine): + rot = None + if rotation is not None: + r = (0, 0, rotation) if isinstance(rotation, (int, float)) else tuple(rotation) + rot = Rotation(r[0], r[1], r[2]) + ctx_locs = _ctx_locations() + out = [] + for o in objs: + if isinstance(o, Curve) and o._specs: + specs = list(o._specs) + elif isinstance(o, (Curve, Edge)): + specs = _specs_from_topo_edges(o) + else: + raise TypeError('add() to BuildLine expects curves') + if rot is not None: + fn_dir = lambda d: _mat_vec(rot._R, d) + specs = [_seg_transform(s, rot._transform_point, fn_dir, rot) + for s in specs] + # replicate at the active Locations contexts, like every other + # object creation (build123d dimension-arrow pattern) + placed_specs = [] + for loc in ctx_locs: + fn_dir = lambda d: _mat_vec(loc._R, d) + placed_specs.extend([_seg_transform(s, loc._transform_point, + fn_dir, loc) + for s in specs]) + builder._specs.extend(placed_specs) + out.append(Curve(w.WireFromSegments(_chain_segments(placed_specs)), + placed_specs)) + return out[0] if len(out) == 1 else ShapeList(out) + # 2D objects added to a BuildPart become pending sketch faces (build123d) + def _is_2d(o): + if not (isinstance(o, Shape) and o.topo is not None): + return False + count = [0] + def _cb(i, s): + count[0] += 1 + w.ForEachSolid(o.topo, _cb) + return count[0] == 0 and len(o.faces()) > 0 + if isinstance(builder, BuildPart) and mode == Mode.ADD and \ + all(_is_2d(o) for o in objs): + for o in objs: + for f in o.faces(): + n = w._faceNormal(f.topo) + c = w._faceCentroid(f.topo) + builder.pending_faces.append(Sketch(f.topo)) + builder.pending_face_planes.append( + Plane(origin=tuple(c), z_dir=tuple(n))) + return objs[0] if len(objs) == 1 else ShapeList(objs) + results = [] + for o in objs: + s = _wrap_like(o, _topo(o)) + if rotation is not None: + r = tuple(rotation) if not isinstance(rotation, (int, float)) else (0, 0, rotation) + s = Rotation(r[0], r[1], r[2]) * s + # replicate at location contexts / workplanes like object creation + locs = _ctx_locations() + planes = builder.workplanes if isinstance(builder, BuildPart) else [Plane.XY] + placed = [] + for pl in planes: + for loc in locs: + placed.append((pl.location * loc) * _wrap_like(s, w.Translate([0, 0, 0], s.topo, True))) + results.append(placed[0] if len(placed) == 1 else placed[0] + placed[1:]) + obj = results[0] if len(results) == 1 else results[0] + results[1:] + return _combine(builder, obj, mode) + + +def make_face(edges=None, mode=Mode.ADD): + builder = _active_builder(BuildSketch) + if edges is None: + if builder is None or not builder.pending_edge_specs: + raise ValueError('make_face: no pending edges (draw with ' + 'BuildLine inside BuildSketch first)') + specs = builder.pending_edge_specs + builder.pending_edge_specs = [] + else: + specs = [] + for e in _tolist(edges): + if isinstance(e, Curve) and e._specs: + specs.extend(e._specs) + elif isinstance(e, (Curve, Edge)): + specs.extend(_specs_from_topo_edges(e)) + else: + raise NotImplementedError('make_face from non-curve objects') + chained = _chain_segments(specs) + wire = w.WireFromSegments(chained) + face = w.MakeFace(wire) + # build123d's make_face goes through _add_to_context, which CLEANS the + # result (ShapeUpgrade_UnifySameDomain): tangent-continuous Bezier/spline + # edges merge into one B-spline. That changes the geometry slightly (the + # merged spline approximates the chain), so skipping it makes downstream + # results diverge — bicycle_tire's revolved tire was 0.84% off with 40 + # profile edges instead of upstream's 37. + face = w.UnifyWire(face, True) + # normalize XY-planar faces to a +Z normal (build123d faces from wires + # come out +Z regardless of the chained winding; ours follow the wire) + n = w._faceNormal(face) + bb = w.BoundingBox(face) + if n[2] < -0.5 and bb and abs(bb[5] - bb[2]) < 1e-6: + face = w.ReverseFace(face) + return _combine(builder, Sketch(face), mode) + + +def bounding_box(objects=None, mode=Mode.PRIVATE): + """The bounding box as an object: a Rectangle sketch for 2D input, a Box + part for 3D (build123d's bounding_box operation).""" + builder = _active_builder() + targets = _tolist(objects) if objects is not None else \ + ([builder._obj] if builder is not None and builder._obj is not None else []) + if not targets: + raise ValueError('bounding_box: nothing to measure') + results = [] + for t in targets: + bb = list(w.BoundingBox(_topo(t))) + flat = abs(bb[5] - bb[2]) < 1e-9 + if flat: + face = w.Polygon([[bb[0], bb[1], bb[2]], [bb[3], bb[1], bb[2]], + [bb[3], bb[4], bb[2]], [bb[0], bb[4], bb[2]]]) + results.append(Sketch(face)) + else: + box = w.Translate([(bb[0] + bb[3]) / 2, (bb[1] + bb[4]) / 2, + (bb[2] + bb[5]) / 2], + w.Box(bb[3] - bb[0], bb[4] - bb[1], + bb[5] - bb[2], True)) + results.append(Part(box)) + obj = results[0] if len(results) == 1 else results[0] + results[1:] + return _combine(builder, obj, mode) + + +def _convex_hull_2d(pts): + """Andrew monotone chain over (x, y) tuples -> CCW hull without the + closing point.""" + pts = sorted(set(pts)) + if len(pts) <= 2: + return list(pts) + + def cross(o, a, b): + return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]) + lower = [] + for p in pts: + while len(lower) >= 2 and cross(lower[-2], lower[-1], p) <= 0: + lower.pop() + lower.append(p) + upper = [] + for p in reversed(pts): + while len(upper) >= 2 and cross(upper[-2], upper[-1], p) <= 0: + upper.pop() + upper.append(p) + return lower[:-1] + upper[:-1] + + +def _simplify_polyline(pts, tol): + """Douglas-Peucker on a closed polygon (keeps hull bbox within tol).""" + if len(pts) < 8: + return list(pts) + + def dp(seg): + if len(seg) < 3: + return list(seg) + (x1, y1), (x2, y2) = seg[0], seg[-1] + dx, dy = x2 - x1, y2 - y1 + ln = math.hypot(dx, dy) or 1.0 + worst, wi = -1.0, 0 + for i in range(1, len(seg) - 1): + d = abs(dy * (seg[i][0] - x1) - dx * (seg[i][1] - y1)) / ln + if d > worst: + worst, wi = d, i + if worst <= tol: + return [seg[0], seg[-1]] + left = dp(seg[:wi + 1]) + return left[:-1] + dp(seg[wi:]) + half = len(pts) // 2 + a = dp(pts[:half + 1]) + b = dp(pts[half:] + pts[:1]) + return a[:-1] + b[:-1] + + +def _connected_edges_of(edge, parent): + """The edges of parent that share a vertex with edge (build123d's + topo_explore_connected_edges). Upstream accumulates into a set, so ITS + order is memory-address order and varies run to run; lite keeps the + parent's own edge order, which is deterministic.""" + if parent is None: + raise ValueError('edge must be extracted from shape') + keys = [_shape_key(v, 'vertex') for v in edge.vertices()] + out = ShapeList() + for other in parent.edges(): + if _shape_key(other, 'edge') == _shape_key(edge, 'edge'): + continue + for v in other.vertices(): + if _shape_key(v, 'vertex') in keys: + out.append(other) + break + return out + + +def full_round(edge, invert=False, voronoi_point_count=100, mode=Mode.REPLACE): + """Replace an edge of the sketch's face with the arc of the largest empty + circle that fits in the face (build123d full_round) - a + statement-for-statement port of operations_sketch.full_round. + + The candidate centres are the VORONOI VERTICES of 101 samples per edge over + the target edge and its two neighbours; the best three (by how equal their + three edge distances are) are averaged. The scipy shim's 2-D Voronoi is a + Bowyer-Watson triangulation whose circumcentres are qhull's finite Voronoi + vertices - verified vertex-set-identical to scipy on exactly these + inputs.""" + from scipy.spatial import Voronoi + + builder = _active_builder(BuildSketch) + if not isinstance(edge, Edge): + raise ValueError('A single Edge must be provided') + parent = getattr(edge, 'parent', None) + if parent is None and builder is not None: + parent = builder._obj + connected = _connected_edges_of(edge, parent) + if len(connected) != 2: + raise ValueError('Invalid geometry - 3 or more edges required') + + edge_group = [edge] + list(connected) + points = [] + for e in edge_group: + for i in range(voronoi_point_count + 1): + v = e.position_at(i / voronoi_point_count) + points.append([v.X, v.Y]) + vertices = [Vector(v[0], v[1], 0) for v in Voronoi(points).vertices] + + best_three = [(float('inf'), 0), (float('inf'), 0), (float('inf'), 0)] + for i, v in enumerate(vertices): + distances = [e.distance_to(v) for e in edge_group] + avg = sum(distances) / 3 + difference = max([abs(d - avg) for d in distances]) + if difference < best_three[-1][0]: + best_three[-1] = (difference, i) + best_three.sort(key=lambda x: x[0]) + center = Vector(0, 0, 0) + for _, i in best_three: + center = center + vertices[i] + center = center * (1.0 / 3.0) + + ends = [e.distance_to_with_closest_points(center)[1] for e in connected] + middle = edge.distance_to_with_closest_points(center)[1] + + origin = (ends[0] + ends[1]) * 0.5 + x_dir = (ends[1] - ends[0]).normalized() + to_arc = origin - middle + z_dir = (to_arc - x_dir * to_arc.dot(x_dir)).normalized() + split_pln = Plane(origin=origin, x_dir=x_dir, z_dir=z_dir) + trimmed = [] + for e in connected: + piece = e.split(split_pln) + if piece is None: + raise ValueError('Invalid geometry to create the end arc') + trimmed.append(piece) + + if invert: + middle = center * 2 - middle + + new_arc = Edge.make_three_point_arc(ends[0], middle, ends[1]) + + keep_keys = [_shape_key(e, 'edge') for e in [edge] + list(connected)] + others = ShapeList([e for e in parent.edges() + if _shape_key(e, 'edge') not in keep_keys]) + + wires = Wire.combine(list(trimmed) + [new_arc] + list(others)) + wires = ShapeList(wires).sort_by(SortBy.LENGTH, reverse=True) + pending = Face(wires[0], list(wires[1:])) + if parent.faces()[0].normal_at() != pending.normal_at(): + pending = -pending + result = Sketch(pending.topo) + if builder is not None: + _combine(builder, result, mode) + builder.pending_edge_specs = [] + return result + + +def make_hull(edges=None, tolerance=1e-3, mode=Mode.ADD): + """Face from the 2D convex hull of the given edges (or the pending edges + + the sketch under construction) — a statement-for-statement port of + build123d's Wire.make_convex_hull: sample every edge at + int(2 / tolerance) parameters, take the 2-D convex hull of the cloud, then + read the hull back as (a) straight CONNECTING edges between the sampled + contact points and (b) TRIMMED pieces of the source edges between them. The + arcs of the hull are therefore the source arcs, exactly, and the only + approximation is where the tangent lines touch them (upstream's own + documented limitation).""" + builder = _active_builder(BuildSketch) + hull_edges = [] + if edges is not None: + for e in _tolist(edges): + hull_edges.extend([e] if isinstance(e, Edge) else e.edges()) + elif builder is not None: + if builder.pending_edge_specs: + tmp = Curve(w.WireFromSegments( + _chain_segments(builder.pending_edge_specs))) + hull_edges.extend(tmp.edges()) + builder.pending_edge_specs = [] + if builder._obj is not None and builder._obj.topo is not None: + hull_edges.extend(builder._obj.edges()) + if not hull_edges: + raise ValueError('No objects to create a hull') + # 1) a cloud of points along all edges (upstream's fragments_per_edge) + fragments = int(2 / tolerance) + pts = [] + lookup = [] # global point index -> (edge index, edge parameter) + for ei, e in enumerate(hull_edges): + for i in range(fragments): + param = i / (fragments - 1) + q = e.position_at(param) + pts.append((q.X, q.Y, len(lookup))) + lookup.append((ei, param)) + hull = _convex_hull_2d(pts) + if len(hull) < 3: + raise ValueError('make_hull: degenerate hull') + # 2) the hull facets, as scipy would hand them over: index pairs. A cyclic + # hull of N vertices has exactly N of them. + simplices = [(hull[i][2], hull[(i + 1) % len(hull)][2]) + for i in range(len(hull))] + # 3+4) connecting edges within one source edge and between two of them + connecting_edge_data = [] + trim_points = {} + + def _mark(edge_index, point_index): + if edge_index not in trim_points: + trim_points[edge_index] = [point_index] + else: + trim_points[edge_index].append(point_index) + for s0, s1 in simplices: + e0, u0 = lookup[s0] + e1, u1 = lookup[s1] + if e0 != e1: + _mark(e0, s0) + _mark(e1, s1) + connecting_edge_data.append(((e0, u0), (e1, u1))) + elif abs(s0 - s1) != 1: + lo, hi = min(s0, s1), max(s0, s1) + _mark(e0, lo) + _mark(e0, hi) + connecting_edge_data.append(((e0, lookup[lo][1]), + (e0, lookup[hi][1]))) + # 5) pair the trim points up per edge + trim_data = {} + for edge_index in trim_points: + s_points = sorted(trim_points[edge_index]) + pairs = [] + for i in range(0, len(s_points) - 1, 2): + if s_points[i] != s_points[i + 1]: + pairs.append((s_points[i], s_points[i + 1])) + trim_data[edge_index] = pairs + # 6) the connecting (tangent/chord) edges + result_edges = [Edge.make_line(hull_edges[a[0]].position_at(a[1]), + hull_edges[b[0]].position_at(b[1])) + for a, b in connecting_edge_data] + # 7) the surviving pieces of the source edges + for edge_index in trim_data: + for (p0, p1) in trim_data[edge_index]: + result_edges.append(hull_edges[edge_index].trim( + lookup[p0][1], lookup[p1][1])) + # 8) one wire, then the planar face the sketch wants + wire = w.WireFromEdgesFixed([e.topo for e in result_edges], _TOL_1E6) + face = w.MakeFace(wire) + if w._faceNormal(face)[2] < -0.5: + face = w.ReverseFace(face) + return _combine(builder, Sketch(face), mode) + + +def draft(faces, neutral_plane, angle): + """Apply a draft angle to faces of the active part + (BRepOffsetAPI_DraftAngle — build123d's Solid.draft conventions).""" + face_list = _tolist(faces) + if not face_list: + raise ValueError('draft: no faces given') + parent = face_list[0].parent + builder = _active_builder(BuildPart) + target = parent if parent is not None else \ + (builder._obj if builder is not None else None) + if target is None or target.topo is None: + raise ValueError('draft: faces have no parent part') + result = _wrap_like(target, w.DraftAngleFaces( + target.topo, [f.topo for f in face_list], angle, + list(neutral_plane.origin), list(neutral_plane.z_dir))) + if builder is not None: + builder._obj = builder._wrap(result.topo) + return result + + +def project(objects=None, workplane=None, target=None, mode=Mode.ADD): + """Project objects along a workplane's normal onto a target. + COMPROMISE(project): only the BuildPart form used by the examples is + implemented — pending sketch faces are projected onto the part + (Face.project_to_shape) and the NEAREST resulting face(s) become the + new pending faces for a following extrude(); the BuildLine/BuildSketch + screen-projection forms raise.""" + builder = _active_builder(BuildPart) + if objects is None and isinstance(builder, BuildPart) and \ + builder.pending_faces: + object_list = list(builder.pending_faces) + planes = list(builder.pending_face_planes) + builder.pending_faces = [] + builder.pending_face_planes = [] + workplane = workplane or (planes[0] if planes else Plane.XY) + if target is None: + target = builder._obj + if target is None or target.topo is None: + raise ValueError('project: no target part') + tc = target.center() + for obj in object_list: + for f in obj.faces(): + oc_ = f.center() + d = Vector(workplane.z_dir) + # aim the projection at the target + if d.dot(tc - oc_) < 0: + d = -d + projected = f.project_to_shape(target, tuple(d)) + if not projected: + raise ValueError('project: projection missed the part') + # ALL projected surface pieces become pending faces (front + # AND back, like build123d's project into BuildPart). Their + # pending plane z_dir is the REVERSED projection direction + # (validated against 0.11.1 maker_coin: a following + # extrude(-depth, SUBTRACT) cuts front pieces INTO the part + # and back pieces harmlessly out the back). + back = -d + for piece in projected: + c = w._faceCentroid(piece.topo) + builder.pending_faces.append(Sketch(piece.topo)) + builder.pending_face_planes.append( + Plane(origin=tuple(c), z_dir=tuple(back))) + return Sketch(w.MakeCompound( + [s.topo for s in builder.pending_faces], True)) \ + if len(builder.pending_faces) > 1 else builder.pending_faces[0] + raise NotImplementedError('project onto a screen workplane is not ' + 'supported in build123d-lite (only the ' + 'BuildPart pending-faces form)') + + +def thicken(to_thicken=None, amount=None, normal_override=None, both=False, + clean=True, mode=Mode.ADD): + """Thicken face(s) into solid(s) along their normals — build123d's + operations_part.thicken over Solid.thicken.""" + if amount is None: + raise ValueError('An amount must be provided') + builder = _active_builder(BuildPart) + if to_thicken is None: + faces = [Face(t) for t, _pl in _pending_or_given(None)] + elif isinstance(to_thicken, (list, tuple, ShapeList)): + faces = [f for f in to_thicken] + else: + faces = list(to_thicken.faces()) + solids = [] + for f in faces: + n = normal_override if normal_override is not None else f.normal_at() + for direction in ([1, -1] if both else [1]): + solids.append(Part.thicken( + f, amount, normal_override=Vector(n) * direction)) + result = solids[0] if len(solids) == 1 else Part().fuse(*solids) + if builder is not None: + return _combine(builder, result, mode, Part) + return Part(_topo(result)) + + +def section(obj=None, section_by=Plane.XZ, height=0.0, clean=True, + mode=Mode.PRIVATE): + """Cross-section of a part: intersect (BRepAlgoAPI_Common) with a large + finite rectangle face on each section plane, exactly like build123d's + operations_part.section. Returns a Sketch of the section faces; default + mode is Mode.PRIVATE (the section does NOT modify the builder).""" + builder = _active_builder(BuildPart) + to_section = obj if obj is not None else \ + (builder._obj if builder is not None else None) + if to_section is None: + raise ValueError('section: no object to section') + body = _topo(to_section) + bb = list(w.BoundingBox(body)) + diag = math.sqrt((bb[3] - bb[0]) ** 2 + (bb[4] - bb[1]) ** 2 + + (bb[5] - bb[2]) ** 2) + max_size = max(abs(v) for v in bb) + diag + planes_in = section_by if isinstance(section_by, (list, tuple)) \ + else [section_by] + faces = [] + for pl in planes_in: + cut_plane = Plane(origin=tuple(pl.origin + pl.z_dir * height), + z_dir=tuple(pl.z_dir)) + rect = Face.make_rect(2 * max_size, 2 * max_size, cut_plane) + common = w.Intersection([body, rect.topo], True, 1e-7, True) + faces.extend(Shape(common).faces()) + if not faces: + raise ValueError('section: no intersection with the section plane') + topos = [f.topo for f in faces] + topo = topos[0] if len(topos) == 1 else w.MakeCompound(topos, True) + return _combine(builder, Sketch(topo), mode) + + +# --------------------------------------------------- joints & exporters --- + +def _hlr_curve(topo): + c = Curve(topo) + return c + + +class ExportSVG: + """No-op SVG exporter: geometry-side effects only (browser worker has + no filesystem for the .svg — a warning is printed instead).""" + + def __init__(self, *args, **kwargs): + print('build123d-lite: ExportSVG writes nothing in the browser') + + def add_layer(self, *args, **kwargs): + return self + + def add_shape(self, *args, **kwargs): + return self + + def write(self, *args, **kwargs): + return True + + +def _unsupported(name): + def f(*args, **kwargs): + raise NotImplementedError(name + ' is not supported in build123d-lite') + return f + + +# COMPROMISE(joints): joints are pure LOCATION ALGEBRA on lite shapes — a +# named attachment frame per part, with connect_to solving the same relative +# Location build123d does and repositioning the other part's baked geometry. +# There is NO assembly structure (no anytree parent/child, no XCAF, no +# symbol/triad rendering); that is a separate roadmap item. +class Joint: + """Named attachment frame bound to a part (build123d Joint ABC).""" + + def __init__(self, label, parent): + self.label = label + self.parent = parent + self.connected_to = None + + @property + def location(self): + return self.parent.location * self.relative_location + + def _connect_to(self, other, **kwargs): + if not isinstance(other, Joint): + raise TypeError('other must be a Joint, not ' + + type(other).__name__) + relative_location = self.relative_to(other, **kwargs) + other.parent.locate(self.parent.location * relative_location) + self.connected_to = other + + def connect_to(self, other, **kwargs): + return self._connect_to(other, **kwargs) + + @property + def symbol(self): + """The viewer symbol upstream draws for this joint (build123d + Joint.symbol). Base form (RigidJoint): a triad at the joint frame, + scaled to the parent's bounding-box diagonal / 12.""" + size = self.parent.bounding_box().diagonal / 12 + return Compound.make_triad(axes_scale=size).locate(self.location) + + def _lite_rebind(self, new_parent): + """A copy of this joint bound to new_parent (used by copy.copy).""" + c = self.__class__.__new__(self.__class__) + c.__dict__.update(self.__dict__) + c.parent = new_parent + c.connected_to = None + return c + + +def _joint_part(to_part): + if to_part is None: + builder = _active_builder(BuildPart) + if builder is None: + raise ValueError('Either specify to_part or place in BuildPart ' + 'scope') + return builder + return to_part + + +class RigidJoint(Joint): + def __init__(self, label, to_part=None, joint_location=None): + part = _joint_part(to_part) + if joint_location is None: + joint_location = Location() + self.relative_location = part.location.inverse() * joint_location + part.joints[label] = self + Joint.__init__(self, label, part) + + def relative_to(self, other, **kwargs): + if isinstance(other, RigidJoint): + return self.relative_location * other.relative_location.inverse() + if isinstance(other, RevoluteJoint): + return other.relative_to(self, + angle=kwargs.get('angle')).inverse() + if isinstance(other, LinearJoint): + return other.relative_to( + self, position=kwargs.get('position')).inverse() + if isinstance(other, CylindricalJoint): + return other.relative_to(self, position=kwargs.get('position'), + angle=kwargs.get('angle')).inverse() + if isinstance(other, BallJoint): + return other.relative_to(self, + angles=kwargs.get('angles')).inverse() + raise TypeError('unsupported joint pairing') + + +class RevoluteJoint(Joint): + def __init__(self, label, to_part=None, axis=None, + angle_reference=None, angular_range=(0, 360), **kwargs): + part = _joint_part(to_part) + if axis is None: + axis = Axis.Z + self.angular_range = angular_range + if angle_reference is not None: + self.angle_reference = Vector(angle_reference) + else: + self.angle_reference = Plane(origin=(0, 0, 0), + z_dir=axis.direction).x_dir + self.relative_axis = axis.located(part.location.inverse()) + part.joints[label] = self + Joint.__init__(self, label, part) + + @property + def location(self): + return self.parent.location * self.relative_axis.location + + @property + def symbol(self): + """Axis of rotation (build123d RevoluteJoint.symbol).""" + radius = self.parent.bounding_box().diagonal / 30 + return Compound([Edge.make_line((0, 0, 0), (0, 0, radius * 10)), + Edge.make_circle(radius), + Edge.make_line((0, 0, 0), (radius, 0, 0))] + ).move(self.location) + + def relative_to(self, other, angle=None, **kwargs): + if not isinstance(other, RigidJoint): + raise TypeError('RevoluteJoint.relative_to expects a RigidJoint') + angle_degrees = self.angular_range[0] if angle is None else angle + if angle_degrees < self.angular_range[0] or \ + angle_degrees > self.angular_range[1]: + raise ValueError('angle (' + str(angle_degrees) + + ') must be in range of ' + + str(self.angular_range)) + # build123d: "Avoid strange rotations when angle is zero" quirk + if angle_degrees == 0.0: + angle_degrees = 360.0 + return (self.relative_axis.location * Rotation(0, 0, angle_degrees) * + other.relative_location.inverse()) + + +class LinearJoint(Joint): + def __init__(self, label, to_part=None, axis=None, + linear_range=(0, 1e30), **kwargs): + part = _joint_part(to_part) + if axis is None: + axis = Axis.Z + self.axis = axis + self.linear_range = linear_range + self.position = None + self.relative_axis = axis.located(part.location.inverse()) + self.angle = None + part.joints[label] = self + Joint.__init__(self, label, part) + + @property + def location(self): + return self.parent.location * self.relative_axis.location + + @property + def symbol(self): + """Linear axis (build123d LinearJoint.symbol).""" + radius = (self.linear_range[1] - self.linear_range[0]) / 15 + return Compound([Edge.make_line((0, 0, self.linear_range[0]), + (0, 0, self.linear_range[1])), + Edge.make_circle(radius)]).move(self.location) + + def relative_to(self, other, position=None, angle=None, **kwargs): + position = sum(self.linear_range) / 2 if position is None else position + if not self.linear_range[0] <= position <= self.linear_range[1]: + raise ValueError('position (' + str(position) + + ') must be in range of ' + + str(self.linear_range)) + self.position = position + if isinstance(other, RevoluteJoint): + angle = other.angular_range[0] if angle is None else angle + if not other.angular_range[0] <= angle <= other.angular_range[1]: + raise ValueError('angle out of range') + rotation = Location(Plane( + origin=(0, 0, 0), + x_dir=other.angle_reference.rotate(other.relative_axis, angle), + z_dir=other.relative_axis.direction)) + else: + angle = 0.0 + rotation = Location() + self.angle = angle + joint_relative_position = Location( + self.relative_axis.position + + self.relative_axis.direction * position) * rotation + if isinstance(other, RevoluteJoint): + other_relative_location = Location(other.relative_axis.position) + else: + other_relative_location = other.relative_location + return joint_relative_position * other_relative_location.inverse() + + +class CylindricalJoint(Joint): + def __init__(self, label, to_part=None, axis=None, angle_reference=None, + linear_range=(0, 1e30), angular_range=(0, 360), **kwargs): + part = _joint_part(to_part) + if axis is None: + axis = Axis.Z + self.axis = axis + if angle_reference is not None: + self.angle_reference = Vector(angle_reference) + else: + self.angle_reference = Plane(origin=(0, 0, 0), + z_dir=axis.direction).x_dir + self.angular_range = angular_range + self.linear_range = linear_range + self.relative_axis = axis.located(part.location.inverse()) + self.position = None + self.angle = None + part.joints[label] = self + Joint.__init__(self, label, part) + + @property + def location(self): + return self.parent.location * self.relative_axis.location + + @property + def symbol(self): + """Cylindrical axis (build123d CylindricalJoint.symbol).""" + radius = (self.linear_range[1] - self.linear_range[0]) / 15 + return Compound([Edge.make_line((0, 0, self.linear_range[0]), + (0, 0, self.linear_range[1])), + Edge.make_circle(radius), + Edge.make_line((0, 0, 0), (radius, 0, 0))] + ).move(self.location) + + def relative_to(self, other, position=None, angle=None, **kwargs): + if not isinstance(other, RigidJoint): + raise TypeError('CylindricalJoint.relative_to expects a ' + 'RigidJoint') + position = sum(self.linear_range) / 2 if position is None else position + if not self.linear_range[0] <= position <= self.linear_range[1]: + raise ValueError('position (' + str(position) + + ') must be in range of ' + + str(self.linear_range)) + self.position = position + angle = sum(self.angular_range) / 2 if angle is None else angle + if not self.angular_range[0] <= angle <= self.angular_range[1]: + raise ValueError('angle (' + str(angle) + + ') must be in range of ' + + str(self.angular_range)) + self.angle = angle + joint_relative_position = Location( + self.relative_axis.position + + self.relative_axis.direction * position) + joint_rotation = Location(Plane( + origin=(0, 0, 0), + x_dir=self.angle_reference.rotate(self.relative_axis, angle), + z_dir=self.relative_axis.direction)) + return (joint_relative_position * joint_rotation * + other.relative_location.inverse()) + + +class BallJoint(Joint): + def __init__(self, label, to_part=None, joint_location=None, + angular_range=((0, 360), (0, 360), (0, 360)), + angle_reference=None, **kwargs): + part = _joint_part(to_part) + if joint_location is None: + joint_location = Location() + self.relative_location = part.location.inverse() * joint_location + part.joints[label] = self + self.angular_range = angular_range + self.angle_reference = angle_reference if angle_reference is not None \ + else Plane.XY + Joint.__init__(self, label, part) + + def relative_to(self, other, angles=None, **kwargs): + if not isinstance(other, RigidJoint): + raise TypeError('BallJoint.relative_to expects a RigidJoint') + if isinstance(angles, Rotation): + angle_rotation = angles + elif isinstance(angles, (tuple, list)): + angle_rotation = Rotation(angles[0], angles[1], angles[2]) + elif angles is None: + angle_rotation = Rotation(self.angular_range[0][0], + self.angular_range[1][0], + self.angular_range[2][0]) + else: + raise TypeError('angles is of an unknown type') + rotation = angle_rotation * self.angle_reference.location + o = rotation.orientation + for i, r in enumerate((o.X, o.Y, o.Z)): + if not self.angular_range[i][0] <= r <= self.angular_range[i][1]: + raise ValueError('angles must be in range of ' + + str(self.angular_range)) + return (self.relative_location * rotation * + other.relative_location.inverse()) + + +class Mesher: + """STL-only mesher (build123d's Mesher writes 3MF/STL via lib3mf). + COMPROMISE(mesher): only STL export is supported, via the JS engine's + StlAPI_Writer into the worker's Emscripten MEMFS — there is no lib3mf in + the WASM build, so .3mf paths raise; read() is not supported.""" + + def __init__(self, unit='MM', **kwargs): + self.unit = unit + self._shapes = [] + self.linear_deflection = 0.001 + self.angular_deflection = 0.1 + + @property + def mesh_count(self): + return len(self._shapes) + + def add_shape(self, shape, linear_deflection=0.001, + angular_deflection=0.1, **kwargs): + for s in _tolist(shape): + self._shapes.append(s) + self.linear_deflection = linear_deflection + self.angular_deflection = angular_deflection + + def add_code_to_metadata(self): + pass # no source file in the browser + + def add_meta_data(self, *args, **kwargs): + pass + + def write(self, file_name): + name = str(file_name) + if not name.lower().endswith('.stl'): + raise NotImplementedError( + 'build123d-lite Mesher writes STL only (no lib3mf in the ' + 'WASM build); got ' + name) + if not self._shapes: + raise ValueError('Mesher: no shapes added') + topos = [_topo(s) for s in self._shapes] + topo = topos[0] if len(topos) == 1 else w.MakeCompound(topos, True) + text = w.ExportSTL(topo, name.replace('/', '_'), + self.linear_deflection, self.angular_deflection) + if text is None: + raise RuntimeError('STL export failed') + return True + + def read(self, file_name): + raise NotImplementedError('Mesher.read is not supported in ' + 'build123d-lite') + + +ExportDXF = _unsupported('ExportDXF') + + +def import_step(file_name): + """Read a STEP asset that was handed to the worker ahead of the run + (build123d import_step). The worker has no filesystem, so the file cannot + be opened from the path the script computes next to __file__; instead the + host delivers its text through CascadeAPI.loadExternalFiles() (which is + exactly the app's own STEP-import path: STEPControl_Reader over a MEMFS + data file) and the BASE NAME of the requested path is looked up here. + + Upstream returns a Compound carrying the STEP assembly's labels and + colours; lite returns the geometry only.""" + name = str(file_name) + topo = w.GetExternalShape(name) + if topo is None or not topo: + raise FileNotFoundError( + 'import_step: "' + name + '" was not delivered to the worker. ' + 'The CAD worker has no filesystem; pass the file content with ' + 'CascadeAPI.loadExternalFiles({"' + name.split('/')[-1] + + '": }) before running the script.') + res = Compound.__new__(Compound) + Shape.__init__(res, topo) + res.label = name.split('/')[-1] + return res + + +import_stl = _unsupported('import_stl') +import_svg = _unsupported('import_svg') + + +def export_stl(to_export, file_path, tolerance=1e-3, angular_tolerance=0.1, + ascii_format=False): + """Write an STL into the worker's MEMFS (BRepMesh + StlAPI_Writer, the + same calls build123d's export_stl makes). COMPROMISE(mesher): the file + lands in the in-memory Emscripten FS, not the user's disk (browser + workers have no filesystem access); ascii_format is always True.""" + text = w.ExportSTL(_topo(to_export), str(file_path).replace('/', '_'), + tolerance, angular_tolerance) + return text is not None + + +def export_step(*args, **kwargs): + print('build123d-lite: export_step is a no-op in the browser') + return True + + +def export_gltf(*args, **kwargs): + print('build123d-lite: export_gltf is a no-op in the browser') + return True + + +class Color: + def __init__(self, *args, **kwargs): + self.args = args + + +def _pack2d(objects, width_fn, length_fn): + """Growing rectangle packer (port of build123d.pack_utils._pack2d, which + is itself a port of jakesgordon/bin-packing packer.growing.js).""" + class _Node: + def __init__(self, x=0.0, y=0.0, w=0.0, h=0.0): + self.used = False + self.x, self.y, self.w, self.h = x, y, w, h + self.down = None + self.right = None + + sizes = sorted(((o, width_fn(o), length_fn(o)) for o in objects), + key=lambda t: min(t[1], t[2]), reverse=True) + sizes = sorted(sizes, key=lambda t: max(t[1], t[2]), reverse=True) + root = _Node(w=sizes[0][1], h=sizes[0][2]) + + def find_node(start, ww, hh): + if start is None: + return None + if start.used: + return find_node(start.right, ww, hh) or find_node(start.down, ww, hh) + if ww <= start.w and hh <= start.h: + return start + return None + + def split_node(node, ww, hh): + node.used = True + node.down = _Node(node.x, node.y + hh, node.w, node.h - hh) + node.right = _Node(node.x + ww, node.y, node.w - ww, hh) + return node + + def grow_node(ww, hh): + nonlocal root + can_down = ww <= root.w + can_right = hh <= root.h + should_right = can_right and (root.h >= (root.w + ww)) + should_down = can_down and (root.w >= (root.h + hh)) + if should_right: + return grow_right(ww, hh) + if should_down: + return grow_down(ww, hh) + if can_right: + return grow_right(ww, hh) + if can_down: + return grow_down(ww, hh) + return None + + def grow_right(ww, hh): + nonlocal root + new_root = _Node(0, 0, root.w + ww, root.h) + new_root.used = True + new_root.down = root + new_root.right = _Node(root.w, 0, ww, root.h) + root = new_root + node = find_node(root, ww, hh) + return split_node(node, ww, hh) if node else None + + def grow_down(ww, hh): + nonlocal root + new_root = _Node(0, 0, root.w, root.h + hh) + new_root.used = True + new_root.right = root + new_root.down = _Node(0, root.h, root.w, hh) + root = new_root + node = find_node(root, ww, hh) + return split_node(node, ww, hh) if node else None + + placements = {} + for (o, ww, hh) in sizes: + node = find_node(root, ww, hh) + node = split_node(node, ww, hh) if node else grow_node(ww, hh) + placements[id(o)] = (node.x, node.y) + return [placements[id(o)] for o in objects] + + +def pack(objects, padding, align_z=False): + """Arrange shapes compactly in Plane.XY (port of build123d.pack.pack).""" + objects = list(objects) + bbs = {id(o): o.bounding_box() for o in objects} + translations = _pack2d( + objects, + lambda o: bbs[id(o)].size.X + padding, + lambda o: bbs[id(o)].size.Y + padding) + out = [] + for o, t in zip(objects, translations): + bb = bbs[id(o)] + dz = -bb.min.Z if align_z else 0.0 + out.append(Pos(t[0] - bb.min.X, t[1] - bb.min.Y, dz) * o) + return out + + +# --------------------------------------------------- measurement / show --- + +def volume(shape): + """Absolute volume of a shape in mm^3 (sum over solids).""" + return w.SolidsVolume(_topo(shape)) + + +def show(*shapes, **kwargs): + """Define the render scene (build123d-sandbox / ocp_vscode compat). + The FIRST show() of an evaluation replaces the auto-added scene with + exactly the shown shapes (so 2-D intermediates no longer leak into the + viewport and exports); later show()/show_object() calls append. + # COMPROMISE(show-semantics): ocp_vscode's show() replaces the view on + # every call (last-wins); appending on subsequent calls preserves the + # intent of scripts that show several results separately. + Extra viewer kwargs (names=, colors=, ...) are accepted and ignored. + Membership is tested with 'is' — Brython compares the underlying JS + objects, so it works across wrapper instances.""" + if not getattr(w, '_b123dSceneDefined', False): + while len(w.sceneShapes) > 0: + w.sceneShapes.pop() + w._b123dSceneDefined = True + flat = [] + for s in shapes: + if isinstance(s, (list, tuple, ShapeList)): + flat.extend(s) + else: + flat.append(s) + for s in flat: + if isinstance(s, Builder): + s = s._obj + try: + topo = _topo(s) + except (TypeError, ValueError): + continue + if not any(existing is topo for existing in w.sceneShapes): + w.sceneShapes.push(topo) + + +def show_object(shape, name=None, options=None, **kwargs): + """CQ-editor style alias for show().""" + show(shape) + + +def show_all(*args, **kwargs): + pass + + +def _json_num(x): + x = float(x) + if x != x or x in (float('inf'), float('-inf')): + return 'null' + return repr(x) + + +def _measure_globals_json(g): + """Measure every module-level Shape / builder result in the given globals + dict via the worker's MeasureShape hook; returns a JSON object string + {name: {volume, area, faces, edges, bbox:[6]}} keyed by variable name. + Mirrors the convention of test/b123d-validation/reference.py so lite and + real build123d runs can be compared per variable name.""" + entries = [] + + def measure(name, obj): + try: + topo = _topo(obj) + m = w.MeasureShape(topo) + except Exception as exc: # noqa: BLE001 - report, never crash the run + entries.append('"' + name + '":{"measure_error":"' + + str(exc).replace('"', "'")[:120] + '"}') + return + if m is None: + entries.append('"' + name + '":{"measure_error":"null shape"}') + return + bbox = 'null' + if m.bbox: + bbox = '[' + ','.join([_json_num(v) for v in m.bbox]) + ']' + entries.append('"' + name + '":{' + + '"volume":' + _json_num(m.volume) + + ',"area":' + _json_num(m.area) + + ',"faces":' + str(int(m.faces)) + + ',"edges":' + str(int(m.edges)) + + ',"bbox":' + bbox + '}') + + for name, obj in list(g.items()): + if name.startswith('_') or name in ('show', 'show_object', 'show_all'): + continue + if isinstance(obj, Builder): + if obj._obj is not None and obj._obj.topo is not None: + measure(name, obj._obj) + elif isinstance(obj, Shape): + if obj.topo is not None: + measure(name, obj) + elif isinstance(obj, (list, tuple)) and len(obj) > 0 and \ + all(isinstance(x, Shape) for x in obj): + # Skip face-less elements (edge/vertex selector lists) and sort + # the rest by bbox center: element ORDER frequently differs + # between build123d and lite (selector traversal, location list + # iteration), so per-index comparison would otherwise be noise. + # reference.py applies the same rule. + solids = [x for x in obj + if x.topo is not None and len(x.faces()) > 0] + def _bbkey(x): + bb = w.BoundingBox(x.topo, 0.01) + if not bb: + return (0.0, 0.0, 0.0) + return (round((bb[0] + bb[3]) / 2, 3), + round((bb[1] + bb[4]) / 2, 3), + round((bb[2] + bb[5]) / 2, 3)) + solids = sorted(solids[:64], key=_bbkey) + for i, x in enumerate(solids): + measure(name + '[' + str(i) + ']', x) + return '{' + ','.join(entries) + '}' +`; + +// Small stdlib shims registered as importable Brython modules. brython.js +// ships `math` as a built-in JS module but the import machinery cannot load +// it inside a module worker, and brython_stdlib.js (pure-Python stdlib) is +// deliberately not shipped — these cover what the build123d examples use. +export const PY_SHIM_MODULES = { + math: ` +# math shim delegating to the JS Math object (build123d-lite worker) +from browser import self as _w + +pi = 3.141592653589793 +e = 2.718281828459045 +tau = 6.283185307179586 +inf = float('inf') +nan = float('nan') + + +def sin(x): return _w.Math.sin(x) +def cos(x): return _w.Math.cos(x) +def tan(x): return _w.Math.tan(x) +def asin(x): return _w.Math.asin(x) +def acos(x): return _w.Math.acos(x) +def atan(x): return _w.Math.atan(x) +def atan2(y, x): return _w.Math.atan2(y, x) +def sinh(x): return _w.Math.sinh(x) +def cosh(x): return _w.Math.cosh(x) +def tanh(x): return _w.Math.tanh(x) +def sqrt(x): return _w.Math.sqrt(x) +def exp(x): return _w.Math.exp(x) +def fabs(x): return abs(float(x)) +def floor(x): return int(_w.Math.floor(x)) +def ceil(x): return int(_w.Math.ceil(x)) +def trunc(x): return int(_w.Math.trunc(x)) +def radians(x): return x * 0.017453292519943295 +def degrees(x): return x * 57.29577951308232 +def hypot(*a): + s = 0.0 + for v in a: + s += v * v + return _w.Math.sqrt(s) +def log(x, base=None): + if base is None: + return _w.Math.log(x) + return _w.Math.log(x) / _w.Math.log(base) +def log2(x): return _w.Math.log2(x) +def log10(x): return _w.Math.log10(x) +def pow(x, y): return _w.Math.pow(x, y) +def fmod(x, y): return x - y * int(x / y) if y != 0 else nan +def copysign(x, y): return abs(x) * (1.0 if y >= 0 else -1.0) +def isclose(a, b, rel_tol=1e-09, abs_tol=0.0): + return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) +def isnan(x): return x != x +def isinf(x): return x == inf or x == -inf +def isfinite(x): return not (isnan(x) or isinf(x)) +def gcd(a, b): + a, b = abs(int(a)), abs(int(b)) + while b: + a, b = b, a % b + return a +def factorial(n): + r = 1 + for i in range(2, int(n) + 1): + r *= i + return r +def dist(p, q): + return sqrt(sum((a - b) ** 2 for a, b in zip(p, q))) +def prod(it, start=1): + r = start + for v in it: + r *= v + return r +`, + copy: ` +# copy shim: shallow/deep copies; build123d-lite Shapes copy via _lite_copy +def copy(x): + if hasattr(x, '_lite_copy'): + return x._lite_copy() + if hasattr(x, '__copy__'): + return x.__copy__() + if isinstance(x, list): + return list(x) + if isinstance(x, dict): + return dict(x) + if isinstance(x, set): + return set(x) + return x + + +def deepcopy(x, memo=None): + if hasattr(x, '_lite_copy'): + return x._lite_copy() + if hasattr(x, '__deepcopy__'): + return x.__deepcopy__(memo) + if isinstance(x, list): + return [deepcopy(v, memo) for v in x] + if isinstance(x, tuple): + return tuple(deepcopy(v, memo) for v in x) + if isinstance(x, dict): + return dict((deepcopy(k, memo), deepcopy(v, memo)) for k, v in x.items()) + if isinstance(x, set): + return set(deepcopy(v, memo) for v in x) + return x +`, + typing: ` +# typing shim: annotations only — subscripting returns the object itself +class _AnyType: + def __getitem__(self, item): + return self + + def __call__(self, *a, **k): + return self + + +Any = _AnyType() +Union = _AnyType() +Optional = _AnyType() +Literal = _AnyType() +Callable = _AnyType() +Iterable = _AnyType() +Iterator = _AnyType() +Sequence = _AnyType() +List = _AnyType() +Dict = _AnyType() +Tuple = _AnyType() +Set = _AnyType() +Type = _AnyType() +TypeVar = lambda *a, **k: _AnyType() +cast = lambda t, v: v +TYPE_CHECKING = False +`, + functools: ` +# functools shim (pure Python subset) +def reduce(fn, it, *init): + items = iter(it) + if init: + acc = init[0] + else: + acc = next(items) + for v in items: + acc = fn(acc, v) + return acc + + +def partial(fn, *pargs, **pkw): + def inner(*args, **kw): + merged = dict(pkw) + merged.update(kw) + return fn(*(pargs + args), **merged) + return inner + + +def lru_cache(maxsize=None, typed=False): + def deco(fn): + cache = {} + + def inner(*args): + if args in cache: + return cache[args] + r = fn(*args) + cache[args] = r + return r + return inner + if callable(maxsize): + return deco(maxsize) + return deco + + +def wraps(wrapped): + def deco(fn): + return fn + return deco + + +def cmp_to_key(cmp): + class K: + def __init__(self, obj, *a): + self.obj = obj + + def __lt__(self, other): + return cmp(self.obj, other.obj) < 0 + + def __eq__(self, other): + return cmp(self.obj, other.obj) == 0 + return K +`, + itertools: ` +# itertools shim (pure Python subset) +def product(*iterables, repeat=1): + pools = [tuple(p) for p in iterables] * repeat + result = [[]] + for pool in pools: + result = [x + [y] for x in result for y in pool] + for prod_item in result: + yield tuple(prod_item) + + +def chain(*iterables): + for it in iterables: + for v in it: + yield v + + +def repeat(obj, times=None): + if times is None: + while True: + yield obj + else: + for _ in range(times): + yield obj + + +def count(start=0, step=1): + n = start + while True: + yield n + n += step + + +def islice(it, *args): + if len(args) == 1: + start, stop, step = 0, args[0], 1 + elif len(args) == 2: + start, stop, step = args[0], args[1], 1 + else: + start, stop, step = args + for i, v in enumerate(it): + if stop is not None and i >= stop: + return + if i >= start and (i - start) % step == 0: + yield v + + +def combinations(iterable, r): + pool = tuple(iterable) + n = len(pool) + if r > n: + return + indices = list(range(r)) + yield tuple(pool[i] for i in indices) + while True: + for i in reversed(range(r)): + if indices[i] != i + n - r: + break + else: + return + indices[i] += 1 + for j in range(i + 1, r): + indices[j] = indices[j - 1] + 1 + yield tuple(pool[i] for i in indices) + + +def permutations(iterable, r=None): + pool = tuple(iterable) + n = len(pool) + r = n if r is None else r + for idx in product(range(n), repeat=r): + if len(set(idx)) == r: + yield tuple(pool[i] for i in idx) + + +def cycle(iterable): + saved = [] + for v in iterable: + yield v + saved.append(v) + while saved: + for v in saved: + yield v + + +def zip_longest(*iterables, fillvalue=None): + its = [iter(i) for i in iterables] + while True: + row = [] + done = 0 + for it in its: + try: + row.append(next(it)) + except StopIteration: + row.append(fillvalue) + done += 1 + if done == len(its): + return + yield tuple(row) +`, + operator: ` +# operator shim (pure Python subset) +def add(a, b): return a + b +def sub(a, b): return a - b +def mul(a, b): return a * b +def truediv(a, b): return a / b +def neg(a): return -a +def and_(a, b): return a & b +def or_(a, b): return a | b +def eq(a, b): return a == b +def lt(a, b): return a < b +def le(a, b): return a <= b +def gt(a, b): return a > b +def ge(a, b): return a >= b + + +def itemgetter(*items): + if len(items) == 1: + key = items[0] + return lambda obj: obj[key] + return lambda obj: tuple(obj[k] for k in items) + + +def attrgetter(*attrs): + def resolve(obj, name): + for part in name.split('.'): + obj = getattr(obj, part) + return obj + if len(attrs) == 1: + return lambda obj: resolve(obj, attrs[0]) + return lambda obj: tuple(resolve(obj, a) for a in attrs) + + +def methodcaller(name, *args, **kwargs): + return lambda obj: getattr(obj, name)(*args, **kwargs) +`, + timeit: ` +# timeit shim (wall-clock via JS Date) +from browser import self as _w + + +def default_timer(): + return _w.Date.now() / 1000.0 + + +def timeit(stmt='pass', setup='pass', number=1000000, globals=None): + raise NotImplementedError('timeit.timeit is not supported in the worker') + + +class Timer: + def __init__(self, *a, **k): + pass + + def timeit(self, number=1000000): + raise NotImplementedError('timeit.Timer is not supported in the worker') +`, + random: ` +# random shim: exact CPython semantics (MT19937 + 53-bit random()) so that +# seeded scripts reproduce the reference geometry bit-for-bit +class _MT: + def __init__(self): + self.mt = [0] * 624 + self.index = 625 + self.seed_int(5489) + + def seed_int(self, s): + self.mt[0] = s & 0xFFFFFFFF + for i in range(1, 624): + self.mt[i] = (1812433253 * (self.mt[i - 1] ^ (self.mt[i - 1] >> 30)) + i) & 0xFFFFFFFF + self.index = 624 + + def init_by_array(self, key): + self.seed_int(19650218) + i, j = 1, 0 + k = max(624, len(key)) + while k: + self.mt[i] = ((self.mt[i] ^ ((self.mt[i - 1] ^ (self.mt[i - 1] >> 30)) * 1664525)) + key[j] + j) & 0xFFFFFFFF + i += 1 + j += 1 + if i >= 624: + self.mt[0] = self.mt[623] + i = 1 + if j >= len(key): + j = 0 + k -= 1 + k = 623 + while k: + self.mt[i] = ((self.mt[i] ^ ((self.mt[i - 1] ^ (self.mt[i - 1] >> 30)) * 1566083941)) - i) & 0xFFFFFFFF + i += 1 + if i >= 624: + self.mt[0] = self.mt[623] + i = 1 + k -= 1 + self.mt[0] = 0x80000000 + + def genrand(self): + if self.index >= 624: + for i in range(624): + y = (self.mt[i] & 0x80000000) + (self.mt[(i + 1) % 624] & 0x7FFFFFFF) + self.mt[i] = self.mt[(i + 397) % 624] ^ (y >> 1) + if y % 2: + self.mt[i] ^= 2567483615 + self.index = 0 + y = self.mt[self.index] + self.index += 1 + y ^= y >> 11 + y ^= (y << 7) & 2636928640 + y ^= (y << 15) & 4022730752 + y ^= y >> 18 + return y + + +_state = _MT() + + +def seed(a=None): + if a is None: + import time + a = 0 + if isinstance(a, int): + # CPython: init_by_array over the absolute value's 32-bit chunks + v = abs(a) + key = [] + while True: + key.append(v & 0xFFFFFFFF) + v >>= 32 + if v == 0: + break + _state.init_by_array(key) + else: + raise NotImplementedError('random.seed only supports ints here') + + +def random(): + a = _state.genrand() >> 5 + b = _state.genrand() >> 6 + return (a * 67108864.0 + b) / 9007199254740992.0 + + +def getrandbits(k): + if k <= 32: + return _state.genrand() >> (32 - k) + out = 0 + shift = 0 + while k > 0: + take = min(k, 32) + out |= (_state.genrand() >> (32 - take)) << shift + shift += take + k -= take + return out + + +def _randbelow(n): + if n <= 0: + return 0 + k = n.bit_length() + r = getrandbits(k) + while r >= n: + r = getrandbits(k) + return r + + +def randrange(start, stop=None, step=1): + if stop is None: + return _randbelow(start) + width = stop - start + if step == 1: + return start + _randbelow(width) + n = (width + step - 1) // step + return start + step * _randbelow(n) + + +def randint(a, b): + return randrange(a, b + 1) + + +def uniform(a, b): + return a + (b - a) * random() + + +def choice(seq): + return seq[_randbelow(len(seq))] + + +def shuffle(x): + for i in range(len(x) - 1, 0, -1): + j = _randbelow(i + 1) + x[i], x[j] = x[j], x[i] +`, + _scipy_shim: ` +# scipy shim implementation module (imported by the 'scipy' package shims). +# COMPROMISE(scipy-shim): pure-Python Nelder-Mead stands in for +# scipy.optimize.minimize (same simplex init/reflect/expand/contract/shrink +# rules and convergence thresholds as scipy's implementation, but float +# arithmetic instead of numpy arrays — objectives must accept plain lists); +# minimize_scalar supports method='bounded' via golden-section. EVERY other +# scipy API raises loudly instead of approximating. + + +class OptimizeResult(dict): + def __getattr__(self, k): + try: + return self[k] + except KeyError: + raise AttributeError(k) + + def __setattr__(self, k, v): + self[k] = v + + +def minimize(fun, x0, args=(), method='Nelder-Mead', bounds=None, tol=None, + options=None, **kwargs): + if method is not None and str(method).lower() != 'nelder-mead': + raise NotImplementedError( + 'scipy shim: only minimize(method="Nelder-Mead") is available ' + 'in build123d-lite (got ' + repr(method) + ')') + if not isinstance(args, (list, tuple)): + args = (args,) + try: + x0 = [float(v) for v in x0] + except TypeError: + x0 = [float(x0)] + n = len(x0) + xatol = fatol = 1e-4 + if tol is not None: + xatol = fatol = float(tol) + opts = options or {} + xatol = opts.get('xatol', xatol) + fatol = opts.get('fatol', fatol) + maxiter = opts.get('maxiter', 200 * n) + lo = [None] * n + hi = [None] * n + if bounds is not None: + for i, b in enumerate(bounds): + lo[i], hi[i] = b[0], b[1] + + def clip(x): + out = [] + for i, v in enumerate(x): + if lo[i] is not None and v < lo[i]: + v = lo[i] + if hi[i] is not None and v > hi[i]: + v = hi[i] + out.append(v) + return out + + def f(x): + r = fun(list(x), *args) + try: + return float(r) + except TypeError: + return float(r[0]) + + sim = [clip(list(x0))] + for i in range(n): + y = list(x0) + y[i] = y[i] * 1.05 if y[i] != 0 else 0.00025 + sim.append(clip(y)) + fsim = [f(x) for x in sim] + it = 0 + for it in range(int(maxiter)): + order = sorted(range(n + 1), key=lambda j: fsim[j]) + sim = [sim[j] for j in order] + fsim = [fsim[j] for j in order] + if max(abs(sim[j][i] - sim[0][i]) + for j in range(1, n + 1) for i in range(n)) <= xatol and \ + max(abs(fsim[j] - fsim[0]) for j in range(1, n + 1)) <= fatol: + break + cen = [sum(sim[j][i] for j in range(n)) / n for i in range(n)] + xr = clip([cen[i] + (cen[i] - sim[n][i]) for i in range(n)]) + fr = f(xr) + if fr < fsim[0]: + xe = clip([cen[i] + 2.0 * (cen[i] - sim[n][i]) for i in range(n)]) + fe = f(xe) + if fe < fr: + sim[n], fsim[n] = xe, fe + else: + sim[n], fsim[n] = xr, fr + elif fr < fsim[n - 1]: + sim[n], fsim[n] = xr, fr + else: + if fr < fsim[n]: + xc = clip([cen[i] + 0.5 * (cen[i] - sim[n][i]) + for i in range(n)]) + else: + xc = clip([cen[i] - 0.5 * (cen[i] - sim[n][i]) + for i in range(n)]) + fc = f(xc) + if fc < min(fr, fsim[n]): + sim[n], fsim[n] = xc, fc + else: + for j in range(1, n + 1): + sim[j] = clip([sim[0][i] + 0.5 * (sim[j][i] - sim[0][i]) + for i in range(n)]) + fsim[j] = f(sim[j]) + order = sorted(range(n + 1), key=lambda j: fsim[j]) + return OptimizeResult(x=list(sim[order[0]]), fun=fsim[order[0]], + success=True, nit=it + 1) + + +def minimize_scalar(fun, bounds=None, method='bounded', args=(), + options=None, **kwargs): + if str(method).lower() != 'bounded' or bounds is None: + raise NotImplementedError( + 'scipy shim: only minimize_scalar(method="bounded", bounds=...) ' + 'is available in build123d-lite') + if not isinstance(args, (list, tuple)): + args = (args,) + xatol = (options or {}).get('xatol', 1e-5) + a, b = float(bounds[0]), float(bounds[1]) + phi = 0.6180339887498949 + c = b - phi * (b - a) + d = a + phi * (b - a) + fc, fd = fun(c, *args), fun(d, *args) + while (b - a) > xatol: + if fc < fd: + b, d, fd = d, c, fc + c = b - phi * (b - a) + fc = fun(c, *args) + else: + a, c, fc = c, d, fd + d = a + phi * (b - a) + fd = fun(d, *args) + x = (a + b) / 2.0 + return OptimizeResult(x=x, fun=fun(x, *args), success=True) + + +def _raising(name): + def f(*args, **kwargs): + raise NotImplementedError( + 'scipy.' + name + ' is not available in build123d-lite (only ' + 'optimize.minimize / optimize.minimize_scalar are shimmed)') + return f + + +class _IndexRows(list): + """Nested int lists standing in for scipy's ndarray of indices.""" + + def tolist(self): + return [list(r) if isinstance(r, list) else r for r in self] + + +class ConvexHull: + """3-D convex hull computed by the worker's bundled quickhull3d. + Only the attributes the examples use are provided: .points, + .simplices (triangulated facets, scipy convention) and .vertices. + 2-D hulls are not implemented (scipy uses qhull; the 2-D case in + lite is served by make_hull's own Andrew-monotone hull).""" + + def __init__(self, points, *args, **kwargs): + pts = [[float(c) for c in p] for p in points] + if len(pts) == 0 or len(pts[0]) != 3: + raise NotImplementedError( + 'scipy shim: only 3-D ConvexHull is supported in ' + 'build123d-lite') + from browser import self as _w + tris = _w.ConvexHull3D(pts) + self.points = pts + self.simplices = _IndexRows( + _IndexRows(int(i) for i in t) for t in tris) + seen = set() + for t in self.simplices: + seen.update(t) + self.vertices = _IndexRows(sorted(seen)) + + +def _bowyer_watson(points): + """Delaunay triangulation of 2-D points (Bowyer-Watson incremental + insertion). Returns the triangles as index triples into 'points', with + every triangle touching the enclosing super-triangle discarded — which is + exactly the set whose circumcentres are qhull's FINITE Voronoi vertices.""" + pts = [(float(p[0]), float(p[1])) for p in points] + # dedupe: coincident inputs (shared edge endpoints) would make degenerate + # triangles; qhull merges them too (Qbb Qc) + seen = {} + uniq = [] + for p in pts: + key = (round(p[0], 12), round(p[1], 12)) + if key not in seen: + seen[key] = True + uniq.append(p) + if len(uniq) < 3: + return [], uniq + xs = [p[0] for p in uniq] + ys = [p[1] for p in uniq] + cx = (min(xs) + max(xs)) / 2.0 + cy = (min(ys) + max(ys)) / 2.0 + span = max(max(xs) - min(xs), max(ys) - min(ys)) + if span <= 0: + return [], uniq + big = 1000.0 * span + verts = list(uniq) + [(cx - big, cy - big), (cx + big, cy - big), + (cx, cy + big)] + n = len(uniq) + tris = [(n, n + 1, n + 2)] + + def circum(a, b, c): + ax, ay = verts[a] + bx, by = verts[b] + cx2, cy2 = verts[c] + d = 2.0 * (ax * (by - cy2) + bx * (cy2 - ay) + cx2 * (ay - by)) + if abs(d) < 1e-18: + return None + a2 = ax * ax + ay * ay + b2 = bx * bx + by * by + c2 = cx2 * cx2 + cy2 * cy2 + ux = (a2 * (by - cy2) + b2 * (cy2 - ay) + c2 * (ay - by)) / d + uy = (a2 * (cx2 - bx) + b2 * (ax - cx2) + c2 * (bx - ax)) / d + r2 = (ax - ux) * (ax - ux) + (ay - uy) * (ay - uy) + return (ux, uy, r2) + + circles = {tris[0]: circum(*tris[0])} + for i in range(n): + px, py = verts[i] + bad = [] + for t in tris: + cc = circles.get(t) + if cc is None: + continue + dx = px - cc[0] + dy = py - cc[1] + # a strictly-inside test with a relative epsilon: points exactly ON + # a circumcircle (this input is full of cocircular samples) must not + # flip the triangulation nondeterministically + if dx * dx + dy * dy < cc[2] * (1.0 - 1e-12): + bad.append(t) + if not bad: + continue + edge_count = {} + for t in bad: + for e in ((t[0], t[1]), (t[1], t[2]), (t[2], t[0])): + key = (e[0], e[1]) if e[0] < e[1] else (e[1], e[0]) + edge_count[key] = edge_count.get(key, 0) + 1 + for t in bad: + tris.remove(t) + circles.pop(t, None) + for key in sorted(edge_count.keys()): + if edge_count[key] != 1: + continue # interior edge of the cavity + t = (key[0], key[1], i) + cc = circum(*t) + if cc is None: + continue + tris.append(t) + circles[t] = cc + return [t for t in tris if max(t) < n], uniq + + +class Voronoi: + """2-D Voronoi diagram (scipy.spatial.Voronoi). + + Only .vertices is produced, because that is the only attribute + build123d reads (operations_sketch.full_round takes the Voronoi vertices + as its candidate centres for the largest empty circle). Those vertices are + the circumcentres of the Delaunay triangulation, computed here with + Bowyer-Watson instead of qhull (which is not available in the worker) and + deduplicated the way qhull's 'Qbb Qc' merges cocircular circumcentres. + Verified against scipy 1.18 on full_round's own inputs: the vertex SETS are + identical (220 and 210 vertices, max pairwise deviation 2e-13). + + Unbounded ridges have no finite Voronoi vertex and qhull does not list one + either; they fall out of the construction because every triangle touching + the super-triangle is discarded. Nothing else about the diagram (ridges, + regions, point_region) is offered rather than half-offered.""" + + def __init__(self, points, *args, **kwargs): + rows = [list(p) for p in points] + if not rows or len(rows[0]) != 2: + raise NotImplementedError( + 'scipy shim: only 2-D Voronoi is supported in build123d-lite ' + '(qhull is not available in the worker)') + self.points = _IndexRows(_IndexRows(float(c) for c in r) for r in rows) + self.ndim = 2 + self.npoints = len(rows) + tris, uniq = _bowyer_watson(rows) + verts = [] + seen = {} + for t in tris: + ax, ay = uniq[t[0]] + bx, by = uniq[t[1]] + cx, cy = uniq[t[2]] + d = 2.0 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by)) + if abs(d) < 1e-18: + continue + a2 = ax * ax + ay * ay + b2 = bx * bx + by * by + c2 = cx * cx + cy * cy + ux = (a2 * (by - cy) + b2 * (cy - ay) + c2 * (ay - by)) / d + uy = (a2 * (cx - bx) + b2 * (ax - cx) + c2 * (bx - ax)) / d + key = (round(ux, 9), round(uy, 9)) + if key in seen: + continue + seen[key] = True + verts.append((ux, uy)) + self.vertices = _IndexRows(_IndexRows(v) for v in verts) + + def __getattr__(self, name): + if name in ('ridge_points', 'ridge_vertices', 'regions', + 'point_region', 'furthest_site'): + raise NotImplementedError( + 'scipy shim: Voronoi.' + name + ' is not available in ' + 'build123d-lite (only .vertices is computed)') + raise AttributeError(name) + + +class _Namespace: + def __init__(self, prefix, **entries): + self._prefix = prefix + for k, v in entries.items(): + setattr(self, k, v) + + def __getattr__(self, name): + if name.startswith('_'): + raise AttributeError(name) + raise NotImplementedError( + 'scipy.' + self._prefix + '.' + name + ' is not available in ' + 'build123d-lite') + + +optimize = _Namespace('optimize', minimize=minimize, + minimize_scalar=minimize_scalar, + OptimizeResult=OptimizeResult) +spatial = _Namespace('spatial', ConvexHull=ConvexHull, Voronoi=Voronoi) +`, + scipy: ` +# __path__ marks this as a package so Brython's importer resolves the +# pre-registered 'scipy.optimize' / 'scipy.spatial' submodules from cache +__path__ = [] +from _scipy_shim import optimize, spatial +`, + 'scipy.optimize': ` +from _scipy_shim import minimize, minimize_scalar, OptimizeResult +`, + 'scipy.spatial': ` +from _scipy_shim import ConvexHull, Voronoi +`, + pytest: ` +# pytest shim: approx() ONLY, implemented for real (pytest's documented +# default tolerances: relative 1e-6, absolute 1e-12, whichever is looser). +# Several build123d doc scripts close with 'assert value == pytest.approx(x)', +# which is a genuine numeric comparison worth honouring; everything else about +# pytest (fixtures, marks, raises, the test runner) is absent, so a script that +# actually wants to run tests fails loudly. + + +class approx: + def __init__(self, expected, rel=None, abs=None, nan_ok=False): + self.expected = expected + self.rel = 1e-6 if rel is None else rel + self.abs = 1e-12 if abs is None else abs + self.nan_ok = nan_ok + + def _close(self, actual, expected): + tolerance = max(self.abs, self.rel * builtins_abs(expected)) + return builtins_abs(actual - expected) <= tolerance + + def __eq__(self, actual): + if isinstance(self.expected, (list, tuple)): + if len(actual) != len(self.expected): + return False + return all([self._close(a, e) + for a, e in zip(actual, self.expected)]) + if isinstance(self.expected, dict): + if set(actual.keys()) != set(self.expected.keys()): + return False + return all([self._close(actual[k], self.expected[k]) + for k in self.expected]) + return self._close(actual, self.expected) + + def __ne__(self, actual): + return not self.__eq__(actual) + + def __repr__(self): + return 'approx(' + repr(self.expected) + ' +- ' + \\ + repr(max(self.abs, self.rel * builtins_abs(self.expected))) + ')' + + +builtins_abs = abs + + +def __getattr__(name): + raise NotImplementedError( + 'build123d-lite ships only pytest.approx, not ' + repr(name) + + ' (there is no test runner in the CAD worker)') +`, + os: ` +# os shim: pure PATH ARITHMETIC only (os.path.join/dirname/abspath/... and +# os.getcwd), which is all the build123d docs scripts use it for - they build +# asset paths next to __file__ for SVG/screenshot output. There is no real +# filesystem in the worker, so nothing here touches one: os.path.exists is +# always False and open()/listdir are absent, so a script that genuinely needs +# a file still fails loudly instead of silently doing nothing. +sep = '/' +extsep = '.' +curdir = '.' +pardir = '..' +linesep = '\\n' +name = 'posix' +environ = {} + + +def getcwd(): + return '/' + + +def fspath(p): + return p + + +class _Path: + sep = '/' + extsep = '.' + curdir = '.' + pardir = '..' + + @staticmethod + def join(*parts): + out = '' + for p in parts: + p = str(p) + if p.startswith('/'): + out = p + elif out == '' or out.endswith('/'): + out = out + p + else: + out = out + '/' + p + return out + + @staticmethod + def split(p): + p = str(p) + i = p.rfind('/') + if i < 0: + return ('', p) + if i == 0: + return ('/', p[1:]) + return (p[:i], p[i + 1:]) + + @staticmethod + def dirname(p): + return _Path.split(p)[0] + + @staticmethod + def basename(p): + return _Path.split(p)[1] + + @staticmethod + def splitext(p): + base = _Path.basename(p) + i = base.rfind('.') + if i <= 0: + return (str(p), '') + return (str(p)[:len(str(p)) - (len(base) - i)], base[i:]) + + @staticmethod + def isabs(p): + return str(p).startswith('/') + + @staticmethod + def normpath(p): + p = str(p) + absolute = p.startswith('/') + out = [] + for part in p.split('/'): + if part == '' or part == '.': + continue + if part == '..': + if out and out[-1] != '..': + out.pop() + elif not absolute: + out.append('..') + continue + out.append(part) + joined = '/'.join(out) + if absolute: + return '/' + joined + return joined if joined else '.' + + @staticmethod + def abspath(p): + p = str(p) + if not p.startswith('/'): + p = getcwd() + ('' if getcwd().endswith('/') else '/') + p + return _Path.normpath(p) + + @staticmethod + def exists(p): + return False + + @staticmethod + def isfile(p): + return False + + @staticmethod + def isdir(p): + return False + + +path = _Path +`, + logging: ` +# logging shim: swallows everything (worker console is used via print) +DEBUG = 10 +INFO = 20 +WARNING = 30 +ERROR = 40 +CRITICAL = 50 + + +class _Logger: + def debug(self, *a, **k): pass + def info(self, *a, **k): pass + def warning(self, *a, **k): pass + def error(self, *a, **k): pass + def critical(self, *a, **k): pass + def exception(self, *a, **k): pass + def setLevel(self, *a, **k): pass + def addHandler(self, *a, **k): pass + + +_logger = _Logger() + + +def getLogger(name=None): + return _logger + + +def basicConfig(*a, **k): + pass + + +def debug(*a, **k): + pass + + +def info(*a, **k): + pass + + +def warning(*a, **k): + pass + + +def error(*a, **k): + pass + + +def critical(*a, **k): + pass + + +def exception(*a, **k): + pass +`, +}; diff --git a/packages/cascade-core/src/worker/CascadeWorker.js b/packages/cascade-core/src/worker/CascadeWorker.js index cb9b06d5..4fd86f62 100644 --- a/packages/cascade-core/src/worker/CascadeWorker.js +++ b/packages/cascade-core/src/worker/CascadeWorker.js @@ -3,6 +3,8 @@ import { CascadeStudioStandardLibrary } from './StandardLibrary.js'; import { CascadeStudioMesher } from './ShapeToMesh.js'; import { CascadeStudioFileIO } from './FileUtils.js'; +import { USED_OCCT_SYMBOLS } from './UsedOCCTSymbols.generated.js'; +import { ensurePythonRuntime } from './PythonRuntime.js'; /** Main CAD worker class. Initializes OpenCascade WASM, loads dependencies, * and orchestrates evaluation/rendering of user CAD code. */ @@ -35,6 +37,36 @@ class CascadeStudioWorker { self.messageHandlers["Evaluate"] = this.evaluate.bind(this); self.messageHandlers["combineAndRenderShapes"] = this.combineAndRenderShapes.bind(this); self.messageHandlers["meshHistoryStep"] = this.meshHistoryStep.bind(this); + self.messageHandlers["memoryStats"] = this.memoryStats.bind(this); + } + + /** Worker-side memory footprint, split by owner. Used by the Python + * runtime comparison (test/b123d-validation/runtime-comparison.md) — the + * page cannot see any of this, since it all lives in the worker. + * + * `occtWasm`/`pythonWasm` are exact wasm linear-memory sizes (they only + * ever grow). `jsHeap*` comes from `performance.memory`, which Chromium + * does NOT expose in workers — expect zeros there, and read renderer RSS + * instead (bench-runtime.mjs does). A GC is forced first when the browser + * was started with --js-flags=--expose-gc. */ + memoryStats() { + if (typeof globalThis.gc === 'function') { + try { globalThis.gc(); globalThis.gc(); } catch (e) { /* best effort */ } + } + const mem = (typeof performance !== 'undefined' && performance.memory) || {}; + let pythonWasm = 0; + try { + const py = self._pyodideRuntime; + if (py && py._module && py._module.HEAPU8) { pythonWasm = py._module.HEAPU8.length; } + } catch (e) { /* no Pyodide in this session */ } + return { + pyRuntime: self._pythonRuntimeKind || null, + jsHeapUsed: mem.usedJSHeapSize || 0, + jsHeapTotal: mem.totalJSHeapSize || 0, + occtWasm: self.ocMemory ? self.ocMemory.buffer.byteLength : 0, + pythonWasm, + bootTiming: self._pythonBootTiming || null, + }; } /** Override console.log/error to forward messages to the main thread. */ @@ -43,7 +75,11 @@ class CascadeStudioWorker { const realError = this.realConsoleError; console.log = function (...args) { - const message = args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' '); + const message = args.map(a => { + if (typeof a === 'string') { return a; } + // Circular objects (e.g. Brython internals) must not break logging + try { return JSON.stringify(a); } catch (e) { return String(a); } + }).join(' '); setTimeout(() => { postMessage({ type: "log", payload: message }); }, 0); realLog.apply(console, args); }; @@ -55,7 +91,11 @@ class CascadeStudioWorker { err.message = "INTERNAL OPENCASCADE ERROR DURING GENERATE: " + err.message; throw err; } else { - throw new Error("INTERNAL OPENCASCADE ERROR: " + err); + // Raw wasm exceptions arrive as pointer numbers — decode them into + // OCCT's own diagnostic where possible (self.describeOCCTException + // is installed by CascadeStudioUtils). + throw new Error("INTERNAL OPENCASCADE ERROR: " + (self.describeOCCTException + ? self.describeOCCTException(err) : err)); } }, 0); realError.apply(console, arguments); @@ -97,31 +137,90 @@ class CascadeStudioWorker { this.mesher = new CascadeStudioMesher(); this.fileIO = new CascadeStudioFileIO(); - // Preload fonts available via Text3D - this._loadFonts(opentype); + // Preload fonts available via Text3D/Text2D. Awaited so the first + // evaluation can never race the font fetch (they are small local TTFs). + await this._loadFonts(opentype); // Load the OpenCascade WebAssembly Module (v2 Embind) + const wasmPath = (path) => { + if (path.endsWith('.wasm')) { + // In build mode, WASM is copied to the build output directory + return typeof ESBUILD !== 'undefined' ? './cascadestudio.wasm' : '../../node_modules/opencascade.js/dist/cascadestudio.wasm'; + } + return path; + }; try { const openCascade = await initOpenCascade({ - locateFile(path) { - if (path.endsWith('.wasm')) { - // In build mode, WASM is copied to the build output directory - return typeof ESBUILD !== 'undefined' ? './cascadestudio.wasm' : '../../node_modules/opencascade.js/dist/cascadestudio.wasm'; - } - return path; + locateFile: wasmPath, + // Emscripten's documented instantiation hook, used ONLY to keep a + // reference to the wasm linear memory. + // + // Why: OCCT throws C++ exceptions, which arrive in JS as raw pointer + // NUMBERS. The fork binds `OCJS::getStandard_FailureData(ptr)` to turn + // one back into a `Standard_Failure`, but that binding is UNCALLABLE in + // this build — embind refuses with "unbound types: St9exception", + // because Standard_Failure derives from std::exception, which is not a + // registered type. This build also exports no runtime helpers (no + // HEAPU8/getValue/UTF8ToString), so there is no other way in. + // Capturing the Memory here lets StandardUtils.decodeOCCTException read + // Standard_Failure's message directly (see its layout notes), turning + // "the kernel threw '6454200'" into OCCT's own diagnostic. + instantiateWasm(imports, receiveInstance) { + const url = wasmPath('cascadestudio.wasm'); + (async () => { + let result; + try { + result = await WebAssembly.instantiateStreaming(fetch(url), imports); + } catch (streamError) { + // wrong MIME type / no streaming support: fall back exactly like + // Emscripten's own instantiateAsync does + const bytes = await (await fetch(url)).arrayBuffer(); + result = await WebAssembly.instantiate(bytes, imports); + } + for (const value of Object.values(result.instance.exports)) { + if (value instanceof WebAssembly.Memory) { self.ocMemory = value; break; } + } + receiveInstance(result.instance, result.module); + })(); } }); // Register the "OpenCascade" WebAssembly Module under the shorthand "oc" self.oc = openCascade; - // Route incoming messages to registered handlers + // Numbered Embind overload suffixes (e.g. BRepBuilderAPI_MakeEdge_24) + // are derived from each class's overload set and can be renumbered by + // an OCCT upgrade. Verify every symbol the worker references so a + // mismatched WASM build fails loudly at startup instead of surfacing + // as cryptic errors mid-evaluation. + const missingSymbols = USED_OCCT_SYMBOLS.filter((s) => !(s in openCascade)); + if (missingSymbols.length > 0) { + const message = "OCCT build is missing " + missingSymbols.length + + " symbol(s) used by the standard library (overload suffixes may " + + "have been renumbered by an OCCT upgrade): " + missingSymbols.join(", "); + postMessage({ type: "error", payload: message }); + console.error(message); + } + + // Route incoming messages to registered handlers. Handlers may return + // a Promise (e.g. meshing that waits on an async Python evaluation); + // the response is posted once it resolves. onmessage = function (e) { + const respond = (response) => { + if (response !== undefined || e.data.requestId) { + const msg = { "type": e.data.type, payload: response }; + if (e.data.requestId) { msg.requestId = e.data.requestId; } + postMessage(msg); + } + }; let response = self.messageHandlers[e.data.type](e.data.payload); - if (response !== undefined || e.data.requestId) { - const msg = { "type": e.data.type, payload: response }; - if (e.data.requestId) { msg.requestId = e.data.requestId; } - postMessage(msg); + if (response instanceof Promise) { + response.then(respond, (err) => { + postMessage({ type: "resetWorking" }); + setTimeout(() => { throw err; }, 0); + }); + } else { + respond(response); } }; @@ -139,24 +238,80 @@ class CascadeStudioWorker { const preloadedFonts = [ fontBase + 'Roboto.ttf', fontBase + 'Papyrus.ttf', - fontBase + 'Consolas.ttf' + fontBase + 'Consolas.ttf', + fontBase + 'LiberationSans-Regular.ttf', + fontBase + 'FreeSans.ttf', + fontBase + 'FreeSansBold.ttf', + fontBase + 'FreeSansOblique.ttf', + fontBase + 'FreeSansBoldOblique.ttf' ]; self.loadedFonts = {}; - preloadedFonts.forEach((fontURL) => { + self.fontKernPairs = {}; + return Promise.all(preloadedFonts.map((fontURL) => new Promise((resolve) => { // { isUrl: true } forces XHR instead of require('fs') since workers lack `window` opentype.load(fontURL, function (err, font) { if (err) { console.log(err); } let fontName = fontURL.split("./fonts/")[1] || fontURL.split("/fonts/")[1]; fontName = fontName.split(".ttf")[0]; self.loadedFonts[fontName] = font; + // opentype.js only reads the FIRST kern subtable — parse all of the + // format-0 subtables ourselves so Text2D can match FreeType's + // kerning (build123d text parity) + fetch(fontURL).then((r) => r.arrayBuffer()).then((buf) => { + self.fontKernPairs[fontName] = CascadeStudioWorker._parseKernTable(buf); + resolve(); + }).catch(() => resolve()); }, { isUrl: true }); - }); + }))); + } + + /** Parse every format-0 'kern' subtable of a TTF into a Map keyed by + * "leftGid,rightGid" -> kern value in font units. */ + static _parseKernTable(buf) { + const pairs = new Map(); + try { + const dv = new DataView(buf); + const numTables = dv.getUint16(4); + let kernOffset = 0, kernLength = 0; + for (let i = 0; i < numTables; i++) { + const rec = 12 + i * 16; + const tag = String.fromCharCode(dv.getUint8(rec), dv.getUint8(rec + 1), + dv.getUint8(rec + 2), dv.getUint8(rec + 3)); + if (tag === 'kern') { kernOffset = dv.getUint32(rec + 8); kernLength = dv.getUint32(rec + 12); } + } + if (!kernOffset) { return pairs; } + const nSub = dv.getUint16(kernOffset + 2); + let off = kernOffset + 4; + for (let t = 0; t < nSub; t++) { + const len = dv.getUint16(off + 2); + const coverage = dv.getUint16(off + 4); + const format = coverage >> 8; + // horizontal kerning only: skip 'minimum' and cross-stream subtables + const horizontal = (coverage & 0x1) === 1; + const minimum = (coverage & 0x2) !== 0; + const crossStream = (coverage & 0x4) !== 0; + if (format === 0 && horizontal && !minimum && !crossStream) { + const nPairs = dv.getUint16(off + 6); + let p = off + 14; + for (let i = 0; i < nPairs; i++, p += 6) { + pairs.set(dv.getUint16(p) + ',' + dv.getUint16(p + 2), dv.getInt16(p + 4)); + } + } + off += (len || 6); + if (off >= kernOffset + kernLength) { break; } + } + } catch (e) { /* kerning is best-effort */ } + return pairs; } - /** Evaluate user CAD code (the contents of the Editor Window) and set the GUI State. */ + /** Evaluate user CAD code (the contents of the Editor Window) and set the GUI State. + * payload.language selects the runtime: undefined/'cascadestudio' (and + * transpiled OpenSCAD) eval JS synchronously; 'python' runs build123d-lite + * code through the lazily-bootstrapped Brython runtime (async). */ evaluate(payload) { self.opNumber = 0; self.GUIState = payload.GUIState; + self.evalLanguage = payload.language || 'cascadestudio'; // Reset cache counters and modeling history for this evaluation this.standardLibrary.utils.cacheHits = 0; @@ -167,6 +322,14 @@ class CascadeStudioWorker { this.standardLibrary.utils.modelHistory = self.modelHistory; this.standardLibrary.utils._pendingHistoryOp = null; + if (self.evalLanguage === 'python') { + // Async path: the pending promise is stored so combineAndRenderShapes + // (the engine queues it right behind this message) waits for the + // evaluation to finish before meshing the scene. + this._pendingEvaluation = this._evaluatePython(payload); + return; + } + try { eval(payload.code); } catch (e) { @@ -175,34 +338,66 @@ class CascadeStudioWorker { throw e; }, 0); } finally { - // Flush the final operation's history step - self.flushHistoryStep(); - - // Send lightweight history metadata to main thread (no shape data) - postMessage({ - type: "modelHistory", - payload: self.modelHistory.map((step, i) => ({ - index: i, - fnName: step.fnName, - lineNumber: step.lineNumber, - shapeCount: step.shapeCount, - })) - }); + this._finishEvaluation(); + } + } - postMessage({ type: "log", payload: "Cache: " + self.cacheHits + " hits, " + self.cacheMisses + " misses" }); - postMessage({ type: "resetWorking" }); - // Clean cache; remove unused objects - let usedHashes = this.standardLibrary.utils.usedHashes; - for (let hash in self.argCache) { - if (!usedHashes.hasOwnProperty(hash)) { delete self.argCache[hash]; } - } - for (let key in usedHashes) { delete usedHashes[key]; } + /** Run user Python through Brython (bootstrapped on first use). Never + * rejects: Python errors are re-thrown asynchronously so they surface on + * the main thread exactly like JS-mode evaluation errors. */ + async _evaluatePython(payload) { + try { + const runtime = await ensurePythonRuntime(payload.pyRuntime); + runtime.run(payload.code); + } catch (e) { + setTimeout(() => { throw e; }, 0); + } finally { + this._finishEvaluation(); } } - /** Accumulate all shapes in `sceneShapes` into a compound, - * triangulate with ShapeToMesh, and return for rendering. */ + /** Post-evaluation bookkeeping shared by the JS and Python paths: + * flush history, report it, signal resetWorking, and clean the cache. */ + _finishEvaluation() { + // Flush the final operation's history step + self.flushHistoryStep(); + + // Send lightweight history metadata to main thread (no shape data) + postMessage({ + type: "modelHistory", + payload: self.modelHistory.map((step, i) => ({ + index: i, + fnName: step.fnName, + lineNumber: step.lineNumber, + shapeCount: step.shapeCount, + })) + }); + + postMessage({ type: "log", payload: "Cache: " + self.cacheHits + " hits, " + self.cacheMisses + " misses" }); + postMessage({ type: "resetWorking" }); + // Clean cache; remove unused objects + let usedHashes = this.standardLibrary.utils.usedHashes; + for (let hash in self.argCache) { + if (!usedHashes.hasOwnProperty(hash)) { delete self.argCache[hash]; } + } + for (let key in usedHashes) { delete usedHashes[key]; } + } + + /** Accumulate all shapes in `sceneShapes` into a compound, triangulate + * with ShapeToMesh, and return for rendering. If an async (Python) + * evaluation is still in flight, meshing waits for it and a Promise is + * returned instead (the onmessage router posts it once resolved). */ combineAndRenderShapes(payload) { + if (this._pendingEvaluation) { + const pending = this._pendingEvaluation; + this._pendingEvaluation = null; + return pending.then(() => this._combineAndRenderShapes(payload)); + } + return this._combineAndRenderShapes(payload); + } + + /** Synchronous meshing of the accumulated sceneShapes. */ + _combineAndRenderShapes(payload) { let oc = self.oc; // Initialize currentShape as an empty Compound Solid self.currentShape = new oc.TopoDS_Compound(); @@ -210,6 +405,11 @@ class CascadeStudioWorker { // Note: BRep_Builder and TopoDS_Compound have no overloaded constructors in v2 sceneBuilder.MakeCompound(self.currentShape); let fullShapeEdgeHashes = {}; let fullShapeFaceHashes = {}; + // Map each face/edge hash to the index of its owning top-level sceneShape, + // and record each sceneShape's producing editor line (tagged by CacheOp). + // These flow into the mesh payload so the viewport can map picks → code lines. + let faceHashToShapeIndex = {}; let edgeHashToShapeIndex = {}; + let shapeLines = []; postMessage({ "type": "Progress", "payload": { "opNumber": self.opNumber++, "opType": "Combining Shapes" } }); // If there are sceneShapes, iterate through them and add them to currentShape @@ -217,31 +417,39 @@ class CascadeStudioWorker { for (let shapeInd = 0; shapeInd < self.sceneShapes.length; shapeInd++) { if (!self.sceneShapes[shapeInd] || !self.sceneShapes[shapeInd].IsNull || self.sceneShapes[shapeInd].IsNull()) { console.error("Null Shape detected in sceneShapes; skipping: " + JSON.stringify(self.sceneShapes[shapeInd])); + shapeLines[shapeInd] = -1; continue; } if (!self.sceneShapes[shapeInd].ShapeType) { console.error("Non-Shape detected in sceneShapes; " + "are you sure it is a TopoDS_Shape and not something else that needs to be converted to one?"); console.error(JSON.stringify(self.sceneShapes[shapeInd])); + shapeLines[shapeInd] = -1; continue; } // Scan the edges and faces and add to the edge list - Object.assign(fullShapeEdgeHashes, self.ForEachEdge(self.sceneShapes[shapeInd], (index, edge) => { })); + let shapeEdgeHashes = self.ForEachEdge(self.sceneShapes[shapeInd], (index, edge) => { }); + Object.assign(fullShapeEdgeHashes, shapeEdgeHashes); + for (let edgeHash in shapeEdgeHashes) { edgeHashToShapeIndex[edgeHash] = shapeInd; } self.ForEachFace(self.sceneShapes[shapeInd], (index, face) => { - fullShapeFaceHashes[self.oc.OCJS.HashCode(face, 100000000)] = index; + let faceHash = self.oc.OCJS.HashCode(face, 100000000); + fullShapeFaceHashes[faceHash] = index; + faceHashToShapeIndex[faceHash] = shapeInd; }); + shapeLines[shapeInd] = self.sceneShapes[shapeInd].producingLine || -1; sceneBuilder.Add(self.currentShape, self.sceneShapes[shapeInd]); } // Use ShapeToMesh to output triangulated faces and discretized edges to the 3D Viewport postMessage({ "type": "Progress", "payload": { "opNumber": self.opNumber++, "opType": "Triangulating Faces" } }); let facesAndEdges = self.ShapeToMesh(self.currentShape, - payload.maxDeviation || 0.1, fullShapeEdgeHashes, fullShapeFaceHashes); + payload.maxDeviation || 0.1, fullShapeEdgeHashes, fullShapeFaceHashes, + faceHashToShapeIndex, edgeHashToShapeIndex); self.sceneShapes = []; postMessage({ "type": "Progress", "payload": { "opNumber": self.opNumber, "opType": "" } }); - return [facesAndEdges, payload.sceneOptions]; + return [facesAndEdges, payload.sceneOptions, shapeLines]; } else { console.error("There were no scene shapes returned!"); } diff --git a/packages/cascade-core/src/worker/FileUtils.js b/packages/cascade-core/src/worker/FileUtils.js index a64807ce..4f082589 100644 --- a/packages/cascade-core/src/worker/FileUtils.js +++ b/packages/cascade-core/src/worker/FileUtils.js @@ -15,19 +15,45 @@ class CascadeStudioFileIO { self.importSTEPorIGES = this.importSTEPorIGES.bind(this); self.importSTL = this.importSTL.bind(this); self.saveShapeSTEP = this.saveShapeSTEP.bind(this); + self.GetExternalShape = this.getExternalShape.bind(this); + } + + /** Look up an already-imported external shape by file name, tolerating a + * full path (only the base name is keyed) and case. Python mode's + * `import_step()` resolves its asset this way: the worker has no + * filesystem, so the harness/app hands the file over ahead of the run + * (CascadeAPI.loadExternalFiles → loadPrexistingExternalFiles) and the + * script's `os.path.join(dirname(__file__), "x.step")` is matched on + * "x.step". Returns null when the asset was never delivered. */ + getExternalShape(name) { + const shapes = self.externalShapes || {}; + if (shapes[name]) { return shapes[name]; } + const base = String(name).split('/').pop().toLowerCase(); + for (const key of Object.keys(shapes)) { + if (key.split('/').pop().toLowerCase() === base) { return shapes[key]; } + } + return null; } /** Synchronously loads the "files" in the current project into * the `externalFiles` dictionary upon startup. */ loadPrexistingExternalFiles(externalFileDict) { console.log("Loading Pre-Existing external files..."); + const loaded = []; for (let key in externalFileDict) { - if (key.includes(".stl")) { - this.importSTL(key, externalFileDict[key].content); - } else { - this.importSTEPorIGES(key, externalFileDict[key].content); + let shape = null; + try { + shape = key.includes(".stl") + ? this.importSTL(key, externalFileDict[key].content) + : this.importSTEPorIGES(key, externalFileDict[key].content); + } catch (e) { + console.log("Failed to import " + key + ": " + e.message); } + if (shape) { loaded.push(key); } } + // Reported back so callers can WAIT for the import (CascadeAPI's + // loadExternalFiles awaits this) instead of racing the next evaluation. + return loaded; } /** Synchronously loads a list of files into the `externalShapes` diff --git a/packages/cascade-core/src/worker/GordonSurface.js b/packages/cascade-core/src/worker/GordonSurface.js new file mode 100644 index 00000000..a2526909 --- /dev/null +++ b/packages/cascade-core/src/worker/GordonSurface.js @@ -0,0 +1,2305 @@ +// GordonSurface.js — Gordon curve-network surface interpolation for +// CascadeStudio's build123d-lite (upstream: Face.make_gordon_surface, which +// delegates to the external `ocp_gordon` package — itself a Python port of +// TiGL's Gordon interpolator). +// +// This is a faithful JS port of ocp_gordon 1.x (internal/: bspline_algorithms, +// curves_to_surface, points_to_bspline_interpolation, bspline_approx_interp, +// curve_network_sorter, gordon_surface_builder, interpolate_curve_network), +// with these honest deviations (see the COMPROMISE notes inline): +// +// - COMPROMISE(gordon-intersections): curve/curve intersection points are +// found with OCCT's GeomAPI_ExtremaCurveCurve (+ endpoint projections) +// instead of ocp_gordon's recursive box-subdivision + BFGS refinement +// (OCP's math_BFGS/math_Matrix are not compiled into this wasm). Both +// find the same parameter pairs for networks whose curve pairs intersect +// transversally at <= 2 points — the only networks the upstream algorithm +// accepts anyway (it raises on > 2 intersections). +// +// - COMPROMISE(gordon-reparam-interpolate): the 1-D reparametrization +// function old(new) is interpolated with a pure-JS reimplementation of +// Geom2dAPI_Interpolate's no-tangent path (clamped C2 cubic with +// Lagrange-cubic end tangents; single-span quadratic for 3 points, +// linear for 2) — verified pole-for-pole against the native OCP class — +// because Geom2dAPI_Interpolate is not compiled into this wasm. +// +// - COMPROMISE(gordon-conic-approx): conic input edges (circle/ellipse +// arcs) are converted to EXACT rational quadratic B-splines in JS +// (affine-mapped unit-circle arcs) and then approximated to non-rational +// splines with the ported least-squares machinery below, targeting +// GeomConvert_ApproxCurve's tolerance (Precision::Approximation * size / +// 200) — that class is not compiled into this wasm. Upstream approximates +// the conic directly (C2, MaxDeg 5); the fitted poles therefore differ, +// but both curves agree with the true conic to ~1e-7 * size. +// +// - COMPROMISE(gordon-surface-realization): the mathematically-exact Gordon +// surface (poles/knots computed below) CANNOT be constructed as a +// Geom_BSplineSurface in this wasm build — the concrete class does not +// compile (no constructor, no pole accessors; only the opaque handle +// exists). The final Face is instead built by sampling the exact surface +// on a dense grid (every knot line included) and REFITTING it with +// GeomAPI_PointsToBSplineSurface's C2 least-squares approximation, scored +// against the exact surface's area (see realizeSurfaceAsFace). Its +// Interpolate is unusable here: interpolating 200+ sample lines through a +// surface with a DEGENERATE boundary (point guides) oscillates at the pole +// and the boundary stops being degenerate. The refit reproduces the exact +// boundaries, degenerate poles included, to under a micron; on +// examples/bracelet the resulting tip surface is within 0.02% of the +// reference area. An `OCJS.MakeBSplineSurface(...)` C++ helper would make +// this step exact (signature in test/b123d-validation/report.md). +// +// Everything else — curve-network sorting, [0,1] reparametrization, network +// compatibility + averaging of intersection parameters, the continuous +// reparametrization approximation (Park-knot least squares with interpolation +// constraints and parameter optimization), skinning of both directions, +// the tensor-product surface, degree matching + common knot vectors, and the +// S_profiles + S_guides − S_tensor pole combination — follows ocp_gordon +// statement by statement. All B-spline curve MUTATIONS (degree elevation, +// knot insertion/removal, segmenting) are delegated to the wasm's fully-bound +// Geom_BSplineCurve so the numerics match OCCT's exactly; surfaces (whose +// concrete class is unavailable) are held as plain {poles, knots, mults, +// degrees} data and mutated column-by-column/row-by-row through the same +// OCCT curve calls — mathematically identical to the Geom_BSplineSurface +// methods, which operate per pole row/column. + +'use strict'; + +const REL_TOL_CLOSED = 1e-8; // BSplineAlgorithms::REL_TOL_CLOSED +const PAR_CHECK_TOL = 1e-5; // BSplineAlgorithms::PAR_CHECK_TOL +const CONFUSION = 1e-7; // Precision::Confusion +const PCONFUSION = 1e-9; // Precision::PConfusion +const APPROXIMATION = 1e-6; // Precision::Approximation + +// ============================ small linear algebra ========================== + +/** Solve A x = B for dense A (n×n) and B (n×m); Gaussian elimination with + * partial pivoting. A and B are modified. Returns x as n×m array. */ +function solveDense(A, B) { + const n = A.length; + const m = B[0].length; + for (let col = 0; col < n; col++) { + let piv = col; + for (let r = col + 1; r < n; r++) { + if (Math.abs(A[r][col]) > Math.abs(A[piv][col])) piv = r; + } + if (Math.abs(A[piv][col]) < 1e-300) throw new Error('Gordon: singular matrix in linear solve'); + if (piv !== col) { + const t = A[piv]; A[piv] = A[col]; A[col] = t; + const tb = B[piv]; B[piv] = B[col]; B[col] = tb; + } + const d = A[col][col]; + for (let r = col + 1; r < n; r++) { + const f = A[r][col] / d; + if (f === 0) continue; + for (let c = col; c < n; c++) A[r][c] -= f * A[col][c]; + for (let c = 0; c < m; c++) B[r][c] -= f * B[col][c]; + } + } + for (let col = n - 1; col >= 0; col--) { + const d = A[col][col]; + for (let c = 0; c < m; c++) { + let s = B[col][c]; + for (let k = col + 1; k < n; k++) s -= A[col][k] * B[k][c]; + B[col][c] = s / d; + } + } + return B; +} + +/** Derivative at t of the Lagrange polynomial through (ts, ys). */ +function lagrangeDeriv(ts, ys, t) { + const n = ts.length; + let out = 0.0; + for (let j = 0; j < n; j++) { + let s = 0.0; + for (let mth = 0; mth < n; mth++) { + if (mth === j) continue; + let prod = 1.0; + for (let k = 0; k < n; k++) { + if (k === j || k === mth) continue; + prod *= (t - ts[k]) / (ts[j] - ts[k]); + } + s += prod / (ts[j] - ts[mth]); + } + out += ys[j] * s; + } + return out; +} + +// ============================ B-spline basics =============================== +// Curve data: {deg, knots:[unique], mults:[], poles:[[x,y,z],...], +// weights:null|[...], periodic:bool} +// Surface data: {udeg, vdeg, uknots, umults, vknots, vmults, +// poles: poles[i][j] = [x,y,z]} (non-rational) + +function flatKnots(knots, mults) { + const out = []; + for (let i = 0; i < knots.length; i++) { + for (let k = 0; k < mults[i]; k++) out.push(knots[i]); + } + return out; +} + +/** 0-based find-span: returns s with flat[s] <= u < flat[s+1], clamped to + * [deg, ncp-1] (Piegl A2.1 conventions). */ +function findSpan(flat, deg, u) { + const n = flat.length - deg - 2; // last pole index + if (u >= flat[n + 1]) return n; + if (u <= flat[deg]) return deg; + let low = deg, high = n + 1; + let mid = (low + high) >> 1; + while (u < flat[mid] || u >= flat[mid + 1]) { + if (u < flat[mid]) high = mid; else low = mid; + mid = (low + high) >> 1; + } + return mid; +} + +/** Nonzero basis functions and derivatives at u (Piegl A2.3). + * Returns ders[k][j], k = 0..nDers, j = 0..deg (pole index span-deg+j). */ +function dersBasis(flat, deg, u, nDers) { + const span = findSpan(flat, deg, u); + const ndu = []; + for (let i = 0; i <= deg; i++) ndu.push(new Array(deg + 1).fill(0)); + const left = new Array(deg + 1).fill(0); + const right = new Array(deg + 1).fill(0); + ndu[0][0] = 1.0; + for (let j = 1; j <= deg; j++) { + left[j] = u - flat[span + 1 - j]; + right[j] = flat[span + j] - u; + let saved = 0.0; + for (let r = 0; r < j; r++) { + ndu[j][r] = right[r + 1] + left[j - r]; + const temp = ndu[r][j - 1] / ndu[j][r]; + ndu[r][j] = saved + right[r + 1] * temp; + saved = left[j - r] * temp; + } + ndu[j][j] = saved; + } + const ders = []; + for (let k = 0; k <= nDers; k++) ders.push(new Array(deg + 1).fill(0)); + for (let j = 0; j <= deg; j++) ders[0][j] = ndu[j][deg]; + const a = [new Array(deg + 1).fill(0), new Array(deg + 1).fill(0)]; + for (let r = 0; r <= deg; r++) { + let s1 = 0, s2 = 1; + a[0][0] = 1.0; + for (let k = 1; k <= nDers && k <= deg; k++) { + let d = 0.0; + const rk = r - k, pk = deg - k; + if (r >= k) { a[s2][0] = a[s1][0] / ndu[pk + 1][rk]; d = a[s2][0] * ndu[rk][pk]; } + const j1 = rk >= -1 ? 1 : -rk; + const j2 = (r - 1 <= pk) ? k - 1 : deg - r; + for (let j = j1; j <= j2; j++) { + a[s2][j] = (a[s1][j] - a[s1][j - 1]) / ndu[pk + 1][rk + j]; + d += a[s2][j] * ndu[rk + j][pk]; + } + if (r <= pk) { a[s2][k] = -a[s1][k - 1] / ndu[pk + 1][r]; d += a[s2][k] * ndu[r][pk]; } + ders[k][r] = d; + const t = s1; s1 = s2; s2 = t; + } + } + let rfac = deg; + for (let k = 1; k <= nDers && k <= deg; k++) { + for (let j = 0; j <= deg; j++) ders[k][j] *= rfac; + rfac *= (deg - k); + } + return { span, ders }; +} + +/** B-spline basis matrix — BSplineAlgorithms.bspline_basis_mat. + * rows = params, cols = ncp = flat.length - deg - 1. */ +function basisMat(deg, flat, params, derivOrder) { + const ncp = flat.length - deg - 1; + const out = []; + for (let ip = 0; ip < params.length; ip++) { + const row = new Array(ncp).fill(0); + const { span, ders } = dersBasis(flat, deg, params[ip], derivOrder); + for (let j = 0; j <= deg; j++) { + const col = span - deg + j; + if (col >= 0 && col < ncp) row[col] = ders[derivOrder][j]; + } + out.push(row); + } + return out; +} + +/** Rational-aware curve point (and up to 2 derivatives) at u. */ +function curveEval(d, u, nDers) { + nDers = nDers || 0; + const flat = flatKnots(d.knots, d.mults); + const { span, ders } = dersBasis(flat, d.deg, u, nDers); + const W = d.weights; + // homogeneous accumulation + const A = []; + for (let k = 0; k <= nDers; k++) A.push([0, 0, 0, 0]); + const npoles = d.poles.length; + for (let j = 0; j <= d.deg; j++) { + let idx = span - d.deg + j; + if (d.periodic) idx = ((idx % npoles) + npoles) % npoles; + if (idx < 0 || idx >= npoles) continue; + const w = W ? W[idx] : 1.0; + const P = d.poles[idx]; + for (let k = 0; k <= nDers; k++) { + const b = ders[k][j]; + A[k][0] += b * P[0] * w; A[k][1] += b * P[1] * w; + A[k][2] += b * P[2] * w; A[k][3] += b * w; + } + } + if (!W) { + return A.map((v) => [v[0], v[1], v[2]]); + } + // rational derivatives (quotient rule, up to order 2) + const C0 = [A[0][0] / A[0][3], A[0][1] / A[0][3], A[0][2] / A[0][3]]; + const out = [C0]; + if (nDers >= 1) { + const w0 = A[0][3], w1 = A[1][3]; + const C1 = [0, 1, 2].map((i) => (A[1][i] - w1 * C0[i]) / w0); + out.push(C1); + if (nDers >= 2) { + const w2 = A[2][3]; + const C2 = [0, 1, 2].map((i) => (A[2][i] - w2 * C0[i] - 2 * w1 * C1[i]) / w0); + out.push(C2); + } + } + return out; +} + +function curvePoint(d, u) { return curveEval(d, u, 0)[0]; } + +function dist3(a, b) { + const dx = a[0] - b[0], dy = a[1] - b[1], dz = a[2] - b[2]; + return Math.sqrt(dx * dx + dy * dy + dz * dz); +} + +function firstParam(d) { return d.knots[0]; } +function lastParam(d) { return d.knots[d.knots.length - 1]; } + +/** curve_network_sorter._is_zero_length_curve */ +function isZeroLength(d, tol) { + tol = tol || 1e-9; + const ref = d.poles[0]; + for (let i = 1; i < d.poles.length; i++) { + if (dist3(ref, d.poles[i]) > tol) return false; + } + return true; +} + +/** BSplineAlgorithms.scale(curve): max pole distance from the first pole. */ +function curveScale(d) { + let s = 0.0; + for (let i = 1; i < d.poles.length; i++) s = Math.max(s, dist3(d.poles[0], d.poles[i])); + return s > 0 ? s : 1.0; +} +function curveListScale(list) { + let s = 0.0; + for (const d of list) s = Math.max(s, curveScale(d)); + return s > 0 ? s : 1.0; +} +/** BSplineAlgorithms.scale(TColgp_Array2OfPnt) on poles[i][j]. */ +function grid2Scale(grid) { + let s = 0.0; + for (let i = 0; i < grid.length; i++) { + const pFirst = grid[i][0]; + for (let j = 1; j < grid[i].length; j++) s = Math.max(s, dist3(pFirst, grid[i][j])); + } + return s; +} + +/** Geom_BSplineCurve::IsEqual-alike on data. */ +function curvesEqual(a, b, tol) { + if (a.deg !== b.deg || a.poles.length !== b.poles.length || + a.knots.length !== b.knots.length || !!a.periodic !== !!b.periodic) return false; + for (let i = 0; i < a.knots.length; i++) { + if (Math.abs(a.knots[i] - b.knots[i]) > tol || a.mults[i] !== b.mults[i]) return false; + } + for (let i = 0; i < a.poles.length; i++) { + if (dist3(a.poles[i], b.poles[i]) > tol) return false; + const wa = a.weights ? a.weights[i] : 1, wb = b.weights ? b.weights[i] : 1; + if (Math.abs(wa - wb) > tol) return false; + } + return true; +} + +function cloneCurve(d) { + return { + deg: d.deg, periodic: !!d.periodic, + knots: d.knots.slice(), mults: d.mults.slice(), + poles: d.poles.map((p) => p.slice()), + weights: d.weights ? d.weights.slice() : null, + }; +} + +/** Reverse (Geom_BSplineCurve::Reverse on data). */ +function reverseCurveData(d) { + const first = d.knots[0], last = d.knots[d.knots.length - 1]; + d.poles.reverse(); + if (d.weights) d.weights.reverse(); + const nk = d.knots.length; + const newKnots = []; + for (let i = nk - 1; i >= 0; i--) newKnots.push(first + last - d.knots[i]); + d.knots = newKnots; + d.mults.reverse(); +} + +/** Linear knot re-map — BSplCLib::Reparametrize + SetKnots. */ +function reparametrizeSimple(d, umin, umax) { + const f = d.knots[0], l = d.knots[d.knots.length - 1]; + const a = (umax - umin) / (l - f); + d.knots = d.knots.map((k) => umin + (k - f) * a); + // exact endpoints (avoid drift) + d.knots[0] = umin; d.knots[d.knots.length - 1] = umax; +} + +// ================== OCCT-backed curve mutations (exact numerics) ============ + +function makeEngine(oc) { + + function dataToOCCT(d) { + const np = d.poles.length; + const poles = new oc.TColgp_Array1OfPnt_2(1, np); + for (let i = 0; i < np; i++) { + poles.SetValue(i + 1, new oc.gp_Pnt_3(d.poles[i][0], d.poles[i][1], d.poles[i][2])); + } + const knots = new oc.TColStd_Array1OfReal_2(1, d.knots.length); + for (let i = 0; i < d.knots.length; i++) knots.SetValue(i + 1, d.knots[i]); + const mults = new oc.TColStd_Array1OfInteger_2(1, d.mults.length); + for (let i = 0; i < d.mults.length; i++) mults.SetValue(i + 1, d.mults[i]); + if (d.weights) { + const w = new oc.TColStd_Array1OfReal_2(1, np); + for (let i = 0; i < np; i++) w.SetValue(i + 1, d.weights[i]); + return new oc.Geom_BSplineCurve_2(poles, w, knots, mults, d.deg, !!d.periodic, false); + } + return new oc.Geom_BSplineCurve_1(poles, knots, mults, d.deg, !!d.periodic); + } + + function occtToData(c) { + const np = c.NbPoles(), nk = c.NbKnots(); + const d = { + deg: c.Degree(), periodic: c.IsPeriodic(), + knots: [], mults: [], poles: [], weights: null, + }; + for (let i = 1; i <= nk; i++) { d.knots.push(c.Knot(i)); d.mults.push(c.Multiplicity(i)); } + for (let i = 1; i <= np; i++) { + const p = c.Pole(i); + d.poles.push([p.X(), p.Y(), p.Z()]); + } + if (c.IsRational()) { + d.weights = []; + for (let i = 1; i <= np; i++) d.weights.push(c.Weight(i)); + } + return d; + } + + function withOCCT(d, fn) { + const c = dataToOCCT(d); + fn(c); + const out = occtToData(c); + return out; + } + + function increaseDegreeData(d, deg) { + if (d.deg >= deg) return d; + return withOCCT(d, (c) => c.IncreaseDegree(deg)); + } + + /** _insert_knots port: for each (knot, mult): raise an existing (within + * tol) knot's multiplicity to mult, else insert it. */ + function insertKnotsData(d, knots, mults, tol) { + return withOCCT(d, (c) => { + for (let k = 0; k < knots.length; k++) { + let exists = false; + const nk = c.NbKnots(); + for (let i = 1; i <= nk; i++) { + if (Math.abs(c.Knot(i) - knots[k]) < tol) { + exists = true; + c.IncreaseMultiplicity_1(i, mults[k]); + break; + } + } + if (!exists) c.InsertKnot(knots[k], mults[k], tol, false); + } + }); + } + + function segmentData(d, u1, u2) { + return withOCCT(d, (c) => c.Segment(u1, u2, PCONFUSION)); + } + + function setNotPeriodicData(d) { + if (!d.periodic) return d; + return withOCCT(d, (c) => c.SetNotPeriodic()); + } + + /** Try to reduce knot #index (1-based over unique knots) to multiplicity M + * within tol. Returns {ok, data}. */ + function removeKnotData(d, index, M, tol) { + const c = dataToOCCT(d); + let ok = false; + try { ok = !!c.RemoveKnot(index, M, tol); } catch (e) { ok = false; } + return { ok, data: ok ? occtToData(c) : d }; + } + + // =========================== intersections ================================ + // COMPROMISE(gordon-intersections) — see file header. + + function projectPointParam(pnt, curveData_) { + const c = dataToOCCT(curveData_); + const h = new oc.Handle_Geom_Curve_2(c); + const p = new oc.gp_Pnt_3(pnt[0], pnt[1], pnt[2]); + let best = null; + try { + const proj = new oc.GeomAPI_ProjectPointOnCurve_2(p, h); + const n = proj.NbPoints(); + for (let i = 1; i <= n; i++) { + const par = proj.Parameter(i); + const q = proj.Point(i); + const dd = dist3(pnt, [q.X(), q.Y(), q.Z()]); + if (best === null || dd < best[1]) best = [par, dd]; + } + } catch (e) { /* extrema may fail for degenerate configs */ } + // endpoints too (projection reports only interior extrema) + for (const t of [firstParam(curveData_), lastParam(curveData_)]) { + const q = curvePoint(curveData_, t); + const dd = dist3(pnt, q); + if (best === null || dd < best[1]) best = [t, dd]; + } + return best; // [param, distance] + } + + /** All intersections of two curve datas within (absolute) tolerance. + * Returns [[paramOnC1, paramOnC2], ...] — IntersectBSplines equivalent. */ + function intersectCurves(d1, d2, tol) { + const zero1 = isZeroLength(d1), zero2 = isZeroLength(d2); + if (zero1 && zero2) { + return dist3(d1.poles[0], d2.poles[0]) <= tol ? [[0.0, 0.0]] : []; + } + if (zero2) { + const r = projectPointParam(d2.poles[0], d1); + return r && r[1] <= tol ? [[r[0], 0.0]] : []; + } + if (zero1) { + const r = projectPointParam(d1.poles[0], d2); + return r && r[1] <= tol ? [[0.0, r[0]]] : []; + } + const cands = []; + const c1 = dataToOCCT(d1), c2 = dataToOCCT(d2); + const h1 = new oc.Handle_Geom_Curve_2(c1), h2 = new oc.Handle_Geom_Curve_2(c2); + try { + const ext = new oc.GeomAPI_ExtremaCurveCurve_2(h1, h2); + const n = ext.NbExtrema(); + for (let i = 1; i <= n; i++) { + if (ext.Distance(i) <= tol) { + const u = { current: 0 }, v = { current: 0 }; + ext.Parameters(i, u, v); + cands.push([u.current, v.current]); + } + } + } catch (e) { /* no extrema */ } + // endpoint hits (boundary intersections are not always extrema) + for (const t of [firstParam(d1), lastParam(d1)]) { + const r = projectPointParam(curvePoint(d1, t), d2); + if (r && r[1] <= tol) cands.push([t, r[0]]); + } + for (const t of [firstParam(d2), lastParam(d2)]) { + const r = projectPointParam(curvePoint(d2, t), d1); + if (r && r[1] <= tol) cands.push([r[0], t]); + } + // dedupe (parameter space) + const eps1 = 1e-5 * Math.max(1e-30, Math.abs(lastParam(d1) - firstParam(d1))); + const eps2 = 1e-5 * Math.max(1e-30, Math.abs(lastParam(d2) - firstParam(d2))); + const out = []; + for (const c of cands) { + let dup = false; + for (const o of out) { + if (Math.abs(c[0] - o[0]) < eps1 && Math.abs(c[1] - o[1]) < eps2) { dup = true; break; } + } + if (!dup) out.push(c); + } + return out; + } + + // ================= PointsToBSplineInterpolation (Park 2000) =============== + + /** BSplineAlgorithms.knots_from_curve_parameters — NOTE: may mutate params + * (closed even-degree shift), exactly like the Python. */ + function knotsFromCurveParameters(params, degree, closedCurve) { + if (params.length < 2) throw new Error('Parameters must contain two or more elements.'); + let nCp = params.length; + if (closedCurve) nCp += degree - 1; + const nInner = nCp - degree + 1; + const inner = new Array(nInner).fill(0); + inner[0] = params[0]; + inner[nInner - 1] = params[params.length - 1]; + const knots = []; + if (closedCurve && degree % 2 === 0) { + const m = params.length - 2; + const dparm = new Array(m + 1).fill(0); + for (let i = 0; i <= m; i++) dparm[i] = params[i + 1] - params[i]; + inner[1] = inner[0] + 0.5 * (dparm[0] + dparm[m]); + for (let i = 1; i < m; i++) inner[i + 1] = inner[i] + 0.5 * (dparm[i - 1] + dparm[i]); + for (let i = 0; i < params.length; i++) params[i] += dparm[m] / 2.0; + } else if (closedCurve) { + if (inner.length !== params.length) throw new Error('Inner knots size mismatch'); + for (let i = 0; i < params.length; i++) inner[i] = params[i]; + } else { + for (let j = 1; j < params.length - degree; j++) { + let sum = 0.0; + for (let i = j; i < j + degree; i++) sum += params[i]; + inner[j] = sum / degree; + } + } + if (closedCurve) { + const offset = inner[0] - inner[nInner - 1]; + for (let ik = 0; ik < degree; ik++) knots.push(offset + inner[nInner - degree - 1 + ik]); + for (let ik = 0; ik < nInner; ik++) knots.push(inner[ik]); + for (let ik = 0; ik < degree; ik++) knots.push(-offset + inner[ik + 1]); + } else { + for (let ik = 0; ik < degree; ik++) knots.push(inner[0]); + for (let ik = 0; ik < nInner; ik++) knots.push(inner[ik]); + for (let ik = 0; ik < degree; ik++) knots.push(inner[nInner - 1]); + } + if (closedCurve && degree <= 1) { + knots[0] = knots[1]; + knots[knots.length - 1] = knots[knots.length - 2]; + } + return knots; + } + + /** flat knot vector -> unique knots + multiplicities (BSplCLib::Knots). */ + function uniqueKnots(flat) { + const knots = [], mults = []; + for (const k of flat) { + if (knots.length && Math.abs(k - knots[knots.length - 1]) < 1e-12) { + mults[mults.length - 1]++; + } else { knots.push(k); mults.push(1); } + } + return { knots, mults }; + } + + function maxDistanceOfPoints(points) { + let m = 0.0; + for (let i = 0; i < points.length; i++) { + for (let j = i + 1; j < points.length; j++) m = Math.max(m, dist3(points[i], points[j])); + } + return m; + } + + /** PointsToBSplineInterpolation.curve() — points: [[x,y,z]], + * parameters given (gordon always passes them). */ + function pointsToBSplineInterpolation(points, parameters, maxDegree, continuousIfClosed) { + maxDegree = maxDegree === undefined ? 3 : maxDegree; + const nPts = points.length; + if (nPts < 2) throw new Error('Too few points in PointsToBSplineInterpolation'); + if (parameters.length !== nPts) throw new Error('Number of parameters and points don\'t match'); + const maxDist = maxDistanceOfPoints(points); + const isClosed = dist3(points[0], points[nPts - 1]) <= 1e-6 * maxDist && !!continuousIfClosed; + let maxAllowed = nPts - 1; + if (isClosed) maxAllowed -= 1; + const degree = Math.min(maxAllowed, maxDegree); + if (degree <= 0) throw new Error('Invalid degree computed'); + const needsShift = (degree % 2 === 0) && isClosed; + + let params = parameters.slice(); + const knots = knotsFromCurveParameters(params, degree, isClosed); + if (isClosed) params = params.slice(0, -1); + + const nParams = params.length; + const bsplMat = basisMat(degree, knots, params, 0); + const lhs = []; + for (let i = 0; i < nParams; i++) { + const row = new Array(nParams).fill(0); + for (let j = 0; j < nParams; j++) row[j] = bsplMat[i][j]; + lhs.push(row); + } + if (isClosed) { + for (let j = 0; j < degree; j++) { + for (let i = 0; i < nParams; i++) lhs[i][j] += bsplMat[i][nParams + j]; + } + } + const rhs = []; + for (let i = 0; i < nParams; i++) rhs.push(points[i].slice()); + const cp = solveDense(lhs, rhs); + + let nCtrl = isClosed ? parameters.length + degree - 1 : nParams; + if (needsShift) nCtrl += 1; + const poles = []; + for (let i = 0; i < nParams; i++) poles.push(cp[i].slice()); + if (isClosed) { + for (let i = 0; i < degree; i++) poles.push(cp[i].slice()); + } + if (needsShift) { + const deg = degree; + knots.push(knots[knots.length - 1] + knots[2 * deg + 1] - knots[2 * deg]); + poles.push(poles[deg].slice()); + for (let i = 0; i < knots.length; i++) knots[i] -= params[0]; + } + while (poles.length < nCtrl) poles.push(poles[poles.length - 1].slice()); + + const uk = uniqueKnots(knots); + let result = { + deg: degree, periodic: false, knots: uk.knots, mults: uk.mults, + poles, weights: null, + }; + if (isClosed) { + // clamp: Geom_TrimmedCurve + CurveToBSplineCurve == Segment + result = segmentData(result, parameters[0], parameters[parameters.length - 1]); + } + return result; + } + + // ======================= BSplineApproxInterp ============================== + + function insertKnotWithMult(knot, count, degree, knots, mults, tol) { + if (!(knots[0] - tol <= knot && knot <= knots[knots.length - 1] + tol)) { + throw new Error('knot out of range'); + } + let found = -1; + for (let i = 0; i < knots.length; i++) { + if (Math.abs(knots[i] - knot) < tol) { found = i; break; } + } + if (found >= 0) { + mults[found] = Math.min(mults[found] + count, degree); + } else { + let idx = 0; + while (idx < knots.length && knots[idx] < knot) idx++; + knots.splice(idx, 0, knot); + mults.splice(idx, 0, Math.min(count, degree)); + } + } + + class BSplineApproxInterp { + constructor(points, nControlPoints, degree, continuousIfClosed) { + this.pnts = points.map((p) => p.slice()); + this.indexOfApproximated = []; + for (let i = 0; i < points.length; i++) this.indexOfApproximated.push(i); + this.indexOfInterpolated = []; + this.indexOfKinks = []; + this.degree = degree === undefined ? 3 : degree; + this.ncp = nControlPoints; + this.c2Continuous = !!continuousIfClosed; + } + + interpolatePoint(pointIndex, withKink) { + const pos = this.indexOfApproximated.indexOf(pointIndex); + if (pos < 0) throw new Error('Invalid index in BSplineApproxInterp::interpolate_point'); + this.indexOfApproximated.splice(pos, 1); + this.indexOfInterpolated.push(pointIndex); + if (withKink) this.indexOfKinks.push(pointIndex); + } + + maxDistanceOfBoundingBox() { + const lo = [Infinity, Infinity, Infinity], hi = [-Infinity, -Infinity, -Infinity]; + for (const p of this.pnts) { + for (let k = 0; k < 3; k++) { lo[k] = Math.min(lo[k], p[k]); hi[k] = Math.max(hi[k], p[k]); } + } + const d = [hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2]]; + return Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); + } + + isClosed() { + if (!this.c2Continuous) return false; + const err = 1e-12 * this.maxDistanceOfBoundingBox(); + return dist3(this.pnts[0], this.pnts[this.pnts.length - 1]) <= err; + } + + firstAndLastInterpolated() { + return this.indexOfInterpolated.indexOf(0) >= 0 && + this.indexOfInterpolated.indexOf(this.pnts.length - 1) >= 0; + } + + computeKnots(ncp, params) { + const order = this.degree + 1; + if (ncp < order) throw new Error('Number of control points too small!'); + const umin = Math.min(...params), umax = Math.max(...params); + const knots = new Array(ncp - this.degree + 1).fill(0); + const mults = new Array(ncp - this.degree + 1).fill(0); + knots[0] = umin; mults[0] = order; + const N = ncp - order; + for (let i = 1; i <= N; i++) { + knots[i] = umin + (umax - umin) * i / (N + 1); + mults[i] = 1; + } + knots[N + 1] = umax; mults[N + 1] = order; + for (const kinkIdx of this.indexOfKinks) { + insertKnotWithMult(params[kinkIdx], this.degree, this.degree, knots, mults, 1e-4); + } + return { knots, mults }; + } + + getContinuityMatrix(nCtrPnts, continCons, params, flat) { + const rows = []; + const p1 = [params[0]], p2 = [params[params.length - 1]]; + const d11 = basisMat(this.degree, flat, p1, 1)[0]; + const d12 = basisMat(this.degree, flat, p2, 1)[0]; + const d21 = basisMat(this.degree, flat, p1, 2)[0]; + const d22 = basisMat(this.degree, flat, p2, 2)[0]; + rows.push(d11.map((v, i) => v - d12[i])); // C1 + rows.push(d21.map((v, i) => v - d22[i])); // C2 + if (!this.firstAndLastInterpolated()) { + const d01 = basisMat(this.degree, flat, p1, 0)[0]; + const d02 = basisMat(this.degree, flat, p2, 0)[0]; + rows.push(d01.map((v, i) => v - d02[i])); // C0 + } + while (rows.length < continCons) rows.push(new Array(nCtrPnts).fill(0)); + return rows.slice(0, continCons); + } + + solve(params, knots, mults) { + const flat = flatKnots(knots, mults); + const nApprox = this.indexOfApproximated.length; + const nInterp = this.indexOfInterpolated.length; + let nContin = 0; + const makeClosed = this.isClosed(); + if (makeClosed) { + nContin = 3; + if (this.firstAndLastInterpolated()) nContin -= 1; + } + const nCtrPnts = flat.length - this.degree - 1; + if (nCtrPnts < nInterp + nContin || nCtrPnts < this.degree + 1 + nContin) { + throw new Error('Too few control points for curve interpolation!'); + } + if (nApprox === 0 && nCtrPnts !== nInterp + nContin) { + throw new Error('Wrong number of control points for curve interpolation!'); + } + const nVars = nCtrPnts + nInterp + nContin; + const lhs = []; + for (let i = 0; i < nVars; i++) lhs.push(new Array(nVars).fill(0)); + const rhs = []; + for (let i = 0; i < nVars; i++) rhs.push([0, 0, 0]); + + if (nApprox > 0) { + const appParams = this.indexOfApproximated.map((i) => params[i]); + const A = basisMat(this.degree, flat, appParams, 0); + // lhs[0:n,0:n] = At*A ; rhs[0:n] = At*b + for (let i = 0; i < nCtrPnts; i++) { + for (let j = 0; j < nCtrPnts; j++) { + let s = 0.0; + for (let r = 0; r < appParams.length; r++) s += A[r][i] * A[r][j]; + lhs[i][j] = s; + } + for (let c = 0; c < 3; c++) { + let s = 0.0; + for (let r = 0; r < appParams.length; r++) { + s += A[r][i] * this.pnts[this.indexOfApproximated[r]][c]; + } + rhs[i][c] = s; + } + } + } + if (nInterp + nContin > 0) { + if (nInterp > 0) { + const interpParams = this.indexOfInterpolated.map((i) => params[i]); + const C = basisMat(this.degree, flat, interpParams, 0); + for (let r = 0; r < nInterp; r++) { + for (let j = 0; j < nCtrPnts; j++) { + lhs[j][nCtrPnts + r] = C[r][j]; + lhs[nCtrPnts + r][j] = C[r][j]; + } + const p = this.pnts[this.indexOfInterpolated[r]]; + rhs[nCtrPnts + r] = [p[0], p[1], p[2]]; + } + } + if (makeClosed) { + const cm = this.getContinuityMatrix(nCtrPnts, nContin, params, flat); + for (let r = 0; r < nContin; r++) { + for (let j = 0; j < nCtrPnts; j++) { + lhs[nCtrPnts + nInterp + r][j] = cm[r][j]; + lhs[j][nCtrPnts + nInterp + r] = cm[r][j]; + } + rhs[nCtrPnts + nInterp + r] = [0, 0, 0]; + } + } + } + for (let i = 0; i < nVars; i++) lhs[i][i] += 1e-15; + const sol = solveDense(lhs, rhs); + const poles = []; + for (let i = 0; i < nCtrPnts; i++) poles.push(sol[i].slice()); + const curve = { + deg: this.degree, periodic: false, + knots: knots.slice(), mults: mults.slice(), poles, weights: null, + }; + let maxError = 0.0; + for (const idx of this.indexOfApproximated) { + maxError = Math.max(maxError, dist3(curvePoint(curve, params[idx]), this.pnts[idx])); + } + return { curve, error: maxError }; + } + + projectOnCurve(pnt, curve, initialParam) { + const maxIter = 10, eps = 1e-6; + let t = initialParam, tNew = initialParam; + let diffMag = 0.0; + for (let i = 0; i < maxIter; i++) { + t = tNew; + const E = curveEval(curve, t, 2); + const diff = [E[0][0] - pnt[0], E[0][1] - pnt[1], E[0][2] - pnt[2]]; + diffMag = Math.sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2]); + const df = diff[0] * E[1][0] + diff[1] * E[1][1] + diff[2] * E[1][2]; + const d2f = diff[0] * E[2][0] + diff[1] * E[2][1] + diff[2] * E[2][2] + + E[1][0] * E[1][0] + E[1][1] * E[1][1] + E[1][2] * E[1][2]; + if (Math.abs(d2f) < 1e-12) break; + const dt = -df / d2f; + if (Math.abs(dt) < eps) break; + tNew = t + dt; + if (tNew < firstParam(curve) || tNew > lastParam(curve)) break; + } + return { parameter: t, error: diffMag }; + } + + optimizeParameters(curve, params) { + for (const idx of this.indexOfApproximated) { + const res = this.projectOnCurve(this.pnts[idx], curve, params[idx]); + params[idx] = res.parameter; + } + } + + fitCurveOptimal(initialParams, maxIter) { + maxIter = maxIter === undefined ? 10 : maxIter; + const params = (initialParams && initialParams.length) + ? initialParams.slice() : this.computeParameters(0.5); + if (params.length !== this.pnts.length) { + throw new Error("Number of parameters don't match number of points"); + } + const { knots, mults } = this.computeKnots(this.ncp, params); + let iteration = 0; + let result = this.solve(params, knots, mults); + let oldError = result.error * 2.0; + while (result.error > 0 && + (oldError - result.error) / Math.max(result.error, 1e-6) > 1e-3 && + iteration < maxIter) { + oldError = result.error; + this.optimizeParameters(result.curve, params); + result = this.solve(params, knots, mults); + iteration += 1; + } + return result; + } + + computeParameters(alpha) { + let sumLen = 0.0; + const n = this.pnts.length; + const params = new Array(n).fill(0); + for (let i = 1; i < n; i++) { + const len2 = Math.pow(dist3(this.pnts[i - 1], this.pnts[i]), 2); + sumLen += Math.pow(len2, alpha / 2.0); + params[i] = sumLen; + } + const tmax = params[n - 1]; + if (tmax < 1e-10) { + for (let i = 0; i < n; i++) params[i] = n > 1 ? i / (n - 1) : 0.0; + } else { + for (let i = 1; i < n; i++) params[i] /= tmax; + } + if (n > 0) params[n - 1] = 1.0; + return params; + } + } + + // ============== 1-D interpolation (Geom2dAPI_Interpolate port) ============ + // COMPROMISE(gordon-reparam-interpolate) — see file header. Returns an + // evaluator for the scalar function y(x) through (xs, ys) that matches + // OCCT's no-tangent interpolation exactly (verified against native OCP). + + function interpolate1D(xs, ys) { + const n = xs.length; + if (n === 2) { + const x0 = xs[0], x1 = xs[1], y0 = ys[0], y1 = ys[1]; + return (u) => y0 + (y1 - y0) * (u - x0) / (x1 - x0); + } + if (n === 3) { + // single-span quadratic: knots [x0, x2] mult 3; middle pole from + // B(t1) = y1 with t normalized + const t = (xs[1] - xs[0]) / (xs[2] - xs[0]); + const b0 = (1 - t) * (1 - t), b1 = 2 * t * (1 - t), b2 = t * t; + const p1 = (ys[1] - b0 * ys[0] - b2 * ys[2]) / b1; + const flat = [xs[0], xs[0], xs[0], xs[2], xs[2], xs[2]]; + const ctrl = [ys[0], p1, ys[2]]; + return (u) => eval1D(flat, 2, ctrl, u); + } + // n >= 4: clamped C2 cubic, knots = xs, end tangents from the Lagrange + // cubic through the first/last 4 points + const flat = [xs[0], xs[0], xs[0]]; + for (let i = 0; i < n; i++) flat.push(xs[i]); + flat.push(xs[n - 1], xs[n - 1], xs[n - 1]); + const ncp = n + 2; + const d0 = lagrangeDeriv(xs.slice(0, 4), ys.slice(0, 4), xs[0]); + const d1 = lagrangeDeriv(xs.slice(n - 4), ys.slice(n - 4), xs[n - 1]); + const A = []; + const B = []; + const rows0 = basisMat(3, flat, xs, 0); + for (let i = 0; i < n; i++) { A.push(rows0[i]); B.push([ys[i]]); } + A.push(basisMat(3, flat, [xs[0]], 1)[0]); B.push([d0]); + A.push(basisMat(3, flat, [xs[n - 1]], 1)[0]); B.push([d1]); + if (A.length !== ncp) throw new Error('interpolate1D: system size mismatch'); + const sol = solveDense(A, B); + const ctrl = sol.map((r) => r[0]); + return (u) => eval1D(flat, 3, ctrl, u); + } + + function eval1D(flat, deg, ctrl, u) { + const { span, ders } = dersBasis(flat, deg, u, 0); + let s = 0.0; + for (let j = 0; j <= deg; j++) { + const idx = span - deg + j; + if (idx >= 0 && idx < ctrl.length) s += ders[0][j] * ctrl[idx]; + } + return s; + } + + // ===================== BSplineAlgorithms (curve level) ===================== + + function linspaceWithBreaks(umin, umax, nValues, breaks) { + if (nValues < 2) return nValues === 1 ? [umin] : []; + const du = (umax - umin) / (nValues - 1); + const result = []; + for (let i = 0; i < nValues; i++) result.push(umin + i * du); + const eps = 0.3; + for (const bp of breaks) { + let foundPos = -1; + for (let i = 0; i < result.length; i++) { + if (Math.abs(result[i] - bp) < du * eps) { result[i] = bp; foundPos = i; break; } + } + if (foundPos === -1) { + let closest = -1, minDist = Infinity; + for (let i = 0; i < result.length; i++) { + const dd = Math.abs(result[i] - bp); + if (dd < minDist) { minDist = dd; closest = i; } + } + if (closest !== -1) { + if (result[closest] > bp) result.splice(closest, 0, bp); + else result.splice(closest + 1, 0, bp); + } else { + result.push(bp); + } + } + } + return result; + } + + function getKinkParameters(d) { + const eps = 1e-8; + const kinks = []; + for (let ki = 1; ki < d.knots.length - 1; ki++) { + if (d.mults[ki] === d.deg) { + const knot = d.knots[ki]; + const t1 = curveEval(d, knot + eps, 1)[1]; + const t2 = curveEval(d, knot - eps, 1)[1]; + const dot = t1[0] * t2[0] + t1[1] * t2[1] + t1[2] * t2[2]; + const m1 = Math.hypot(t1[0], t1[1], t1[2]), m2 = Math.hypot(t2[0], t2[1], t2[2]); + let ang = Math.acos(Math.max(-1, Math.min(1, dot / (m1 * m2 || 1)))); + const angleTol = 1e-4; + if (!(Math.abs(ang) < angleTol || Math.abs(ang - Math.PI) < angleTol)) kinks.push(knot); + } + } + return kinks; + } + + function matchDegree(curves) { + let maxDeg = 0; + for (const c of curves) maxDeg = Math.max(maxDeg, c.deg); + for (let i = 0; i < curves.length; i++) { + if (curves[i].deg < maxDeg) curves[i] = increaseDegreeData(curves[i], maxDeg); + } + } + + function createCommonKnotsVectorCurve(curves, tol) { + // exact-value union like the Python set(), tolerance applies on insertion + const all = []; + for (const c of curves) { + for (const k of c.knots) if (all.indexOf(k) < 0) all.push(k); + } + all.sort((a, b) => a - b); + const commonKnots = [], commonMults = []; + for (const k of all) { + let maxMult = 0; + for (const c of curves) { + for (let i = 0; i < c.knots.length; i++) { + if (Math.abs(c.knots[i] - k) < tol) { maxMult = Math.max(maxMult, c.mults[i]); break; } + } + } + commonKnots.push(k); commonMults.push(maxMult); + } + return curves.map((c) => insertKnotsData(c, commonKnots, commonMults, tol)); + } + + function reparametrizeContinuouslyApprox(spline, oldParameters, newParameters, nControlPnts) { + if (oldParameters.length !== newParameters.length) { + throw new Error('parameter sizes dont match'); + } + const repar = interpolate1D(newParameters, oldParameters); + + let breaks = newParameters.slice(1, -1); + const parTol = 1e-10; + + // kinks (mult == degree interior knots) of the input spline, mapped to + // the NEW parameter space (root of repar(t) = kink; repar is monotone) + const kinks = getKinkParameters(spline).map((kOld) => { + let lo = newParameters[0], hi = newParameters[newParameters.length - 1]; + for (let it = 0; it < 100; it++) { + const mid = 0.5 * (lo + hi); + if (repar(mid) < kOld) lo = mid; else hi = mid; + } + return 0.5 * (lo + hi); + }); + breaks = breaks.filter((b) => !kinks.some((k) => Math.abs(b - k) < parTol)); + + let parameters = linspaceWithBreaks( + newParameters[0], newParameters[newParameters.length - 1], + Math.max(101, nControlPnts * 2), breaks); + for (const k of kinks) { + let idx = 0; + while (idx < parameters.length && parameters[idx] < k) idx++; + parameters.splice(idx, 0, k); + } + + const points = parameters.map((t) => curvePoint(spline, repar(t))); + + const sp = curvePoint(spline, firstParam(spline)); + const ep = curvePoint(spline, lastParam(spline)); + let makeContinuous = false; + if (dist3(sp, ep) <= CONFUSION) { + const t1 = curveEval(spline, firstParam(spline), 1)[1]; + const t2 = curveEval(spline, lastParam(spline), 1)[1]; + const dot = t1[0] * t2[0] + t1[1] * t2[1] + t1[2] * t2[2]; + const ang = Math.acos(Math.max(-1, Math.min(1, + dot / ((Math.hypot(...t1) * Math.hypot(...t2)) || 1)))); + makeContinuous = ang < (6.0 / 180.0) * Math.PI; + } + + const approx = new BSplineApproxInterp(points, Math.round(nControlPnts), 3, makeContinuous); + const breaksWithEnds = [newParameters[0], ...breaks, newParameters[newParameters.length - 1]]; + for (const b of breaksWithEnds) { + const idx = parameters.findIndex((p) => Math.abs(p - b) < parTol); + if (idx >= 0) approx.interpolatePoint(idx, false); + } + for (const k of kinks) { + const idx = parameters.findIndex((p) => Math.abs(p - k) < parTol); + if (idx >= 0) approx.interpolatePoint(idx, true); + } + return approx.fitCurveOptimal(parameters); + } + + function computeParamsBSplineCurve(points, alpha) { + alpha = alpha === undefined ? 0.5 : alpha; + const n = points.length; + if (n < 2) return n === 1 ? [0.0, 1.0] : []; + const chord = []; + let total = 0.0; + for (let i = 1; i < n; i++) { + const l = Math.pow(dist3(points[i - 1], points[i]), alpha); + chord.push(l); total += l; + } + const params = [0.0]; + let cur = 0.0; + for (const l of chord) { cur += l; params.push(cur / total); } + return params; + } + + // ========================= surface data helpers =========================== + + function surfClone(s) { + return { + udeg: s.udeg, vdeg: s.vdeg, + uknots: s.uknots.slice(), umults: s.umults.slice(), + vknots: s.vknots.slice(), vmults: s.vmults.slice(), + poles: s.poles.map((row) => row.map((p) => p.slice())), + }; + } + + function surfNbUPoles(s) { return s.poles.length; } + function surfNbVPoles(s) { return s.poles[0].length; } + + function surfExchangeUV(s) { + const poles = []; + for (let j = 0; j < surfNbVPoles(s); j++) { + const row = []; + for (let i = 0; i < surfNbUPoles(s); i++) row.push(s.poles[i][j].slice()); + poles.push(row); + } + return { + udeg: s.vdeg, vdeg: s.udeg, + uknots: s.vknots.slice(), umults: s.vmults.slice(), + vknots: s.uknots.slice(), vmults: s.umults.slice(), + poles, + }; + } + + function uColumnCurve(s, j) { + return { + deg: s.udeg, periodic: false, + knots: s.uknots.slice(), mults: s.umults.slice(), + poles: s.poles.map((row) => row[j].slice()), weights: null, + }; + } + function vRowCurve(s, i) { + return { + deg: s.vdeg, periodic: false, + knots: s.vknots.slice(), mults: s.vmults.slice(), + poles: s.poles[i].map((p) => p.slice()), weights: null, + }; + } + + /** Apply an OCCT curve op along U (per V-column) and rebuild the surface. */ + function surfApplyU(s, op) { + const nV = surfNbVPoles(s); + const cols = []; + for (let j = 0; j < nV; j++) cols.push(op(uColumnCurve(s, j))); + const first = cols[0]; + const poles = []; + for (let i = 0; i < first.poles.length; i++) { + const row = []; + for (let j = 0; j < nV; j++) row.push(cols[j].poles[i]); + poles.push(row); + } + return { + udeg: first.deg, vdeg: s.vdeg, + uknots: first.knots, umults: first.mults, + vknots: s.vknots.slice(), vmults: s.vmults.slice(), + poles, + }; + } + function surfApplyV(s, op) { + const nU = surfNbUPoles(s); + const rows = []; + for (let i = 0; i < nU; i++) rows.push(op(vRowCurve(s, i))); + const first = rows[0]; + return { + udeg: s.udeg, vdeg: first.deg, + uknots: s.uknots.slice(), umults: s.umults.slice(), + vknots: first.knots, vmults: first.mults, + poles: rows.map((r) => r.poles), + }; + } + + function surfIncreaseDegree(s, du, dv) { + let out = s; + if (out.udeg < du) out = surfApplyU(out, (c) => increaseDegreeData(c, du)); + if (out.vdeg < dv) out = surfApplyV(out, (c) => increaseDegreeData(c, dv)); + return out; + } + + function surfInsertKnots(s, direction, knots, mults, tol) { + if (direction === 'u') return surfApplyU(s, (c) => insertKnotsData(c, knots, mults, tol)); + return surfApplyV(s, (c) => insertKnotsData(c, knots, mults, tol)); + } + + /** GordonSurfaceBuilder._create_common_knots_vector_surface_internal */ + function createCommonKnotsVectorSurface(surfaces, tol) { + let result = surfaces.map(surfClone); + for (const dir of ['u', 'v']) { + const all = []; + for (const s of result) { + const ks = dir === 'u' ? s.uknots : s.vknots; + for (const k of ks) if (all.indexOf(k) < 0) all.push(k); + } + all.sort((a, b) => a - b); + const commonKnots = [], commonMults = []; + for (const k of all) { + let maxMult = 0; + for (const s of result) { + const ks = dir === 'u' ? s.uknots : s.vknots; + const ms = dir === 'u' ? s.umults : s.vmults; + for (let i = 0; i < ks.length; i++) { + if (Math.abs(ks[i] - k) < tol) { maxMult = Math.max(maxMult, ms[i]); break; } + } + } + commonKnots.push(k); commonMults.push(maxMult); + } + result = result.map((s) => surfInsertKnots(s, dir, commonKnots, commonMults, tol)); + } + return result; + } + + function surfEval(s, u, v) { + const uflat = flatKnots(s.uknots, s.umults); + const vflat = flatKnots(s.vknots, s.vmults); + const bu = dersBasis(uflat, s.udeg, u, 0); + const bv = dersBasis(vflat, s.vdeg, v, 0); + const out = [0, 0, 0]; + for (let i = 0; i <= s.udeg; i++) { + const iu = bu.span - s.udeg + i; + if (iu < 0 || iu >= surfNbUPoles(s)) continue; + for (let j = 0; j <= s.vdeg; j++) { + const jv = bv.span - s.vdeg + j; + if (jv < 0 || jv >= surfNbVPoles(s)) continue; + const f = bu.ders[0][i] * bv.ders[0][j]; + const P = s.poles[iu][jv]; + out[0] += f * P[0]; out[1] += f * P[1]; out[2] += f * P[2]; + } + } + return out; + } + + /** Evaluate the surface on a whole parameter GRID: same values as + * surfEval(s, u, v) per point, but the flat knot vectors and basis + * functions are computed once and the u direction is collapsed to a + * control curve per u row (surfEval rebuilds the flat knots on every + * call, which dominates dense sampling). Returns out[i][j]. */ + function surfEvalGrid(s, us, vs) { + const uflat = flatKnots(s.uknots, s.umults); + const vflat = flatKnots(s.vknots, s.vmults); + const nU = surfNbUPoles(s), nV = surfNbVPoles(s); + const bvs = vs.map((v) => dersBasis(vflat, s.vdeg, v, 0)); + const out = []; + for (let a = 0; a < us.length; a++) { + const bu = dersBasis(uflat, s.udeg, us[a], 0); + const tmp = new Array(nV); + for (let jv = 0; jv < nV; jv++) tmp[jv] = [0, 0, 0]; + for (let i = 0; i <= s.udeg; i++) { + const iu = bu.span - s.udeg + i; + if (iu < 0 || iu >= nU) continue; + const f = bu.ders[0][i]; + if (f === 0) continue; + const row = s.poles[iu]; + for (let jv = 0; jv < nV; jv++) { + const P = row[jv]; + tmp[jv][0] += f * P[0]; tmp[jv][1] += f * P[1]; tmp[jv][2] += f * P[2]; + } + } + const rowOut = []; + for (let b = 0; b < vs.length; b++) { + const bv = bvs[b]; + let x = 0, y = 0, z = 0; + for (let j = 0; j <= s.vdeg; j++) { + const jv = bv.span - s.vdeg + j; + if (jv < 0 || jv >= nV) continue; + const f = bv.ders[0][j]; + x += f * tmp[jv][0]; y += f * tmp[jv][1]; z += f * tmp[jv][2]; + } + rowOut.push([x, y, z]); + } + out.push(rowOut); + } + return out; + } + + function isUDirClosed(grid, tol) { + // grid[u][v]; first row vs last row + const uhi = grid.length - 1; + for (let j = 0; j < grid[0].length; j++) { + if (dist3(grid[0][j], grid[uhi][j]) > tol) return false; + } + return true; + } + function isVDirClosed(grid, tol) { + const vhi = grid[0].length - 1; + for (let i = 0; i < grid.length; i++) { + if (dist3(grid[i][0], grid[i][vhi]) > tol) return false; + } + return true; + } + + // ========================== CurvesToSurface =============================== + + /** clamp_bspline (curves_to_surface): remove periodicity, trim, convert. */ + function clampBSpline(d) { + if (!d.periodic) return null; + const f = firstParam(d), l = lastParam(d); + let out = setNotPeriodicData(d); + out = segmentData(out, f, l); + return out; + } + + class CurvesToSurface { + constructor(curves, parameters, continuousIfClosed, tolerance) { + this.inputCurves = curves.map(cloneCurve); + this.parameters = parameters ? parameters.slice() : []; + this.continuousIfClosed = !!continuousIfClosed; + this.tolerance = tolerance === undefined ? 1e-14 : tolerance; + this.maxDegree = 3; + this.skinnedSurface = null; + matchDegree(this.inputCurves); + if (!this.parameters.length) this.calculateParameters(); + if (!this.compatibleSplines || !this.compatibleSplines.length) { + this.compatibleSplines = createCommonKnotsVectorCurve(this.inputCurves, this.tolerance); + } + } + + calculateParameters() { + this.compatibleSplines = createCommonKnotsVectorCurve(this.inputCurves, this.tolerance); + const first = this.compatibleSplines[0]; + const numPolesU = first.poles.length; + const nSplines = this.compatibleSplines.length; + // control point grid: rows = poles (u), cols = splines (v) + const grid = []; + for (let i = 0; i < numPolesU; i++) { + const row = []; + for (let jSpline = 0; jSpline < nSplines; jSpline++) { + row.push(this.compatibleSplines[jSpline].poles[i]); + } + grid.push(row); + } + // compute_params_bspline_surf: average v-params over rows + const paramsV = new Array(nSplines).fill(0); + for (let i = 0; i < numPolesU; i++) { + const rowParams = computeParamsBSplineCurve(grid[i], 0.5); + for (let j = 0; j < nSplines; j++) paramsV[j] += rowParams[j]; + } + for (let j = 0; j < nSplines; j++) paramsV[j] /= numPolesU; + this.parameters = paramsV; + } + + surface() { + if (!this.skinnedSurface) this.perform(); + return this.skinnedSurface; + } + + perform() { + if (this.inputCurves.length < 2) return; + if (this.parameters.length !== this.inputCurves.length) { + throw new Error('The amount of given parameters has to be equal to the amount of given B-splines!'); + } + const tolerance = curveListScale(this.inputCurves) * REL_TOL_CLOSED; + const makeClosed = this.continuousIfClosed && + curvesEqual(this.inputCurves[0], this.inputCurves[this.inputCurves.length - 1], tolerance); + const nCurves = this.inputCurves.length; + const first = this.compatibleSplines[0]; + const numControlPointsU = first.poles.length; + let degreeV = 0; + const degreeU = first.deg; + let knotsV = null, multsV = null; + const cpSurf = []; + let interpSpline = null; + for (let cpU = 0; cpU < numControlPointsU; cpU++) { + const interpPoints = []; + for (let cpV = 0; cpV < nCurves; cpV++) { + interpPoints.push(this.compatibleSplines[cpV].poles[cpU].slice()); + } + interpSpline = pointsToBSplineInterpolation( + interpPoints, this.parameters, this.maxDegree, makeClosed); + if (makeClosed) { + const clamped = clampBSpline(interpSpline); + if (clamped) interpSpline = clamped; + } + if (cpU === 0) { + degreeV = interpSpline.deg; + knotsV = interpSpline.knots.slice(); + multsV = interpSpline.mults.slice(); + } else if (degreeV !== interpSpline.deg) { + throw new Error('Inconsistent degree_v in skinning'); + } + cpSurf.push(interpSpline.poles.map((p) => p.slice())); + } + this.skinnedSurface = { + udeg: degreeU, vdeg: degreeV, + uknots: first.knots.slice(), umults: first.mults.slice(), + vknots: knotsV, vmults: multsV, + poles: cpSurf, + }; + } + } + + // ======================== GordonSurfaceBuilder ============================ + + function pointsToSurface(grid, uParams, vParams, makeUClosed, makeVClosed) { + // grid[u][v]: interpolate u-direction curves per column (v index), then + // skin them at vParams — BSplineAlgorithms::pointsToSurface + const uCurves = []; + for (let j = 0; j < grid[0].length; j++) { + const col = grid.map((row) => row[j]); + uCurves.push(pointsToBSplineInterpolation(col, uParams, 3, makeUClosed)); + } + const skinner = new CurvesToSurface(uCurves, vParams, makeVClosed); + return skinner.surface(); + } + + class GordonSurfaceBuilder { + constructor(profiles, guides, intersectParamsU, intersectParamsV, tolerance) { + this.profiles = profiles; + this.guides = guides; + this.intersectionParamsSplineU = intersectParamsU; + this.intersectionParamsSplineV = intersectParamsV; + this.tolerance = tolerance; + this.performed = false; + } + + perform() { + if (this.performed) return; + this.createGordonSurface(); + this.performed = true; + } + + assertRange(c, umin, umax, tol) { + if (Math.abs(firstParam(c) - umin) > tol || Math.abs(lastParam(c) - umax) > tol) { + throw new Error('Gordon: curve not in range [' + umin + ', ' + umax + '].'); + } + } + + checkCurveNetworkCompatibility() { + const paramsU = this.intersectionParamsSplineU; + const paramsV = this.intersectionParamsSplineV; + const tol = this.tolerance; + const splinesScale = 0.5 * (curveListScale(this.profiles) + curveListScale(this.guides)); + if (Math.abs(paramsU[0]) > splinesScale * tol || + Math.abs(paramsU[paramsU.length - 1] - 1.0) > splinesScale * tol) { + throw new Error('Gordon: B-splines in u-direction mustn\'t stick out, spline network must be closed!'); + } + if (Math.abs(paramsV[0]) > splinesScale * tol || + Math.abs(paramsV[paramsV.length - 1] - 1.0) > splinesScale * tol) { + throw new Error('Gordon: B-splines in v-direction mustn\'t stick out, spline network must be closed!'); + } + for (let uIdx = 0; uIdx < paramsU.length; uIdx++) { + const splineV = this.guides[uIdx]; + for (let vIdx = 0; vIdx < paramsV.length; vIdx++) { + const splineU = this.profiles[vIdx]; + const pProf = curvePoint(splineU, paramsU[uIdx]); + const pGuid = curvePoint(splineV, paramsV[vIdx]); + if (dist3(pProf, pGuid) > splinesScale * tol) { + throw new Error('Gordon: B-spline network is incompatible (e.g. wrong parametrization) ' + + 'or intersection parameters are in a wrong order!'); + } + } + } + } + + createGordonSurface() { + const profiles = this.profiles, guides = this.guides; + if (profiles.length < 2) throw new Error('There must be at least two profiles for the gordon surface.'); + if (guides.length < 2) throw new Error('There must be at least two guides for the gordon surface.'); + const umin = firstParam(profiles[0]), umax = lastParam(profiles[0]); + for (const p of profiles) this.assertRange(p, umin, umax, 1e-5); + const vmin = firstParam(guides[0]), vmax = lastParam(guides[0]); + for (const g of guides) this.assertRange(g, vmin, vmax, 1e-5); + + this.checkCurveNetworkCompatibility(); + + const paramsU = this.intersectionParamsSplineU; + const paramsV = this.intersectionParamsSplineV; + // intersection grid: rows = u params, cols = profiles + const grid = []; + for (let ui = 0; ui < paramsU.length; ui++) { + const row = []; + for (let si = 0; si < profiles.length; si++) { + row.push(curvePoint(profiles[si], paramsU[ui])); + } + grid.push(row); + } + + const curveUTol = REL_TOL_CLOSED * curveListScale(guides); + const curveVTol = REL_TOL_CLOSED * curveListScale(profiles); + const tpTol = REL_TOL_CLOSED * grid2Scale(grid); + + const makeUClosed = isUDirClosed(grid, tpTol) && + curvesEqual(guides[0], guides[guides.length - 1], curveUTol); + const makeVClosed = isVDirClosed(grid, tpTol) && + curvesEqual(profiles[0], profiles[profiles.length - 1], curveVTol); + + const surfProfilesSkinner = new CurvesToSurface(profiles, paramsV, makeVClosed); + let surfProfiles = surfProfilesSkinner.surface(); + + const surfGuidesSkinner = new CurvesToSurface(guides, paramsU, makeUClosed); + let surfGuides = surfGuidesSkinner.surface(); + surfGuides = surfExchangeUV(surfGuides); // flip_surface + + let tensorProdSurf = pointsToSurface(grid, paramsU, paramsV, makeUClosed, makeVClosed); + + const degreeU = Math.max(surfGuides.udeg, surfProfiles.udeg, tensorProdSurf.udeg); + const degreeV = Math.max(surfGuides.vdeg, surfProfiles.vdeg, tensorProdSurf.vdeg); + surfGuides = surfIncreaseDegree(surfGuides, degreeU, degreeV); + surfProfiles = surfIncreaseDegree(surfProfiles, degreeU, degreeV); + tensorProdSurf = surfIncreaseDegree(tensorProdSurf, degreeU, degreeV); + + const vec = createCommonKnotsVectorSurface([surfGuides, surfProfiles, tensorProdSurf], 1e-7); + this.surfaceGuides = vec[0]; + this.surfaceProfiles = vec[1]; + this.surfaceIntersections = vec[2]; + + const nU = surfNbUPoles(this.surfaceProfiles), nV = surfNbVPoles(this.surfaceProfiles); + if (surfNbUPoles(this.surfaceGuides) !== nU || surfNbUPoles(this.surfaceIntersections) !== nU || + surfNbVPoles(this.surfaceGuides) !== nV || surfNbVPoles(this.surfaceIntersections) !== nV) { + throw new Error('Gordon: internal surface pole-count mismatch after compatibility'); + } + const gordon = surfClone(this.surfaceProfiles); + for (let i = 0; i < nU; i++) { + for (let j = 0; j < nV; j++) { + const a = this.surfaceProfiles.poles[i][j]; + const b = this.surfaceGuides.poles[i][j]; + const c = this.surfaceIntersections.poles[i][j]; + gordon.poles[i][j] = [a[0] + b[0] - c[0], a[1] + b[1] - c[1], a[2] + b[2] - c[2]]; + } + } + this.surfaceGordon = gordon; + } + } + + // ===================== InterpolateCurveNetwork ============================ + + function findFirstNonZeroLengthIndex(curves) { + for (let i = 0; i < curves.length; i++) { + if (!isZeroLength(curves[i])) return i; + } + return -1; + } + + class CurveNetworkSorter { + constructor(profiles, guides, parmsIntersProfiles, parmsIntersGuides) { + this.profiles = profiles; + this.guides = guides; + this.mU = parmsIntersProfiles; // [profile][guide] + this.mV = parmsIntersGuides; + const n = profiles.length, m = guides.length; + if (this.mU.length !== n || this.mV.length !== n || + this.mU[0].length !== m || this.mV[0].length !== m) { + throw new Error('Gordon: invalid intersection matrix sizes'); + } + this.profIdx = []; + for (let i = 0; i < n; i++) this.profIdx.push(String(i)); + this.guidIdx = []; + for (let j = 0; j < m; j++) this.guidIdx.push(String(j)); + } + + nProfiles() { return this.profiles.length; } + nGuides() { return this.guides.length; } + + maxRowIndex(m, irow) { + let maxVal = -Infinity, jmax = -1; + for (let j = 0; j < m[0].length; j++) { + if (isZeroLength(this.guides[j])) continue; + if (jmax === -1 || m[irow][j] > maxVal) { maxVal = m[irow][j]; jmax = j; } + } + return jmax; + } + maxColIndex(m, jcol) { + let maxVal = -Infinity, imax = -1; + for (let i = 0; i < m.length; i++) { + if (isZeroLength(this.profiles[i])) continue; + if (imax === -1 || m[i][jcol] > maxVal) { maxVal = m[i][jcol]; imax = i; } + } + return imax; + } + minRowIndex(m, irow) { + let minVal = Infinity, jmin = -1; + for (let j = 0; j < m[0].length; j++) { + if (isZeroLength(this.guides[j])) continue; + if (jmin === -1 || m[irow][j] < minVal) { minVal = m[irow][j]; jmin = j; } + } + return jmin; + } + minColIndex(m, jcol) { + let minVal = Infinity, imin = -1; + for (let i = 0; i < m.length; i++) { + if (isZeroLength(this.profiles[i])) continue; + if (imin === -1 || m[i][jcol] < minVal) { minVal = m[i][jcol]; imin = i; } + } + return imin; + } + + swapProfiles(i1, i2) { + if (i1 === i2) return; + [this.profiles[i1], this.profiles[i2]] = [this.profiles[i2], this.profiles[i1]]; + [this.profIdx[i1], this.profIdx[i2]] = [this.profIdx[i2], this.profIdx[i1]]; + [this.mU[i1], this.mU[i2]] = [this.mU[i2], this.mU[i1]]; + [this.mV[i1], this.mV[i2]] = [this.mV[i2], this.mV[i1]]; + } + swapGuides(j1, j2) { + if (j1 === j2) return; + [this.guides[j1], this.guides[j2]] = [this.guides[j2], this.guides[j1]]; + [this.guidIdx[j1], this.guidIdx[j2]] = [this.guidIdx[j2], this.guidIdx[j1]]; + for (let i = 0; i < this.mU.length; i++) { + [this.mU[i][j1], this.mU[i][j2]] = [this.mU[i][j2], this.mU[i][j1]]; + [this.mV[i][j1], this.mV[i][j2]] = [this.mV[i][j2], this.mV[i][j1]]; + } + } + + getStartCurveIndices() { + for (let irow = 0; irow < this.nProfiles(); irow++) { + if (isZeroLength(this.profiles[irow])) continue; + const jmin = this.minRowIndex(this.mU, irow); + if (jmin === -1) continue; + const imin = this.minColIndex(this.mV, jmin); + if (imin === -1) continue; + if (imin === irow) return [imin, jmin, false]; + } + for (let irow = 0; irow < this.nProfiles(); irow++) { + if (isZeroLength(this.profiles[irow])) continue; + const jmin = this.minRowIndex(this.mU, irow); + if (jmin === -1) continue; + const imax = this.maxColIndex(this.mV, jmin); + if (imax === -1) continue; + if (imax === irow) return [imax, jmin, true]; + } + throw new Error('Cannot find starting curves of curve network.'); + } + + reverseProfile(i) { + const profile = this.profiles[i]; + const lastParm = lastParam(profile); + const firstParm = firstParam(profile); + for (let j = 0; j < this.nGuides(); j++) { + this.mU[i][j] = -this.mU[i][j] + firstParm + lastParm; + } + reverseCurveData(profile); + this.profIdx[i] = '-' + this.profIdx[i]; + } + reverseGuide(j) { + const guide = this.guides[j]; + const lastParm = lastParam(guide); + const firstParm = firstParam(guide); + for (let i = 0; i < this.nProfiles(); i++) { + this.mV[i][j] = -this.mV[i][j] + firstParm + lastParm; + } + reverseCurveData(guide); + this.guidIdx[j] = '-' + this.guidIdx[j]; + } + + perform() { + const [profStart, guideStart, guideMustBeReversed] = this.getStartCurveIndices(); + this.swapProfiles(0, profStart); + this.swapGuides(0, guideStart); + if (guideMustBeReversed) this.reverseGuide(0); + const nGuides = this.nGuides(), nProfiles = this.nProfiles(); + for (let n = nGuides; n > 1; n--) { + for (let j = 0; j < n - 1; j++) { + if (this.mU[0][j] > this.mU[0][j + 1]) this.swapGuides(j, j + 1); + } + } + const firstNonZeroGuide = findFirstNonZeroLengthIndex(this.guides); + if (firstNonZeroGuide === -1) throw new Error('No non-zero-length guide found to sort profiles.'); + for (let n = nProfiles; n > 1; n--) { + for (let i = 0; i < n - 1; i++) { + if (this.mV[i][firstNonZeroGuide] > this.mV[i + 1][firstNonZeroGuide]) { + this.swapProfiles(i, i + 1); + } + } + } + for (let iProf = 1; iProf < nProfiles; iProf++) { + if (this.mU[iProf][0] > this.mU[iProf][nGuides - 1]) this.reverseProfile(iProf); + } + for (let iGuid = 1; iGuid < nGuides; iGuid++) { + if (this.mV[0][iGuid] > this.mV[nProfiles - 1][iGuid]) this.reverseGuide(iGuid); + } + } + } + + class InterpolateCurveNetwork { + constructor(profiles, guides, spatialTolerance) { + if (profiles.length < 2) throw new Error('There must be at least two profiles for the curve network interpolation.'); + if (guides.length < 2) throw new Error('There must be at least two guides for the curve network interpolation.'); + const uniqueProfiles = []; + for (const p of profiles) { + if (!uniqueProfiles.some((u) => curvesEqual(p, u, PCONFUSION))) uniqueProfiles.push(p); + } + const uniqueGuides = []; + for (const g of guides) { + if (!uniqueGuides.some((u) => curvesEqual(g, u, PCONFUSION))) uniqueGuides.push(g); + } + if (uniqueProfiles.length < 2) throw new Error('There must be at least two unique profiles for the curve network interpolation.'); + if (uniqueGuides.length < 2) throw new Error('There must be at least two unique guides for the curve network interpolation.'); + this.profiles = uniqueProfiles; + this.guides = uniqueGuides; + this.spatialTol = spatialTolerance; + this.performed = false; + } + + computeIntersectionsMatrix() { + const nP = this.profiles.length, nG = this.guides.length; + const mU = [], mV = []; + for (let i = 0; i < nP; i++) { mU.push(new Array(nG).fill(0)); mV.push(new Array(nG).fill(0)); } + for (let i = 0; i < nP; i++) { + for (let j = 0; j < nG; j++) { + const res = intersectCurves(this.profiles[i], this.guides[j], this.spatialTol); + if (res.length === 0) { + throw new Error('U-directional B-spline ' + i + ' and V-directional B-spline ' + j + ' don\'t intersect!'); + } else if (res.length === 1) { + mU[i][j] = res[0][0]; mV[i][j] = res[0][1]; + } else if (res.length === 2) { + mU[i][j] = Math.min(res[0][0], res[1][0]); + mV[i][j] = Math.min(res[0][1], res[1][1]); + } else { + throw new Error('U-directional B-spline ' + i + ' and V-directional B-spline ' + j + + ' have more than two intersections!'); + } + } + } + return { mU, mV }; + } + + eliminateInaccuraciesNetworkIntersections(mU, mV) { + const nP = this.profiles.length, nG = this.guides.length; + const firstKnotU = this.profiles[0].knots[0]; + const lastKnotU = this.profiles[0].knots[this.profiles[0].knots.length - 1]; + const firstKnotV = this.guides[0].knots[0]; + const lastKnotV = this.guides[0].knots[this.guides[0].knots.length - 1]; + for (let i = 0; i < nP; i++) { + if (Math.abs(mU[i][0] - firstKnotU) < 0.001) { + mU[i][0] = Math.abs(firstKnotU) < 1e-10 ? 0 : firstKnotU; + } + } + for (let j = 0; j < nG; j++) { + if (Math.abs(mV[0][j] - firstKnotV) < 0.001) { + mV[0][j] = Math.abs(firstKnotV) < 1e-10 ? 0 : firstKnotV; + } + } + for (let i = 0; i < nP; i++) { + if (Math.abs(mU[i][nG - 1] - lastKnotU) < 0.001) mU[i][nG - 1] = lastKnotU; + } + for (let j = 0; j < nG; j++) { + if (Math.abs(mV[nP - 1][j] - lastKnotV) < 0.001) mV[nP - 1][j] = lastKnotV; + } + } + + clamp(val, lo, hi) { + if (lo > hi) throw new Error('Minimum may not be larger than maximum in clamp!'); + return Math.max(lo, Math.min(val, hi)); + } + + makeCurvesCompatible() { + for (const p of this.profiles) reparametrizeSimple(p, 0.0, 1.0); + for (const g of this.guides) reparametrizeSimple(g, 0.0, 1.0); + + const { mU, mV } = this.computeIntersectionsMatrix(); + + const sorter = new CurveNetworkSorter(this.profiles, this.guides, mU, mV); + sorter.perform(); + this.profiles = sorter.profiles; + this.guides = sorter.guides; + + for (let i = 1; i < this.profiles.length - 1; i++) { + if (isZeroLength(this.profiles[i])) { + throw new Error('Profile#' + i + ' is a point. Points are only permitted at the beginning and end.'); + } + } + for (let j = 1; j < this.guides.length - 1; j++) { + if (isZeroLength(this.guides[j])) { + throw new Error('Guides#' + j + ' is a point. Points are only permitted at the beginning and end.'); + } + } + + let sortedU = sorter.mU, sortedV = sorter.mV; + + const firstNZProfile = findFirstNonZeroLengthIndex(this.profiles); + const firstNZGuide = findFirstNonZeroLengthIndex(this.guides); + if (firstNZProfile === -1) throw new Error('No non-zero-length profile found for closed curve check.'); + if (firstNZGuide === -1) throw new Error('No non-zero-length guide found for closed curve check.'); + + const profNZ = this.profiles[firstNZProfile]; + const guideNZ = this.guides[firstNZGuide]; + const isClosedProfile = profNZ.periodic || + dist3(curvePoint(profNZ, firstParam(profNZ)), curvePoint(profNZ, lastParam(profNZ))) <= CONFUSION; + const isClosedGuides = guideNZ.periodic || + dist3(curvePoint(guideNZ, firstParam(guideNZ)), curvePoint(guideNZ, lastParam(guideNZ))) <= CONFUSION; + + if (isClosedProfile && isClosedGuides) { + throw new Error('Both profiles and guides cannot be closed simultaneously.'); + } + + let nP = this.profiles.length, nG = this.guides.length; + let finalU, finalV; + if (isClosedProfile) { + this.guides.push(cloneCurve(this.guides[0])); + nG += 1; + finalU = []; finalV = []; + for (let i = 0; i < nP; i++) { finalU.push(new Array(nG).fill(0)); finalV.push(new Array(nG).fill(0)); } + for (let i = 0; i < nP; i++) { + for (let j = 0; j < nG - 1; j++) { finalU[i][j] = sortedU[i][j]; finalV[i][j] = sortedV[i][j]; } + const valU = sortedU[i][0]; + finalU[i][nG - 1] = Math.abs(valU) < PAR_CHECK_TOL ? 1.0 : valU; + finalV[i][nG - 1] = sortedV[i][0]; + } + } else if (isClosedGuides) { + this.profiles.push(cloneCurve(this.profiles[0])); + nP += 1; + finalU = []; finalV = []; + for (let i = 0; i < nP; i++) { finalU.push(new Array(nG).fill(0)); finalV.push(new Array(nG).fill(0)); } + for (let i = 0; i < nP - 1; i++) { + for (let j = 0; j < nG; j++) { finalU[i][j] = sortedU[i][j]; finalV[i][j] = sortedV[i][j]; } + } + for (let j = 0; j < nG; j++) { + const valV = sortedV[0][j]; + finalU[nP - 1][j] = sortedU[0][j]; + finalV[nP - 1][j] = Math.abs(valV) < PAR_CHECK_TOL ? 1.0 : valV; + } + } else { + finalU = sortedU; finalV = sortedV; + } + + this.eliminateInaccuraciesNetworkIntersections(finalU, finalV); + + const newParametersProfiles = []; + for (let j = 0; j < nG; j++) { + let sumU = 0.0, cnt = 0; + for (let i = 0; i < nP; i++) { + if (isZeroLength(this.profiles[i])) continue; + sumU += finalU[i][j]; cnt += 1; + } + newParametersProfiles.push(cnt > 0 ? sumU / cnt : 0.0); + } + const newParametersGuides = []; + for (let i = 0; i < nP; i++) { + let sumV = 0.0, cnt = 0; + for (let j = 0; j < nG; j++) { + if (isZeroLength(this.guides[j])) continue; + sumV += finalV[i][j]; cnt += 1; + } + newParametersGuides.push(cnt > 0 ? sumV / cnt : 0.0); + } + + if (newParametersProfiles[0] > PAR_CHECK_TOL || newParametersGuides[0] > PAR_CHECK_TOL) { + throw new Error('At least one B-spline has no intersection at the beginning.'); + } + + let maxCpU = 0, maxCpV = 0; + for (const p of this.profiles) maxCpU = Math.max(maxCpU, p.poles.length); + for (const g of this.guides) maxCpV = Math.max(maxCpV, g.poles.length); + const minCp = 10, maxCp = 120; + const minU = Math.max(nG + 2, minCp); + const minV = Math.max(nP + 2, minCp); + const maxU = Math.max(minU, maxCp); + const maxV = Math.max(minV, maxCp); + const finalMaxCpU = this.clamp(maxCpU + 10, minU, maxU); + const finalMaxCpV = this.clamp(maxCpV + 10, minV, maxV); + + const skipReparametrize = (curve, oldPs, newPs) => { + if (curve.weights) return false; + if (curve.periodic) return false; + for (let i = 0; i < oldPs.length; i++) { + if (Math.abs(oldPs[i] - newPs[i]) >= PCONFUSION) return false; + } + return true; + }; + + for (let i = 0; i < nP; i++) { + if (isZeroLength(this.profiles[i])) continue; + const oldParameters = []; + for (let j = 0; j < nG; j++) oldParameters.push(finalU[i][j]); + if (skipReparametrize(this.profiles[i], oldParameters, newParametersProfiles)) continue; + if (Math.abs(oldParameters[0]) < PAR_CHECK_TOL) oldParameters[0] = 0.0; + if (Math.abs(newParametersProfiles[0]) < PAR_CHECK_TOL) newParametersProfiles[0] = 0.0; + if (Math.abs(oldParameters[oldParameters.length - 1] - 1.0) < PAR_CHECK_TOL) oldParameters[oldParameters.length - 1] = 1.0; + if (Math.abs(newParametersProfiles[newParametersProfiles.length - 1] - 1.0) < PAR_CHECK_TOL) newParametersProfiles[newParametersProfiles.length - 1] = 1.0; + const result = reparametrizeContinuouslyApprox( + this.profiles[i], oldParameters, newParametersProfiles, finalMaxCpU); + if (result.curve) this.profiles[i] = result.curve; + } + for (let j = 0; j < nG; j++) { + if (isZeroLength(this.guides[j])) continue; + const oldParameters = []; + for (let i = 0; i < nP; i++) oldParameters.push(finalV[i][j]); + if (skipReparametrize(this.guides[j], oldParameters, newParametersGuides)) continue; + if (Math.abs(oldParameters[0]) < PAR_CHECK_TOL) oldParameters[0] = 0.0; + if (Math.abs(newParametersGuides[0]) < PAR_CHECK_TOL) newParametersGuides[0] = 0.0; + if (Math.abs(oldParameters[oldParameters.length - 1] - 1.0) < PAR_CHECK_TOL) oldParameters[oldParameters.length - 1] = 1.0; + if (Math.abs(newParametersGuides[newParametersGuides.length - 1] - 1.0) < PAR_CHECK_TOL) newParametersGuides[newParametersGuides.length - 1] = 1.0; + const result = reparametrizeContinuouslyApprox( + this.guides[j], oldParameters, newParametersGuides, finalMaxCpV); + if (result.curve) this.guides[j] = result.curve; + } + + this.intersectionParamsU = newParametersProfiles; + this.intersectionParamsV = newParametersGuides; + } + + ensureC2(gordonSurf) { + const tol = this.spatialTol; + let s = gordonSurf; + const minUMult = Math.max(1, s.udeg - 2); + const minVMult = Math.max(1, s.vdeg - 2); + for (let i = 1; i < s.uknots.length - 1; i++) { + if (s.umults[i] > minUMult) { + // per-column removal, all-or-nothing (== Geom_BSplineSurface::RemoveUKnot) + const nV = surfNbVPoles(s); + const newCols = []; + let allOk = true; + for (let j = 0; j < nV; j++) { + const res = removeKnotData(uColumnCurve(s, j), i + 1, minUMult, tol); + if (!res.ok) { allOk = false; break; } + newCols.push(res.data); + } + if (allOk) { + const first = newCols[0]; + const poles = []; + for (let pi = 0; pi < first.poles.length; pi++) { + poles.push(newCols.map((c) => c.poles[pi])); + } + s = { + udeg: s.udeg, vdeg: s.vdeg, + uknots: first.knots, umults: first.mults, + vknots: s.vknots, vmults: s.vmults, poles, + }; + } + } + } + for (let i = 1; i < s.vknots.length - 1; i++) { + if (s.vmults[i] > minVMult) { + const nU = surfNbUPoles(s); + const newRows = []; + let allOk = true; + for (let r = 0; r < nU; r++) { + const res = removeKnotData(vRowCurve(s, r), i + 1, minVMult, tol); + if (!res.ok) { allOk = false; break; } + newRows.push(res.data); + } + if (allOk) { + const first = newRows[0]; + s = { + udeg: s.udeg, vdeg: s.vdeg, + uknots: s.uknots, umults: s.umults, + vknots: first.knots, vmults: first.mults, + poles: newRows.map((r) => r.poles), + }; + } + } + } + return s; + } + + perform() { + if (this.performed) return; + this.makeCurvesCompatible(); + const builder = new GordonSurfaceBuilder( + this.profiles, this.guides, + this.intersectionParamsU, this.intersectionParamsV, this.spatialTol); + builder.perform(); + this.surfaceGordon = this.ensureC2(builder.surfaceGordon); + this.surfaceProfilesSkin = builder.surfaceProfiles; + this.surfaceGuidesSkin = builder.surfaceGuides; + this.surfaceTensor = builder.surfaceIntersections; + this.performed = true; + } + + surface() { + this.perform(); + return this.surfaceGordon; + } + } + + // ======================= edge -> curve conversion ========================= + + /** Exact rational quadratic B-spline for a conic arc: the conic is an + * affine image of the unit circle, and NURBS are affinely invariant, so + * the classic <=90°-per-segment rational-quadratic circle construction + * transfers verbatim. center/xd/yd are the conic frame axes scaled by the + * radii; the arc covers parametric angles [a1, a2] (knots = angles). */ + function conicArcData(center, xd, yd, a1, a2) { + const nSeg = Math.max(1, Math.ceil((a2 - a1) / (Math.PI / 2) - 1e-9)); + const dA = (a2 - a1) / nSeg; + const w = Math.cos(dA / 2); + const pt = (t) => [ + center[0] + xd[0] * Math.cos(t) + yd[0] * Math.sin(t), + center[1] + xd[1] * Math.cos(t) + yd[1] * Math.sin(t), + center[2] + xd[2] * Math.cos(t) + yd[2] * Math.sin(t), + ]; + // unit-circle middle pole maps through the same affine map + const midPole = (t0, t1) => { + const tm = 0.5 * (t0 + t1); + const c = Math.cos(tm) / w, s = Math.sin(tm) / w; + return [ + center[0] + xd[0] * c + yd[0] * s, + center[1] + xd[1] * c + yd[1] * s, + center[2] + xd[2] * c + yd[2] * s, + ]; + }; + const poles = [pt(a1)]; + const weights = [1.0]; + const knots = [a1]; + const mults = [3]; + for (let i = 0; i < nSeg; i++) { + const t0 = a1 + i * dA, t1 = a1 + (i + 1) * dA; + poles.push(midPole(t0, t1)); weights.push(w); + poles.push(pt(t1)); weights.push(1.0); + knots.push(t1); + mults.push(i === nSeg - 1 ? 3 : 2); + } + return { deg: 2, periodic: false, knots, mults, poles, weights }; + } + + /** COMPROMISE(gordon-conic-approx): non-rational approximation of a curve + * (used for conics; GeomConvert_ApproxCurve is not in this wasm). Fits + * with the ported least-squares machinery, growing the control-point + * count until the fit error beats the tolerance. */ + function approximateNonRational(d, tol) { + const nSample = 201; + const f = firstParam(d), l = lastParam(d); + const points = []; + const params = []; + for (let i = 0; i < nSample; i++) { + const t = f + (l - f) * i / (nSample - 1); + params.push(t); + points.push(curvePoint(d, t)); + } + let best = null; + for (const ncp of [15, 25, 40, 60, 90, 120]) { + if (ncp + 4 > nSample) break; + const approx = new BSplineApproxInterp(points, ncp, 3, false); + approx.interpolatePoint(0, false); + approx.interpolatePoint(nSample - 1, false); + const res = approx.fitCurveOptimal(params); + best = res; + if (res.error < tol) break; + } + return best.curve; + } + + /** Convert a TopoDS edge into curve data — Face.make_gordon_surface's + * to_geom_curve + BSplineAlgorithms.to_bsplines/_convert_to_bspline. */ + function edgeToCurveData(edgeTopo) { + const adaptor = new oc.BRepAdaptor_Curve_2(edgeTopo); + const f = adaptor.FirstParameter(), l = adaptor.LastParameter(); + const CT = oc.GeomAbs_CurveType; + const type = adaptor.GetType(); + if (type === CT.GeomAbs_BSplineCurve) { + let d = occtToData(adaptor.BSpline().get()); + if (d.periodic) d = setNotPeriodicData(d); + if (Math.abs(firstParam(d) - f) > PCONFUSION || Math.abs(lastParam(d) - l) > PCONFUSION) { + d = segmentData(d, f, l); + } + return d; + } + if (type === CT.GeomAbs_BezierCurve) { + const bez = adaptor.Bezier().get(); + const poles = []; + const weights = []; + let rational = false; + for (let i = 1; i <= bez.NbPoles(); i++) { + const p = bez.Pole(i); + poles.push([p.X(), p.Y(), p.Z()]); + const w = bez.Weight(i); + weights.push(w); + if (Math.abs(w - 1.0) > 1e-15) rational = true; + } + let d = { + deg: bez.Degree(), periodic: false, + knots: [0, 1], mults: [bez.Degree() + 1, bez.Degree() + 1], + poles, weights: rational ? weights : null, + }; + if (Math.abs(f) > PCONFUSION || Math.abs(l - 1) > PCONFUSION) d = segmentData(d, f, l); + return d; + } + if (type === CT.GeomAbs_Line) { + const p0 = new oc.gp_Pnt_1(), p1 = new oc.gp_Pnt_1(); + adaptor.D0(f, p0); adaptor.D0(l, p1); + return { + deg: 1, periodic: false, knots: [f, l], mults: [2, 2], + poles: [[p0.X(), p0.Y(), p0.Z()], [p1.X(), p1.Y(), p1.Z()]], weights: null, + }; + } + if (type === CT.GeomAbs_Circle || type === CT.GeomAbs_Ellipse) { + let center, xd, yd; + if (type === CT.GeomAbs_Circle) { + const circ = adaptor.Circle(); + const pos = circ.Position(); // gp_Ax2 + const loc = pos.Location(), xdir = pos.XDirection(), ydir = pos.YDirection(); + const r = circ.Radius(); + center = [loc.X(), loc.Y(), loc.Z()]; + xd = [xdir.X() * r, xdir.Y() * r, xdir.Z() * r]; + yd = [ydir.X() * r, ydir.Y() * r, ydir.Z() * r]; + } else { + const el = adaptor.Ellipse(); + const pos = el.Position(); + const loc = pos.Location(), xdir = pos.XDirection(), ydir = pos.YDirection(); + const ra = el.MajorRadius(), rb = el.MinorRadius(); + center = [loc.X(), loc.Y(), loc.Z()]; + xd = [xdir.X() * ra, xdir.Y() * ra, xdir.Z() * ra]; + yd = [ydir.X() * rb, ydir.Y() * rb, ydir.Z() * rb]; + } + const exact = conicArcData(center, xd, yd, f, l); + // upstream approximates conics non-rationally (GeomConvert_ApproxCurve, + // tol = Precision::Approximation * size / 200) + let size = 0.0; + const start = curvePoint(exact, f); + for (let i = 1; i < 3; i++) { + const u = (1 - i / 4) * f + (i / 4) * l; + size = Math.max(size, dist3(start, curvePoint(exact, u))); + } + const tol = APPROXIMATION * size / 200; + return approximateNonRational(exact, tol); + } + // generic fallback: sample the edge and fit (same machinery) + const nS = 201; + const points = []; + const params = []; + const p = new oc.gp_Pnt_1(); + for (let i = 0; i < nS; i++) { + const t = f + (l - f) * i / (nS - 1); + adaptor.D0(t, p); + params.push(t); + points.push([p.X(), p.Y(), p.Z()]); + } + let scale = 0.0; + for (const q of points) scale = Math.max(scale, dist3(points[0], q)); + const approx = new BSplineApproxInterp(points, Math.min(60, nS - 4), 3, false); + approx.interpolatePoint(0, false); + approx.interpolatePoint(nS - 1, false); + return approx.fitCurveOptimal(params).curve; + } + + function pointToCurveData(p) { + // Face.make_gordon_surface's create_zero_length_bspline_curve + return { + deg: 1, periodic: false, knots: [0.0, 1.0], mults: [2, 2], + poles: [[p[0], p[1], p[2]], [p[0], p[1], p[2]]], weights: null, + }; + } + + // ======================= surface realization ============================== + // COMPROMISE(gordon-surface-realization) — see file header. + + function realizeSurfaceAsFace(surf) { + const u0 = surf.uknots[0], u1 = surf.uknots[surf.uknots.length - 1]; + const v0 = surf.vknots[0], v1 = surf.vknots[surf.vknots.length - 1]; + const nu = Math.min(181, Math.max(41, 2 * surfNbUPoles(surf) + 1)); + const nv = Math.min(181, Math.max(41, 2 * surfNbVPoles(surf) + 1)); + let uParams = linspaceWithBreaks(u0, u1, nu, surf.uknots.slice(1, -1)); + let vParams = linspaceWithBreaks(v0, v1, nv, surf.vknots.slice(1, -1)); + // Drop sample lines that are (nearly) COINCIDENT in 3D with the one before + // them. GeomAPI_PointsToBSplineSurface parametrizes by chord length, so + // near-coincident lines get near-equal parameters and the interpolant + // oscillates wildly between them. That happens on every surface with a + // degenerate boundary (a Gordon network whose first/last guide is a POINT, + // like bracelet's tip): all the sample rows crowd into the pole. + const rowScale = (() => { + let lo = [Infinity, Infinity, Infinity], hi = [-Infinity, -Infinity, -Infinity]; + const probe = surfEvalGrid(surf, [u0, 0.5 * (u0 + u1), u1], + [v0, 0.5 * (v0 + v1), v1]); + for (const row of probe) { + for (const p of row) { + for (let k = 0; k < 3; k++) { lo[k] = Math.min(lo[k], p[k]); hi[k] = Math.max(hi[k], p[k]); } + } + } + return Math.sqrt((hi[0] - lo[0]) ** 2 + (hi[1] - lo[1]) ** 2 + (hi[2] - lo[2]) ** 2); + })(); + const minStep = Math.max(1e-9, 1e-3 * rowScale); + const pruneParams = (params, lines) => { + if (params.length < 3) return params; + const lineDist = (a, b) => { + let mx = 0; + for (let k = 0; k < a.length; k++) mx = Math.max(mx, dist3(a[k], b[k])); + return mx; + }; + const keep = [0]; + for (let i = 1; i < params.length - 1; i++) { + if (lineDist(lines[keep[keep.length - 1]], lines[i]) >= minStep) keep.push(i); + } + const last = params.length - 1; + while (keep.length > 1 && lineDist(lines[keep[keep.length - 1]], lines[last]) < minStep) keep.pop(); + keep.push(last); + return keep.map((i) => params[i]); + }; + const probeV = [0, 0.25, 0.5, 0.75, 1].map((t) => v0 + t * (v1 - v0)); + const probeU = [0, 0.25, 0.5, 0.75, 1].map((t) => u0 + t * (u1 - u0)); + uParams = pruneParams(uParams, surfEvalGrid(surf, uParams, probeV)); + const vProbeRows = surfEvalGrid(surf, probeU, vParams); + vParams = pruneParams(vParams, + vParams.map((v, j) => vProbeRows.map((row) => row[j]))); + const grid = surfEvalGrid(surf, uParams, vParams); + const arr = new oc.TColgp_Array2OfPnt_2(1, uParams.length, 1, vParams.length); + for (let i = 0; i < uParams.length; i++) { + for (let j = 0; j < vParams.length; j++) { + const p = grid[i][j]; + arr.SetValue(i + 1, j + 1, new oc.gp_Pnt_3(p[0], p[1], p[2])); + } + } + // Fit the sampled grid with GeomAPI_PointsToBSplineSurface's C2 + // least-squares APPROXIMATION rather than its Interpolate: interpolating + // 200+ sample lines through a surface with a DEGENERATE boundary (a Gordon + // network whose first/last guide is a point, like bracelet's tip) gives a + // wildly oscillating pole row at the pole — the boundary stays inside a + // ~1e-2 mm ball but wiggles into a 0.5 mm long edge, so the face is no + // longer degenerate there and capping it with a planar face fails. The + // approximation reproduces the exact surface's boundaries, degenerate poles + // included, to well under a micron. + // + // Tolerances are tried tightest-first, and each candidate is CHECKED + // against the exact surface by comparing the arc length of all four + // boundary curves: too tight a tolerance makes the approximator + // over-segment and oscillate again (arc length inflates), too loose and it + // cuts corners (arc length shrinks). Interpolation is the last resort. + // The candidates are SCORED by surface area against the exact surface's + // area (the sample grid's triangulated area, which for this grid density is + // within ~1e-5 relative of the true one). Area is parametrization- + // independent and catches both failure modes: an over-segmented fit + // oscillates (area grows), a loose fit cuts corners (area shrinks). + let exactArea = 0; + { + const triArea = (a, b, c) => { + const ux = b[0] - a[0], uy = b[1] - a[1], uz = b[2] - a[2]; + const vx = c[0] - a[0], vy = c[1] - a[1], vz = c[2] - a[2]; + const cx = uy * vz - uz * vy, cy = uz * vx - ux * vz, cz = ux * vy - uy * vx; + return 0.5 * Math.sqrt(cx * cx + cy * cy + cz * cz); + }; + for (let i = 0; i + 1 < grid.length; i++) { + for (let j = 0; j + 1 < vParams.length; j++) { + exactArea += triArea(grid[i][j], grid[i + 1][j], grid[i + 1][j + 1]) + + triArea(grid[i][j], grid[i + 1][j + 1], grid[i][j + 1]); + } + } + } + const faceArea = (face) => { + const props = new oc.GProp_GProps_1(); + oc.BRepGProp.SurfaceProperties_1(face, props, false, false); + return props.Mass(); + }; + const makeFace = (fitter) => { + const hs = fitter.Surface().AsGeomSurface(); + return new oc.BRepBuilderAPI_MakeFace_8(hs, PCONFUSION).Face(); + }; + let best = null, bestErr = Infinity; + const consider = (fitter) => { + let face, err; + try { + face = makeFace(fitter); + err = Math.abs(faceArea(face) - exactArea) / Math.max(1e-12, exactArea); + } catch (e) { return false; } + if (err < bestErr) { bestErr = err; best = face; } + return bestErr <= 1e-4; // good enough, stop fitting + }; + // The ladder starts at 1e-7 relative: below that OCCT's approximator still + // reports IsDone but over-segments the fit and starts oscillating again + // (the area check does not catch it — the oscillation is tangential — + // but the resulting face no longer sews into a solid). + let settled = false; + for (const relTol of [1e-7, 1e-6, 1e-5, 1e-4]) { + const cand = new oc.GeomAPI_PointsToBSplineSurface_2( + arr, 3, 8, oc.GeomAbs_Shape.GeomAbs_C2, Math.max(1e-9, relTol * rowScale)); + if (!cand.IsDone()) continue; + if (consider(cand)) { settled = true; break; } + } + if (!settled) { + const interp = new oc.GeomAPI_PointsToBSplineSurface_1(); + interp.Interpolate_1(arr, false); + if (interp.IsDone()) consider(interp); + } + if (best === null) throw new Error('Gordon: final surface fit failed'); + return best; + } + + // ============================ public API ================================== + + /** profiles/guides: arrays of TopoDS edge topos or [x,y,z] points. + * Returns a TopoDS_Face. */ + function gordonSurfaceFace(profiles, guides, tolerance) { + const conv = (item) => + Array.isArray(item) ? pointToCurveData(item) : edgeToCurveData(item); + const p = profiles.map(conv); + const g = guides.map(conv); + const interp = new InterpolateCurveNetwork(p, g, tolerance === undefined ? 3e-4 : tolerance); + const surf = interp.surface(); + return realizeSurfaceAsFace(surf); + } + + return { + // main entry + gordonSurfaceFace, + // exposed for verification/tests + InterpolateCurveNetwork, + GordonSurfaceBuilder, + CurvesToSurface, + BSplineApproxInterp, + CurveNetworkSorter, + pointsToBSplineInterpolation, + reparametrizeContinuouslyApprox, + createCommonKnotsVectorCurve, + matchDegree, + interpolate1D, + intersectCurves, + edgeToCurveData, + pointToCurveData, + conicArcData, + realizeSurfaceAsFace, + curveEval, curvePoint, surfEval, surfEvalGrid, surfClone, flatKnots, basisMat, + dataToOCCT, occtToData, isZeroLength, curveScale, linspaceWithBreaks, + knotsFromCurveParameters, + }; +} + +export { makeEngine as createGordonEngine }; diff --git a/packages/cascade-core/src/worker/PyodideRuntime.js b/packages/cascade-core/src/worker/PyodideRuntime.js new file mode 100644 index 00000000..442b6279 --- /dev/null +++ b/packages/cascade-core/src/worker/PyodideRuntime.js @@ -0,0 +1,360 @@ +// PyodideRuntime.js - EXPERIMENTAL alternative Python runtime (CPython on +// wasm) for build123d-lite, selected with `?pyruntime=pyodide`. +// +// Brython stays the default (see PythonRuntime.js and +// test/b123d-validation/runtime-comparison.md for the measurements behind +// that call). This module exists so the choice is a measurement rather than +// an assumption: it runs the SAME Build123dLite.js source, unmodified, on +// real CPython 3.14 and must reproduce the harness classification exactly. +// +// It is only reachable when the Pyodide core distribution has been vendored +// (`node packages/cascade-core/scripts/fetch-pyodide.cjs`) and copied to +// dist/pyodide/ by the build; otherwise the bootstrap fails loudly and the +// user can fall back to Brython. +// +// The interesting part is the interop layer, which has to reproduce the +// boundary semantics Brython gives build123d-lite for free: +// +// * `from browser import self as w` — a `browser` module is registered whose +// `self` proxies the worker's JS globals. Brython auto-converts Python +// containers to JS ones on the way out and JS arrays to list-likes on the +// way in; Pyodide does neither, so the facade does it explicitly. +// * Object identity. Lite compares shapes with `is` (JS-side indexOf cannot +// see through the wrappers), which requires the same JS object to always +// surface as the same Python object. Pyodide mints a fresh JsProxy per +// conversion, so the bridge memoizes them by `js_id` for the duration of +// an evaluation. +// * `w.sceneShapes` is a LIVE array (show() clears it with .pop() and adds +// with .push()), so the list-like it converts to writes those two +// mutators through to the JS array. +// * `getPythonUserLine` (CacheOp's line tagging) and `_pythonCallerFrame` +// (the Builder same-stack-frame rule) are plain CPython frame walks — +// `sys._getframe()` sees the user's frames even when the call arrives +// from JS, because the JS call is synchronous from Python. +// * Stdlib: CPython brings math/copy/typing/functools/itertools/operator/ +// timeit/random/os for real, so only the POLICY shims are registered +// (scipy's Nelder-Mead/quickhull stand-ins, the pytest.approx subset, and +// the logging swallower). + +import { BUILD123D_LITE_PY, PY_SHIM_MODULES } from './Build123dLite.js'; + +/** The module name user scripts execute under (frame walks look for it). */ +const PY_USER_MODULE = 'main'; + +/** Shims that stay shimmed on CPython: these encode a POLICY (what lite + * refuses to fake / where it substitutes an algorithm), not a missing + * stdlib. Everything else in PY_SHIM_MODULES is a Brython gap filler and is + * replaced by the real CPython module. */ +const PYODIDE_SHIMS = ['logging', '_scipy_shim', 'scipy', 'scipy.optimize', + 'scipy.spatial', 'pytest']; + +let _runtimePromise = null; + +/** Lazily bootstrap Pyodide + build123d-lite. Same contract as + * ensurePythonRuntime(): resolves to an object with `run(code)`. */ +export function ensurePyodideRuntime() { + if (!_runtimePromise) { + _runtimePromise = _bootstrap().catch((e) => { + _runtimePromise = null; // allow a retry on the next evaluation + throw e; + }); + } + return _runtimePromise; +} + +/** Python source of the interop bridge (executed as the module `_cs_bridge`, + * which also registers `browser`). Kept free of backticks and ${ } for the + * same reason Build123dLite.js is. */ +const BRIDGE_PY = ` +import sys, types, builtins, traceback +import js +from pyodide.ffi import to_js, JsProxy, JsArray + +_USER_MODULE = 'main' +_object_from_entries = js.Object.fromEntries + +# js_id -> the FIRST JsProxy handed to Python for that JS object. Brython +# hands out one stable wrapper per JS object, and build123d-lite relies on it +# ('existing is topo' over w.sceneShapes). Cleared between evaluations. +_proxy_cache = {} +_fn_cache = {} + + +class JsList(list): + """A JS array as a Python list. Reads are a snapshot of wrapped elements + (so 'is' comparisons and isinstance(..., list) both behave); push/pop + write through, which is what show() needs from w.sceneShapes.""" + + __slots__ = ('_js',) + + def __init__(self, jsarr): + list.__init__(self, [_wrap(x) for x in jsarr]) + self._js = jsarr + + def push(self, value): + self._js.push(_unwrap(value)) + list.append(self, value) + + def pop(self, index=-1): + if index == -1 or index == len(self) - 1: + self._js.pop() + else: + self._js.splice(index, 1) + return list.pop(self, index) + + +def _wrap(value): + """JS -> Python at the worker boundary (mirrors Brython's jsobj2pyobj).""" + if isinstance(value, JsProxy): + if isinstance(value, JsArray): + return JsList(value) + key = value.js_id + got = _proxy_cache.get(key) + if got is None: + _proxy_cache[key] = value + return value + return got + return value + + +def _unwrap(value): + """Python -> JS (mirrors Brython's pyobj2jsobj): lists/tuples become real + JS arrays, dicts become plain objects, JsProxies unwrap to their JS + object, primitives pass through.""" + if value is None or isinstance(value, (bool, int, float, str, JsProxy)): + return value + if isinstance(value, JsList): + return value._js + return to_js(value, dict_converter=_object_from_entries) + + +class _JsFn: + """A JS worker function with Brython's conversion behaviour.""" + + __slots__ = ('_fn', '_name') + + def __init__(self, fn, name): + self._fn = fn + self._name = name + + def __call__(self, *args, **kwargs): + if kwargs: + return _wrap(self._fn(*[_unwrap(a) for a in args], + **dict((k, _unwrap(v)) for k, v in kwargs.items()))) + return _wrap(self._fn(*[_unwrap(a) for a in args])) + + def __repr__(self): + return '' + + +def _python_caller_frame(): + """The frame of the code that called the function asking for it — the + Builder same-stack-frame rule. (Brython walks $B.frame_obj.prev.)""" + frame = sys._getframe(1) + return frame.f_back if frame is not None else None + + +def get_python_user_line(): + """Innermost line number inside the user's module (CacheOp line tagging). + Called FROM JS while the user's Python frames are still on the stack.""" + frame = sys._getframe(1) + while frame is not None: + if frame.f_globals.get('__name__') == _USER_MODULE: + return frame.f_lineno + frame = frame.f_back + return 0 + + +_natives = { + '_pythonCallerFrame': _python_caller_frame, + 'getPythonUserLine': get_python_user_line, +} + + +class WorkerGlobals: + """'from browser import self as w' — the CAD worker's JS global scope.""" + + def __getattr__(self, name): + native = _natives.get(name) + if native is not None: + return native + fn = _fn_cache.get(name) + if fn is not None: + return fn + value = getattr(js, name) # AttributeError if the global is unset + if callable(value): + # The standard library is installed once at worker startup, so + # its function objects are stable and worth caching (the bridge + # is on the hot path of every CAD call). + fn = _JsFn(value, name) + _fn_cache[name] = fn + return fn + return _wrap(value) + + def __setattr__(self, name, value): + setattr(js, name, _unwrap(value)) + + +_browser = types.ModuleType('browser') +_browser.self = WorkerGlobals() +_browser.window = _browser.self +_browser.console = js.console +sys.modules['browser'] = _browser + + +def register_module(name, source): + module = types.ModuleType(name) + module.__name__ = name + module.__builtins__ = builtins + sys.modules[name] = module + try: + exec(compile(source, '<' + name + '>', 'exec'), module.__dict__) + except BaseException: + del sys.modules[name] + raise + if '.' in name: + parent, _, leaf = name.rpartition('.') + setattr(sys.modules[parent], leaf, module) + return module + + +def run_user(source): + """Execute user code as a fresh module 'main'. Returns None on success or + the formatted traceback (line numbers = the user's editor lines) — the + error crosses back as a VALUE so nothing is lost in exception + translation.""" + _proxy_cache.clear() + module = types.ModuleType(_USER_MODULE) + module.__name__ = _USER_MODULE + module.__builtins__ = builtins + # Brython gives the user module a __file__ and a dozen upstream doc + # scripts derive an asset directory from it + # (os.path.dirname(os.path.abspath(__file__))). Same string on both + # runtimes so the scripts take the same branch. + module.__file__ = 'cascade-worker.js#main' + sys.modules[_USER_MODULE] = module + try: + code = compile(source, '
', 'exec') + except BaseException as exc: + return ''.join(traceback.format_exception_only(type(exc), exc)) + try: + exec(code, module.__dict__) + except BaseException as exc: + # Drop this function's own frame from the traceback. + tb = exc.__traceback__.tb_next if exc.__traceback__ else None + return ''.join(traceback.format_exception(type(exc), exc, tb)) + return None + + +def reset_state(): + _proxy_cache.clear() + try: + import build123d + build123d._reset_state() + except Exception: + pass +`; + +async function _bootstrap() { + const t0 = performance.now(); + // Dual-path like brython.js: the build copies the vendored core + // distribution next to the worker bundle. + const indexURL = typeof ESBUILD !== 'undefined' + ? new URL('./pyodide/', self.location.href).href + : new URL('../../../../vendor/pyodide/', self.location.href).href; + + // A non-analyzable specifier keeps esbuild from trying to bundle Pyodide + // (it must stay an external, lazily fetched asset). + const moduleURL = indexURL + 'pyodide.mjs'; + let pyodideModule; + try { + pyodideModule = await import(/* @vite-ignore */ moduleURL); + } catch (e) { + // A plain checkout has no vendor/pyodide, so this is the expected way to + // arrive here: say so instead of leaking "failed to fetch module". + throw new Error('Pyodide is not available at ' + moduleURL + + ' — the experimental ?pyruntime=pyodide runtime needs the vendored core ' + + 'distribution: run `node packages/cascade-core/scripts/fetch-pyodide.cjs` ' + + 'and rebuild, or drop the flag to use Brython. (' + e.message + ')'); + } + const tImported = performance.now(); + + const stderrBuffer = []; + const pyodide = await pyodideModule.loadPyodide({ + indexURL, + // Python print() lands in the worker console exactly like Brython's. + stdout: (line) => { console.log(line); }, + // The worker's console.error override RETHROWS, so stderr is buffered + // and replayed by run() only when the evaluation survived. + stderr: (line) => { stderrBuffer.push(line); }, + }); + + const tInitialized = performance.now(); + self._pyodideRuntime = pyodide; // memoryStats() reads its wasm heap + + const bridge = pyodide.runPython(BRIDGE_PY + '\nglobals()'); + const registerModule = bridge.get('register_module'); + const runUser = bridge.get('run_user'); + const resetState = bridge.get('reset_state'); + const userLine = bridge.get('get_python_user_line'); + + for (const name of PYODIDE_SHIMS) { + if (PY_SHIM_MODULES[name]) { registerModule(name, PY_SHIM_MODULES[name]); } + } + registerModule('build123d', BUILD123D_LITE_PY); + + // Same split as the Brython path: fetching the interpreter, bringing it + // up, compiling build123d-lite. `initMs` covers loadPyodide, which does + // its own fetching of pyodide.asm.wasm + python_stdlib.zip — so on a cold + // cache it carries the download too. + const tDone = performance.now(); + self._pythonBootTiming = { + runtime: 'pyodide', + version: pyodide.version, + fetchMs: +(tImported - t0).toFixed(1), + initMs: +(tInitialized - tImported).toFixed(1), + libMs: +(tDone - tInitialized).toFixed(1), + totalMs: +(tDone - t0).toFixed(1), + }; + console.log('[pyruntime] pyodide boot ' + JSON.stringify(self._pythonBootTiming)); + + return { + /** Execute user Python source. Throws a JS Error whose message starts + * with the one-line Python summary followed by the full traceback. */ + run(code) { + for (const k in self.argCache) { delete self.argCache[k]; } + // Own the shared hooks: a worker that has ALSO booted Brython (mode + // switched mid-session) must not keep Brython's frame walker. + self.getPythonUserLine = () => userLine(); + self._pythonRuntimeKind = 'pyodide'; + self._b123dSceneDefined = false; + stderrBuffer.length = 0; + + resetState(); + const trace = runUser(code); + if (trace) { throw new Error(_formatPythonError(trace)); } + for (const line of stderrBuffer) { console.error(line); } + }, + /** Test/benchmark hook: the wasm heap the Python interpreter occupies. */ + heapBytes() { + try { return pyodide._module.HEAPU8.length; } catch (e) { return 0; } + }, + pyodide, + }; +} + +/** Same shape as the Brython formatter: summary first (so a truncated error + * surface still shows the interesting line), then the whole traceback. */ +function _formatPythonError(trace) { + const text = String(trace).trim(); + const lines = text.split('\n').filter((l) => l.trim() !== ''); + let summary = lines.length > 0 ? lines[lines.length - 1] : 'unknown error'; + // A raw OCCT Standard_Failure escapes as a JS number; decode it the way + // the Brython path does instead of showing a bare pointer. + const raw = summary.match(/JsException:\s*(\d+)\s*$/); + if (raw && self.describeOCCTException) { + summary = 'INTERNAL OPENCASCADE ERROR: ' + + self.describeOCCTException(parseInt(raw[1], 10)); + } + return 'Python ' + summary + (lines.length > 1 ? '\n' + text : ''); +} diff --git a/packages/cascade-core/src/worker/PythonRuntime.js b/packages/cascade-core/src/worker/PythonRuntime.js new file mode 100644 index 00000000..00eb8d68 --- /dev/null +++ b/packages/cascade-core/src/worker/PythonRuntime.js @@ -0,0 +1,248 @@ +// PythonRuntime.js - lazy Brython bootstrap for Python (build123d) mode +// +// Architecture (deliberate, per project owner): Python support runs on +// Brython (~1.38 MB JS, ~300 KB gzipped, lazy-loaded on the FIRST Python +// evaluation), NOT Pyodide. Brython compiles Python to JS in-process, so +// Python code calls the very same standard-library functions (and their +// sceneShapes bookkeeping) that JS mode uses — no separate CAD kernel, no +// extra WASM. +// +// Bootstrap steps (module workers lack importScripts): +// 1. fetch brython.js as text and indirect-eval it in the worker's global +// scope, then pin `self.$B = self.__BRYTHON__` (generated code refers to +// the global `$B`). +// 2. defensively stub document.dispatchEvent/addEventListener (Brython +// dispatches a "brython_done" event in non-worker environments). +// 3. execute the embedded build123d-lite source under the module name +// 'build123d' — Brython caches it in $B.imported, which makes +// `from build123d import *` resolve for user code. +// 4. run user code with __BRYTHON__.runPythonSource(src, 'main'); errors +// are re-thrown as JS Errors carrying the Python traceback, whose line +// numbers refer 1:1 to the user's editor lines (the library is a +// separate module, nothing is prepended to user code). + +import { BUILD123D_LITE_PY, PY_SHIM_MODULES } from './Build123dLite.js'; +import { ensurePyodideRuntime } from './PyodideRuntime.js'; + +/** The Brython module name user scripts execute under. */ +const PY_USER_MODULE = 'main'; + +let _runtimePromise = null; + +/** Lazily bootstrap the Python runtime + build123d-lite. Returns a Promise + * for a runtime object with a synchronous `run(code)` method. Safe to call + * on every evaluation — the bootstrap happens once (retried if it failed). + * + * `kind` selects the interpreter: 'brython' (default) or the experimental + * 'pyodide' (CPython on wasm, `?pyruntime=pyodide`; needs the vendored core + * distribution — see PyodideRuntime.js). Both run the same + * Build123dLite.js source. */ +export function ensurePythonRuntime(kind) { + if (kind === 'pyodide') { return ensurePyodideRuntime(); } + if (!_runtimePromise) { + _runtimePromise = _bootstrap().catch((e) => { + _runtimePromise = null; // allow a retry on the next evaluation + throw e; + }); + } + return _runtimePromise; +} + +async function _bootstrap() { + const t0 = performance.now(); + // Dual-path like the WASM locateFile: the build copies brython.js next to + // the worker bundle; unbuilt workers load it from node_modules. + const brythonURL = typeof ESBUILD !== 'undefined' + ? './brython.js' + : '../../node_modules/brython/brython.js'; + const response = await fetch(brythonURL); + if (!response.ok) { + throw new Error('Failed to fetch Brython (' + response.status + ') from ' + brythonURL); + } + const source = await response.text(); + const tFetched = performance.now(); + + // Indirect eval as an importScripts substitute. brython.js begins with + // "use strict", so its top-level `var __BRYTHON__` stays local to the + // eval'd script — export it onto the worker global from INSIDE the same + // script text (this is also where the generated code's `$B` comes from). + (0, eval)(source + + '\n;globalThis.__BRYTHON__ = __BRYTHON__; globalThis.$B = __BRYTHON__;'); + if (!self.__BRYTHON__) { + throw new Error('Brython did not initialize (__BRYTHON__ is undefined after eval)'); + } + + // Brython installs its own non-DOM `document` stub, but it lacks event + // methods; stub them so any completion-event dispatch is a no-op. + if (typeof self.document === 'undefined') { self.document = {}; } + if (!self.document.dispatchEvent) { self.document.dispatchEvent = function () {}; } + if (!self.document.addEventListener) { self.document.addEventListener = function () {}; } + + const B = self.__BRYTHON__; + const tInitialized = performance.now(); + + // Expose a resolver so CacheOp (StandardUtils.js) can tag shapes with the + // *Python* source line that produced them: walk Brython's frame chain to + // the innermost frame belonging to the user module. (The JS-mode + // getCallingLocation() parses eval stack frames, which is meaningless for + // Brython-generated code.) This keeps modelHistory line numbers and the + // viewport's pick → editor-line mapping working in Python mode. + // Frame of the code that CALLED the currently-executing Python function + // (build123d-lite's Builder.__enter__ uses it to replicate build123d's + // same-stack-frame rule for transferring a builder's result to its parent). + self._pythonCallerFrame = function () { + try { + const frameObj = B.frame_obj; + return frameObj && frameObj.prev ? frameObj.prev.frame : null; + } catch (e) { return null; } + }; + + const getBrythonUserLine = function () { + try { + let frameObj = B.frame_obj; + while (frameObj) { + const frame = frameObj.frame; + if (frame && frame[2] === PY_USER_MODULE) { + return frame.$lineno || 0; + } + frameObj = frameObj.prev; + } + } catch (e) { /* line mapping is best-effort */ } + return 0; + }; + self.getPythonUserLine = getBrythonUserLine; + + // Register stdlib shims first (brython.js cannot import even its own + // built-in `math` inside a module worker, and brython_stdlib.js is not + // shipped), then build123d-lite — executing a source under a module name + // caches it in $B.imported for subsequent imports. + for (const name of Object.keys(PY_SHIM_MODULES)) { + _runGuarded(B, PY_SHIM_MODULES[name], name); + if (name.indexOf('.') !== -1 && !B.imported[name]) { + // run_script sanitizes dots out of script ids ('scipy.optimize' is + // cached as 'scipy_optimize') — alias the module back under its + // dotted submodule name so `from scipy.optimize import minimize` + // resolves from the imported-module cache (the 'scipy' shim sets + // __path__ = [] to satisfy the package check). + const sanitized = name.replace(/\./g, '_'); + if (B.imported[sanitized]) { B.imported[name] = B.imported[sanitized]; } + } + } + _runGuarded(B, BUILD123D_LITE_PY, 'build123d'); + + // Boot budget, split the same way PyodideRuntime reports it (fetching the + // interpreter / bringing it up / compiling build123d-lite) so the two are + // directly comparable — see test/b123d-validation/runtime-comparison.md. + const tDone = performance.now(); + self._pythonBootTiming = { + runtime: 'brython', + fetchMs: +(tFetched - t0).toFixed(1), + initMs: +(tInitialized - tFetched).toFixed(1), + libMs: +(tDone - tInitialized).toFixed(1), + totalMs: +(tDone - t0).toFixed(1), + bytes: source.length, + }; + console.log('[pyruntime] brython boot ' + JSON.stringify(self._pythonBootTiming)); + + return { + /** Execute user Python source synchronously. Throws a JS Error whose + * message starts with the one-line Python summary followed by the full + * traceback (line numbers = the user's editor lines). */ + run(code) { + // The build123d module persists across evaluations — clear its + // builder-context stacks in case a previous run aborted inside a + // `with BuildPart():` block without unwinding. The op cache is also + // cleared: rare hash collisions between shapes from DIFFERENT + // evaluations have produced observably wrong booleans, and a stale + // cache buys little in Python mode (scripts are re-run whole). + for (const k in self.argCache) { delete self.argCache[k]; } + // Own the shared hooks (a worker that also booted Pyodide this session + // would otherwise leave ITS frame walker installed). + self.getPythonUserLine = getBrythonUserLine; + self._pythonRuntimeKind = 'brython'; + _runGuarded(B, 'import build123d\nbuild123d._reset_state()', '_b123d_reset'); + _runGuarded(B, code, PY_USER_MODULE); + } + }; +} + +/** Stringify a console argument without exploding on Brython's circular + * internal objects (the worker's console.log override JSON.stringifys). */ +function _safeString(arg) { + if (typeof arg === 'string') { return arg; } + try { return JSON.stringify(arg); } catch (e) { return String(arg); } +} + +/** Run a Python source under `moduleName` with the worker console guarded. + * + * Brython prints internal diagnostics — including raw (circular) exception + * objects — through console.log/console.error while an exception unwinds, + * and the worker overrides console.error to *rethrow*. So for the duration + * of the run: + * - console.log passes through with stringify-safe args (print() output + * still reaches the main-thread console), + * - console.error is buffered; on success the buffer is replayed through + * the real console.error (CAD validation errors like "Union produced + * near-zero volume" surface normally), on failure it is discarded in + * favor of the single formatted Python traceback that is thrown. */ +function _runGuarded(B, source, moduleName) { + const prevLog = console.log; + const prevError = console.error; + const stderrBuffer = []; + console.log = (...args) => { + const parts = args.map(_safeString); + // Python's print() flushes with a trailing newline; the console panel + // adds its own line breaks, so strip it for parity with JS-mode logs. + if (parts.length > 0) { parts[parts.length - 1] = parts[parts.length - 1].replace(/\n$/, ''); } + prevLog(...parts); + }; + console.error = (...args) => { stderrBuffer.push(args.map(_safeString).join(' ')); }; + + let caught = null; + try { + // Each evaluation starts with an undefined scene: the first show() call + // replaces the auto-added shapes (see Build123dLite show()). + self._b123dSceneDefined = false; + B.runPythonSource(source, moduleName); + } catch (exc) { + caught = exc; + } finally { + console.log = prevLog; + console.error = prevError; + } + + if (caught) { + // A raw wasm exception (a NUMBER — an OCCT Standard_Failure pointer) that + // escaped a direct oc.* call outside CacheOp cannot carry a Python + // traceback; decode it into OCCT's own message rather than printing the + // bare pointer value. + if (typeof caught === 'number' && self.describeOCCTException) { + throw new Error('INTERNAL OPENCASCADE ERROR: ' + self.describeOCCTException(caught)); + } + throw new Error(_formatPythonError(B, caught, moduleName)); + } + for (const line of stderrBuffer) { console.error(line); } +} + +/** Extract a useful message from a Brython exception: the one-line summary + * ("NameError: name 'x' is not defined") first — so truncated error + * surfaces still show the interesting part — then the full traceback. */ +function _formatPythonError(B, exc, moduleName) { + let trace = ''; + try { + if (B.error_trace) { trace = B.error_trace(exc) || ''; } + } catch (e) { /* fall through to args */ } + trace = String(trace).trim(); + if (!trace) { + try { + trace = String(exc && exc.args && exc.args.length ? exc.args[0] : exc); + } catch (e) { + trace = String(exc); + } + } + const lines = trace.split('\n').filter((l) => l.trim() !== ''); + const summary = lines.length > 0 ? lines[lines.length - 1] : 'unknown error'; + const inLibrary = moduleName !== PY_USER_MODULE + ? ' (while loading the ' + moduleName + ' module)' : ''; + return 'Python ' + summary + inLibrary + (lines.length > 1 ? '\n' + trace : ''); +} diff --git a/packages/cascade-core/src/worker/ShapeToMesh.js b/packages/cascade-core/src/worker/ShapeToMesh.js index 28835387..48f2bdeb 100644 --- a/packages/cascade-core/src/worker/ShapeToMesh.js +++ b/packages/cascade-core/src/worker/ShapeToMesh.js @@ -65,8 +65,15 @@ class CascadeStudioMesher { return edgeHashes; } - shapeToMesh(shape, maxDeviation, fullShapeEdgeHashes, fullShapeFaceHashes) { + /** Triangulate `shape` into face/edge records for rendering. + * `faceHashToShapeIndex`/`edgeHashToShapeIndex` (optional) map subshape + * hashes to their owning top-level sceneShape index; when provided, each + * face/edge record carries a `shape_index` for pick → shape resolution. */ + shapeToMesh(shape, maxDeviation, fullShapeEdgeHashes, fullShapeFaceHashes, + faceHashToShapeIndex, edgeHashToShapeIndex) { let facelist = [], edgeList = []; + let shapeIndexOfFace = (hash) => (faceHashToShapeIndex && hash in faceHashToShapeIndex) ? faceHashToShapeIndex[hash] : -1; + let shapeIndexOfEdge = (hash) => (edgeHashToShapeIndex && hash in edgeHashToShapeIndex) ? edgeHashToShapeIndex[hash] : -1; try { let oc = self.oc; // Set up the Incremental Mesh builder, with a precision @@ -82,13 +89,15 @@ class CascadeStudioMesher { let myT = oc.BRep_Tool.Triangulation(myFace, aLocation, 0 /* Poly_MeshPurpose_NONE */); if (myT.IsNull()) { console.error("Encountered Null Face!"); for (let k in self.argCache) delete self.argCache[k]; return; } + let faceHash = self.oc.OCJS.HashCode(myFace, 100000000); let this_face = { vertex_coord: [], uv_coord: [], normal_coord: [], tri_indexes: [], number_of_triangles: 0, - face_index: fullShapeFaceHashes[self.oc.OCJS.HashCode(myFace, 100000000)] + face_index: fullShapeFaceHashes[faceHash], + shape_index: shapeIndexOfFace(faceHash) }; let nbNodes = myT.get().NbNodes(); @@ -184,7 +193,8 @@ class CascadeStudioMesher { if (fullShapeEdgeHashes2.hasOwnProperty(edgeHash)) { let this_edge = { vertex_coord: [], - edge_index: -1 + edge_index: -1, + shape_index: shapeIndexOfEdge(edgeHash) }; try { @@ -254,7 +264,8 @@ class CascadeStudioMesher { if (!fullShapeEdgeHashes2.hasOwnProperty(edgeHash)) { let this_edge = { vertex_coord: [], - edge_index: -1 + edge_index: -1, + shape_index: shapeIndexOfEdge(edgeHash) }; // BRepAdaptor_Curve already applies the edge's location transform, @@ -278,6 +289,13 @@ class CascadeStudioMesher { } catch (err) { setTimeout(() => { + // A raw wasm exception is a NUMBER: assigning .message to it throws a + // TypeError in strict mode, which used to hide the real fault. Decode + // the pointer into OCCT's own message instead. + if (typeof err !== 'object' || err === null) { + throw new Error("INTERNAL OPENCASCADE ERROR DURING GENERATE: " + + self.describeOCCTException(err)); + } err.message = "INTERNAL OPENCASCADE ERROR DURING GENERATE: " + err.message; throw err; }, 0); diff --git a/packages/cascade-core/src/worker/StandardLibrary.js b/packages/cascade-core/src/worker/StandardLibrary.js index 4a4442c8..627074b9 100644 --- a/packages/cascade-core/src/worker/StandardLibrary.js +++ b/packages/cascade-core/src/worker/StandardLibrary.js @@ -1,3 +1,5 @@ +import { createGordonEngine } from './GordonSurface.js'; + // Cascade Studio Standard Library // Adding new standard library features and functions: // 1. Research the OpenCascade API: https://www.opencascade.com/doc/occt-7.4.0/refman/html/annotated.html @@ -12,6 +14,7 @@ // - From there, you can graft those into CascadeStudio/node_modules/opencascade.js/dist (following its existing conventions) import { CascadeStudioUtils } from './StandardUtils.js'; +import quickhull3d from 'quickhull3d'; // --- CAD API Functions --- // These are regular function declarations (NOT class methods) to preserve @@ -22,7 +25,12 @@ function Box(x, y, z, centered) { let curBox = self.CacheOp(arguments, "Box", () => { let box = new self.oc.BRepPrimAPI_MakeBox_2(x, y, z).Shape(); if (centered) { - return Translate([-x / 2, -y / 2, -z / 2], box); + let centeredBox = Translate([-x / 2, -y / 2, -z / 2], box); + // Translate() scene-registers its result, and Box pushes curBox below — + // deregister the nested result so a cache miss doesn't double-add it + // (same pattern as Text3D's internal Rotate/Extrude). + self.sceneShapes = self.Remove(self.sceneShapes, centeredBox); + return centeredBox; } else { return box; } @@ -40,6 +48,19 @@ function Sphere(radius) { return curSphere; } +/** Full or PARTIAL sphere (BRepPrimAPI_MakeSphere with two latitude angles + * and a longitude sweep, in DEGREES — build123d's Solid.make_sphere). */ +function PartialSphere(radius, angle1, angle2, angle3) { + let sphere = self.CacheOp(arguments, "PartialSphere", () => { + let ax2 = new self.oc.gp_Ax2_4(new self.oc.gp_Pnt_3(0, 0, 0), self.oc.gp.DZ()); + const rad = Math.PI / 180; + return new self.oc.BRepPrimAPI_MakeSphere_12( + ax2, radius, angle1 * rad, angle2 * rad, angle3 * rad).Shape(); + }); + self.sceneShapes.push(sphere); + return sphere; +} + function Cylinder(radius, height, centered) { let curCylinder = self.CacheOp(arguments, "Cylinder", () => { let cylinderPlane = new self.oc.gp_Ax2_4(new self.oc.gp_Pnt_3(0, 0, centered ? -height / 2 : 0), new self.oc.gp_Dir_5(0, 0, 1)); @@ -116,16 +137,102 @@ function BSpline(inPoints, closed) { return curSpline; } -function Text3D(text, size, height, fontName) { - if (!size ) { size = 36; } - if (!height && height !== 0.0) { height = 0.15; } - if (!fontName) { fontName = "Roboto"; } +/** Kerning between two glyphs in font units, from the worker's own kern + * table parse (opentype.js misses multi-subtable kern tables). */ +function _kernValue(fontName, leftGlyph, rightGlyph) { + let pairs = self.fontKernPairs && self.fontKernPairs[fontName]; + if (!pairs) { return 0; } + return pairs.get(leftGlyph.index + ',' + rightGlyph.index) || 0; +} - let textArgs = JSON.stringify(arguments); - let curText = self.CacheOp(arguments, "Text3D", () => { - if (self.loadedFonts[fontName] === undefined) { for (let k in self.argCache) delete self.argCache[k]; console.log("Font not loaded or found yet! Try again..."); return; } +/** Convert an opentype.js glyph path (y-down canvas coords, baseline at 0) + * into a face-with-holes. Shared by Text3D and Text2D. Lays glyphs out + * itself (advance + kern-table pairs, like FreeType) instead of relying on + * opentype's getPath kerning. Returns the face, or null when the font is + * not loaded yet. */ +function _opentypeTextFace(text, size, fontName, perGlyphCompound) { + if (self.loadedFonts[fontName] === undefined) { for (let k in self.argCache) delete self.argCache[k]; console.log("Font not loaded or found yet! Try again..."); return null; } + let font = self.loadedFonts[fontName]; + let scale = size / font.unitsPerEm; + let glyphCommandRuns = []; + let penX = 0; + let prevGlyph = null; + for (const ch of text) { + let glyph = font.charToGlyph(ch); + if (prevGlyph) { penX += _kernValue(fontName, prevGlyph, glyph) * scale; } + glyphCommandRuns.push(glyph.getPath(penX, 0, size).commands); + penX += glyph.advanceWidth * scale; + prevGlyph = glyph; + } + if (!perGlyphCompound) { + return _textCommandsToFace([].concat.apply([], glyphCommandRuns)); + } + // Per-glyph faces collected in a compound — the topology build123d's + // Text produces (its faces() are the individual glyphs, with DISJOINT + // outer contours as separate faces: the dot of an 'i'/'j' is its own + // face, while nested contours like the counter of an 'o' stay holes). + let glyphFaces = []; + for (let g = 0; g < glyphCommandRuns.length; g++) { + glyphFaces = glyphFaces.concat(_glyphContourFaces(glyphCommandRuns[g])); + } + if (glyphFaces.length === 0) { return null; } + if (glyphFaces.length === 1) { return glyphFaces[0]; } + let builder = new self.oc.BRep_Builder(); + let compound = new self.oc.TopoDS_Compound(); + builder.MakeCompound(compound); + for (let g = 0; g < glyphFaces.length; g++) { builder.Add(compound, glyphFaces[g]); } + compound.hash = self.oc.OCJS.HashCode(compound, 100000000); + return compound; +} + +/** Split one glyph's path commands into faces by contour winding: TrueType + * outlines wind outer contours one way and holes the other, so contours + * matching the first contour's winding start a NEW face and + * opposite-winding contours become holes of the most recent outer. This + * reproduces Font_BRepTextBuilder's topology (i/j dots are separate + * faces; o/a/d counters are holes). */ +function _glyphContourFaces(commands) { + let contours = []; + let cur = null; + for (let i = 0; i < commands.length; i++) { + if (commands[i].type === "M") { cur = []; contours.push(cur); } + if (cur) { cur.push(commands[i]); } + } + let signedArea = (cmds) => { + let pts = []; + for (let i = 0; i < cmds.length; i++) { + if (cmds[i].x !== undefined) { pts.push([cmds[i].x, cmds[i].y]); } + } + let a = 0; + for (let i = 0; i < pts.length; i++) { + let p = pts[i], q = pts[(i + 1) % pts.length]; + a += p[0] * q[1] - q[0] * p[1]; + } + return a / 2; + }; + let outerSign = null; + let groups = []; + for (let i = 0; i < contours.length; i++) { + let s = Math.sign(signedArea(contours[i])) || 1; + if (outerSign === null) { outerSign = s; } + if (s === outerSign || groups.length === 0) { + groups.push(contours[i].slice()); + } else { + // hole: append to the most recent outer's command run + let last = groups[groups.length - 1]; + for (let k = 0; k < contours[i].length; k++) { last.push(contours[i][k]); } + } + } + let faces = []; + for (let i = 0; i < groups.length; i++) { + let f = _textCommandsToFace(groups[i]); + if (f) { faces.push(f); } + } + return faces; +} + +function _textCommandsToFace(commands) { let textFaces = []; - let commands = self.loadedFonts[fontName].getPath(text, 0, 0, size).commands; for (let idx = 0; idx < commands.length; idx++) { if (commands[idx].type === "M") { var firstPoint = new self.oc.gp_Pnt_3(commands[idx].x, commands[idx].y, 0); @@ -181,12 +288,23 @@ function Text3D(text, size, height, fontName) { lastPoint = nextPoint; } } + return textFaces.length > 0 ? textFaces[textFaces.length - 1] : null; +} +function Text3D(text, size, height, fontName) { + if (!size ) { size = 36; } + if (!height && height !== 0.0) { height = 0.15; } + if (!fontName) { fontName = "Roboto"; } + + let textArgs = JSON.stringify(arguments); + let curText = self.CacheOp(arguments, "Text3D", () => { + let textFace = _opentypeTextFace(text, size, fontName); + if (!textFace) { return; } if (height === 0) { - return textFaces[textFaces.length - 1]; + return textFace; } else { - textFaces[textFaces.length - 1].hash = self.stringToHash(textArgs); - let textSolid = Rotate([1, 0, 0], -90, Extrude(textFaces[textFaces.length - 1], [0, 0, height * size])); + textFace.hash = self.stringToHash(textArgs); + let textSolid = Rotate([1, 0, 0], -90, Extrude(textFace, [0, 0, height * size])); self.sceneShapes = self.Remove(self.sceneShapes, textSolid); return textSolid; } @@ -202,7 +320,10 @@ function ForEachSolid(shape, callback) { let solid_index = 0; let anExplorer = new self.oc.TopExp_Explorer_2(shape, self.oc.TopAbs_ShapeEnum.TopAbs_SOLID, self.oc.TopAbs_ShapeEnum.TopAbs_SHAPE); for (anExplorer.Init(shape, self.oc.TopAbs_ShapeEnum.TopAbs_SOLID, self.oc.TopAbs_ShapeEnum.TopAbs_SHAPE); anExplorer.More(); anExplorer.Next()) { - callback(solid_index++, self.oc.TopoDS_Cast.Solid_1(anExplorer.Current())); + let solid = self.oc.TopoDS_Cast.Solid_1(anExplorer.Current()); + // sub-shapes need a stable hash for CacheOp (see EdgeSelector) + if (solid.hash === undefined) { solid.hash = self.oc.OCJS.HashCode(solid, 100000000); } + callback(solid_index++, solid); } } function GetNumSolidsInCompound(shape) { @@ -254,11 +375,16 @@ function ForEachWire(shape, callback) { callback(wire_index++, self.oc.TopoDS_Cast.Wire_1(anExplorer.Current())); } } -function MakeFace(wire, keepWire) { +/** A face bounded by a wire. `onlyPlanar` forces BRepBuilderAPI's OnlyPlane + * mode, which build123d's Face(wire) always uses — without it the builder + * recovers whatever surface the wire's edges carry pcurves for, so the + * boundary of a freeform face rebuilds that same freeform face instead of + * capping it flat. */ +function MakeFace(wire, keepWire, onlyPlanar) { if (!wire || wire.IsNull()) { console.error("MakeFace: input wire is null!"); return wire; } let face = self.CacheOp(arguments, "MakeFace", () => { let w = wire.ShapeType().value === 5 ? wire : self.oc.TopoDS_Cast.Wire_1(wire); - return new self.oc.BRepBuilderAPI_MakeFace_15(w, false).Face(); + return new self.oc.BRepBuilderAPI_MakeFace_15(w, !!onlyPlanar).Face(); }); if (!keepWire) { self.sceneShapes = self.Remove(self.sceneShapes, wire); } @@ -399,13 +525,17 @@ function Rotate(axis, degrees, shapes, keepOriginal) { let transformation = new self.oc.gp_Trsf_1(); transformation.SetRotation_1( new self.oc.gp_Ax1_2(new self.oc.gp_Pnt_3(0, 0, 0), new self.oc.gp_Dir_3( - new self.oc.gp_Vec_4(axis[0], axis[1], axis[2]))), degrees * 0.0174533); - let rotation = new self.oc.TopLoc_Location_4(transformation); + new self.oc.gp_Vec_4(axis[0], axis[1], axis[2]))), degrees * (Math.PI / 180)); + // Bake the rotation into the geometry (deep copy) instead of hanging a + // TopLoc_Location on the shape: boolean ops in this WASM build silently + // fail to fuse/cut shapes that carry rotation Locations (they come out + // as unfused compounds), which broke e.g. subtracting a rotated copy. if (!self.isArrayLike(shapes)) { - newRot = shapes.Moved(rotation, false); + newRot = new self.oc.BRepBuilderAPI_Transform_2(shapes, transformation, true, false).Shape(); } else if (shapes.length >= 1) { + newRot = []; for (let shapeIndex = 0; shapeIndex < shapes.length; shapeIndex++) { - shapes[shapeIndex].Move(rotation, false); + newRot.push(new self.oc.BRepBuilderAPI_Transform_2(shapes[shapeIndex], transformation, true, false).Shape()); } } return newRot; @@ -490,6 +620,18 @@ function Union(objectsToJoin, keepObjects, fuzzValue, keepEdges) { } } + // Recover from the known 8.0.1 fuse operand-drop fault (see + // _rebuildFuseFromGF above): a valid fuse can never be smaller than + // its largest input. + let maxInput = Math.max(...objectsToJoin.map(o => _quickVolume(o))); + if (maxInput > 1e-6 && _quickVolume(combined) < maxInput * 0.999 - 1e-9) { + let rebuilt = _rebuildFuseFromGF(objectsToJoin); + if (rebuilt && _quickVolume(rebuilt) >= maxInput * 0.999 - 1e-9) { + console.log("Union: BRepAlgoAPI_Fuse dropped an operand (known OCCT 8.0.1 wasm fault); rebuilt the union from the General-Fuse partition."); + combined = rebuilt; + } + } + if (!keepEdges) { let fusor = new self.oc.ShapeUpgrade_UnifySameDomain_2(combined, true, true, false); fusor.Build(); combined = fusor.Shape(); @@ -509,6 +651,29 @@ function Union(objectsToJoin, keepObjects, fuzzValue, keepEdges) { return curUnion; } +// KNOWN OCCT 8.0.1 wasm kernel fault: BRepAlgoAPI_Fuse's result-ASSEMBLY +// phase can silently DROP an operand when coplanar faces meet along BSpline +// edges (e.g. font glyphs extruded off a planar face). The defaults audit +// (test/b123d-validation/report.md) showed upstream build123d/OCP defaults +// (no fuzzy value, no NonDestructive) reproduce the drop identically — but +// the General-Fuse SPLIT phase is correct on the same inputs. So fall back +// to the exact GF partition: a compound of disjoint-interior solids whose +// union IS the fuse result (volumes/bboxes exact). COMPROMISE(kernel-guard): +// the partition keeps the internal contact faces (the operands are not +// merged into one solid), so face/edge selectors see the contact topology. +function _rebuildFuseFromGF(shapes) { + try { + let op = new self.oc.BOPAlgo_Builder_1(); + for (let i = 0; i < shapes.length; i++) { op.AddArgument(shapes[i]); } + op.Perform(new self.oc.Message_ProgressRange_1()); + if (op.HasErrors()) { return null; } + let gf = op.Shape(); + let sawSolid = false; + ForEachSolid(gf, () => { sawSolid = true; }); + return sawSolid ? gf : null; + } catch (e) { return null; } +} + function Difference(mainBody, objectsToSubtract, keepObjects, fuzzValue, keepEdges) { if (!fuzzValue) { fuzzValue = 1e-7; } let mainVol = _quickVolume(mainBody); @@ -603,6 +768,20 @@ function Extrude(face, direction, keepFace) { return curExtrusion; } +/** ShapeUpgrade_UnifySameDomain — build123d's Shape.clean(). `concat` also + * concatenates tangent-continuous B-spline/Bezier edges into one curve. + * The result is re-typed as a face/wire when it is one, so face/wire APIs + * keep working on it. */ +function UnifyWire(shape, concat) { + let fusor = new self.oc.ShapeUpgrade_UnifySameDomain_2(shape, true, true, !!concat); + fusor.Build(); + let out = fusor.Shape(); + if (out.ShapeType().value === 4) { out = self.oc.TopoDS_Cast.Face_1(out); } + else if (out.ShapeType().value === 5) { out = self.oc.TopoDS_Cast.Wire_1(out); } + out.hash = self.oc.OCJS.HashCode(out, 100000000); + return out; +} + function RemoveInternalEdges(shape, keepShape) { let cleanShape = self.CacheOp(arguments, "RemoveInternalEdges", () => { let fusor = new self.oc.ShapeUpgrade_UnifySameDomain_2(shape, true, true, false); @@ -615,10 +794,13 @@ function RemoveInternalEdges(shape, keepShape) { return cleanShape; } -function Offset(shape, offsetDistance, tolerance, keepShape) { +function Offset(shape, offsetDistance, tolerance, keepShape, joinType) { if (!shape || shape.IsNull()) { console.error("Offset: input shape is null!"); return shape; } if (!tolerance) { tolerance = 0.1; } if (offsetDistance === 0.0) { return shape; } + let join = joinType === 'intersection' + ? self.oc.GeomAbs_JoinType.GeomAbs_Intersection + : self.oc.GeomAbs_JoinType.GeomAbs_Arc; let curOffset = self.CacheOp(arguments, "Offset", () => { let offset = null; let shapeType = shape.ShapeType().value; @@ -630,8 +812,7 @@ function Offset(shape, offsetDistance, tolerance, keepShape) { } else if (shapeType === 4) { // Face: 2D boundary offset using the face's own surface as reference plane let face = self.oc.TopoDS_Cast.Face_1(shape); - offset = new self.oc.BRepOffsetAPI_MakeOffset_2(face, - self.oc.GeomAbs_JoinType.GeomAbs_Arc, false); + offset = new self.oc.BRepOffsetAPI_MakeOffset_2(face, join, false); offset.Perform(offsetDistance); // Result is a wire — extract and rebuild as a face let resultShape = offset.Shape(); @@ -646,7 +827,7 @@ function Offset(shape, offsetDistance, tolerance, keepShape) { } else { // Solid/Shell: 3D shell offset offset = new self.oc.BRepOffsetAPI_MakeOffsetShape(); - offset.PerformByJoin(shape, offsetDistance, tolerance, self.oc.BRepOffset_Mode.BRepOffset_Skin, false, false, self.oc.GeomAbs_JoinType.GeomAbs_Arc, false, new self.oc.Message_ProgressRange_1()); + offset.PerformByJoin(shape, offsetDistance, tolerance, self.oc.BRepOffset_Mode.BRepOffset_Skin, false, false, join, false, new self.oc.Message_ProgressRange_1()); } let offsetShape = offset.Shape(); @@ -682,6 +863,164 @@ function OffsetWire(wire, offsetDistance, keepWire) { return result; } +/** BRepOffsetAPI_MakeOffset on a planar wire with an explicit join type — + * the exact construction of build123d's Wire.offset_2d (Init(kind) + + * AddWire + Perform). For an OPEN wire the result is a closed contour: both + * offset sides plus round end caps, which the caller can split per side. */ +function OffsetPlanarWire(wire, offsetDistance, joinType) { + let join = joinType === 'intersection' + ? self.oc.GeomAbs_JoinType.GeomAbs_Intersection + : self.oc.GeomAbs_JoinType.GeomAbs_Arc; + let result = self.CacheOp(arguments, "OffsetPlanarWire", () => { + let offset = new self.oc.BRepOffsetAPI_MakeOffset_1(); + offset.Init_2(join, false); + offset.AddWire(_asWire(wire)); + offset.Perform(offsetDistance, 0.0); + let out = offset.Shape(); + if (out.ShapeType().value === 5) { return self.oc.TopoDS_Cast.Wire_1(out); } + let wires = []; + ForEachWire(out, (i, wr) => { wires.push(wr); }); + if (wires.length !== 1) { + console.error("OffsetPlanarWire: expected one offset wire, got " + wires.length); + return null; + } + return wires[0]; + }); + if (result) { self.sceneShapes.push(result); } + return result; +} + +/** Center of a circular/elliptical edge, or null (build123d Edge.arc_center). */ +function _edgeArcCenter(edge) { + let curve = new self.oc.BRepAdaptor_Curve_2(_asEdge(edge)); + let CT = self.oc.GeomAbs_CurveType; + let type = curve.GetType(); + let loc = null; + if (type === CT.GeomAbs_Circle) { loc = curve.Circle().Location(); } + else if (type === CT.GeomAbs_Ellipse) { loc = curve.Ellipse().Location(); } + if (loc === null) { return null; } + return [loc.X(), loc.Y(), loc.Z()]; +} + +/** An EXACT B-spline edge from poles, unique knots + multiplicities, degree and + * optional weights (build123d's Edge.make_bspline -> Geom_BSplineCurve). */ +function BSplineEdge(poles, knots, mults, degree, weights, periodic) { + return self.CacheOp(arguments, "BSplineEdge", () => { + let poleArr = new self.oc.TColgp_Array1OfPnt_2(1, poles.length); + for (let i = 0; i < poles.length; i++) { + poleArr.SetValue(i + 1, new self.oc.gp_Pnt_3(poles[i][0], poles[i][1], poles[i][2] || 0)); + } + let knotArr = new self.oc.TColStd_Array1OfReal_2(1, knots.length); + for (let i = 0; i < knots.length; i++) { knotArr.SetValue(i + 1, knots[i]); } + let multArr = new self.oc.TColStd_Array1OfInteger_2(1, mults.length); + for (let i = 0; i < mults.length; i++) { multArr.SetValue(i + 1, mults[i]); } + let spline; + if (weights && weights.length) { + let weightArr = new self.oc.TColStd_Array1OfReal_2(1, weights.length); + for (let i = 0; i < weights.length; i++) { weightArr.SetValue(i + 1, weights[i]); } + spline = new self.oc.Geom_BSplineCurve_2(poleArr, weightArr, knotArr, multArr, + degree, !!periodic, false); + } else { + spline = new self.oc.Geom_BSplineCurve_1(poleArr, knotArr, multArr, degree, !!periodic); + } + return new self.oc.BRepBuilderAPI_MakeEdge_24( + new self.oc.Handle_Geom_Curve_2(spline)).Edge(); + }); +} + +/** The order-th derivative of an edge's curve at the normalized arc-length + * position u — build123d's Mixin1D.derivative_at (BRepAdaptor_Curve::DN at + * the same GCPnts_AbscissaPoint parameter position_at uses). NOT normalized: + * the magnitude carries the curve's natural "speed", which is what + * BlendCurve's tangent scalars scale. */ +function _edgeDerivativeAt(edge, u, order) { + let e = _asEdge(edge); + let curve = new self.oc.BRepAdaptor_Curve_2(e); + let vec = curve.DN(_edgeParamAtFraction(curve, u), order); + return [vec.X(), vec.Y(), vec.Z()]; +} + +/** Normal of a circular/elliptical edge: its gp_Circ/gp_Elips axis direction + * (build123d Mixin1D.normal's conic branch). null for any other curve type — + * the caller falls back to a planarity check. */ +function _edgeArcNormal(edge) { + let curve = new self.oc.BRepAdaptor_Curve_2(_asEdge(edge)); + let CT = self.oc.GeomAbs_CurveType; + let type = curve.GetType(); + let dir = null; + if (type === CT.GeomAbs_Circle) { dir = curve.Circle().Axis().Direction(); } + else if (type === CT.GeomAbs_Ellipse) { dir = curve.Ellipse().Axis().Direction(); } + if (dir === null) { return null; } + return [dir.X(), dir.Y(), dir.Z()]; +} + +/** Radius of a circular edge, or null when the edge is not a circle + * (build123d Edge.radius). */ +function _edgeArcRadius(edge) { + let curve = new self.oc.BRepAdaptor_Curve_2(_asEdge(edge)); + if (curve.GetType() !== self.oc.GeomAbs_CurveType.GeomAbs_Circle) { return null; } + return curve.Circle().Radius(); +} + +/** A circle or circular ARC edge on a plane given by origin/normal/x-dir, + * angles in DEGREES measured from the x direction (build123d + * Edge.make_circle; start == end means a full circle). */ +function CircularEdge(radius, startAngle, endAngle, origin, normal, xDir) { + return self.CacheOp(arguments, "CircularEdge", () => { + let ax2 = new self.oc.gp_Ax2_2( + new self.oc.gp_Pnt_3(origin[0], origin[1], origin[2]), + new self.oc.gp_Dir_5(normal[0], normal[1], normal[2]), + new self.oc.gp_Dir_5(xDir[0], xDir[1], xDir[2])); + let circle = new self.oc.gp_Circ_2(ax2, radius); + if (Math.abs(startAngle - endAngle) % 360 < 1e-9) { + return new self.oc.BRepBuilderAPI_MakeEdge_8(circle).Edge(); + } + const rad = Math.PI / 180; + return new self.oc.BRepBuilderAPI_MakeEdge_9( + circle, startAngle * rad, endAngle * rad).Edge(); + }); +} + +/** build123d's Edge.is_interior: an edge is INTERIOR when the two faces + * meeting at it, each offset outward by length/100, still intersect in an + * edge (an exterior/convex edge's offsets separate). Exactly upstream's + * construction (topo_explore_connected_faces + offset_topods_face + + * BRepAlgoAPI_Section). */ +function EdgeIsInterior(edge, parentShape) { + let e = _asEdge(edge); + let faces = []; + let source = parentShape || e; + if (parentShape) { + // faces of the parent that contain this edge + let target = self.oc.OCJS.HashCode(e, 100000000); + ForEachFace(parentShape, (i, face) => { + let found = false; + ForEachEdge(face, (j, fe) => { + if (self.oc.OCJS.HashCode(fe, 100000000) === target) { found = true; } + }); + if (found) { faces.push(face); } + }); + } + if (faces.length !== 2) { return false; } + let dist = _edgeLength(e) / 100; + let offsets = []; + for (let i = 0; i < 2; i++) { + // BRepOffset_MakeOffset (upstream's offset_topods_face) is unbound here; + // BRepOffsetAPI_MakeOffsetShape drives the same BRepOffset algorithm + let mk = new self.oc.BRepOffsetAPI_MakeOffsetShape(); + mk.PerformByJoin(faces[i], dist, 1e-6, + self.oc.BRepOffset_Mode.BRepOffset_Skin, false, false, + self.oc.GeomAbs_JoinType.GeomAbs_Arc, false, + new self.oc.Message_ProgressRange_1()); + offsets.push(mk.Shape()); + } + let section = new self.oc.BRepAlgoAPI_Section_3(offsets[0], offsets[1], false); + section.Build(new self.oc.Message_ProgressRange_1()); + let found = false; + ForEachEdge(section.Shape(), () => { found = true; }); + return found; +} + function Revolve(shape, degrees, direction, keepShape, copy) { if (!degrees ) { degrees = 360.0; } if (!direction) { direction = [0, 0, 1]; } @@ -700,7 +1039,7 @@ function Revolve(shape, degrees, direction, keepShape, copy) { return new self.oc.BRepPrimAPI_MakeRevol_1(shape, new self.oc.gp_Ax1_2(new self.oc.gp_Pnt_3(0, 0, 0), new self.oc.gp_Dir_5(direction[0], direction[1], direction[2])), - degrees * 0.0174533, copy).Shape(); + degrees * (Math.PI / 180), copy).Shape(); } }); @@ -725,8 +1064,8 @@ function RotatedExtrude(wire, height, rotation, keepWire) { for (let i = 0; i <= steps; i++) { let alpha = i / steps; aspinePoints.push([ - 20 * Math.sin(alpha * rotation * 0.0174533), - 20 * Math.cos(alpha * rotation * 0.0174533), + 20 * Math.sin(alpha * rotation * (Math.PI / 180)), + 20 * Math.cos(alpha * rotation * (Math.PI / 180)), height * alpha]); } @@ -1076,7 +1415,7 @@ function Dropdown(name = "Dropdown", defaultValue = "", options = {}, realTime = // --- Internal Topology Helpers (used by selectors, not exported to user API) --- function _edgeMidpoint(edge) { - let curve = new self.oc.BRepAdaptor_Curve_2(edge); + let curve = new self.oc.BRepAdaptor_Curve_2(_asEdge(edge)); let midParam = (curve.FirstParameter() + curve.LastParameter()) / 2; let pnt = new self.oc.gp_Pnt_1(); curve.D0(midParam, pnt); @@ -1090,7 +1429,7 @@ function _edgeLength(edge) { } function _edgeCurveType(edge) { - let curve = new self.oc.BRepAdaptor_Curve_2(edge); + let curve = new self.oc.BRepAdaptor_Curve_2(_asEdge(edge)); let type = curve.GetType(); let CT = self.oc.GeomAbs_CurveType; if (type === CT.GeomAbs_Line) return "Line"; @@ -1136,7 +1475,1634 @@ function _faceNormal(face) { let normal = du.Crossed(dv); let mag = normal.Magnitude(); if (mag < 1e-10) return [0, 0, 1]; - return [normal.X() / mag, normal.Y() / mag, normal.Z() / mag]; + // respect the face's topological orientation (outward normals on solids) + let flip = face.Orientation_1() === self.oc.TopAbs_Orientation.TopAbs_REVERSED ? -1 : 1; + return [flip * normal.X() / mag, flip * normal.Y() / mag, flip * normal.Z() / mag]; +} + +/** The x direction build123d's Plane(face) derives: for elementary surfaces + * the underlying gp_Ax3's XDirection (surface.Position().XDirection()); for + * bounded surfaces (BSpline/Bezier/trimmed) the U derivative at RAW surface + * parameters (0.5, 0.5) — exactly geometry.py's Plane.__init__ Face branch. */ +function _faceUDir(face) { + let f = face.ShapeType && face.ShapeType().value === 4 ? self.oc.TopoDS_Cast.Face_1(face) : face; + let surf = new self.oc.BRepAdaptor_Surface_2(f, false); + let ST = self.oc.GeomAbs_SurfaceType; + let type = surf.GetType(); + if (type === ST.GeomAbs_Plane) { + let xd = surf.Plane().Position().XDirection(); + return [xd.X(), xd.Y(), xd.Z()]; + } + let pnt = new self.oc.gp_Pnt_1(); + let du = new self.oc.gp_Vec_1(); + let dv = new self.oc.gp_Vec_1(); + if (type === ST.GeomAbs_BSplineSurface || type === ST.GeomAbs_BezierSurface) { + // Geom_BoundedSurface: build123d evaluates D1 at raw (0.5, 0.5) + surf.D1(0.5, 0.5, pnt, du, dv); + } else { + let uMid = (surf.FirstUParameter() + surf.LastUParameter()) / 2; + let vMid = (surf.FirstVParameter() + surf.LastVParameter()) / 2; + surf.D1(uMid, vMid, pnt, du, dv); + } + let mag = du.Magnitude(); + if (mag < 1e-10) return null; + return [du.X() / mag, du.Y() / mag, du.Z() / mag]; +} + +/** TopoDS_Face view of a shape that IS a face but may still be typed as a + * generic TopoDS_Shape (everything that comes back from a transform or a + * boolean is). Single-face shells/compounds resolve to their one face: the + * extrusion of a wire is a SHELL even when build123d calls the result a + * Face, and build123d's face APIs work on it all the same. */ +function _asFace(face) { + if (face.ShapeType().value === 4) { return self.oc.TopoDS_Cast.Face_1(face); } + let found = []; + ForEachFace(face, (i, f) => { found.push(f); }); + if (found.length === 1) { return found[0]; } + throw new Error("expected a single face, got a shape with " + found.length + " faces"); +} + +/** The face's UV parameter bounds, [uMin, uMax, vMin, vMax] (BRepTools:: + * UVBounds) — build123d's Face._uv_bounds, the domain its normalized u/v + * arguments are mapped into. */ +function _faceUVBounds(face) { + let u1 = { current: 0 }, u2 = { current: 0 }, v1 = { current: 0 }, v2 = { current: 0 }; + self.oc.BRepTools.UVBounds_1(_asFace(face), u1, u2, v1, v2); + return [u1.current, u2.current, v1.current, v2.current]; +} + +/** Point and U/V partial derivatives of the face's underlying surface at RAW + * surface parameters: [[x,y,z], [dU], [dV]] — the D1 evaluation + * build123d's Face.location_at performs (Restriction=false, so the RAW + * surface parameterization, exactly like BRep_Tool::Surface). */ +function _faceD1(face, u, v) { + let surf = new self.oc.BRepAdaptor_Surface_2(_asFace(face), false); + let pnt = new self.oc.gp_Pnt_1(); + let du = new self.oc.gp_Vec_1(); + let dv = new self.oc.gp_Vec_1(); + surf.D1(u, v, pnt, du, dv); + return [[pnt.X(), pnt.Y(), pnt.Z()], + [du.X(), du.Y(), du.Z()], + [dv.X(), dv.Y(), dv.Z()]]; +} + +/** RAW (u, v) surface parameters of the point of the face's surface closest to + * `point` — what build123d reads out of GeomAPI_ProjectPointOnSurf for the + * surface_point overloads of normal_at/location_at. `hint` ([u, v]) seeds the + * search when the caller already knows roughly where the point lands. + * + * This is upstream's call: GeomAPI_ProjectPointOnSurf over the face's own UV + * box, read back through OCJS_Out.ProjectPointOnSurf_LowerDistanceParameters + * (LowerDistanceParameters returns (u, v) through Standard_Real&, which + * Embind passes by value). The UV-grid + Newton search below is kept as a + * fallback for the cases where OCCT reports no solution — it is exact for + * points on or near the surface and was the only route before + * Extrema_ExtAlgo/Extrema_ExtFlag were bound. */ +function _faceParamsAtPoint(face, point, hint) { + let f = _asFace(face); + let projected = _faceParamsAtPointOCCT(f, point); + if (projected) { return projected; } + return _faceParamsAtPointSearch(f, point, hint); +} + +/** GeomAPI_ProjectPointOnSurf on the face's surface, restricted to the face's + * own UV bounds (build123d's Face.normal_at/location_at path). */ +function _faceParamsAtPointOCCT(f, point) { + let bounds = _faceUVBounds(f); + let surface = self.oc.BRep_Tool.Surface_2(f); + let projector = new self.oc.GeomAPI_ProjectPointOnSurf_5( + new self.oc.gp_Pnt_3(point[0], point[1], point[2]), surface, + bounds[0], bounds[1], bounds[2], bounds[3], + self.oc.Extrema_ExtAlgo.Extrema_ExtAlgo_Grad); + if (!projector.IsDone() || projector.NbPoints() < 1) { return null; } + let uv = self.oc.OCJS_Out.ProjectPointOnSurf_LowerDistanceParameters(projector); + return [uv.u, uv.v]; +} + +function _faceParamsAtPointSearch(face, point, hint) { + let f = _asFace(face); + let surf = new self.oc.BRepAdaptor_Surface_2(f, false); + let bounds = _faceUVBounds(f); + let uMin = bounds[0], uMax = bounds[1], vMin = bounds[2], vMax = bounds[3]; + let px = point[0], py = point[1], pz = point[2]; + let pnt = new self.oc.gp_Pnt_1(); + let d1u = new self.oc.gp_Vec_1(), d1v = new self.oc.gp_Vec_1(); + let d2u = new self.oc.gp_Vec_1(), d2v = new self.oc.gp_Vec_1(), d2uv = new self.oc.gp_Vec_1(); + let dist2 = (u, v) => { + surf.D0(u, v, pnt); + let dx = pnt.X() - px, dy = pnt.Y() - py, dz = pnt.Z() - pz; + return dx * dx + dy * dy + dz * dz; + }; + let bu, bv; + if (hint) { + bu = Math.min(uMax, Math.max(uMin, hint[0])); + bv = Math.min(vMax, Math.max(vMin, hint[1])); + } else { + const N = 24; + let best = Infinity; + for (let i = 0; i <= N; i++) { + let u = uMin + (uMax - uMin) * (i / N); + for (let j = 0; j <= N; j++) { + let v = vMin + (vMax - vMin) * (j / N); + let d = dist2(u, v); + if (d < best) { best = d; bu = u; bv = v; } + } + } + if (best === Infinity) { return null; } + } + let dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + for (let iter = 0; iter < 40; iter++) { + surf.D2(bu, bv, pnt, d1u, d1v, d2u, d2v, d2uv); + let fv = [pnt.X() - px, pnt.Y() - py, pnt.Z() - pz]; + let su = [d1u.X(), d1u.Y(), d1u.Z()], sv = [d1v.X(), d1v.Y(), d1v.Z()]; + let suu = [d2u.X(), d2u.Y(), d2u.Z()], svv = [d2v.X(), d2v.Y(), d2v.Z()]; + let suv = [d2uv.X(), d2uv.Y(), d2uv.Z()]; + let g0 = dot(fv, su), g1 = dot(fv, sv); + let a = dot(su, su) + dot(fv, suu); + let b = dot(su, sv) + dot(fv, suv); + let c = dot(sv, sv) + dot(fv, svv); + let det = a * c - b * b; + let du, dv; + if (Math.abs(det) < 1e-30) { + // singular Hessian (degenerate/ruled directions): plain gradient step + // scaled by the first fundamental form + du = dot(su, su) > 1e-30 ? -g0 / dot(su, su) : 0; + dv = dot(sv, sv) > 1e-30 ? -g1 / dot(sv, sv) : 0; + } else { + du = (b * g1 - c * g0) / det; + dv = (b * g0 - a * g1) / det; + } + // backtracking so the residual never grows, clamped into the UV box + let cur = dot(fv, fv); + let t = 1.0, stepped = false; + for (let k = 0; k < 24; k++) { + let nu = Math.min(uMax, Math.max(uMin, bu + t * du)); + let nv = Math.min(vMax, Math.max(vMin, bv + t * dv)); + if (dist2(nu, nv) <= cur + 1e-18) { + stepped = Math.abs(nu - bu) > 1e-14 * (1 + Math.abs(bu)) || + Math.abs(nv - bv) > 1e-14 * (1 + Math.abs(bv)); + bu = nu; bv = nv; + break; + } + t *= 0.5; + } + if (!stepped) { break; } + } + return [bu, bv]; +} + +/** Unit surface normal at RAW (u, v) parameters, respecting the face's + * topological orientation (BRepGProp_Face::Normal) — build123d's + * Face.normal_at. */ +function _faceNormalAt(face, u, v) { + let props = new self.oc.BRepGProp_Face_2(_asFace(face), false); + let pnt = new self.oc.gp_Pnt_1(); + let nrm = new self.oc.gp_Vec_1(); + props.Normal(u, v, pnt, nrm); + let mag = nrm.Magnitude(); + if (mag < 1e-12) { return [0, 0, 1]; } + return [nrm.X() / mag, nrm.Y() / mag, nrm.Z() / mag]; +} + +function _faceSurfaceType(face) { + let surf = new self.oc.BRepAdaptor_Surface_2(face, true); + let type = surf.GetType(); + let ST = self.oc.GeomAbs_SurfaceType; + if (type === ST.GeomAbs_Plane) return "Plane"; + if (type === ST.GeomAbs_Cylinder) return "Cylinder"; + if (type === ST.GeomAbs_Cone) return "Cone"; + if (type === ST.GeomAbs_Sphere) return "Sphere"; + if (type === ST.GeomAbs_Torus) return "Torus"; + if (type === ST.GeomAbs_BezierSurface) return "BezierSurface"; + if (type === ST.GeomAbs_BSplineSurface) return "BSplineSurface"; + return "Other"; +} + +/** The face's outer boundary wire (BRepTools::OuterWire). */ +function _faceOuterWire(face) { + let f = face.ShapeType().value === 4 ? self.oc.TopoDS_Cast.Face_1(face) : face; + let wire = self.oc.BRepTools.OuterWire(f); + if (wire.hash === undefined) { wire.hash = self.oc.OCJS.HashCode(wire, 100000000); } + return wire; +} + +/** Whether an edge's topological orientation is FORWARD (build123d's + * Edge.is_forward — position_at/tangent_at flip on REVERSED edges). */ +function _edgeIsForward(edge) { + return _asEdge(edge).Orientation_1() !== self.oc.TopAbs_Orientation.TopAbs_REVERSED; +} + +/** TopoDS_Shape::IsSame across the Brython boundary. */ +function _sameShape(a, b) { + return a.IsSame(b); +} + +function _vertexPoint(vertex) { + let p = self.oc.BRep_Tool.Pnt(vertex); + return [p.X(), p.Y(), p.Z()]; +} + +/** Curve parameter at the given ARC-LENGTH fraction u of an edge — + * GCPnts_AbscissaPoint, matching build123d's default + * PositionMode.LENGTH for position_at/tangent_at. (Raw parameter + * fraction only coincides with this for uniform-speed curves like + * lines and circles; it diverges on BSplines, e.g. surface-surface + * intersection curves.) */ +function _edgeParamAtFraction(curve, u) { + let first = curve.FirstParameter(); + let last = curve.LastParameter(); + if (u === 0) { return first; } + if (u === 1) { return last; } + // u outside [0, 1] EXTRAPOLATES along the underlying curve, like + // build123d's param_at/position_at ("positions outside [0, 1] are not + // validated and yield OCCT-dependent results"). The docs rely on it: + // `line @ 2/3` parses as `(line @ 2) / 3`, so the object lands at twice the + // line's end point divided by three. + let len = self.oc.GCPnts_AbscissaPoint.Length_5(curve, first, last); + let ap = new self.oc.GCPnts_AbscissaPoint_2(curve, len * u, first); + if (ap.IsDone()) { return ap.Parameter(); } + return first + (last - first) * u; +} + +function _edgePointAt(edge, u) { + let curve = new self.oc.BRepAdaptor_Curve_2(_asEdge(edge)); + let param = _edgeParamAtFraction(curve, u); + let pnt = new self.oc.gp_Pnt_1(); + curve.D0(param, pnt); + return [pnt.X(), pnt.Y(), pnt.Z()]; +} + +function _edgeTangentAt(edge, u) { + let curve = new self.oc.BRepAdaptor_Curve_2(_asEdge(edge)); + let param = _edgeParamAtFraction(curve, u); + let pnt = new self.oc.gp_Pnt_1(); + let vec = new self.oc.gp_Vec_1(); + curve.D1(param, pnt, vec); + let mag = vec.Magnitude(); + if (mag < 1e-12) { return [0, 0, 0]; } + return [vec.X() / mag, vec.Y() / mag, vec.Z() / mag]; +} + +/** 2D fillet of a planar face's corner vertices (BRepFilletAPI_MakeFillet2d). + * `points` selects vertices by position ([[x,y,z], ...], 1e-6 tolerance); + * pass null to fillet every corner. Returns the new face. */ +function FilletFace2D(face, radius, points, keepFace) { + if (!face || face.IsNull()) { console.error("FilletFace2D: input face is null!"); return face; } + let result = self.CacheOp(arguments, "FilletFace2D", () => { + let f = face.ShapeType().value === 4 ? self.oc.TopoDS_Cast.Face_1(face) : face; + let mkFillet = new self.oc.BRepFilletAPI_MakeFillet2d_2(f); + let seen = {}; + let added = 0; + ForEachVertex(f, (vertex) => { + let p = self.oc.BRep_Tool.Pnt(vertex); + let key = p.X().toFixed(6) + "," + p.Y().toFixed(6) + "," + p.Z().toFixed(6); + if (seen[key]) { return; } + let wanted = !points; + if (points) { + for (let i = 0; i < points.length; i++) { + if (Math.abs(points[i][0] - p.X()) < 1e-6 && + Math.abs(points[i][1] - p.Y()) < 1e-6 && + Math.abs((points[i][2] || 0) - p.Z()) < 1e-6) { wanted = true; break; } + } + } + if (wanted) { seen[key] = true; mkFillet.AddFillet(vertex, radius); added++; } + }); + if (added === 0) { + console.error("FilletFace2D: no vertices matched — nothing filleted."); + return face; + } + mkFillet.Build(new self.oc.Message_ProgressRange_1()); + return mkFillet.Shape(); + }); + if (!keepFace) { self.sceneShapes = self.Remove(self.sceneShapes, face); } + self.sceneShapes.push(result); + return result; +} + +/** Reverse a face's topological orientation (flips the oriented normal), + * returning a properly-typed TopoDS_Face. */ +function ReverseFace(face, keepFace) { + let reversed = self.oc.TopoDS_Cast.Face_1(face.Reversed()); + reversed.hash = self.oc.OCJS.HashCode(reversed, 100000000); + if (!keepFace) { self.sceneShapes = self.Remove(self.sceneShapes, face); } + self.sceneShapes.push(reversed); + return reversed; +} + +// --------------------------------------------------------------------------- +// Curve-on-surface primitives for build123d-lite's wrap()/wrap_faces(): the +// exact OCCT calls upstream's Face._wrap_edge / _wrap_wire / _wrap_face and +// Edge._extend_spline / trim / param_at make. +// --------------------------------------------------------------------------- + +function _asEdge(shape) { + return shape.ShapeType().value === 6 ? self.oc.TopoDS_Cast.Edge_1(shape) : shape; +} + +/** Curve parameter at an arc-length FRACTION of an edge, allowing fractions + * outside [0, 1] (build123d's Edge.param_at, which _extend_spline calls with + * -0.1 / 1.1 to run past the ends). */ +function _edgeParam(edge, u) { + let curve = new self.oc.BRepAdaptor_Curve_2(_asEdge(edge)); + let first = curve.FirstParameter(), last = curve.LastParameter(); + let len = self.oc.GCPnts_AbscissaPoint.Length_5(curve, first, last); + let ap = new self.oc.GCPnts_AbscissaPoint_2(curve, len * u, first); + if (ap.IsDone()) { return ap.Parameter(); } + return first + (last - first) * u; +} + +/** The part of an edge between two arc-length fractions (build123d + * Edge.trim), oriented from f0 towards f1. */ +function TrimEdge(edge, f0, f1) { + let e = _asEdge(edge); + let p0 = _edgeParam(e, f0), p1 = _edgeParam(e, f1); + let first = { current: 0 }, last = { current: 0 }; + let curve = self.oc.BRep_Tool.Curve_2(e, first, last); + let lo = Math.min(p0, p1), hi = Math.max(p0, p1); + let trimmed = new self.oc.BRepBuilderAPI_MakeEdge_25(curve, lo, hi).Edge(); + if (p1 < p0) { trimmed = self.oc.TopoDS_Cast.Edge_1(trimmed.Reversed()); } + trimmed.hash = self.oc.OCJS.HashCode(trimmed, 100000000); + self.sceneShapes.push(trimmed); + return trimmed; +} + +// --------------------------------------------------------------------------- +// Canonical free-edge parametrization primitives (build123d-lite's +// Mixin1D.canonical — see the upstream proposal in +// zalo/build123d branch canonical-research, research/). The rule itself is pure +// geometry and lives +// in Python (Build123dLite.js); these are the four kernel operations it needs: +// reverse a 1D shape, locate a point on an edge, measure a point's distance to +// an edge, and concatenate an ordered edge chain into ONE edge (which is what +// gives a re-seamed closed loop an unambiguous start point). +// --------------------------------------------------------------------------- + +/** Reverse the topological orientation of an Edge or a Wire, keeping the + * concrete TopoDS type (build123d's Edge.reversed / _reverse_1d). */ +function ReverseEdgeOrWire(shape) { + let reversed = shape.Reversed(); + let kind = shape.ShapeType().value; + if (kind === 6) { reversed = self.oc.TopoDS_Cast.Edge_1(reversed); } + else if (kind === 5) { reversed = self.oc.TopoDS_Cast.Wire_1(reversed); } + reversed.hash = self.oc.OCJS.HashCode(reversed, 100000000); + self.sceneShapes.push(reversed); + return reversed; +} + +/** Minimal distance between two shapes and the closest point ON EACH + * (BRepExtrema_DistShapeShape) — build123d's + * Shape.distance_to_with_closest_points / closest_points / distance. + * Returns [distance, [x1, y1, z1], [x2, y2, z2]], or null when the extrema + * algorithm finds no solution. */ +function _distShapeShape(shapeA, shapeB) { + let ext = new self.oc.BRepExtrema_DistShapeShape_1(); + ext.LoadS1(shapeA); + ext.LoadS2(shapeB); + ext.Perform(new self.oc.Message_ProgressRange_1()); + if (!ext.IsDone() || ext.NbSolution() < 1) { return null; } + let p1 = ext.PointOnShape1(1), p2 = ext.PointOnShape2(1); + return [ext.Value(), [p1.X(), p1.Y(), p1.Z()], [p2.X(), p2.Y(), p2.Z()]]; +} + +/** Sign of a face's curvature relative to its OWN geometry, for the three + * surface types build123d's Face.is_circular_convex/_concave support + * (cylinder, sphere, torus): positive = convex, negative = concave, 0 for + * every other surface type — build123d's Face._curvature_sign. + * + * This is upstream's own comparison: the surface's reference geometry + * (gp_Cylinder's axis, gp_Sphere's centre, the core circle of a gp_Torus) is + * read off the adaptor and dotted against the oriented normal at the face's + * mid parameters. gp_Cylinder/gp_Sphere/gp_Torus are bound in the fork as of + * this round; the second-fundamental-form substitution that stood in for them + * is kept below for any other kernel where they are missing. */ +function _faceCurvatureSign(face) { + let f = _asFace(face); + let surf = new self.oc.BRepAdaptor_Surface_2(f, true); + let ST = self.oc.GeomAbs_SurfaceType; + let type = surf.GetType(); + if (type !== ST.GeomAbs_Cylinder && type !== ST.GeomAbs_Sphere && + type !== ST.GeomAbs_Torus) { return 0.0; } + let midU = (surf.FirstUParameter() + surf.LastUParameter()) / 2; + let midV = (surf.FirstVParameter() + surf.LastVParameter()) / 2; + let reference = null; + if (type === ST.GeomAbs_Sphere) { + let loc = surf.Sphere().Location(); + reference = [loc.X(), loc.Y(), loc.Z()]; + } else if (type === ST.GeomAbs_Cylinder) { + // the point on the cylinder's axis nearest the sample point + let axis = surf.Cylinder().Axis(); + let o = axis.Location(), d = axis.Direction(); + let p = new self.oc.gp_Pnt_1(); + surf.D0(midU, midV, p); + let t = (p.X() - o.X()) * d.X() + (p.Y() - o.Y()) * d.Y() + (p.Z() - o.Z()) * d.Z(); + reference = [o.X() + d.X() * t, o.Y() + d.Y() * t, o.Z() + d.Z() * t]; + } else { + // torus: the point on the CORE circle nearest the sample point + let tor = surf.Torus(); + let pos = tor.Position(); + let o = pos.Location(), d = pos.Direction(); + let major = tor.MajorRadius(); + let p = new self.oc.gp_Pnt_1(); + surf.D0(midU, midV, p); + let vx = p.X() - o.X(), vy = p.Y() - o.Y(), vz = p.Z() - o.Z(); + let along = vx * d.X() + vy * d.Y() + vz * d.Z(); + let rx = vx - d.X() * along, ry = vy - d.Y() * along, rz = vz - d.Z() * along; + let rl = Math.sqrt(rx * rx + ry * ry + rz * rz); + if (rl > 1e-12) { + reference = [o.X() + rx / rl * major, o.Y() + ry / rl * major, + o.Z() + rz / rl * major]; + } + } + if (reference) { + let p = new self.oc.gp_Pnt_1(); + surf.D0(midU, midV, p); + let n = _faceNormalAt(f, midU, midV); + let dx = p.X() - reference[0], dy = p.Y() - reference[1], dz = p.Z() - reference[2]; + let dist = Math.sqrt(dx * dx + dy * dy + dz * dz); + if (dist > 1e-12) { + // upstream: normal . (P - reference) > 0 is convex; the magnitude is the + // reference distance, which callers compare against _TOL_1E6 + return (n[0] * dx + n[1] * dy + n[2] * dz) > 0 ? dist : -dist; + } + } + let u = (surf.FirstUParameter() + surf.LastUParameter()) / 2; + let v = (surf.FirstVParameter() + surf.LastVParameter()) / 2; + let pnt = new self.oc.gp_Pnt_1(); + let du = new self.oc.gp_Vec_1(), dv = new self.oc.gp_Vec_1(); + let duu = new self.oc.gp_Vec_1(), dvv = new self.oc.gp_Vec_1(); + let duv = new self.oc.gp_Vec_1(); + surf.D2(u, v, pnt, du, dv, duu, dvv, duv); + let normal = du.Crossed(dv); + let mag = normal.Magnitude(); + if (mag < 1e-12) { return 0.0; } + let flip = f.Orientation_1() === self.oc.TopAbs_Orientation.TopAbs_REVERSED ? -1 : 1; + let nx = flip * normal.X() / mag, ny = flip * normal.Y() / mag, nz = flip * normal.Z() / mag; + let curvature = (second, first) => { + let sq = first.SquareMagnitude(); + if (sq < 1e-24) { return 0.0; } + return (second.X() * nx + second.Y() * ny + second.Z() * nz) / sq; + }; + let ku = curvature(duu, du), kv = curvature(dvv, dv); + let k = Math.abs(ku) >= Math.abs(kv) ? ku : kv; + if (Math.abs(k) < 1e-12) { return 0.0; } + // report the reference distance (1/|k| == the radius upstream dots against), + // signed the way upstream signs it + return -Math.sign(k) / Math.abs(k); +} + +/** 2-D corner fillet between two connected edges of an OPEN planar wire + * (build123d's `_solve_wire_fillet_corner_chfi2d`, the primary solver behind + * Wire.fillet_2d). `vertexPoint` is the shared corner. Returns + * `{ fillet, trimmed1, trimmed2 }` TopoDS_Edges, or null when ChFi2d finds no + * solution — upstream then falls back to the Geom2dGcc tangent-arc solver. + * + * ChFi2d_FilletAlgo::Result hands the two trimmed edges back through + * references; the fork registers OCJS_Out.FilletAlgo_Result for that. */ +function FilletWireCorner(edge1, edge2, vertexPoint, radius) { + let algo = new self.oc.ChFi2d_FilletAlgo_1(); + algo.Init_2(_asEdge(edge1), _asEdge(edge2), + new self.oc.gp_Pln_3(new self.oc.gp_Pnt_3(0, 0, 0), + new self.oc.gp_Dir_5(0, 0, 1))); + if (!algo.Perform(radius)) { return null; } + let corner = new self.oc.gp_Pnt_3(vertexPoint[0], vertexPoint[1], + vertexPoint[2] || 0); + if (algo.NbResults(corner) === 0) { return null; } + let out = self.oc.OCJS_Out.FilletAlgo_Result(algo, corner); + return [out.fillet, out.trimmed1, out.trimmed2]; +} + +/** Radius of a cylindrical or spherical face (build123d Face.radius), null for + * every other surface type. Reads the surface's own gp_Cylinder/gp_Sphere, + * which the fork binds as of this round. */ +function _faceRadius(face) { + let surf = new self.oc.BRepAdaptor_Surface_2(_asFace(face), true); + let ST = self.oc.GeomAbs_SurfaceType; + let type = surf.GetType(); + if (type === ST.GeomAbs_Cylinder) { return surf.Cylinder().Radius(); } + if (type === ST.GeomAbs_Sphere) { return surf.Sphere().Radius(); } + return null; +} + +/** Rotational axis of a cone/cylinder/sphere/torus/surface-of-revolution + * face (build123d Face.axis_of_rotation) as [origin, direction], else null. */ +function _faceAxisOfRotation(face) { + let surf = new self.oc.BRepAdaptor_Surface_2(_asFace(face), true); + let ST = self.oc.GeomAbs_SurfaceType; + let type = surf.GetType(); + let ax = null; + if (type === ST.GeomAbs_Cone) { ax = surf.Cone().Axis(); } + else if (type === ST.GeomAbs_Cylinder) { ax = surf.Cylinder().Axis(); } + else if (type === ST.GeomAbs_Torus) { ax = surf.Torus().Axis(); } + else if (type === ST.GeomAbs_Sphere) { ax = surf.Sphere().Position().Axis(); } + else if (type === ST.GeomAbs_SurfaceOfRevolution) { ax = surf.AxeOfRevolution(); } + if (!ax) { return null; } + let o = ax.Location(), d = ax.Direction(); + return [[o.X(), o.Y(), o.Z()], [d.X(), d.Y(), d.Z()]]; +} + +// --------------------------------------------------------------------------- +// 2-D geometric constraint solvers (OCCT Geom2dGcc) — the kernel side of +// build123d's ConstrainedArcs / ConstrainedLines. +// +// A statement-for-statement port of build123d 0.11.1's +// topology/constrained_lines.py: every argument is projected onto Plane.XY +// (GeomAPI::To2d), wrapped in a Geom2dGcc_QualifiedCurve with the script's +// Tangency qualifier, handed to the matching Geom2dGcc solver, and each +// solution is kept only when its tangency parameter falls inside the +// argument's TRIMMED range (upstream's _param_in_trim). +// +// `Tangency1/2/3` return their two parameters through `Standard_Real&`, which +// Embind passes by value; the fork registers OCJS_Out._Tangency() +// for exactly this (see builds/cascadestudio.yml). +// --------------------------------------------------------------------------- + +const _GCC_TOLERANCE = 1e-6; // build123d.geometry.TOLERANCE + +function _gccQualifier(name) { + const P = self.oc.GccEnt_Position; + if (name === 'ENCLOSING') { return P.GccEnt_enclosing; } + if (name === 'ENCLOSED') { return P.GccEnt_enclosed; } + if (name === 'OUTSIDE') { return P.GccEnt_outside; } + return P.GccEnt_unqualified; +} + +function _gccXYPlane() { + return new self.oc.gp_Pln_3(new self.oc.gp_Pnt_3(0, 0, 0), + new self.oc.gp_Dir_5(0, 0, 1)); +} + +/** build123d's `_edge_to_qualified_2d`: the edge's 3-D curve projected onto + * Plane.XY, kept on the edge's own parameter range. */ +function _gccQualifiedCurve(edge, qualifier) { + let e = _asEdge(edge); + let adaptor = new self.oc.BRepAdaptor_Curve_2(e); + let first = adaptor.FirstParameter(), last = adaptor.LastParameter(); + let curve3d = self.oc.BRep_Tool.Curve_2(e, { current: 0 }, { current: 0 }); + let curve2d = self.oc.GeomAPI.To2d(curve3d, _gccXYPlane()); + let adapt2d = new self.oc.Geom2dAdaptor_Curve_3(curve2d, first, last); + return { + isEdge: true, + q: new self.oc.Geom2dGcc_QualifiedCurve(adapt2d, _gccQualifier(qualifier)), + curve2d: curve2d, adapt2d: adapt2d, first: first, last: last, + }; +} + +/** One tangency/target argument: `{edge, qualifier}` or `{point: [x, y]}`. */ +function _gccArg(spec) { + if (spec.point) { + return { + isEdge: false, + q: new self.oc.Geom2d_CartesianPoint_2(spec.point[0], spec.point[1]), + pnt2d: new self.oc.gp_Pnt2d_3(spec.point[0], spec.point[1]), + }; + } + return _gccQualifiedCurve(spec.edge, spec.qualifier); +} + +/** upstream's `_param_in_trim`: normalize onto the period, then test the + * argument's trimmed range with TOLERANCE. */ +function _gccParamInTrim(arg, u) { + if (!arg.isEdge) { return true; } + let v = u; + if (arg.adapt2d.IsPeriodic()) { + let period = arg.adapt2d.Period(); + v = ((u - arg.first) % period + period) % period + arg.first; + } + return v >= arg.first - _GCC_TOLERANCE && v <= arg.last + _GCC_TOLERANCE; +} + +/** A 3-D edge on Plane.XY from a trimmed 2-D circle span, exactly like + * upstream's `_edge_from_circle` (Geom2d_TrimmedCurve on the XY surface, + * then BRepLib::BuildCurves3d). */ +function _gccEdgeFromCircle2d(circ2d, u1, u2) { + let geomCircle = new self.oc.Geom2d_Circle_1(circ2d); + let handle = new self.oc.Handle_Geom2d_Curve_2(geomCircle); + let trimmed = new self.oc.Geom2d_TrimmedCurve(handle, u1, u2, true, true); + let surface = new self.oc.Handle_Geom_Surface_2( + new self.oc.Geom_Plane_2(_gccXYPlane())); + let edge = new self.oc.BRepBuilderAPI_MakeEdge_30( + new self.oc.Handle_Geom2d_Curve_2(trimmed), surface).Edge(); + self.oc.BRepLib.BuildCurves3d_2(edge); + return edge; +} + +/** Both arcs of a solution circle between two of its parameters — upstream's + * `_two_arc_edges_from_params` (the forward span and its complement). */ +function _gccTwoArcs(circ2d, u1, u2) { + const period = 2 * Math.PI; + const norm = (u) => ((u % period) + period) % period; + let u1n = norm(u1), u2n = norm(u2); + let d = u2n - u1n; + if (d < 0) { d += period; } + if (d <= _GCC_TOLERANCE || Math.abs(period - d) <= _GCC_TOLERANCE) { return []; } + return [_gccEdgeFromCircle2d(circ2d, u1n, u1n + d), + _gccEdgeFromCircle2d(circ2d, u2n, u2n + (period - d))]; +} + +/** upstream's `_edge_from_line`: a finite segment between two 2-D points. */ +function _gccEdgeFromLine(p1, p2) { + let v1 = new self.oc.BRepBuilderAPI_MakeVertex( + new self.oc.gp_Pnt_3(p1[0], p1[1], 0)).Vertex(); + let v2 = new self.oc.BRepBuilderAPI_MakeVertex( + new self.oc.gp_Pnt_3(p2[0], p2[1], 0)).Vertex(); + let mk = new self.oc.BRepBuilderAPI_MakeEdge_2(v1, v2); + if (!mk.IsDone()) { return null; } + return mk.Edge(); +} + +/** Sagitta selection: BOTH keeps the pair, SHORT/LONG index the pair sorted + * by arc length (upstream sorts with GCPnts_AbscissaPoint). */ +function _gccPickSagitta(arcs, sagitta, out) { + if (arcs.length === 0) { return; } + if (sagitta === 1) { for (const a of arcs) { out.push(a); } return; } + let sorted = arcs.slice().sort((a, b) => _edgeLength(a) - _edgeLength(b)); + out.push(sorted[sagitta === -1 ? sorted.length - 1 : 0]); +} + +/** Upstream's `_enclosed_circ_param_offset`: when a solution circle sits + * INSIDE a circular tangency target and at least one argument is not a + * circle, OCCT reports the tangency parameter half a turn away. */ +function _gccEnclosedOffset(specs, circ2d, params) { + let isCirc = specs.map((s) => { + if (!s.edge) { return false; } + return _edgeCurveType(s.edge) === 'CIRCLE'; + }); + if (isCirc.every((c) => c)) { return params.slice(); } + let center = circ2d.Location(); + return params.map((p, i) => { + if (!specs[i].edge || !isCirc[i]) { return p; } + let c = _edgeArcCenter(specs[i].edge); + let dx = center.X() - c[0], dy = center.Y() - c[1], dz = 0 - c[2]; + let dist = Math.sqrt(dx * dx + dy * dy + dz * dz); + return dist < _edgeArcRadius(specs[i].edge) ? p + Math.PI : p; + }); +} + +/** Circular arcs constrained by tangency (build123d Edge.make_constrained_arcs + * / ConstrainedArcs). `specs` is 1-3 tangency arguments; `opts` selects the + * overload exactly as upstream's keyword arguments do: + * {radius} -> Geom2dGcc_Circ2d2TanRad (2 args) + * {centerOn} -> Geom2dGcc_Circ2d2TanOn (2 args) + * {} -> Geom2dGcc_Circ2d3Tan (3 args) + * {center} -> Geom2dGcc_Circ2dTanCen (1 arg) + * {radius, centerOn} -> Geom2dGcc_Circ2dTanOnRad (1 arg) + * Returns an array of TopoDS_Edge (not added to the scene). */ +function ConstrainedArcs2D(specs, opts) { + const oc = self.oc; + const sagitta = opts.sagitta === undefined ? 0 : opts.sagitta; + let args = specs.map(_gccArg); + let out = []; + + // --- fixed centre, one tangency: full circles --------------------------- + if (opts.center) { + let cx = opts.center[0], cy = opts.center[1]; + if (!args[0].isEdge) { + let p = args[0].q.Pnt2d(); + let r = Math.hypot(p.X() - cx, p.Y() - cy); + if (r <= _GCC_TOLERANCE) { return []; } + let circ = new oc.gp_Circ2d_2(new oc.gp_Ax2d_2( + new oc.gp_Pnt2d_3(cx, cy), new oc.gp_Dir2d_5(1, 0)), r, true); + return [_gccEdgeFromCircle2d(circ, 0, 2 * Math.PI)]; + } + let gcc = new oc.Geom2dGcc_Circ2dTanCen( + args[0].q, new oc.Handle_Geom2d_Point_2( + new oc.Geom2d_CartesianPoint_2(cx, cy)), _GCC_TOLERANCE); + if (!gcc.IsDone() || gcc.NbSolutions() === 0) { + throw new Error('ConstrainedArcs: no tangent circle for the given centre'); + } + for (let i = 1; i <= gcc.NbSolutions(); i++) { + let t = oc.OCJS_Out.Circ2dTanCen_Tangency1(gcc, i); + if (!_gccParamInTrim(args[0], t.parArg)) { continue; } + out.push(_gccEdgeFromCircle2d(gcc.ThisSolution(i), 0, 2 * Math.PI)); + } + return out; + } + + // --- one tangency + radius + centre locus: full circles ------------------ + if (opts.centerOn && specs.length === 1) { + let on = _gccQualifiedCurve(opts.centerOn, 'UNQUALIFIED'); + let gcc = new oc.Geom2dGcc_Circ2dTanOnRad_1( + args[0].q, on.adapt2d, opts.radius, _GCC_TOLERANCE); + if (!gcc.IsDone() || gcc.NbSolutions() === 0) { + throw new Error('ConstrainedArcs: no circle for the TanOnRad constraints'); + } + for (let i = 1; i <= gcc.NbSolutions(); i++) { + let t = oc.OCJS_Out.Circ2dTanOnRad_Tangency1(gcc, i); + if (!_gccParamInTrim(args[0], t.parArg)) { continue; } + let circ = gcc.ThisSolution(i); + // the centre must land on the TRIMMED locus + let proj = new oc.Geom2dAPI_ProjectPointOnCurve_2(circ.Location(), on.curve2d); + if (proj.NbPoints() < 1 || !_gccParamInTrim(on, proj.Parameter_1(1))) { continue; } + out.push(_gccEdgeFromCircle2d(circ, 0, 2 * Math.PI)); + } + return out; + } + + // --- two/three tangencies: arcs between the first two tangency points ---- + let gcc, tangency1, tangency2, tangency3 = null; + if (opts.centerOn) { + let on = _gccQualifiedCurve(opts.centerOn, 'UNQUALIFIED'); + let guesses = []; + for (const a of args) { if (a.isEdge) { guesses.push((a.first + a.last) / 2); } } + if (on.isEdge) { guesses.push((on.first + on.last) / 2); } + gcc = guesses.length === 3 + ? new oc.Geom2dGcc_Circ2d2TanOn_1(args[0].q, args[1].q, on.adapt2d, + _GCC_TOLERANCE, guesses[0], guesses[1], guesses[2]) + : new oc.Geom2dGcc_Circ2d2TanOn_3(args[0].q, args[1].q, on.adapt2d, _GCC_TOLERANCE); + tangency1 = (i) => oc.OCJS_Out.Circ2d2TanOn_Tangency1(gcc, i); + tangency2 = (i) => oc.OCJS_Out.Circ2d2TanOn_Tangency2(gcc, i); + } else if (specs.length === 3) { + let guesses = args.map((a) => (a.isEdge ? (a.first + a.last) / 2 : 0)); + gcc = new oc.Geom2dGcc_Circ2d3Tan_1(args[0].q, args[1].q, args[2].q, + _GCC_TOLERANCE, guesses[0], guesses[1], guesses[2]); + tangency1 = (i) => oc.OCJS_Out.Circ2d3Tan_Tangency1(gcc, i); + tangency2 = (i) => oc.OCJS_Out.Circ2d3Tan_Tangency2(gcc, i); + tangency3 = (i) => oc.OCJS_Out.Circ2d3Tan_Tangency3(gcc, i); + } else { + gcc = new oc.Geom2dGcc_Circ2d2TanRad_1(args[0].q, args[1].q, + opts.radius, _GCC_TOLERANCE); + tangency1 = (i) => oc.OCJS_Out.Circ2d2TanRad_Tangency1(gcc, i); + tangency2 = (i) => oc.OCJS_Out.Circ2d2TanRad_Tangency2(gcc, i); + } + if (!gcc.IsDone() || gcc.NbSolutions() === 0) { + throw new Error('ConstrainedArcs: unable to find a tangent arc'); + } + for (let i = 1; i <= gcc.NbSolutions(); i++) { + let circ = gcc.ThisSolution(i); + let t1 = tangency1(i); + if (!_gccParamInTrim(args[0], t1.parArg)) { continue; } + let t2 = tangency2(i); + if (!_gccParamInTrim(args[1], t2.parArg)) { continue; } + let params = [t1.parSol, t2.parSol]; + if (tangency3) { + let t3 = tangency3(i); + if (!_gccParamInTrim(args[2], t3.parArg)) { continue; } + params.push(t3.parSol); + } + if (tangency3 || opts.centerOn) { params = _gccEnclosedOffset(specs, circ, params); } + _gccPickSagitta(_gccTwoArcs(circ, params[0], params[1]), sagitta, out); + } + return out; +} + +/** Lines constrained by tangency (build123d Edge.make_constrained_lines / + * ConstrainedLines). Two forms, matching upstream: + * specs = [tangency, tangency|point] -> Geom2dGcc_Lin2d2Tan + * specs = [tangency], opts = {angle, axis} -> Geom2dGcc_Lin2dTanObl + * Returns an array of TopoDS_Edge (not added to the scene). */ +function ConstrainedLines2D(specs, opts) { + const oc = self.oc; + opts = opts || {}; + let a1 = _gccArg(specs[0]); + let out = []; + + if (opts.axis) { + // tangent to one curve at a fixed orientation, trimmed between the + // tangency point and the reference axis + let pos = opts.axis.position, dir = opts.axis.direction; + let refLin = new oc.gp_Lin2d_3(new oc.gp_Pnt2d_3(pos[0], pos[1]), + new oc.gp_Dir2d_5(dir[0], dir[1])); + let thetaAbs = Math.atan2(dir[1], dir[0]) + opts.angle; + let gcc = new oc.Geom2dGcc_Lin2dTanObl_1(a1.q, refLin, _GCC_TOLERANCE, opts.angle); + for (let i = 1; i <= gcc.NbSolutions(); i++) { + let t = oc.OCJS_Out.Lin2dTanObl_Tangency1(gcc, i); + // Intersection of the solution line with the reference axis. Upstream + // runs IntAna2d_AnaIntersection here and notes Intersection2() is not + // reliable; IntAna2d_IntPoint is not registered in this build, so the + // same two-line solve is done in closed form (one linear system, no + // tolerance of its own). + let cx = Math.cos(thetaAbs), cy = Math.sin(thetaAbs); + let den = cx * dir[1] - cy * dir[0]; + if (Math.abs(den) < 1e-15) { continue; } + let s = ((pos[0] - t.x) * dir[1] - (pos[1] - t.y) * dir[0]) / den; + let px = t.x + cx * s, py = t.y + cy * s; + if (!(Math.hypot(px - t.x, py - t.y) >= _GCC_TOLERANCE)) { continue; } + let edge = _gccEdgeFromLine([t.x, t.y], [px, py]); + if (edge) { out.push(edge); } + } + return out; + } + + let a2 = _gccArg(specs[1]); + let gcc = a2.isEdge + ? new oc.Geom2dGcc_Lin2d2Tan_1(a1.q, a2.q, _GCC_TOLERANCE) + : new oc.Geom2dGcc_Lin2d2Tan_2(a1.q, a2.pnt2d, _GCC_TOLERANCE); + if (!gcc.IsDone() || gcc.NbSolutions() === 0) { + throw new Error('ConstrainedLines: unable to find a common tangent line'); + } + for (let i = 1; i <= gcc.NbSolutions(); i++) { + // The two contact points come from intersecting the solution line with + // each argument curve (upstream: Tangency1/Tangency2 can index the same + // line differently, so it uses Geom2dAPI_InterCurveCurve). + let lin = new oc.Handle_Geom2d_Curve_2( + new oc.Geom2d_Line_2(gcc.ThisSolution(i))); + let inter1 = new oc.Geom2dAPI_InterCurveCurve_2(lin, a1.curve2d, _GCC_TOLERANCE); + if (inter1.NbPoints() < 1) { continue; } + let p1 = inter1.Point(1); + let p2; + if (a2.isEdge) { + let inter2 = new oc.Geom2dAPI_InterCurveCurve_2(lin, a2.curve2d, _GCC_TOLERANCE); + if (inter2.NbPoints() < 1) { continue; } + p2 = inter2.Point(1); + } else { + p2 = a2.pnt2d; + } + let sep = p1.Distance(p2); + if (!(sep >= _GCC_TOLERANCE)) { continue; } + let edge = _gccEdgeFromLine([p1.X(), p1.Y()], [p2.X(), p2.Y()]); + if (edge) { out.push(edge); } + } + return out; +} + +/** Minimal distance from a point to an edge (build123d's Shape.distance_to for + * the Edge/point case), measured on the edge's own parameter range. */ +function _edgeDistanceToPoint(edge, point) { + let e = _asEdge(edge); + let curve = new self.oc.BRepAdaptor_Curve_2(e); + let first = curve.FirstParameter(), last = curve.LastParameter(); + let pnt = new self.oc.gp_Pnt_3(point[0], point[1], point[2]); + let best = Infinity; + for (let u of [first, last]) { + let p = new self.oc.gp_Pnt_1(); + curve.D0(u, p); + best = Math.min(best, p.Distance(pnt)); + } + let handle = self.oc.BRep_Tool.Curve_2(e, { current: 0 }, { current: 0 }); + let projector = new self.oc.GeomAPI_ProjectPointOnCurve_3(pnt, handle, first, last); + if (projector.NbPoints() > 0) { best = Math.min(best, projector.LowerDistance()); } + return best; +} + +/** The normalized ARC-LENGTH position (0..1, measured along the underlying + * curve from its first parameter) of the point on an edge closest to `point` + * — build123d's Edge.param_at_point, same three-stage strategy: endpoint + * snap, GeomAPI_ProjectPointOnCurve validated by re-evaluation, then a + * sampled + golden-section search. Returns -1 when the point is not on the + * edge. */ +function _edgeParamAtPoint(edge, point) { + let e = _asEdge(edge); + let curve = new self.oc.BRepAdaptor_Curve_2(e); + let first = curve.FirstParameter(), last = curve.LastParameter(); + let total = self.oc.GCPnts_AbscissaPoint.Length_5(curve, first, last); + if (!(total > 0)) { return 0.0; } + let pnt = new self.oc.gp_Pnt_3(point[0], point[1], point[2]); + let pointAtParam = (u) => { + let p = new self.oc.gp_Pnt_1(); + curve.D0(Math.min(last, Math.max(first, u)), p); + return p; + }; + let distAtFraction = (u) => { + let p = new self.oc.gp_Pnt_1(); + curve.D0(_edgeParamAtFraction(curve, u), p); + return p.Distance(pnt); + }; + // 1. endpoint snap (a vertex of the edge) + if (pointAtParam(first).Distance(pnt) <= 1e-6) { return 0.0; } + if (pointAtParam(last).Distance(pnt) <= 1e-6) { return 1.0; } + // 2. projection onto the curve, wrapped back into range when periodic + let handle = self.oc.BRep_Tool.Curve_2(e, { current: 0 }, { current: 0 }); + let projector = new self.oc.GeomAPI_ProjectPointOnCurve_2(pnt, handle); + if (projector.NbPoints() > 0) { + let param = projector.LowerDistanceParameter(); + if (curve.IsPeriodic()) { + let period = curve.Period(); + param = first + (((param - first) % period) + period) % period; + } + if (param >= first - 1e-9 && param <= last + 1e-9) { + param = Math.min(last, Math.max(first, param)); + let u = self.oc.GCPnts_AbscissaPoint.Length_5(curve, first, param) / total; + if (distAtFraction(u) <= 1e-6) { return u; } + } + } + // 3. sampled scan + golden-section refinement of the distance minimum + let samples = 512, bestU = 0.0, bestD = Infinity; + for (let i = 0; i <= samples; i++) { + let u = i / samples, d = distAtFraction(u); + if (d < bestD) { bestD = d; bestU = u; } + } + let invPhi = 0.6180339887498949; + let lo = Math.max(0, bestU - 1 / samples), hi = Math.min(1, bestU + 1 / samples); + let x1 = hi - invPhi * (hi - lo), x2 = lo + invPhi * (hi - lo); + let f1 = distAtFraction(x1), f2 = distAtFraction(x2); + for (let i = 0; i < 60; i++) { + if (f1 <= f2) { hi = x2; x2 = x1; f2 = f1; x1 = hi - invPhi * (hi - lo); f1 = distAtFraction(x1); } + else { lo = x1; x1 = x2; f1 = f2; x2 = lo + invPhi * (hi - lo); f2 = distAtFraction(x2); } + } + let u = f1 <= f2 ? x1 : x2; + // -1 (not null) for "not on this edge": a JS null crosses into Brython as + // NullType, which cannot be compared or tested with `is None` + return distAtFraction(u) <= 1e-6 ? u : -1.0; +} + +function _bsplineDataOf(curve) { + let data = { + deg: curve.Degree(), periodic: curve.IsPeriodic(), + knots: [], mults: [], poles: [], weights: null, + }; + for (let i = 1; i <= curve.NbPoles(); i++) { + let p = curve.Pole(i); + data.poles.push([p.X(), p.Y(), p.Z()]); + } + for (let i = 1; i <= curve.NbKnots(); i++) { + data.knots.push(curve.Knot(i)); + data.mults.push(curve.Multiplicity(i)); + } + if (curve.IsRational()) { + data.weights = []; + for (let i = 1; i <= curve.NbPoles(); i++) { data.weights.push(curve.Weight(i)); } + } + return data; +} + +function _bsplineFromData(data) { + let count = data.poles.length; + let poles = new self.oc.TColgp_Array1OfPnt_2(1, count); + for (let i = 0; i < count; i++) { + poles.SetValue(i + 1, new self.oc.gp_Pnt_3( + data.poles[i][0], data.poles[i][1], data.poles[i][2])); + } + let knots = new self.oc.TColStd_Array1OfReal_2(1, data.knots.length); + for (let i = 0; i < data.knots.length; i++) { knots.SetValue(i + 1, data.knots[i]); } + let mults = new self.oc.TColStd_Array1OfInteger_2(1, data.mults.length); + for (let i = 0; i < data.mults.length; i++) { mults.SetValue(i + 1, data.mults[i]); } + if (data.weights) { + let weights = new self.oc.TColStd_Array1OfReal_2(1, count); + for (let i = 0; i < count; i++) { weights.SetValue(i + 1, data.weights[i]); } + return new self.oc.Geom_BSplineCurve_2( + poles, weights, knots, mults, data.deg, !!data.periodic, false); + } + return new self.oc.Geom_BSplineCurve_1(poles, knots, mults, data.deg, !!data.periodic); +} + +/** Exact rational-quadratic NURBS of a conic arc: built in the unit-circle + * parameter plane (where the classic cos(alpha/2) construction is exact) and + * mapped through the conic's own affine frame, which is exact for ellipses + * too because an ellipse IS an affine image of a circle. */ +function _conicArcData(origin, xDir, yDir, radiusX, radiusY, u0, u1) { + let span = u1 - u0; + let segments = Math.max(1, Math.ceil(Math.abs(span) / (Math.PI / 2) - 1e-9)); + let step = span / segments; + let half = Math.cos(step / 2); + let map = (px, py) => [0, 1, 2].map( + (k) => origin[k] + px * radiusX * xDir[k] + py * radiusY * yDir[k]); + let poles = [map(Math.cos(u0), Math.sin(u0))], weights = [1]; + for (let i = 0; i < segments; i++) { + let a0 = u0 + i * step, a1 = a0 + step, mid = 0.5 * (a0 + a1); + poles.push(map(Math.cos(mid) / half, Math.sin(mid) / half)); + weights.push(half); + poles.push(map(Math.cos(a1), Math.sin(a1))); + weights.push(1); + } + let knots = [], mults = []; + for (let i = 0; i <= segments; i++) { + knots.push(u0 + i * step); + mults.push(i === 0 || i === segments ? 3 : 2); + } + return { deg: 2, periodic: false, knots, mults, poles, weights }; +} + +/** An edge's curve as B-spline pole/knot data, trimmed to the edge's range and + * oriented the way the EDGE runs (build123d's `bspline_of` inside + * _concatenate_edges: CurveToBSplineCurve of a Geom_TrimmedCurve, reversed + * for a REVERSED edge). This wasm build cannot bind + * Convert_ParameterisationType, so GeomConvert is unavailable and the + * analytic curve types are converted here instead — exactly, not by + * approximation. */ +function _edgeBSplineData(edge) { + let e = _asEdge(edge); + let curve = new self.oc.BRepAdaptor_Curve_2(e); + let type = curve.GetType(), types = self.oc.GeomAbs_CurveType; + let first = curve.FirstParameter(), last = curve.LastParameter(); + let bspline; + if (type === types.GeomAbs_Line) { + let p0 = new self.oc.gp_Pnt_1(), p1 = new self.oc.gp_Pnt_1(); + curve.D0(first, p0); + curve.D0(last, p1); + bspline = _bsplineFromData({ + deg: 1, periodic: false, knots: [first, last], mults: [2, 2], weights: null, + poles: [[p0.X(), p0.Y(), p0.Z()], [p1.X(), p1.Y(), p1.Z()]], + }); + } else if (type === types.GeomAbs_Circle || type === types.GeomAbs_Ellipse) { + let isCircle = type === types.GeomAbs_Circle; + let conic = isCircle ? curve.Circle() : curve.Ellipse(); + let frame = conic.Position(); + let origin = frame.Location(), xDir = frame.XDirection(), yDir = frame.YDirection(); + bspline = _bsplineFromData(_conicArcData( + [origin.X(), origin.Y(), origin.Z()], + [xDir.X(), xDir.Y(), xDir.Z()], + [yDir.X(), yDir.Y(), yDir.Z()], + isCircle ? conic.Radius() : conic.MajorRadius(), + isCircle ? conic.Radius() : conic.MinorRadius(), + first, last)); + } else if (type === types.GeomAbs_BezierCurve) { + let bezier = curve.Bezier().get(); + let poles = [], weights = bezier.IsRational() ? [] : null; + for (let i = 1; i <= bezier.NbPoles(); i++) { + let p = bezier.Pole(i); + poles.push([p.X(), p.Y(), p.Z()]); + if (weights) { weights.push(bezier.Weight(i)); } + } + let deg = bezier.Degree(); + bspline = _bsplineFromData({ + deg, periodic: false, knots: [0, 1], mults: [deg + 1, deg + 1], poles, weights, + }); + if (first > 0 || last < 1) { bspline.Segment(first, last, 1e-9); } + } else if (type === types.GeomAbs_BSplineCurve) { + // rebuild from data first: Segment() mutates in place and the adaptor's + // handle points at the edge's own basis curve + bspline = _bsplineFromData(_bsplineDataOf(curve.BSpline().get())); + bspline.Segment(first, last, 1e-9); + } else { + throw new Error( + "build123d-lite cannot canonicalize a closed shape containing a " + + "hyperbola, parabola or offset curve (no exact B-spline form)"); + } + if (e.Orientation_1() === self.oc.TopAbs_Orientation.TopAbs_REVERSED) { + bspline.Reverse(); + } + return _bsplineDataOf(bspline); +} + +/** ONE edge whose curve is the exact concatenation of an ordered, head-to-tail + * edge chain (build123d's _concatenate_edges, which uses + * GeomConvert_CompCurveToBSplineCurve — unavailable here, see + * _edgeBSplineData). Used to give a re-seamed closed loop an unambiguous + * start point: a closed TopoDS_Wire carries no distinguished first edge, + * while an Edge's curve parametrization does. */ +function ConcatEdgesToEdge(edges) { + let pieces = edges.map(_edgeBSplineData); + let degree = pieces.reduce((d, piece) => Math.max(d, piece.deg), 1); + pieces = pieces.map((piece) => { + if (piece.deg === degree) { return piece; } + let raised = _bsplineFromData(piece); + raised.IncreaseDegree(degree); + return _bsplineDataOf(raised); + }); + let joined = pieces[0]; + for (let i = 1; i < pieces.length; i++) { + let next = pieces[i]; + let shift = joined.knots[joined.knots.length - 1] - next.knots[0]; + let rational = !!(joined.weights || next.weights); + let weightsA = joined.weights || joined.poles.map(() => 1); + let weightsB = next.weights || next.poles.map(() => 1); + // the junction pole is shared, so rescale the incoming weights to match + let scale = weightsA[weightsA.length - 1] / weightsB[0]; + joined = { + deg: degree, + periodic: false, + poles: joined.poles.concat(next.poles.slice(1)), + weights: rational + ? weightsA.concat(weightsB.slice(1).map((weight) => weight * scale)) + : null, + knots: joined.knots.concat(next.knots.slice(1).map((knot) => knot + shift)), + // C0 junction: multiplicity == degree instead of the clamped degree + 1 + mults: joined.mults.slice(0, -1).concat([degree]).concat(next.mults.slice(1)), + }; + } + let handle = new self.oc.Handle_Geom_Curve_2(_bsplineFromData(joined)); + let out = new self.oc.BRepBuilderAPI_MakeEdge_24(handle).Edge(); + out.hash = self.oc.OCJS.HashCode(out, 100000000); + self.sceneShapes.push(out); + return out; +} + +/** An edge's 3D curve projected onto a face's surface (GeomProjLib::Project — + * build123d's "snap_to_face" step of _wrap_edge). */ +function ProjectEdgeOnFace(edge, face) { + let e = _asEdge(edge); + let first = { current: 0 }, last = { current: 0 }; + let curve = self.oc.BRep_Tool.Curve_2(e, first, last); + let surf = self.oc.BRep_Tool.Surface_2(_asFace(face)); + let projected = self.oc.GeomProjLib.Project(curve, surf); + if (!projected) { return null; } + let out = new self.oc.BRepBuilderAPI_MakeEdge_24(projected).Edge(); + out.hash = self.oc.OCJS.HashCode(out, 100000000); + self.sceneShapes.push(out); + return out; +} + +/** Extend a B-spline edge past one of its ends by `factor` of its length and + * snap the result back onto a face's surface — build123d's + * Edge._extend_spline, used to make the first and last wrapped edges of a + * closed wire cross so they can be trimmed to a clean junction. */ +function ExtendSplineOnFace(edge, atStart, face, factor) { + let e = _asEdge(edge); + let adaptor = new self.oc.BRepAdaptor_Curve_2(e); + let bspl = adaptor.BSpline().get(); + let poles = []; + for (let i = 1; i <= bspl.NbPoles(); i++) { + let p = bspl.Pole(i); + poles.push([p.X(), p.Y(), p.Z()]); + } + let pointAt = (f) => { + let pnt = new self.oc.gp_Pnt_1(); + adaptor.D0(_edgeParam(e, f), pnt); + return [pnt.X(), pnt.Y(), pnt.Z()]; + }; + let tangentAt = (f) => { + let pnt = new self.oc.gp_Pnt_1(), vec = new self.oc.gp_Vec_1(); + adaptor.D1(_edgeParam(e, f), pnt, vec); + let m = vec.Magnitude() || 1; + return [vec.X() / m, vec.Y() / m, vec.Z() / m]; + }; + let ends = atStart ? [-factor, 1] : [0, 1 + factor]; + if (atStart) { poles.unshift(pointAt(-factor)); } else { poles.push(pointAt(1 + factor)); } + let tangents = [tangentAt(ends[0]), tangentAt(ends[1])]; + let extended = InterpolatedEdge(poles, tangents, false, true); + return ProjectEdgeOnFace(extended, face); +} + +/** A single edge exactly interpolating the given points (GeomAPI_Interpolate, + * build123d's Edge.make_spline). */ +function InterpolatedEdge(points, tangents, periodic, scale) { + let wire = WireFromSegments([['interp', points.map((p) => [p[0], p[1], p[2]]), + [tangents && tangents.length ? tangents : null, !!periodic, + scale === undefined ? true : !!scale]]], true); + let edges = []; + ForEachEdge(wire, (i, e) => { edges.push(e); }); + if (edges.length !== 1) { + throw new Error('InterpolatedEdge: expected one edge, got ' + edges.length); + } + return edges[0]; +} + +/** Curve parameters of the closest extremum between two edges' curves + * (GeomAPI_ExtremaCurveCurve, build123d's first/last wrapped-edge junction). + * Returns [paramOnFirst, paramOnSecond] or null. */ +function ExtremaEdgeParams(edgeA, edgeB) { + let fa = { current: 0 }, la = { current: 0 }, fb = { current: 0 }, lb = { current: 0 }; + let ca = self.oc.BRep_Tool.Curve_2(_asEdge(edgeA), fa, la); + let cb = self.oc.BRep_Tool.Curve_2(_asEdge(edgeB), fb, lb); + let ext = new self.oc.GeomAPI_ExtremaCurveCurve_2(ca, cb); + if (ext.NbExtrema() < 1) { return null; } + let u = { current: 0 }, v = { current: 0 }; + ext.LowerDistanceParameters(u, v); + return [u.current, v.current]; +} + +/** A potentially NON-planar face bounded by the given edges, optionally + * refined by interior points and holed by interior wires — the exact + * BRepOffsetAPI_MakeFilling construction of build123d's Face.make_surface. */ +function FillingFace(edges, points, interiorWires) { + let filling = new self.oc.BRepOffsetAPI_MakeFilling( + 3, 15, 2, false, 0.00001, 0.0001, 0.01, 0.1, 8, 9); + let C0 = self.oc.GeomAbs_Shape.GeomAbs_C0; + for (let i = 0; i < edges.length; i++) { + filling.Add_1(_asEdge(edges[i]), C0, true); + } + filling.Build(new self.oc.Message_ProgressRange_1()); + if (!filling.IsDone()) { console.error("FillingFace: surface filling failed"); return null; } + if (points && points.length) { + for (let i = 0; i < points.length; i++) { + filling.Add_4(new self.oc.gp_Pnt_3(points[i][0], points[i][1], points[i][2])); + } + filling.Build(new self.oc.Message_ProgressRange_1()); + if (!filling.IsDone()) { + console.error("FillingFace: surface filling with interior points failed"); + return null; + } + } + let face = self.oc.TopoDS_Cast.Face_1(filling.Shape()); + if (interiorWires && interiorWires.length) { + face = _asFace(FaceWithHoles(_faceOuterWire(face), interiorWires.map(_asWire))); + } + let fixer = new self.oc.ShapeFix_Shape_2(face); + fixer.Perform(new self.oc.Message_ProgressRange_1()); + face = _asFace(fixer.Shape()); + face.hash = self.oc.OCJS.HashCode(face, 100000000); + self.sceneShapes.push(face); + return face; +} + +/** A wire from edges, reordered and gap-closed with ShapeFix_Wire (build123d + * closes the wrapped-wire junction this way). */ +function WireFromEdgesFixed(edges, precision) { + let mkWire = new self.oc.BRepBuilderAPI_MakeWire_1(); + // build123d adds every edge at once (TopTools_ListOfShape), which lets the + // builder connect them in any order instead of demanding that each new edge + // touch the wire built so far + let list = new self.oc.TopTools_ListOfShape(); + for (let i = 0; i < edges.length; i++) { list.Append(_asEdge(edges[i])); } + mkWire.Add_3(list); + let raw; + if (mkWire.IsDone()) { + raw = mkWire.Wire(); + } else { + // The gaps between independently projected wrapped edges can exceed + // MakeWire's connectivity tolerance. Assemble the wire directly and let + // ShapeFix close the gaps — which is exactly what build123d's + // SetPrecision(2 * closing_error) + FixConnected pass is there for. + let builder = new self.oc.BRep_Builder(); + raw = new self.oc.TopoDS_Wire(); + builder.MakeWire(raw); + for (let i = 0; i < edges.length; i++) { builder.Add(raw, _asEdge(edges[i])); } + } + let fixer = new self.oc.ShapeFix_Wire_1(); + if (precision > 0) { fixer.SetPrecision(precision); } + fixer.Load_1(raw); + fixer.FixReorder_1(false); + fixer.FixConnected_1(precision > 0 ? precision : 1e-7); + let wire = fixer.Wire(); + wire.hash = self.oc.OCJS.HashCode(wire, 100000000); + self.sceneShapes.push(wire); + return wire; +} + +/** Whether a wire is topologically closed (BRep_Tool::IsClosed). */ +function _wireIsClosed(wire) { + return !!self.oc.BRep_Tool.IsClosed_1(_asWire(wire)); +} + +/** A wire's edges in CONNECTION order (BRepTools_WireExplorer — build123d's + * Wire.order_edges); ForEachEdge follows TopExp's storage order instead. */ +/** A wire built from edges IN THE GIVEN ORDER, each keeping its own + * orientation (BRepBuilderAPI_MakeWire::Add per edge). build123d's + * Wire.fillet_2d rebuilds a filleted wire this way, and the traversal order + * it produces matters downstream: BRepOffsetAPI_MakeOffset fails on the same + * edges assembled order-agnostically. Falls back to WireFromEdgesFixed. */ +function WireFromOrderedEdges(edges) { + let mkWire = new self.oc.BRepBuilderAPI_MakeWire_1(); + for (let i = 0; i < edges.length; i++) { + mkWire.Add_1(_asEdge(edges[i])); + if (!mkWire.IsDone()) { return WireFromEdgesFixed(edges); } + } + let wire = mkWire.Wire(); + if (!wire || wire.IsNull()) { return WireFromEdgesFixed(edges); } + wire.hash = self.oc.OCJS.HashCode(wire, 100000000); + return wire; +} + +function OrderedEdges(wire) { + let out = []; + let exp = new self.oc.BRepTools_WireExplorer_2(_asWire(wire)); + for (; exp.More(); exp.Next()) { + let e = self.oc.TopoDS_Cast.Edge_1(exp.Current()); + if (e.hash === undefined) { e.hash = self.oc.OCJS.HashCode(e, 100000000); } + out.push(e); + } + return out; +} + +/** The single TopoDS_Face of a one-face shape (shell/compound), or the shape + * unchanged when it holds none or several. Extruding a one-edge wire yields a + * SHELL here where build123d's Face.extrude casts straight to TopoDS_Face, + * and downstream OCCT algorithms (BRepProj_Projection above all) treat a + * shell differently from the face inside it. */ +function AsSingleFace(shape, keepShape) { + if (shape.ShapeType().value === 4) { return shape; } + let found = []; + ForEachFace(shape, (i, f) => { found.push(f); }); + if (found.length !== 1) { return shape; } + let face = found[0]; + if (face.hash === undefined) { face.hash = self.oc.OCJS.HashCode(face, 100000000); } + if (!keepShape) { self.sceneShapes = self.Remove(self.sceneShapes, shape); } + self.sceneShapes.push(face); + return face; +} + +/** TopoDS_Wire view of a wire, or a wire built from a shape's edges. */ +function _asWire(shape) { + if (shape.ShapeType().value === 5) { return self.oc.TopoDS_Cast.Wire_1(shape); } + let mkWire = new self.oc.BRepBuilderAPI_MakeWire_1(); + ForEachEdge(shape, (i, e) => { mkWire.Add_1(e); }); + return mkWire.Wire(); +} + +/** Project a wire (or a shape's edges) onto a target shape, either along a + * direction or from a conical `center` point (pass one, null the other) — + * BRepProj_Projection, exactly build123d's Wire/Edge.project_to_shape. + * Results keep the input's orientation and, when the projection lands on + * more than one surface, are sorted nearest-first along the projection + * (wires BEHIND the profile are dropped for directional projection, like + * build123d). + * COMPROMISE(projection-sort): build123d sorts by Wire.center() (the + * position at half arc length); this sorts by center of mass, which orders + * front/back hits identically but can differ for exotic wires. */ +function ProjectWireOnShape(profile, target, direction, center) { + let wire = _asWire(profile); + let proj = direction + ? new self.oc.BRepProj_Projection_1(wire, target, + new self.oc.gp_Dir_5(direction[0], direction[1], direction[2])) + : new self.oc.BRepProj_Projection_2(wire, target, + new self.oc.gp_Pnt_3(center[0], center[1], center[2])); + let wanted = wire.Orientation_1(); + let found = []; + for (; proj.More(); proj.Next()) { + let pw = proj.Current(); + if (pw.Orientation_1() !== wanted) { pw = self.oc.TopoDS_Cast.Wire_1(pw.Reversed()); } + // build123d cleans the projected wires "to remove cases where projection + // artificially split edges". + // COMPROMISE(projected-edge-split): this kernel splits more eagerly than + // OCP 7.x's — a single projected arc comes back as two BSpline edges + // meeting where the curve grazes the surface boundary — so unification + // has to CONCATENATE B-splines (build123d's clean() leaves that flag off) + // to get back to build123d's one-edge result. Same curve, same length. + pw = UnifyWire(pw, true); + pw.hash = self.oc.OCJS.HashCode(pw, 100000000); + found.push(pw); + } + if (found.length > 1) { + let c0 = _shapeLinearCenter(wire); + let keyed = []; + for (let i = 0; i < found.length; i++) { + let c = _shapeLinearCenter(found[i]); + let d = [c[0] - c0[0], c[1] - c0[1], c[2] - c0[2]]; + let len = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); + if (direction) { + let dot = d[0] * direction[0] + d[1] * direction[1] + d[2] * direction[2]; + if (dot < 0) { continue; } // behind the profile: not a projection hit + } + keyed.push([len, found[i]]); + } + keyed.sort((a, b) => a[0] - b[0]); + found = keyed.map((k) => k[1]); + } + for (let i = 0; i < found.length; i++) { self.sceneShapes.push(found[i]); } + return found; +} + +function _shapeLinearCenter(shape) { + let props = new self.oc.GProp_GProps_1(); + self.oc.BRepGProp.LinearProperties(shape, props, false, false); + let c = props.CentreOfMass(); + return [c.X(), c.Y(), c.Z()]; +} + +/** Reverse ANY shape's topological orientation (TopoDS_Shape::Complemented — + * what build123d's Mixin2D.__neg__ does), re-typing the result when it is a + * face so downstream face APIs keep working. */ +function ReverseShape(shape, keepShape) { + let reversed = shape.Complemented(); + if (reversed.ShapeType().value === 4) { + reversed = self.oc.TopoDS_Cast.Face_1(reversed); + } + reversed.hash = self.oc.OCJS.HashCode(reversed, 100000000); + if (!keepShape) { self.sceneShapes = self.Remove(self.sceneShapes, shape); } + self.sceneShapes.push(reversed); + return reversed; +} + +/** Uniform scale about a center point, BAKED into the geometry + * (BRepBuilderAPI_Transform + gp_Trsf::SetScale — what build123d's + * Shape.scale does). The legacy Scale() encodes the factor in a + * TopLoc_Location, which downstream OCCT algorithms handle + * inconsistently (TopLoc is only specified for isometries). */ +function ScaleUniform(shape, factor, center, keepShape) { + if (!shape || shape.IsNull()) { console.error("ScaleUniform: input shape is null!"); return shape; } + if (!center) { center = [0, 0, 0]; } + let scaled = self.CacheOp(arguments, "ScaleUniform", () => { + let trsf = new self.oc.gp_Trsf_1(); + trsf.SetScale(new self.oc.gp_Pnt_3(center[0], center[1], center[2]), factor); + let op = new self.oc.BRepBuilderAPI_Transform_2(shape, trsf, true, false); + op.Build(new self.oc.Message_ProgressRange_1()); + return op.Shape(); + }); + if (!keepShape) { self.sceneShapes = self.Remove(self.sceneShapes, shape); } + self.sceneShapes.push(scaled); + return scaled; +} + +/** Non-uniform scale via gp_GTrsf + BRepBuilderAPI_GTransform (converts + * analytic surfaces to BSplines where needed — same as build123d). */ +function ScaleXYZ(factors, shape, keepShape) { + if (!shape || shape.IsNull()) { console.error("ScaleXYZ: input shape is null!"); return shape; } + let scaled = self.CacheOp(arguments, "ScaleXYZ", () => { + let gtrsf = new self.oc.gp_GTrsf_1(); + gtrsf.SetValue(1, 1, factors[0]); + gtrsf.SetValue(2, 2, factors[1]); + gtrsf.SetValue(3, 3, factors[2]); + let op = new self.oc.BRepBuilderAPI_GTransform_2(shape, gtrsf, true); + op.Build(new self.oc.Message_ProgressRange_1()); + return op.Shape(); + }); + if (!keepShape) { self.sceneShapes = self.Remove(self.sceneShapes, shape); } + self.sceneShapes.push(scaled); + return scaled; +} + +/** Hidden-line-removal projection: project the shape onto a viewport with + * the given view direction, returning [visibleEdges, hiddenEdges] as two + * compounds (HLRBRep — what build123d's project_to_viewport uses). */ +function HLRProject(shape, viewDir, keepShape) { + let result = self.CacheOp(arguments, "HLRProject", () => { + let hlr = new self.oc.HLRBRep_Algo_1(); + hlr.Add_2(shape, 0); + let projDir = new self.oc.gp_Dir_5(viewDir[0], viewDir[1], viewDir[2]); + let ax2 = new self.oc.gp_Ax2_4(new self.oc.gp_Pnt_3(0, 0, 0), projDir); + let projector = new self.oc.HLRAlgo_Projector_2(ax2); + hlr.Projector_1(projector); + hlr.Update(); + hlr.Hide_1(); + let toShape = new self.oc.HLRBRep_HLRToShape(new self.oc.Handle_HLRBRep_Algo_2(hlr)); + let visible = []; + let hidden = []; + let grab = (s, into) => { if (s && !s.IsNull()) { into.push(s); } }; + grab(toShape.VCompound_1(), visible); + grab(toShape.Rg1LineVCompound_1(), visible); + grab(toShape.OutLineVCompound_1(), visible); + grab(toShape.HCompound_1(), hidden); + grab(toShape.Rg1LineHCompound_1(), hidden); + grab(toShape.OutLineHCompound_1(), hidden); + let mk = (list) => { + let builder = new self.oc.BRep_Builder(); + let compound = new self.oc.TopoDS_Compound(); + builder.MakeCompound(compound); + for (let i = 0; i < list.length; i++) { builder.Add(compound, list[i]); } + self.oc.BRepLib.BuildCurves3d_2(compound); + compound.hash = self.oc.OCJS.HashCode(compound, 100000000); + return compound; + }; + return [mk(visible), mk(hidden)]; + }); + return result; +} + +/** Surface through a 2D grid of points, returned as a face. build123d uses + * GeomAPI_PointsToBSplineSurface, whose Surface() accessor returns + * Handle_Geom_BSplineSurface — a type this WASM build does not bind — so + * instead each row (fixed V, varying U) is interpolated exactly + * (GeomAPI_Interpolate, 1e-6) and the rows are skinned with + * BRepOffsetAPI_ThruSections (non-solid, 1e-6). Both constructions + * approximate the same grid to well below harness tolerance. + * `points` outer index = V, inner = U, like build123d. */ +function SurfaceFromPoints(points, tol, degMin, degMax, smoothing) { + let curFace = self.CacheOp(arguments, "SurfaceFromPoints", () => { + // The exact calls build123d's Face.make_surface_from_array_of_points + // makes: GeomAPI_PointsToBSplineSurface(points, DegMin, DegMax, + // GeomAbs_C2, Tol3D) — a 2-D least-squares fit — then + // BRepBuilderAPI_MakeFace(surface, Precision::Confusion()). + // With smoothing weights: the variational (Weight1..3) constructor. + let arr = new self.oc.TColgp_Array2OfPnt_2(1, points.length, 1, points[0].length); + for (let i = 0; i < points.length; i++) { + let row = points[i]; + for (let j = 0; j < row.length; j++) { + let p = row[j]; + arr.SetValue(i + 1, j + 1, new self.oc.gp_Pnt_3(p[0], p[1], p.length > 2 ? p[2] : 0)); + } + } + let alg; + if (smoothing && smoothing.length === 3) { + alg = new self.oc.GeomAPI_PointsToBSplineSurface_4( + arr, smoothing[0], smoothing[1], smoothing[2], degMax, + self.oc.GeomAbs_Shape.GeomAbs_C2, tol); + } else { + alg = new self.oc.GeomAPI_PointsToBSplineSurface_2( + arr, degMin, degMax, self.oc.GeomAbs_Shape.GeomAbs_C2, tol); + } + if (!alg.IsDone()) { + console.error("SurfaceFromPoints: B-spline surface approximation failed"); + return null; + } + let surfaceHandle = alg.Surface().AsGeomSurface(); + let face = new self.oc.BRepBuilderAPI_MakeFace_8(surfaceHandle, 1.0e-7).Face(); + face.hash = self.oc.OCJS.HashCode(face, 100000000); + return face; + }); + self.sceneShapes.push(curFace); + return curFace; +} + +/** Sweep profile wires along a spine wire with BRepOffsetAPI_MakePipeShell — + * the exact calls build123d's Solid.sweep/sweep_multi make: + * - trihedron: SetMode(isFrenet) (false = corrected Frenet), unless a + * constant `binormal` vector ([x,y,z], build123d normal=) or an + * auxiliary spine wire (`auxSpine`, build123d binormal=) is given + * - transition: 'transformed' | 'round' | 'right' (ignored when null, + * matching sweep_multi which never sets a transition mode) + * - every profile is added with Add(profile, WithContact=false, + * WithCorrection=rotate) where rotate is true only for a binormal vector + * Multiple profiles = multisection sweep (profile correspondence is OCCT's). + * Profiles must be TopoDS_Wire; returns a solid (MakeSolid), or the raw + * shell when makeShell is true. */ +function PipeShellSweep(profileWires, spineWire, isFrenet, transition, binormal, auxSpine, auxCurvilinear, makeShell) { + let result = self.CacheOp(arguments, "PipeShellSweep", () => { + let toWire = (w) => { + // rebuild for exact Embind TopoDS_Wire typing (see Loft). The edges are + // added AS A LIST: TopExp_Explorer hands them back in storage order, and + // adding them one at a time makes BRepBuilderAPI_MakeWire silently drop + // any edge that does not touch the wire built so far (which quietly cost + // brake-formed sections a side face). + let mw = new self.oc.BRepBuilderAPI_MakeWire_1(); + let list = new self.oc.TopTools_ListOfShape(); + let exp = new self.oc.TopExp_Explorer_2(w, self.oc.TopAbs_ShapeEnum.TopAbs_EDGE, + self.oc.TopAbs_ShapeEnum.TopAbs_SHAPE); + while (exp.More()) { list.Append(self.oc.TopoDS_Cast.Edge_1(exp.Current())); exp.Next(); } + mw.Add_3(list); + return mw.Wire(); + }; + let builder = new self.oc.BRepOffsetAPI_MakePipeShell(toWire(spineWire)); + let rotate = false; + if (binormal && binormal.length) { + let ax = new self.oc.gp_Ax2_1(); + let start = _edgePointAt( + new self.oc.TopExp_Explorer_2(spineWire, self.oc.TopAbs_ShapeEnum.TopAbs_EDGE, + self.oc.TopAbs_ShapeEnum.TopAbs_SHAPE).Current(), 0.0); + ax.SetLocation(new self.oc.gp_Pnt_3(start[0], start[1], start[2])); + ax.SetDirection(new self.oc.gp_Dir_5(binormal[0], binormal[1], binormal[2])); + builder.SetMode_2(ax); + rotate = true; + } else if (auxSpine) { + // binormal wire -> CurvilinearEquivalence true (Solid._set_sweep_mode); + // extrude_linear_with_rotation's helix aux spine passes false + let curv = (auxCurvilinear === undefined || auxCurvilinear === null || auxCurvilinear === '') ? true : !!auxCurvilinear; + builder.SetMode_5(toWire(auxSpine), curv, self.oc.BRepFill_TypeOfContact.BRepFill_NoContact); + } else { + builder.SetMode_1(!!isFrenet); + } + if (transition) { + let TM = self.oc.BRepBuilderAPI_TransitionMode; + let mode = transition === 'round' ? TM.BRepBuilderAPI_RoundCorner : + transition === 'right' ? TM.BRepBuilderAPI_RightCorner : + TM.BRepBuilderAPI_Transformed; + builder.SetTransitionMode(mode); + } + for (let i = 0; i < profileWires.length; i++) { + builder.Add_1(toWire(profileWires[i]), false, rotate); + } + builder.Build(new self.oc.Message_ProgressRange_1()); + if (!makeShell) { builder.MakeSolid(); } + return builder.Shape(); + }); + self.sceneShapes.push(result); + return result; +} + +/** Planar face from an outer wire plus hole wires (build123d's + * Face(outer_wire, inner_wires)) — no booleans: MakeFace + Add(wire) with a + * ShapeFix_Face orientation pass so the holes subtract regardless of the + * input wires' winding. */ +function FaceWithHoles(outerWire, holeWires) { + let curFace = self.CacheOp(arguments, "FaceWithHoles", () => { + let toWire = (shape) => shape.ShapeType().value === 5 + ? self.oc.TopoDS_Cast.Wire_1(shape) : self.oc.TopoDS_Cast.Wire_1( + new self.oc.TopExp_Explorer_2(shape, self.oc.TopAbs_ShapeEnum.TopAbs_WIRE, + self.oc.TopAbs_ShapeEnum.TopAbs_SHAPE).Current()); + let mk = new self.oc.BRepBuilderAPI_MakeFace_15(toWire(outerWire), true); + for (let i = 0; i < holeWires.length; i++) { mk.Add(toWire(holeWires[i])); } + let fixer = new self.oc.ShapeFix_Face_2(mk.Face()); + fixer.FixOrientation_1(); + return fixer.Face(); + }); + self.sceneShapes.push(curFace); + return curFace; +} + +/** Apply a draft angle to the given faces of a solid — BRepOffsetAPI_DraftAngle + * with build123d's Solid.draft conventions (pull direction and neutral plane + * from the neutral Plane's z_dir/origin, Flag=true). */ +function DraftAngleFaces(shape, faces, angleDeg, planeOrigin, planeNormal, keepShape) { + let result = self.CacheOp(arguments, "DraftAngleFaces", () => { + let builder = new self.oc.BRepOffsetAPI_DraftAngle_2(shape); + let dir = new self.oc.gp_Dir_5(planeNormal[0], planeNormal[1], planeNormal[2]); + let pln = new self.oc.gp_Pln_3( + new self.oc.gp_Pnt_3(planeOrigin[0], planeOrigin[1], planeOrigin[2]), dir); + for (let i = 0; i < faces.length; i++) { + let f = faces[i].ShapeType().value === 4 ? self.oc.TopoDS_Cast.Face_1(faces[i]) : faces[i]; + builder.Add(f, dir, angleDeg * (Math.PI / 180), pln, true); + if (!builder.AddDone()) { + console.error("DraftAngleFaces: draft could not be added to face " + i); + return shape; + } + } + builder.Build(new self.oc.Message_ProgressRange_1()); + return builder.Shape(); + }); + if (!keepShape) { self.sceneShapes = self.Remove(self.sceneShapes, shape); } + self.sceneShapes.push(result); + return result; +} + +/** Write the shape as an STL file into the worker's Emscripten MEMFS and + * return the file's text content (ASCII) or byte length (binary) — the + * engine behind build123d-lite's Mesher/export_stl. */ +function ExportSTL(shape, filename, linearDeflection, angularDeflection, asciiFormat) { + if (!shape || shape.IsNull()) { console.error("ExportSTL: input shape is null!"); return null; } + if (!linearDeflection) { linearDeflection = 1e-3; } + if (!angularDeflection) { angularDeflection = 0.1; } + new self.oc.BRepMesh_IncrementalMesh_2(shape, linearDeflection, true, angularDeflection, true); + let writer = new self.oc.StlAPI_Writer(); + // StlAPI_Writer defaults to ASCII in OCCT; the binding exposes ASCIIMode() + // as a getter only, so we always write ASCII (fine for MEMFS round-trips) + let done = writer.Write_1(shape, "/" + filename, new self.oc.Message_ProgressRange_1()); + if (!done) { console.error("ExportSTL: STL write failed"); return null; } + let text = self.oc.FS.readFile("/" + filename, { encoding: "utf8" }); + return text; +} + +/** Group shapes into a single TopoDS_Compound (no boolean fusion). */ +function MakeCompound(shapes, keepInputs) { + let builder = new self.oc.BRep_Builder(); + let compound = new self.oc.TopoDS_Compound(); + builder.MakeCompound(compound); + for (let i = 0; i < shapes.length; i++) { builder.Add(compound, shapes[i]); } + // not CacheOp'd — give the result a stable identity for downstream CacheOps + compound.hash = self.oc.OCJS.HashCode(compound, 100000000); + if (!keepInputs) { + for (let i = 0; i < shapes.length; i++) { self.sceneShapes = self.Remove(self.sceneShapes, shapes[i]); } + } + self.sceneShapes.push(compound); + return compound; } function _dot(a, b) { @@ -1159,6 +3125,10 @@ class EdgeSelector { constructor(shape) { this._entries = []; ForEachEdge(shape, (index, edge) => { + // Sub-shapes carry no .hash, so CacheOp's ptr-stripping would hash any + // two of them identically ("{}") — give each a stable identity so ops + // that receive raw edges/faces are cached correctly. + if (edge.hash === undefined) { edge.hash = self.oc.OCJS.HashCode(edge, 100000000); } this._entries.push({ index, edge }); }); } @@ -1343,6 +3313,8 @@ class FaceSelector { constructor(shape) { this._entries = []; ForEachFace(shape, (index, face) => { + // see EdgeSelector: raw sub-shapes need a stable hash for CacheOp + if (face.hash === undefined) { face.hash = self.oc.OCJS.HashCode(face, 100000000); } this._entries.push({ index, face }); }); } @@ -1494,6 +3466,45 @@ function Faces(shape) { return new FaceSelector(shape); } +/** build123d's `new_edges(*objects, combined=)` (topology/utils.py): the edges + * of `combined` that no shape in `originals` contributed — i.e. the edges the + * combining operation created. + * + * Implemented with upstream's exact algorithm rather than a geometric + * comparison: a boolean CUT of the combined shape's edge list by the + * originals' edge list, which also splits partially-shared edges so only the + * genuinely new portion survives. + * + * @param {TopoDS_Shape} combined - the result of the operation + * @param {TopoDS_Shape[]} originals - its inputs + * @returns {TopoDS_Edge[]} the new edges */ +function NewEdges(combined, originals) { + if (!combined || combined.IsNull()) { return []; } + let combinedEdges = new self.oc.TopTools_ListOfShape(); + let combinedCount = 0; + let allCombined = []; + ForEachEdge(combined, (i, edge) => { + combinedEdges.Append(edge); combinedCount++; allCombined.push(edge); + }); + if (combinedCount === 0) { return []; } + let originalEdges = new self.oc.TopTools_ListOfShape(); + let originalCount = 0; + for (let i = 0; i < originals.length; i++) { + if (!originals[i] || originals[i].IsNull()) { continue; } + ForEachEdge(originals[i], (j, edge) => { originalEdges.Append(edge); originalCount++; }); + } + if (originalCount === 0) { return allCombined; } + let cut = new self.oc.BRepAlgoAPI_Cut_1(); + cut.SetArguments(combinedEdges); + cut.SetTools(originalEdges); + // (upstream also calls SetRunParallel(True) - a BOPAlgo_Options perf flag + // that is not bound in this build; it does not affect the result) + cut.Build(new self.oc.Message_ProgressRange_1()); + let out = []; + ForEachEdge(cut.Shape(), (i, edge) => { out.push(edge); }); + return out; +} + // --- Measurement Functions --- function Volume(shape) { @@ -1502,6 +3513,15 @@ function Volume(shape) { return props.Mass(); } +/** Sum of |volume| over the shape's SOLIDS only — immune to the spurious + * open-face contributions VolumeProperties picks up on mixed compounds + * in this OCCT build (build123d-lite's Shape.volume semantics). */ +function SolidsVolume(shape) { + let total = 0; + ForEachSolid(shape, (i, solid) => { total += Math.abs(Volume(solid)); }); + return total; +} + function SurfaceArea(shape) { let props = new self.oc.GProp_GProps_1(); self.oc.BRepGProp.SurfaceProperties_1(shape, props, false, false); @@ -1521,8 +3541,457 @@ function EdgeLength(shape) { return props.Mass(); } +/** Build a single TopoDS_Wire from an ordered list of connected segments. + * Each segment is [kind, points] with 3D points; kinds: + * 'line' [start, end] + * 'arc3' [start, pointOnArc, end] (circular arc through 3 points) + * 'bezier' [ctrl0, ctrl1, ..., ctrlN] (Bezier control points) + * 'spline' [p0, p1, ..., pN] (fit through points, C2, 1e-3) + * 'interp' [p0, p1, ..., pN] + params [tangents|null, periodic, scale] + * (exact GeomAPI_Interpolate — build123d's Edge.make_spline; + * tangents is null, [t0, t1] end tangents, or one per point) + * Used by build123d-lite's BuildLine/make_face (the segment MATH lives in + * Python; this helper only assembles edges with the same OCCT calls the + * Sketch class already uses). Returns the wire (scene-registered). */ +function WireFromSegments(segments, keepInputs) { + let curWire = self.CacheOp(arguments, "WireFromSegments", () => { + let toPnt = (p) => new self.oc.gp_Pnt_3(p[0], p[1], p.length > 2 ? p[2] : 0); + // Disjoint segment runs (e.g. build123d dimension lines with arrows) + // become SEPARATE wires collected into a compound — feeding a + // disconnected edge to one MakeWire aborts inside the kernel. + let wires = []; + let wireBuilder = new self.oc.BRepBuilderAPI_MakeWire_1(); + let started = false; + let lastEnd = null; + let closeRun = () => { + if (started) { wires.push(wireBuilder.Wire()); } + wireBuilder = new self.oc.BRepBuilderAPI_MakeWire_1(); + started = false; + }; + let near = (a, b) => a && b && + Math.abs(a[0] - b[0]) < 1e-6 && Math.abs(a[1] - b[1]) < 1e-6 && + Math.abs((a[2] || 0) - (b[2] || 0)) < 1e-6; + for (let s = 0; s < segments.length; s++) { + let segPts = segments[s][1]; + if (started && !near(lastEnd, segPts[0])) { closeRun(); } + lastEnd = segPts[segPts.length - 1]; + let kind = segments[s][0], pts = segments[s][1]; + let curveHandle = null; + if (kind === 'line') { + curveHandle = new self.oc.GC_MakeSegment_1(toPnt(pts[0]), toPnt(pts[1])).Value(); + } else if (kind === 'arc3') { + curveHandle = new self.oc.GC_MakeArcOfCircle_4(toPnt(pts[0]), toPnt(pts[1]), toPnt(pts[2])).Value(); + } else if (kind === 'bezier') { + let ptList = new self.oc.TColgp_Array1OfPnt_2(1, pts.length); + for (let i = 0; i < pts.length; i++) { ptList.SetValue(i + 1, toPnt(pts[i])); } + let bezier = new self.oc.Geom_BezierCurve_1(ptList); + let edge = new self.oc.BRepBuilderAPI_MakeEdge_24(new self.oc.Handle_Geom_Curve_2(bezier)).Edge(); + wireBuilder.Add_2(new self.oc.BRepBuilderAPI_MakeWire_2(edge).Wire()); + started = true; + continue; + } else if (kind === 'spline') { + let ptList = new self.oc.TColgp_Array1OfPnt_2(1, pts.length); + for (let i = 0; i < pts.length; i++) { ptList.SetValue(i + 1, toPnt(pts[i])); } + // cubic fit only: higher degrees oscillate/overshoot on the densely + // sampled clamped splines build123d-lite feeds through here + curveHandle = new self.oc.GeomAPI_PointsToBSpline_2(ptList, 3, 3, + (self.oc.GeomAbs_Shape ? self.oc.GeomAbs_Shape.GeomAbs_C2 : 2), 1.0e-4).Curve(); + } else if (kind === 'interp') { + // exact interpolation through the points — GeomAPI_Interpolate, the + // same calls as build123d's Edge.make_spline (tol 1e-6): + // params = [tangents, periodic, scale] + // tangents: null | [[t0],[t1]] end tangents | one (or null) per point + // scale: true = only tangent DIRECTION matters (OCCT rescales) + let p = segments[s][2] || []; + let tangents = (p[0] && p[0].length) ? p[0] : null; + let periodic = !!p[1]; + let scaleFlag = (p[2] === undefined || p[2] === null) ? true : !!p[2]; + let ptList = new self.oc.TColgp_HArray1OfPnt_2(1, pts.length); + for (let i = 0; i < pts.length; i++) { ptList.SetValue(i + 1, toPnt(pts[i])); } + let interp = new self.oc.GeomAPI_Interpolate_1( + new self.oc.Handle_TColgp_HArray1OfPnt_2(ptList), periodic, 1.0e-6); + if (tangents && tangents.length === 2 && pts.length !== 2) { + // start/end tangents only (build123d passes them via Load this way) + interp.Load_1(new self.oc.gp_Vec_4(tangents[0][0], tangents[0][1], tangents[0][2]), + new self.oc.gp_Vec_4(tangents[1][0], tangents[1][1], tangents[1][2]), + scaleFlag); + } else if (tangents && tangents.length > 0) { + if (tangents.length !== pts.length) { + console.error("WireFromSegments: interp needs 2 or per-point tangents"); + continue; + } + let tanArr = new self.oc.TColgp_Array1OfVec_2(1, tangents.length); + let flagArr = new self.oc.TColStd_HArray1OfBoolean_2(1, tangents.length); + for (let i = 0; i < tangents.length; i++) { + let t = tangents[i]; + let has = !!(t && t.length === 3); + flagArr.SetValue(i + 1, has); + tanArr.SetValue(i + 1, new self.oc.gp_Vec_4(has ? t[0] : 0, has ? t[1] : 0, has ? t[2] : 0)); + } + interp.Load_2(tanArr, new self.oc.Handle_TColStd_HArray1OfBoolean_2(flagArr), scaleFlag); + } + interp.Perform(); + if (!interp.IsDone()) { + console.error("WireFromSegments: B-spline interpolation failed"); + continue; + } + curveHandle = interp.Curve(); + } else if (kind === 'raw') { + // pre-existing TopoDS_Edge passed through untouched (exact geometry + // for edges that cannot be reconstructed as an analytic segment) + let edge = self.oc.TopoDS_Cast.Edge_1(segments[s][2][0]); + wireBuilder.Add_2(new self.oc.BRepBuilderAPI_MakeWire_2(edge).Wire()); + started = true; + continue; + } else if (kind === 'earc') { + // elliptical arc: pts = [start, end] (chaining bookkeeping only), + // params = [center, xdir, normal, major, minor, a1deg, a2deg] + let p = segments[s][2]; + let ax2 = new self.oc.gp_Ax2_4(new self.oc.gp_Pnt_3(p[0][0], p[0][1], p[0][2]), + new self.oc.gp_Dir_5(p[2][0], p[2][1], p[2][2])); + ax2.SetXDirection(new self.oc.gp_Dir_5(p[1][0], p[1][1], p[1][2])); + let elips = new self.oc.gp_Elips_2(ax2, p[3], p[4]); + let deg = Math.PI / 180; + let arc = new self.oc.GC_MakeArcOfEllipse_1(elips, p[5] * deg, p[6] * deg, true).Value(); + let edge = new self.oc.BRepBuilderAPI_MakeEdge_24(new self.oc.Handle_Geom_Curve_2(arc.get())).Edge(); + wireBuilder.Add_2(new self.oc.BRepBuilderAPI_MakeWire_2(edge).Wire()); + started = true; + continue; + } else if (kind === 'circle') { + // a FULL circle as ONE closed edge (build123d's CenterArc with + // |arc_size| >= 360 is a single Geom_Circle edge, and the number of + // edges a full circle is made of changes what sampling-based + // operations like make_hull see): params = [center, normal, xdir, r] + let p = segments[s][2]; + let edge = CircularEdge(p[3], 360, 360, p[0], p[1], p[2]); + wireBuilder.Add_2(new self.oc.BRepBuilderAPI_MakeWire_2(edge).Wire()); + started = true; + continue; + } else if (kind === 'parab' || kind === 'hypr') { + // conic arc: pts = [start, end] (chaining bookkeeping only), params = + // [origin, xdir, normal, focal | [xr, yr], a1deg, a2deg, sense] + // — build123d's Edge.make_parabola / make_hyperbola + let p = segments[s][2]; + let ax2 = new self.oc.gp_Ax2_4(new self.oc.gp_Pnt_3(p[0][0], p[0][1], p[0][2]), + new self.oc.gp_Dir_5(p[2][0], p[2][1], p[2][2])); + ax2.SetXDirection(new self.oc.gp_Dir_5(p[1][0], p[1][1], p[1][2])); + let deg = Math.PI / 180; + let arc; + if (kind === 'parab') { + arc = new self.oc.GC_MakeArcOfParabola_1( + new self.oc.gp_Parab_2(ax2, p[3]), p[4] * deg, p[5] * deg, !!p[6]).Value(); + } else { + arc = new self.oc.GC_MakeArcOfHyperbola_1( + new self.oc.gp_Hypr_2(ax2, p[3][0], p[3][1]), p[4] * deg, p[5] * deg, + !!p[6]).Value(); + } + let edge = new self.oc.BRepBuilderAPI_MakeEdge_24( + new self.oc.Handle_Geom_Curve_2(arc.get())).Edge(); + wireBuilder.Add_2(new self.oc.BRepBuilderAPI_MakeWire_2(edge).Wire()); + started = true; + continue; + } else if (kind === 'bspline') { + // EXACT B-spline: pts = [start, end] (chaining bookkeeping only), + // params = [poles, knots, mults, degree, weights, periodic] + // — build123d's Edge.make_bspline + let p = segments[s][2]; + let edge = BSplineEdge(p[0], p[1], p[2], p[3], p[4], p[5]); + wireBuilder.Add_2(new self.oc.BRepBuilderAPI_MakeWire_2(edge).Wire()); + started = true; + continue; + } else { + console.error("WireFromSegments: unknown segment kind '" + kind + "'"); + continue; + } + let edge = new self.oc.BRepBuilderAPI_MakeEdge_24(new self.oc.Handle_Geom_Curve_2(curveHandle.get())).Edge(); + wireBuilder.Add_2(new self.oc.BRepBuilderAPI_MakeWire_2(edge).Wire()); + started = true; + } + closeRun(); + if (wires.length === 1) { return wires[0]; } + let builder = new self.oc.BRep_Builder(); + let compound = new self.oc.TopoDS_Compound(); + builder.MakeCompound(compound); + for (let i = 0; i < wires.length; i++) { builder.Add(compound, wires[i]); } + return compound; + }); + self.sceneShapes.push(curWire); + return curWire; +} + +/** Hollow a solid with the given wall thickness, removing `openingFaces` + * (raw face sub-shapes of `shape`) — BRepOffsetAPI_MakeThickSolid, the same + * operation build123d's offset(openings=...) performs. Negative offset + * shells inward. */ +function ThickSolidOffset(shape, openingFaces, offsetDistance, tolerance, keepShape) { + if (!shape || shape.IsNull()) { console.error("ThickSolidOffset: input shape is null!"); return shape; } + if (!tolerance) { tolerance = 1e-4; } + let result = self.CacheOp(arguments, "ThickSolidOffset", () => { + let facesToRemove = new self.oc.TopTools_ListOfShape(); + for (let i = 0; i < openingFaces.length; i++) { facesToRemove.Append(openingFaces[i]); } + let mkThick = new self.oc.BRepOffsetAPI_MakeThickSolid(); + mkThick.MakeThickSolidByJoin(shape, facesToRemove, offsetDistance, tolerance, + self.oc.BRepOffset_Mode.BRepOffset_Skin, false, false, + self.oc.GeomAbs_JoinType.GeomAbs_Arc, false, new self.oc.Message_ProgressRange_1()); + return mkThick.Shape(); + }); + if (!keepShape) { self.sceneShapes = self.Remove(self.sceneShapes, shape); } + self.sceneShapes.push(result); + return result; +} + +/** Thicken a face into a solid along its normals like build123d's + * Solid.thicken. COMPROMISE(thicken): upstream drives BRepOffset_MakeOffset + * with Thickening=true (offset shell + MakeMissingWalls + MakeSolid); that + * class is unbound here and BRepOffsetAPI_MakeThickSolid never builds the + * missing walls for open input. So this reconstructs the same solid + * manually: the offset surface comes from the identical BRepOffset engine + * (MakeThickSolidByJoin with no closing faces, Skin/Intersection join like + * upstream), the side walls are RULED ThruSections lofts between each + * boundary wire and its offset image (upstream's MakeMissingWalls also + * builds ruled walls between matching edges), and the three sheets are + * sewn and solidified. */ +function ThickenSolid(surface, depth) { + if (!surface || surface.IsNull()) { console.error("ThickenSolid: input surface is null!"); return surface; } + let result = self.CacheOp(arguments, "ThickenSolid", () => { + // Closed surfaces (e.g. a full sphere face) have no free boundary to + // build walls from — upstream produces a hollow two-shell solid there, + // which this reconstruction does not support. + let edgeCounts = []; + let edgeExp = new self.oc.TopExp_Explorer_2(surface, + self.oc.TopAbs_ShapeEnum.TopAbs_EDGE, self.oc.TopAbs_ShapeEnum.TopAbs_SHAPE); + for (; edgeExp.More(); edgeExp.Next()) { + let e = self.oc.TopoDS_Cast.Edge_1(edgeExp.Current()); + let found = null; + for (let k = 0; k < edgeCounts.length; k++) { + if (edgeCounts[k].edge.IsSame(e)) { found = edgeCounts[k]; break; } + } + if (found) { found.count++; } else { edgeCounts.push({ edge: e, count: 1, degen: self.oc.BRep_Tool.Degenerated(e) }); } + } + let freeEdges = edgeCounts.filter((e) => e.count === 1 && !e.degen).length; + if (freeEdges === 0) { + console.error("ThickenSolid: the surface is closed (no free boundary); thicken of closed surfaces is not supported"); + return null; + } + + // 1) the offset surface (pure offset of the input, no walls) + let closingFaces = new self.oc.TopTools_ListOfShape(); + let mkThick = new self.oc.BRepOffsetAPI_MakeThickSolid(); + mkThick.MakeThickSolidByJoin(surface, closingFaces, depth, 1.0e-5, + self.oc.BRepOffset_Mode.BRepOffset_Skin, true, false, + self.oc.GeomAbs_JoinType.GeomAbs_Intersection, true, + new self.oc.Message_ProgressRange_1()); + let offsetShell = mkThick.Shape(); + if (!offsetShell || offsetShell.IsNull()) { + console.error("ThickenSolid: offset surface construction failed"); + return null; + } + + // 2) pair each boundary wire with its offset image (nearest centroid) + let wiresOf = (shape) => { + let out = []; + ForEachWire(shape, (i, wire) => { out.push(wire); }); + return out; + }; + let wireCenter = (wire) => { + let props = new self.oc.GProp_GProps_1(); + self.oc.BRepGProp.LinearProperties(wire, props, false, false); + let p = props.CentreOfMass(); + return [p.X(), p.Y(), p.Z()]; + }; + let baseWires = wiresOf(surface); + let offWires = wiresOf(offsetShell); + if (baseWires.length === 0 || offWires.length === 0 || + baseWires.length !== offWires.length) { + console.error("ThickenSolid: could not match boundary wires (" + + baseWires.length + " vs " + offWires.length + ")"); + return null; + } + let offCenters = offWires.map(wireCenter); + + // 3) ruled walls per wire pair + 4) sew everything into a solid + let facesToSew = []; + ForEachFace(surface, (i, f) => { facesToSew.push(f); }); + ForEachFace(offsetShell, (i, f) => { facesToSew.push(f); }); + for (let i = 0; i < baseWires.length; i++) { + let c = wireCenter(baseWires[i]); + let best = 0, bestD = Infinity; + for (let j = 0; j < offWires.length; j++) { + let d = Math.pow(c[0] - offCenters[j][0], 2) + + Math.pow(c[1] - offCenters[j][1], 2) + + Math.pow(c[2] - offCenters[j][2], 2); + if (d < bestD) { bestD = d; best = j; } + } + let loft = new self.oc.BRepOffsetAPI_ThruSections(false, true, 1.0e-6); + loft.AddWire(self.oc.TopoDS_Cast.Wire_1(baseWires[i])); + loft.AddWire(self.oc.TopoDS_Cast.Wire_1(offWires[best])); + loft.Build(new self.oc.Message_ProgressRange_1()); + ForEachFace(loft.Shape(), (k, f) => { facesToSew.push(f); }); + } + + let sew = new self.oc.BRepBuilderAPI_Sewing(1.0e-5, true, true, true, false); + for (let i = 0; i < facesToSew.length; i++) { sew.Add(facesToSew[i]); } + sew.Perform(new self.oc.Message_ProgressRange_1()); + let solids = []; + ForEachShell(sew.SewedShape(), (i, shell) => { + let fixer = new self.oc.ShapeFix_Solid_1(); + let solid = fixer.SolidFromShell(shell); + if (solid && !solid.IsNull()) { solids.push(solid); } + }); + if (solids.length === 0) { + console.error("ThickenSolid: sewing the thickened boundary failed"); + return null; + } + return solids[0]; + }); + self.sceneShapes = self.Remove(self.sceneShapes, surface); + self.sceneShapes.push(result); + return result; +} + +/** Draft-angle ("tapered") extrusion of a planar face — LocOpe_DPrism, the + * primitive behind build123d's extrude(taper=...). Positive taper angles + * narrow the profile with height. */ +function TaperExtrude(face, height, angleDeg, keepFace) { + if (!face || face.IsNull()) { console.error("TaperExtrude: input face is null!"); return face; } + let result = self.CacheOp(arguments, "TaperExtrude", () => { + let f = face.ShapeType().value === 4 ? self.oc.TopoDS_Cast.Face_1(face) : face; + // LocOpe_DPrism measures Height along the tapered slant; scale so the + // resulting solid is `height` tall like build123d's extrude(taper=) + let slant = height / Math.cos(angleDeg * (Math.PI / 180)); + let dprism = new self.oc.LocOpe_DPrism_2(f, slant, angleDeg * (Math.PI / 180)); + return dprism.Shape(); + }); + if (!keepFace) { self.sceneShapes = self.Remove(self.sceneShapes, face); } + self.sceneShapes.push(result); + return result; +} + +/** Render text as a planar face for build123d-lite's Text: opentype.js + * outlines from the bundled Liberation Sans (what Linux fontconfig + * resolves 'Arial' to, so glyph geometry matches native build123d), plus + * OCCT-text-builder-compatible alignment offsets. Alignment references + * the FONT LAYOUT metrics (advance width, ascender/descender), not the + * ink bounding box — like Font_TextFormatter. */ +function Text2D(text, size, fontName, halign, valign) { + if (!fontName) { fontName = "FreeSans"; } + let curText = self.CacheOp(arguments, "Text2D", () => { + let face = _opentypeTextFace(text, size, fontName, true); + if (!face) { return; } + let font = self.loadedFonts[fontName]; + let upm = font.unitsPerEm; + // Width for alignment: kerned advance PLUS a spurious kern(last, last) + // pair — Font_TextFormatter (which build123d's Text uses) evaluates the + // kerning of the final glyph against itself when flushing the line, and + // matching it here makes centered text line up exactly. + let advance = 0; + let prev = null; + for (const ch of text) { + let g = font.charToGlyph(ch); + if (prev) { advance += _kernValue(fontName, prev, g) / upm * size; } + advance += g.advanceWidth / upm * size; + prev = g; + } + if (prev) { advance += _kernValue(fontName, prev, prev) / upm * size; } + // Vertical alignment uses the OS/2 typographic metrics (verified against + // build123d 0.11.1: TOP = -typoAscender, CENTER = lineSpacing/2 - + // typoAscender, BOTTOM = baseline). + let os2 = font.tables.os2 || {}; + let typoAsc = (os2.sTypoAscender !== undefined ? os2.sTypoAscender : font.ascender) / upm * size; + let typoDesc = (os2.sTypoDescender !== undefined ? os2.sTypoDescender : font.descender) / upm * size; + let typoGap = (os2.sTypoLineGap !== undefined ? os2.sTypoLineGap : 0) / upm * size; + let lineSpacing = typoAsc - typoDesc + typoGap; + let dx = halign === 'left' ? 0 : halign === 'right' ? -advance : -advance / 2; + let dy = valign === 'bottom' ? 0 : + valign === 'top' ? -typoAsc : lineSpacing / 2 - typoAsc; + // opentype glyph paths are y-DOWN (canvas convention) — mirror across + // the baseline (bakes geometry, keeping hole orientations valid), then + // reverse the face so its oriented normal is +Z like build123d text + // (mirroring flips the surface handedness; extrusions and fuses follow + // the ORIENTED normal) + // The freshly built face MUST carry a stable hash before entering the + // CacheOp'd Mirror below: un-hashed shapes hash as "{}" (ptr stripped), + // which made every Text2D after the first REUSE the first text's + // mirrored geometry (fresh alignment, stale glyphs). + face.hash = self.oc.OCJS.HashCode(face, 100000000); + let mirrored = Mirror([0, 1, 0], face); + let translated = Translate([dx, dy, 0], mirrored); + let moved; + if (translated.ShapeType().value === 4) { + moved = self.oc.TopoDS_Cast.Face_1(translated.Reversed()); + } else { + // per-glyph compound: give each glyph face a +Z ORIENTED normal (the + // same rule as the single-face branch). The mirror transform above + // already flipped the sub-face orientation flags inside the compound + // (BRepTools_TrsfModification keeps oriented normals consistent for + // container shapes), so reverse CONDITIONALLY on the actual oriented + // normal instead of blindly — a blind .Reversed() double-flips. + let builder = new self.oc.BRep_Builder(); + let compound = new self.oc.TopoDS_Compound(); + builder.MakeCompound(compound); + ForEachFace(translated, (i, f) => { + builder.Add(compound, _faceNormal(f)[2] < 0 ? f.Reversed() : f); + }); + compound.hash = self.oc.OCJS.HashCode(compound, 100000000); + moved = compound; + } + self.sceneShapes = self.Remove(self.sceneShapes, moved); + return moved; + }); + if (curText) { self.sceneShapes.push(curText); } + return curText; +} + +/** Axis-aligned bounding box [minX,minY,minZ,maxX,maxY,maxZ] via + * BRepBndLib.AddOptimal — the exact box, no triangulation-tolerance + * padding, matching build123d's Shape.bounding_box(optimal=True). + * (The `deflection` parameter is legacy from the pre-OCCT-8.0.1 build, + * which had no Bnd_Box binding and meshed a deep copy instead.) */ +function BoundingBox(shape, deflection) { + if (!shape || shape.IsNull()) { console.error("BoundingBox: input shape is null!"); return null; } + let box = new self.oc.Bnd_Box_1(); + self.oc.BRepBndLib.AddOptimal(shape, box, false, false); + if (box.IsVoid()) { return null; } + return [box.GetXMin(), box.GetYMin(), box.GetZMin(), + box.GetXMax(), box.GetYMax(), box.GetZMax()]; +} + +/** Measure a shape for the build123d validation harness / lite bounding_box: + * volume (mm^3, absolute), surface area, unique face/edge counts, and the + * mesh-approximated bounding box. Returns a plain JS object. */ +function MeasureShape(shape, deflection) { + if (!shape || shape.IsNull()) { console.error("MeasureShape: input shape is null!"); return null; } + let nFaces = 0; ForEachFace(shape, () => { nFaces++; }); + let nEdges = 0; ForEachEdge(shape, () => { nEdges++; }); + // COMPROMISE(volume-measure): VolumeProperties on OPEN faces yields + // meaningless partial integrals — report 0 for shapes with no solid + // (matches build123d's Sketch.volume). For compounds MIXING solids and + // stray faces, this OCCT 8.0.1 build's VolumeProperties also picks up + // spurious face contributions (7.x reported the solids' volume alone), + // so volume is summed per-solid instead of one whole-shape integral. + let nSolids = 0; ForEachSolid(shape, () => { nSolids++; }); + return { + volume: nSolids > 0 ? SolidsVolume(shape) : 0, + area: SurfaceArea(shape), + faces: nFaces, + edges: nEdges, + bbox: BoundingBox(shape, deflection) + }; +} + // --- Additional Primitives --- +/** A wedge whose near face is dx by dz and whose far face spans xmin..xmax by + * zmin..zmax (BRepPrimAPI_MakeWedge's min/max form) — build123d's + * Solid.make_wedge. */ +function WedgeMinMax(dx, dy, dz, xmin, zmin, xmax, zmax) { + let result = self.CacheOp(arguments, "WedgeMinMax", () => { + return new self.oc.BRepPrimAPI_MakeWedge_3(dx, dy, dz, xmin, zmin, xmax, zmax).Shape(); + }); + self.sceneShapes.push(result); + return result; +} + function Wedge(dx, dy, dz, ltx) { let curWedge = self.CacheOp(arguments, "Wedge", () => { return new self.oc.BRepPrimAPI_MakeWedge_1(dx, dy, dz, ltx).Shape(); @@ -1549,6 +4018,82 @@ function Section(shape, planeOrigin, planeNormal) { return curSection; } +/** 3-D convex hull of an array of [x,y,z] points via quickhull3d (pure JS, + * esbuild-bundled into the worker). Returns triangulated facets as arrays + * of vertex indices — the same convention as scipy's ConvexHull.simplices + * (used by build123d-lite's scipy.spatial shim). */ +function ConvexHull3D(points) { + return quickhull3d(points); +} + +/** Sew a list of faces into shell(s) and build solid(s) — the OCCT calls + * behind build123d's Solid(Shell(faces)): BRepBuilderAPI_Sewing + + * ShapeFix_Solid::SolidFromShell (which also orients the shell outward). + * Returns a single solid, or a compound if the faces sew into multiple + * closed shells. */ +function SewSolidFromFaces(faces) { + let curSolid = self.CacheOp(arguments, "SewSolidFromFaces", () => { + let sew = new self.oc.BRepBuilderAPI_Sewing(1.0e-6, true, true, true, false); + for (let i = 0; i < faces.length; i++) { sew.Add(faces[i]); } + sew.Perform(new self.oc.Message_ProgressRange_1()); + let sewed = sew.SewedShape(); + let solids = []; + ForEachShell(sewed, (i, shell) => { + let fixer = new self.oc.ShapeFix_Solid_1(); + let solid = fixer.SolidFromShell(shell); + if (solid && !solid.IsNull()) { solids.push(solid); } + }); + if (solids.length === 0) { + console.error("SewSolidFromFaces: sewing produced no closed shell"); + return null; + } + if (solids.length === 1) { return solids[0]; } + let builder = new self.oc.BRep_Builder(); + let compound = new self.oc.TopoDS_Compound(); + builder.MakeCompound(compound); + for (let i = 0; i < solids.length; i++) { builder.Add(compound, solids[i]); } + return compound; + }); + self.sceneShapes.push(curSolid); + return curSolid; +} + +/** Intersect an infinite line with a shape's surface — + * BRepIntCurveSurface_Inter, exactly build123d's + * Shape.find_intersection_points. Returns [[point, unitNormalAtPoint, + * distanceAlongLine], ...] sorted by distance along the line. */ +function IntersectLineShape(shape, origin, direction, tolerance) { + if (!tolerance) { tolerance = 1e-6; } + let pnt = new self.oc.gp_Pnt_3(origin[0], origin[1], origin[2]); + let dir = new self.oc.gp_Dir_5(direction[0], direction[1], direction[2]); + let line = new self.oc.gp_Lin_3(pnt, dir); + let inter = new self.oc.BRepIntCurveSurface_Inter(); + inter.Init_2(shape, line, tolerance); + let out = []; + while (inter.More()) { + let p = inter.Pnt(); + let face = inter.Face(); + let gpf = new self.oc.BRepGProp_Face_2(face, false); + let np = new self.oc.gp_Pnt_1(); + let nv = new self.oc.gp_Vec_1(); + gpf.Normal(inter.U(), inter.V(), np, nv); + let mag = nv.Magnitude(); + let n = mag > 1e-12 ? [nv.X() / mag, nv.Y() / mag, nv.Z() / mag] : [0, 0, 1]; + out.push([[p.X(), p.Y(), p.Z()], n, inter.W()]); + inter.Next(); + } + out.sort((a, b) => a[2] - b[2]); + return out; +} + +/** A TopoDS_Vertex at the given [x,y,z] point (BRepBuilderAPI_MakeVertex). */ +function PointVertex(p) { + let v = new self.oc.BRepBuilderAPI_MakeVertex( + new self.oc.gp_Pnt_3(p[0], p[1], p.length > 2 ? p[2] : 0)).Vertex(); + v.hash = self.oc.OCJS.HashCode(v, 100000000); + return v; +} + // --- Library Class (organizes initialization and self-registration) --- /** Wraps initialization of all CAD standard library functions. @@ -1559,9 +4104,32 @@ class CascadeStudioStandardLibrary { // Instantiate utility dependencies this.utils = new CascadeStudioUtils(); + // Gordon curve-network surfaces (build123d Face.make_gordon_surface — + // see GordonSurface.js). Engine is created lazily: oc must be live. + let gordonEngine = null; + self.GordonSurfaceFace = function (profiles, guides, tolerance) { + if (!gordonEngine) { gordonEngine = createGordonEngine(self.oc); } + // The engine's edgeToCurveData needs a downcast TopoDS_Edge; callers + // hand generic TopoDS_Shape (single-edge wires included). Points pass + // through as [x, y, z] arrays. + const toEdge = (item) => { + if (Array.isArray(item)) { return item; } + const t = item.ShapeType().value; + if (t === 6) { return self.oc.TopoDS_Cast.Edge_1(item); } + let edge = null, count = 0; + for (let ex = new self.oc.TopExp_Explorer_2(item, self.oc.TopAbs_ShapeEnum.TopAbs_EDGE, self.oc.TopAbs_ShapeEnum.TopAbs_SHAPE); ex.More(); ex.Next()) { + edge = self.oc.TopoDS_Cast.Edge_1(ex.Current()); count++; + } + if (count !== 1) { throw new Error("make_gordon_surface: each profile/guide must be a single edge or a point (got " + count + " edges)"); } + return edge; + }; + return gordonEngine.gordonSurfaceFace(profiles.map(toEdge), guides.map(toEdge), tolerance); + }; + // Assign all CAD API functions to self for eval() access self.Box = Box; self.Sphere = Sphere; + self.PartialSphere = PartialSphere; self.Cylinder = Cylinder; self.Cone = Cone; self.Polygon = Polygon; @@ -1590,6 +4158,7 @@ class CascadeStudioStandardLibrary { self.Intersection = Intersection; self.Extrude = Extrude; self.RemoveInternalEdges = RemoveInternalEdges; + self.UnifyWire = UnifyWire; self.Offset = Offset; self.OffsetWire = OffsetWire; self.Revolve = Revolve; @@ -1607,18 +4176,99 @@ class CascadeStudioStandardLibrary { // Selectors self.Edges = Edges; self.Faces = Faces; + self.NewEdges = NewEdges; self.EdgeSelector = EdgeSelector; self.FaceSelector = FaceSelector; // Measurement self.Volume = Volume; + self.SolidsVolume = SolidsVolume; self.SurfaceArea = SurfaceArea; self.CenterOfMass = CenterOfMass; self.EdgeLength = EdgeLength; + self.BoundingBox = BoundingBox; + self.MeasureShape = MeasureShape; + self.WireFromSegments = WireFromSegments; + self.ThickSolidOffset = ThickSolidOffset; + self.ThickenSolid = ThickenSolid; + self.TaperExtrude = TaperExtrude; + self.Text2D = Text2D; + self.ScaleXYZ = ScaleXYZ; + self.ScaleUniform = ScaleUniform; + self.ReverseFace = ReverseFace; + self.ReverseShape = ReverseShape; + self.ProjectWireOnShape = ProjectWireOnShape; + self.AsSingleFace = AsSingleFace; + self._edgeParam = _edgeParam; + self.TrimEdge = TrimEdge; + self.ReverseEdgeOrWire = ReverseEdgeOrWire; + self._edgeDistanceToPoint = _edgeDistanceToPoint; + self._edgeParamAtPoint = _edgeParamAtPoint; + self.ConcatEdgesToEdge = ConcatEdgesToEdge; + self.ProjectEdgeOnFace = ProjectEdgeOnFace; + self.ExtendSplineOnFace = ExtendSplineOnFace; + self.InterpolatedEdge = InterpolatedEdge; + self.ExtremaEdgeParams = ExtremaEdgeParams; + self.FillingFace = FillingFace; + self.WireFromEdgesFixed = WireFromEdgesFixed; + self._wireIsClosed = _wireIsClosed; + self.OrderedEdges = OrderedEdges; + self.WireFromOrderedEdges = WireFromOrderedEdges; + self.OffsetPlanarWire = OffsetPlanarWire; + self._edgeArcCenter = _edgeArcCenter; + self._edgeArcRadius = _edgeArcRadius; + self._edgeArcNormal = _edgeArcNormal; + self._edgeDerivativeAt = _edgeDerivativeAt; + self.BSplineEdge = BSplineEdge; + self._distShapeShape = _distShapeShape; + self._faceCurvatureSign = _faceCurvatureSign; + self.FilletWireCorner = FilletWireCorner; + self._faceRadius = _faceRadius; + self._faceAxisOfRotation = _faceAxisOfRotation; + self.CircularEdge = CircularEdge; + self.EdgeIsInterior = EdgeIsInterior; + self.HLRProject = HLRProject; + self.SurfaceFromPoints = SurfaceFromPoints; + self.PipeShellSweep = PipeShellSweep; + self.ExportSTL = ExportSTL; + self.DraftAngleFaces = DraftAngleFaces; + self.FaceWithHoles = FaceWithHoles; + + // Per-entity introspection helpers (used by build123d-lite's Python + // selectors: filter_by/group_by/sort_by need positions, directions, + // lengths, areas and geometry types of individual edges/faces). + self._edgeMidpoint = _edgeMidpoint; + self._edgeLength = _edgeLength; + self._edgeCurveType = _edgeCurveType; + self._edgeDirection = _edgeDirection; + self._faceCentroid = _faceCentroid; + self._faceArea = _faceArea; + self._faceNormal = _faceNormal; + self._faceUDir = _faceUDir; + self._faceUVBounds = _faceUVBounds; + self._faceD1 = _faceD1; + self._faceParamsAtPoint = _faceParamsAtPoint; + self._faceNormalAt = _faceNormalAt; + self._faceSurfaceType = _faceSurfaceType; + self._faceOuterWire = _faceOuterWire; + self._sameShape = _sameShape; + self._edgeIsForward = _edgeIsForward; + self._vertexPoint = _vertexPoint; + self._edgePointAt = _edgePointAt; + self._edgeTangentAt = _edgeTangentAt; + self.MakeCompound = MakeCompound; + self.FilletFace2D = FilletFace2D; // Additional primitives & operations self.Wedge = Wedge; + self.WedgeMinMax = WedgeMinMax; self.Section = Section; + self.ConvexHull3D = ConvexHull3D; + self.SewSolidFromFaces = SewSolidFromFaces; + self.IntersectLineShape = IntersectLineShape; + self.PointVertex = PointVertex; + self.ConstrainedArcs2D = ConstrainedArcs2D; + self.ConstrainedLines2D = ConstrainedLines2D; } } diff --git a/packages/cascade-core/src/worker/StandardUtils.js b/packages/cascade-core/src/worker/StandardUtils.js index 3a40539f..1d721ffd 100644 --- a/packages/cascade-core/src/worker/StandardUtils.js +++ b/packages/cascade-core/src/worker/StandardUtils.js @@ -33,6 +33,8 @@ class CascadeStudioUtils { self.convertToPnt = CascadeStudioUtils.convertToPnt; self.stringToHash = CascadeStudioUtils.stringToHash; self.CantorPairing = CascadeStudioUtils.CantorPairing; + self.decodeOCCTException = CascadeStudioUtils.decodeOCCTException; + self.describeOCCTException = CascadeStudioUtils.describeOCCTException; } /** Hashes input arguments and checks the cache for that hash. @@ -51,7 +53,12 @@ class CascadeStudioUtils { this.currentOp = fnName; self.currentOp = this.currentOp; - this.currentLineNumber = CascadeStudioUtils.getCallingLocation()[0]; + // getCallingLocation() parses JS eval stack frames, which is meaningless + // for Brython-generated code — Python mode resolves the user's source + // line from Brython's frame chain instead (see PythonRuntime.js). + this.currentLineNumber = (self.evalLanguage === 'python') + ? (self.getPythonUserLine ? self.getPythonUserLine() : 0) + : CascadeStudioUtils.getCallingLocation()[0]; self.currentLineNumber = this.currentLineNumber; postMessage({ "type": "Progress", "payload": { "opNumber": this.opNumber++, "opType": fnName } }); self.opNumber = this.opNumber; @@ -66,11 +73,31 @@ class CascadeStudioUtils { toReturn.hash = check.hash; this.cacheHits = (this.cacheHits || 0) + 1; } else { - toReturn = cacheMiss(); + try { + toReturn = cacheMiss(); + } catch (e) { + // Emscripten-compiled OCCT throws raw NUMBERS (C++ exception + // pointers) on kernel aborts. Brython cannot attach a traceback to + // a primitive ("Cannot create property '__traceback__' on number"), + // which masks the real failure — normalize to a proper Error here, + // DECODING the pointer back into OCCT's own message first. + if (typeof e === 'number' || typeof e === 'string') { + throw new Error("INTERNAL OPENCASCADE ERROR in " + fnName + ": " + + CascadeStudioUtils.describeOCCTException(e)); + } + throw e; + } toReturn.hash = curHash; if (self.GUIState["Cache?"]) { this.AddToCache(curHash, toReturn); } this.cacheMisses = (this.cacheMisses || 0) + 1; } + // Tag the shape with the 1-based editor line that produced it so the + // main thread can map picked shapes back to their source line. + // (Refreshed on every call, including cache hits, since the same cached + // shape may be produced from a different line after edits.) + if (toReturn && typeof toReturn === 'object') { + toReturn.producingLine = this.currentLineNumber; + } self.cacheHits = this.cacheHits; self.cacheMisses = this.cacheMisses; @@ -123,6 +150,87 @@ class CascadeStudioUtils { // --- Static utility methods (no instance state needed) --- + /** Decode a RAW wasm exception into the message OpenCascade actually raised. + * + * Emscripten-compiled OCCT throws C++ exceptions as raw NUMBERS: the value + * is a pointer to the thrown object in wasm linear memory. Everything OCCT + * raises derives from `Standard_Failure`, whose layout is stable and small + * (Standard_Failure.hxx, OCCT 8.0.1): + * + * class Standard_Failure : public std::exception { // vtable only + * StringRef* myMessage; // +4 + * StringRef* myStackTrace; // +8 + * }; + * struct StringRef { int Counter; char Message[1]; }; // text at +4 + * + * so the message is the NUL-terminated string at `*(ptr + 4) + 4`. + * + * Reading it by hand is a substitution, not a preference: + * COMPROMISE(failure-decode) — the fork binds + * `OCJS::getStandard_FailureData(intptr_t) -> Standard_Failure*` + * (builds/cascadestudio.yml) for exactly this purpose, but calling it in + * this build raises "Cannot call OCJS.getStandard_FailureData due to + * unbound types: St9exception": Standard_Failure derives from + * std::exception, which the build never registers, so embind treats the + * whole type as unresolved. The module also exports no runtime helpers + * (HEAPU8 / getValue / UTF8ToString are all absent), so the wasm Memory is + * captured at instantiation instead (CascadeWorker's `instantiateWasm`). + * + * Returns `{ message }` on success, or null when the value cannot be + * decoded — an Emscripten abort ("memory access out of bounds") arrives as + * a RuntimeError rather than a number, a non-OCCT C++ throw has a different + * layout, and either way the caller must fall back to the raw value. + * + * @param {*} e - the caught value + * @returns {{message: string}|null} */ + static decodeOCCTException(e) { + // Only integral pointer-shaped values can be exception pointers. + if (typeof e !== 'number' || !Number.isInteger(e) || e <= 0) { return null; } + const memory = self.ocMemory; + if (!memory || !memory.buffer) { return null; } + try { + // Views must be rebuilt per call: growing the wasm memory detaches the + // previous ArrayBuffer. + const u32 = new Uint32Array(memory.buffer); + const u8 = new Uint8Array(memory.buffer); + if (e + 12 > u8.length || (e & 3) !== 0) { return null; } + const stringRef = u32[(e >> 2) + 1]; // myMessage + if (!stringRef || stringRef + 8 > u8.length) { return null; } + let text = ''; + for (let i = stringRef + 4; i < u8.length && u8[i] !== 0; i++) { + if (text.length >= 512) { return null; } // not a message: bail out + text += String.fromCharCode(u8[i]); + } + const message = CascadeStudioUtils._plausibleOCCTText(text); + return message === null ? null : { message }; + } catch (decodeError) { + return null; // undecodable: the caller reports the raw value + } + } + + /** Accept only short, printable, non-empty text as a decoded OCCT message + * (a mis-decoded pointer yields control characters or binary noise). */ + static _plausibleOCCTText(value) { + if (typeof value !== 'string' || value.length === 0 || value.length > 512) { return null; } + // eslint-disable-next-line no-control-regex + if (/[\x00-\x08\x0b-\x1f\x7f-\x9f]/.test(value)) { return null; } + return value.trim() === '' ? null : value.trim(); + } + + /** Human-readable one-liner for any caught kernel value: OCCT's own message + * when the throw was a Standard_Failure pointer, else the raw value. Used + * by every worker error path so users see real diagnostics. */ + static describeOCCTException(e) { + const decoded = CascadeStudioUtils.decodeOCCTException(e); + if (decoded) { + return "the OCCT kernel raised '" + decoded.message + "'"; + } + if (e && typeof e === 'object' && e.message) { return String(e.message); } + return "the OCCT kernel threw '" + e + "' (a raw wasm exception carrying " + + "no readable message — most likely an Emscripten abort rather than a " + + "Standard_Failure)"; + } + /** This function recursively traverses x and calls `callback()` on each subelement. */ static recursiveTraverse(x, callback) { if (Object.prototype.toString.call(x) === '[object Array]') { diff --git a/packages/cascade-core/src/worker/UsedOCCTSymbols.generated.js b/packages/cascade-core/src/worker/UsedOCCTSymbols.generated.js new file mode 100644 index 00000000..06403b98 --- /dev/null +++ b/packages/cascade-core/src/worker/UsedOCCTSymbols.generated.js @@ -0,0 +1,185 @@ +// GENERATED by scripts/generate-occt-symbols.cjs — do not edit. +// Every oc.* symbol referenced by the worker sources. Verified against +// the loaded OpenCascade module at startup (see CascadeWorker.init). +export const USED_OCCT_SYMBOLS = [ + "BOPAlgo_Builder_1", + "BRepAdaptor_Curve_2", + "BRepAdaptor_Surface_2", + "BRepAlgoAPI_Cut_1", + "BRepAlgoAPI_Section_3", + "BRepAlgoAPI_Section_5", + "BRepBndLib", + "BRepBuilderAPI_GTransform_2", + "BRepBuilderAPI_MakeEdge_2", + "BRepBuilderAPI_MakeEdge_24", + "BRepBuilderAPI_MakeEdge_25", + "BRepBuilderAPI_MakeEdge_30", + "BRepBuilderAPI_MakeEdge_8", + "BRepBuilderAPI_MakeEdge_9", + "BRepBuilderAPI_MakeFace_15", + "BRepBuilderAPI_MakeFace_22", + "BRepBuilderAPI_MakeFace_8", + "BRepBuilderAPI_MakeSolid_1", + "BRepBuilderAPI_MakeVertex", + "BRepBuilderAPI_MakeWire_1", + "BRepBuilderAPI_MakeWire_2", + "BRepBuilderAPI_Sewing", + "BRepBuilderAPI_Transform_2", + "BRepBuilderAPI_TransitionMode", + "BRepExtrema_DistShapeShape_1", + "BRepFill_TypeOfContact", + "BRepFilletAPI_MakeChamfer", + "BRepFilletAPI_MakeFillet", + "BRepFilletAPI_MakeFillet2d_2", + "BRepGProp", + "BRepGProp_Face_2", + "BRepIntCurveSurface_Inter", + "BRepLib", + "BRepMesh_IncrementalMesh_2", + "BRepOffsetAPI_DraftAngle_2", + "BRepOffsetAPI_MakeFilling", + "BRepOffsetAPI_MakeOffsetShape", + "BRepOffsetAPI_MakeOffset_1", + "BRepOffsetAPI_MakeOffset_2", + "BRepOffsetAPI_MakePipeShell", + "BRepOffsetAPI_MakePipe_1", + "BRepOffsetAPI_MakeThickSolid", + "BRepOffsetAPI_ThruSections", + "BRepOffset_Mode", + "BRepPrimAPI_MakeBox_2", + "BRepPrimAPI_MakeCone_1", + "BRepPrimAPI_MakeCylinder_3", + "BRepPrimAPI_MakePrism_1", + "BRepPrimAPI_MakeRevol_1", + "BRepPrimAPI_MakeRevol_2", + "BRepPrimAPI_MakeSphere_12", + "BRepPrimAPI_MakeSphere_9", + "BRepPrimAPI_MakeWedge_1", + "BRepPrimAPI_MakeWedge_3", + "BRepProj_Projection_1", + "BRepProj_Projection_2", + "BRepTools", + "BRepTools_WireExplorer_2", + "BRep_Builder", + "BRep_Tool", + "Bnd_Box_1", + "ChFi2d_FilletAlgo_1", + "ChFi3d_FilletShape", + "Extrema_ExtAlgo", + "FS", + "GCPnts_AbscissaPoint", + "GCPnts_AbscissaPoint_2", + "GCPnts_TangentialDeflection_2", + "GC_MakeArcOfCircle_4", + "GC_MakeArcOfEllipse_1", + "GC_MakeArcOfHyperbola_1", + "GC_MakeArcOfParabola_1", + "GC_MakeCircle_2", + "GC_MakeSegment_1", + "GProp_GProps_1", + "GccEnt_Position", + "Geom2dAPI_InterCurveCurve_2", + "Geom2dAPI_ProjectPointOnCurve_2", + "Geom2dAdaptor_Curve_3", + "Geom2dGcc_Circ2d2TanOn_1", + "Geom2dGcc_Circ2d2TanOn_3", + "Geom2dGcc_Circ2d2TanRad_1", + "Geom2dGcc_Circ2d3Tan_1", + "Geom2dGcc_Circ2dTanCen", + "Geom2dGcc_Circ2dTanOnRad_1", + "Geom2dGcc_Lin2d2Tan_1", + "Geom2dGcc_Lin2d2Tan_2", + "Geom2dGcc_Lin2dTanObl_1", + "Geom2dGcc_QualifiedCurve", + "Geom2d_CartesianPoint_2", + "Geom2d_Circle_1", + "Geom2d_Line_2", + "Geom2d_TrimmedCurve", + "GeomAPI", + "GeomAPI_ExtremaCurveCurve_2", + "GeomAPI_Interpolate_1", + "GeomAPI_PointsToBSplineSurface_1", + "GeomAPI_PointsToBSplineSurface_2", + "GeomAPI_PointsToBSplineSurface_4", + "GeomAPI_PointsToBSpline_2", + "GeomAPI_ProjectPointOnCurve_2", + "GeomAPI_ProjectPointOnCurve_3", + "GeomAPI_ProjectPointOnSurf_5", + "GeomAbs_CurveType", + "GeomAbs_JoinType", + "GeomAbs_Shape", + "GeomAbs_SurfaceType", + "GeomAdaptor_Curve_2", + "GeomProjLib", + "Geom_BSplineCurve_1", + "Geom_BSplineCurve_2", + "Geom_BezierCurve_1", + "Geom_Plane_2", + "HLRAlgo_Projector_2", + "HLRBRep_Algo_1", + "HLRBRep_HLRToShape", + "Handle_Geom2d_Curve_2", + "Handle_Geom2d_Point_2", + "Handle_Geom_Curve_2", + "Handle_Geom_Surface_2", + "Handle_HLRBRep_Algo_2", + "Handle_TColStd_HArray1OfBoolean_2", + "Handle_TColgp_HArray1OfPnt_2", + "IFSelect_ReturnStatus", + "IGESControl_Reader_1", + "LocOpe_DPrism_2", + "Message_ProgressRange_1", + "OCJS", + "OCJS_Out", + "STEPControl_Reader_1", + "STEPControl_StepModelType", + "STEPControl_Writer_1", + "ShapeFix_Face_2", + "ShapeFix_Shape_2", + "ShapeFix_Solid_1", + "ShapeFix_Wire_1", + "ShapeUpgrade_UnifySameDomain_2", + "StlAPI_Reader", + "StlAPI_Writer", + "TColStd_Array1OfInteger_2", + "TColStd_Array1OfReal_2", + "TColStd_HArray1OfBoolean_2", + "TColgp_Array1OfPnt_2", + "TColgp_Array1OfVec_2", + "TColgp_Array2OfPnt_2", + "TColgp_HArray1OfPnt_2", + "TopAbs_Orientation", + "TopAbs_ShapeEnum", + "TopExp_Explorer_2", + "TopLoc_Location_1", + "TopLoc_Location_4", + "TopTools_ListOfShape", + "TopoDS_Cast", + "TopoDS_Compound", + "TopoDS_Shape", + "TopoDS_Wire", + "gp", + "gp_Ax1_2", + "gp_Ax2_1", + "gp_Ax2_2", + "gp_Ax2_4", + "gp_Ax2d_2", + "gp_Circ2d_2", + "gp_Circ_2", + "gp_Dir2d_5", + "gp_Dir_3", + "gp_Dir_5", + "gp_Elips_2", + "gp_GTrsf_1", + "gp_Hypr_2", + "gp_Lin2d_3", + "gp_Lin_3", + "gp_Parab_2", + "gp_Pln_3", + "gp_Pnt2d_3", + "gp_Pnt_1", + "gp_Pnt_3", + "gp_Trsf_1", + "gp_Vec_1", + "gp_Vec_4" +]; diff --git a/packages/cascade-studio/css/main.css b/packages/cascade-studio/css/main.css index 07c0decf..f86be85c 100644 --- a/packages/cascade-studio/css/main.css +++ b/packages/cascade-studio/css/main.css @@ -348,6 +348,146 @@ body { border-radius: 2px; } +/* ============================================================ + GUI Modeling Tools (viewport toolbar + overlays) + ============================================================ */ + +.cs-toolbar { + position: absolute; + top: 8px; + left: 8px; + z-index: 20; + display: flex; + flex-direction: column; + gap: 4px; + background: rgba(34, 34, 34, 0.85); + backdrop-filter: blur(8px); + border: 1px solid var(--cs-border); + border-radius: var(--cs-radius); + padding: 4px; +} + +.cs-tool-btn { + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + font-size: 15px; + color: var(--cs-text-secondary); + background: transparent; + border: 1px solid transparent; + border-radius: var(--cs-radius); + cursor: pointer; + transition: color var(--cs-transition), background var(--cs-transition); +} + +.cs-tool-btn:hover { + color: var(--cs-text-primary); + background: var(--cs-bg-elevated); +} + +.cs-tool-btn.cs-tool-active { + color: var(--cs-accent); + background: var(--cs-bg-elevated); + border-color: var(--cs-accent-dim); +} + +/* Tool unavailable in the current language mode (e.g. Sketch in Python) */ +.cs-tool-btn.cs-tool-disabled { + opacity: 0.35; + cursor: not-allowed; +} + +.cs-fillet-panel, +.cs-sketch-panel { + position: absolute; + top: 8px; + left: 48px; + z-index: 20; + display: flex; + align-items: center; + gap: 6px; + background: rgba(34, 34, 34, 0.9); + backdrop-filter: blur(8px); + border: 1px solid var(--cs-border); + border-radius: var(--cs-radius); + padding: 5px 8px; + font-size: 12px; + color: var(--cs-text-secondary); +} + +.cs-fillet-panel input, +.cs-sketch-panel input { + width: 52px; + background: var(--cs-bg-secondary); + color: var(--cs-text-primary); + border: 1px solid var(--cs-border); + border-radius: var(--cs-radius); + padding: 2px 4px; + font-size: 12px; +} + +.cs-sketch-panel select { + background: var(--cs-bg-secondary); + color: var(--cs-text-primary); + border: 1px solid var(--cs-border); + border-radius: var(--cs-radius); + padding: 2px 4px; + font-size: 12px; +} + +.cs-fillet-panel button, +.cs-sketch-panel button { + background: var(--cs-accent-dim); + color: var(--cs-text-primary); + border: none; + border-radius: var(--cs-radius); + padding: 3px 8px; + font-size: 12px; + cursor: pointer; +} + +.cs-fillet-panel button:hover, +.cs-sketch-panel button:hover { + background: var(--cs-accent); +} + +.cs-sketch-panel button.cs-sketch-cancel { + background: var(--cs-bg-elevated); +} + +.cs-sketch-panel button.cs-sketch-cancel:hover { + background: #555; +} + +/* Line/Arc segment-type toggle */ +.cs-sketch-panel button.cs-seg-active { + background: var(--cs-accent); + color: #fff; +} + +.cs-tool-label { + position: absolute; + z-index: 30; + pointer-events: none; + background: rgba(17, 17, 17, 0.85); + color: var(--cs-text-primary); + border: 1px solid var(--cs-border); + border-radius: var(--cs-radius); + padding: 2px 6px; + font-size: 11px; + font-family: var(--cs-font-mono); + white-space: nowrap; +} + +/* Editor line flash when picking a shape with the Select tool */ +.cs-pick-line-flash { + background: rgba(76, 175, 80, 0.25); + border-left: 2px solid var(--cs-accent); + transition: background 400ms ease; +} + /* ============================================================ Mobile Responsive Overrides ============================================================ */ diff --git a/packages/cascade-studio/lib/openscad-parser/openscad-parser.js b/packages/cascade-studio/lib/openscad-parser/openscad-parser.js index 4d5491c5..ac330e95 100644 --- a/packages/cascade-studio/lib/openscad-parser/openscad-parser.js +++ b/packages/cascade-studio/lib/openscad-parser/openscad-parser.js @@ -1,6 +1,10 @@ var __getOwnPropNames = Object.getOwnPropertyNames; var __commonJS = (cb, mod) => function __require() { - return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; + try { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; + } catch (e) { + throw mod = 0, e; + } }; // node_modules/openscad-parser/dist/CodeSpan.js @@ -1588,8 +1592,8 @@ var require_ASTPinpointer = __commonJS({ var ASTNode_1 = require_ASTNode(); var ASTAssembler_1 = require_ASTAssembler(); var Token_1 = require_Token(); - exports.BinAfter = Symbol("BinAfter"); - exports.BinBefore = Symbol("BinBefore"); + exports.BinAfter = /* @__PURE__ */ Symbol("BinAfter"); + exports.BinBefore = /* @__PURE__ */ Symbol("BinBefore"); var ASTPinpointer = class extends ASTAssembler_1.default { pinpointLocation; /** @@ -5400,7 +5404,7 @@ var require_ScadFileProvider = __commonJS({ // node_modules/openscad-parser/dist/index.js var require_index = __commonJS({ "node_modules/openscad-parser/dist/index.js"(exports) { - var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) { + var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) { if (k2 === void 0) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { @@ -5409,10 +5413,10 @@ var require_index = __commonJS({ } }; } Object.defineProperty(o, k2, desc); - } : function(o, m, k, k2) { + }) : (function(o, m, k, k2) { if (k2 === void 0) k2 = k; o[k2] = m[k]; - }); + })); var __exportStar = exports && exports.__exportStar || function(m, exports2) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports2, p)) __createBinding(exports2, m, p); }; diff --git a/packages/cascade-studio/lib/openscad-parser/openscad-parser.js.map b/packages/cascade-studio/lib/openscad-parser/openscad-parser.js.map index 51942afe..41b43a64 100644 --- a/packages/cascade-studio/lib/openscad-parser/openscad-parser.js.map +++ b/packages/cascade-studio/lib/openscad-parser/openscad-parser.js.map @@ -2,6 +2,6 @@ "version": 3, "sources": ["../../../../node_modules/openscad-parser/src/CodeSpan.ts", "../../../../node_modules/openscad-parser/src/ast/ASTNode.ts", "../../../../node_modules/openscad-parser/src/ast/ErrorNode.ts", "../../../../node_modules/openscad-parser/src/ASTAssembler.ts", "../../../../node_modules/openscad-parser/src/ast/ScadFile.ts", "../../../../node_modules/openscad-parser/src/ast/expressions.ts", "../../../../node_modules/openscad-parser/src/ast/statements.ts", "../../../../node_modules/openscad-parser/src/semantic/nodesWithScopes.ts", "../../../../node_modules/openscad-parser/src/ast/AssignmentNode.ts", "../../../../node_modules/openscad-parser/src/ASTMutator.ts", "../../../../node_modules/openscad-parser/src/extraTokens.ts", "../../../../node_modules/openscad-parser/src/TokenType.ts", "../../../../node_modules/openscad-parser/src/Token.ts", "../../../../node_modules/openscad-parser/src/ASTPinpointer.ts", "../../../../node_modules/openscad-parser/src/ASTPrinter.ts", "../../scripts/node-shims.cjs", "../../../../node_modules/openscad-parser/src/CodeFile.ts", "../../../../node_modules/openscad-parser/src/CodeLocation.ts", "../../../../node_modules/openscad-parser/src/ErrorCollector.ts", "../../../../node_modules/openscad-parser/src/FormattingConfiguration.ts", "../../../../node_modules/openscad-parser/src/errors/CodeError.ts", "../../../../node_modules/openscad-parser/src/errors/LexingError.ts", "../../../../node_modules/openscad-parser/src/errors/lexingErrors.ts", "../../../../node_modules/openscad-parser/src/keywords.ts", "../../../../node_modules/openscad-parser/src/LiteralToken.ts", "../../../../node_modules/openscad-parser/src/Lexer.ts", "../../../../node_modules/openscad-parser/src/comments/annotations.ts", "../../../../node_modules/openscad-parser/src/comments/DocComment.ts", "../../../../node_modules/openscad-parser/src/errors/ParsingError.ts", "../../../../node_modules/openscad-parser/src/friendlyTokenNames.ts", "../../../../node_modules/openscad-parser/src/errors/parsingErrors.ts", "../../../../node_modules/openscad-parser/src/Parser.ts", "../../../../node_modules/openscad-parser/src/ParsingHelper.ts", "../../../../node_modules/openscad-parser/src/semantic/Scope.ts", "../../../../node_modules/openscad-parser/src/semantic/ASTScopePopulator.ts", "../../../../node_modules/openscad-parser/src/prelude/PreludeUtil.ts", "../../../../node_modules/openscad-parser/src/semantic/ASTSymbolLister.ts", "../../../../node_modules/openscad-parser/src/semantic/CompletionSymbol.ts", "../../../../node_modules/openscad-parser/src/semantic/CompletionType.ts", "../../../../node_modules/openscad-parser/src/semantic/IncludeResolver.ts", "../../../../node_modules/openscad-parser/src/semantic/FilenameCompletionProvider.ts", "../../../../node_modules/openscad-parser/src/semantic/KeywordsCompletionProvider.ts", "../../../../node_modules/openscad-parser/src/semantic/ScopeSymbolCompletionProvider.ts", "../../../../node_modules/openscad-parser/src/semantic/CompletionUtil.ts", "../../../../node_modules/openscad-parser/src/semantic/resolvedNodes.ts", "../../../../node_modules/openscad-parser/src/semantic/unresolvedSymbolErrors.ts", "../../../../node_modules/openscad-parser/src/semantic/SymbolResolver.ts", "../../../../node_modules/openscad-parser/src/SolutionManager.ts", "../../../../node_modules/openscad-parser/dist/ast/ASTVisitor.js", "../../../../node_modules/openscad-parser/dist/comments/DocAnnotationClass.js", "../../../../node_modules/openscad-parser/dist/semantic/CompletionProvider.js", "../../../../node_modules/openscad-parser/dist/semantic/NodeWithScope.js", "../../../../node_modules/openscad-parser/dist/semantic/ScadFileProvider.js", "../../../../node_modules/openscad-parser/src/index.ts"], "sourcesContent": ["import CodeLocation from \"./CodeLocation\";\n\nexport default class CodeSpan {\n constructor(public start: CodeLocation, public end: CodeLocation) {}\n\n toString() {\n return `${this.start.toString()} - ${this.end.toString()}`;\n }\n\n static combine(...rawSpans: (CodeSpan | null | undefined)[]) {\n let spans = rawSpans.filter((s) => s != null) as CodeSpan[];\n if (spans.length === 0) {\n throw new Error(\"Cannot combine zero spans\");\n }\n if (spans.length === 1) {\n return spans[0];\n }\n let min: CodeSpan = spans[0];\n let max: CodeSpan = spans[0];\n for (let span of spans) {\n if (span.start.char < min.start.char) {\n min = span;\n }\n if (span.end.char > max.end.char) {\n max = span;\n }\n }\n return new CodeSpan(min.start, max.end);\n }\n\n static combineObject(spans: { [key: string]: CodeSpan }) {\n return CodeSpan.combine(...Object.values(spans));\n }\n}\n", "import CodeLocation from \"../CodeLocation\";\nimport CodeSpan from \"../CodeSpan\";\nimport Token from \"../Token\";\nimport ASTVisitor from \"./ASTVisitor\";\n\n/**\n * @category AST\n */\nexport default abstract class ASTNode {\n constructor() {}\n\n abstract tokens: { [key: string]: Token | Token[] | null };\n\n abstract accept(visitor: ASTVisitor): R;\n\n get span(): CodeSpan {\n return CodeSpan.combine(...Object.values(this.tokens).flat().map((t) => t?.span));\n }\n}\n", "import CodeLocation from \"../CodeLocation\";\nimport Token from \"../Token\";\nimport ASTNode from \"./ASTNode\";\nimport ASTVisitor from \"./ASTVisitor\";\n\n/**\n * Is put into the AST after it failed to parse something. Such an AST is invalid, and an error must have been generated.\n * It is generated during synchronisation, which occurs on every statement, but you should expect it everywhere when handling the AST.\n * @category AST\n */\nexport default class ErrorNode extends ASTNode {\n constructor(\n public tokens: {\n tokens: Token[];\n }\n ) {\n super();\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitErrorNode(this);\n }\n}\n", "import AssignmentNode from \"./ast/AssignmentNode\";\nimport ASTNode from \"./ast/ASTNode\";\nimport ASTVisitor from \"./ast/ASTVisitor\";\nimport ErrorNode from \"./ast/ErrorNode\";\nimport {\n AnonymousFunctionExpr,\n ArrayLookupExpr,\n AssertExpr,\n BinaryOpExpr,\n EchoExpr,\n FunctionCallExpr,\n GroupingExpr,\n LcEachExpr,\n LcForCExpr,\n LcForExpr,\n LcIfExpr,\n LcLetExpr,\n LetExpr,\n LiteralExpr,\n LookupExpr,\n MemberLookupExpr,\n RangeExpr,\n TernaryExpr,\n UnaryOpExpr,\n VectorExpr,\n} from \"./ast/expressions\";\nimport ScadFile from \"./ast/ScadFile\";\nimport {\n BlockStmt,\n FunctionDeclarationStmt,\n IfElseStatement,\n IncludeStmt,\n ModuleDeclarationStmt,\n ModuleInstantiationStmt,\n NoopStmt,\n UseStmt,\n} from \"./ast/statements\";\nimport Token from \"./Token\";\n\n/**\n * This class walks through the AST and generates arrays of tokens and function, which themselves return the same array.\n * It can be used to search through the AST, or determine the ranges of AST nodes.\n */\nexport default abstract class ASTAssembler implements ASTVisitor {\n protected abstract processAssembledNode(\n t: (Token | (() => R))[],\n self: ASTNode\n ): R;\n visitScadFile(n: ScadFile): R {\n return this.processAssembledNode(\n [...n.statements.map((stmt) => () => stmt.accept(this)), n.tokens.eot],\n n\n );\n }\n visitAssignmentNode(n: AssignmentNode): R {\n const arr: (Token | (() => R))[] = [];\n if (n.tokens.name) {\n arr.push(n.tokens.name);\n }\n if (n.tokens.equals) {\n arr.push(n.tokens.equals);\n }\n if (n.value) {\n // n.value won't be modified, so we can assert it is not null\n arr.push(() => n.value!.accept(this));\n }\n if (n.tokens.trailingCommas) {\n arr.push(...n.tokens.trailingCommas);\n }\n if (n.tokens.semicolon) {\n arr.push(n.tokens.semicolon);\n }\n return this.processAssembledNode(arr, n);\n }\n visitUnaryOpExpr(n: UnaryOpExpr): R {\n return this.processAssembledNode(\n [n.tokens.operator, () => n.right.accept(this)],\n n\n );\n }\n visitBinaryOpExpr(n: BinaryOpExpr): R {\n return this.processAssembledNode(\n [\n () => n.left.accept(this),\n n.tokens.operator,\n () => n.right.accept(this),\n ],\n n\n );\n }\n visitTernaryExpr(n: TernaryExpr): R {\n return this.processAssembledNode(\n [\n () => n.cond.accept(this),\n n.tokens.questionMark,\n () => n.ifExpr.accept(this),\n n.tokens.colon,\n () => n.elseExpr.accept(this),\n ],\n n\n );\n }\n visitArrayLookupExpr(n: ArrayLookupExpr): R {\n return this.processAssembledNode(\n [\n () => n.array.accept(this),\n n.tokens.firstBracket,\n () => n.index.accept(this),\n n.tokens.secondBracket,\n ],\n n\n );\n }\n visitLiteralExpr(n: LiteralExpr): R {\n return this.processAssembledNode([n.tokens.literalToken], n);\n }\n visitRangeExpr(n: RangeExpr): R {\n if (n.step && n.tokens.secondColon) {\n let parts = [() => n.begin.accept(this), n.tokens.firstColon];\n if (n.step) {\n parts.push(() => n!.step!.accept(this));\n }\n\n parts.push(n.tokens.secondColon, () => n.end.accept(this));\n return this.processAssembledNode(parts, n);\n }\n return this.processAssembledNode(\n [\n () => n.begin.accept(this),\n n.tokens.firstColon,\n () => n.end.accept(this),\n ],\n n\n );\n }\n visitVectorExpr(n: VectorExpr): R {\n const arr = [];\n arr.push(n.tokens.firstBracket);\n for (let i = 0; i < n.children.length; i++) {\n arr.push(() => n.children[i].accept(this));\n if (i < n.children.length - 1) {\n arr.push(n.tokens.commas[i]);\n }\n }\n arr.push(...n.tokens.commas.slice(n.children.length));\n arr.push(n.tokens.secondBracket);\n return this.processAssembledNode(arr, n);\n }\n visitLookupExpr(n: LookupExpr): R {\n return this.processAssembledNode([n.tokens.identifier], n);\n }\n visitMemberLookupExpr(n: MemberLookupExpr): R {\n return this.processAssembledNode(\n [() => n.expr.accept(this), n.tokens.dot, n.tokens.memberName],\n n\n );\n }\n visitFunctionCallExpr(n: FunctionCallExpr): R {\n return this.processAssembledNode(\n [\n () => n.callee.accept(this),\n n.tokens.firstParen,\n ...n.args.map((a) => () => a.accept(this)),\n n.tokens.secondParen,\n ],\n n\n );\n }\n visitLetExpr(n: LetExpr): R {\n return this.processAssembledNode(\n [\n n.tokens.name,\n n.tokens.firstParen,\n ...n.args.map((a) => () => a.accept(this)),\n n.tokens.secondParen,\n ],\n n\n );\n }\n visitAssertExpr(n: AssertExpr): R {\n return this.processAssembledNode(\n [\n n.tokens.name,\n n.tokens.firstParen,\n ...n.args.map((a) => () => a.accept(this)),\n n.tokens.secondParen,\n ],\n n\n );\n }\n visitEchoExpr(n: EchoExpr): R {\n return this.processAssembledNode(\n [\n n.tokens.name,\n n.tokens.firstParen,\n ...n.args.map((a) => () => a.accept(this)),\n n.tokens.secondParen,\n ],\n n\n );\n }\n visitLcIfExpr(n: LcIfExpr): R {\n const elseStuff: (Token | (() => R))[] = [];\n if (n.elseExpr && n.tokens.elseKeyword) {\n elseStuff.push(n.tokens.elseKeyword, () => n.elseExpr!.accept(this));\n }\n return this.processAssembledNode(\n [\n n.tokens.ifKeyword,\n n.tokens.firstParen,\n () => n.cond.accept(this),\n n.tokens.secondParen,\n () => n.ifExpr.accept(this),\n ...elseStuff,\n ],\n n\n );\n }\n visitLcEachExpr(n: LcEachExpr): R {\n return this.processAssembledNode(\n [n.tokens.eachKeyword, () => n.expr.accept(this)],\n n\n );\n }\n visitLcForExpr(n: LcForExpr): R {\n return this.processAssembledNode(\n [\n n.tokens.forKeyword,\n n.tokens.firstParen,\n ...n.args.map((a) => () => a.accept(this)),\n n.tokens.secondParen,\n () => n.expr.accept(this),\n ],\n n\n );\n }\n visitLcForCExpr(n: LcForCExpr): R {\n return this.processAssembledNode(\n [\n n.tokens.forKeyword,\n n.tokens.firstParen,\n ...n.args.map((a) => () => a.accept(this)),\n n.tokens.firstSemicolon,\n () => n.cond.accept(this),\n n.tokens.secondSemicolon,\n ...n.incrArgs.map((a) => () => a.accept(this)),\n n.tokens.secondParen,\n () => n.expr.accept(this),\n ],\n n\n );\n }\n visitLcLetExpr(n: LcLetExpr): R {\n return this.processAssembledNode(\n [\n n.tokens.letKeyword,\n n.tokens.firstParen,\n ...n.args.map((a) => () => a.accept(this)),\n n.tokens.secondParen,\n () => n.expr.accept(this),\n ],\n n\n );\n }\n visitGroupingExpr(n: GroupingExpr): R {\n return this.processAssembledNode(\n [n.tokens.firstParen, () => n.inner.accept(this), n.tokens.secondParen],\n n\n );\n }\n visitUseStmt(n: UseStmt): R {\n return this.processAssembledNode(\n [n.tokens.useKeyword, n.tokens.filename],\n n\n );\n }\n\n visitIncludeStmt(n: IncludeStmt): R {\n return this.processAssembledNode(\n [n.tokens.includeKeyword, n.tokens.filename],\n n\n );\n }\n visitModuleInstantiationStmt(n: ModuleInstantiationStmt): R {\n const arr = [];\n arr.push(...n.tokens.modifiersInOrder);\n arr.push(n.tokens.name);\n arr.push(n.tokens.firstParen);\n arr.push(...n.args.map((a) => () => a.accept(this)));\n arr.push(n.tokens.secondParen);\n if (\n n.child &&\n !(n.child instanceof ErrorNode && n.child.tokens.tokens.length === 0) // omit zero-width error nodes since they contribute nothing.\n ) {\n arr.push(() => n.child!.accept(this));\n }\n\n return this.processAssembledNode(arr, n);\n }\n visitModuleDeclarationStmt(n: ModuleDeclarationStmt): R {\n return this.processAssembledNode(\n [\n n.tokens.moduleKeyword,\n n.tokens.name,\n n.tokens.firstParen,\n ...n.definitionArgs.map((a) => () => a.accept(this)),\n n.tokens.secondParen,\n () => n.stmt.accept(this),\n ],\n n\n );\n }\n visitFunctionDeclarationStmt(n: FunctionDeclarationStmt): R {\n return this.processAssembledNode(\n [\n n.tokens.functionKeyword,\n n.tokens.name,\n n.tokens.firstParen,\n ...n.definitionArgs.map((a) => () => a.accept(this)),\n n.tokens.secondParen,\n () => n.expr.accept(this),\n n.tokens.semicolon,\n ],\n n\n );\n }\n visitBlockStmt(n: BlockStmt): R {\n return this.processAssembledNode(\n [\n n.tokens.firstBrace,\n ...n.children.map((a) => () => a.accept(this)),\n n.tokens.secondBrace,\n ],\n n\n );\n }\n visitNoopStmt(n: NoopStmt): R {\n return this.processAssembledNode([n.tokens.semicolon], n);\n }\n visitIfElseStatement(n: IfElseStatement): R {\n const arr = [];\n arr.push(...n.tokens.modifiersInOrder);\n arr.push(n.tokens.ifKeyword);\n arr.push(n.tokens.firstParen);\n arr.push(() => n.cond.accept(this));\n arr.push(n.tokens.secondParen);\n arr.push(() => n.thenBranch.accept(this));\n if (n.elseBranch) {\n arr.push(n!.tokens!.elseKeyword!, () => n!.elseBranch!.accept(this));\n }\n return this.processAssembledNode(arr, n);\n }\n visitAnonymousFunctionExpr(n: AnonymousFunctionExpr): R {\n return this.processAssembledNode(\n [\n n.tokens.functionKeyword,\n n.tokens.firstParen,\n ...n.definitionArgs.map((a) => () => a.accept(this)),\n n.tokens.secondParen,\n () => n.expr.accept(this),\n ],\n n\n );\n }\n visitErrorNode(n: ErrorNode): R {\n return this.processAssembledNode([...n.tokens.tokens], n);\n }\n}\n", "import CodeLocation from \"../CodeLocation\";\nimport Token from \"../Token\";\nimport ASTNode from \"./ASTNode\";\nimport ASTVisitor from \"./ASTVisitor\";\nimport { Statement } from \"./statements\";\n\n/**\n * The root node of any AST tree.\n *\n * Contains top-level statements including the use statements.\n *\n * @category AST\n */\nexport default class ScadFile extends ASTNode {\n constructor(\n public statements: Statement[],\n public tokens: {\n eot: Token;\n }\n ) {\n super();\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitScadFile(this);\n }\n}\n", "import CodeLocation from \"../CodeLocation\";\nimport LiteralToken from \"../LiteralToken\";\nimport Token from \"../Token\";\nimport TokenType from \"../TokenType\";\nimport AssignmentNode from \"./AssignmentNode\";\nimport ASTNode from \"./ASTNode\";\nimport ASTVisitor from \"./ASTVisitor\";\n\nexport abstract class Expression extends ASTNode {}\n\n/**\n * Represents an unary expression (!right, -right)\n * @category AST\n */\nexport class UnaryOpExpr extends Expression {\n /**\n * The operation of this unary expression.\n */\n operation: TokenType;\n\n /**\n * The expression on which the operation is performed.\n */\n right: Expression;\n\n constructor(\n op: TokenType,\n right: Expression,\n public tokens: { operator: Token }\n ) {\n super();\n this.operation = op;\n this.right = right;\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitUnaryOpExpr(this);\n }\n}\n\n/**\n * Represents a binary expression (LogicalAnd, LogicalOr, Multiply, Divide, Modulo, Plus, Minus, Less, LessEqual, Greater, GreaterEqual, Equal, NotEqual).\n * @category AST\n */\nexport class BinaryOpExpr extends Expression {\n /**\n * The left side of the operation.\n */\n left: Expression;\n\n /**\n * The type of the operation performed.\n */\n operation: TokenType;\n\n /**\n * The right side of the operation\n */\n right: Expression;\n\n constructor(\n left: Expression,\n operation: TokenType,\n right: Expression,\n public tokens: { operator: Token }\n ) {\n super();\n this.left = left;\n this.operation = operation;\n this.right = right;\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitBinaryOpExpr(this);\n }\n}\n\n/**\n * Represents a ternary expression (cond ? ifexpr : elsexpr)\n * @category AST\n */\nexport class TernaryExpr extends Expression {\n cond: Expression;\n ifExpr: Expression;\n elseExpr: Expression;\n constructor(\n cond: Expression,\n ifExpr: Expression,\n elseExpr: Expression,\n public tokens: {\n questionMark: Token;\n colon: Token;\n }\n ) {\n super();\n this.cond = cond;\n this.ifExpr = ifExpr;\n this.elseExpr = elseExpr;\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitTernaryExpr(this);\n }\n}\n\n/**\n * Represents a lookup operation on an array (indexing). Example: arr[5]\n * @category AST\n */\nexport class ArrayLookupExpr extends Expression {\n /**\n * The array being indexed.\n */\n array: Expression;\n\n /**\n * The index which is being looked up.\n */\n index: Expression;\n\n constructor(\n array: Expression,\n index: Expression,\n public tokens: {\n firstBracket: Token;\n secondBracket: Token;\n }\n ) {\n super();\n this.array = array;\n this.index = index;\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitArrayLookupExpr(this);\n }\n}\n\n/**\n * A literal expression (just a simple number, string or a boolean)\n * @category AST\n */\nexport class LiteralExpr extends Expression {\n value: TValue;\n\n constructor(\n value: TValue,\n public tokens: {\n literalToken: LiteralToken;\n }\n ) {\n super();\n this.value = value;\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitLiteralExpr(this);\n }\n}\n\n/**\n * A range epxression. Example: [0: 1 :20]\n * @category AST\n */\nexport class RangeExpr extends Expression {\n begin: Expression;\n /**\n * The optional step expression.\n * It defaults to 1 if not specified.\n */\n step: Expression | null;\n end: Expression;\n constructor(\n begin: Expression,\n step: Expression | null,\n end: Expression,\n public tokens: {\n firstBracket: Token;\n firstColon: Token;\n secondColon: Token | null;\n secondBracket: Token;\n }\n ) {\n super();\n this.begin = begin;\n this.step = step;\n this.end = end;\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitRangeExpr(this);\n }\n}\n\n/**\n * A vector literal expression. Example: [1, 2, 3, 4]\n * @category AST\n */\nexport class VectorExpr extends Expression {\n children: Expression[];\n constructor(\n children: Expression[],\n public tokens: {\n firstBracket: Token;\n commas: Token[];\n secondBracket: Token;\n }\n ) {\n super();\n this.children = children;\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitVectorExpr(this);\n }\n}\n\n/**\n * A lookup expression, it references a variable, module or function by name.\n * @category AST\n */\nexport class LookupExpr extends Expression {\n name: string;\n\n constructor(name: string, public tokens: { identifier: Token }) {\n super();\n this.name = name;\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitLookupExpr(this);\n }\n}\n\n/**\n * A member lookup expression, (abc.ddd)\n * @category AST\n */\nexport class MemberLookupExpr extends Expression {\n expr: Expression;\n member: string;\n\n constructor(\n expr: Expression,\n member: string,\n public tokens: {\n dot: Token;\n memberName: LiteralToken;\n }\n ) {\n super();\n this.expr = expr;\n this.member = member;\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitMemberLookupExpr(this);\n }\n}\n\n/**\n * A function call expression. Example: sin(10)\n * @category AST\n */\nexport class FunctionCallExpr extends Expression {\n /**\n * The expression that is being called.\n */\n callee: Expression;\n\n /**\n * The named arguments of the function call\n */\n args: AssignmentNode[];\n constructor(\n callee: Expression,\n args: AssignmentNode[],\n public tokens: {\n firstParen: Token;\n secondParen: Token;\n }\n ) {\n super();\n this.callee = callee;\n this.args = args;\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitFunctionCallExpr(this);\n }\n}\n\n/**\n * A common class for the Echo, Assert and Let expression so that the constructor is not copied.\n * @category AST\n */\nexport abstract class FunctionCallLikeExpr extends Expression {\n /**\n * The names of the assigned variables in this let expression.\n */\n args: AssignmentNode[];\n\n /**\n * The inner expression which will use the expression.\n */\n expr: Expression;\n\n constructor(\n args: AssignmentNode[],\n expr: Expression,\n public tokens: { name: Token; firstParen: Token; secondParen: Token }\n ) {\n super();\n this.args = args;\n this.expr = expr;\n }\n}\n\n/**\n * Represents a let expression. Please note that this is syntactically diffrent from the let module instantation and the let list comprehension.\n * @category AST\n */\nexport class LetExpr extends FunctionCallLikeExpr {\n accept(visitor: ASTVisitor): R {\n return visitor.visitLetExpr(this);\n }\n}\n\n/**\n * @category AST\n */\nexport class AssertExpr extends FunctionCallLikeExpr {\n accept(visitor: ASTVisitor): R {\n return visitor.visitAssertExpr(this);\n }\n}\n\n/**\n * @category AST\n */\nexport class EchoExpr extends FunctionCallLikeExpr {\n accept(visitor: ASTVisitor): R {\n return visitor.visitEchoExpr(this);\n }\n}\n\n/**\n * @category AST\n */\nexport abstract class ListComprehensionExpression extends Expression {}\n\n/**\n * @category AST\n */\nexport class LcIfExpr extends ListComprehensionExpression {\n cond: Expression;\n ifExpr: Expression;\n elseExpr: Expression | null;\n constructor(\n cond: Expression,\n ifExpr: Expression,\n elseExpr: Expression | null,\n public tokens: {\n ifKeyword: Token;\n firstParen: Token;\n secondParen: Token;\n elseKeyword: Token | null;\n }\n ) {\n super();\n this.cond = cond;\n this.ifExpr = ifExpr;\n this.elseExpr = elseExpr;\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitLcIfExpr(this);\n }\n}\n\n/**\n * @category AST\n */\nexport class LcEachExpr extends ListComprehensionExpression {\n /**\n * The expression where the declared variables will be accessible.\n */\n expr: Expression;\n\n constructor(\n expr: Expression,\n public tokens: {\n eachKeyword: Token;\n }\n ) {\n super();\n\n this.expr = expr;\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitLcEachExpr(this);\n }\n}\n\n/**\n * @category AST\n */\nexport class LcForExpr extends ListComprehensionExpression {\n /**\n * The variable names in the for expression\n */\n args: AssignmentNode[];\n\n /**\n * The expression which will be looped.\n */\n expr: Expression;\n\n constructor(\n args: AssignmentNode[],\n expr: Expression,\n public tokens: {\n forKeyword: Token;\n firstParen: Token;\n secondParen: Token;\n }\n ) {\n super();\n this.args = args;\n this.expr = expr;\n }\n\n accept(visitor: ASTVisitor): R {\n return visitor.visitLcForExpr(this);\n }\n}\n\n/**\n * @category AST\n */\nexport class LcForCExpr extends ListComprehensionExpression {\n /**\n * The variable names in the for expression\n */\n args: AssignmentNode[];\n\n incrArgs: AssignmentNode[];\n\n cond: Expression;\n /**\n * The expression which will be looped.\n */\n expr: Expression;\n\n constructor(\n args: AssignmentNode[],\n incrArgs: AssignmentNode[],\n cond: Expression,\n expr: Expression,\n public tokens: {\n forKeyword: Token;\n firstParen: Token;\n firstSemicolon: Token;\n secondSemicolon: Token;\n secondParen: Token;\n }\n ) {\n super();\n this.args = args;\n this.incrArgs = incrArgs;\n this.cond = cond;\n this.expr = expr;\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitLcForCExpr(this);\n }\n}\n\n/**\n * @category AST\n */\nexport class LcLetExpr extends ListComprehensionExpression {\n /**\n * The variable names in the let expression\n */\n args: AssignmentNode[];\n\n /**\n * The expression where the declared variables will be accessible.\n */\n expr: Expression;\n\n constructor(\n args: AssignmentNode[],\n expr: Expression,\n public tokens: {\n letKeyword: Token;\n firstParen: Token;\n secondParen: Token;\n }\n ) {\n super();\n this.args = args;\n this.expr = expr;\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitLcLetExpr(this);\n }\n}\n\n/**\n * An expression enclosed in parenthesis.\n * @category AST\n */\nexport class GroupingExpr extends Expression {\n inner: Expression;\n constructor(\n inner: Expression,\n public tokens: {\n firstParen: Token;\n secondParen: Token;\n }\n ) {\n super();\n this.inner = inner;\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitGroupingExpr(this);\n }\n}\n\n/**\n * AnonymousFunctionExpr represents a function expression. 'function(x) x * x'\n * @category AST\n */\nexport class AnonymousFunctionExpr extends Expression {\n constructor(\n public definitionArgs: AssignmentNode[],\n public expr: Expression,\n public tokens: {\n functionKeyword: Token;\n firstParen: Token;\n secondParen: Token;\n }\n ) {\n super();\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitAnonymousFunctionExpr(this);\n }\n}\n", "import CodeLocation from \"../CodeLocation\";\nimport DocComment from \"../comments/DocComment\";\nimport LiteralToken from \"../LiteralToken\";\nimport Token from \"../Token\";\nimport AssignmentNode from \"./AssignmentNode\";\nimport ASTNode from \"./ASTNode\";\nimport ASTVisitor from \"./ASTVisitor\";\nimport { Expression } from \"./expressions\";\n\n/**\n * @category AST\n */\nexport abstract class Statement extends ASTNode {}\n\n/**\n * @category AST\n */\nexport class UseStmt extends Statement {\n /**\n *\n * @param pos\n * @param filename The used filename\n */\n constructor(\n \n public filename: string,\n public tokens: {\n useKeyword: Token;\n filename: LiteralToken;\n }\n ) {\n super();\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitUseStmt(this);\n }\n}\n\n/**\n * @category AST\n */\nexport class IncludeStmt extends Statement {\n /**\n *\n * @param pos\n * @param filename The used filename\n */\n constructor(\n public filename: string,\n public tokens: {\n includeKeyword: Token;\n filename: LiteralToken;\n }\n ) {\n super();\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitIncludeStmt(this);\n }\n}\n\n/**\n * Represents a statement that can be prefixed with the !%#* symbols to change it's behaviour.\n * @category AST\n */\nexport interface TaggableStatement {\n /**\n * Set to true if this module instantation has been tagged with a '!' symbol.\n */\n tagRoot: boolean;\n\n /**\n * Set to true if this module instantation has been tagged with a '#' symbol.\n */\n tagHighlight: boolean;\n\n /**\n * Set to true if this module instantation has been tagged with a '%' symbol.\n */\n tagBackground: boolean;\n\n /**\n * Set to true if this module instantation has been tagged with a '*' symbol.\n */\n tagDisabled: boolean;\n}\n\n/**\n * @category AST\n */\nexport class ModuleInstantiationStmt\n extends Statement\n implements TaggableStatement\n{\n /**\n * !\n */\n public tagRoot: boolean = false;\n\n /**\n * #\n */\n public tagHighlight: boolean = false;\n\n /**\n * %\n */\n public tagBackground: boolean = false;\n\n /**\n * *\n */\n public tagDisabled: boolean = false;\n\n constructor(\n \n public name: string,\n public args: AssignmentNode[],\n /**\n * The child statement in a module instantiation chain.\n * Can be null if this is the last statement in the chain.\n */\n public child: Statement | null,\n public tokens: {\n name: Token;\n firstParen: Token;\n secondParen: Token;\n modifiersInOrder: Token[];\n }\n ) {\n super();\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitModuleInstantiationStmt(this);\n }\n}\n\n/**\n * @category AST\n */\nexport class ModuleDeclarationStmt extends Statement {\n constructor(\n \n public name: string,\n public definitionArgs: AssignmentNode[],\n public stmt: Statement,\n public tokens: {\n moduleKeyword: Token;\n name: Token;\n firstParen: Token;\n secondParen: Token;\n },\n public docComment: DocComment\n ) {\n super();\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitModuleDeclarationStmt(this);\n }\n}\n\n/**\n * FunctionDeclarationStmt reperesents a named function declaration statement.\n * @category AST\n */\nexport class FunctionDeclarationStmt extends Statement {\n constructor(\n \n public name: string,\n public definitionArgs: AssignmentNode[],\n public expr: Expression,\n public tokens: {\n functionKeyword: Token;\n name: Token;\n firstParen: Token;\n secondParen: Token;\n equals: Token;\n semicolon: Token;\n },\n public docComment: DocComment\n ) {\n super();\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitFunctionDeclarationStmt(this);\n }\n}\n\n/**\n * @category AST\n */\nexport class BlockStmt extends Statement {\n constructor(\n \n public children: Statement[],\n public tokens: {\n firstBrace: Token;\n secondBrace: Token;\n }\n ) {\n super();\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitBlockStmt(this);\n }\n}\n\n/**\n * @category AST\n */\nexport class NoopStmt extends Statement {\n constructor(\n \n public tokens: {\n semicolon: Token;\n }\n ) {\n super();\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitNoopStmt(this);\n }\n}\n\n/**\n * IfElseStmt represents an if-else statement. elseIfs are represented as\n * additional IfElseStmt instances in the else branch (simmilar to how C works).\n * @category AST\n */\nexport class IfElseStatement extends Statement implements TaggableStatement {\n public tagRoot: boolean = false;\n public tagHighlight: boolean = false;\n public tagBackground: boolean = false;\n public tagDisabled: boolean = false;\n constructor(\n \n public cond: Expression,\n public thenBranch: Statement,\n /**\n * The else branch.\n * It can be null if there is no else branch.\n */\n public elseBranch: Statement | null,\n public tokens: {\n ifKeyword: Token;\n firstParen: Token;\n secondParen: Token;\n elseKeyword: Token | null;\n modifiersInOrder: Token[];\n }\n ) {\n super();\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitIfElseStatement(this);\n }\n}\n", "import ASTVisitor from \"../ast/ASTVisitor\";\nimport { AnonymousFunctionExpr, LcForCExpr, LcForExpr, LcLetExpr, LetExpr } from \"../ast/expressions\";\nimport ScadFile from \"../ast/ScadFile\";\nimport {\n BlockStmt,\n FunctionDeclarationStmt,\n ModuleDeclarationStmt,\n ModuleInstantiationStmt,\n} from \"../ast/statements\";\nimport NodeWithScope from \"./NodeWithScope\";\nimport Scope from \"./Scope\";\n\nexport interface ASTVisitorForNodesWithScopes extends ASTVisitor {\n visitBlockStmtWithScope(n: BlockStmtWithScope): R;\n visitLetExprWithScope(n: LetExprWithScope): R;\n visitScadFileWithScope(n: ScadFileWithScope): R;\n visitFunctionDeclarationStmtWithScope(n: FunctionDeclarationStmtWithScope): R;\n visitModuleDeclarationStmtWithScope(n: ModuleDeclarationStmtWithScope): R;\n visitLcLetExprWithScope(n: LcLetExprWithScope): R;\n visitLcForExprWithScope(n: LcForExprWithScope): R;\n visitLcForCExprWithScope(n: LcForCExprWithScope): R;\n visitModuleInstantiationStmtWithScope(n: ModuleInstantiationStmtWithScope): R;\n visitAnonymousFunctionExprWithScope(n: AnonymousFunctionExprWithScope): R;\n}\n\nexport class BlockStmtWithScope extends BlockStmt implements NodeWithScope {\n scope!: Scope;\n accept(visitor: ASTVisitorForNodesWithScopes): R {\n if (visitor.visitBlockStmtWithScope) {\n return visitor.visitBlockStmtWithScope(this);\n }\n return visitor.visitBlockStmt(this);\n }\n}\nexport class LetExprWithScope extends LetExpr implements NodeWithScope {\n scope!: Scope;\n accept(visitor: ASTVisitorForNodesWithScopes): R {\n if (visitor.visitLetExprWithScope) {\n return visitor.visitLetExprWithScope(this);\n }\n return visitor.visitLetExpr(this);\n }\n}\n\nexport class ScadFileWithScope extends ScadFile implements NodeWithScope {\n scope!: Scope;\n accept(visitor: ASTVisitorForNodesWithScopes): R {\n if (visitor.visitScadFileWithScope) {\n return visitor.visitScadFileWithScope(this);\n }\n return visitor.visitScadFile(this);\n }\n}\n\nexport class FunctionDeclarationStmtWithScope\n extends FunctionDeclarationStmt\n implements NodeWithScope\n{\n scope!: Scope;\n accept(visitor: ASTVisitorForNodesWithScopes): R {\n if (visitor.visitFunctionDeclarationStmtWithScope) {\n return visitor.visitFunctionDeclarationStmtWithScope(this);\n }\n return visitor.visitFunctionDeclarationStmt(this);\n }\n}\n\nexport class ModuleDeclarationStmtWithScope\n extends ModuleDeclarationStmt\n implements NodeWithScope\n{\n scope!: Scope;\n accept(visitor: ASTVisitorForNodesWithScopes): R {\n if (visitor.visitModuleDeclarationStmtWithScope) {\n return visitor.visitModuleDeclarationStmtWithScope(this);\n }\n return visitor.visitModuleDeclarationStmt(this);\n }\n}\n\nexport class ModuleInstantiationStmtWithScope\n extends ModuleInstantiationStmt\n implements NodeWithScope\n{\n scope!: Scope;\n accept(visitor: ASTVisitorForNodesWithScopes): R {\n if (visitor.visitModuleInstantiationStmtWithScope) {\n return visitor.visitModuleInstantiationStmtWithScope(this);\n }\n return visitor.visitModuleInstantiationStmt(this);\n }\n}\n\nexport class LcLetExprWithScope extends LcLetExpr implements NodeWithScope {\n scope!: Scope;\n accept(visitor: ASTVisitorForNodesWithScopes): R {\n if (visitor.visitLcLetExprWithScope) {\n return visitor.visitLcLetExprWithScope(this);\n }\n return visitor.visitLcLetExpr(this);\n }\n}\n\nexport class LcForExprWithScope extends LcForExpr implements NodeWithScope {\n scope!: Scope;\n accept(visitor: ASTVisitorForNodesWithScopes): R {\n if (visitor.visitLcForExprWithScope) {\n return visitor.visitLcForExprWithScope(this);\n }\n return visitor.visitLcForExpr(this);\n }\n}\n\nexport class LcForCExprWithScope extends LcForCExpr implements NodeWithScope {\n scope!: Scope;\n accept(visitor: ASTVisitorForNodesWithScopes): R {\n if (visitor.visitLcForCExprWithScope) {\n return visitor.visitLcForCExprWithScope(this);\n }\n return visitor.visitLcForCExpr(this);\n }\n}\n\nexport class AnonymousFunctionExprWithScope extends AnonymousFunctionExpr implements NodeWithScope {\n scope!: Scope;\n accept(visitor: ASTVisitorForNodesWithScopes): R {\n if (visitor.visitAnonymousFunctionExprWithScope) {\n return visitor.visitAnonymousFunctionExprWithScope(this);\n }\n return visitor.visitAnonymousFunctionExpr(this);\n }\n}\n", "import CodeLocation from \"../CodeLocation\";\nimport DocComment from \"../comments/DocComment\";\nimport Token from \"../Token\";\nimport ASTNode from \"./ASTNode\";\nimport ASTVisitor from \"./ASTVisitor\";\nimport { Expression } from \"./expressions\";\n\nexport enum AssignmentNodeRole {\n VARIABLE_DECLARATION,\n ARGUMENT_DECLARATION,\n ARGUMENT_ASSIGNMENT,\n}\n\n/**\n * Represents a value being assigned to a name. Used when declaring and calling modules or functions.\n * It is also used in control flow structures such as for loops and let expressions.\n * @category AST\n */\nexport default class AssignmentNode extends ASTNode {\n /**\n * The name of the value being assigned.\n * The name field may be empty when it represents a positional argument in a call.\n */\n name: string;\n\n /**\n * THe value of the name being assigned.\n * It can be null when the AssignmentNode is used as a function parameter without a default value.\n */\n value: Expression | null;\n\n /**\n * The documentation and annotations connected with this variable.\n */\n docComment: DocComment | null = null;\n\n constructor(\n name: string,\n value: Expression | null,\n public role: AssignmentNodeRole,\n public tokens: {\n name: Token | null;\n equals: Token | null;\n trailingCommas: Token[] | null;\n semicolon: Token | null;\n }\n ) {\n super();\n this.name = name;\n this.value = value;\n }\n accept(visitor: ASTVisitor): R {\n return visitor.visitAssignmentNode(this);\n }\n}\n", "import ScadFile from \"./ast/ScadFile\";\n\nimport ASTVisitor from \"./ast/ASTVisitor\";\nimport {\n UnaryOpExpr,\n BinaryOpExpr,\n TernaryExpr,\n ArrayLookupExpr,\n LiteralExpr,\n RangeExpr,\n VectorExpr,\n LookupExpr,\n MemberLookupExpr,\n FunctionCallExpr,\n LetExpr,\n AssertExpr,\n EchoExpr,\n LcIfExpr,\n LcEachExpr,\n LcForExpr,\n LcForCExpr,\n LcLetExpr,\n GroupingExpr,\n AnonymousFunctionExpr,\n} from \"./ast/expressions\";\nimport {\n UseStmt,\n IncludeStmt,\n ModuleInstantiationStmt,\n ModuleDeclarationStmt,\n FunctionDeclarationStmt,\n BlockStmt,\n NoopStmt,\n IfElseStatement,\n} from \"./ast/statements\";\nimport DocComment from \"./comments/DocComment\";\nimport {\n ASTVisitorForNodesWithScopes,\n BlockStmtWithScope,\n LetExprWithScope,\n ScadFileWithScope,\n FunctionDeclarationStmtWithScope,\n ModuleDeclarationStmtWithScope,\n LcLetExprWithScope,\n LcForExprWithScope,\n LcForCExprWithScope,\n ModuleInstantiationStmtWithScope,\n AnonymousFunctionExprWithScope,\n} from \"./semantic/nodesWithScopes\";\nimport AssignmentNode from \"./ast/AssignmentNode\";\nimport ASTNode from \"./ast/ASTNode\";\nimport ErrorNode from \"./ast/ErrorNode\";\n\nexport default class ASTMutator\n implements ASTVisitorForNodesWithScopes\n{\n visitScadFile(n: ScadFile): ASTNode {\n const stmts = n.statements.map((s) => s.accept(this));\n if (stmts.length === n.statements.length) {\n let modified = false;\n for (let i = 0; i < stmts.length; i++) {\n if (stmts[i] !== n.statements[i]) {\n modified = true;\n break;\n }\n }\n if (!modified) {\n return n;\n }\n }\n\n return new ScadFile(stmts, n.tokens);\n }\n visitAssignmentNode(n: AssignmentNode): ASTNode {\n const newValue = n.value ? n.value.accept(this) : null;\n if (newValue === n.value) {\n return n;\n }\n return new AssignmentNode(n.name, newValue, n.role, n.tokens);\n }\n visitUnaryOpExpr(n: UnaryOpExpr): ASTNode {\n const newRight = n.right.accept(this);\n if (newRight === n.right) {\n return n;\n }\n return new UnaryOpExpr(n.operation, newRight, n.tokens);\n }\n visitBinaryOpExpr(n: BinaryOpExpr): ASTNode {\n const newLeft = n.left.accept(this);\n const newRight = n.right.accept(this);\n if (newRight === n.right && newLeft === n.left) {\n return n;\n }\n return new BinaryOpExpr(newLeft, n.operation, newRight, n.tokens);\n }\n visitTernaryExpr(n: TernaryExpr): ASTNode {\n const newCond = n.cond.accept(this);\n const newIfExpr = n.ifExpr.accept(this);\n const newElseExpr = n.elseExpr.accept(this);\n if (\n newCond === n.cond &&\n newIfExpr === n.ifExpr &&\n newElseExpr === n.elseExpr\n ) {\n return n;\n }\n return new TernaryExpr(n.cond, n.ifExpr, n.elseExpr, n.tokens);\n }\n visitArrayLookupExpr(n: ArrayLookupExpr): ASTNode {\n const newArray = n.array.accept(this);\n const newIndex = n.index.accept(this);\n if (newArray === n.array && newIndex === n.index) {\n return n;\n }\n return new ArrayLookupExpr(newArray, newIndex, n.tokens);\n }\n visitLiteralExpr(n: LiteralExpr): ASTNode {\n return n;\n }\n visitRangeExpr(n: RangeExpr): ASTNode {\n const newBegin = n.begin.accept(this);\n const newStep = n.step ? n.step.accept(this) : null;\n const newEnd = n.end.accept(this);\n if (newBegin === n.begin && newStep === n.step && newEnd === n.end) {\n return n;\n }\n return new RangeExpr(newBegin, newStep, newEnd, n.tokens);\n }\n visitVectorExpr(n: VectorExpr): ASTNode {\n const newChildren = n.children.map((c) => c.accept(this));\n if (newChildren.length === n.children.length) {\n let modified = false;\n for (let i = 0; i < newChildren.length; i++) {\n if (newChildren[i] !== n.children[i]) {\n modified = true;\n break;\n }\n }\n if (!modified) return n;\n }\n return new VectorExpr(newChildren, n.tokens);\n }\n visitLookupExpr(n: LookupExpr): ASTNode {\n return n;\n }\n visitMemberLookupExpr(n: MemberLookupExpr): ASTNode {\n const newExpr = n.expr.accept(this);\n if (newExpr === n.expr) {\n return n;\n }\n return new MemberLookupExpr(newExpr, n.member, n.tokens);\n }\n visitFunctionCallExpr(n: FunctionCallExpr): ASTNode {\n const newArgs = n.args.map((a) => a.accept(this)) as AssignmentNode[];\n const newCalee = n.callee.accept(this);\n if (newArgs.length === n.args.length && newCalee === n.callee) {\n let modified = false;\n for (let i = 0; i < newArgs.length; i++) {\n if (newArgs[i] !== n.args[i]) {\n modified = true;\n break;\n }\n }\n if (!modified) return n;\n }\n return new FunctionCallExpr(newCalee, newArgs, n.tokens);\n }\n visitLetExpr(n: LetExpr): ASTNode {\n const newExpr = n.expr.accept(this);\n const newArgs = n.args.map((a) => a.accept(this)) as AssignmentNode[];\n if (newArgs.length === n.args.length && newExpr === n.expr) {\n let modified = false;\n for (let i = 0; i < newArgs.length; i++) {\n if (newArgs[i] !== n.args[i]) {\n modified = true;\n break;\n }\n }\n if (!modified) return n;\n }\n\n return new LetExpr(newArgs, newExpr, n.tokens);\n }\n visitAssertExpr(n: AssertExpr): ASTNode {\n const newExpr = n.expr.accept(this);\n const newArgs = n.args.map((a) => a.accept(this)) as AssignmentNode[];\n if (newArgs.length === n.args.length && newExpr === n.expr) {\n let modified = false;\n for (let i = 0; i < newArgs.length; i++) {\n if (newArgs[i] !== n.args[i]) {\n modified = true;\n break;\n }\n }\n if (!modified) return n;\n }\n\n return new AssertExpr(newArgs, newExpr, n.tokens);\n }\n visitEchoExpr(n: EchoExpr): ASTNode {\n const newExpr = n.expr.accept(this);\n const newArgs = n.args.map((a) => a.accept(this)) as AssignmentNode[];\n if (newArgs.length === n.args.length && newExpr === n.expr) {\n let modified = false;\n for (let i = 0; i < newArgs.length; i++) {\n if (newArgs[i] !== n.args[i]) {\n modified = true;\n break;\n }\n }\n if (!modified) return n;\n }\n\n return new EchoExpr(newArgs, newExpr, n.tokens);\n }\n visitLcIfExpr(n: LcIfExpr): ASTNode {\n const newCond = n.cond.accept(this);\n const newIfExpr = n.ifExpr.accept(this);\n const newElseExpr = n.elseExpr ? n.elseExpr.accept(this) : null;\n if (\n newCond === n.cond &&\n newIfExpr === n.ifExpr &&\n newElseExpr === n.elseExpr\n ) {\n return n;\n }\n return new LcIfExpr(newCond, newIfExpr, newElseExpr, n.tokens);\n }\n visitLcEachExpr(n: LcEachExpr): ASTNode {\n const newExpr = n.expr.accept(this);\n if (newExpr === n.expr) {\n return n;\n }\n return new LcEachExpr(newExpr, n.tokens);\n }\n visitLcForExpr(n: LcForExpr): ASTNode {\n const newExpr = n.expr.accept(this);\n const newArgs = n.args.map((a) => a.accept(this)) as AssignmentNode[];\n if (newArgs.length === n.args.length && newExpr === n.expr) {\n let modified = false;\n for (let i = 0; i < newArgs.length; i++) {\n if (newArgs[i] !== n.args[i]) {\n modified = true;\n break;\n }\n }\n if (!modified) return n;\n }\n return new LcForExpr(newArgs, newExpr, n.tokens);\n }\n visitLcForCExpr(n: LcForCExpr): ASTNode {\n const newArgs = n.args.map((a) => a.accept(this)) as AssignmentNode[];\n const newIncrArgs = n.incrArgs.map((a) =>\n a.accept(this)\n ) as AssignmentNode[];\n const newExpr = n.expr.accept(this);\n const newCond = n.cond.accept(this);\n if (\n newArgs.length === n.args.length &&\n newIncrArgs.length === n.incrArgs.length &&\n newExpr === n.expr &&\n newCond === n.cond\n ) {\n let modified = false;\n for (let i = 0; i < newArgs.length; i++) {\n if (newArgs[i] !== n.args[i]) {\n modified = true;\n break;\n }\n }\n for (let i = 0; i < newIncrArgs.length; i++) {\n if (newIncrArgs[i] !== n.incrArgs[i]) {\n modified = true;\n break;\n }\n }\n if (!modified) return n;\n }\n return new LcForCExpr(newArgs, newIncrArgs, newCond, newExpr, n.tokens);\n }\n visitLcLetExpr(n: LcLetExpr): ASTNode {\n const newExpr = n.expr.accept(this);\n const newArgs = n.args.map((a) => a.accept(this)) as AssignmentNode[];\n if (newArgs.length === n.args.length && newExpr === n.expr) {\n let modified = false;\n for (let i = 0; i < newArgs.length; i++) {\n if (newArgs[i] !== n.args[i]) {\n modified = true;\n break;\n }\n }\n if (!modified) return n;\n }\n return new LcLetExpr(newArgs, newExpr, n.tokens);\n }\n visitGroupingExpr(n: GroupingExpr): ASTNode {\n const newInner = n.inner.accept(this);\n if (newInner === n.inner) {\n return n;\n }\n return new GroupingExpr(n.inner.accept(this), n.tokens);\n }\n visitUseStmt(n: UseStmt): ASTNode {\n return n;\n }\n visitIncludeStmt(n: IncludeStmt): ASTNode {\n return n;\n }\n visitModuleInstantiationStmt(n: ModuleInstantiationStmt): ASTNode {\n // TODO: add cached check\n const inst = new ModuleInstantiationStmt(\n n.name,\n n.args.map((a) => a.accept(this)) as AssignmentNode[],\n n.child ? n.child.accept(this) : null,\n n.tokens\n );\n inst.tagRoot = n.tagRoot;\n inst.tagHighlight = n.tagHighlight;\n inst.tagBackground = n.tagBackground;\n inst.tagDisabled = n.tagDisabled;\n return inst;\n }\n visitModuleDeclarationStmt(n: ModuleDeclarationStmt): ASTNode {\n const newDefinitionArgs = n.definitionArgs.map((a) =>\n a.accept(this)\n ) as AssignmentNode[];\n const newStmt = n.stmt.accept(this);\n if (\n newDefinitionArgs.length === n.definitionArgs.length &&\n newStmt === n.stmt\n ) {\n let modified = false;\n for (let i = 0; i < newDefinitionArgs.length; i++) {\n if (newDefinitionArgs[i] !== n.definitionArgs[i]) {\n modified = true;\n break;\n }\n }\n if (!modified) return n;\n }\n return new ModuleDeclarationStmt(\n n.name,\n newDefinitionArgs,\n newStmt,\n n.tokens,\n n.docComment\n );\n }\n visitFunctionDeclarationStmt(n: FunctionDeclarationStmt): ASTNode {\n const newDefinitionArgs = n.definitionArgs.map((a) =>\n a.accept(this)\n ) as AssignmentNode[];\n const newExpr = n.expr.accept(this);\n if (\n newDefinitionArgs.length === n.definitionArgs.length &&\n newExpr === n.expr\n ) {\n let modified = false;\n for (let i = 0; i < newDefinitionArgs.length; i++) {\n if (newDefinitionArgs[i] !== n.definitionArgs[i]) {\n modified = true;\n break;\n }\n }\n if (!modified) return n;\n }\n return new FunctionDeclarationStmt(\n n.name,\n newDefinitionArgs,\n newExpr,\n n.tokens,\n n.docComment\n );\n }\n visitBlockStmt(n: BlockStmt): ASTNode {\n const children = n.children.map((s) => s.accept(this));\n if (children.length === n.children.length) {\n let modified = false;\n for (let i = 0; i < children.length; i++) {\n if (children[i] !== n.children[i]) {\n modified = true;\n break;\n }\n }\n if (!modified) {\n return n;\n }\n }\n\n return new BlockStmt(children, n.tokens);\n }\n visitNoopStmt(n: NoopStmt): ASTNode {\n return n;\n }\n visitIfElseStatement(n: IfElseStatement): ASTNode {\n const newCond = n.cond.accept(this);\n const newThenBranch = n.thenBranch.accept(this);\n const newElseBranch = n.elseBranch ? n.elseBranch.accept(this) : null;\n if (\n newCond === n.cond &&\n newThenBranch === n.thenBranch &&\n newElseBranch === n.elseBranch\n ) {\n return n;\n }\n return new IfElseStatement(newCond, newThenBranch, newElseBranch, n.tokens);\n }\n visitErrorNode(n: ErrorNode): ASTNode {\n return n;\n }\n\n visitAnonymousFunctionExpr(n: AnonymousFunctionExpr): ASTNode {\n const newArgs = n.definitionArgs.map((a) =>\n a.accept(this)\n ) as AssignmentNode[];\n const newBody = n.expr.accept(this);\n if (newArgs.length === n.definitionArgs.length && newBody === n.expr) {\n let modified = false;\n for (let i = 0; i < newArgs.length; i++) {\n if (newArgs[i] !== n.definitionArgs[i]) {\n modified = true;\n break;\n }\n }\n if (!modified) return n;\n }\n return new AnonymousFunctionExpr(newArgs, newBody, n.tokens);\n }\n\n visitBlockStmtWithScope(n: BlockStmtWithScope): ASTNode {\n const oldNode = this.visitBlockStmt(n) as BlockStmt;\n const newNode = new BlockStmtWithScope(oldNode.children, oldNode.tokens);\n newNode.scope = n.scope;\n return newNode;\n }\n visitLetExprWithScope(n: LetExprWithScope): ASTNode {\n const oldNode = this.visitLetExpr(n) as LetExpr;\n const newNode = new LetExprWithScope(\n oldNode.args,\n oldNode.expr,\n oldNode.tokens\n );\n newNode.scope = n.scope;\n return newNode;\n }\n visitScadFileWithScope(n: ScadFileWithScope): ASTNode {\n const oldNode = this.visitScadFile(n) as ScadFile;\n const newNode = new ScadFileWithScope(oldNode.statements, oldNode.tokens);\n newNode.scope = n.scope;\n return newNode;\n }\n visitFunctionDeclarationStmtWithScope(\n n: FunctionDeclarationStmtWithScope\n ): ASTNode {\n const oldNode = this.visitFunctionDeclarationStmt(\n n\n ) as FunctionDeclarationStmt;\n const newNode = new FunctionDeclarationStmtWithScope(\n oldNode.name,\n oldNode.definitionArgs,\n oldNode.expr,\n oldNode.tokens,\n oldNode.docComment\n );\n newNode.scope = n.scope;\n return newNode;\n }\n visitModuleDeclarationStmtWithScope(\n n: ModuleDeclarationStmtWithScope\n ): ASTNode {\n const oldNode = this.visitModuleDeclarationStmt(n) as ModuleDeclarationStmt;\n const newNode = new ModuleDeclarationStmtWithScope(\n oldNode.name,\n oldNode.definitionArgs,\n oldNode.stmt,\n oldNode.tokens,\n n.docComment\n );\n newNode.scope = n.scope;\n return newNode;\n }\n visitModuleInstantiationStmtWithScope(n: ModuleInstantiationStmtWithScope) {\n const oldNode = this.visitModuleInstantiationStmt(\n n\n ) as ModuleInstantiationStmt;\n const newNode = new ModuleInstantiationStmtWithScope(\n oldNode.name,\n oldNode.args,\n oldNode.child,\n oldNode.tokens\n );\n newNode.scope = n.scope;\n return newNode;\n }\n visitLcLetExprWithScope(n: LcLetExprWithScope): ASTNode {\n const oldNode = this.visitLcLetExpr(n) as LcLetExpr;\n const newNode = new LcLetExprWithScope(\n oldNode.args,\n oldNode.expr,\n oldNode.tokens\n );\n newNode.scope = n.scope;\n return newNode;\n }\n visitLcForExprWithScope(n: LcForExprWithScope): ASTNode {\n const oldNode = this.visitLcForExpr(n) as LcForExpr;\n const newNode = new LcForExprWithScope(\n oldNode.args,\n oldNode.expr,\n oldNode.tokens\n );\n newNode.scope = n.scope;\n return newNode;\n }\n visitLcForCExprWithScope(n: LcForCExprWithScope): ASTNode {\n const oldNode = this.visitLcForCExpr(n) as LcForCExpr;\n const newNode = new LcForCExprWithScope(\n oldNode.args,\n oldNode.incrArgs,\n oldNode.cond,\n oldNode.expr,\n oldNode.tokens\n );\n newNode.scope = n.scope;\n return newNode;\n }\n\n visitAnonymousFunctionExprWithScope(n: AnonymousFunctionExprWithScope) {\n const oldNode = this.visitAnonymousFunctionExpr(n) as AnonymousFunctionExpr;\n const newNode = new AnonymousFunctionExprWithScope(\n oldNode.definitionArgs,\n oldNode.expr,\n oldNode.tokens\n );\n newNode.scope = n.scope;\n return newNode;\n }\n}\n", "import CodeLocation from \"./CodeLocation\";\n\n/**\n * An extra tolen is a parto of the source file that doesn't directly influence the AST, but it should be preserved when foromatting the code.\n */\nexport abstract class ExtraToken {\n constructor(public pos: CodeLocation) {}\n}\n\n/**\n * A new line between two other tokens.\n */\nexport class NewLineExtraToken extends ExtraToken {}\n\nexport class SingleLineComment extends ExtraToken {\n constructor(pos: CodeLocation, public contents: string) {\n super(pos);\n }\n}\n\nexport class MultiLineComment extends ExtraToken {\n constructor(pos: CodeLocation, public contents: string) {\n super(pos);\n }\n}\n", "enum TokenType {\n Error,\n /**\n * Eot is always pushed as the last token and used by the parser to detect the endo of the file.\n */\n Eot,\n /**\n * The module keyword.\n */\n Module,\n /**\n * The function keyword.\n */\n Function,\n /**\n * The if keyword.\n */\n If,\n /**\n * The else keyword.\n */\n Else,\n /**\n * The for keyword.\n */\n For,\n /**\n * The let keyword.\n */\n Let,\n /**\n * The assert keyword.\n */\n Assert,\n /**\n * The echo keyword.\n */\n Echo,\n /**\n * The each keyword.\n */\n Each,\n /**\n * The use keyword.\n */\n Use,\n /**\n * An identifier, represents a function, module or variable name\n */\n Identifier,\n /**\n * A string literal (e.g. quoted color names)\n */\n StringLiteral,\n /**\n * A number literal.\n */\n NumberLiteral,\n\n /**\n * The true keyword.\n */\n True,\n /**\n * The false keyword.\n */\n False,\n /**\n * The undef keyword.\n */\n Undef,\n\n /**\n * !\n */\n Bang,\n /**\n * <\n */\n Less,\n /**\n * >\n */\n Greater,\n /**\n * <=\n */\n LessEqual,\n /**\n * >=\n */\n GreaterEqual,\n /**\n * ==\n */\n EqualEqual,\n /**\n * =\n */\n Equal,\n /**\n * !=\n */\n BangEqual,\n /**\n * &&\n */\n AND,\n /**\n * ||\n */\n OR,\n\n Plus,\n Minus,\n Star,\n Slash,\n Percent,\n Caret,\n \n /**\n * Left parenthesis: (\n */\n LeftParen,\n /**\n * Right parenthesis: )\n */\n RightParen,\n /**\n * Left bracket: [\n */\n LeftBracket,\n /**\n * Right bracket: ]\n */\n RightBracket,\n /**\n * Left brace: {\n */\n LeftBrace,\n /**\n * Right brace: }\n */\n RightBrace,\n /**\n * ;\n */\n Semicolon,\n /**\n * ,\n */\n Comma,\n /**\n * .\n */\n Dot,\n\n /**\n * The ? symbol\n */\n QuestionMark,\n\n /**\n * The : symbol\n */\n Colon,\n\n /**\n * The '#' symbol\n */\n Hash,\n\n /**\n * The filename of an imported file e.g. ''\n */\n FilenameInChevrons,\n\n /**\n * The include keyword.\n */\n Include,\n}\n\nexport default TokenType;\n", "import CodeLocation from \"./CodeLocation\";\nimport CodeSpan from \"./CodeSpan\";\nimport { ExtraToken, NewLineExtraToken } from \"./extraTokens\";\nimport TokenType from \"./TokenType\";\n\nexport default class Token {\n /**\n * All the newlines and comments that appear before this token and should be preserved when printing the AST.\n */\n public extraTokens: ExtraToken[] = [];\n\n /**\n * Start of this token, including all the whitespace before it.\n * \n * Set externally in the lexer.\n */\n public startWithWhitespace!: CodeLocation;\n\n constructor(\n public type: TokenType,\n public span: CodeSpan,\n public lexeme: string\n ) {}\n\n toString(): string {\n return `token ${TokenType[this.type]} ${this.span.toString()}`;\n }\n\n hasNewlineInExtraTokens() {\n return this.extraTokens.some((t) => t instanceof NewLineExtraToken);\n }\n}\n", "import ASTNode from \"./ast/ASTNode\";\nimport ASTVisitor from \"./ast/ASTVisitor\";\nimport ASTAssembler from \"./ASTAssembler\";\nimport CodeLocation from \"./CodeLocation\";\nimport Token from \"./Token\";\n\nexport const BinAfter = Symbol(\"BinAfter\");\nexport const BinBefore = Symbol(\"BinBefore\");\n\nexport type PinpointerRet = ASTNode | typeof BinAfter | typeof BinBefore;\n\nexport type DispatchTokenMix = (Token | (() => PinpointerRet))[];\n\n/**\n * This class searches through the AST to find a node based on its position.\n * It may return BinAfter or BinBefore if the node cannot be found.\n */\nexport default class ASTPinpointer\n extends ASTAssembler\n implements ASTVisitor\n{\n /**\n * Contains all the ancestors of the pinpointed nodes. The pinpointed node is always first.\n */\n public bottomUpHierarchy: ASTNode[] = [];\n\n constructor(public pinpointLocation: CodeLocation) {\n super();\n }\n\n /**\n * Returns the node at pinpointLocation and populates bottomUpHierarchy.\n * @param n The AST (or AST fragment) to search through.\n */\n doPinpoint(n: ASTNode): PinpointerRet {\n this.bottomUpHierarchy = [];\n return n.accept(this);\n }\n protected processAssembledNode(\n t: DispatchTokenMix,\n self: ASTNode\n ): PinpointerRet {\n let l = 0,\n r = t.length - 1;\n // perform a binary search on the tokens\n while (l <= r) {\n let pivot = Math.floor((r + l) / 2);\n if (t[pivot] instanceof Token) {\n const tokenAtPiviot = t[pivot] as Token;\n if (tokenAtPiviot.span.end.char <= this.pinpointLocation.char) {\n l = pivot + 1;\n continue;\n }\n if (\n tokenAtPiviot.startWithWhitespace.char > this.pinpointLocation.char\n ) {\n r = pivot - 1;\n continue;\n }\n this.bottomUpHierarchy.push(self);\n return self; // yay this is us\n } else if (typeof t[pivot] === \"function\") {\n const astFunc = t[pivot] as () => PinpointerRet;\n const result = astFunc.call(this) as PinpointerRet;\n\n if (result === BinBefore) {\n r = pivot - 1;\n continue;\n }\n if (result === BinAfter) {\n l = pivot + 1;\n continue;\n }\n if (result instanceof ASTNode) {\n this.bottomUpHierarchy.push(self);\n return result;\n }\n } else {\n throw new Error(\n `Bad element in token mix: ${typeof t[pivot]} at index ${pivot}.`\n );\n }\n }\n const firstThing = t[0];\n if (firstThing instanceof Token) {\n if (firstThing.span.end.char <= this.pinpointLocation.char) {\n return BinAfter;\n }\n return BinBefore;\n }\n if (typeof firstThing === \"function\") {\n return firstThing.call(this);\n }\n throw new Error(\n `Bad element in first token mix element. Recieved ${firstThing}, expected a function or a Token.`\n );\n }\n}\n", "import { LiteralToken } from \".\";\nimport AssignmentNode from \"./ast/AssignmentNode\";\nimport ASTVisitor from \"./ast/ASTVisitor\";\nimport ErrorNode from \"./ast/ErrorNode\";\nimport {\n AnonymousFunctionExpr,\n ArrayLookupExpr,\n AssertExpr,\n BinaryOpExpr,\n EchoExpr,\n FunctionCallExpr,\n GroupingExpr,\n LcEachExpr,\n LcForCExpr,\n LcForExpr,\n LcIfExpr,\n LcLetExpr,\n LetExpr,\n LiteralExpr,\n LookupExpr,\n MemberLookupExpr,\n RangeExpr,\n TernaryExpr,\n UnaryOpExpr,\n VectorExpr,\n} from \"./ast/expressions\";\nimport ScadFile from \"./ast/ScadFile\";\nimport {\n BlockStmt,\n FunctionDeclarationStmt,\n IfElseStatement,\n IncludeStmt,\n ModuleDeclarationStmt,\n ModuleInstantiationStmt,\n NoopStmt,\n Statement,\n UseStmt,\n} from \"./ast/statements\";\nimport {\n MultiLineComment,\n NewLineExtraToken,\n SingleLineComment,\n} from \"./extraTokens\";\nimport FormattingConfiguration from \"./FormattingConfiguration\";\nimport Token from \"./Token\";\nimport TokenType from \"./TokenType\";\n\nexport default class ASTPrinter implements ASTVisitor {\n indentLevel = 0;\n breakBetweenModuleInstantations = false;\n firstModuleInstantation = true;\n doNotAddNewlineAfterBlockStatement = false;\n /**\n * We store data that is global between all the copies of the ASTPrinter in an object so that it is passed by reference.\n */\n deepGlobals = {\n didAddNewline: false,\n shouldAddNewlineAfterNextComment: false,\n newlineAfterNextCommentReason: \"\",\n };\n\n constructor(public config: FormattingConfiguration) {}\n\n visitErrorNode(n: ErrorNode): string {\n throw new Error(\"Cannot pretty print ast with an error node.\");\n }\n\n visitScadFile(n: ScadFile): string {\n let source = \"\";\n for (const stmt of n.statements) {\n source += this.processStatementWithBreakIfNeeded(stmt);\n }\n source += this.stringifyExtraTokens(n.tokens.eot);\n return source;\n }\n visitAssignmentNode(n: AssignmentNode): string {\n let source = \"\";\n if (n.name) {\n source += this.stringifyExtraTokens(n.tokens.name!);\n source += n.name;\n if (n.tokens.equals) {\n source += this.stringifyExtraTokens(n.tokens.equals);\n source += \" = \";\n }\n }\n\n if (n.value) {\n source += n.value.accept(this);\n }\n\n if (n.tokens.trailingCommas && n.tokens.trailingCommas.length > 0) {\n for (const tc of n.tokens.trailingCommas) {\n source += this.stringifyExtraTokens(tc);\n }\n source += \", \";\n }\n\n if (n.tokens.semicolon) {\n source += this.stringifyExtraTokens(n.tokens.semicolon);\n source += \";\";\n this.newLineAfterNextComment(\"after assignment\");\n }\n\n return source;\n }\n visitUnaryOpExpr(n: UnaryOpExpr): string {\n let source = \"\";\n source += this.stringifyExtraTokens(n.tokens.operator);\n if (n.operation === TokenType.Bang) {\n source += \"!\";\n } else if (n.operation === TokenType.Plus) {\n source += \"+\";\n } else if (n.operation === TokenType.Minus) {\n source += \"-\";\n }\n source += n.right.accept(this);\n return source;\n }\n visitBinaryOpExpr(n: BinaryOpExpr): string {\n let source = \"\";\n source += n.left.accept(this);\n source += this.stringifyExtraTokens(n.tokens.operator);\n source += \" \";\n if (n.operation === TokenType.Star) {\n source += \"*\";\n } else if (n.operation === TokenType.Slash) {\n source += \"/\";\n } else if (n.operation === TokenType.Caret) {\n source += \"^\";\n } else if (n.operation === TokenType.Percent) {\n source += \"%\";\n } else if (n.operation === TokenType.Less) {\n source += \"<\";\n } else if (n.operation === TokenType.LessEqual) {\n source += \"<=\";\n } else if (n.operation === TokenType.Greater) {\n source += \">\";\n } else if (n.operation === TokenType.GreaterEqual) {\n source += \">=\";\n } else if (n.operation === TokenType.AND) {\n source += \"&&\";\n } else if (n.operation === TokenType.OR) {\n source += \"||\";\n } else if (n.operation === TokenType.EqualEqual) {\n source += \"==\";\n } else if (n.operation === TokenType.BangEqual) {\n source += \"!=\";\n } else if (n.operation === TokenType.Plus) {\n source += \"+\";\n } else if (n.operation === TokenType.Minus) {\n source += \"-\";\n }\n source += \" \";\n source += n.right.accept(this);\n return source;\n }\n visitTernaryExpr(n: TernaryExpr): string {\n let source = \"\";\n source += n.cond.accept(this);\n source += this.stringifyExtraTokens(n.tokens.questionMark);\n source += \" ? \";\n source += n.ifExpr.accept(this);\n source += this.stringifyExtraTokens(n.tokens.colon);\n source += \" : \";\n source += n.elseExpr.accept(this);\n return source;\n }\n visitArrayLookupExpr(n: ArrayLookupExpr): string {\n let source = \"\";\n source += n.array.accept(this);\n source += this.stringifyExtraTokens(n.tokens.firstBracket);\n source += \"[\";\n source += n.index.accept(this);\n source += this.stringifyExtraTokens(n.tokens.secondBracket);\n source += \"]\";\n return source;\n }\n visitLiteralExpr(n: LiteralExpr): string {\n let source = \"\";\n\n source += this.stringifyExtraTokens(n.tokens.literalToken);\n if (n.value === null) {\n source += \"undef\";\n } else if (typeof n.value === \"string\") {\n source += JSON.stringify(n.value); // TODO: change to a custom stringification function\n } else {\n source += n.value;\n }\n\n return source;\n }\n visitRangeExpr(n: RangeExpr): string {\n let source = \"\";\n\n source += this.stringifyExtraTokens(n.tokens.firstBracket);\n source += \"[\";\n\n source += n.begin.accept(this);\n source += this.stringifyExtraTokens(n.tokens.firstColon);\n source += \" : \";\n if (n.step && n.tokens.secondColon) {\n source += n.step.accept(this);\n source += this.stringifyExtraTokens(n.tokens.secondColon);\n source += \" : \";\n }\n source += n.end.accept(this);\n source += this.stringifyExtraTokens(n.tokens.secondBracket);\n source += \"]\";\n return source;\n }\n visitVectorExpr(n: VectorExpr): string {\n let source = \"\";\n source += this.stringifyExtraTokens(n.tokens.firstBracket);\n source += \"[\";\n let commaI = 0;\n for (let i = 0; i < n.children.length; i++) {\n const child = n.children[i];\n source += child.accept(this.copyWithIndent());\n if (i < n.children.length - 1) {\n source += this.stringifyExtraTokens(n.tokens.commas[commaI]);\n commaI++;\n source += \", \";\n }\n }\n for (; commaI < n.tokens.commas.length; commaI++) {\n source += this.stringifyExtraTokens(n.tokens.commas[commaI]);\n }\n\n source += this.stringifyExtraTokens(n.tokens.secondBracket);\n source += \"]\";\n return source;\n }\n visitLookupExpr(n: LookupExpr): string {\n let source = \"\";\n\n source += this.stringifyExtraTokens(n.tokens.identifier);\n source += n.name;\n\n return source;\n }\n visitMemberLookupExpr(n: MemberLookupExpr): string {\n let source = \"\";\n source += n.expr.accept(this);\n source += this.stringifyExtraTokens(n.tokens.dot);\n source += \".\";\n source += this.stringifyExtraTokens(n.tokens.memberName);\n source += n.member;\n\n return source;\n }\n visitFunctionCallExpr(n: FunctionCallExpr): string {\n let source = n.callee.accept(this);\n source += this.stringifyExtraTokens(n.tokens.firstParen);\n source += \"(\";\n for (let i = 0; i < n.args.length; i++) {\n const arg = n.args[i];\n source += arg.accept(this);\n // if (i < n.args.length - 1) {\n // source += \", \";\n // }\n }\n source += this.stringifyExtraTokens(n.tokens.secondParen);\n source += \")\";\n return source;\n }\n visitLetExpr(n: LetExpr): string {\n let source = \"\";\n source += this.stringifyExtraTokens(n.tokens.name);\n source += \"let\";\n source += this.stringifyExtraTokens(n.tokens.firstParen);\n source += \"(\";\n for (let i = 0; i < n.args.length; i++) {\n const arg = n.args[i];\n source += arg.accept(this.copyWithIndent());\n }\n source += this.stringifyExtraTokens(n.tokens.secondParen);\n source += \")\";\n source += \" \";\n source += n.expr.accept(this);\n return source;\n }\n visitAssertExpr(n: AssertExpr): string {\n let source = \"\";\n source += this.stringifyExtraTokens(n.tokens.name);\n source += \"assert\";\n source += this.stringifyExtraTokens(n.tokens.firstParen);\n source += \"(\";\n for (let i = 0; i < n.args.length; i++) {\n const arg = n.args[i];\n source += arg.accept(this);\n // if (i < n.args.length - 1) {\n // source += \", \";\n // }\n }\n source += this.stringifyExtraTokens(n.tokens.secondParen);\n source += \")\";\n source += \" \";\n source += n.expr.accept(this);\n return source;\n }\n visitEchoExpr(n: EchoExpr): string {\n let source = \"\";\n source += this.stringifyExtraTokens(n.tokens.name);\n source += \"echo\";\n source += this.stringifyExtraTokens(n.tokens.firstParen);\n source += \"(\";\n for (let i = 0; i < n.args.length; i++) {\n const arg = n.args[i];\n source += arg.accept(this);\n // if (i < n.args.length - 1) {\n // source += \", \";\n // }\n }\n source += this.stringifyExtraTokens(n.tokens.secondParen);\n source += \")\";\n source += \" \";\n source += n.expr.accept(this);\n return source;\n }\n visitLcIfExpr(n: LcIfExpr): string {\n let source = \"\";\n source += this.stringifyExtraTokens(n.tokens.ifKeyword);\n source += \"if\";\n source += this.stringifyExtraTokens(n.tokens.firstParen);\n source += \"(\";\n source += n.cond.accept(this);\n source += this.stringifyExtraTokens(n.tokens.secondParen);\n source += \") \";\n source += n.ifExpr.accept(this);\n if (n.elseExpr && n.tokens.elseKeyword) {\n source += this.stringifyExtraTokens(n.tokens.elseKeyword);\n source += \" else \";\n source += n.elseExpr.accept(this);\n }\n\n return source;\n }\n visitLcEachExpr(n: LcEachExpr): string {\n let source = \"\";\n source += this.stringifyExtraTokens(n.tokens.eachKeyword);\n source += \"each \";\n source += n.expr.accept(this);\n return source;\n }\n visitLcForExpr(n: LcForExpr): string {\n let source = \"\";\n source += this.stringifyExtraTokens(n.tokens.forKeyword);\n source += \"for\";\n source += this.stringifyExtraTokens(n.tokens.firstParen);\n source += \"(\";\n for (let i = 0; i < n.args.length; i++) {\n const arg = n.args[i];\n source += arg.accept(this);\n }\n source += this.stringifyExtraTokens(n.tokens.secondParen);\n source += \") \";\n source += n.expr.accept(this);\n\n return source;\n }\n visitLcForCExpr(n: LcForCExpr): string {\n let source = \"\";\n source += this.stringifyExtraTokens(n.tokens.forKeyword);\n source += \"for\";\n source += this.stringifyExtraTokens(n.tokens.firstParen);\n source += \"(\";\n for (let i = 0; i < n.args.length; i++) {\n const arg = n.args[i];\n source += arg.accept(this);\n }\n source += this.stringifyExtraTokens(n.tokens.firstSemicolon);\n source += \"; \";\n source += n.cond.accept(this);\n source += this.stringifyExtraTokens(n.tokens.secondSemicolon);\n source += \"; \";\n for (let i = 0; i < n.incrArgs.length; i++) {\n const arg = n.incrArgs[i];\n source += arg.accept(this);\n }\n source += this.stringifyExtraTokens(n.tokens.secondParen);\n source += \") \";\n source += n.expr.accept(this);\n\n return source;\n }\n visitLcLetExpr(n: LcLetExpr): string {\n let source = \"\";\n source += this.stringifyExtraTokens(n.tokens.letKeyword);\n source += \"let\";\n source += this.stringifyExtraTokens(n.tokens.firstParen);\n source += \"(\";\n for (let i = 0; i < n.args.length; i++) {\n const arg = n.args[i];\n source += arg.accept(this);\n }\n source += this.stringifyExtraTokens(n.tokens.secondParen);\n source += \") \";\n source += n.expr.accept(this);\n\n return source;\n }\n visitGroupingExpr(n: GroupingExpr): string {\n let source = \"\";\n\n source += this.stringifyExtraTokens(n.tokens.firstParen);\n source += \"(\";\n source += n.inner.accept(this);\n source += this.stringifyExtraTokens(n.tokens.secondParen);\n source += \")\";\n return source;\n }\n visitUseStmt(n: UseStmt): string {\n let source = \"\";\n source += this.stringifyExtraTokens(n.tokens.useKeyword);\n source +=\n \"use \" +\n this.stringifyExtraTokens(n.tokens.filename) +\n \" <\" +\n n.filename +\n \">\" +\n this.newLine();\n return source;\n }\n\n visitIncludeStmt(n: IncludeStmt): string {\n let source = \"\";\n source += this.stringifyExtraTokens(n.tokens.includeKeyword);\n source +=\n \"include \" +\n this.stringifyExtraTokens(n.tokens.filename) +\n \" <\" +\n n.filename +\n \">\" +\n this.newLine();\n return source;\n }\n\n visitModuleInstantiationStmt(n: ModuleInstantiationStmt): string {\n let source = \"\";\n source += n.tokens.modifiersInOrder\n .map((tk) => this.stringifyExtraTokens(tk) + tk.lexeme)\n .join(\" \");\n if (source != \"\") {\n source += \" \";\n }\n source += this.stringifyExtraTokens(n.tokens.name);\n source += n.name;\n source += this.stringifyExtraTokens(n.tokens.firstParen);\n source += \"(\";\n for (let i = 0; i < n.args.length; i++) {\n const arg = n.args[i];\n source += arg.accept(this);\n // if (i < n.args.length - 1) {\n // source += \", \";\n // }\n }\n source += this.stringifyExtraTokens(n.tokens.secondParen);\n source += \")\";\n if (\n !(n.child instanceof NoopStmt) &&\n !this.breakBetweenModuleInstantations\n ) {\n source += \" \";\n }\n if (this.breakBetweenModuleInstantations) {\n if (n.child instanceof ModuleInstantiationStmt) {\n let c = this as ASTPrinter;\n if (this.firstModuleInstantation) {\n c = this.copyWithIndent();\n c.firstModuleInstantation = false;\n }\n this.newLineAfterNextComment(\"breakBetweenModuleInstantations\");\n source += n.child.accept(c);\n } else {\n const c = this.copyWithBreakBetweenModuleInstantations(false);\n c.firstModuleInstantation = true;\n if (n.child) source += n.child.accept(c);\n }\n } else {\n let c: ASTPrinter = this;\n if (n.child instanceof ModuleInstantiationStmt) {\n if (\n this.firstModuleInstantation &&\n n.child.tokens.name.hasNewlineInExtraTokens()\n ) {\n c = this.copyWithIndent();\n c.firstModuleInstantation = false;\n }\n } else {\n c.firstModuleInstantation = true;\n }\n if (n.child) source += n.child.accept(c);\n }\n return source;\n }\n visitModuleDeclarationStmt(n: ModuleDeclarationStmt): string {\n let source = \"\";\n source += this.stringifyExtraTokens(n.tokens.moduleKeyword);\n source += \"module \";\n source += this.stringifyExtraTokens(n.tokens.name);\n source += (n.tokens.name as LiteralToken).value;\n source += this.stringifyExtraTokens(n.tokens.firstParen);\n source += \"(\";\n for (let i = 0; i < n.definitionArgs.length; i++) {\n const arg = n.definitionArgs[i];\n source += arg.accept(this);\n }\n source += this.stringifyExtraTokens(n.tokens.secondParen);\n source += \")\";\n if (!this.config.definitionsOnly) {\n if (!(n.stmt instanceof NoopStmt)) {\n source += \" \";\n }\n source += n.stmt.accept(this);\n }\n return source;\n }\n visitFunctionDeclarationStmt(n: FunctionDeclarationStmt): string {\n let source = \"\";\n source += this.stringifyExtraTokens(n.tokens.functionKeyword);\n source += \"function \";\n source += this.stringifyExtraTokens(n.tokens.name);\n source += n.name;\n source += this.stringifyExtraTokens(n.tokens.firstParen);\n source += \"(\";\n for (let i = 0; i < n.definitionArgs.length; i++) {\n const arg = n.definitionArgs[i];\n source += arg.accept(this);\n // if (i < n.definitionArgs.length - 1) {\n // source += \", \";\n // }\n }\n source += this.stringifyExtraTokens(n.tokens.secondParen);\n source += \")\";\n if (!this.config.definitionsOnly) {\n source += this.stringifyExtraTokens(n.tokens.equals);\n source += \" = \";\n source += n.expr.accept(this.copyWithIndent());\n source += this.stringifyExtraTokens(n.tokens.semicolon);\n source += \";\" + this.newLine(false, \"afterFunctionDeclaration\");\n }\n return source;\n }\n visitBlockStmt(n: BlockStmt): string {\n let source = \"\";\n source += this.stringifyExtraTokens(n.tokens.firstBrace);\n let withIndent = this.copyWithIndent();\n source += \"{\" + withIndent.newLine(false, \"beforeBlockStmt\");\n if (this.doNotAddNewlineAfterBlockStatement) {\n withIndent.doNotAddNewlineAfterBlockStatement = false;\n }\n for (const stmt of n.children) {\n source += withIndent.processStatementWithBreakIfNeeded(stmt);\n }\n source += withIndent.stringifyExtraTokens(n.tokens.secondBrace);\n // erease indentation\n if (\n n.tokens.secondBrace.extraTokens[\n n.tokens.secondBrace.extraTokens.length - 1\n ] instanceof NewLineExtraToken\n ) {\n source = source.substring(0, source.length - this.config.indentCount);\n }\n source += \"}\";\n if (!this.doNotAddNewlineAfterBlockStatement) {\n this.newLineAfterNextComment(\"afterBlockStmt\");\n }\n return source;\n }\n visitNoopStmt(n: NoopStmt): string {\n let source = \"\";\n source += this.stringifyExtraTokens(n.tokens.semicolon);\n source += \";\";\n return source;\n }\n visitIfElseStatement(n: IfElseStatement): string {\n let source = \"\";\n source += this.stringifyExtraTokens(n.tokens.ifKeyword);\n source += \"if\";\n source += this.stringifyExtraTokens(n.tokens.firstParen);\n source += \"(\";\n source += n.cond.accept(this);\n source += this.stringifyExtraTokens(n.tokens.secondParen);\n source += \")\";\n if (!(n.thenBranch instanceof NoopStmt)) {\n source += \" \";\n }\n source += n.thenBranch.accept(\n n.tokens.elseKeyword\n ? this.copyWithDoNotAddNewlineAfterBlockStatement()\n : this\n );\n if (n.tokens.elseKeyword && n.elseBranch) {\n source += this.stringifyExtraTokens(n.tokens.elseKeyword);\n source += \" else\";\n if (!(n.elseBranch instanceof NoopStmt)) {\n source += \" \";\n }\n source += n.elseBranch.accept(this);\n }\n return source;\n }\n visitAnonymousFunctionExpr(n: AnonymousFunctionExpr): string {\n let source = \"\";\n source += this.stringifyExtraTokens(n.tokens.functionKeyword);\n source += \"function\";\n source += this.stringifyExtraTokens(n.tokens.firstParen);\n source += \"(\";\n for (let i = 0; i < n.definitionArgs.length; i++) {\n const arg = n.definitionArgs[i];\n source += arg.accept(this);\n }\n source += this.stringifyExtraTokens(n.tokens.secondParen);\n source += \")\";\n if (!this.config.definitionsOnly) {\n source += n.expr.accept(this.copyWithIndent());\n }\n return source;\n }\n\n /**\n * Tries printing a ModuleInstantiationStmt without breaking it, if it exceeds 40 chars it breaks it, by printing it again.\n * @param stmt\n */\n protected processStatementWithBreakIfNeeded(stmt: Statement) {\n if (stmt instanceof ModuleInstantiationStmt) {\n const saved = this.saveDeepGlobals();\n const line = stmt.accept(this);\n\n // try finding the first line without a comment and break if it is too long\n const firstRealLine = line\n .split(\"\\n\")\n .find((l) => !!l.split(\"//\")[0].trim());\n\n if (\n firstRealLine &&\n firstRealLine.length > this.config.moduleInstantiationBreakLength\n ) {\n this.restoreDeepGlobals(saved);\n return stmt.accept(this.copyWithBreakBetweenModuleInstantations());\n }\n return line;\n } else {\n return stmt.accept(this);\n }\n }\n\n protected stringifyExtraTokens(token: Token) {\n const source = token.extraTokens\n .map((et) => {\n if (et instanceof NewLineExtraToken) {\n if (this.deepGlobals.didAddNewline) {\n this.deepGlobals.didAddNewline = false;\n return \"\";\n }\n this.deepGlobals.shouldAddNewlineAfterNextComment = false;\n return this.newLine(true, \"forcedNewlineExtraToken\");\n }\n\n if (\n !this.config.definitionsOnly &&\n (et instanceof MultiLineComment || et instanceof SingleLineComment)\n ) {\n let commentText = \"\";\n if (this.deepGlobals.shouldAddNewlineAfterNextComment) {\n commentText += \" \"; // add a spece since we are in the same line as the previous token\n }\n if (et instanceof MultiLineComment) {\n commentText += \"/*\" + et.contents + \"*/\";\n } else if (et instanceof SingleLineComment) {\n commentText += \"//\" + et.contents;\n }\n\n // here we execute some logic to make sure that a newline is inserted after the comment if needed\n // since the information about the comments and newlines is stored in the next token, we need to do this weird stuff\n if (this.deepGlobals.shouldAddNewlineAfterNextComment) {\n this.deepGlobals.shouldAddNewlineAfterNextComment = false;\n return (\n commentText +\n this.newLine(\n false,\n this.deepGlobals.newlineAfterNextCommentReason\n )\n );\n }\n\n return commentText;\n }\n return \"\";\n })\n .reduce((prev, curr) => prev + curr, \"\");\n this.deepGlobals.didAddNewline = false;\n if (source === \"\" && this.deepGlobals.shouldAddNewlineAfterNextComment) {\n this.deepGlobals.shouldAddNewlineAfterNextComment = false;\n return this.newLine(\n false,\n this.deepGlobals.newlineAfterNextCommentReason\n );\n }\n return source;\n }\n protected newLine(forced = false, newlineReason = \"no reason\") {\n if (!forced) {\n this.deepGlobals.didAddNewline = true;\n }\n if (this.config.debugNewlines) {\n return ` /* NL: ${newlineReason} */` + \"\\n\" + this.makeIndent();\n }\n return \"\\n\" + this.makeIndent();\n }\n\n /**\n * Schedules a newline to be added after the next comment, if present.\n * Otherwise it will be inserted immediately.\n */\n protected newLineAfterNextComment(reason: string) {\n this.deepGlobals.shouldAddNewlineAfterNextComment = true;\n this.deepGlobals.newlineAfterNextCommentReason = reason;\n }\n\n protected makeIndent() {\n let ind = \"\";\n for (let i = 0; i < this.indentLevel * this.config.indentCount; i++) {\n ind += this.config.indentChar;\n }\n return ind;\n }\n\n protected copy() {\n const next = new ASTPrinter(this.config);\n next.indentLevel = this.indentLevel;\n next.deepGlobals = this.deepGlobals;\n next.breakBetweenModuleInstantations = this.breakBetweenModuleInstantations;\n return next;\n }\n\n protected copyWithIndent() {\n const next = this.copy();\n next.indentLevel++;\n return next;\n }\n\n protected copyWithBreakBetweenModuleInstantations(doBreak = true) {\n const next = this.copy();\n next.breakBetweenModuleInstantations = doBreak;\n return next;\n }\n\n protected copyWithDoNotAddNewlineAfterBlockStatement(val = true) {\n const next = this.copy();\n next.doNotAddNewlineAfterBlockStatement = val;\n return next;\n }\n\n protected saveDeepGlobals() {\n return JSON.parse(JSON.stringify(this.deepGlobals));\n }\n\n protected restoreDeepGlobals(dat: any) {\n for (const k of Object.keys(dat)) {\n (this.deepGlobals as any)[k] = dat[k];\n }\n }\n}\n", "// Empty shims for Node.js builtins that openscad-parser references\n// but doesn't use in browser context (fs, path, os are only used for\n// file loading features like include/use which we don't support)\nmodule.exports = {};\n", "import * as fs from \"fs\";\nimport * as path from \"path\";\n\nexport default class CodeFile {\n constructor(public path: string, public code: string) {}\n\n get filename() {\n return path.basename(this.path);\n }\n\n /**\n * Loads an openscad file from the filesystem.\n */\n static async load(pathToLoad: string): Promise {\n pathToLoad = path.resolve(pathToLoad); // normalize the path\n const contents = await new Promise((res, rej) => {\n fs.readFile(\n pathToLoad,\n {\n encoding: \"utf8\",\n },\n (err, data) => {\n if (err) {\n rej(err);\n return;\n }\n res(data);\n }\n );\n });\n return new CodeFile(pathToLoad, contents);\n }\n}\n", "import CodeFile from \"./CodeFile\";\n\n/**\n * THe number of lines to display when printing the context of the error.\n */\nconst CONTEXT_LINES_BEFORE = 5;\n\nexport default class CodeLocation {\n constructor(\n file: CodeFile | null = null,\n char: number = 0,\n line: number = 0,\n col: number = 0\n ) {\n this.file = file;\n this.char = char;\n this.line = line;\n this.col = col;\n }\n\n /**\n * THe file to which this location points.\n */\n readonly file: CodeFile | null;\n\n /**\n * The character offset in the file contents.\n */\n readonly char: number = 0;\n\n /**\n * The line number of this location. Zero-indexed.\n */\n readonly line: number = 0;\n\n /**\n * The column number of this location. Zero-indexed.\n */\n readonly col: number = 0;\n\n toString(): string {\n return `file '${this.filename}' line ${\n this.line + 1\n } column ${this.col + 1}'`;\n }\n\n formatWithContext() {\n if(!this.file) {\n throw new Error(\"No CodeFile associated with this location\");\n }\n let outStr = `${this.filename}:${this.line + 1}:${this.col}:\\n`;\n const sourceLines = this.file.code.split(\"\\n\");\n const contextStartIndex = Math.max(0, this.line - CONTEXT_LINES_BEFORE);\n\n const linesToDisplay = sourceLines.slice(contextStartIndex, this.line + 1);\n outStr += linesToDisplay.reduce((prev, line, index) => {\n return (\n prev +\n ` ${(contextStartIndex + index + 1).toString().padStart(3)}| ${line}\\n`\n );\n }, \"\");\n outStr += \"\";\n for (let i = -5; i < this.col; i++) {\n outStr += \" \";\n }\n outStr += \"^\\n\";\n return outStr;\n }\n\n private get filename(): string {\n return this?.file?.filename || \"\"\n }\n}\n", "import CodeError from \"./errors/CodeError\";\n\nexport default class ErrorCollector {\n errors: CodeError[] = [];\n reportError(err: ET): ET {\n this.errors.push(err);\n return err;\n }\n printErrors() {\n const msgs = this.errors.reduce((prev, e) => {\n return (\n prev +\n e.codeLocation.formatWithContext() +\n Object.getPrototypeOf(e).constructor.name +\n \": \" +\n e.message +\n \"\\n\"\n );\n }, \"\");\n console.log(msgs);\n }\n hasErrors() {\n return this.errors.length > 0;\n }\n /**\n * Throws the first error on the list. Used to simplify testing.\n */\n throwIfAny() {\n if (this.errors.length > 0) {\n throw this.errors[0];\n }\n }\n}\n", "export default class FormattingConfiguration {\n indentChar = \" \";\n indentCount = 4;\n moduleInstantiationBreakLength = 40;\n\n /**\n * When sets to true the printer does not print bodies of functions and modules.\n * Used for generating focumentation stubs.\n */\n definitionsOnly = false;\n\n /**\n * When set to true the formatter adds a comment to each newline describing its purpose.\n */\n debugNewlines = false;\n\n}\n", "import CodeLocation from \"../CodeLocation\";\n\n/**\n * A root class for all the errors generated during parsing and lexing.\n * @category Error\n */\nexport default abstract class CodeError extends Error {\n constructor(public codeLocation: CodeLocation, message: string) {\n super(message);\n }\n}\n", "import CodeError from \"./CodeError\";\n\n/**\n * @category Error\n */\nexport default class LexingError extends CodeError {}\n", "import CodeLocation from \"../CodeLocation\";\nimport LexingError from \"./LexingError\";\n\n/**\n * @category Error\n */\nexport class UnterminatedMultilineCommentLexingError extends LexingError {\n constructor(pos: CodeLocation) {\n super(pos, `Unterminated multiline comment.`);\n }\n}\n\n/**\n * @category Error\n */\nexport class SingleCharacterNotAllowedLexingError extends LexingError {\n constructor(pos: CodeLocation, char: string) {\n super(pos, `Single '${char}' is not allowed.`);\n }\n}\n\n/**\n * @category Error\n */\nexport class UnexpectedCharacterLexingError extends LexingError {\n constructor(pos: CodeLocation, char: string) {\n super(pos, `Unexpected character '${char}'.`);\n }\n}\n\n/**\n * @category Error\n */\nexport class IllegalStringEscapeSequenceLexingError extends LexingError {\n constructor(pos: CodeLocation, sequence: string) {\n super(pos, `Illegal string escape sequence '${sequence}'.`);\n }\n}\n\n/**\n * @category Error\n */\nexport class UnterminatedStringLiteralLexingError extends LexingError {\n constructor(pos: CodeLocation) {\n super(pos, `Unterminated string literal.`);\n }\n}\n\n/**\n * @category Error\n */\nexport class TooManyDotsInNumberLiteralLexingError extends LexingError {\n constructor(pos: CodeLocation, lexeme: string) {\n super(\n pos,\n `Too many dots in number literal ${lexeme}. Number literals must contain zero or one dot.`\n );\n }\n}\n\n/**\n * @category Error\n */\nexport class TooManyEInNumberLiteralLexingError extends LexingError {\n constructor(pos: CodeLocation, lexeme: string) {\n super(\n pos,\n `Too many 'e' separators in number literal ${lexeme}. Number literals must contain zero or one 'e'.`\n );\n }\n}\n\n/**\n * @category Error\n */\nexport class InvalidNumberLiteralLexingError extends LexingError {\n constructor(pos: CodeLocation, lexeme: string) {\n super(pos, `Invalid number literal ${lexeme}.`);\n }\n}\n\n/**\n * @category Error\n */\nexport class UnterminatedFilenameLexingError extends LexingError {\n constructor(pos: CodeLocation) {\n super(pos, `Unterminated filename.`);\n }\n}\n", "import TokenType from \"./TokenType\";\n\n/**\n * A dictionary which maps keyword string values to their TokenType.\n */\nconst keywords: { [x: string]: TokenType } = {\n true: TokenType.True,\n false: TokenType.False,\n undef: TokenType.Undef,\n module: TokenType.Module,\n function: TokenType.Function,\n if: TokenType.If,\n else: TokenType.Else,\n for: TokenType.For,\n assert: TokenType.Assert,\n each: TokenType.Each,\n echo: TokenType.Echo,\n use: TokenType.Use,\n let: TokenType.Let,\n include: TokenType.Include,\n};\n\nexport const keywordDocumentation: { [x: keyof typeof keywords]: string } = {\n true: \"Represents the boolean value true.\",\n false: \"Represents the boolean value false.\",\n undef: `Represents the undefined value. \n\nIt's the initial value of a variable that hasn't been assigned a value, and it is often returned as a result by functions or operations that are passed illegal arguments. `,\n module: `Starts a module declaration.\n\nUsage:\n\n${\"```scad\"}\nmodule my_module(arg = \"default\") {\n // module code\n}\n${\"```\"}\n`,\n function: `Starts a function declaration.\n\nUsage:\n\n${\"```scad\"}\nfunction my_function (x) = x * x;\n\n// or for anonymous functions\nsquare = function (x) x * x;\n\n${\"```\"}\n`,\n if: `Starts an if statement or expression.\n\nUsage:\n\n${\"```scad\"}\nif (x > 0) {\n // do something\n}\n${\"```\"}\n`,\n else: `Marks the beginning of an else block in an if statement.\n\nUsage:\n${\"```scad\"}\nif (x > 0) {\n // if x is positive\n} else {\n // if x is zero or negative\n}\n${\"```\"}\n`,\n for: `Starts a for loop.\n\nUsage:\n\n${\"```scad\"}\nfor ( i = [0 : 5] ){\n rotate( i * 60, [1, 0, 0])\n translate([0, 10, 0])\n sphere(r = 10);\n}\n${\"```\"}\n`,\n assert: `Starts an assert statement.\n\nUsage:\n\n${\"```scad\"}\nassert(x > 0, \"x is not positive\");\n\n${\"```\"}\n`,\n};\n\nexport default keywords;\n", "import CodeSpan from \"./CodeSpan\";\nimport Token from \"./Token\";\nimport TokenType from \"./TokenType\";\n\n/**\n * This represents a token which contains a literal value (e.g. string literal, number literal and identifiers.).\n */\nexport default class LiteralToken extends Token {\n constructor(\n type: TokenType,\n span: CodeSpan,\n lexeme: string,\n public value: ValueT\n ) {\n super(type, span, lexeme);\n }\n}\n", "import CodeFile from \"./CodeFile\";\nimport CodeLocation from \"./CodeLocation\";\nimport CodeSpan from \"./CodeSpan\";\nimport ErrorCollector from \"./ErrorCollector\";\nimport {\n IllegalStringEscapeSequenceLexingError,\n InvalidNumberLiteralLexingError,\n SingleCharacterNotAllowedLexingError,\n TooManyDotsInNumberLiteralLexingError,\n TooManyEInNumberLiteralLexingError,\n UnexpectedCharacterLexingError,\n UnterminatedFilenameLexingError,\n UnterminatedMultilineCommentLexingError,\n UnterminatedStringLiteralLexingError,\n} from \"./errors/lexingErrors\";\nimport {\n ExtraToken,\n MultiLineComment,\n NewLineExtraToken,\n SingleLineComment,\n} from \"./extraTokens\";\nimport keywords from \"./keywords\";\nimport LiteralToken from \"./LiteralToken\";\nimport Token from \"./Token\";\nimport TokenType from \"./TokenType\";\n\n/**\n * The lexer is responsible for turning a string of characters into a stream of\n * tokens. The tokens are then used by the parser to build an abstract syntax\n * tree.\n *\n * The lexer handles parsing of string literals, digraphs (e.g. `<=`), and numbers.\n * It also handles detecting keywords and identifiers.\n */\nexport default class Lexer {\n protected start!: CodeLocation;\n protected startWithWhitespace!: CodeLocation;\n public tokens: Token[] = [];\n protected currentExtraTokens: ExtraToken[] = [];\n\n protected charOffset = 0;\n protected lineOffset = 0;\n protected colOffset = 0;\n protected _currLocCache: CodeLocation | null = null;\n\n constructor(\n public codeFile: CodeFile,\n public errorCollector: ErrorCollector\n ) {}\n /**\n * Scans the whole CodeFile and splits it into tokens.\n * @throws LexingError\n */\n scan(): Token[] {\n this.start = this.getLoc();\n this.startWithWhitespace = this.getLoc();\n while (!this.isAtEnd()) {\n this.start = this.getLoc();\n this.scanToken();\n }\n this.start = this.getLoc();\n this.addToken(TokenType.Eot);\n return this.tokens;\n }\n\n protected scanToken() {\n const c = this.advance();\n switch (c) {\n case \"(\":\n this.addToken(TokenType.LeftParen);\n break;\n case \")\":\n this.addToken(TokenType.RightParen);\n break;\n case \"{\":\n this.addToken(TokenType.LeftBrace);\n break;\n case \"}\":\n this.addToken(TokenType.RightBrace);\n break;\n case \"[\":\n this.addToken(TokenType.LeftBracket);\n break;\n case \"]\":\n this.addToken(TokenType.RightBracket);\n break;\n case \"+\":\n this.addToken(TokenType.Plus);\n break;\n case \"-\":\n this.addToken(TokenType.Minus);\n break;\n case \"%\":\n this.addToken(TokenType.Percent);\n break;\n case \"*\":\n this.addToken(TokenType.Star);\n break;\n case \"^\":\n this.addToken(TokenType.Caret);\n break;\n case \"/\":\n if (this.match(\"/\")) {\n const comment = new SingleLineComment(this.getLoc(), \"\");\n // consume a comment\n while (this.peek() != \"\\n\" && !this.isAtEnd()) {\n comment.contents += this.advance();\n }\n this.currentExtraTokens.push(comment);\n } else if (this.match(\"*\")) {\n const comment = new MultiLineComment(this.getLoc(), \"\");\n\n // multiline comment\n while (\n !(this.peek() == \"*\" && this.peekNext() == \"/\") &&\n !this.isAtEnd()\n ) {\n comment.contents += this.advance();\n }\n if (this.isAtEnd()) {\n throw this.errorCollector.reportError(\n new UnterminatedMultilineCommentLexingError(this.getLoc())\n );\n }\n this.currentExtraTokens.push(comment);\n this.advance(); // advance the star\n this.advance(); // advance the slash\n } else {\n this.addToken(TokenType.Slash);\n }\n break;\n case \".\":\n // allow lexing of numbers without the leading 0\n if (/[0-9]/.test(this.peek())) {\n this.consumeNumberLiteral();\n break;\n }\n this.addToken(TokenType.Dot);\n break;\n case \",\":\n this.addToken(TokenType.Comma);\n break;\n case \":\":\n this.addToken(TokenType.Colon);\n break;\n case \"?\":\n this.addToken(TokenType.QuestionMark);\n break;\n case \";\":\n this.addToken(TokenType.Semicolon);\n break;\n case \"#\":\n this.addToken(TokenType.Hash);\n break;\n case \"!\":\n if (this.match(\"=\")) {\n this.addToken(TokenType.BangEqual);\n } else {\n this.addToken(TokenType.Bang);\n }\n break;\n case \"<\":\n if (this.match(\"=\")) {\n this.addToken(TokenType.LessEqual);\n } else {\n this.addToken(TokenType.Less);\n }\n break;\n case \">\":\n if (this.match(\"=\")) {\n this.addToken(TokenType.GreaterEqual);\n } else {\n this.addToken(TokenType.Greater);\n }\n break;\n case \"=\":\n if (this.match(\"=\")) {\n this.addToken(TokenType.EqualEqual);\n } else {\n this.addToken(TokenType.Equal);\n }\n break;\n case \"&\":\n if (this.match(\"&\")) {\n this.addToken(TokenType.AND);\n } else {\n throw this.errorCollector.reportError(\n new SingleCharacterNotAllowedLexingError(this.getLoc(), \"&\")\n );\n }\n break;\n case \"|\":\n if (this.match(\"|\")) {\n this.addToken(TokenType.OR);\n } else {\n throw this.errorCollector.reportError(\n new SingleCharacterNotAllowedLexingError(this.getLoc(), \"&\")\n );\n }\n break;\n case \"\\n\":\n this.currentExtraTokens.push(new NewLineExtraToken(this.getLoc()));\n break;\n case \"\\r\":\n case \" \":\n case \"\\t\":\n break; // ignore whitespace\n case '\"':\n this.consumeStringLiteral();\n break;\n default:\n if (/[0-9]/.test(c)) {\n this.consumeNumberOrIdentifierOrKeyword();\n } else if (/[A-Za-z\\$_]/.test(c)) {\n this.consumeIdentifierOrKeyword();\n } else {\n throw this.errorCollector.reportError(\n new UnexpectedCharacterLexingError(this.getLoc(), c)\n );\n }\n }\n }\n protected consumeStringLiteral() {\n let str = \"\";\n while (this.peek() != '\"' && !this.isAtEnd()) {\n const c = this.advance();\n // handle escape sequences\n if (c == \"\\\\\") {\n if (this.match('\"')) {\n str += '\"';\n } else if (this.match(\"\\\\\")) {\n str += \"\\\\\";\n } else if (this.match(\"n\")) {\n str += \"\\n\";\n } else if (this.match(\"t\")) {\n str += \"\\t\";\n } else if (this.match(\"r\")) {\n str += \"\\r\";\n } else {\n throw this.errorCollector.reportError(\n new IllegalStringEscapeSequenceLexingError(this.getLoc(), `\\\\${c}`)\n );\n }\n //TODO: Add unicode escape sequences handling\n } else {\n str += c;\n }\n }\n if (this.isAtEnd()) {\n throw this.errorCollector.reportError(\n new UnterminatedStringLiteralLexingError(this.getLoc())\n );\n }\n this.advance();\n this.addToken(TokenType.StringLiteral, str);\n }\n protected consumeNumberLiteral() {\n let ateDigit = /[0-9]/.test(this.codeFile.code[this.start.char]);\n let ateDot = \".\" === this.codeFile.code[this.start.char];\n let justAteExp = false;\n\n while (\n /[0-9]/.test(this.peek()) ||\n (this.peek() == \".\" && /[0-9]/.test(this.peekNext())) ||\n ((this.peek() == \"e\" || this.peek() == \"E\") &&\n /[0-9\\-+]/.test(this.peekNext())) ||\n (this.peek() == \"-\" && /[0-9]/.test(this.peekNext()) && justAteExp) ||\n (this.peek() == \"+\" && /[0-9]/.test(this.peekNext()) && justAteExp) ||\n (this.peek() == \".\" && ateDigit && !ateDot)\n ) {\n ateDigit = ateDigit || /[0-9]/.test(this.peek());\n ateDot = ateDot || this.peek() == \".\";\n justAteExp = this.peek() == \"e\" || this.peek() == \"E\";\n this.advance();\n }\n const lexeme = this.codeFile.code.substring(\n this.start.char,\n this.charOffset\n );\n if ((lexeme.match(/\\./g) || []).length > 1) {\n throw this.errorCollector.reportError(\n new TooManyDotsInNumberLiteralLexingError(this.getLoc(), lexeme)\n );\n }\n if ((lexeme.match(/e/g) || []).length > 1) {\n throw this.errorCollector.reportError(\n new TooManyEInNumberLiteralLexingError(this.getLoc(), lexeme)\n );\n }\n const value = parseFloat(lexeme);\n if (isNaN(value) || !isFinite(value)) {\n throw this.errorCollector.reportError(\n new InvalidNumberLiteralLexingError(this.getLoc(), lexeme)\n );\n }\n this.addToken(TokenType.NumberLiteral, value);\n }\n protected consumeIdentifierOrKeyword() {\n while (/[A-Za-z0-9_\\$]/.test(this.peek()) && !this.isAtEnd()) {\n this.advance();\n }\n const lexeme = this.codeFile.code.substring(\n this.start.char,\n this.charOffset\n );\n if (lexeme in keywords) {\n const keywordType = keywords[lexeme];\n this.addToken(keywordType);\n // check if we need to lex a filename\n if (keywordType === TokenType.Use || keywordType === TokenType.Include) {\n this.consumeFileNameInChevrons();\n }\n return;\n }\n this.addToken(TokenType.Identifier, lexeme);\n }\n\n protected consumeNumberOrIdentifierOrKeyword() {\n // OpenSCAD does accept identifiers starting with a digit.\n // `9e9e9=1;echo(9e9e9);` is a valid code, `9e9=1;` produces a syntax error.\n // Docs don't specify how conflicts are resolved, but from experiments\n // it seems like a number is chosen unless an identifier is a longer match.\n // That would be consistent with how lex/flex generated lexers work.\n\n let wordLength = 1;\n while (\n this.start.char + wordLength < this.codeFile.code.length &&\n /[0-9a-zA-Z_\\$]/.test(this.codeFile.code[this.start.char + wordLength])\n ) {\n wordLength++;\n }\n\n const possibleNumberStarts = [\n this.peekRegex(/^[0-9]+/),\n this.peekRegex(/^[0-9]+[.]/),\n this.peekRegex(/^[0-9]+[eE][+-]?[0-9]+/),\n ];\n const numberLength = Math.max(...possibleNumberStarts.map((x) => x.length));\n\n // If number is longer or same length as an indentifier - number wins.\n if (numberLength >= wordLength) {\n return this.consumeNumberLiteral();\n } else {\n return this.consumeIdentifierOrKeyword();\n }\n }\n\n protected consumeFileNameInChevrons() {\n this.startWithWhitespace = this.getLoc();\n while (!this.isAtEnd()) {\n this.start = this.getLoc();\n if (\n this.match(\"\\n\") ||\n this.match(\"\\t\") ||\n this.match(\"\\r\") ||\n this.match(\" \")\n )\n continue; // ignore whitespace\n\n if (this.match(\"<\")) break;\n // The openscad parser does not allow putting comments like this: `use /* ddd*/ `\n // We must check that and report an error\n throw this.errorCollector.reportError(\n new UnexpectedCharacterLexingError(this.getLoc(), this.advance())\n );\n }\n if (this.isAtEnd()) {\n throw this.errorCollector.reportError(\n new UnterminatedFilenameLexingError(this.getLoc())\n );\n }\n let filename = \"\";\n let didEnd = false;\n while (!this.isAtEnd()) {\n const c = this.advance();\n if (c === \">\") {\n didEnd = true;\n break;\n }\n filename += c;\n }\n if (!didEnd) {\n throw this.errorCollector.reportError(\n new UnterminatedFilenameLexingError(this.getLoc())\n );\n }\n this.addToken(TokenType.FilenameInChevrons, filename);\n }\n\n /**\n * Adds a token to the token list. If a value is provieded a LiteralToken is pushed.\n *\n * Additionally it handles clearing and attaching the extra tokens.\n */\n protected addToken(\n tokenType: TokenType,\n value: TValue | null = null\n ) {\n const lexeme = this.codeFile.code.substring(\n this.start.char,\n this.charOffset\n );\n let token;\n if (value != null) {\n token = new LiteralToken(\n tokenType,\n new CodeSpan(this.start, this.getLoc()),\n lexeme,\n value\n );\n } else {\n token = new Token(\n tokenType,\n new CodeSpan(this.start, this.getLoc()),\n lexeme\n );\n }\n token.extraTokens = this.currentExtraTokens;\n token.startWithWhitespace = this.startWithWhitespace;\n this.startWithWhitespace = this.getLoc();\n this.currentExtraTokens = [];\n this.tokens.push(token);\n }\n protected isAtEnd() {\n return this.charOffset >= this.codeFile.code.length;\n }\n protected match(expected: string) {\n if (this.isAtEnd()) return false;\n if (this.codeFile.code[this.charOffset] !== expected) return false;\n this.advance();\n return true;\n }\n protected advance() {\n const c = this.codeFile.code[this.charOffset];\n this.charOffset++;\n if (c === \"\\n\") {\n this.lineOffset++;\n this.colOffset = 0;\n } else {\n this.colOffset++;\n }\n this._currLocCache = null;\n return c;\n }\n\n protected getLoc() {\n if (!this._currLocCache) {\n this._currLocCache = new CodeLocation(\n this.codeFile,\n this.charOffset,\n this.lineOffset,\n this.colOffset\n );\n }\n return this._currLocCache;\n }\n\n protected peek() {\n if (this.isAtEnd()) return \"\\0\";\n return this.codeFile.code[this.charOffset];\n }\n protected peekNext() {\n if (this.charOffset + 1 >= this.codeFile.code.length) return \"\\0\";\n return this.codeFile.code[this.charOffset + 1];\n }\n\n protected peekRegex(regex: RegExp) {\n const text = this.codeFile.code.slice(this.start.char);\n const match = regex.exec(text);\n return !match || match.index !== 0 ? \"\" : match[0];\n }\n}\n", "/**\n * Adds special flags for built in constructs in the language.\n * Only for use in the prelude.\n * Used to mark the `for` and `intersection_for` modules as loops, and their arguments are in fact variable declarations.\n */\nexport class IntrinsicAnnotation {\n static annotationTag = \"intrinsic\";\n intrinsicType: string;\n constructor(contents: string[]) {\n this.intrinsicType = contents[0] || \"\";\n }\n}\n\n/**\n * Renames this symbol to a diffrent name (which for example is a reserved keyword).\n * Used by the prelude to define `for` and `intersection_for` so that they can be resolved without errors.\n */\nexport class IntrinsicRenameAnnotation {\n static annotationTag = \"intrinsicRename\";\n newName: string;\n constructor(contents: string[]) {\n this.newName = contents[0] || \"\";\n }\n}\n\n/**\n * An annotation with a link to online documentation.\n * @todo Add links to other source-code locations\n */\nexport class SeeAnnotation {\n static annotationTag = \"see\";\n link: string;\n constructor(contents: string[]) {\n this.link = contents[0] || \"\";\n }\n}\n\n/**\n * Describes a module or function parameter annotation.\n * It has the form of `@param name [... optional tags] description`\n * The tags either contain a name (`[positional]`) for binary tags or a name and a value (`[conflictsWith=abc,cba]`)\n */\nexport class ParamAnnotation {\n static annotationTag = \"param\";\n link: string;\n description: string;\n tags: {\n [x: string]: any;\n positional: boolean;\n named: boolean;\n required: boolean;\n type: string[];\n conflictsWith: string[];\n possibleValues: string[];\n } = {\n positional: false,\n named: false,\n required: false,\n type: [],\n conflictsWith: [],\n possibleValues: [],\n };\n constructor(contents: string[]) {\n this.link = contents[0] || \"\";\n this.description = contents\n .slice(1)\n .filter((c) => {\n let m = c.match(/^\\[(.*?)(=(.*))?\\]$/);\n if (!m) return true;\n if (!m[3]) {\n // boolean tag, no value\n this.tags[m[1]] = true;\n } else {\n this.tags[m[1]] = m[3].split(\",\");\n }\n return false;\n })\n .join(\" \");\n }\n}\n", "import {\n ExtraToken,\n MultiLineComment,\n NewLineExtraToken,\n SingleLineComment,\n} from \"../extraTokens\";\nimport {\n IntrinsicAnnotation,\n IntrinsicRenameAnnotation,\n ParamAnnotation,\n SeeAnnotation,\n} from \"./annotations\";\nimport DocAnnotationClass from \"./DocAnnotationClass\";\n\nexport default class DocComment {\n static possibleAnnotations: DocAnnotationClass[] = [\n IntrinsicAnnotation,\n IntrinsicRenameAnnotation,\n ParamAnnotation,\n SeeAnnotation,\n ];\n constructor(\n public documentationContent: string,\n public annotations: Object[]\n ) {}\n static fromExtraTokens(extraTokens: ExtraToken[]): DocComment {\n const docComments: (MultiLineComment | SingleLineComment)[] = [];\n let beginningNewlinesLimit = 5;\n // iterate through the extra tokens backwards, looking from the annotated element\n for (let i = extraTokens.length - 1; i >= 0; i--) {\n if (extraTokens[i] instanceof NewLineExtraToken) {\n beginningNewlinesLimit--;\n }\n if (\n extraTokens[i] instanceof MultiLineComment ||\n extraTokens[i] instanceof SingleLineComment\n ) {\n beginningNewlinesLimit = 2;\n docComments.unshift(\n extraTokens[i] as MultiLineComment | SingleLineComment\n );\n }\n if (beginningNewlinesLimit <= 0) {\n break;\n }\n }\n // we assemble the comments into one string, and remove the preceding stars\n const lines = docComments\n .map((c) => c.contents)\n .flatMap((c) => c.split(\"\\n\"))\n .map((l) => l.trim().replace(/^\\*/, \"\").trim());\n let contents = \"\";\n let annotations: Object[] = [];\n // we loop over every line of the preceeding comment to find the documentation contents and the annotations\n // for each line we check if it stats with a @ (annotation)\n for (const line of lines) {\n if (line.startsWith(\"@\")) {\n // this is an annotation\n const segments = line.substring(1).split(\" \");\n let foundAnnotation = false;\n for (const possible of this.possibleAnnotations) {\n if (possible.annotationTag === segments[0]) {\n annotations.push(new possible(segments.slice(1)));\n foundAnnotation = true;\n break;\n }\n }\n if (foundAnnotation) {\n continue;\n }\n }\n\n contents += line + \"\\n\";\n }\n contents = contents.trim();\n return new DocComment(contents, annotations);\n }\n}\n", "import CodeError from \"./CodeError\";\n\n/**\n * @category Error\n */\nexport default class ParsingError extends CodeError {}\n", "import TokenType from \"./TokenType\";\n\nexport default {\n [TokenType.AND]: \"'&&' (AND)\",\n [TokenType.Assert]: \"'assert' (Assert)\",\n [TokenType.Bang]: \"'!' (Bang)\",\n [TokenType.BangEqual]: \"'!=' (BangEqual)\",\n [TokenType.Colon]: \"':' (Colon)\",\n [TokenType.Comma]: \"',' (Comma)\",\n [TokenType.Dot]: \"'.' (Dot)\",\n [TokenType.Each]: \"'each' (Each)\",\n [TokenType.Echo]: \"'echo' (Echo)\",\n [TokenType.Else]: \"'else' (Else)\",\n [TokenType.Eot]: \"end of file (Eot)\",\n [TokenType.Equal]: \"'=' (Equal)\",\n [TokenType.EqualEqual]: \"'==' (EqualEqual)\",\n [TokenType.Error]: \" (Error)\",\n [TokenType.False]: \"'false' (False)\",\n [TokenType.For]: \"'for' (For)\",\n [TokenType.Function]: \"'function' (Function)\",\n [TokenType.Greater]: \"'>' (Greater)\",\n [TokenType.GreaterEqual]: \"'>=' (GreaterEqual)\",\n [TokenType.Hash]: \"'#' (Hash)\",\n [TokenType.Identifier]: \"identifier (Identifier)\",\n [TokenType.If]: \"'if' (If)\",\n [TokenType.LeftBrace]: \"'{' (LeftBrace)\",\n [TokenType.LeftBracket]: \"'[' (LeftBracket)\",\n [TokenType.LeftParen]: \"'(' (LeftParen)\",\n [TokenType.Less]: \"'<' (Less)\",\n [TokenType.LessEqual]: \"'<=' (LessEqual)\",\n [TokenType.Let]: \"'let' (Let)\",\n [TokenType.Minus]: \"'-' (Minus)\",\n [TokenType.Module]: \"'module' (Module)\",\n [TokenType.NumberLiteral]: \"number literal (NumberLiteral)\",\n [TokenType.OR]: \"'||' (OR)\",\n [TokenType.Percent]: \"'%' (Percent)\",\n [TokenType.Plus]: \"'+' (Plus)\",\n [TokenType.QuestionMark]: \"'?' (QuestionMark)\",\n [TokenType.RightBrace]: \"'}' (RightBrace)\",\n [TokenType.RightBracket]: \"']' (RightBracket)\",\n [TokenType.RightParen]: \"')' (RightParen)\",\n [TokenType.Semicolon]: \"';' (Semicolon)\",\n [TokenType.Slash]: \"'/' (Slash)\",\n [TokenType.Star]: \"'*' (Star)\",\n [TokenType.Caret]: \"'^' (Caret)\",\n [TokenType.StringLiteral]: \"string literal (StringLiteral)\",\n [TokenType.True]: \"'true' (True)\",\n [TokenType.Undef]: \"'undef' (Undef)\",\n [TokenType.Use]: \"'use' (Use)\",\n [TokenType.FilenameInChevrons]: \"filename (FilenameInChevrons)\",\n [TokenType.Include]: \"'include' (Include)\",\n};\n", "import CodeLocation from \"../CodeLocation\";\nimport friendlyTokenNames from \"../friendlyTokenNames\";\nimport TokenType from \"../TokenType\";\nimport ParsingError from \"./ParsingError\";\n\n/**\n * @category Error\n */\nexport class UnterminatedUseStatementParsingError extends ParsingError {\n constructor(pos: CodeLocation) {\n super(pos, `Unterminated 'use' statement.`);\n }\n}\n\n/**\n * @category Error\n */\nexport class UnexpectedTokenParsingError extends ParsingError {\n constructor(pos: CodeLocation, tt: TokenType, extraMsg?: string) {\n if (extraMsg) {\n super(pos, `Unexpected token ${friendlyTokenNames[tt]}${extraMsg}`);\n } else {\n super(pos, `Unexpected token ${friendlyTokenNames[tt]}.`);\n }\n }\n}\n\n/**\n * @category Error\n */\nexport class UnexpectedTokenWhenStatementParsingError extends UnexpectedTokenParsingError {\n constructor(pos: CodeLocation, tt: TokenType) {\n super(pos, tt, `, expected statement.`);\n }\n}\n\n/**\n * @category Error\n */\nexport class UnexpectedTokenAfterIdentifierInStatementParsingError extends UnexpectedTokenParsingError {\n constructor(pos: CodeLocation, tt: TokenType) {\n super(\n pos,\n tt,\n `, expected ${friendlyTokenNames[TokenType.LeftParen]} or ${\n friendlyTokenNames[TokenType.Equal]\n } after identifier in statement.`\n );\n }\n}\n\n/**\n * @category Error\n */\nexport class UnexpectedEndOfFileBeforeModuleInstantiationParsingError extends ParsingError {\n constructor(pos: CodeLocation) {\n super(pos, `Unexpected end of file before module instantiation.`);\n }\n}\n\n/**\n * @category Error\n */\nexport class UnterminatedParametersListParsingError extends ParsingError {\n constructor(pos: CodeLocation) {\n super(pos, `Unterminated parameters list.`);\n }\n}\n\n/**\n * @category Error\n */\nexport class UnexpectedTokenInNamedArgumentsListParsingError extends UnexpectedTokenParsingError {\n constructor(pos: CodeLocation, tt: TokenType) {\n super(pos, tt, ` in named arguments list.`);\n }\n}\n\n/**\n * @category Error\n */\nexport class UnterminatedForLoopParamsParsingError extends ParsingError {\n constructor(pos: CodeLocation) {\n super(pos, `Unterminated for loop params.`);\n }\n}\n\n/**\n * @category Error\n */\nexport class UnexpectedTokenInForLoopParamsListParsingError extends UnexpectedTokenParsingError {\n constructor(pos: CodeLocation, tt: TokenType) {\n super(pos, tt, ` in for loop params list.`);\n }\n}\n\n/**\n * @category Error\n */\nexport class FailedToMatchPrimaryExpressionParsingError extends ParsingError {\n constructor(pos: CodeLocation) {\n super(pos, `Failed to match primary expression.`);\n }\n}\n\n/**\n * @category Error\n */\nexport class UnterminatedVectorExpressionParsingError extends ParsingError {\n constructor(pos: CodeLocation) {\n super(pos, `Unterminated vector literal.`);\n }\n}\n\n/**\n * @category Error\n */\nexport class ConsumptionParsingError extends UnexpectedTokenParsingError {\n constructor(\n pos: CodeLocation,\n public real: TokenType,\n public expected: TokenType,\n where: string\n ) {\n super(pos, real, `, expected ${friendlyTokenNames[expected]} ${where}.`);\n }\n}\n\n/**\n * @category Error\n */\nexport class UnexpectedCommentBeforeUseChevronParsingError extends ParsingError {\n constructor(pos: CodeLocation) {\n super(pos, `Comments are illegal before '<' in the use statement.`);\n }\n}\n\n/**\n * @category Error\n */\nexport class UnexpectedUseStatementParsingError extends ParsingError {\n constructor(pos: CodeLocation) {\n super(\n pos,\n `Use ('use <...>') statements are only allowed at the root scope of the file, not inside of blocks.`\n );\n }\n}\n\n/**\n * @category Error\n */\nexport class UnexpectedIncludeStatementParsingError extends ParsingError {\n constructor(pos: CodeLocation) {\n super(\n pos,\n `Include ('include <...>') statements are only allowed at the root scope of the file, not inside of blocks.`\n );\n }\n}\n", "import AssignmentNode, { AssignmentNodeRole } from \"./ast/AssignmentNode\";\nimport ErrorNode from \"./ast/ErrorNode\";\nimport {\n AnonymousFunctionExpr,\n ArrayLookupExpr,\n AssertExpr,\n BinaryOpExpr,\n EchoExpr,\n Expression,\n FunctionCallExpr,\n GroupingExpr,\n LcEachExpr,\n LcForCExpr,\n LcForExpr,\n LcIfExpr,\n LcLetExpr,\n LetExpr,\n ListComprehensionExpression,\n LiteralExpr,\n LookupExpr,\n MemberLookupExpr,\n RangeExpr,\n TernaryExpr,\n UnaryOpExpr,\n VectorExpr,\n} from \"./ast/expressions\";\nimport ScadFile from \"./ast/ScadFile\";\nimport {\n BlockStmt,\n FunctionDeclarationStmt,\n IfElseStatement,\n IncludeStmt,\n ModuleDeclarationStmt,\n ModuleInstantiationStmt,\n NoopStmt,\n Statement,\n UseStmt,\n} from \"./ast/statements\";\nimport CodeFile from \"./CodeFile\";\nimport CodeLocation from \"./CodeLocation\";\nimport { IntrinsicRenameAnnotation } from \"./comments/annotations\";\nimport DocComment from \"./comments/DocComment\";\nimport ErrorCollector from \"./ErrorCollector\";\nimport ParsingError from \"./errors/ParsingError\";\nimport {\n ConsumptionParsingError,\n FailedToMatchPrimaryExpressionParsingError,\n UnexpectedEndOfFileBeforeModuleInstantiationParsingError,\n UnexpectedIncludeStatementParsingError,\n UnexpectedTokenAfterIdentifierInStatementParsingError,\n UnexpectedTokenInForLoopParamsListParsingError,\n UnexpectedTokenInNamedArgumentsListParsingError,\n UnexpectedTokenWhenStatementParsingError,\n UnexpectedUseStatementParsingError,\n UnterminatedForLoopParamsParsingError,\n UnterminatedParametersListParsingError,\n UnterminatedVectorExpressionParsingError,\n} from \"./errors/parsingErrors\";\nimport keywords from \"./keywords\";\nimport LiteralToken from \"./LiteralToken\";\nimport Token from \"./Token\";\nimport TokenType from \"./TokenType\";\n\nconst moduleInstantiationTagTokens = [\n TokenType.Bang,\n TokenType.Hash,\n TokenType.Percent,\n TokenType.Star,\n];\n\nconst keywordModuleNames = [\n TokenType.For,\n TokenType.Let,\n TokenType.Assert,\n TokenType.Echo,\n TokenType.Each,\n TokenType.If,\n];\n\nconst listComprehensionElementKeywords = [\n TokenType.For,\n TokenType.Let,\n TokenType.Each,\n TokenType.If,\n];\n\nexport default class Parser {\n protected currentToken = 0;\n\n /**\n * The code file being parsed.\n */\n public code: CodeFile;\n\n /**\n * The tokens being parsed. They have to be provided from the lexer\n * @see [[Lexer.scan]]\n */\n public tokens: Token[];\n\n /**\n * The ErrorCollector for this parser. All the errors encountered by the parser will be put there, since it does not throw on non-fatal errors.\n */\n public errorCollector: ErrorCollector;\n\n constructor(code: CodeFile, tokens: Token[], errorCollector: ErrorCollector) {\n this.code = code;\n this.tokens = tokens;\n this.errorCollector = errorCollector;\n }\n\n /**\n * Attempts to parse a file and return the AST with the ScadFile as a root node.\n * @throws ParsingError\n */\n parse(): ScadFile {\n const statements: Statement[] = [];\n while (!this.isAtEnd()) {\n statements.push(this.statement(true));\n }\n const eot = this.peek();\n return new ScadFile(statements, { eot });\n }\n\n protected synchronize(e: ParsingError) {\n if (e instanceof ConsumptionParsingError) {\n if (e.expected === TokenType.Semicolon) {\n if (this.peek().hasNewlineInExtraTokens()) {\n return;\n }\n }\n }\n if (e instanceof FailedToMatchPrimaryExpressionParsingError) {\n if (this.peek().hasNewlineInExtraTokens()) {\n // assume that when there is a newline we want to parse the next statement\n return;\n }\n }\n if (e instanceof UnexpectedTokenAfterIdentifierInStatementParsingError) {\n if (this.peek().hasNewlineInExtraTokens()) {\n return;\n }\n }\n this.advance();\n while (!this.isAtEnd()) {\n if (this.previous().type === TokenType.Semicolon) return;\n switch (this.peek().type) {\n case TokenType.Module:\n case TokenType.Function:\n case TokenType.If:\n case TokenType.For:\n case TokenType.Echo:\n case TokenType.Assert:\n case TokenType.Let:\n return;\n }\n this.advance();\n }\n }\n\n /**\n * Parses a statement, including `use` and `include` when isAtRoot is set to true.\n * @param isAtRoot whther we are parsing a statement in the root of the file, set to false inside blocks or modules.\n */\n protected statement(isAtRoot = false) {\n const syncStartToken = this.currentToken;\n const syncStartLocation = this.getLocation();\n try {\n if (this.matchToken(TokenType.Use)) {\n if (!isAtRoot) {\n throw this.errorCollector.reportError(\n new UnexpectedUseStatementParsingError(this.getLocation())\n );\n }\n const useKeyword = this.previous();\n const filenameToken: LiteralToken = this.consume(\n TokenType.FilenameInChevrons,\n \"after 'use' keyword\"\n ) as LiteralToken;\n\n return new UseStmt(filenameToken.value, {\n useKeyword,\n filename: filenameToken,\n });\n }\n if (this.matchToken(TokenType.Include)) {\n if (!isAtRoot) {\n throw this.errorCollector.reportError(\n new UnexpectedIncludeStatementParsingError(this.getLocation())\n );\n }\n const includeKeyword = this.previous();\n const filenameToken: LiteralToken = this.consume(\n TokenType.FilenameInChevrons,\n \"after 'include' keyword\"\n ) as LiteralToken;\n\n return new IncludeStmt(filenameToken.value, {\n includeKeyword,\n filename: filenameToken,\n });\n }\n if (this.matchToken(TokenType.Semicolon)) {\n const semicolon = this.previous();\n return new NoopStmt({ semicolon });\n }\n if (this.matchToken(TokenType.LeftBrace)) {\n return this.blockStatement();\n }\n if (this.matchToken(TokenType.Module)) {\n return this.moduleDeclarationStatement();\n }\n if (this.matchToken(TokenType.Function)) {\n return this.functionDeclarationStatement();\n }\n const assignmentOrInst = this.matchAssignmentOrModuleInstantation();\n if (assignmentOrInst) {\n return assignmentOrInst;\n }\n throw this.errorCollector.reportError(\n new UnexpectedTokenWhenStatementParsingError(\n this.getLocation(),\n this.peek().type\n )\n );\n } catch (e) {\n if (e instanceof ParsingError) {\n this.synchronize(e);\n return new ErrorNode({\n tokens: this.tokens.slice(syncStartToken, this.currentToken),\n });\n } else {\n throw e;\n }\n }\n }\n protected matchAssignmentOrModuleInstantation() {\n // identifiers can mean either an instantiation is incoming or an assignment\n if (this.matchToken(TokenType.Identifier)) {\n if (this.peek().type === TokenType.Equal) {\n return this.assignmentStatement();\n }\n if (this.peek().type === TokenType.LeftParen) {\n return this.moduleInstantiationStatement();\n }\n throw this.errorCollector.reportError(\n new UnexpectedTokenAfterIdentifierInStatementParsingError(\n this.getLocation(),\n this.peek().type\n )\n );\n }\n if (\n this.matchToken(...moduleInstantiationTagTokens, ...keywordModuleNames)\n ) {\n return this.moduleInstantiationStatement();\n }\n return null;\n }\n protected blockStatement() {\n const firstBrace = this.previous();\n const startLocation = this.getLocation();\n const innerStatements: Statement[] = [];\n while (!this.checkToken(TokenType.RightBrace) && !this.isAtEnd()) {\n innerStatements.push(this.statement());\n }\n this.consume(TokenType.RightBrace, \"after block statement\");\n const secondBrace = this.previous();\n return new BlockStmt(innerStatements, {\n firstBrace,\n secondBrace,\n });\n }\n protected moduleDeclarationStatement(): ModuleDeclarationStmt {\n const moduleKeyword = this.previous();\n const nameToken = this.consume(\n TokenType.Identifier,\n \"after 'module' keyword\"\n );\n this.consume(TokenType.LeftParen, \"after module name\");\n const firstParen = this.previous();\n const args: AssignmentNode[] = this.args();\n const secondParen = this.previous();\n const body = this.statement();\n const doc = DocComment.fromExtraTokens(moduleKeyword.extraTokens);\n let name = (nameToken as LiteralToken).value;\n\n // handle renaming of the symbol via annotations in documentation comments\n // used by the prelude\n const renameAnnotation = doc.annotations.find(\n (a) => a instanceof IntrinsicRenameAnnotation\n ) as IntrinsicRenameAnnotation;\n if (renameAnnotation) {\n name = renameAnnotation.newName;\n }\n return new ModuleDeclarationStmt(\n name,\n args,\n body,\n {\n moduleKeyword,\n name: nameToken,\n firstParen,\n secondParen,\n },\n doc\n );\n }\n protected functionDeclarationStatement(): FunctionDeclarationStmt {\n const functionKeyword = this.previous();\n const nameToken = this.consume(\n TokenType.Identifier,\n \"after 'function' keyword\"\n );\n this.consume(TokenType.LeftParen, \"after function name\");\n const firstParen = this.previous();\n const args = this.args();\n const secondParen = this.previous();\n this.consume(TokenType.Equal, \"after function parameters\");\n const equals = this.previous();\n const body = this.expression();\n this.consume(TokenType.Semicolon, \"after function declaration\");\n const semicolon = this.previous();\n return new FunctionDeclarationStmt(\n (nameToken as LiteralToken).value,\n args,\n body,\n {\n functionKeyword,\n equals,\n firstParen,\n name: nameToken,\n secondParen,\n semicolon,\n },\n DocComment.fromExtraTokens(functionKeyword.extraTokens)\n );\n }\n\n protected assignmentStatement() {\n const pos = this.getLocation();\n const name = this.previous() as LiteralToken;\n this.consume(TokenType.Equal, \"after assignment name\");\n const equals = this.previous();\n const expr = this.expression();\n this.consume(TokenType.Semicolon, \"after assignment statement\");\n const semicolon = this.previous();\n const node = new AssignmentNode(\n name.value,\n expr,\n AssignmentNodeRole.VARIABLE_DECLARATION,\n {\n name,\n equals,\n trailingCommas: null,\n semicolon,\n }\n );\n node.docComment = DocComment.fromExtraTokens(name.extraTokens);\n return node;\n }\n protected moduleInstantiationStatement():\n | ModuleInstantiationStmt\n | IfElseStatement {\n if (this.isAtEnd()) {\n throw this.errorCollector.reportError(\n new UnexpectedEndOfFileBeforeModuleInstantiationParsingError(\n this.getLocation()\n )\n );\n }\n if (this.previous().type === TokenType.Bang) {\n const tagToken = this.previous();\n this.advance();\n const mod = this.moduleInstantiationStatement();\n mod.tagRoot = true;\n mod.tokens.modifiersInOrder.push(tagToken);\n return mod;\n }\n if (this.previous().type === TokenType.Hash) {\n const tagToken = this.previous();\n this.advance();\n const mod = this.moduleInstantiationStatement();\n mod.tagHighlight = true;\n mod.tokens.modifiersInOrder.push(tagToken);\n return mod;\n }\n if (this.previous().type === TokenType.Percent) {\n const tagToken = this.previous();\n this.advance();\n const mod = this.moduleInstantiationStatement();\n mod.tagBackground = true;\n mod.tokens.modifiersInOrder.push(tagToken);\n return mod;\n }\n if (this.previous().type === TokenType.Star) {\n const tagToken = this.previous();\n this.advance();\n const mod = this.moduleInstantiationStatement();\n mod.tagDisabled = true;\n mod.tokens.modifiersInOrder.push(tagToken);\n return mod;\n }\n const mod = this.singleModuleInstantiation();\n if (!(mod instanceof IfElseStatement)) {\n mod.child = this.statement();\n }\n return mod;\n }\n protected ifElseStatement(): IfElseStatement {\n const ifKeyword = this.previous();\n this.consume(TokenType.LeftParen, \"after the if keyword\");\n const firstParen = this.previous();\n const cond = this.expression();\n this.consume(TokenType.RightParen, \"after the if condition\");\n const secondParen = this.previous();\n const thenBranch = this.statement();\n let elseBranch: Statement | null = null;\n let elseKeyword = null;\n if (this.matchToken(TokenType.Else)) {\n elseKeyword = this.previous();\n elseBranch = this.statement();\n }\n return new IfElseStatement(cond, thenBranch, elseBranch, {\n ifKeyword,\n elseKeyword,\n firstParen,\n secondParen,\n modifiersInOrder: [],\n });\n }\n protected singleModuleInstantiation() {\n const prev = this.previous();\n if (prev.type === TokenType.If) {\n return this.ifElseStatement();\n }\n this.consume(TokenType.LeftParen, \"after module instantation\");\n const firstParen = this.previous();\n let name!: string;\n if (prev instanceof LiteralToken) {\n name = prev.value as string;\n } else {\n for (const keywordName of Object.keys(keywords)) {\n if (keywords[keywordName] === prev.type) {\n name = keywordName;\n break;\n }\n }\n }\n let isForLoop = name === \"for\" || name === \"intersection_for\";\n const args = this.args(true, isForLoop ? AssignmentNodeRole.VARIABLE_DECLARATION : null);\n const secondParen = this.previous();\n return new ModuleInstantiationStmt(name, args, null, {\n firstParen,\n name: prev,\n secondParen,\n modifiersInOrder: [],\n });\n }\n /**\n * Parses an argument list including the finishing paren. Can handle trailing and extra commas as well as an empty arguments list.\n * The initial paren must be consumed.\n * @param allowPositional Set to true when in call mode, positional arguments will be allowed.\n */\n protected args(\n allowPositional = false,\n forceType: AssignmentNodeRole | null = null\n ): AssignmentNode[] {\n this.consumeUselessCommas();\n const args: AssignmentNode[] = [];\n if (this.matchToken(TokenType.RightParen)) {\n return args;\n }\n while (true) {\n if (this.isAtEnd()) {\n break;\n }\n if (!allowPositional && this.peek().type !== TokenType.Identifier) {\n // error out when we encounter a positional argument when it is not allowed\n break;\n }\n let value: Expression | null = null;\n let name: string;\n let nameToken: Token | null = null;\n let equals: Token | null = null;\n if (!allowPositional || this.peekNext().type === TokenType.Equal) {\n // this is a named parameter\n name = (this.advance() as LiteralToken).value;\n nameToken = this.previous();\n // a value is provided for this param\n if (this.matchToken(TokenType.Equal)) {\n equals = this.previous();\n value = this.expression();\n }\n } else {\n name = \"\";\n value = this.expression();\n // this is a positional paramater\n }\n\n const arg = new AssignmentNode(\n name,\n value,\n forceType == null\n ? allowPositional\n ? AssignmentNodeRole.ARGUMENT_ASSIGNMENT\n : AssignmentNodeRole.ARGUMENT_DECLARATION\n : forceType,\n {\n name: nameToken,\n equals,\n semicolon: null,\n trailingCommas: [],\n }\n );\n args.push(arg);\n\n if (this.matchToken(TokenType.Comma)) {\n arg.tokens.trailingCommas!.push(this.previous());\n this.consumeUselessCommas(arg.tokens.trailingCommas!);\n if (this.matchToken(TokenType.RightParen)) {\n return args;\n }\n continue;\n }\n this.consumeUselessCommas(arg.tokens.trailingCommas!);\n // end of named arguments\n if (this.matchToken(TokenType.RightParen)) {\n return args;\n }\n }\n if (this.isAtEnd()) {\n throw this.errorCollector.reportError(\n new UnterminatedParametersListParsingError(this.getLocation())\n );\n }\n throw this.errorCollector.reportError(\n new UnexpectedTokenInNamedArgumentsListParsingError(\n this.getLocation(),\n this.advance().type\n )\n );\n }\n /**\n * Parses arguments from the 'for' loop comprehension.\n * The initial paren must be consumed. Stops on semicolon or right paren, but does not consume them.\n */\n protected forComprehensionArgs(): AssignmentNode[] {\n this.consumeUselessCommas();\n const args: AssignmentNode[] = [];\n if (\n this.checkToken(TokenType.RightParen) ||\n this.checkToken(TokenType.Semicolon)\n ) {\n return args;\n }\n while (true) {\n if (this.isAtEnd()) {\n break;\n }\n\n let arg;\n\n if (\n this.peek().type === TokenType.Identifier &&\n this.peekNext().type === TokenType.Equal\n ) {\n // Named for loop variable\n const name = (this.advance() as LiteralToken).value;\n const nameToken = this.previous();\n // a value is provided for this param\n this.consume(\n TokenType.Equal,\n \"after variable name in the 'for' list comprehension\"\n );\n const equals = this.previous();\n const value = this.expression();\n\n arg = new AssignmentNode(\n name,\n value,\n AssignmentNodeRole.VARIABLE_DECLARATION,\n {\n equals,\n semicolon: null,\n name: nameToken,\n trailingCommas: [],\n }\n );\n args.push(arg);\n } else {\n // This condition handles this a pathological case where the for list comprehension can\n // have a single expression without any variable declaration.\n // This can be used to repeat the same element a number of times.\n // See: https://github.com/alufers/openscad-parser/issues/27\n const value = this.expression();\n arg = new AssignmentNode(\n \"\",\n value,\n AssignmentNodeRole.ARGUMENT_ASSIGNMENT,\n {\n equals: null,\n semicolon: null,\n name: null,\n trailingCommas: [],\n }\n );\n args.push(arg);\n }\n\n if (this.matchToken(TokenType.Comma)) {\n arg.tokens.trailingCommas!.push(this.previous());\n this.consumeUselessCommas(arg.tokens.trailingCommas!);\n if (\n this.checkToken(TokenType.RightParen) ||\n this.checkToken(TokenType.Semicolon)\n ) {\n return args;\n }\n continue;\n }\n this.consumeUselessCommas(arg.tokens.trailingCommas!);\n if (\n this.checkToken(TokenType.RightParen) ||\n this.checkToken(TokenType.Semicolon)\n ) {\n return args;\n }\n }\n if (this.isAtEnd()) {\n throw this.errorCollector.reportError(\n new UnterminatedForLoopParamsParsingError(this.getLocation())\n );\n }\n throw this.errorCollector.reportError(\n new UnexpectedTokenInForLoopParamsListParsingError(\n this.getLocation(),\n this.advance().type\n )\n );\n }\n /**\n * Consumes redundant commas and returns true if it consumed any.\n *\n * You can also pass an array of tokens to which all the comma tokens will be pushed.\n */\n protected consumeUselessCommas(trailingArr?: Token[]) {\n let ret = false;\n while (this.matchToken(TokenType.Comma) && !this.isAtEnd()) {\n if (trailingArr) {\n trailingArr.push(this.previous());\n }\n ret = true;\n }\n return ret;\n }\n protected expression(): Expression {\n return this.ternary();\n }\n /**\n * Parses the ternary '? :' expression\n */\n protected ternary() {\n let expr = this.logicalOr();\n while (this.matchToken(TokenType.QuestionMark)) {\n const questionMark = this.previous();\n const thenBranch = this.ternary();\n this.consume(TokenType.Colon, \"between ternary expression branches\");\n const colon = this.previous();\n const elseBranch = this.ternary();\n expr = new TernaryExpr(expr, thenBranch, elseBranch, {\n questionMark,\n colon,\n });\n }\n return expr;\n }\n /**\n * Parses the '||' operators\n */\n protected logicalOr() {\n let expr = this.logicalAnd();\n while (this.matchToken(TokenType.OR)) {\n const operator = this.previous();\n const right = this.logicalAnd();\n expr = new BinaryOpExpr(expr, operator.type, right, {\n operator,\n });\n }\n return expr;\n }\n /**\n * Parses the '&&' operators\n */\n protected logicalAnd() {\n let expr = this.equality();\n while (this.matchToken(TokenType.AND)) {\n const operator = this.previous();\n const right = this.equality();\n expr = new BinaryOpExpr(expr, operator.type, right, {\n operator,\n });\n }\n return expr;\n }\n /**\n * Parses the '==' and '!=' operators.\n */\n protected equality(): Expression {\n let expr = this.comparsion();\n while (this.matchToken(TokenType.EqualEqual, TokenType.BangEqual)) {\n const operator = this.previous();\n const right = this.comparsion();\n expr = new BinaryOpExpr(expr, operator.type, right, {\n operator,\n });\n }\n return expr;\n }\n protected comparsion(): Expression {\n let expr = this.addition();\n while (\n this.matchToken(\n TokenType.Less,\n TokenType.LessEqual,\n TokenType.Greater,\n TokenType.GreaterEqual\n )\n ) {\n const operator = this.previous();\n const right = this.addition();\n expr = new BinaryOpExpr(expr, operator.type, right, {\n operator,\n });\n }\n return expr;\n }\n protected addition(): Expression {\n let expr = this.multiplication();\n while (this.matchToken(TokenType.Plus, TokenType.Minus)) {\n const operator = this.previous();\n const right = this.multiplication();\n expr = new BinaryOpExpr(expr, operator.type, right, {\n operator,\n });\n }\n return expr;\n }\n protected multiplication(): Expression {\n let expr = this.exponentiation();\n while (\n this.matchToken(TokenType.Star, TokenType.Slash, TokenType.Percent)\n ) {\n const operator = this.previous();\n const right = this.exponentiation();\n expr = new BinaryOpExpr(expr, operator.type, right, {\n operator,\n });\n }\n return expr;\n }\n\n /**\n * Parses b ^ e.\n */\n protected exponentiation(): Expression {\n let expr = this.unary();\n while (this.matchToken(TokenType.Caret)) {\n const operator = this.previous();\n const right = this.unary();\n expr = new BinaryOpExpr(expr, operator.type, right, {\n operator,\n });\n }\n return expr;\n }\n\n /**\n * Parses +expr, -expr and !expr.\n */\n protected unary(): Expression {\n if (this.matchToken(TokenType.Plus, TokenType.Minus, TokenType.Bang)) {\n const operator = this.previous();\n const right = this.unary();\n return new UnaryOpExpr(operator.type, right, {\n operator,\n });\n }\n return this.memberLookupOrArrayLookup();\n }\n protected memberLookupOrArrayLookup() {\n let expr = this.primary();\n while (true) {\n if (this.matchToken(TokenType.Dot)) {\n const dot = this.previous();\n const name = this.consume(\n TokenType.Identifier,\n \"after '.'\"\n ) as LiteralToken;\n expr = new MemberLookupExpr(expr, name.value, {\n dot,\n memberName: name,\n });\n } else if (this.matchToken(TokenType.LeftBracket)) {\n const firstBracket = this.previous();\n const index = this.expression();\n this.consume(TokenType.RightBracket, \"after array index expression\");\n const secondBracket = this.previous();\n expr = new ArrayLookupExpr(expr, index, {\n firstBracket,\n secondBracket,\n });\n } else if (this.matchToken(TokenType.LeftParen)) {\n expr = this.finishCall(expr);\n } else {\n break;\n }\n }\n return expr;\n }\n protected finishCall(callee: Expression): Expression {\n const firstParen = this.previous();\n const args = this.args(true);\n const secondParen = this.previous();\n return new FunctionCallExpr(callee, args, {\n firstParen,\n secondParen,\n });\n }\n protected primary(): Expression {\n if (this.matchToken(TokenType.True)) {\n return new LiteralExpr(true, {\n literalToken: this.previous() as LiteralToken,\n });\n }\n if (this.matchToken(TokenType.False)) {\n return new LiteralExpr(false, {\n literalToken: this.previous() as LiteralToken,\n });\n }\n if (this.matchToken(TokenType.Undef)) {\n return new LiteralExpr(null, {\n literalToken: this.previous() as LiteralToken,\n });\n }\n if (this.matchToken(TokenType.NumberLiteral)) {\n return new LiteralExpr((this.previous() as LiteralToken).value, {\n literalToken: this.previous() as LiteralToken,\n });\n }\n if (this.matchToken(TokenType.StringLiteral)) {\n return new LiteralExpr((this.previous() as LiteralToken).value, {\n literalToken: this.previous() as LiteralToken,\n });\n }\n if (this.matchToken(TokenType.Identifier)) {\n const tok = this.previous() as LiteralToken;\n return new LookupExpr(tok.value, {\n identifier: tok,\n });\n }\n if (this.matchToken(TokenType.Assert)) {\n const keyword = this.previous();\n this.consume(TokenType.LeftParen, \"after call expression\");\n const firstParen = this.previous();\n const vars = this.args(true);\n const secondParen = this.previous();\n const innerExpr = this.expression();\n return new AssertExpr(vars, innerExpr, {\n firstParen,\n secondParen,\n name: keyword,\n });\n }\n if (this.matchToken(TokenType.Let)) {\n const keyword = this.previous();\n this.consume(TokenType.LeftParen, `after call expression`);\n const firstParen = this.previous();\n const vars = this.args(true);\n const secondParen = this.previous();\n const innerExpr = this.expression();\n return new LetExpr(vars, innerExpr, {\n firstParen,\n secondParen,\n name: keyword,\n });\n }\n if (this.matchToken(TokenType.Echo)) {\n const keyword = this.previous();\n this.consume(TokenType.LeftParen, `after call expression`);\n const firstParen = this.previous();\n const vars = this.args(true);\n const secondParen = this.previous();\n const innerExpr = this.expression();\n return new EchoExpr(vars, innerExpr, {\n firstParen,\n secondParen,\n name: keyword,\n });\n }\n if (this.matchToken(TokenType.Function)) {\n return this.anonymousFunction();\n }\n if (this.matchToken(TokenType.LeftParen)) {\n const firstParen = this.previous();\n const expr = this.expression();\n this.consume(TokenType.RightParen, \"after grouping expression\");\n const secondParen = this.previous();\n return new GroupingExpr(expr, {\n firstParen,\n secondParen,\n });\n }\n if (this.matchToken(TokenType.LeftBracket)) {\n return this.bracketInsides();\n }\n throw this.errorCollector.reportError(\n new FailedToMatchPrimaryExpressionParsingError(this.previous().span.start)\n );\n }\n /**\n * Handles the parsing of vector literals and range literals.\n */\n protected bracketInsides(): Expression {\n const startBracket = this.previous();\n // the openscad bison parser has a weird thing where it allows optional commas only if the brackets represent an empty vector\n // Good: [,,,,,,]\n // Bad: [,,,,10]\n // Bad [,,,,,10: 20 : 20]\n const uselessCommaTokens: Token[] = [];\n if (this.consumeUselessCommas(uselessCommaTokens)) {\n this.consume(\n TokenType.RightBracket,\n \"after leading commas in a vector literal\"\n );\n const secondBracket = this.previous();\n return new VectorExpr([], {\n firstBracket: startBracket,\n secondBracket,\n commas: uselessCommaTokens,\n });\n }\n\n if (this.matchToken(TokenType.RightBracket)) {\n const secondBracket = this.previous();\n return new VectorExpr([], {\n firstBracket: startBracket,\n commas: [],\n secondBracket,\n });\n }\n\n const first = this.listComprehensionElementsOrExpr();\n // check if we are parsing a range\n if (\n !(first instanceof ListComprehensionExpression) &&\n this.matchToken(TokenType.Colon)\n ) {\n const firstColon = this.previous();\n let secondRangeExpr = this.expression();\n let thirdRangeExpr = null;\n let secondColon = null;\n if (this.matchToken(TokenType.Colon)) {\n secondColon = this.previous();\n thirdRangeExpr = this.expression();\n }\n this.consume(\n TokenType.RightBracket,\n \"after expression in a range literal\"\n );\n const secondBracket = this.previous();\n if (thirdRangeExpr) {\n return new RangeExpr(first, secondRangeExpr, thirdRangeExpr, {\n firstBracket: startBracket,\n firstColon,\n secondColon,\n secondBracket,\n });\n } else {\n return new RangeExpr(first, null, secondRangeExpr, {\n firstBracket: startBracket,\n firstColon,\n secondColon,\n secondBracket,\n });\n }\n }\n\n // we are parsing a vector expression\n const vectorLiteral = new VectorExpr([first], {\n commas: [],\n firstBracket: startBracket,\n secondBracket: null as unknown as any, // we will add the second bracket later in the parsing, so we allow to have a null here\n });\n if (this.matchToken(TokenType.Comma)) {\n vectorLiteral.tokens.commas.push(this.previous()); // add the comma to the tokens list, because we matchedIt\n this.consumeUselessCommas(vectorLiteral.tokens.commas);\n if (this.matchToken(TokenType.RightBracket)) {\n vectorLiteral.tokens.secondBracket = this.previous();\n return vectorLiteral;\n }\n while (true) {\n if (this.isAtEnd()) {\n throw this.errorCollector.reportError(\n new UnterminatedVectorExpressionParsingError(this.getLocation())\n );\n }\n\n vectorLiteral.children.push(this.listComprehensionElementsOrExpr());\n if (this.matchToken(TokenType.RightBracket)) {\n vectorLiteral.tokens.secondBracket = this.previous();\n break;\n }\n this.consume(TokenType.Comma, \"after vector literal element\");\n vectorLiteral.tokens.commas.push(this.previous()); // we musn't forget about adding the comma to the array since it may contain comments\n this.consumeUselessCommas(vectorLiteral.tokens.commas);\n if (this.matchToken(TokenType.RightBracket)) {\n vectorLiteral.tokens.secondBracket = this.previous();\n break;\n }\n }\n } else {\n this.consume(\n TokenType.RightBracket,\n \"after the only vector expression element\"\n );\n vectorLiteral.tokens.secondBracket = this.previous();\n }\n\n return vectorLiteral;\n }\n\n protected anonymousFunction(): AnonymousFunctionExpr {\n const functionKeyword = this.previous();\n const firstParen = this.consume(\n TokenType.LeftParen,\n \"after function keyword in anonymous function\"\n );\n const args = this.args();\n const secondParen = this.previous();\n const body = this.expression();\n return new AnonymousFunctionExpr(args, body, {\n functionKeyword,\n firstParen,\n secondParen,\n });\n }\n\n protected listComprehensionElements(): Expression {\n if (this.matchToken(TokenType.Let)) {\n const letKwrd = this.previous();\n this.consume(TokenType.LeftParen, \"after the let keyword\");\n const firstParen = this.previous();\n const args = this.args();\n const secondParen = this.previous();\n const next = this.listComprehensionElementsOrExpr();\n return new LcLetExpr(args, next, {\n letKeyword: letKwrd,\n firstParen,\n secondParen,\n });\n }\n if (this.matchToken(TokenType.Each)) {\n const eachKwrd = this.previous();\n const next = this.listComprehensionElementsOrExpr();\n return new LcEachExpr(next, {\n eachKeyword: eachKwrd,\n });\n }\n if (this.matchToken(TokenType.For)) {\n return this.listComprehensionFor();\n }\n if (this.matchToken(TokenType.If)) {\n const ifKwrd = this.previous();\n this.consume(TokenType.LeftParen, \"after the if keyword\");\n const firstParen = this.previous();\n const cond = this.expression();\n this.consume(\n TokenType.RightParen,\n \"after the if comprehension condition\"\n );\n const secondParen = this.previous();\n const thenBranch = this.listComprehensionElementsOrExpr();\n let elseBranch: Expression | null = null;\n let elseKeyword = null;\n if (this.matchToken(TokenType.Else)) {\n elseKeyword = this.previous();\n elseBranch = this.listComprehensionElementsOrExpr();\n }\n return new LcIfExpr(cond, thenBranch, elseBranch, {\n ifKeyword: ifKwrd,\n elseKeyword,\n firstParen,\n secondParen,\n });\n }\n // we should not get here\n throw new Error(\n \"Unexpected token in list comprehension elements! THIS SHOULD NOT HAPPEN\"\n );\n }\n protected listComprehensionFor(): Expression {\n const forKwrd = this.previous();\n this.consume(\n TokenType.LeftParen,\n \"after for keyword in list comprehension\"\n );\n const firstParen = this.previous();\n const firstArgs = this.forComprehensionArgs();\n if (this.matchToken(TokenType.RightParen)) {\n const secondParen = this.previous();\n return new LcForExpr(firstArgs, this.listComprehensionElementsOrExpr(), {\n forKeyword: forKwrd,\n firstParen,\n secondParen,\n });\n }\n this.consume(\n TokenType.Semicolon,\n \"after first 'for' comprehension parameters\"\n );\n const firstSemicolon = this.previous();\n const condition = this.expression();\n this.consume(TokenType.Semicolon, \"after 'for' comprehension condition\");\n const secondSemicolon = this.previous();\n const secondArgs = this.forComprehensionArgs();\n this.consume(\n TokenType.RightParen,\n \"after second 'for' comprehension parameters\"\n );\n const secondParen = this.previous();\n const next = this.listComprehensionElementsOrExpr();\n return new LcForCExpr(firstArgs, secondArgs, condition, next, {\n firstParen,\n forKeyword: forKwrd,\n firstSemicolon,\n secondParen,\n secondSemicolon,\n });\n }\n protected listComprehensionElementsOrExpr(): Expression {\n // checks if we have a list comprehension element.\n if (\n listComprehensionElementKeywords.includes(this.peek().type) ||\n (this.peek().type === TokenType.LeftParen &&\n listComprehensionElementKeywords.includes(this.peekNext().type))\n ) {\n let withParens = false;\n\n if (this.matchToken(TokenType.LeftParen)) {\n withParens = true;\n }\n const comprElemsResult = this.listComprehensionElements();\n if (withParens) {\n this.consume(\n TokenType.RightParen,\n \"after parenthesized list comprehension expression\"\n );\n }\n return comprElemsResult;\n }\n\n return this.expression();\n }\n protected consume(tt: TokenType, where: string) {\n if (this.checkToken(tt)) {\n return this.advance();\n }\n throw this.errorCollector.reportError(\n new ConsumptionParsingError(\n this.getLocation(),\n this.peek().type,\n tt,\n where\n )\n );\n }\n protected matchToken(...toMatch: TokenType[]) {\n for (const tt of toMatch) {\n if (this.checkToken(tt)) {\n this.advance();\n return true;\n }\n }\n return false;\n }\n protected checkToken(tt: TokenType) {\n if (this.isAtEnd()) {\n return false;\n }\n return this.peek().type == tt;\n }\n protected advance() {\n if (!this.isAtEnd()) {\n this.currentToken++;\n }\n return this.previous();\n }\n protected isAtEnd() {\n return this.peek().type === TokenType.Eot;\n }\n protected peek(): Token {\n return this.tokens[this.currentToken];\n }\n protected peekNext(): Token {\n if (this.tokens[this.currentToken].type === TokenType.Eot) {\n return this.tokens[this.currentToken];\n }\n return this.tokens[this.currentToken + 1];\n }\n protected getLocation() {\n return this.peek().span.start;\n }\n protected previous(): Token {\n return this.tokens[this.currentToken - 1];\n }\n}\n", "import ScadFile from \"./ast/ScadFile\";\nimport CodeFile from \"./CodeFile\";\nimport ErrorCollector from \"./ErrorCollector\";\nimport Lexer from \"./Lexer\";\nimport Parser from \"./Parser\";\nimport Token from \"./Token\";\n\nexport default class ParsingHelper {\n static parseFile(f: CodeFile): [ScadFile | null, ErrorCollector] {\n const errorCollector = new ErrorCollector();\n const lexer = new Lexer(f, errorCollector);\n let tokens: Token[] | undefined;\n try {\n tokens = lexer.scan();\n } catch (e) {}\n if (errorCollector.hasErrors()) {\n return [null, errorCollector];\n }\n if (!tokens) {\n throw new Error(\"No tokens returned from lexer, and no errors were reported\");\n }\n const parser = new Parser(f, tokens, errorCollector);\n let ast: ScadFile | null = null;\n try {\n ast = parser.parse();\n } catch (e) {}\n return [ast, errorCollector];\n }\n}\n", "import AssignmentNode from \"../ast/AssignmentNode\";\nimport {\n FunctionDeclarationStmt,\n ModuleDeclarationStmt,\n} from \"../ast/statements\";\n\nexport type KeysOfType = {\n [P in keyof T]: T[P] extends TProp ? P : never;\n}[keyof T];\n\n/**\n * Represents a lexical scope, where variables, modules, and functions are resolved.\n * It links symbol names with their declarations.\n */\nexport default class Scope {\n /**\n * References to other, 'include'd or 'use'd file scopes, filled by the solution manager.\n * We can use those scopes to resolve types from those files.\n */\n siblingScopes: Scope[] = [];\n parent: Scope | null = null;\n functions = new Map();\n variables = new Map();\n modules = new Map();\n\n copy(): Scope {\n const s = new Scope();\n s.siblingScopes = [...this.siblingScopes];\n s.functions = this.functions;\n s.variables = this.variables;\n s.modules = this.modules;\n return s;\n }\n\n lookupVariable(name: string) {\n return this.lookup(\"variables\", name) as AssignmentNode;\n }\n\n lookupModule(name: string) {\n return this.lookup(\"modules\", name) as ModuleDeclarationStmt;\n }\n\n lookupFunction(name: string) {\n return this.lookup(\"functions\", name) as FunctionDeclarationStmt;\n }\n\n private lookup(\n x: KeysOfType>,\n name: string,\n visited: WeakMap = new WeakMap()\n ): FunctionDeclarationStmt | AssignmentNode | ModuleDeclarationStmt | null {\n if (visited.has(this)) {\n return null;\n }\n visited.set(this, true);\n if (this[x].has(name)) {\n return this[x].get(name) || null;\n }\n if (this.parent) {\n const val = this.parent.lookup(x, name, visited);\n if (val) {\n return val;\n }\n }\n for (const ss of this.siblingScopes) {\n const val = ss.lookup(x, name, visited);\n if (val) {\n return val;\n }\n }\n return null;\n }\n}\n", "import { notStrictEqual } from \"assert\";\nimport AssignmentNode, { AssignmentNodeRole } from \"../ast/AssignmentNode\";\nimport ASTNode from \"../ast/ASTNode\";\nimport ASTVisitor from \"../ast/ASTVisitor\";\nimport ErrorNode from \"../ast/ErrorNode\";\nimport {\n AnonymousFunctionExpr,\n ArrayLookupExpr,\n AssertExpr,\n BinaryOpExpr,\n EchoExpr,\n FunctionCallExpr,\n GroupingExpr,\n LcEachExpr,\n LcForCExpr,\n LcForExpr,\n LcIfExpr,\n LcLetExpr,\n LetExpr,\n LiteralExpr,\n LookupExpr,\n MemberLookupExpr,\n RangeExpr,\n TernaryExpr,\n UnaryOpExpr,\n VectorExpr,\n} from \"../ast/expressions\";\nimport ScadFile from \"../ast/ScadFile\";\nimport {\n BlockStmt,\n FunctionDeclarationStmt,\n IfElseStatement,\n IncludeStmt,\n ModuleDeclarationStmt,\n ModuleInstantiationStmt,\n NoopStmt,\n Statement,\n UseStmt,\n} from \"../ast/statements\";\nimport {\n AnonymousFunctionExprWithScope,\n BlockStmtWithScope,\n FunctionDeclarationStmtWithScope,\n LcForCExprWithScope,\n LcForExprWithScope,\n LcLetExprWithScope,\n LetExprWithScope,\n ModuleDeclarationStmtWithScope,\n ModuleInstantiationStmtWithScope,\n ScadFileWithScope,\n} from \"./nodesWithScopes\";\nimport Scope from \"./Scope\";\n\nexport default class ASTScopePopulator implements ASTVisitor {\n nearestScope: Scope;\n constructor(rootScope: Scope) {\n this.nearestScope = rootScope;\n }\n\n protected copyWithNewNearestScope(newScope: Scope) {\n return new ASTScopePopulator(newScope);\n }\n populate(n: ASTNode) {\n return n.accept(this);\n }\n visitScadFile(n: ScadFile): ASTNode {\n const sf = new ScadFileWithScope(\n n.statements.map((stmt) => stmt.accept(this)),\n n.tokens\n );\n sf.scope = this.nearestScope; // we assume the nearest scope is the root scope, since we are processing the scad file\n return sf;\n }\n visitAssignmentNode(n: AssignmentNode): ASTNode {\n const an = new AssignmentNode(\n n.name,\n n.value ? n.value.accept(this) : null,\n n.role,\n n.tokens\n );\n if (n.name && n.role != AssignmentNodeRole.ARGUMENT_ASSIGNMENT) {\n this.nearestScope.variables.set(an.name, an);\n }\n return an;\n }\n visitUnaryOpExpr(n: UnaryOpExpr): ASTNode {\n return new UnaryOpExpr(n.operation, n.right.accept(this), n.tokens);\n }\n visitBinaryOpExpr(n: BinaryOpExpr): ASTNode {\n return new BinaryOpExpr(\n n.left.accept(this),\n n.operation,\n n.right.accept(this),\n n.tokens\n );\n }\n visitTernaryExpr(n: TernaryExpr): ASTNode {\n return new TernaryExpr(\n n.cond.accept(this),\n n.ifExpr.accept(this),\n n.elseExpr.accept(this),\n n.tokens\n );\n }\n visitArrayLookupExpr(n: ArrayLookupExpr): ASTNode {\n return new ArrayLookupExpr(\n n.array.accept(this),\n n.index.accept(this),\n n.tokens\n );\n }\n visitLiteralExpr(n: LiteralExpr): ASTNode {\n return new LiteralExpr(n.value, n.tokens);\n }\n visitRangeExpr(n: RangeExpr): ASTNode {\n return new RangeExpr(\n n.begin.accept(this),\n n.step ? n.step.accept(this) : null,\n n.end.accept(this),\n n.tokens\n );\n }\n visitVectorExpr(n: VectorExpr): ASTNode {\n return new VectorExpr(\n n.children.map((c) => c.accept(this)),\n n.tokens\n );\n }\n visitLookupExpr(n: LookupExpr): ASTNode {\n return new LookupExpr(n.name, n.tokens);\n }\n visitMemberLookupExpr(n: MemberLookupExpr): ASTNode {\n return new MemberLookupExpr(n.expr.accept(this), n.member, n.tokens);\n }\n visitFunctionCallExpr(n: FunctionCallExpr): ASTNode {\n return new FunctionCallExpr(\n n.callee,\n n.args.map((a) => a.accept(this)) as AssignmentNode[],\n n.tokens\n );\n }\n visitLetExpr(n: LetExpr): ASTNode {\n const letExprWithScope = new LetExprWithScope(\n null as unknown as any,\n null as unknown as any,\n n.tokens\n );\n letExprWithScope.scope = new Scope();\n letExprWithScope.scope.parent = this.nearestScope;\n const copy = this.copyWithNewNearestScope(letExprWithScope.scope);\n letExprWithScope.args = n.args.map((a) =>\n a.accept(copy)\n ) as AssignmentNode[];\n letExprWithScope.expr = n.expr.accept(copy);\n for (const a of letExprWithScope.args) {\n if (a.name) {\n letExprWithScope.scope.variables.set(a.name, a);\n }\n }\n return letExprWithScope;\n }\n visitAssertExpr(n: AssertExpr): ASTNode {\n return new AssertExpr(\n n.args.map((a) => a.accept(this)) as AssignmentNode[],\n n.expr.accept(this),\n n.tokens\n );\n }\n visitEchoExpr(n: EchoExpr): ASTNode {\n return new EchoExpr(\n n.args.map((a) => a.accept(this)) as AssignmentNode[],\n n.expr.accept(this),\n n.tokens\n );\n }\n visitLcIfExpr(n: LcIfExpr): ASTNode {\n return new LcIfExpr(\n n.cond.accept(this),\n n.ifExpr.accept(this),\n n.elseExpr ? n.elseExpr.accept(this) : null,\n n.tokens\n );\n }\n visitLcEachExpr(n: LcEachExpr): ASTNode {\n return new LcEachExpr(n.expr.accept(this), n.tokens);\n }\n visitLcForExpr(n: LcForExpr): ASTNode {\n const newNode = new LcForExprWithScope(\n null as unknown as any,\n null as unknown as any,\n n.tokens\n );\n newNode.scope = new Scope();\n newNode.scope.parent = this.nearestScope;\n const copy = this.copyWithNewNearestScope(newNode.scope);\n newNode.args = n.args.map((a) => a.accept(copy)) as AssignmentNode[];\n newNode.expr = n.expr.accept(copy);\n return newNode;\n }\n visitLcForCExpr(n: LcForCExpr): ASTNode {\n const newNode = new LcForCExprWithScope(\n null as unknown as any,\n null as unknown as any,\n null as unknown as any,\n null as unknown as any,\n n.tokens\n );\n newNode.scope = new Scope();\n newNode.scope.parent = this.nearestScope;\n const copy = this.copyWithNewNearestScope(newNode.scope);\n newNode.args = n.args.map((a) => a.accept(copy)) as AssignmentNode[];\n newNode.incrArgs = n.incrArgs.map((a) =>\n a.accept(copy)\n ) as AssignmentNode[];\n newNode.cond = n.cond.accept(copy);\n newNode.expr = n.expr.accept(copy);\n return newNode;\n }\n visitLcLetExpr(n: LcLetExpr): ASTNode {\n const lcLetWithScopeExpr = new LcLetExprWithScope(\n null as unknown as any,\n null as unknown as any,\n n.tokens\n );\n lcLetWithScopeExpr.scope = new Scope();\n lcLetWithScopeExpr.scope.parent = this.nearestScope;\n const copy = this.copyWithNewNearestScope(lcLetWithScopeExpr.scope);\n lcLetWithScopeExpr.args = n.args.map((a) =>\n a.accept(copy)\n ) as AssignmentNode[];\n lcLetWithScopeExpr.expr = n.expr.accept(copy);\n return lcLetWithScopeExpr;\n }\n visitGroupingExpr(n: GroupingExpr): ASTNode {\n return new GroupingExpr(n.inner.accept(this), n.tokens);\n }\n visitUseStmt(n: UseStmt): ASTNode {\n return n;\n }\n visitIncludeStmt(n: IncludeStmt): ASTNode {\n return n;\n }\n visitModuleInstantiationStmt(n: ModuleInstantiationStmt): ASTNode {\n if (n.name === \"for\" || n.name === \"intersection_for\") {\n const inst = new ModuleInstantiationStmtWithScope(\n n.name,\n null as unknown as any,\n null,\n n.tokens\n );\n inst.scope = new Scope();\n inst.scope.parent = this.nearestScope;\n const copy = this.copyWithNewNearestScope(inst.scope);\n inst.args = n.args.map((a) => a.accept(copy)) as AssignmentNode[];\n inst.child = n.child ? n.child.accept(copy) : null;\n }\n const inst = new ModuleInstantiationStmt(\n n.name,\n n.args.map((a) => a.accept(this)) as AssignmentNode[],\n n.child ? n.child.accept(this) : null,\n n.tokens\n );\n inst.tagRoot = n.tagRoot;\n inst.tagHighlight = n.tagHighlight;\n inst.tagBackground = n.tagBackground;\n inst.tagDisabled = n.tagDisabled;\n return inst;\n }\n visitModuleDeclarationStmt(n: ModuleDeclarationStmt): ASTNode {\n const md = new ModuleDeclarationStmtWithScope(\n n.name,\n null as unknown as any,\n null as unknown as any,\n n.tokens,\n n.docComment\n );\n this.nearestScope.modules.set(md.name, md);\n md.scope = new Scope();\n md.scope.parent = this.nearestScope;\n const copy = this.copyWithNewNearestScope(md.scope);\n md.definitionArgs = n.definitionArgs.map((a) =>\n a.accept(copy)\n ) as AssignmentNode[];\n md.stmt = n.stmt.accept(copy);\n return md;\n }\n visitFunctionDeclarationStmt(n: FunctionDeclarationStmt): ASTNode {\n const fDecl = new FunctionDeclarationStmtWithScope(\n n.name,\n null as unknown as any,\n null as unknown as any,\n n.tokens,\n n.docComment\n );\n this.nearestScope.functions.set(n.name, fDecl);\n fDecl.scope = new Scope();\n fDecl.scope.parent = this.nearestScope;\n const newPopulator = this.copyWithNewNearestScope(fDecl.scope);\n fDecl.definitionArgs = n.definitionArgs.map((a) =>\n a.accept(newPopulator)\n ) as AssignmentNode[];\n fDecl.expr = n.expr.accept(newPopulator);\n return fDecl;\n }\n visitAnonymousFunctionExpr(n: AnonymousFunctionExpr): ASTNode {\n const fDecl = new AnonymousFunctionExprWithScope(\n null as unknown as any,\n null as unknown as any,\n n.tokens\n );\n fDecl.scope = new Scope();\n fDecl.scope.parent = this.nearestScope;\n const newPopulator = this.copyWithNewNearestScope(fDecl.scope);\n fDecl.definitionArgs = n.definitionArgs.map((a) =>\n a.accept(newPopulator)\n ) as AssignmentNode[];\n fDecl.expr = n.expr.accept(newPopulator);\n return fDecl;\n }\n visitBlockStmt(n: BlockStmt): ASTNode {\n const blk = new BlockStmtWithScope(null as unknown as any, n.tokens);\n blk.scope = new Scope();\n blk.scope.parent = this.nearestScope;\n blk.children = n.children.map((c) =>\n c.accept(this.copyWithNewNearestScope(blk.scope))\n ) as Statement[];\n return blk;\n }\n visitNoopStmt(n: NoopStmt): ASTNode {\n return new NoopStmt(n.tokens);\n }\n visitIfElseStatement(n: IfElseStatement): ASTNode {\n return new IfElseStatement(\n n.cond.accept(this),\n n.thenBranch.accept(this),\n n.elseBranch ? n.elseBranch.accept(this) : null,\n n.tokens\n );\n }\n visitErrorNode(n: ErrorNode): ASTNode {\n return new ErrorNode(n.tokens);\n }\n}\n", "import { readFileSync } from \"fs\";\nimport { join } from \"path\";\nimport ScadFile from \"../ast/ScadFile\";\nimport CodeFile from \"../CodeFile\";\nimport ParsingHelper from \"../ParsingHelper\";\nimport ASTScopePopulator from \"../semantic/ASTScopePopulator\";\nimport Scope from \"../semantic/Scope\";\n\nexport default class PreludeUtil {\n private static _cachedPreludeScope: Scope | null = null;\n public static get preludeScope() {\n if (!this._cachedPreludeScope) {\n const preludeLocation = join(__dirname, \"prelude.scad\");\n let [ast, ec] = ParsingHelper.parseFile(\n new CodeFile(preludeLocation, readFileSync(preludeLocation, \"utf8\"))\n );\n ec.throwIfAny();\n this._cachedPreludeScope = new Scope();\n const pop = new ASTScopePopulator(this._cachedPreludeScope);\n if(!ast) {\n throw new Error(\"prelude ast is null\");\n }\n ast = ast.accept(pop) as ScadFile;\n }\n\n return this._cachedPreludeScope;\n }\n}\n", "import AssignmentNode, { AssignmentNodeRole } from \"../ast/AssignmentNode\";\nimport ASTNode from \"../ast/ASTNode\";\nimport {\n FunctionDeclarationStmt,\n ModuleDeclarationStmt,\n} from \"../ast/statements\";\nimport ASTAssembler from \"../ASTAssembler\";\nimport CodeSpan from \"../CodeSpan\";\nimport LiteralToken from \"../LiteralToken\";\nimport Token from \"../Token\";\n\nexport enum SymbolKind {\n MODULE,\n FUNCTION,\n VARIABLE,\n}\n\n/**\n * Generates a symbol tree for the outline view in vscode.\n * It uses AST assembler to walk down the tree and determine the full range of a symbol.\n */\nexport default class ASTSymbolLister extends ASTAssembler {\n constructor(\n public makeSymbol: (\n name: string,\n kind: SymbolKind,\n fullRange: CodeSpan,\n nameRange: CodeSpan,\n children: SymType[]\n ) => SymType\n ) {\n super();\n }\n\n /**\n * Returns the node at pinpointLocation and populates bottomUpHierarchy.\n * @param n The AST (or AST fragment) to search through.\n */\n doList(n: ASTNode): SymType[] {\n n.accept(this);\n return this.symbolsAtCurrentDepth;\n }\n\n private symbolsAtCurrentDepth: SymType[] = [];\n\n protected processAssembledNode(\n t: (Token | (() => Token[]))[],\n self: ASTNode\n ): Token[] {\n let currKind: SymbolKind | null = null;\n let currName: LiteralToken | null = null;\n if (self instanceof FunctionDeclarationStmt) {\n currKind = SymbolKind.FUNCTION;\n currName = self.tokens.name as LiteralToken;\n } else if (self instanceof ModuleDeclarationStmt) {\n currKind = SymbolKind.MODULE;\n currName = self.tokens.name as LiteralToken;\n } else if (\n self instanceof AssignmentNode &&\n self.role === AssignmentNodeRole.VARIABLE_DECLARATION\n ) {\n currKind = SymbolKind.VARIABLE;\n currName = self.tokens.name as LiteralToken;\n }\n const newArr: Token[] = [];\n for (const m of t) {\n if (typeof m === \"function\") {\n newArr.push(...m());\n } else {\n newArr.push(m);\n }\n }\n if (currKind != null && currName != null) {\n let savedSymbols = this.symbolsAtCurrentDepth;\n this.symbolsAtCurrentDepth = [];\n\n const childrenSymbols = this.symbolsAtCurrentDepth;\n this.symbolsAtCurrentDepth = savedSymbols; // restore the symbols\n this.symbolsAtCurrentDepth.push(\n this.makeSymbol(\n currName.value,\n currKind,\n CodeSpan.combine(...newArr.map((t) => t.span)),\n currName.span,\n childrenSymbols\n )\n );\n return newArr;\n } else {\n return newArr;\n }\n }\n}\n", "import {\n AssignmentNode,\n DocComment,\n FunctionDeclarationStmt,\n ModuleDeclarationStmt,\n} from \"..\";\nimport CompletionType from \"./CompletionType\";\n\nexport type Declaration =\n | AssignmentNode\n | ModuleDeclarationStmt\n | FunctionDeclarationStmt;\n\nexport default class CompletionSymbol {\n constructor(\n public type: CompletionType,\n public name: string,\n public decl?: Declaration\n ) {}\n}\n", "enum CompletionType {\n VARIABLE,\n FUNCTION,\n MODULE,\n KEYWORD,\n FILE,\n DIRECTORY,\n}\n\nexport default CompletionType;\n", "import ScadFileProvider, { WithExportedScopes } from \"./ScadFileProvider\";\nimport ScadFile from \"../ast/ScadFile\";\nimport { UseStmt, IncludeStmt } from \"../ast/statements\";\nimport { promises as fs } from \"fs\";\nimport * as os from \"os\";\nimport * as path from \"path\";\nimport ErrorCollector from \"../ErrorCollector\";\nimport CodeError from \"../errors/CodeError\";\nimport CodeLocation from \"../CodeLocation\";\n\nexport class IncludedFileNotFoundError extends CodeError {\n constructor(pos: CodeLocation, filename: string) {\n super(pos, `Included file '${filename} not found.'`);\n }\n}\n\nexport class UsedFileNotFoundError extends CodeError {\n constructor(pos: CodeLocation, filename: string) {\n super(pos, `Used file '${filename} not found.'`);\n }\n}\n\nexport default class IncludeResolver {\n constructor(private provider: ScadFileProvider) {}\n /**\n * Finds all file includes and returns paths to them\n * @param f\n */\n async resolveIncludes(f: ScadFile, ec: ErrorCollector) {\n if (!f.span.start.file) {\n throw new Error(\"file in pos is null\");\n }\n const includes: string[] = [];\n for (const stmt of f.statements) {\n if (stmt instanceof IncludeStmt) {\n const filePath = await this.locateScadFile(\n f.span.start.file.path,\n stmt.filename\n );\n if (!filePath) {\n ec.reportError(\n new IncludedFileNotFoundError(\n stmt.tokens.filename.span.start,\n stmt.filename\n )\n );\n continue;\n }\n includes.push(filePath);\n }\n }\n return Promise.all(\n includes.map((incl) => this.provider.provideScadFile(incl))\n );\n }\n\n /**\n * Finds all file uses and returns paths to them.\n * Uses do not export to parent scopes and do not execute statements inside of the used files.\n * @param f\n */\n async resolveUses(f: ScadFile, ec: ErrorCollector) {\n if(!f.span.start.file) {\n throw new Error(\"file in pos is null\");\n }\n const uses: string[] = [];\n for (const stmt of f.statements) {\n if (stmt instanceof UseStmt) {\n const filePath = await this.locateScadFile(\n f.span.start.file.path,\n stmt.filename\n );\n if (!filePath) {\n ec.reportError(\n new UsedFileNotFoundError(stmt.tokens.filename.span.start, stmt.filename)\n );\n continue;\n }\n uses.push(filePath);\n }\n }\n return Promise.all(uses.map((incl) => this.provider.provideScadFile(incl)));\n }\n\n async locateScadFile(parent: string, relativePath: string) {\n const searchDirs = [path.dirname(parent), ...IncludeResolver.includeDirs];\n for (const dir of searchDirs) {\n const resultingPath = path.resolve(dir, relativePath);\n try {\n if ((await fs.stat(resultingPath)).isFile()) {\n return resultingPath;\n }\n } catch (e) {}\n }\n return null;\n }\n\n private static _includeDirsCache: string[] | null = null;\n\n static get includeDirs() {\n if (!this._includeDirsCache) {\n this._includeDirsCache = [];\n const ENV_SEP = os.platform() === \"win32\" ? \";\" : \":\";\n this._includeDirsCache.push(\n ...(process.env.OPENSCADPATH || \"\").split(ENV_SEP)\n );\n if (os.platform() === \"win32\") {\n // TODO: add my documents path\n // TODO: add installation directory\n }\n if (os.platform() === \"linux\") {\n this._includeDirsCache.push(\n path.join(os.homedir(), \".local/share/OpenSCAD/libraries\")\n );\n this._includeDirsCache.push(\"/usr/share/openscad/libraries\");\n }\n if (os.platform() === \"darwin\") {\n this._includeDirsCache.push(\n path.join(os.homedir(), \"Documents/OpenSCAD/libraries\")\n );\n //TODO: add installation directory\n }\n }\n return this._includeDirsCache;\n }\n}\n", "import CompletionProvider from \"./CompletionProvider\";\nimport CompletionSymbol from \"./CompletionSymbol\";\nimport * as path from \"path\";\nimport { promises as fs } from \"fs\";\nimport CompletionType from \"./CompletionType\";\nimport IncludeResolver from \"./IncludeResolver\";\nimport ASTNode from \"../ast/ASTNode\";\nimport CodeLocation from \"../CodeLocation\";\n/**\n * FilenameCompletionProvider provides completions to the include<> and use<> statements.\n */\nexport default class FilenameCompletionProvider implements CompletionProvider {\n textOnly = true;\n exclusive = true;\n /**\n * Determines whether we are in a include<> or use<> statement\n * @param ast\n * @param loc\n */\n shouldActivate(ast: ASTNode, loc: CodeLocation): boolean {\n return this.getExistingPath(ast, loc) != null;\n }\n\n async getSymbolsAtLocation(\n ast: ASTNode,\n locM: CodeLocation\n ): Promise {\n const loc = new CodeLocation(locM.file, locM.char, locM.line, locM.col);\n let existingPath = this.getExistingPath(ast, loc) || \"\";\n let searchDirs: string[] = [];\n if (path.isAbsolute(existingPath)) {\n searchDirs = [path.dirname(existingPath)];\n } else {\n searchDirs = IncludeResolver.includeDirs.map((id) =>\n path.join(id, path.dirname(existingPath))\n );\n }\n let output: CompletionSymbol[] = [];\n\n for (const sd of searchDirs) {\n try {\n const filenames = (await fs.readdir(sd)).filter((p) =>\n p.startsWith(path.basename(existingPath))\n );\n\n output = [\n ...output,\n ...((\n await Promise.all(\n filenames.map(async (f) => {\n const stat = await fs.stat(path.join(sd, f));\n if (stat.isDirectory()) {\n return new CompletionSymbol(CompletionType.DIRECTORY, f);\n }\n if (stat.isFile() && f.endsWith(\".scad\")) {\n return new CompletionSymbol(CompletionType.FILE, f);\n }\n return null;\n })\n )\n ).filter((s) => !!s) as CompletionSymbol[]),\n ];\n } catch (e) {\n console.error(\"filed to find in dir\", sd, e);\n }\n }\n\n return output;\n }\n\n /**\n * Obtains the part of the included path the user has already entered\n * @param ast the ast to search\n * @param loc the location where the user is typing\n * @returns the part of the included path the user has already entered\n */\n getExistingPath(ast: ASTNode, loc: CodeLocation): string | null {\n let charPos = loc.char;\n let linesLimit = 5;\n let stage = 0;\n let existingFilename = \"\";\n let isFirst = true;\n if(!loc.file) {\n throw new Error(\"No file in CodeLocation\");\n }\n while (true) {\n if (charPos <= 0 || linesLimit <= 0) {\n return null;\n }\n const char = loc.file.code[charPos];\n if (char === \"\\n\") {\n linesLimit--;\n }\n if (!isFirst && char === \">\") {\n return null;\n }\n\n if (!isFirst && stage === 0 && char === \"<\") {\n stage++;\n existingFilename = loc.file.code.substring(charPos + 1, loc.char + 1);\n } else if (\n stage === 1 &&\n char !== \" \" &&\n char !== \"\\t\" &&\n char !== \"\\r\" &&\n char !== \"\\n\"\n ) {\n if (\n loc.file.code.substring(charPos - \"use\".length + 1, charPos + 1) ===\n \"use\"\n ) {\n if (existingFilename.endsWith(\">\")) {\n return existingFilename.slice(0, -1);\n }\n return existingFilename;\n }\n if (\n loc.file.code.substring(\n charPos - \"include\".length + 1,\n charPos + 1\n ) === \"include\"\n ) {\n if (existingFilename.endsWith(\">\")) {\n return existingFilename.slice(0, -1);\n }\n return existingFilename;\n }\n return null;\n }\n isFirst = false;\n charPos--;\n }\n }\n}\n", "import CompletionProvider from \"./CompletionProvider\";\nimport CompletionSymbol from \"./CompletionSymbol\";\nimport ASTNode from \"../ast/ASTNode\";\nimport CodeLocation from \"../CodeLocation\";\nimport keywords from \"../keywords\";\nimport CompletionType from \"./CompletionType\";\n\nexport default class KeywordsCompletionProvider implements CompletionProvider {\n textOnly = true;\n exclusive = false;\n shouldActivate(ast: ASTNode, loc: CodeLocation): boolean {\n return true;\n }\n async getSymbolsAtLocation(\n ast: ASTNode,\n loc: CodeLocation\n ): Promise {\n return Object.keys(keywords).map(\n (kwrd) => new CompletionSymbol(CompletionType.KEYWORD, kwrd)\n );\n }\n}\n", "import ASTNode from \"../ast/ASTNode\";\nimport ASTPinpointer from \"../ASTPinpointer\";\nimport CodeLocation from \"../CodeLocation\";\nimport CompletionProvider from \"./CompletionProvider\";\nimport CompletionSymbol from \"./CompletionSymbol\";\nimport CompletionType from \"./CompletionType\";\nimport NodeWithScope from \"./NodeWithScope\";\nimport Scope from \"./Scope\";\n\nexport default class ScopeSymbolCompletionProvider\n implements CompletionProvider\n{\n textOnly = false;\n exclusive = false;\n shouldActivate(ast: ASTNode, loc: CodeLocation): boolean {\n return true;\n }\n async getSymbolsAtLocation(\n ast: ASTNode,\n loc: CodeLocation\n ): Promise {\n const pp = new ASTPinpointer(loc);\n pp.doPinpoint(ast);\n let symbols: CompletionSymbol[] = [];\n const scopesToShow: Scope[] = [];\n for (const h of pp.bottomUpHierarchy) {\n const hh: NodeWithScope = h as NodeWithScope;\n if (\"scope\" in hh && hh.scope instanceof Scope) {\n scopesToShow.push(hh.scope);\n scopesToShow.push(...hh.scope.siblingScopes);\n }\n }\n for (const scope of scopesToShow) {\n for (const v of scope.variables) {\n symbols.push(\n new CompletionSymbol(CompletionType.VARIABLE, v[1].name, v[1])\n );\n }\n for (const f of scope.functions) {\n symbols.push(\n new CompletionSymbol(CompletionType.FUNCTION, f[1].name, f[1])\n );\n }\n for (const m of scope.modules) {\n symbols.push(\n new CompletionSymbol(CompletionType.MODULE, m[1].name, m[1])\n );\n }\n }\n\n return symbols;\n }\n}\n", "import ASTNode from \"../ast/ASTNode\";\nimport CodeLocation from \"../CodeLocation\";\nimport CompletionProvider from \"./CompletionProvider\";\nimport FilenameCompletionProvider from \"./FilenameCompletionProvider\";\nimport KeywordsCompletionProvider from \"./KeywordsCompletionProvider\";\nimport ScopeSymbolCompletionProvider from \"./ScopeSymbolCompletionProvider\";\nimport CompletionSymbol from \"./CompletionSymbol\";\n\nexport default class CompletionUtil {\n static completionProviders: CompletionProvider[] = [\n new FilenameCompletionProvider(),\n new KeywordsCompletionProvider(),\n new ScopeSymbolCompletionProvider(),\n ];\n static async getSymbolsAtLocation(\n ast: ASTNode,\n loc: CodeLocation\n ): Promise {\n let symbols: CompletionSymbol[] = [];\n for (const cp of this.completionProviders) {\n if (!cp.textOnly && !ast) continue;\n if (cp.shouldActivate(ast, loc)) {\n symbols = [...symbols, ...(await cp.getSymbolsAtLocation(ast, loc))];\n if (cp.exclusive) {\n break;\n }\n }\n }\n return symbols;\n }\n}\n", "import AssignmentNode from \"../ast/AssignmentNode\";\nimport { FunctionCallExpr, LookupExpr } from \"../ast/expressions\";\nimport {\n FunctionDeclarationStmt,\n ModuleDeclarationStmt,\n ModuleInstantiationStmt,\n} from \"../ast/statements\";\n\n/**\n * Represents a resolved lookup expression. It can either\n * point to an assignment node, or to a named function declaration.\n * \n * resolvedDeclaration must be set by the instantiating class.\n */\nexport class ResolvedLookupExpr extends LookupExpr {\n resolvedDeclaration!: AssignmentNode | FunctionDeclarationStmt;\n}\n\nexport class ResolvedModuleInstantiationStmt extends ModuleInstantiationStmt {\n resolvedDeclaration!: ModuleDeclarationStmt;\n}\n", "import CodeLocation from \"../CodeLocation\";\nimport CodeError from \"../errors/CodeError\";\n\nexport class UnresolvedFunctionError extends CodeError {\n constructor(pos: CodeLocation, functionName: string) {\n super(pos, `Unresolved function '${functionName}'.`);\n }\n}\n\nexport class UnresolvedModuleError extends CodeError {\n constructor(pos: CodeLocation, functionName: string) {\n super(pos, `Unresolved module '${functionName}'.`);\n }\n}\n\nexport class UnresolvedVariableError extends CodeError {\n constructor(pos: CodeLocation, functionName: string) {\n super(pos, `Unresolved variable '${functionName}'.`);\n }\n}\n", "import AssignmentNode from \"../ast/AssignmentNode\";\nimport ASTNode from \"../ast/ASTNode\";\nimport {\n AnonymousFunctionExpr,\n FunctionCallExpr,\n LcForCExpr,\n LcForExpr,\n LcLetExpr,\n LetExpr,\n LookupExpr,\n} from \"../ast/expressions\";\nimport ScadFile from \"../ast/ScadFile\";\nimport {\n BlockStmt,\n FunctionDeclarationStmt,\n ModuleDeclarationStmt,\n ModuleInstantiationStmt,\n} from \"../ast/statements\";\nimport ASTMutator from \"../ASTMutator\";\nimport ErrorCollector from \"../ErrorCollector\";\nimport NodeWithScope from \"./NodeWithScope\";\nimport {\n ResolvedLookupExpr,\n ResolvedModuleInstantiationStmt,\n} from \"./resolvedNodes\";\nimport Scope from \"./Scope\";\nimport {\n UnresolvedFunctionError,\n UnresolvedModuleError,\n UnresolvedVariableError,\n} from \"./unresolvedSymbolErrors\";\n\nexport default class SymbolResolver extends ASTMutator {\n constructor(\n private errorCollector: ErrorCollector,\n /** \n * Represents the scope where the resolver has descended.\n * It initially is null, but the resolver should encounter a NodeWithScope \n * and set this to the scope of that node.\n */\n public currentScope: Scope | null = null,\n public isInCallee: boolean = false\n ) {\n super();\n }\n\n visitLookupExpr(n: LookupExpr): ASTNode {\n if(! this.currentScope) {\n throw new Error(\"currentScope cannot be null when resolving lookup\");\n }\n const resolved = new ResolvedLookupExpr(n.name, n.tokens);\n resolved.resolvedDeclaration = this.currentScope.lookupVariable(n.name);\n if(this.isInCallee && !resolved.resolvedDeclaration) {\n resolved.resolvedDeclaration = this.currentScope.lookupFunction(n.name);\n }\n if (!resolved.resolvedDeclaration) {\n this.errorCollector.reportError(\n new UnresolvedVariableError(n.span.start, n.name)\n );\n return n;\n }\n return resolved;\n }\n\n visitModuleInstantiationStmt(n: ModuleInstantiationStmt): ASTNode {\n if(! this.currentScope) {\n throw new Error(\"currentScope cannot be null when resolving module\");\n }\n const resolved = new ResolvedModuleInstantiationStmt(\n n.name,\n n.args.map((a) => a.accept(this)) as AssignmentNode[],\n n.child ? n.child.accept(this) : null,\n n.tokens\n );\n resolved.resolvedDeclaration = this.currentScope.lookupModule(n.name);\n if (!resolved.resolvedDeclaration) {\n this.errorCollector.reportError(new UnresolvedModuleError(n.span.start, n.name));\n return n;\n }\n return resolved;\n }\n\n \n /**\n * visitFunctionCallExpr switches the SymbolResolver into a special mode where\n * it falls back to resolving named functions when processing lookup expressions.\n * This behaviour tries to mimic the behaviour of OpenSCAD's function call resolution,\n * it is not perfect, since you can abuse this to do things like assign a named function\n * to a variable which is not allowed in OpenSCAD. So this covers all but the most\n * pathological cases.\n * @param n \n * @returns \n */\n visitFunctionCallExpr(n: FunctionCallExpr): ASTNode {\n return super.visitFunctionCallExpr.call(\n this.copyWithIsInCallee(),\n n\n );\n }\n\n // scope handling\n private copyWithNextScope(s: Scope) {\n if (!s) {\n throw new Error(\"Scope cannot be falsy\");\n }\n return new SymbolResolver(this.errorCollector, s, this.isInCallee);\n }\n\n private copyWithIsInCallee() {\n return new SymbolResolver(this.errorCollector, this.currentScope, true);\n }\n\n visitBlockStmt(n: BlockStmt): ASTNode {\n return super.visitBlockStmt.call(\n this.copyWithNextScope((n as unknown as NodeWithScope).scope),\n n\n );\n }\n visitLetExpr(n: LetExpr): ASTNode {\n return super.visitLetExpr.call(\n this.copyWithNextScope((n as unknown as NodeWithScope).scope),\n n\n );\n }\n visitScadFile(n: ScadFile): ASTNode {\n return super.visitScadFile.call(\n this.copyWithNextScope((n as unknown as NodeWithScope).scope),\n n\n );\n }\n visitFunctionDeclarationStmt(n: FunctionDeclarationStmt): ASTNode {\n return super.visitFunctionDeclarationStmt.call(\n this.copyWithNextScope((n as unknown as NodeWithScope).scope),\n n\n );\n }\n visitModuleDeclarationStmt(n: ModuleDeclarationStmt): ASTNode {\n return super.visitModuleDeclarationStmt.call(\n this.copyWithNextScope((n as unknown as NodeWithScope).scope),\n n\n );\n }\n visitLcLetExpr(n: LcLetExpr): ASTNode {\n return super.visitLcLetExpr.call(\n this.copyWithNextScope((n as unknown as NodeWithScope).scope),\n n\n );\n }\n visitLcForExpr(n: LcForExpr): ASTNode {\n return super.visitLcForExpr.call(\n this.copyWithNextScope((n as unknown as NodeWithScope).scope),\n n\n );\n }\n visitLcForCExpr(n: LcForCExpr): ASTNode {\n return super.visitLcForCExpr.call(\n this.copyWithNextScope((n as unknown as NodeWithScope).scope),\n n\n );\n }\n visitAnonymousFunctionExpr(n: AnonymousFunctionExpr): ASTNode {\n return super.visitAnonymousFunctionExpr.call(\n this.copyWithNextScope((n as unknown as NodeWithScope).scope),\n n\n );\n }\n}\n", "import * as path from \"path\";\nimport ASTNode from \"./ast/ASTNode\";\nimport ScadFile from \"./ast/ScadFile\";\nimport ASTPinpointer from \"./ASTPinpointer\";\nimport ASTPrinter from \"./ASTPrinter\";\nimport CodeFile from \"./CodeFile\";\nimport CodeLocation from \"./CodeLocation\";\nimport CodeSpan from \"./CodeSpan\";\nimport FormattingConfiguration from \"./FormattingConfiguration\";\nimport ParsingHelper from \"./ParsingHelper\";\nimport PreludeUtil from \"./prelude/PreludeUtil\";\nimport ASTScopePopulator from \"./semantic/ASTScopePopulator\";\nimport ASTSymbolLister, { SymbolKind } from \"./semantic/ASTSymbolLister\";\nimport CompletionUtil from \"./semantic/CompletionUtil\";\nimport IncludeResolver from \"./semantic/IncludeResolver\";\nimport { ScadFileWithScope } from \"./semantic/nodesWithScopes\";\nimport {\n ResolvedLookupExpr,\n ResolvedModuleInstantiationStmt\n} from \"./semantic/resolvedNodes\";\nimport ScadFileProvider, {\n WithExportedScopes\n} from \"./semantic/ScadFileProvider\";\nimport Scope from \"./semantic/Scope\";\nimport SymbolResolver from \"./semantic/SymbolResolver\";\n\nexport class SolutionFile implements WithExportedScopes {\n codeFile!: CodeFile;\n ast: ASTNode|null = null;\n dependencies!: SolutionFile[];\n errors!: Error[];\n includeResolver: IncludeResolver;\n\n includedFiles!: SolutionFile[];\n\n onlyOwnScope!: Scope;\n\n constructor(public solutionManager: SolutionManager) {\n this.includeResolver = new IncludeResolver(this.solutionManager);\n }\n\n async parseAndProcess() {\n let [ast, errors] = ParsingHelper.parseFile(this.codeFile);\n if (ast) {\n this.ast = new ASTScopePopulator(new Scope()).populate(ast);\n this.includedFiles = await this.includeResolver.resolveIncludes(\n this.ast as ScadFile,\n errors\n );\n const usedFiles = await this.includeResolver.resolveIncludes(\n this.ast as ScadFile,\n errors\n );\n this.dependencies = [...this.includedFiles, ...usedFiles];\n this.onlyOwnScope = (this.ast as ScadFileWithScope).scope.copy();\n (this.ast as ScadFileWithScope).scope.siblingScopes = [\n ...this.includedFiles.map((f) => f.getExportedScopes()).flat(),\n ...usedFiles.map((f) => f.getExportedScopes()).flat(),\n PreludeUtil.preludeScope,\n ];\n this.ast = this.ast.accept(new SymbolResolver(errors));\n }\n this.errors = errors.errors;\n }\n getCompletionsAtLocation(loc: CodeLocation) {\n return CompletionUtil.getSymbolsAtLocation(this.ast!, loc);\n }\n\n getSymbols(\n makeSymbol: (\n name: string,\n kind: SymbolKind,\n fullRange: CodeSpan,\n nameRange: CodeSpan,\n children: SymType[]\n ) => SymType\n ) {\n const l = new ASTSymbolLister(makeSymbol);\n return l.doList(this.ast!);\n }\n\n getFormatted() {\n return new ASTPrinter(new FormattingConfiguration()).visitScadFile(\n this.ast as ScadFile\n );\n }\n\n getExportedScopes(): Scope[] {\n return [\n this.onlyOwnScope,\n ...this.includedFiles.map((f) => f.getExportedScopes()).flat(),\n ];\n }\n getSymbolDeclaration(loc: CodeLocation) {\n const pp = new ASTPinpointer(loc).doPinpoint(this.ast!);\n if (\n pp instanceof ResolvedLookupExpr ||\n pp instanceof ResolvedModuleInstantiationStmt\n ) {\n return pp.resolvedDeclaration;\n }\n return null;\n }\n getSymbolDeclarationLocation(loc: CodeLocation): CodeLocation | null {\n const decl = this.getSymbolDeclaration(loc);\n if (decl) {\n return decl.tokens.name ? decl.tokens.name.span.start : null;\n }\n return null;\n }\n}\n\nexport default class SolutionManager implements ScadFileProvider {\n openedFiles: Map = new Map();\n allFiles: Map = new Map();\n notReadyFiles: Map> = new Map();\n\n /**\n * Returns a registered solution file for a given path. It supports getting files which have not been fully processed yet.\n * @param filePath\n */\n async getFile(filePath: string) {\n if (!path.isAbsolute(filePath)) {\n throw new Error(\"Path must be absolute and normalized.\");\n }\n let file = this.allFiles.get(filePath);\n if (file) {\n return file;\n }\n return await this.notReadyFiles.get(filePath);\n }\n\n async notifyNewFileOpened(filePath: string, contents: string) {\n if (!path.isAbsolute(filePath)) {\n throw new Error(\"Path must be absolute and normalized.\");\n }\n const cFile = new CodeFile(filePath, contents);\n\n this.openedFiles.set(filePath, await this.attachSolutionFile(cFile));\n }\n\n async notifyFileChanged(filePath: string, contents: string) {\n if (!path.isAbsolute(filePath)) {\n throw new Error(\"Path must be absolute and normalized.\");\n }\n const cFile = new CodeFile(filePath, contents);\n let sf = this.openedFiles.get(filePath);\n if (!sf) {\n if (this.notReadyFiles.has(filePath)) {\n sf = await this.notReadyFiles.get(filePath) as SolutionFile;\n } else {\n throw new Error(\"No such file\");\n }\n }\n sf.codeFile = cFile;\n await sf.parseAndProcess();\n }\n\n notifyFileClosed(filePath: string) {\n this.openedFiles.delete(filePath);\n this.garbageCollect();\n }\n\n protected async attachSolutionFile(codeFile: CodeFile) {\n const solutionFile = new SolutionFile(this);\n solutionFile.codeFile = codeFile;\n try {\n let resolve!: (s: SolutionFile) => void;\n this.notReadyFiles.set(\n codeFile.path,\n new Promise((r) => (resolve = r))\n );\n await solutionFile.parseAndProcess();\n resolve(solutionFile);\n this.allFiles.set(codeFile.path, solutionFile);\n return solutionFile;\n } finally {\n this.notReadyFiles.delete(codeFile.path);\n }\n }\n\n /**\n * Checks whether a file is already in the solution, and if not it loads it from disk.\n * @param filePath The dependent-upon file.\n */\n async provideScadFile(filePath: string) {\n let f: SolutionFile | undefined = await this.getFile(filePath);\n if (f) return f; // the file is already opened or refrenced by antoher\n return await this.attachSolutionFile(await CodeFile.load(filePath));\n }\n\n /**\n * Removes dependencies that aren't directly or indirectly referenced in any of the open files to free memory.\n */\n protected garbageCollect() {\n const gcMarked = new WeakMap();\n function markRecursive(f: SolutionFile) {\n gcMarked.set(f, true);\n for (const dep of f.dependencies) {\n if (!gcMarked.has(dep)) {\n markRecursive(dep);\n }\n }\n }\n for (const [_, dep] of this.openedFiles) {\n markRecursive(dep);\n }\n for (const [path, f] of this.allFiles) {\n if (!gcMarked.has(f)) {\n this.allFiles.delete(path);\n }\n }\n }\n}\n", "\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\n//# sourceMappingURL=ASTVisitor.js.map", "\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\n//# sourceMappingURL=DocAnnotationClass.js.map", "\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\n//# sourceMappingURL=CompletionProvider.js.map", "\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\n//# sourceMappingURL=NodeWithScope.js.map", "\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\n//# sourceMappingURL=ScadFileProvider.js.map", "/**\n * @file Automatically generated by barrelsby.\n */\n\nexport { default as ASTAssembler } from \"./ASTAssembler\";\nexport * from \"./ASTAssembler\";\nexport { default as ASTMutator } from \"./ASTMutator\";\nexport * from \"./ASTMutator\";\nexport { default as ASTPinpointer } from \"./ASTPinpointer\";\nexport * from \"./ASTPinpointer\";\nexport { default as ASTPrinter } from \"./ASTPrinter\";\nexport * from \"./ASTPrinter\";\nexport { default as CodeFile } from \"./CodeFile\";\nexport * from \"./CodeFile\";\nexport { default as CodeLocation } from \"./CodeLocation\";\nexport * from \"./CodeLocation\";\nexport { default as ErrorCollector } from \"./ErrorCollector\";\nexport * from \"./ErrorCollector\";\nexport { default as FormattingConfiguration } from \"./FormattingConfiguration\";\nexport * from \"./FormattingConfiguration\";\nexport { default as Lexer } from \"./Lexer\";\nexport * from \"./Lexer\";\nexport { default as LiteralToken } from \"./LiteralToken\";\nexport * from \"./LiteralToken\";\nexport { default as Parser } from \"./Parser\";\nexport * from \"./Parser\";\nexport { default as ParsingHelper } from \"./ParsingHelper\";\nexport * from \"./ParsingHelper\";\nexport { default as SolutionManager } from \"./SolutionManager\";\nexport * from \"./SolutionManager\";\nexport { default as Token } from \"./Token\";\nexport * from \"./Token\";\nexport { default as TokenType } from \"./TokenType\";\nexport * from \"./TokenType\";\nexport * from \"./extraTokens\";\nexport { default as friendlyTokenNames } from \"./friendlyTokenNames\";\nexport * from \"./friendlyTokenNames\";\nexport { default as keywords } from \"./keywords\";\nexport * from \"./keywords\";\nexport { default as ASTNode } from \"./ast/ASTNode\";\nexport * from \"./ast/ASTNode\";\nexport { default as ASTVisitor } from \"./ast/ASTVisitor\";\nexport * from \"./ast/ASTVisitor\";\nexport { default as AssignmentNode } from \"./ast/AssignmentNode\";\nexport * from \"./ast/AssignmentNode\";\nexport { default as ErrorNode } from \"./ast/ErrorNode\";\nexport * from \"./ast/ErrorNode\";\nexport { default as ScadFile } from \"./ast/ScadFile\";\nexport * from \"./ast/ScadFile\";\nexport * from \"./ast/expressions\";\nexport * from \"./ast/statements\";\nexport { default as DocAnnotationClass } from \"./comments/DocAnnotationClass\";\nexport * from \"./comments/DocAnnotationClass\";\nexport { default as DocComment } from \"./comments/DocComment\";\nexport * from \"./comments/DocComment\";\nexport * from \"./comments/annotations\";\nexport { default as CodeError } from \"./errors/CodeError\";\nexport * from \"./errors/CodeError\";\nexport { default as LexingError } from \"./errors/LexingError\";\nexport * from \"./errors/LexingError\";\nexport { default as ParsingError } from \"./errors/ParsingError\";\nexport * from \"./errors/ParsingError\";\nexport * from \"./errors/lexingErrors\";\nexport * from \"./errors/parsingErrors\";\nexport { default as PreludeUtil } from \"./prelude/PreludeUtil\";\nexport * from \"./prelude/PreludeUtil\";\nexport { default as ASTScopePopulator } from \"./semantic/ASTScopePopulator\";\nexport * from \"./semantic/ASTScopePopulator\";\nexport { default as ASTSymbolLister } from \"./semantic/ASTSymbolLister\";\nexport * from \"./semantic/ASTSymbolLister\";\nexport { default as CompletionProvider } from \"./semantic/CompletionProvider\";\nexport * from \"./semantic/CompletionProvider\";\nexport { default as CompletionSymbol } from \"./semantic/CompletionSymbol\";\nexport * from \"./semantic/CompletionSymbol\";\nexport { default as CompletionType } from \"./semantic/CompletionType\";\nexport * from \"./semantic/CompletionType\";\nexport { default as CompletionUtil } from \"./semantic/CompletionUtil\";\nexport * from \"./semantic/CompletionUtil\";\nexport { default as FilenameCompletionProvider } from \"./semantic/FilenameCompletionProvider\";\nexport * from \"./semantic/FilenameCompletionProvider\";\nexport { default as IncludeResolver } from \"./semantic/IncludeResolver\";\nexport * from \"./semantic/IncludeResolver\";\nexport { default as KeywordsCompletionProvider } from \"./semantic/KeywordsCompletionProvider\";\nexport * from \"./semantic/KeywordsCompletionProvider\";\nexport { default as NodeWithScope } from \"./semantic/NodeWithScope\";\nexport * from \"./semantic/NodeWithScope\";\nexport { default as ScadFileProvider } from \"./semantic/ScadFileProvider\";\nexport * from \"./semantic/ScadFileProvider\";\nexport { default as Scope } from \"./semantic/Scope\";\nexport * from \"./semantic/Scope\";\nexport { default as ScopeSymbolCompletionProvider } from \"./semantic/ScopeSymbolCompletionProvider\";\nexport * from \"./semantic/ScopeSymbolCompletionProvider\";\nexport { default as SymbolResolver } from \"./semantic/SymbolResolver\";\nexport * from \"./semantic/SymbolResolver\";\nexport * from \"./semantic/nodesWithScopes\";\nexport * from \"./semantic/resolvedNodes\";\nexport * from \"./semantic/unresolvedSymbolErrors\";\n"], - "mappings": "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", + "mappings": "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", "names": ["AssignmentNodeRole", "TokenType", "path", "mod", "secondParen", "inst", "SymbolKind", "t", "CompletionType", "path"] } diff --git a/packages/cascade-studio/scripts/build.cjs b/packages/cascade-studio/scripts/build.cjs index 0dfb663e..01f4cef4 100644 --- a/packages/cascade-studio/scripts/build.cjs +++ b/packages/cascade-studio/scripts/build.cjs @@ -139,7 +139,7 @@ fs.writeFileSync(path.join(distDir, 'index.html'), ` -

AGENT API: Run CascadeAPI.getQuickStart() to learn how to use this CAD app. All interaction goes through window.CascadeAPI — do NOT use browser_take_screenshot or browser_run_code.

+

AGENT API: Run CascadeAPI.getQuickStart() to learn how to use this CAD app. All interaction goes through window.CascadeAPI — do NOT use browser_take_screenshot or browser_run_code. The editor opens in Python (build123d) mode; call CascadeAPI.setMode('cascadestudio') for the JavaScript API.

Cascade Studio
@@ -155,6 +155,9 @@ fs.writeFileSync(path.join(distDir, 'index.html'), ` Clear Imported diff --git a/packages/cascade-studio/src/CascadeAPI.js b/packages/cascade-studio/src/CascadeAPI.js index 795bb3e1..4e936eb7 100644 --- a/packages/cascade-studio/src/CascadeAPI.js +++ b/packages/cascade-studio/src/CascadeAPI.js @@ -2,6 +2,7 @@ import { STLExporter } from 'three/examples/jsm/exporters/STLExporter.js'; import { OBJExporter } from 'three/examples/jsm/exporters/OBJExporter.js'; +import { resolvePyRuntime } from './EditorManager.js'; /** Exposes window.CascadeAPI for programmatic control of Cascade Studio. * Designed for use by AI agents (via Playwright) and developer tooling. */ @@ -71,6 +72,14 @@ class CascadeAPI { /** Compact quick-start guide. Call this FIRST to learn the API. */ getQuickStart() { return { + mode: { + current: this._app.editor.mode, + note: 'The editor language mode decides how runCode() interprets your code. ' + + 'A fresh load starts in "python" (build123d algebra mode: ' + + '`from build123d import *` … `show(shape)`). Everything below documents the ' + + 'CascadeStudio JS API — call CascadeAPI.setMode("cascadestudio") before using it.', + switch: 'CascadeAPI.setMode("python" | "cascadestudio" | "openscad")', + }, workflow: [ 'result = await CascadeAPI.runCode(code) → {success, errors, logs, historySteps}', 'CascadeAPI.setCameraAngle(azimuth, elevation) → 0=front, 90=right; 0=level, 90=top', @@ -205,6 +214,12 @@ Revolve(profile, 360);`, isReady() { return this._app.engine && this._app.engine.isReady; } isWorking() { return window.workerWorking; } + /** Internal: the viewport's GUI ToolManager (for tests/tooling). */ + get _tools() { + const viewport = this._app.viewport; + return viewport ? viewport.toolManager : null; + } + setMode(mode) { this._app.editor.setMode(mode); const modeSelect = document.getElementById('editorMode'); @@ -212,6 +227,23 @@ Revolve(profile, 360);`, } getMode() { return this._app.editor.mode; } + /** Worker memory footprint (JS heap + the OCCT and Python wasm heaps) and + * the Python runtime's boot timing. Used by the runtime comparison. */ + async _memoryStats() { return this._app.engine.memoryStats(); } + + /** Which Python interpreter Python mode evaluates on: 'brython' (default) + * or the experimental 'pyodide'. Set with `?pyruntime=pyodide` or + * setPyRuntime(); a change takes effect on the NEXT evaluation, but the + * worker keeps whichever runtime it already booted for the session. */ + getPyRuntime() { return resolvePyRuntime(); } + setPyRuntime(kind) { + try { + window.localStorage.setItem('cascade-py-runtime', + kind === 'pyodide' ? 'pyodide' : 'brython'); + } catch (e) { console.error('setPyRuntime: ' + e.message); } + return this.getPyRuntime(); + } + // Debug / history inspection showHistoryStep(index) { const viewport = this._app.viewport; @@ -231,6 +263,19 @@ Revolve(profile, 360);`, ).join('\n'); } + /** Load STEP/IGES/STL assets into the worker's `externalShapes` dict, keyed + * by file name. This is how a script that reads a file next to itself gets + * its data: the worker has no filesystem, so the asset is handed over + * up-front and Python mode's `import_step(path)` resolves it by base name. + * `files` is `{ "part.step": "", ... }`. */ + async loadExternalFiles(files) { + const dict = {}; + for (const name of Object.keys(files || {})) { + dict[name] = { content: files[name] }; + } + return await this._app.engine.loadExternalFilesAwaited(dict); + } + // Export formats getSTEP() { return this._app.engine.exportSTEP(); } getSTL() { diff --git a/packages/cascade-studio/src/CascadeMain.js b/packages/cascade-studio/src/CascadeMain.js index b9aebf71..63336fb4 100644 --- a/packages/cascade-studio/src/CascadeMain.js +++ b/packages/cascade-studio/src/CascadeMain.js @@ -107,10 +107,7 @@ class CascadeStudioApp { this._savedCode[this.editor.mode] = this.editor.getCode(); this.editor.setMode(newMode); // Load saved code or starter code for the new mode - const starter = newMode === 'openscad' - ? CascadeStudioApp.OPENSCAD_STARTER_CODE - : CascadeStudioApp.STARTER_CODE; - this.editor.setCode(this._savedCode[newMode] || starter); + this.editor.setCode(this._savedCode[newMode] || CascadeStudioApp.starterCode(newMode)); // Re-fit camera and auto-evaluate if (this.viewport) { this.viewport._fitOnNextRender = true; } this.editor.evaluateCode(); @@ -189,7 +186,20 @@ class CascadeStudioApp { let searchParams = new URLSearchParams(window.location.search || window.location.hash.substr(1)); let loadFromURL = searchParams.has("code"); - let codeStr = CascadeStudioApp.STARTER_CODE; + // Resolve the language mode BEFORE the code, so every source of content + // brings its own default with it: + // fresh load (no URL params, no project) → Python (build123d) + // ?code=... without &mode= → CascadeStudio JS, because + // links shared before mode serialization existed are always JS and + // must NOT be captured by the new Python default + // ?mode=... (with or without &code=) → that mode + // saved project → the mode stored in the file + // (legacy project files have none → CascadeStudio JS) + let mode = loadFromURL ? 'cascadestudio' : CascadeStudioApp.DEFAULT_MODE; + const urlMode = searchParams.get("mode"); + if (CascadeStudioApp.MODES.includes(urlMode)) { mode = urlMode; } + + let codeStr = CascadeStudioApp.starterCode(mode); this.gui.state = {}; if (projectContent) { @@ -198,19 +208,28 @@ class CascadeStudioApp { let parsed = JSON.parse(projectContent); if (parsed._cascadeState) { // New Dockview project format - codeStr = parsed._cascadeState.code || codeStr; + mode = CascadeStudioApp.MODES.includes(parsed._cascadeState.mode) + ? parsed._cascadeState.mode : 'cascadestudio'; + codeStr = parsed._cascadeState.code || CascadeStudioApp.starterCode(mode); this.gui.state = parsed._cascadeState.guiState || {}; } else if (parsed.content || parsed.root) { // Legacy GoldenLayout project format — extract code from componentState + mode = 'cascadestudio'; let code = this._extractLegacyCode(parsed); - if (code) { codeStr = code; } + codeStr = code || CascadeStudioApp.STARTER_CODE; } } catch (e) { console.error("Failed to parse project:", e); } } else if (loadFromURL) { codeStr = CascadeStudioApp.decode(searchParams.get("code")); - this.gui.state = JSON.parse(CascadeStudioApp.decode(searchParams.get("gui"))); + if (searchParams.has("gui")) { + try { + this.gui.state = JSON.parse(CascadeStudioApp.decode(searchParams.get("gui"))); + } catch (e) { + console.error("Failed to parse the GUI state in the URL: " + e.message); + } + } } // Dispose previous layout @@ -330,6 +349,9 @@ class CascadeStudioApp { }, 50); } + // The editor panel now exists — switch it to the resolved language mode + this._applyMode(mode); + // Resize the layout when the browser resizes if (this._updateLayoutSize) { window.removeEventListener('resize', this._updateLayoutSize); @@ -344,6 +366,19 @@ class CascadeStudioApp { requestAnimationFrame(this._updateLayoutSize); } + /** Apply a resolved language mode to the editor and the topnav switcher, + * keeping the content the caller already loaded into the editor. */ + _applyMode(mode) { + const modeSelect = document.getElementById('editorMode'); + if (modeSelect) { modeSelect.value = mode; } + if (!this.editor || !this.editor.editor || mode === this.editor.mode) { return; } + const code = this.editor.getCode(); + this.editor.setMode(mode); + // setMode swaps in the new mode's starter when the current text is another + // mode's starter; the content we were handed always wins here. + if (this.editor.getCode() !== code) { this.editor.setCode(code); } + } + /** Initialize the Three.js 3D Viewport. */ _initCascadeView(container, state) { this.gui.state = state; @@ -399,6 +434,7 @@ class CascadeStudioApp { let projectData = { _cascadeState: { code: currentCode, + mode: this.editor.mode, guiState: this.gui.state, externalFiles: this.console.goldenContainer.getState() } @@ -462,6 +498,13 @@ class CascadeStudioApp { // --- Static utility methods --- + /** Starter code for a language mode (falls back to CascadeStudio JS). */ + static starterCode(mode) { + if (mode === 'python') { return CascadeStudioApp.PYTHON_STARTER_CODE; } + if (mode === 'openscad') { return CascadeStudioApp.OPENSCAD_STARTER_CODE; } + return CascadeStudioApp.STARTER_CODE; + } + /** Get a new file handle via the File System Access API. */ static async getNewFileHandle(desc, mime, ext, open = false) { const options = { @@ -530,9 +573,20 @@ class CascadeStudioApp { } } -/** Default starter code shown in the editor. */ +/** Editor language modes, and the mode a fresh (parameter-less) load starts in. + * Share URLs carry `&mode=`; links without it are pre-mode + * legacy links and load as CascadeStudio JS (see initialize()). */ +CascadeStudioApp.MODES = ['cascadestudio', 'openscad', 'python']; +CascadeStudioApp.DEFAULT_MODE = 'python'; + +/** CascadeStudio JS starter code (the `cascadestudio` mode). */ CascadeStudioApp.STARTER_CODE = -`// Welcome to Cascade Studio! A Browser-Based CAD Modeling Environment. +`// Cascade Studio — CascadeStudio JS mode (OpenCascade, Z-up, millimetres). +// F5 (or Ctrl+S) evaluates; the language dropdown up top switches to +// Python (build123d, the default mode) or OpenSCAD. +// The viewport toolbar (Box / Cylinder / Sphere / Sketch / Fillet) writes +// code into THIS editor — the code is the scene, so tool output is editable. +// // Adjust these sliders to modify the model in real time: let width = Slider("Width", 80, 40, 120); let depth = Slider("Depth", 60, 30, 100); @@ -649,4 +703,39 @@ translate([0, 0, shaft_h + 2]) } `; +/** Default Python (build123d-lite) starter code — the default mode on a fresh + * load. A parametric flanged bearing mount: 2-D profile → extrude → booleans + * → selector-driven fillet. Renders in well under a second. */ +CascadeStudioApp.PYTHON_STARTER_CODE = +`# CascadeStudio build123d mode — coverage table: github.com/zalo/CascadeStudio#python-build123d-mode +# Algebra mode: \`+\` fuses, \`-\` cuts, \`&\` intersects. Primitives are CENTERED, +# so Pos(x, y, z) * shape moves and Rot(rx, ry, rz) * shape turns (degrees). +from build123d import * + +L, W, T = 80, 60, 8 # flange plate: length / width / thickness +boss_d, boss_h, bore_d = 34, 20, 16 +hole_d, inset = 6, 10 # M6 bolt holes, inset from the plate edges + +# Selectors: edges() is a ShapeList — filter_by(Axis.Z) grabs the four +# vertical corner edges, so one fillet() call rounds the whole plate +plate = Pos(0, 0, T / 2) * Box(L, W, T) +plate = fillet(plate.edges().filter_by(Axis.Z), 12) + +# Algebra: fuse the bearing boss on top, then cut the bore, the bolt holes +# (GridLocations * shape places one cutter per location) and a set screw +mount = plate + Pos(0, 0, T + boss_h / 2) * Cylinder(boss_d / 2, boss_h) +mount -= Cylinder(bore_d / 2, 200) +mount -= GridLocations(L - 2 * inset, W - 2 * inset, 2, 2) * Cylinder(hole_d / 2, 200) +mount -= Pos(0, 0, T + boss_h / 2) * Rot(0, 90, 0) * Cylinder(2.5, 200) + +# group_by(Axis.Z)[-1] = the highest edges, i.e. the boss's top rim +mount = fillet(mount.edges().group_by(Axis.Z)[-1], 1.5) + +show(mount) # show() puts a shape in the 3-D viewport +print("volume:", round(volume(mount), 1), "mm^3") + +# 2-D -> 3-D works too: extrude(RectangleRounded(L, W, 12), T) instead of the +# Box above, or sweep/revolve/loft a Curve. Full API: build123d.readthedocs.io +`; + export { CascadeStudioApp }; diff --git a/packages/cascade-studio/src/CascadeView.js b/packages/cascade-studio/src/CascadeView.js index b0704ccc..15d3ec5b 100644 --- a/packages/cascade-studio/src/CascadeView.js +++ b/packages/cascade-studio/src/CascadeView.js @@ -5,6 +5,7 @@ import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; import { STLExporter } from 'three/examples/jsm/exporters/STLExporter.js'; import { OBJExporter } from 'three/examples/jsm/exporters/OBJExporter.js'; import { HandleManager } from './CascadeViewHandles.js'; +import { ToolManager } from './tools/ToolManager.js'; /** Base class for a 3D viewport environment. * Includes floor, grid, fog, camera, lights, and orbit controls. */ @@ -147,9 +148,15 @@ class CascadeEnvironment { // Create the timeline overlay DOM this._createTimelineOverlay(); + // Per-sceneShape producing line numbers (from the worker's mesh payload) + this._shapeLines = []; + // Initialize the Handle Manager (no messageBus needed — app wires events) this.handleManager = new HandleManager(this); + // Initialize the GUI modeling tools (toolbar + tool state machines) + this.toolManager = new ToolManager(this); + // Start the animation loop this._animate(); this.environment.renderer.render(this.environment.scene, this.environment.camera); @@ -164,6 +171,8 @@ class CascadeEnvironment { if (!facelist) { return; } if (!sceneOptions) { sceneOptions = {}; } this._lastSceneOptions = sceneOptions; + this._shapeLines = meshData.shapeLines || []; + if (this.toolManager) { this.toolManager.onSceneRebuilt(); } // The old mainObject is dead! Long live the mainObject! this.environment.scene.remove(this.mainObject); @@ -309,8 +318,10 @@ class CascadeEnvironment { ); } + // Per-vertex metadata: (local face index, global face index, owning sceneShape index) + let faceShapeIndex = (face.shape_index !== undefined) ? face.shape_index : -1; for (let i = 0; i < face.vertex_coord.length; i += 3) { - colors.push(face.face_index, globalFaceIndex, 0); + colors.push(face.face_index, globalFaceIndex, faceShapeIndex); } globalFaceIndex++; @@ -338,6 +349,8 @@ class CascadeEnvironment { edgelist.forEach((edge) => { let edgeMetadata = {}; edgeMetadata.localEdgeIndex = edge.edge_index; + edgeMetadata.shapeIndex = (edge.shape_index !== undefined) ? edge.shape_index : -1; + edgeMetadata.globalEdgeIndex = curGlobalEdgeIndex; edgeMetadata.start = globalEdgeIndices.length; for (let i = 0; i < edge.vertex_coord.length - 3; i += 3) { lineVertices.push(new THREE.Vector3( @@ -366,13 +379,22 @@ class CascadeEnvironment { line.name = "Model Edges"; line.lineColors = lineColors; line.globalEdgeMetadata = globalEdgeMetadata; + // Global edge indices selected by tools (e.g. the Fillet tool); rendered + // in orange and preserved across hover highlight repaints. + line.selectedEdges = new Set(); line.highlightEdgeAtLineIndex = function (lineIndex) { let edgeIndex = lineIndex >= 0 ? this.globalEdgeIndices[lineIndex] : lineIndex; let startIndex = this.globalEdgeMetadata[edgeIndex].start; let endIndex = this.globalEdgeMetadata[edgeIndex].end; - for (let i = 0; i < this.lineColors.length; i++) { - let colIndex = Math.floor(i / 3); - this.lineColors[i] = (colIndex >= startIndex && colIndex <= endIndex) ? 1 : 0; + for (let v = 0; v < this.lineColors.length / 3; v++) { + let isHovered = (v >= startIndex && v <= endIndex); + let isSelected = this.selectedEdges.has(this.globalEdgeIndices[v]); + let r = 0, g = 0, b = 0; + if (isSelected) { r = 1.0; g = 0.55; b = 0.1; } + if (isHovered) { r = 1.0; g = isSelected ? 0.8 : 1.0; b = isSelected ? 0.4 : 1.0; } + this.lineColors[(v * 3) + 0] = r; + this.lineColors[(v * 3) + 1] = g; + this.lineColors[(v * 3) + 2] = b; } this.geometry.setAttribute('color', new THREE.Float32BufferAttribute(this.lineColors, 3)); }.bind(line); @@ -387,6 +409,37 @@ class CascadeEnvironment { return group; } + /** Resolve pick metadata from a raycast intersection against mainObject. + * Returns { kind, shapeIndex, ... } or null. */ + getPickInfo(intersect) { + if (!intersect || !intersect.object) return null; + if (intersect.object.type === "LineSegments") { + let meta = intersect.object.getEdgeMetadataAtLineIndex(intersect.index); + if (!meta) return null; + return { + kind: "edge", + shapeIndex: meta.shapeIndex, + localEdgeIndex: meta.localEdgeIndex, + globalEdgeIndex: meta.globalEdgeIndex + }; + } + if (intersect.face && intersect.object.geometry.attributes.color) { + let colors = intersect.object.geometry.attributes.color; + return { + kind: "face", + shapeIndex: colors.getZ(intersect.face.a), + faceIndex: colors.getX(intersect.face.a) + }; + } + return null; + } + + /** Get the editor line number (1-based) that produced a sceneShape index. */ + getShapeLine(shapeIndex) { + if (shapeIndex == null || shapeIndex < 0) return -1; + return this._shapeLines[shapeIndex] || -1; + } + /** Create the timeline overlay DOM elements. */ _createTimelineOverlay() { this._timelineContainer = document.createElement('div'); diff --git a/packages/cascade-studio/src/CascadeViewHandles.js b/packages/cascade-studio/src/CascadeViewHandles.js index 900be7ba..630453c3 100644 --- a/packages/cascade-studio/src/CascadeViewHandles.js +++ b/packages/cascade-studio/src/CascadeViewHandles.js @@ -35,7 +35,8 @@ class HandleManager { // Inject transform data back into the editor upon completion if (this.viewport.environment.controls.enabled) { - let code = window.monacoEditor.getValue().split("\n"); + let editor = this.viewport._app.editor; + let code = editor.getCode().split("\n"); let lineNum = handle.lineAndColumn[0] - 1; let translateString = "[" + @@ -71,8 +72,8 @@ class HandleManager { let newCode = ""; code.forEach((codeLine) => { newCode += codeLine + "\n"; }); - window.monacoEditor.setValue(newCode.slice(0, -1)); - window.monacoEditor.evaluateCode(false); + editor.setCode(newCode.slice(0, -1)); + editor.evaluateCode(false); } } }; diff --git a/packages/cascade-studio/src/EditorManager.js b/packages/cascade-studio/src/EditorManager.js index edd5640c..4639a01d 100644 --- a/packages/cascade-studio/src/EditorManager.js +++ b/packages/cascade-studio/src/EditorManager.js @@ -2,6 +2,22 @@ const monaco = window.monaco; +/** Which Python interpreter the worker should use for Python mode: + * 'brython' (default, ~300 KB gz, boots in a few hundred ms) or the + * experimental 'pyodide' (real CPython on wasm — see + * test/b123d-validation/runtime-comparison.md for why it is not the + * default). Selected with `?pyruntime=pyodide` or, so it survives reloads, + * localStorage['cascade-py-runtime']. */ +export function resolvePyRuntime() { + try { + const fromURL = new URLSearchParams(window.location.search).get('pyruntime'); + if (fromURL) { return fromURL === 'pyodide' ? 'pyodide' : 'brython'; } + const stored = window.localStorage.getItem('cascade-py-runtime'); + if (stored === 'pyodide') { return 'pyodide'; } + } catch (e) { /* no URL/storage access — fall through to the default */ } + return 'brython'; +} + /** Manages the Monaco code editor instance, mode switching, and code evaluation. */ class EditorManager { constructor(app) { @@ -106,6 +122,63 @@ class EditorManager { if (this.editor) { this.editor.setValue(code); } } + /** Insert a snippet on a new line after the last non-empty line of the + * document. Uses executeEdits so the Monaco undo stack is preserved. + * Returns the 1-based line number the snippet's first line landed on. */ + insertCode(snippet) { + if (!this.editor) return -1; + const model = this.editor.getModel(); + let lastLine = model.getLineCount(); + while (lastLine > 1 && model.getLineContent(lastLine).trim() === '') { lastLine--; } + const isEmptyDoc = (lastLine === 1 && model.getLineContent(1).trim() === ''); + const col = model.getLineMaxColumn(lastLine); + const text = isEmptyDoc ? snippet : '\n' + snippet; + this.editor.pushUndoStop(); + this.editor.executeEdits('cascade-gui-tools', [{ + range: new monaco.Range(lastLine, col, lastLine, col), + text: text + }]); + this.editor.pushUndoStop(); + return isEmptyDoc ? lastLine : lastLine + 1; + } + + /** Get the text of a 1-based line (empty string if out of range). */ + getLineContent(lineNumber) { + if (!this.editor) return ''; + const model = this.editor.getModel(); + if (lineNumber < 1 || lineNumber > model.getLineCount()) return ''; + return model.getLineContent(lineNumber); + } + + /** Replace the full text of a 1-based line (undo-friendly). */ + replaceLine(lineNumber, newText) { + if (!this.editor) return; + const model = this.editor.getModel(); + if (lineNumber < 1 || lineNumber > model.getLineCount()) return; + this.editor.pushUndoStop(); + this.editor.executeEdits('cascade-gui-tools', [{ + range: new monaco.Range(lineNumber, 1, lineNumber, model.getLineMaxColumn(lineNumber)), + text: newText + }]); + this.editor.pushUndoStop(); + } + + /** Reveal a line and flash a temporary highlight on it. + * Used by the Select tool's pick → code line mapping. */ + flashLine(lineNumber) { + if (!this.editor || !lineNumber || lineNumber < 1) return; + this.editor.revealLineInCenterIfOutsideViewport(lineNumber); + const decorations = this.editor.deltaDecorations(this._flashDecorations || [], [{ + range: new monaco.Range(lineNumber, 1, lineNumber, 1), + options: { isWholeLine: true, className: 'cs-pick-line-flash' } + }]); + this._flashDecorations = decorations; + clearTimeout(this._flashTimeout); + this._flashTimeout = setTimeout(() => { + this._flashDecorations = this.editor.deltaDecorations(this._flashDecorations || [], []); + }, 1200); + } + /** Evaluate the current code: transpile if OpenSCAD, then send to worker via engine. */ evaluateCode(saveToURL = false) { if (window.workerWorking) { return; } @@ -133,9 +206,13 @@ class EditorManager { } } - // Use CascadeEngine to evaluate and get mesh data + // Use CascadeEngine to evaluate and get mesh data. + // Python code is passed through as-is; the worker runs it via Brython + // (or Pyodide when the experimental flag is set). this._app.engine.evaluate(codeToEval, { guiState: this._app.gui.state, + language: this.mode === 'python' ? 'python' : undefined, + pyRuntime: this.mode === 'python' ? resolvePyRuntime() : undefined, }).then((result) => { if (this._app.viewport && result.meshData) { this._app.viewport.renderMeshData(result.meshData, result.sceneOptions); @@ -150,10 +227,14 @@ class EditorManager { if (saveToURL) { const AppClass = this._app.constructor; console.log("Saved to URL!"); + // `mode` is a plain, human-readable param so the language travels with + // the code. Links without it predate mode serialization and load as + // CascadeStudio JS (see CascadeStudioApp.initialize). window.history.replaceState({}, 'Cascade Studio', new URL( location.pathname + "?code=" + AppClass.encode(newCode) + - "&gui=" + AppClass.encode(JSON.stringify(this._app.gui.state)), + "&gui=" + AppClass.encode(JSON.stringify(this._app.gui.state)) + + "&mode=" + encodeURIComponent(this.mode), location.href ).href ); @@ -162,18 +243,20 @@ class EditorManager { console.log("Generating Model"); } - /** Set editor mode: 'cascadestudio' or 'openscad'. */ + /** Set editor mode: 'cascadestudio', 'openscad', or 'python'. */ setMode(newMode) { if (newMode === this.mode) return; - // Swap starter code if current content matches the other mode's starter + // Swap starter code if the current content is any known mode's starter + const AppClass = this._app.constructor; + const starters = { + cascadestudio: AppClass.STARTER_CODE, + openscad: AppClass.OPENSCAD_STARTER_CODE, + python: AppClass.PYTHON_STARTER_CODE, + }; const currentCode = this.editor.getValue(); - const csStarter = this._app.constructor.STARTER_CODE; - const osStarter = this._app.constructor.OPENSCAD_STARTER_CODE; - if (newMode === 'openscad' && osStarter && currentCode === csStarter) { - this.editor.setValue(osStarter); - } else if (newMode === 'cascadestudio' && currentCode === osStarter) { - this.editor.setValue(csStarter); + if (starters[newMode] && Object.values(starters).includes(currentCode)) { + this.editor.setValue(starters[newMode]); } // Fit camera on the next render after a mode switch @@ -187,21 +270,28 @@ class EditorManager { this._openscadProviders.forEach(d => d.dispose()); this._openscadProviders = []; + const model = this.editor.getModel(); if (newMode === 'openscad') { // Switch to OpenSCAD language - const model = this.editor.getModel(); monaco.editor.setModelLanguage(model, 'openscad'); // Register OpenSCAD providers if available if (this._app._openscadMonaco) { this._openscadProviders = this._app._openscadMonaco.registerProviders(this.editor); } + } else if (newMode === 'python') { + // Monaco ships a built-in Python tokenizer — no custom language needed + monaco.editor.setModelLanguage(model, 'python'); } else { // Switch back to TypeScript - const model = this.editor.getModel(); monaco.editor.setModelLanguage(model, 'typescript'); monaco.languages.typescript.typescriptDefaults.setExtraLibs(this._extraLibs); } + + // Let the GUI tools react (e.g. the Sketch tool is JS-only for now) + if (this._app.viewport && this._app.viewport.toolManager) { + this._app.viewport.toolManager.onLanguageChanged(); + } } /** Get the container for the code editor. */ diff --git a/packages/cascade-studio/src/tools/BoxTool.js b/packages/cascade-studio/src/tools/BoxTool.js new file mode 100644 index 00000000..9c8de988 --- /dev/null +++ b/packages/cascade-studio/src/tools/BoxTool.js @@ -0,0 +1,177 @@ +// BoxTool - drag a footprint on the ground plane, then drag the height +import * as THREE from 'three'; +import { Tool } from './Tool.js'; + +const IDLE = 0, DRAG_BASE = 1, DRAG_HEIGHT = 2; + +/** Box creation tool (LeapShape-style state machine): + * 1. pointerdown on the ground plane sets the base corner (snapped to mm) + * 2. drag (or move) sizes the footprint rectangle (live preview) + * 3. release/click locks the footprint; drag or move then sizes the height + * 4. release/click commits: emits `let boxN = Translate(..., Box(w, d, h));` + * + * Both gestures work for every stage (see Tool.stageDown/stageUp): + * press-drag-release, and click-move-click. Escape cancels. */ +class BoxTool extends Tool { + constructor(manager) { + super(manager, 'box'); + this.state = IDLE; + this.preview = null; + this.baseCAD = null; // CAD [x, y, 0] of the first corner + this.cornerCAD = null; // CAD [x, y, 0] of the dragged corner + this.height = 0; // CAD z height (may be negative) + } + + isInteracting() { return this.state !== IDLE; } + + onPointerDown(event) { + if (event.button !== 0) return false; + + if (this.state === IDLE) { + const hit = this.manager.raycastGround(event); + if (!hit) return false; + this.baseCAD = this.manager.snapGroundToCad(hit); + this.cornerCAD = this.baseCAD.slice(); + this.height = 0; + this._createPreview(); + this.manager.beginInteraction(); + this.stagePressed = true; + this.state = DRAG_BASE; + return true; + } + + // Click-move-click: a press with the stage's dimension already set locks + // it in; a press with nothing set yet starts a drag (and must NOT cancel). + if (this.state === DRAG_BASE) { + if (this.stageDown(this._hasFootprint())) { this.state = DRAG_HEIGHT; } + return true; + } + + if (this.state === DRAG_HEIGHT) { + if (this.stageDown(this.height !== 0)) { this._commit(); } + return true; + } + return false; + } + + onPointerMove(event) { + if (this.state === DRAG_BASE) { + const hit = this.manager.raycastGround(event); + if (hit) { + this.cornerCAD = this.manager.snapGroundToCad(hit); + this._updatePreview(); + const { w, d } = this._dims(); + this.manager.showLabel(event, w + ' × ' + d); + } + return true; + } + if (this.state === DRAG_HEIGHT) { + const centerThree = this.manager.cadToThree(this._footprintCenterCAD()); + centerThree.y = 0; + this.height = this.manager.heightFromRay(event, centerThree); + this._updatePreview(); + this.manager.showLabel(event, 'h = ' + this.height); + return true; + } + return false; + } + + onPointerUp(event) { + if (this.state === DRAG_BASE) { + if (this.stageUp(this._hasFootprint())) { this.state = DRAG_HEIGHT; } + return true; + } + if (this.state === DRAG_HEIGHT) { + if (this.stageUp(this.height !== 0)) { this._commit(); } + return true; + } + return false; + } + + cancel() { + this.manager.removePreview(this.preview); + this.preview = null; + if (this.state !== IDLE) { this.manager.endInteraction(); } + this.state = IDLE; + this.stagePressed = false; + this.manager.hideLabel(); + } + + /** True once the dragged footprint has a non-zero area. */ + _hasFootprint() { + const { w, d } = this._dims(); + return w !== 0 && d !== 0; + } + + /** Footprint dimensions and min corner in CAD space. */ + _dims() { + return { + w: Math.abs(this.cornerCAD[0] - this.baseCAD[0]), + d: Math.abs(this.cornerCAD[1] - this.baseCAD[1]), + minX: Math.min(this.baseCAD[0], this.cornerCAD[0]), + minY: Math.min(this.baseCAD[1], this.cornerCAD[1]), + }; + } + + /** CAD center of the box (footprint center at half height). */ + _footprintCenterCAD() { + const { w, d, minX, minY } = this._dims(); + return [minX + w / 2, minY + d / 2, 0]; + } + + _createPreview() { + this.preview = new THREE.Mesh( + new THREE.BoxGeometry(1, 1, 1), + this.manager.createPreviewMaterial() + ); + this.manager.addPreview(this.preview); + this._updatePreview(); + } + + _updatePreview() { + if (!this.preview) return; + const { w, d, minX, minY } = this._dims(); + const h = this.height; + const zMin = Math.min(0, h); + // CAD center → three.js position; CAD (w, d, |h|) → three scale (x, z, y) + const center = [minX + w / 2, minY + d / 2, zMin + Math.abs(h) / 2]; + this.preview.position.copy(this.manager.cadToThree(center)); + this.preview.scale.set(Math.max(w, 0.01), Math.max(Math.abs(h), 0.01), Math.max(d, 0.01)); + this.manager.environment.viewDirty = true; + } + + _commit() { + const { w, d, minX, minY } = this._dims(); + const h = this.height; + this.cancel(); // removes preview, re-enables controls, resets state + this.emitBox([minX, minY, Math.min(0, h)], [w, d, Math.abs(h)]); + } + + /** Emit code for a box with CAD min-corner `corner` and dims [w, d, h]. */ + emitBox(corner, dims) { + const name = this.manager.nextVarName('box'); + if (this.manager.isPythonMode) { + // build123d's Box is CENTERED on the origin (unlike the JS Box, which + // is corner-origin), so place it via Pos at the box's center point. + const center = [ + corner[0] + dims[0] / 2, + corner[1] + dims[1] / 2, + corner[2] + dims[2] / 2, + ]; + const boxCall = 'Box(' + dims.join(', ') + ')'; + const needsPos = center[0] !== 0 || center[1] !== 0 || center[2] !== 0; + this.manager.commitCode(needsPos + ? name + ' = Pos(' + center.join(', ') + ') * ' + boxCall + : name + ' = ' + boxCall); + return; + } + const boxCall = 'Box(' + dims[0] + ', ' + dims[1] + ', ' + dims[2] + ')'; + const needsTranslate = corner[0] !== 0 || corner[1] !== 0 || corner[2] !== 0; + const snippet = needsTranslate + ? 'let ' + name + ' = Translate([' + corner[0] + ', ' + corner[1] + ', ' + corner[2] + '], ' + boxCall + ');' + : 'let ' + name + ' = ' + boxCall + ';'; + this.manager.commitCode(snippet); + } +} + +export { BoxTool }; diff --git a/packages/cascade-studio/src/tools/CylinderTool.js b/packages/cascade-studio/src/tools/CylinderTool.js new file mode 100644 index 00000000..199db5e8 --- /dev/null +++ b/packages/cascade-studio/src/tools/CylinderTool.js @@ -0,0 +1,153 @@ +// CylinderTool - click center, drag radius, then drag height +import * as THREE from 'three'; +import { Tool } from './Tool.js'; + +const IDLE = 0, DRAG_RADIUS = 1, DRAG_HEIGHT = 2; + +/** Cylinder creation tool: + * 1. pointerdown on the ground plane sets the center (snapped to mm) + * 2. drag (or move) sizes the radius (live preview) + * 3. release/click locks the radius; drag or move then sizes the height + * 4. release/click commits: `let cylinderN = Translate(..., Cylinder(r, h));` + * + * Both gestures work for every stage (see Tool.stageDown/stageUp): + * press-drag-release, and click-move-click. Escape cancels. */ +class CylinderTool extends Tool { + constructor(manager) { + super(manager, 'cylinder'); + this.state = IDLE; + this.preview = null; + this.centerCAD = null; // CAD [x, y, 0] + this.radius = 0; + this.height = 0; // CAD z height (may be negative) + } + + isInteracting() { return this.state !== IDLE; } + + onPointerDown(event) { + if (event.button !== 0) return false; + + if (this.state === IDLE) { + const hit = this.manager.raycastGround(event); + if (!hit) return false; + this.centerCAD = this.manager.snapGroundToCad(hit); + this.radius = 0; + this.height = 0; + this._createPreview(); + this.manager.beginInteraction(); + this.stagePressed = true; + this.state = DRAG_RADIUS; + return true; + } + + // Click-move-click: a press with the stage's dimension already set locks + // it in; a press with nothing set yet starts a drag (and must NOT cancel). + if (this.state === DRAG_RADIUS) { + if (this.stageDown(this.radius !== 0)) { this.state = DRAG_HEIGHT; } + return true; + } + + if (this.state === DRAG_HEIGHT) { + if (this.stageDown(this.height !== 0)) { this._commit(); } + return true; + } + return false; + } + + onPointerMove(event) { + if (this.state === DRAG_RADIUS) { + const hit = this.manager.raycastGround(event); + if (hit) { + const p = this.manager.threeToCad(hit); + this.radius = this.manager.snap(Math.hypot( + p[0] - this.centerCAD[0], p[1] - this.centerCAD[1] + )); + this._updatePreview(); + this.manager.showLabel(event, 'r = ' + this.radius); + } + return true; + } + if (this.state === DRAG_HEIGHT) { + const centerThree = this.manager.cadToThree(this.centerCAD); + centerThree.y = 0; + this.height = this.manager.heightFromRay(event, centerThree); + this._updatePreview(); + this.manager.showLabel(event, 'h = ' + this.height); + return true; + } + return false; + } + + onPointerUp(event) { + if (this.state === DRAG_RADIUS) { + if (this.stageUp(this.radius !== 0)) { this.state = DRAG_HEIGHT; } + return true; + } + if (this.state === DRAG_HEIGHT) { + if (this.stageUp(this.height !== 0)) { this._commit(); } + return true; + } + return false; + } + + cancel() { + this.manager.removePreview(this.preview); + this.preview = null; + if (this.state !== IDLE) { this.manager.endInteraction(); } + this.state = IDLE; + this.stagePressed = false; + this.manager.hideLabel(); + } + + _createPreview() { + // three.js CylinderGeometry's axis is Y-up, which matches CAD Z-up + this.preview = new THREE.Mesh( + new THREE.CylinderGeometry(1, 1, 1, 48), + this.manager.createPreviewMaterial() + ); + this.manager.addPreview(this.preview); + this._updatePreview(); + } + + _updatePreview() { + if (!this.preview) return; + const h = this.height; + const zMin = Math.min(0, h); + const center = [this.centerCAD[0], this.centerCAD[1], zMin + Math.abs(h) / 2]; + this.preview.position.copy(this.manager.cadToThree(center)); + const r = Math.max(this.radius, 0.01); + this.preview.scale.set(r, Math.max(Math.abs(h), 0.01), r); + this.manager.environment.viewDirty = true; + } + + _commit() { + const center = this.centerCAD; + const r = this.radius; + const h = this.height; + this.cancel(); + this.emitCylinder([center[0], center[1], Math.min(0, h)], r, Math.abs(h)); + } + + /** Emit code for a cylinder with CAD base center `base`, radius, height. */ + emitCylinder(base, radius, height) { + const name = this.manager.nextVarName('cylinder'); + const cylCall = 'Cylinder(' + radius + ', ' + height + ')'; + if (this.manager.isPythonMode) { + // build123d's Cylinder is CENTERED along Z (spans -h/2 .. +h/2), so + // place it via Pos at the cylinder's mid-height point. + const center = [base[0], base[1], base[2] + height / 2]; + const needsPos = center[0] !== 0 || center[1] !== 0 || center[2] !== 0; + this.manager.commitCode(needsPos + ? name + ' = Pos(' + center.join(', ') + ') * ' + cylCall + : name + ' = ' + cylCall); + return; + } + const needsTranslate = base[0] !== 0 || base[1] !== 0 || base[2] !== 0; + const snippet = needsTranslate + ? 'let ' + name + ' = Translate([' + base[0] + ', ' + base[1] + ', ' + base[2] + '], ' + cylCall + ');' + : 'let ' + name + ' = ' + cylCall + ';'; + this.manager.commitCode(snippet); + } +} + +export { CylinderTool }; diff --git a/packages/cascade-studio/src/tools/FilletTool.js b/packages/cascade-studio/src/tools/FilletTool.js new file mode 100644 index 00000000..82f3e96d --- /dev/null +++ b/packages/cascade-studio/src/tools/FilletTool.js @@ -0,0 +1,224 @@ +// FilletTool - click edges to select them, set a radius, Enter commits +import { Tool } from './Tool.js'; + +/** Fillet tool. Clicking a solid's edge toggles it in the selection + * (highlighted orange). A small inline panel takes the radius; Enter (or + * Apply) emits `shapeVar = FilletEdges(shapeVar, radius, [indices]);` for + * each shape with selected edges, reusing the exact per-shape edge indices + * that the hover tooltip shows and FilletEdges() consumes. */ +class FilletTool extends Tool { + constructor(manager) { + super(manager, 'fillet'); + // globalEdgeIndex → { shapeIndex, localEdgeIndex } + this.selection = new Map(); + this._downPos = null; + this._panel = null; + } + + isInteracting() { return this.selection.size > 0; } + + activate() { + if (!this._panel) { this._buildPanel(); } + } + + deactivate() { + this.cancel(); + } + + cancel() { + this._clearSelection(); + this._downPos = null; + } + + /** The scene meshes were rebuilt — any selected edge references are stale. */ + onSceneRebuilt() { + this.selection.clear(); + this._updatePanel(); + } + + /** Record the pointer-down position; never consume (orbiting stays live). */ + onPointerDown(event) { + if (event.button === 0) { + this._downPos = { x: event.clientX, y: event.clientY }; + } + return false; + } + + /** On a click (< 5 px of movement), toggle the edge under the pointer. */ + onPointerUp(event) { + if (event.button !== 0 || !this._downPos) return false; + const moved = Math.hypot( + event.clientX - this._downPos.x, + event.clientY - this._downPos.y + ); + this._downPos = null; + if (moved > 5) return false; + + const hit = this.manager.raycastEdge(event); + if (!hit) return false; + const pick = this.manager.viewport.getPickInfo(hit); + if (!pick || pick.kind !== 'edge' || pick.localEdgeIndex < 0 || pick.shapeIndex < 0) { + return false; + } + + if (this.selection.has(pick.globalEdgeIndex)) { + this.selection.delete(pick.globalEdgeIndex); + } else { + this.selection.set(pick.globalEdgeIndex, { + shapeIndex: pick.shapeIndex, + localEdgeIndex: pick.localEdgeIndex + }); + } + this._paintSelection(); + this._updatePanel(); + return true; + } + + /** Commit the fillet: emit FilletEdges() calls and re-evaluate. */ + commit(radius) { + if (this.selection.size === 0) return; + if (!(radius > 0)) { + console.error('Fillet radius must be a positive number.'); + return; + } + + // Group selected local edge indices by owning sceneShape + const byShape = new Map(); + for (let { shapeIndex, localEdgeIndex } of this.selection.values()) { + if (!byShape.has(shapeIndex)) { byShape.set(shapeIndex, new Set()); } + byShape.get(shapeIndex).add(localEdgeIndex); + } + + const claimedNames = new Set(); + const snippets = []; + for (let [shapeIndex, indexSet] of byShape) { + const lineNumber = this.manager.viewport.getShapeLine(shapeIndex); + if (lineNumber < 1) { + console.error('Fillet: could not map the clicked shape back to a code line.'); + continue; + } + const varName = this._resolveVarName(lineNumber, claimedNames); + if (!varName) { + console.error('Fillet: could not identify a variable for the shape on line ' + + lineNumber + '. Assign the shape to a variable first.'); + continue; + } + claimedNames.add(varName); + const indices = [...indexSet].sort((a, b) => a - b); + if (this.manager.isPythonMode) { + // build123d-lite: edges(indices=[...]) is the escape hatch carrying + // the same per-shape edge indices the hover tooltip shows. + snippets.push(varName + ' = fillet(' + varName + '.edges(indices=[' + + indices.join(', ') + ']), ' + radius + ')'); + } else { + snippets.push(varName + ' = FilletEdges(' + varName + ', ' + radius + + ', [' + indices.join(', ') + ']);'); + } + } + + this._clearSelection(); + if (snippets.length > 0) { + this.manager.commitCode(snippets.join('\n')); + } + } + + /** Find (or create) the variable name holding the shape produced at + * `lineNumber`. Bare expression statements like `Box(10, 10, 10);` are + * rewritten in place to `let box1 = Box(10, 10, 10);` (or, in Python + * mode, `box1 = Box(10, 10, 10)`). */ + _resolveVarName(lineNumber, claimedNames) { + const editor = this.manager.editor; + const lineText = editor.getLineContent(lineNumber); + + let m = lineText.match(/^\s*(?:let|var|const)\s+([A-Za-z_$][\w$]*)\s*=/); + if (!m) { m = lineText.match(/^\s*([A-Za-z_$][\w$]*)\s*=[^=]/); } + if (m) { return m[1]; } + + // Bare expression statement — rewrite it with a fresh assignment + const expr = lineText.match(/^(\s*)((?:new\s+)?([A-Za-z_$][\w$]*)\s*\(.*)$/); + if (!expr) { return null; } + const name = this.manager.nextVarName(expr[3].toLowerCase(), claimedNames); + const decl = this.manager.isPythonMode ? '' : 'let '; + editor.replaceLine(lineNumber, expr[1] + decl + name + ' = ' + expr[2]); + return name; + } + + /** Push the current selection into the edge mesh's highlight colors. */ + _paintSelection() { + const line = this._edgeMesh(); + if (!line) return; + line.selectedEdges = new Set(this.selection.keys()); + line.clearHighlights(); + this.manager.environment.viewDirty = true; + } + + _clearSelection() { + this.selection.clear(); + const line = this._edgeMesh(); + if (line) { + line.selectedEdges = new Set(); + line.clearHighlights(); + this.manager.environment.viewDirty = true; + } + this._updatePanel(); + } + + /** The current model's LineSegments mesh (or null). */ + _edgeMesh() { + const mainObject = this.manager.viewport.mainObject; + if (!mainObject) return null; + return mainObject.children.find((c) => c.type === 'LineSegments') || null; + } + + // ===== Radius panel DOM ===== + + _buildPanel() { + this._panel = document.createElement('div'); + this._panel.className = 'cs-fillet-panel'; + this._panel.style.display = 'none'; + + this._countEl = document.createElement('span'); + this._countEl.className = 'cs-fillet-count'; + + const label = document.createElement('span'); + label.textContent = 'r ='; + + this._input = document.createElement('input'); + this._input.type = 'number'; + this._input.value = '2'; + this._input.min = '0.1'; + this._input.step = '0.5'; + this._input.addEventListener('keydown', (e) => { + e.stopPropagation(); + if (e.key === 'Enter') { this.commit(parseFloat(this._input.value)); } + if (e.key === 'Escape') { this.cancel(); } + }); + + const apply = document.createElement('button'); + apply.textContent = 'Apply'; + apply.addEventListener('click', (e) => { + e.stopPropagation(); + this.commit(parseFloat(this._input.value)); + }); + + this._panel.appendChild(this._countEl); + this._panel.appendChild(label); + this._panel.appendChild(this._input); + this._panel.appendChild(apply); + this.manager.viewport.goldenContainer.element.appendChild(this._panel); + } + + _updatePanel() { + if (!this._panel) return; + if (this.selection.size === 0) { + this._panel.style.display = 'none'; + return; + } + this._countEl.textContent = this.selection.size + ' edge' + + (this.selection.size !== 1 ? 's' : ''); + this._panel.style.display = ''; + this._input.focus(); + } +} + +export { FilletTool }; diff --git a/packages/cascade-studio/src/tools/SelectTool.js b/packages/cascade-studio/src/tools/SelectTool.js new file mode 100644 index 00000000..a7362964 --- /dev/null +++ b/packages/cascade-studio/src/tools/SelectTool.js @@ -0,0 +1,47 @@ +// SelectTool - default tool; clicking a shape reveals its producing code line +import { Tool } from './Tool.js'; + +/** Default tool. Camera controls stay fully enabled; a click (as opposed to + * an orbit drag) picks the shape under the pointer and reveals + flashes + * the editor line that produced it. */ +class SelectTool extends Tool { + constructor(manager) { + super(manager, 'select'); + this._downPos = null; + } + + /** Record the pointer-down position to distinguish clicks from drags. + * Never consumes the event, so OrbitControls keeps working. */ + onPointerDown(event) { + if (event.button === 0) { + this._downPos = { x: event.clientX, y: event.clientY }; + } + return false; + } + + /** On a click (< 5 px of movement), map the picked shape to its code line. */ + onPointerUp(event) { + if (event.button !== 0 || !this._downPos) return false; + const moved = Math.hypot( + event.clientX - this._downPos.x, + event.clientY - this._downPos.y + ); + this._downPos = null; + if (moved > 5) return false; + + const hit = this.manager.raycastScene(event); + if (!hit) return false; + const pick = this.manager.viewport.getPickInfo(hit); + if (!pick || pick.shapeIndex < 0) return false; + + const lineNumber = this.manager.viewport.getShapeLine(pick.shapeIndex); + if (lineNumber > 0) { + this.manager.editor.flashLine(lineNumber); + } + return false; + } + + cancel() { this._downPos = null; } +} + +export { SelectTool }; diff --git a/packages/cascade-studio/src/tools/SketchTool.js b/packages/cascade-studio/src/tools/SketchTool.js new file mode 100644 index 00000000..289d1605 --- /dev/null +++ b/packages/cascade-studio/src/tools/SketchTool.js @@ -0,0 +1,720 @@ +// SketchTool - stateful multi-click polyline/arc sketching (Fusion/SolidWorks- +// style sketch → extrude workflow). Emits `new Sketch(...).LineTo(...) +// .ArcTo(...).End(true).Face()` plus an optional Extrude/Revolve against the +// StandardLibrary Sketch builder. +import * as THREE from 'three'; +import { Tool } from './Tool.js'; + +const IDLE = 0, DRAWING = 1, CLOSED = 2, HEIGHT_DRAG = 3; + +/** Sketch tool state machine: + * 1. clicks on the sketch plane place vertices (1 mm grid snap); a + * rubber-band previews the pending segment (length + angle label); + * the Line/Arc toggle (or the L / A keys) selects the segment type — + * in Arc mode each segment takes two clicks (through-point, then end) + * and the rubber-band renders the live three-point circular arc; + * Escape removes the last vertex (a half-placed arc through-point is + * its own undo step); Enter or clicking the first vertex closes the + * profile (min 3 vertices) — closing works from Arc mode too + * 2. once closed, the inline panel offers Extrude / Revolve / Face only + * with a numeric value; for Extrude, dragging vertically inside the + * profile sets the height interactively (the input reflects the drag); + * clicking corner vertices toggles them into a sketch-fillet set + * (vertex 0, the Sketch start point, cannot be filleted — CLAUDE.md + * pitfall 5; arc-junction vertices are allowed, verified against ChFi2d) + * 3. Apply emits e.g.: + * let profile1 = new Sketch([20, 10]) + * .LineTo([60, 10]).ArcTo([70, 25], [60, 40]).LineTo([20, 40]) + * .End(true).Face(); + * let part1 = Extrude(profile1, [0, 0, 25]); + * + * The sketch plane is a parameter (CAD origin + u/v basis) so that + * sketch-on-face support can be added later; v1 always uses the ground + * plane (XY at z=0), which maps 1:1 onto the default `new Sketch([u, v])` + * plane in the emitted code. */ +class SketchTool extends Tool { + constructor(manager) { + super(manager, 'sketch'); + this.state = IDLE; + + // Sketch plane (CAD space): origin + orthonormal u/v basis. + // v1: ground plane. A future sketch-on-face feature swaps this out + // (emission would then need a Transform/plane argument as well). + this.plane = { + origin: [0, 0, 0], + uDir: [1, 0, 0], + vDir: [0, 1, 0], + nDir: [0, 0, 1], + }; + + this.vertices = []; // snapped [u, v] anchor points + // segments[j] connects vertices[j] → vertices[j+1]: + // { type: 'line' } | { type: 'arc', through: [u, v] } + this.segments = []; + this.closingSegment = null; // null = implicit line close via End(true) + this.segMode = 'line'; // 'line' | 'arc' (L / A keys) + this.filletVerts = new Set(); // vertex indices (>= 1) to .Fillet() + this.height = 0; // extrude height (CAD, may be negative) + + this._pendingThrough = null; // arc through-point awaiting its end click + this._downPos = null; + this._group = null; // THREE.Group (rotation maps CAD Z-up → three Y-up) + this._loopLine = null; + this._rubberLine = null; + this._markers = []; + this._extrudePreview = null; + this._panel = null; + } + + isInteracting() { return this.state !== IDLE; } + + activate() { + if (!this._panel) { this._buildPanel(); } + this._panel.style.display = ''; + this._updatePanelStage(); + } + + deactivate() { + this.cancel(); + if (this._panel) { this._panel.style.display = 'none'; } + } + + cancel() { + this.manager.removePreview(this._group); + this._group = null; + this._loopLine = null; + this._rubberLine = null; + this._markers = []; + this._extrudePreview = null; + this.vertices = []; + this.segments = []; + this.closingSegment = null; + this.filletVerts.clear(); + this._pendingThrough = null; + this.height = 0; + this.segMode = 'line'; // each new profile starts in Line mode + if (this.state === HEIGHT_DRAG) { this.manager.endInteraction(); } + this.state = IDLE; + this.manager.hideLabel(); + this._updatePanelStage(); + } + + /** Escape: half-placed arc → drop the through-point; then vertex-level + * undo while drawing; otherwise cancel the whole sketch. */ + onEscape() { + if (this.state === DRAWING && this._pendingThrough) { + this._pendingThrough = null; + this._rebuildVisuals(); + return true; + } + if (this.state === DRAWING && this.vertices.length >= 2) { + this.vertices.pop(); + this.segments.pop(); + this._rebuildVisuals(); + return true; + } + if (this.state !== IDLE) { + this.cancel(); + return true; + } + return false; + } + + /** Enter closes the profile; L / A switch the segment type. */ + onKeyDown(event) { + if (event.code === 'Enter' && this.state === DRAWING && this.vertices.length >= 3) { + this._pendingThrough = null; + this._closeProfile(null); + return true; + } + if ((event.code === 'KeyL' || event.code === 'KeyA') && + (this.state === IDLE || this.state === DRAWING)) { + this.setSegMode(event.code === 'KeyA' ? 'arc' : 'line'); + return true; + } + return false; + } + + /** Switch between Line and Arc segment placement. */ + setSegMode(mode) { + if (mode !== 'line' && mode !== 'arc') return; + this.segMode = mode; + this._pendingThrough = null; + this._updatePanelStage(); + if (this._group) { this._rebuildVisuals(); } + } + + onPointerDown(event) { + if (event.button !== 0) return false; + this._downPos = { x: event.clientX, y: event.clientY }; + + // In the closed state, dragging inside the profile sets the extrude + // height interactively (consumes the event so OrbitControls stays out) + if (this.state === CLOSED && this._panel && this._opSelect.value === 'extrude') { + if (this._vertexIndexAtScreen(event) < 0) { + const uv = this._hitUV(event, false); + if (uv && this._pointInPolygon(uv[0], uv[1])) { + this.manager.beginInteraction(); + this.state = HEIGHT_DRAG; + return true; + } + } + } + return false; // clicks are detected on pointerup; orbiting stays live + } + + onPointerMove(event) { + if (this.state === DRAWING) { + this._updateRubberBand(event); + return false; // don't consume — orbit drags still work mid-sketch + } + if (this.state === HEIGHT_DRAG) { + const base = this._uvToThree(...this._centroidUV()); + this.height = this.manager.heightFromRay(event, base); + this._valueInput.value = this.height; + this._updateExtrudePreview(); + this.manager.showLabel(event, 'h = ' + this.height); + return true; + } + return false; + } + + onPointerUp(event) { + if (this.state === HEIGHT_DRAG) { + this.manager.endInteraction(); + this.state = CLOSED; + this.manager.hideLabel(); + return true; + } + + if (event.button !== 0 || !this._downPos) return false; + const moved = Math.hypot( + event.clientX - this._downPos.x, + event.clientY - this._downPos.y + ); + this._downPos = null; + if (moved > 5) return false; + + if (this.state === IDLE || this.state === DRAWING) { + return this._handleDrawClick(event); + } + if (this.state === CLOSED) { + return this._handleClosedClick(event); + } + return false; + } + + // ===== Drawing phase ===== + + /** Place a vertex / arc through-point, or close the profile when the + * first vertex is clicked (works from Arc mode: the final segment is + * then an arc whose end snaps onto vertex 0). */ + _handleDrawClick(event) { + const uv = this._hitUV(event, true); + if (!uv) return false; + + if (this.state === IDLE) { + this.vertices = [uv]; + this.segments = []; + this.closingSegment = null; + this.filletVerts.clear(); + this._pendingThrough = null; + this.height = 0; + this._createGroup(); + this._rebuildVisuals(); + this.state = DRAWING; + return true; + } + + // Arc mode, first of the two clicks: place the through-point + if (this.segMode === 'arc' && !this._pendingThrough) { + this._pendingThrough = uv; + this._rebuildVisuals(); + return true; + } + + // Close if clicking on (or within snap distance of) the first vertex + const rawUV = this._hitUV(event, false); + const d0 = Math.hypot(rawUV[0] - this.vertices[0][0], rawUV[1] - this.vertices[0][1]); + const closing = this.vertices.length >= 3 && (d0 <= 1.5 || + (uv[0] === this.vertices[0][0] && uv[1] === this.vertices[0][1])); + + if (this.segMode === 'arc' && this._pendingThrough) { + const through = this._pendingThrough; + this._pendingThrough = null; + if (closing) { + this._closeProfile({ type: 'arc', through }); + } else { + const last = this.vertices[this.vertices.length - 1]; + if (uv[0] === last[0] && uv[1] === last[1]) return true; // degenerate + this.vertices.push(uv); + this.segments.push({ type: 'arc', through }); + this._rebuildVisuals(); + } + return true; + } + + if (closing) { + this._closeProfile(null); + return true; + } + + // Ignore duplicate consecutive vertices + const last = this.vertices[this.vertices.length - 1]; + if (uv[0] === last[0] && uv[1] === last[1]) return true; + + this.vertices.push(uv); + this.segments.push({ type: 'line' }); + this._rebuildVisuals(); + return true; + } + + /** @param {?{type: string, through: number[]}} closingSegment + * null closes with an implicit straight line (via `.End(true)`). */ + _closeProfile(closingSegment) { + this.closingSegment = closingSegment; + this.state = CLOSED; + this.manager.hideLabel(); + this._rebuildVisuals(); + this._showCommitStage(); + } + + // ===== Closed phase ===== + + /** Toggle a corner vertex into the sketch-fillet set. */ + _handleClosedClick(event) { + const idx = this._vertexIndexAtScreen(event); + if (idx < 0) return false; + if (idx === 0) { + console.log("The sketch start point can't be filleted — pick another corner."); + return true; + } + if (this.filletVerts.has(idx)) { + this.filletVerts.delete(idx); + } else { + this.filletVerts.add(idx); + } + this._rebuildVisuals(); + return true; + } + + /** Emit the profile (+ operation) and re-evaluate. */ + commit() { + if (this.state !== CLOSED || this.vertices.length < 3) return; + const op = this._opSelect.value; + let value = parseFloat(this._valueInput.value); + if (op === 'extrude' && (!value || isNaN(value))) { + console.error('Extrude height must be a non-zero number (drag inside the profile or type a value).'); + return; + } + if (op === 'revolve' && (isNaN(value) || value === 0)) { value = 360; } + const filletR = parseFloat(this._filletInput.value); + + const snippet = this._buildSnippet(op, value, filletR); + this.cancel(); // clears previews, resets to the drawing-ready state + this.manager.commitCode(snippet); + } + + /** Build the emitted code against the StandardLibrary Sketch builder. */ + _buildSnippet(op, value, filletR) { + const mgr = this.manager; + const profileName = mgr.nextVarName('profile'); + const v = this.vertices; + + // Chained segment calls: .LineTo([x, y]) / .ArcTo([tx, ty], [x, y]) + // with optional .Fillet(r) on selected corner vertices + const calls = []; + for (let i = 1; i < v.length; i++) { + const seg = this.segments[i - 1]; + let call = (seg && seg.type === 'arc') + ? '.ArcTo([' + seg.through[0] + ', ' + seg.through[1] + '], [' + v[i][0] + ', ' + v[i][1] + '])' + : '.LineTo([' + v[i][0] + ', ' + v[i][1] + '])'; + if (this.filletVerts.has(i) && filletR > 0) { + call += '.Fillet(' + filletR + ')'; + } + calls.push(call); + } + if (this.closingSegment && this.closingSegment.type === 'arc') { + const t = this.closingSegment.through; + calls.push('.ArcTo([' + t[0] + ', ' + t[1] + '], [' + v[0][0] + ', ' + v[0][1] + '])'); + } + + // ~3 segment calls per line for readability + const lines = ['let ' + profileName + ' = new Sketch([' + v[0][0] + ', ' + v[0][1] + '])']; + for (let i = 0; i < calls.length; i += 3) { + lines.push(' ' + calls.slice(i, i + 3).join('')); + } + lines.push(' .End(true).Face();'); + let snippet = lines.join('\n'); + + if (op === 'extrude') { + const partName = mgr.nextVarName('part', new Set([profileName])); + snippet += '\nlet ' + partName + ' = Extrude(' + profileName + ', [0, 0, ' + value + ']);'; + } else if (op === 'revolve') { + snippet += '\nRevolve(' + profileName + ', ' + value + ');'; + } + return snippet; + } + + // ===== Plane / coordinate helpers ===== + + /** Raycast the pointer onto the sketch plane; returns [u, v] (snapped + * to the mm grid when `snapped`) or null. */ + _hitUV(event, snapped) { + const p = this.plane; + const normalThree = this.manager.cadToThree(p.nDir).normalize(); + const originThree = this.manager.cadToThree(p.origin); + const threePlane = new THREE.Plane().setFromNormalAndCoplanarPoint(normalThree, originThree); + const ray = this.manager.pointerRay(event).ray; + const hit = new THREE.Vector3(); + if (!ray.intersectPlane(threePlane, hit)) return null; + + const cad = this.manager.threeToCad(hit); + const rel = [cad[0] - p.origin[0], cad[1] - p.origin[1], cad[2] - p.origin[2]]; + let u = rel[0] * p.uDir[0] + rel[1] * p.uDir[1] + rel[2] * p.uDir[2]; + let vv = rel[0] * p.vDir[0] + rel[1] * p.vDir[1] + rel[2] * p.vDir[2]; + if (snapped) { u = this.manager.snap(u); vv = this.manager.snap(vv); } + return [u, vv]; + } + + /** Sketch [u, v] (+ offset `w` along the plane normal) → CAD [x, y, z]. */ + _uvToCad(u, v, w = 0) { + const p = this.plane; + return [ + p.origin[0] + u * p.uDir[0] + v * p.vDir[0] + w * p.nDir[0], + p.origin[1] + u * p.uDir[1] + v * p.vDir[1] + w * p.nDir[1], + p.origin[2] + u * p.uDir[2] + v * p.vDir[2] + w * p.nDir[2], + ]; + } + + /** Sketch [u, v] → three.js world Vector3. */ + _uvToThree(u, v, w = 0) { + return this.manager.cadToThree(this._uvToCad(u, v, w)); + } + + /** Sample the circular arc from A through T to B (24 segments). + * Returns [u, v] points excluding A, including B. Collinear points + * degrade to a straight segment. */ + _sampleArc(A, T, B, count = 24) { + const d = 2 * (A[0] * (T[1] - B[1]) + T[0] * (B[1] - A[1]) + B[0] * (A[1] - T[1])); + if (Math.abs(d) < 1e-9) { return [B.slice()]; } // collinear → line + const a2 = A[0] * A[0] + A[1] * A[1]; + const t2 = T[0] * T[0] + T[1] * T[1]; + const b2 = B[0] * B[0] + B[1] * B[1]; + const cx = (a2 * (T[1] - B[1]) + t2 * (B[1] - A[1]) + b2 * (A[1] - T[1])) / d; + const cy = (a2 * (B[0] - T[0]) + t2 * (A[0] - B[0]) + b2 * (T[0] - A[0])) / d; + const r = Math.hypot(A[0] - cx, A[1] - cy); + + const TWO_PI = Math.PI * 2; + const a0 = Math.atan2(A[1] - cy, A[0] - cx); + const aT = ((Math.atan2(T[1] - cy, T[0] - cx) - a0) % TWO_PI + TWO_PI) % TWO_PI; + const aB = ((Math.atan2(B[1] - cy, B[0] - cx) - a0) % TWO_PI + TWO_PI) % TWO_PI; + // Sweep CCW if the through-point comes before the end going CCW, + // otherwise sweep CW (negative) + const sweep = (aT <= aB) ? aB : aB - TWO_PI; + + const pts = []; + for (let i = 1; i <= count; i++) { + const ang = a0 + sweep * (i / count); + pts.push([cx + r * Math.cos(ang), cy + r * Math.sin(ang)]); + } + pts[pts.length - 1] = B.slice(); // land exactly on B + return pts; + } + + /** Sampled outline of the profile in UV space (arcs discretized). + * Includes the closing segment when the profile is closed. */ + _outlinePoints() { + const pts = [this.vertices[0].slice()]; + for (let j = 0; j < this.segments.length; j++) { + const a = this.vertices[j], b = this.vertices[j + 1]; + const seg = this.segments[j]; + if (seg.type === 'arc') { + pts.push(...this._sampleArc(a, seg.through, b)); + } else { + pts.push(b.slice()); + } + } + if (this.state === CLOSED || this.state === HEIGHT_DRAG) { + const last = this.vertices[this.vertices.length - 1]; + if (this.closingSegment && this.closingSegment.type === 'arc') { + pts.push(...this._sampleArc(last, this.closingSegment.through, this.vertices[0])); + } else { + pts.push(this.vertices[0].slice()); + } + } + return pts; + } + + _centroidUV() { + let cu = 0, cv = 0; + for (let [u, v] of this.vertices) { cu += u; cv += v; } + return [cu / this.vertices.length, cv / this.vertices.length]; + } + + /** Ray-casting point-in-polygon test on the sampled outline. */ + _pointInPolygon(u, v) { + let inside = false; + const vs = this._outlinePoints(); + for (let i = 0, j = vs.length - 1; i < vs.length; j = i++) { + const [xi, yi] = vs[i], [xj, yj] = vs[j]; + if (((yi > v) !== (yj > v)) && + (u < (xj - xi) * (v - yi) / (yj - yi) + xi)) { + inside = !inside; + } + } + return inside; + } + + /** Index of the vertex whose screen projection is within 12 px, or -1. */ + _vertexIndexAtScreen(event) { + const env = this.manager.environment; + const rect = env.renderer.domElement.getBoundingClientRect(); + for (let i = 0; i < this.vertices.length; i++) { + const world = this._uvToThree(this.vertices[i][0], this.vertices[i][1]); + const proj = world.project(env.camera); + const sx = rect.left + (proj.x + 1) / 2 * rect.width; + const sy = rect.top + (1 - (proj.y + 1) / 2) * rect.height; + if (Math.hypot(event.clientX - sx, event.clientY - sy) <= 12) return i; + } + return -1; + } + + // ===== Visuals ===== + + /** The group's -PI/2 X rotation maps CAD Z-up into the three.js Y-up + * scene, so all children use raw CAD coordinates (like mainObject). */ + _createGroup() { + this._group = new THREE.Group(); + this._group.rotation.x = -Math.PI / 2; + this.manager.addPreview(this._group); + } + + /** Convert UV points to slightly normal-offset CAD-space vectors. */ + _uvToLinePoints(uvPts) { + return uvPts.map(([u, v]) => { + const c = this._uvToCad(u, v, 0.05); + return new THREE.Vector3(c[0], c[1], c[2]); + }); + } + + /** Rebuild the polyline, markers, and previews from current state. */ + _rebuildVisuals() { + if (!this._group) return; + + // Committed outline (arcs sampled; closed loop once the profile closes) + if (this._loopLine) { this._group.remove(this._loopLine); } + const pts = this._uvToLinePoints(this._outlinePoints()); + this._loopLine = new THREE.Line( + new THREE.BufferGeometry().setFromPoints(pts), + new THREE.LineBasicMaterial({ color: 0x4CAF50 }) + ); + this._group.add(this._loopLine); + + // Rubber band (drawing phase only) + if (this._rubberLine) { this._group.remove(this._rubberLine); this._rubberLine = null; } + if (this.state === DRAWING || this.state === IDLE) { + const tail = pts[pts.length - 1].clone(); + this._rubberLine = new THREE.Line( + new THREE.BufferGeometry().setFromPoints([tail, tail.clone()]), + new THREE.LineBasicMaterial({ color: 0x88cc88, transparent: true, opacity: 0.7 }) + ); + this._group.add(this._rubberLine); + } + + // Vertex markers: start = green (unfilletable), fillet-selected = + // orange, pending arc through-point = blue + for (let m of this._markers) { this._group.remove(m); } + this._markers = []; + const addMarker = (uv, color, radius) => { + const marker = new THREE.Mesh( + new THREE.SphereGeometry(radius, 12, 8), + new THREE.MeshBasicMaterial({ color }) + ); + const c = this._uvToCad(uv[0], uv[1], 0.05); + marker.position.set(c[0], c[1], c[2]); + this._group.add(marker); + this._markers.push(marker); + }; + for (let i = 0; i < this.vertices.length; i++) { + const color = (i === 0) ? 0x4CAF50 : (this.filletVerts.has(i) ? 0xff8c1a : 0xbbbbbb); + addMarker(this.vertices[i], color, i === 0 ? 1.4 : 1.0); + } + if (this._pendingThrough) { addMarker(this._pendingThrough, 0x66aaff, 0.8); } + + this._updateExtrudePreview(); + this.manager.environment.viewDirty = true; + } + + /** Semi-transparent extruded preview of the closed profile. */ + _updateExtrudePreview() { + if (this._extrudePreview) { + this._group.remove(this._extrudePreview); + this._extrudePreview.geometry.dispose(); + this._extrudePreview = null; + } + const op = this._panel ? this._opSelect.value : 'extrude'; + if (this.state !== CLOSED && this.state !== HEIGHT_DRAG) return; + if (op !== 'extrude' || this.height === 0) return; + + const outline = this._outlinePoints(); + const shape = new THREE.Shape(outline.map(([u, v]) => new THREE.Vector2(u, v))); + const geometry = new THREE.ExtrudeGeometry(shape, { + depth: Math.abs(this.height), bevelEnabled: false + }); + this._extrudePreview = new THREE.Mesh(geometry, this.manager.createPreviewMaterial()); + this._extrudePreview.position.z = Math.min(0, this.height); + this._group.add(this._extrudePreview); + this.manager.environment.viewDirty = true; + } + + /** Live rubber-band segment (straight line, or the three-point arc once + * a through-point is placed) with a length/radius + angle label. */ + _updateRubberBand(event) { + if (!this._rubberLine || this.vertices.length === 0) return; + const uv = this._hitUV(event, true); + if (!uv) return; + const last = this.vertices[this.vertices.length - 1]; + + if (this._pendingThrough) { + // Live three-point arc: last vertex → through-point → cursor + const arcUV = [last, ...this._sampleArc(last, this._pendingThrough, uv)]; + this._rubberLine.geometry.setFromPoints(this._uvToLinePoints(arcUV)); + const d = 2 * (last[0] * (this._pendingThrough[1] - uv[1]) + + this._pendingThrough[0] * (uv[1] - last[1]) + + uv[0] * (last[1] - this._pendingThrough[1])); + if (Math.abs(d) > 1e-9) { + const samples = this._sampleArc(last, this._pendingThrough, uv, 2); + const mid = samples[0]; + const chord = Math.hypot(uv[0] - last[0], uv[1] - last[1]); + const sagitta = Math.hypot(mid[0] - (last[0] + uv[0]) / 2, mid[1] - (last[1] + uv[1]) / 2); + const radius = (sagitta > 1e-9) ? (chord * chord / (8 * sagitta) + sagitta / 2) : 0; + this.manager.showLabel(event, 'arc r ≈ ' + Math.round(radius * 10) / 10 + ' mm'); + } else { + this.manager.showLabel(event, 'arc (collinear)'); + } + } else { + this._rubberLine.geometry.setFromPoints(this._uvToLinePoints([last, uv])); + const du = uv[0] - last[0], dv = uv[1] - last[1]; + const len = Math.round(Math.hypot(du, dv) * 10) / 10; + const ang = Math.round(Math.atan2(dv, du) * 180 / Math.PI); + const prefix = this.segMode === 'arc' ? 'arc through-point ' : ''; + this.manager.showLabel(event, prefix + len + ' mm ∠' + ang + '°'); + } + this.manager.environment.viewDirty = true; + } + + // ===== Panel DOM (Line/Arc toggle always; commit widgets when closed) ===== + + _buildPanel() { + this._panel = document.createElement('div'); + this._panel.className = 'cs-sketch-panel'; + this._panel.style.display = 'none'; + + // Segment type toggle (L / A keyboard shortcuts) + this._lineBtn = document.createElement('button'); + this._lineBtn.textContent = 'Line'; + this._lineBtn.title = 'Straight segments (L)'; + this._lineBtn.addEventListener('click', (e) => { e.stopPropagation(); this.setSegMode('line'); }); + this._arcBtn = document.createElement('button'); + this._arcBtn.textContent = 'Arc'; + this._arcBtn.title = 'Three-point arcs: click the through-point, then the arc end (A)'; + this._arcBtn.addEventListener('click', (e) => { e.stopPropagation(); this.setSegMode('arc'); }); + + this._opSelect = document.createElement('select'); + for (let [val, label] of [['extrude', 'Extrude'], ['revolve', 'Revolve'], ['face', 'Face only']]) { + const opt = document.createElement('option'); + opt.value = val; + opt.textContent = label; + this._opSelect.appendChild(opt); + } + this._opSelect.addEventListener('change', () => { + this._valueLabel.textContent = (this._opSelect.value === 'revolve') ? '∠' : 'h ='; + this._valueInput.value = (this._opSelect.value === 'revolve') ? '360' : String(this.height || 10); + const showValue = this._opSelect.value !== 'face'; + this._valueLabel.style.display = showValue ? '' : 'none'; + this._valueInput.style.display = showValue ? '' : 'none'; + this._updateExtrudePreview(); + }); + + this._valueLabel = document.createElement('span'); + this._valueLabel.textContent = 'h ='; + + this._valueInput = document.createElement('input'); + this._valueInput.type = 'number'; + this._valueInput.value = '10'; + this._valueInput.step = '1'; + this._valueInput.addEventListener('input', () => { + if (this._opSelect.value === 'extrude') { + this.height = parseFloat(this._valueInput.value) || 0; + this._updateExtrudePreview(); + } + }); + + this._filletLabel = document.createElement('span'); + this._filletLabel.textContent = 'fillet r ='; + + this._filletInput = document.createElement('input'); + this._filletInput.type = 'number'; + this._filletInput.value = '3'; + this._filletInput.min = '0.1'; + this._filletInput.step = '0.5'; + + this._applyBtn = document.createElement('button'); + this._applyBtn.textContent = 'Apply'; + this._applyBtn.addEventListener('click', (e) => { e.stopPropagation(); this.commit(); }); + + this._cancelBtn = document.createElement('button'); + this._cancelBtn.textContent = 'Cancel'; + this._cancelBtn.className = 'cs-sketch-cancel'; + this._cancelBtn.addEventListener('click', (e) => { e.stopPropagation(); this.cancel(); }); + + for (let input of [this._valueInput, this._filletInput]) { + input.addEventListener('keydown', (e) => { + e.stopPropagation(); + if (e.key === 'Enter') { this.commit(); } + if (e.key === 'Escape') { this.cancel(); } + }); + } + + this._panel.appendChild(this._lineBtn); + this._panel.appendChild(this._arcBtn); + this._panel.appendChild(this._opSelect); + this._panel.appendChild(this._valueLabel); + this._panel.appendChild(this._valueInput); + this._panel.appendChild(this._filletLabel); + this._panel.appendChild(this._filletInput); + this._panel.appendChild(this._applyBtn); + this._panel.appendChild(this._cancelBtn); + this.manager.viewport.goldenContainer.element.appendChild(this._panel); + } + + /** Show only the Line/Arc toggle while drawing; the full commit + * controls appear once the profile is closed. */ + _updatePanelStage() { + if (!this._panel) return; + const committing = (this.state === CLOSED || this.state === HEIGHT_DRAG); + for (let el of [this._opSelect, this._valueLabel, this._valueInput, + this._filletLabel, this._filletInput, this._applyBtn, this._cancelBtn]) { + el.style.display = committing ? '' : 'none'; + } + const drawing = (this.state === IDLE || this.state === DRAWING); + this._lineBtn.style.display = drawing ? '' : 'none'; + this._arcBtn.style.display = drawing ? '' : 'none'; + this._lineBtn.classList.toggle('cs-seg-active', this.segMode === 'line'); + this._arcBtn.classList.toggle('cs-seg-active', this.segMode === 'arc'); + } + + _showCommitStage() { + if (!this._panel) { this._buildPanel(); } + this._panel.style.display = ''; + this._opSelect.value = 'extrude'; + this._valueLabel.textContent = 'h ='; + this._valueInput.value = String(this.height || 10); + this.height = parseFloat(this._valueInput.value) || 0; + this._updatePanelStage(); + this._updateExtrudePreview(); + } +} + +export { SketchTool }; diff --git a/packages/cascade-studio/src/tools/SphereTool.js b/packages/cascade-studio/src/tools/SphereTool.js new file mode 100644 index 00000000..bca10cf9 --- /dev/null +++ b/packages/cascade-studio/src/tools/SphereTool.js @@ -0,0 +1,120 @@ +// SphereTool - click center, drag radius, release to commit +import * as THREE from 'three'; +import { Tool } from './Tool.js'; + +const IDLE = 0, DRAG_RADIUS = 1; + +/** Sphere creation tool: + * 1. pointerdown on the ground plane sets the center (snapped to mm) + * 2. drag (or move) sizes the radius (live preview) + * 3. release/click commits: emits `let sphereN = Translate(..., Sphere(r));` + * + * Both press-drag-release and click-move-click work (Tool.stageDown/stageUp); + * a zero-radius release keeps the tool armed instead of cancelling. */ +class SphereTool extends Tool { + constructor(manager) { + super(manager, 'sphere'); + this.state = IDLE; + this.preview = null; + this.centerCAD = null; // CAD [x, y, 0] + this.radius = 0; + } + + isInteracting() { return this.state !== IDLE; } + + onPointerDown(event) { + if (event.button !== 0) return false; + + if (this.state === IDLE) { + const hit = this.manager.raycastGround(event); + if (!hit) return false; + this.centerCAD = this.manager.snapGroundToCad(hit); + this.radius = 0; + this._createPreview(); + this.manager.beginInteraction(); + this.stagePressed = true; + this.state = DRAG_RADIUS; + return true; + } + + // Click-move-click: commit on a click once the radius is set; a press + // with no radius yet starts a drag instead of cancelling. + if (this.state === DRAG_RADIUS) { + if (this.stageDown(this.radius > 0)) { this._commit(); } + return true; + } + return false; + } + + onPointerMove(event) { + if (this.state !== DRAG_RADIUS) return false; + const hit = this.manager.raycastGround(event); + if (hit) { + const p = this.manager.threeToCad(hit); + this.radius = this.manager.snap(Math.hypot( + p[0] - this.centerCAD[0], p[1] - this.centerCAD[1] + )); + this._updatePreview(); + this.manager.showLabel(event, 'r = ' + this.radius); + } + return true; + } + + onPointerUp(event) { + if (this.state !== DRAG_RADIUS) return false; + if (this.stageUp(this.radius > 0)) { this._commit(); } + return true; + } + + cancel() { + this.manager.removePreview(this.preview); + this.preview = null; + if (this.state !== IDLE) { this.manager.endInteraction(); } + this.state = IDLE; + this.stagePressed = false; + this.manager.hideLabel(); + } + + _commit() { + const center = this.centerCAD; + const r = this.radius; + this.cancel(); // removes preview, re-enables controls, resets state + this.emitSphere(center, r); + } + + _createPreview() { + this.preview = new THREE.Mesh( + new THREE.SphereGeometry(1, 32, 16), + this.manager.createPreviewMaterial() + ); + this.manager.addPreview(this.preview); + this._updatePreview(); + } + + _updatePreview() { + if (!this.preview) return; + this.preview.position.copy(this.manager.cadToThree(this.centerCAD)); + const r = Math.max(this.radius, 0.01); + this.preview.scale.set(r, r, r); + this.manager.environment.viewDirty = true; + } + + /** Emit code for a sphere with CAD center `center` and `radius`. */ + emitSphere(center, radius) { + const name = this.manager.nextVarName('sphere'); + const sphereCall = 'Sphere(' + radius + ')'; + const needsMove = center[0] !== 0 || center[1] !== 0 || center[2] !== 0; + if (this.manager.isPythonMode) { + this.manager.commitCode(needsMove + ? name + ' = Pos(' + center.join(', ') + ') * ' + sphereCall + : name + ' = ' + sphereCall); + return; + } + const snippet = needsMove + ? 'let ' + name + ' = Translate([' + center[0] + ', ' + center[1] + ', ' + center[2] + '], ' + sphereCall + ');' + : 'let ' + name + ' = ' + sphereCall + ';'; + this.manager.commitCode(snippet); + } +} + +export { SphereTool }; diff --git a/packages/cascade-studio/src/tools/Tool.js b/packages/cascade-studio/src/tools/Tool.js new file mode 100644 index 00000000..5a96fb7e --- /dev/null +++ b/packages/cascade-studio/src/tools/Tool.js @@ -0,0 +1,86 @@ +// Base class for viewport modeling tools + +/** Base class for GUI modeling tools (LeapShape-style state machines). + * Tools receive raw pointer events from the ToolManager and return `true` + * from a handler to consume the event (blocking OrbitControls). */ +class Tool { + /** @param {import('./ToolManager.js').ToolManager} manager + * @param {string} name */ + constructor(manager, name) { + this.manager = manager; + this.name = name; + // True while the pointer button is held down for the stage in progress. + // See stageDown()/stageUp(). + this.stagePressed = false; + } + + /** Resolve a pointerdown that lands inside an already-running numeric + * stage of a multi-stage tool (e.g. the Box's height stage). + * + * Multi-stage tools must accept BOTH natural gestures for every stage: + * - press-drag-release (press, drag to size, release) + * - click-move-click (click to start, move to size, click to lock) + * + * `hasValue` is true when the stage's dimension has already been set by a + * preceding pointermove. Returns true when the stage should advance/commit + * (the click-move-click case). Returns false when a fresh drag is starting, + * and keeps the stage alive — previously a press here cancelled the whole + * in-progress solid, so the second drag of Box/Cylinder always failed. + * @param {boolean} hasValue @returns {boolean} advance */ + stageDown(hasValue) { + // Advancing on a click deliberately clears stagePressed: the release of + // the advancing click must not commit the next stage on a few px of + // pointer jitter. + this.stagePressed = !hasValue; + return hasValue; + } + + /** Resolve a pointerup inside a running numeric stage. Returns true when + * the stage should advance/commit — i.e. the button was pressed for this + * stage (press-drag-release) and the dimension is non-degenerate. + * A zero-dimension release is NOT destructive: the stage stays live so + * the user can keep moving (click-move-click) or press Escape to cancel. + * @param {boolean} hasValue @returns {boolean} advance */ + stageUp(hasValue) { + const pressed = this.stagePressed; + this.stagePressed = false; + return pressed && hasValue; + } + + /** Called when this tool becomes the active tool. */ + activate() {} + + /** Called when another tool becomes active. Cancels any interaction. */ + deactivate() { this.cancel(); } + + /** Abort any in-progress interaction and clean up previews. */ + cancel() {} + + /** True while a multi-step interaction (e.g. drag) is in progress. */ + isInteracting() { return false; } + + /** @param {PointerEvent} event @returns {boolean} consumed */ + onPointerDown(event) { return false; } + + /** @param {PointerEvent} event @returns {boolean} consumed */ + onPointerMove(event) { return false; } + + /** @param {PointerEvent} event @returns {boolean} consumed */ + onPointerUp(event) { return false; } + + /** Handle Escape. Return true if consumed (e.g. stepped back one stage); + * returning false lets the ToolManager switch back to the Select tool. + * Stateful tools (Sketch) override this for finer-grained undo. */ + onEscape() { + if (this.isInteracting()) { + this.cancel(); + return true; + } + return false; + } + + /** @param {KeyboardEvent} event @returns {boolean} consumed */ + onKeyDown(event) { return false; } +} + +export { Tool }; diff --git a/packages/cascade-studio/src/tools/ToolManager.js b/packages/cascade-studio/src/tools/ToolManager.js new file mode 100644 index 00000000..5938cd1f --- /dev/null +++ b/packages/cascade-studio/src/tools/ToolManager.js @@ -0,0 +1,358 @@ +// ToolManager - LeapShape-style GUI modeling tools for the 3D viewport. +// Every GUI operation emits JavaScript code into the Monaco editor; +// the code IS the scene. +import * as THREE from 'three'; +import { SelectTool } from './SelectTool.js'; +import { BoxTool } from './BoxTool.js'; +import { CylinderTool } from './CylinderTool.js'; +import { SphereTool } from './SphereTool.js'; +import { SketchTool } from './SketchTool.js'; +import { FilletTool } from './FilletTool.js'; + +/** Owns the viewport toolbar, routes pointer events to the active tool + * (ahead of OrbitControls), and provides shared raycasting / snapping / + * code-emission helpers for the tools. */ +class ToolManager { + /** @param {import('../CascadeView.js').CascadeEnvironment} viewport */ + constructor(viewport) { + this.viewport = viewport; + this.environment = viewport.environment; + + this._raycaster = new THREE.Raycaster(); + this._groundPlane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0); + + // Instantiate the tools + this.tools = { + select: new SelectTool(this), + box: new BoxTool(this), + cylinder: new CylinderTool(this), + sphere: new SphereTool(this), + sketch: new SketchTool(this), + fillet: new FilletTool(this), + }; + this.activeToolName = 'select'; + + this._buildToolbar(); + this._buildDimLabel(); + this._bindEvents(); + } + + get activeTool() { return this.tools[this.activeToolName]; } + get editor() { return this.viewport._app.editor; } + get scene() { return this.environment.scene; } + + /** Current editor language mode ('cascadestudio' | 'openscad' | 'python'). + * Tools dispatch on this when emitting code. */ + get codeLanguage() { return this.editor.mode; } + get isPythonMode() { return this.codeLanguage === 'python'; } + + /** Activate a tool by name; the previous tool's interaction is cancelled. */ + activate(name) { + if (!this.tools[name] || name === this.activeToolName) return; + if (name === 'sketch' && this.isPythonMode) { + console.error('The Sketch tool is not available in Python mode yet — ' + + 'switch the editor to CascadeStudio JS mode to sketch profiles.'); + return; + } + this.activeTool.deactivate(); + this.activeToolName = name; + this.activeTool.activate(); + + // Update toolbar highlight + viewport cursor + for (let btn of this._toolbarEl.children) { + btn.classList.toggle('cs-tool-active', btn.dataset.tool === name); + } + const canvas = this.environment.renderer.domElement; + canvas.style.cursor = (name === 'select') ? '' : 'crosshair'; + this.environment.viewDirty = true; + } + + /** Notify the active tool that the scene meshes were rebuilt + * (its cached edge/shape selection is no longer valid). */ + onSceneRebuilt() { + if (this.activeTool.onSceneRebuilt) { this.activeTool.onSceneRebuilt(); } + } + + /** Called by EditorManager.setMode when the language mode changes. + * The Sketch tool emits JS-only Sketch chains, so it is disabled in + * Python mode (grayed out with an explanatory tooltip). */ + onLanguageChanged() { + const sketchBtn = [...this._toolbarEl.children] + .find((btn) => btn.dataset.tool === 'sketch'); + if (sketchBtn) { + const disabled = this.isPythonMode; + sketchBtn.classList.toggle('cs-tool-disabled', disabled); + if (!sketchBtn.dataset.defaultTitle) { sketchBtn.dataset.defaultTitle = sketchBtn.title; } + sketchBtn.title = disabled + ? 'Sketch — not available in Python mode yet (switch to CascadeStudio JS mode)' + : sketchBtn.dataset.defaultTitle; + } + if (this.isPythonMode && this.activeToolName === 'sketch') { + this.activate('select'); + } + } + + // ===== Coordinate helpers (three.js scene is Y-up, CAD code is Z-up) ===== + + /** CAD [x, y, z] → three.js world Vector3. */ + cadToThree(p) { return new THREE.Vector3(p[0], p[2], -p[1]); } + + /** three.js world Vector3 → CAD [x, y, z]. */ + threeToCad(v) { return [v.x, -v.z, v.y]; } + + /** Snap a CAD coordinate value to the integer mm grid. */ + snap(value) { return Math.round(value); } + + /** three.js ground-plane point → snapped CAD [x, y, 0]. */ + snapGroundToCad(v) { + const p = this.threeToCad(v); + return [this.snap(p[0]), this.snap(p[1]), 0]; + } + + // ===== Raycast helpers ===== + + /** Set up the shared raycaster from a pointer event; returns it. */ + pointerRay(event) { + const canvas = this.environment.renderer.domElement; + const rect = canvas.getBoundingClientRect(); + const ndc = new THREE.Vector2( + ((event.clientX - rect.left) / rect.width) * 2 - 1, + -((event.clientY - rect.top) / rect.height) * 2 + 1 + ); + this._raycaster.setFromCamera(ndc, this.environment.camera); + return this._raycaster; + } + + /** Raycast the pointer against the ground plane (three-space y=0). + * Returns a THREE.Vector3 world point or null. */ + raycastGround(event) { + const ray = this.pointerRay(event).ray; + const target = new THREE.Vector3(); + return ray.intersectPlane(this._groundPlane, target) ? target : null; + } + + /** Raycast the pointer against the current model. Returns the nearest + * intersection or null. */ + raycastScene(event) { + if (!this.viewport.mainObject) return null; + const raycaster = this.pointerRay(event); + const hits = raycaster.intersectObjects(this.viewport.mainObject.children); + return hits.length > 0 ? hits[0] : null; + } + + /** Raycast preferring model edges (LineSegments) near the nearest hit. + * Returns a LineSegments intersection or null. */ + raycastEdge(event) { + if (!this.viewport.mainObject) return null; + const raycaster = this.pointerRay(event); + const oldThreshold = raycaster.params.Line.threshold; + raycaster.params.Line.threshold = 2; + const hits = raycaster.intersectObjects(this.viewport.mainObject.children); + raycaster.params.Line.threshold = oldThreshold; + if (hits.length === 0) return null; + for (let hit of hits) { + if (hit.object.type === 'LineSegments' && hit.distance <= hits[0].distance + 2) { + return hit; + } + } + return null; + } + + /** Compute the height of the pointer along the vertical (three-space Y) + * axis through `baseThree`, snapped to the CAD grid. Mirrors LeapShape's + * segment-distance approach for the height-drag phase. */ + heightFromRay(event, baseThree) { + const ray = this.pointerRay(event).ray; + const upper = baseThree.clone(); upper.y = 10000; + const lower = baseThree.clone(); lower.y = -10000; + const closest = new THREE.Vector3(); + ray.distanceSqToSegment(lower, upper, null, closest); + return this.snap(closest.y - baseThree.y); + } + + // ===== OrbitControls coordination ===== + + /** Disable camera controls for the duration of a tool interaction. */ + beginInteraction() { this.environment.controls.enabled = false; } + + /** Re-enable camera controls when the interaction commits or cancels. */ + endInteraction() { this.environment.controls.enabled = true; } + + // ===== Preview helpers ===== + + /** Semi-transparent material for live tool previews. */ + createPreviewMaterial() { + return new THREE.MeshBasicMaterial({ + color: 0x4CAF50, transparent: true, opacity: 0.4, + depthWrite: false, side: THREE.DoubleSide + }); + } + + /** Add a preview object to the scene and mark the view dirty. */ + addPreview(obj) { this.scene.add(obj); this.environment.viewDirty = true; } + + /** Remove a preview object from the scene and mark the view dirty. */ + removePreview(obj) { + if (obj) { this.scene.remove(obj); } + this.environment.viewDirty = true; + } + + // ===== Code emission ===== + + /** Collect identifiers already declared/assigned in the editor code. */ + usedVarNames() { + const code = this.editor.getCode(); + const names = new Set(); + const declRe = /\b(?:let|var|const|function)\s+([A-Za-z_$][\w$]*)/g; + const assignRe = /(?:^|[\n;{])\s*([A-Za-z_$][\w$]*)\s*=[^=]/g; + let m; + while ((m = declRe.exec(code)) !== null) { names.add(m[1]); } + while ((m = assignRe.exec(code)) !== null) { names.add(m[1]); } + return names; + } + + /** Generate a fresh variable name: fnName lowercased + counter. */ + nextVarName(base, extraUsed) { + const names = this.usedVarNames(); + if (extraUsed) { extraUsed.forEach((n) => names.add(n)); } + let i = 1; + while (names.has(base + i)) { i++; } + return base + i; + } + + /** Append an emitted snippet to the editor and re-evaluate the code. + * If the worker is busy, evaluation is deferred until it frees up. + * Committing returns to the Select tool: creation tools are one-shot, + * so the camera is immediately usable after each placement (reactivate + * the tool from the toolbar to place another). */ + commitCode(snippet) { + const editor = this.editor; + editor.insertCode(snippet); + this.evaluateSoon(); + if (this.activeToolName !== 'select') { this.activate('select'); } + } + + /** Evaluate the editor code now, or as soon as the worker is free. */ + evaluateSoon() { + const app = this.viewport._app; + if (window.workerWorking) { + const handler = () => { + app.engine.off('resetWorking', handler); + setTimeout(() => app.editor.evaluateCode(false), 0); + }; + app.engine.on('resetWorking', handler); + } else { + app.editor.evaluateCode(false); + } + } + + // ===== Dimension label (follows the pointer during drags) ===== + + _buildDimLabel() { + this._dimLabel = document.createElement('div'); + this._dimLabel.className = 'cs-tool-label'; + this._dimLabel.style.display = 'none'; + this.viewport.goldenContainer.element.appendChild(this._dimLabel); + } + + /** Show the floating dimension label near the pointer. */ + showLabel(event, text) { + const rect = this.viewport.goldenContainer.element.getBoundingClientRect(); + this._dimLabel.textContent = text; + this._dimLabel.style.left = (event.clientX - rect.left + 14) + 'px'; + this._dimLabel.style.top = (event.clientY - rect.top + 14) + 'px'; + this._dimLabel.style.display = ''; + } + + /** Hide the floating dimension label. */ + hideLabel() { this._dimLabel.style.display = 'none'; } + + // ===== Toolbar + event wiring ===== + + /** Create the vertical toolbar overlay in the viewport panel. */ + _buildToolbar() { + this._toolbarEl = document.createElement('div'); + this._toolbarEl.className = 'cs-toolbar'; + + const buttons = [ + { tool: 'select', icon: '↖', label: 'Select (Esc) — click a shape to reveal its code line' }, + { tool: 'box', icon: '□', label: 'Box — size the footprint on the ground, then the height (drag or click-move-click for each stage); Escape cancels' }, + { tool: 'cylinder', icon: '▭', label: 'Cylinder — from the center, size the radius, then the height (drag or click-move-click for each stage); Escape cancels' }, + { tool: 'sphere', icon: '○', label: 'Sphere — from the center, size the radius (drag or click-move-click); Escape cancels' }, + { tool: 'sketch', icon: '✎', label: 'Sketch — click to place vertices, click the first vertex (or Enter) to close, then Extrude/Revolve; Escape removes the last vertex' }, + { tool: 'fillet', icon: '◠', label: 'Fillet — click edges to select, set radius, Enter to commit' }, + ]; + for (let { tool, icon, label } of buttons) { + const btn = document.createElement('button'); + btn.className = 'cs-tool-btn' + (tool === 'select' ? ' cs-tool-active' : ''); + btn.dataset.tool = tool; + btn.title = label; + btn.textContent = icon; + btn.addEventListener('click', (e) => { + e.stopPropagation(); + this.activate(tool); + }); + this._toolbarEl.appendChild(btn); + } + this.viewport.goldenContainer.element.appendChild(this._toolbarEl); + } + + /** Route pointer events to the active tool BEFORE OrbitControls sees them. + * Uses capture-phase listeners on the panel element (an ancestor of the + * renderer canvas), so a consuming tool can stopPropagation() and the + * canvas-level OrbitControls listeners never fire. */ + _bindEvents() { + const el = this.viewport.goldenContainer.element; + const canvas = this.environment.renderer.domElement; + + this._onPointerDown = (e) => { + if (!this.viewport.active || this._isUIEvent(e)) return; + // Only begin new interactions on the canvas itself + if (e.target !== canvas && !this.activeTool.isInteracting()) return; + if (this.activeTool.onPointerDown(e)) { + e.stopPropagation(); + e.preventDefault(); + } + }; + this._onPointerMove = (e) => { + if (!this.viewport.active) return; + if (this._isUIEvent(e) && !this.activeTool.isInteracting()) return; + if (this.activeTool.onPointerMove(e)) { + e.stopPropagation(); + } + }; + this._onPointerUp = (e) => { + if (!this.viewport.active) return; + if (this.activeTool.onPointerUp(e)) { + e.stopPropagation(); + } + }; + this._onKeyDown = (e) => { + if (!this.viewport.active) return; + const ae = document.activeElement; + if (ae && ae.closest && ae.closest('.monaco-editor')) return; + if (e.code === 'Escape') { + // Tools may consume Escape for stage-level undo (e.g. Sketch vertex + // removal); unconsumed Escape returns to the Select tool. + if (!this.activeTool.onEscape() && this.activeToolName !== 'select') { + this.activate('select'); + } + } else if (!(ae && (ae.tagName === 'INPUT' || ae.tagName === 'TEXTAREA'))) { + if (this.activeTool.onKeyDown(e)) { e.preventDefault(); } + } + }; + + el.addEventListener('pointerdown', this._onPointerDown, true); + el.addEventListener('pointermove', this._onPointerMove, true); + window.addEventListener('pointerup', this._onPointerUp, true); + window.addEventListener('keydown', this._onKeyDown); + } + + /** True if the event targets a UI overlay (toolbar, panels, GUI, timeline). */ + _isUIEvent(e) { + return !!(e.target && e.target.closest && + e.target.closest('.cs-toolbar, .cs-fillet-panel, .cs-sketch-panel, .cs-timeline, .gui-panel')); + } +} + +export { ToolManager }; diff --git a/playwright.config.js b/playwright.config.js index 48d5a489..be22d9e8 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -1,6 +1,14 @@ // @ts-check const { defineConfig } = require('@playwright/test'); +// Override with CS_TEST_PORT if another service already occupies 8080 +const PORT = parseInt(process.env.CS_TEST_PORT || '8080', 10); + +// On machines where headless Chromium cannot create a (SwiftShader) WebGL +// context, run headful against an X server instead: +// CS_TEST_HEADFUL=1 DISPLAY=:99 npx playwright test +const HEADLESS = !process.env.CS_TEST_HEADFUL; + module.exports = defineConfig({ testDir: './test', timeout: 120000, @@ -10,7 +18,7 @@ module.exports = defineConfig({ retries: 1, reporter: 'html', use: { - baseURL: 'http://localhost:8080', + baseURL: `http://localhost:${PORT}`, screenshot: 'only-on-failure', trace: 'on-first-retry', }, @@ -19,6 +27,7 @@ module.exports = defineConfig({ name: 'chromium', use: { browserName: 'chromium', + headless: HEADLESS, launchOptions: { args: ['--use-gl=angle', '--use-angle=swiftshader'], }, @@ -26,8 +35,8 @@ module.exports = defineConfig({ }, ], webServer: { - command: 'npx http-server ./packages/cascade-studio/dist -p 8080 -c-1 --silent', - port: 8080, + command: `npx http-server ./packages/cascade-studio/dist -p ${PORT} -c-1 --silent`, + port: PORT, reuseExistingServer: !process.env.CI, timeout: 30000, }, diff --git a/test/b123d-validation/.gitignore b/test/b123d-validation/.gitignore new file mode 100644 index 00000000..01595658 --- /dev/null +++ b/test/b123d-validation/.gitignore @@ -0,0 +1,2 @@ +probe-lite.tmp.mjs +results.json diff --git a/test/b123d-validation/README.md b/test/b123d-validation/README.md new file mode 100644 index 00000000..eb0fd886 --- /dev/null +++ b/test/b123d-validation/README.md @@ -0,0 +1,287 @@ +# build123d-lite validation harness + +Validates CascadeStudio's Python mode (`build123d-lite`, running on Brython in +the CAD worker) against **real build123d** by running the upstream +documentation/examples scripts through both and comparing the geometry each +produces. + +Not part of the default playwright suite — run it manually when extending +`packages/cascade-core/src/worker/Build123dLite.js`. + +## How it works + +1. **`collect.py`** enumerates candidate scripts from a build123d source + clone (`B123D_SRC`, default `/tmp/b123d`) — **everything runnable in + `examples/` and `docs/`**: + - every `examples/*.py`; + - the numbered snippets split out of `docs/general_examples.py` (builder + mode) and `docs/general_examples_algebra.py` (algebra mode); + - the standalone documentation scripts `docs/*.py` (`objects_1d/2d/3d`, + `tutorial_joints`, `slide_latch`, `rod_end`, `heart_token`, + `spitfire_wing_gordon`, `technical_drawing`, …), + `docs/objects/examples/*.py` and `docs/topology_selection/examples/*.py`; + - the 13 **Too Tall Toby** challenge parts in `docs/assets/ttt/*.py`, whose + closing `assert abs(got_mass - want_mass) < tolerance` is kept verbatim — + a ~0.1% bar that a script must clear on its own; + - every ``code-block:: build123d`` / ``python`` snippet in `docs/**/*.rst`, + plus one cumulative script per page (what a reader pasting a whole + tutorial page actually runs). `>>>` doctest transcripts are unwrapped. + + Viewer imports (`ocp_vscode`) and viewer-only calls are stripped + statement-wise (a multi-line `write_svg(...)` goes as a whole); SVG/DXF + export machinery is left in place, since lite ships a no-op `ExportSVG` and + the reference run gets a scratch `assets/` directory. Output: + `manifest-all.json` (382 candidates, code inlined). +2. **`reference.py`** runs each candidate under the reference venv + (`B123D_REF_PY`, default `~/Desktop/ocjs-deps/b123d-ref-venv/bin/python`, + build123d 0.11.1) in a guarded subprocess (60s timeout, scratch cwd with + `__file__` set and the script's non-`.py` siblings symlinked in) and + records, for every module-level shape result — `Shape` instances, + `Builder._obj` results and lists of Shapes, keyed by VARIABLE NAME — + volume, bounding box (6 floats), area, face count, edge count. Scripts that + fail natively are recorded and excluded from scoring. + Output: `reference-all.json`. + + `collect.py prune` then writes the **scored corpus**, `manifest.json` + + `reference.json`: real scripts are always kept (a native failure becomes a + SKIP), while `.rst` fragments that produced no geometry natively — `...` + placeholders, prose pseudo-code, snippets that only print — are dropped, + because they were never runnable code. 382 candidates → **232 scripts**. +3. **`run-lite.mjs`** (playwright) serves the built app, switches to Python + mode and runs every script with a measurement footer appended. A script that + imports a CAD file from its own directory (manifest `assets`, recorded by + `collect.py`) gets it handed to the worker first, via + `CascadeAPI.loadExternalFiles({name: text})`, which AWAITS the worker's STEP + import; `import_step()` then resolves the path by base name. (A worker that + has already evaluated many scripts sometimes stops answering, so the harness + recycles the page and retries the delivery.) The footer + calls `build123d._measure_globals_json(globals())`, which measures the + same module-level-variable convention through the worker's + `MeasureShape`/`BoundingBox` hooks (StandardLibrary.js; bbox comes from a + fine 5e-4 triangulation of a deep copy because the WASM build has no + Bnd_Box binding). Classification per script: + - **PASS** — every reference shape matches by name: volume within 0.5% + relative, bbox within 1e-3 per axis + - **MISMATCH** — runs, but geometry differs (the interesting bucket) + - **ERROR** — Python exception; bucketed by first missing feature + - **TIMEOUT** — evaluation exceeded 60s (page is reloaded) + - **SKIP** — reference build123d itself fails natively + + Output: `results.json` + `report.md` (status counts, sorted feature-gap + frequency table, per-script mismatch details). + +## Running + +```bash +# everything (collect -> reference -> build + lite run): +test/b123d-validation/run.sh + +# on this machine (headless chromium cannot create a WebGL context): +CS_TEST_HEADFUL=1 DISPLAY=:99 test/b123d-validation/run.sh + +# individual stages: +test/b123d-validation/run.sh collect +test/b123d-validation/run.sh reference +CS_TEST_HEADFUL=1 DISPLAY=:99 test/b123d-validation/run.sh lite + +# single script while debugging: +CS_TEST_HEADFUL=1 DISPLAY=:99 node test/b123d-validation/run-lite.mjs \ + --only general_examples_algebra/ex07 +``` + +Env knobs: `B123D_SRC`, `B123D_REF_PY`, `B123D_REF_JOBS` (default 4), +`CS_TEST_PORT` (default 8517), `CS_TEST_HEADFUL`/`DISPLAY`. + +## Which Python interpreter? (Brython vs Pyodide) + +`run-lite.mjs --pyruntime pyodide` (or `CS_PY_RUNTIME=pyodide`, same for +`probe.mjs`) runs the identical corpus on **Pyodide** — real CPython 3.14 on +wasm — instead of Brython, executing the same `Build123dLite.js` source. It +needs the vendored core distribution +(`node packages/cascade-core/scripts/fetch-pyodide.cjs`). + +It is an exact drop-in (204/8/9/1/10 with **zero per-script deltas** and +byte-identical MISMATCH magnitudes) and ~5% faster over the corpus, but it +costs 23x the download, ~3x the boot and ~2.2x the resident memory, so Brython +remains the default. Numbers, method and recommendation: +[`runtime-comparison.md`](runtime-comparison.md); startup/memory measurements +come from `bench-runtime.mjs`. + +## Canonical free edges (cross-kernel check) + +build123d-lite implements the upstream **canonical free-edge parametrization** +proposal — research record, patch and reproduction scripts now live with the +build123d contribution itself, on +[zalo/build123d branch `canonical-research`](https://github.com/zalo/build123d/tree/canonical-research/research) +(`research/`). A free edge (section / projection / boolean +output) inherits a seam, direction and parameter range that depend on the +*parametric frames* of the operand surfaces, so `position_at(0)` and +`Axis(edge)` move when a geometrically identical solid is re-framed; +`edge.canonical()` replaces those with a rule computed from geometry alone. +Lite ports the rule with the same names and the same defaults: `canonical()`, +`Axis(edge, canonical=True)` and `sort_by(..., tie_break=True)` are opt-in — the +default sort stays a plain stable sort, because ties carrying the incoming order +is itself a contract that chained sorts rely on — while +`Edge.make_mid_way`'s canonicalization is unconditional. + +`canonical-cross-kernel.mjs` checks lite's `canonical()` on **OCCT 8.0.1 (wasm)** +against **patched upstream build123d 0.11.1 on OCP 7.9.3**, over three +constructions: the `examples/projection.py` arch (a sphere ∩ cylinder section +loop, 5 sphere frames), a sphere ∩ cylinder locus reassembled with +`edges_to_wires` (each kernel chops it into a different NUMBER of edges), and +`examples/joints.py`'s `make_mid_way` slider axis (3 cutter frames). + +```bash +# 1. reference side (patched build123d on OCP 7.9.3), in a checkout of the +# research branch: git clone -b canonical-research https://github.com/zalo/build123d +cd /research/experiments && ./repatch.sh +PYTHONPATH=/tmp/b123d-0111 ~/Desktop/ocjs-deps/b123d-ref-venv/bin/python \ + lite_cross_kernel.py > canonical-lite-reference.json + +# 2. lite side + diff (needs `npm run build`); point the harness at that JSON +CS_TEST_HEADFUL=1 DISPLAY=:99 B123D_CANONICAL_REFERENCE=<...>/canonical-lite-reference.json \ + node test/b123d-validation/canonical-cross-kernel.mjs +``` + +Result (committed in `canonical-cross-kernel.json`): **185 canonical +measurements, worst delta 0.00e+0 mm**, against 5 recorded raw (pre-canonical) +kernel differences — which is the premise, not a failure. The harness also checks +frame consistency *inside* each kernel, and all three cases are consistent in +both. + +That frame-consistency check earned its keep: it caught the patch failing its own +premise (reversing a reassembled section Wire canonicalized to the other seam of +the loop, winding the other way — reproducible on OCP 7.9.3 alone), which the +patch author fixed in three places, now mirrored here. See REPORT.md §3.3 for the +numbers: a circular "already canonical" test at band-width resolution, a +`_walk_loop` ranking that consults the tangent before the noise-scale gap, and +band discovery by local minima with midpoints ranked on quantised coordinates. +The lite port had a fourth instance of the same species — `_reverse_1d` flipped a +Wire's orientation flag, which `Curve._walk` ignores — so a Wire is now rebuilt +from its edges in reverse order. The canonical seams of loops whose extremal band +comes in a mirror-symmetric pair moved as a result, and the expectations here were +regenerated. + +The rule itself is frozen in `test/python-mode-canonical.spec.js` (part of the +default suite), including a section loop asserted against hand-computed values. + +## Current coverage + + +382 candidates → **232 scripts** in the scored corpus, 222 scored (10 excluded — +real build123d fails on them natively). On the OCCT 8.0.1 wasm build: + +| Status | Count | Note | +|---|---|---| +| PASS | 205 | volume within 0.5%, bbox within 1e-3/axis, per variable | +| MISMATCH | 10 | joints x2 + projection x2 + sort_axis — COMPROMISE(edge-orientation); filter_all_edges_circle + tips/b04 — COMPROMISE(traversal-order); objects_1d — COMPROMISE(triad-labels); tutorial_joints (`m6_screw` alone, a CylindricalJoint hole frame) and sm_hanger (`l1`/`l2` alone, BuildLine locals on a non-XY workplane) | +| ERROR | 5 | the `drafting` module x1 (objects_2d), `sympy` x1, 3MF export x1, and 2 kernel faults (toy_truck fillet, ttt-ppp0110 fuse) | +| TIMEOUT | 2 | spitfire_wing_gordon: reaches the wing Gordon surface, which needs ~390 s here (harness budget 60 s); heat_exchanger sits AT the budget (~55 s idle) and only times out when the four pages contend | +| SKIP | 10 | real build123d 0.11.1 fails natively (`bd_warehouse` x3, `ImageFace`, `ColorMap`, `tcv_screenshots`, no module-level shapes) | + +Every non-PASS is root-caused in the **defaults-audit table** appended to +report.md. Iteration on the broadened corpus: 158 PASS on the first pass → +Select.LAST/NEW + `new_edges` + module-level context selectors → `os` shim → +FilletPolyline / IntersectingLine / SlotCenterPoint / LengthMode / partial +Sphere / `Edge.radius`/`is_interior`/`find_tangent`/`make_circle` / `Axis( +Location)` / `Shell.extrude` / `Compound.make_triad` → builder-scoped location +contexts → property selectors → **177** → topology-selection properties +(`Face.is_circular_convex`/`center_location`, `Mixin1D.normal`, +`param_at_point`, `Shape.distance*`, `GroupBy.group`) + an EXACT convex hull +→ 185 → the 1-D analytic objects (BSpline, parabolic/hyperbolic arcs, +EllipticalStartArc, BlendCurve, Airfoil, Triangle) → 194 → Wedge, +ConvexPolyhedron, text-on-path, `topo_distance_to`, `pytest.approx` and the +position_at/circle-edge/copy-snapshot fidelity fixes → 199 → 2-D face offsets + +BuildSketch's face alignment → 201 → upstream's taper-extrude branch → 202 → +closed-form ConstrainedArcs/ConstrainedLines for circle/point targets → 204 → +**the OCCT-binding round** (the real Geom2dGcc solvers, STEP-asset delivery, +a pure-Python 2-D Voronoi behind `full_round`, `Wire.fillet_2d` on +ChFi2d_FilletAlgo + `make_brake_formed`) → **205**, with two of the remaining +errors demoted to single-shape MISMATCHes. + +### What the OCCT-binding round changed in the fork + +Four of the nine remaining errors were blocked on the WASM build, not on lite. +`~/Desktop/ocjs-fork` (branch `cascadestudio-v3-occt801`) now binds: + +- the whole **`Geom2dGcc` / `GccAna`** family — every binding file in those + packages had been failing to compile on ONE method, + `WhichQualifier(Standard_Integer, GccEnt_Position&, GccEnt_Position&)`, whose + non-const enum out-params Embind cannot bind; +- **`Extrema_ExtAlgo`/`Extrema_ExtFlag`** (makes `GeomAPI_ProjectPointOnSurf` + constructible), **`gp_Cylinder`/`gp_Sphere`/`gp_Torus`**, + **`ChFi2d_FilletAlgo`** and **`IntAna2d_IntPoint`**; +- a hand-registered **`OCJS_Out`** helper for methods that return through + `Standard_Real&` (Embind passes primitives by value): the Geom2dGcc + `Tangency` accessors, `ChFi2d_FilletAlgo::Result` and + `GeomAPI_ProjectPointOnSurf`'s `(u, v)`. + +That retired COMPROMISE(curvature-sign) and COMPROMISE(point-projection). + +
previous corpus (129 candidates / 126 scored) + +| Status | Count | Note | +|---|---|---| +| PASS | 119 | volume within 0.5%, bbox within 1e-3/axis, per variable | +| MISMATCH | 4 | all COMPROMISE(edge-orientation): joints x2, projection x2 | +| ERROR | 3 | two fillet kernel faults (cast_bearing_unit — since root-caused to lite's simplified hull and now PASSING — and toy_truck) + the 3MF export gap | +| TIMEOUT | 0 | | + +Iteration history: round 0 (pre-builders lite) 0 PASS / 126 ERROR → builders + +algebra + selectors + stdlib shims 21 → 2D fillets, hole conventions, face +orientation, CacheOp-collision fix 40 → sweep paths, cubic splines, +Plane.rotated, fused cuts 50 → OCCT 8.0.1 + exact Bnd_Box + deg→rad precision +fix 59 → Text (opentype/FreeSans + kern parity), tangent splines, thick-solid +openings, taper, Kind.INTERSECTION 71 → same-frame builder gating, +Face.offset, until=, HLR projection, pack/random shims, non-uniform scale +85 → exact GeomAPI_Interpolate splines, MakePipeShell sweeps (multisection/ +normal/binormal), section/make_hull/draft/project, joints, scipy shim + +DoubleTangentArc, surface-from-points, Mesher STL, baked uniform scale, +Text2D cache fix, kernel-fault guards 110 → exact PointsToBSplineSurface +(AsGeomSurface), quickhull3d ConvexHull, thicken, general-fuse fallback for +the coplanar fuse operand-drop fault, per-glyph +Z text normals (conditional +reverse), make_text align=None parity, arc-length position_at +(GCPnts_AbscissaPoint), per-contour glyph faces (i/j dots), +find_intersection_points + project_faces 117 → Gordon curve-network surfaces +(ocp_gordon port + least-squares realization), surface location_at/normal_at, +wire project_to_shape, planar Face(wire) → **bracelet** 118 → wrap()/ +wrap_faces(), Face.make_surface, Edge.make_spline/param_at/trim, Trapezoid +obtuse-angle fix, make_face clean parity → **bicycle_tire** 119 → one-sided +open-line offsets (offset(side=)) → dual_color_3mf geometry (still ERROR on +its 3MF write). + +
+ +Full harness pass: ~150 s (4 pages, 232 scripts) / ~5 min single-page; a full +`reference.py` sweep of the 382 candidates is ~90 s with `--jobs 12`. ALWAYS run +the lite stage with CS_TEST_HEADFUL=1 DISPLAY=:99 (headless Chromium has no +WebGL here — it manifests as every script reporting "no measurement produced"). + + +## Notes / caveats + +- Comparison is **by variable name**, so a shape must end up in the same + module-level variable in both runs. Builder variables are measured via + their result object (`_obj`), matching build123d's `BuildSketch._obj == + sketch_local` (LOCAL coordinates) semantics. +- Face/edge counts are recorded but NOT part of the pass criterion: the + CascadeStudio standard library always runs ShapeUpgrade_UnifySameDomain + after booleans, so counts legitimately differ from build123d. +- Every deliberate deviation from upstream is marked in source with a + grep-able `COMPROMISE()` comment (see CLAUDE.md's "Known + compromises" list for the index). +- Debug a single script with + `CS_TEST_HEADFUL=1 DISPLAY=:99 node test/b123d-validation/probe.mjs + [--id | /path/to/snippet.py]` — prints the raw measurement + JSON, worker errors and the last console lines. +- After a raw wasm kernel abort ("memory access out of bounds") the OCCT + heap is corrupt; run-lite.mjs recycles the page before the next script. +- **50** representative passing scripts are frozen as regression tests in + `test/python-mode-examples.spec.js` (part of the default suite) — including + seven Too Tall Toby challenge parts (their own mass asserts run too), the + `new_edges` / context-selector / `is_interior` / `FilletPolyline` doc blocks, + the two "Locations around a builder" cases and, since this round, five + landmarks for the new machinery: `Wedge`/`ConvexPolyhedron` (objects_3d), + `Triangle` (tutorial_constraints/b03), the parabolic/hyperbolic arcs, + slide_latch (sketch-face alignment + Select.LAST vertices) and + group_properties_with_keys (copy snapshots + the exact hull + GroupBy.group). diff --git a/test/b123d-validation/bench-runtime.mjs b/test/b123d-validation/bench-runtime.mjs new file mode 100644 index 00000000..6cbc058b --- /dev/null +++ b/test/b123d-validation/bench-runtime.mjs @@ -0,0 +1,231 @@ +// bench-runtime.mjs - measure what the choice of Python interpreter COSTS: +// startup (cold + warm), worker memory at rest and after work, and the bytes +// each runtime has to download. Backs test/b123d-validation/runtime-comparison.md. +// +// CS_TEST_HEADFUL=1 DISPLAY=:99 node test/b123d-validation/bench-runtime.mjs \ +// --runtime brython|pyodide [--repeat 3] [--corpus] [--out /tmp/bench.json] +// +// Env: CS_TEST_PORT (default 8517). Requires `npm run build` first; the +// pyodide runtime additionally needs the vendored core distribution +// (packages/cascade-core/scripts/fetch-pyodide.cjs). +// +// What each number means: +// assetFetchMs wall time to download the runtime's own assets over HTTP +// with the cache bypassed (localhost — a LOWER BOUND on any +// real network; the byte table is the honest proxy) +// bootTiming worker-reported split: fetch / interpreter init / compiling +// build123d-lite, from PythonRuntime.js + PyodideRuntime.js +// firstEvalMs runCode(trivial script) on a page that has never run +// Python: boot + evaluate + mesh, i.e. what a user waits for +// warmEvalMs the same call once the runtime is up (no boot in it) +// memory occtWasm / pythonWasm are exact wasm linear-memory sizes +// read inside the worker. `performance.memory` does NOT +// exist in workers, so Brython's cost (plain JS objects) is +// invisible from there — the renderer process's RSS is +// sampled instead, after forcing GC in both the page and the +// worker (--js-flags=--expose-gc). RSS is the honest +// apples-to-apples number: it covers V8 heaps and wasm +// memories alike. It is also sticky (V8 rarely returns pages +// to the OS), so read the DELTAS, not the absolutes. +import { chromium } from 'playwright'; +import { spawn } from 'node:child_process'; +import { readFileSync, writeFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import http from 'node:http'; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const ROOT = join(HERE, '..', '..'); +const args = process.argv.slice(2); +const argVal = (name, dflt) => { + const i = args.indexOf(name); + return i >= 0 ? args[i + 1] : dflt; +}; +const PORT = parseInt(process.env.CS_TEST_PORT || '8517', 10); +const RUNTIME = argVal('--runtime', process.env.CS_PY_RUNTIME || 'brython'); +const REPEAT = parseInt(argVal('--repeat', '3'), 10); +const WITH_CORPUS = args.includes('--corpus'); +const CORPUS_TIMEOUT = parseInt(argVal('--script-timeout', '30000'), 10); +const OUT = argVal('--out', `/tmp/bench-${RUNTIME}.json`); + +const RUNTIME_ASSETS = { + brython: ['brython.js'], + pyodide: ['pyodide/pyodide.mjs', 'pyodide/pyodide.asm.mjs', + 'pyodide/pyodide.asm.wasm', 'pyodide/python_stdlib.zip', + 'pyodide/pyodide-lock.json'], +}; + +const TRIVIAL = `from build123d import * +b = Box(1, 1, 1) +show(b) +`; + +async function ensureServer() { + const alive = await new Promise((res) => { + const req = http.get({ host: 'localhost', port: PORT, path: '/' }, (r) => { r.resume(); res(true); }); + req.on('error', () => res(false)); + req.setTimeout(2000, () => { req.destroy(); res(false); }); + }); + if (alive) return null; + const proc = spawn('npx', ['http-server', './packages/cascade-studio/dist', + '-p', String(PORT), '-c-1', '--silent'], { cwd: ROOT, stdio: 'ignore' }); + await new Promise((r) => setTimeout(r, 2500)); + return proc; +} + +/** A page that has loaded the app in JS mode — nothing Python has happened + * yet, so its worker is the BASELINE the Python runtime is charged against. */ +async function newJsModePage(context) { + const page = await context.newPage(); + page.on('pageerror', () => {}); + const query = `?mode=cascadestudio${RUNTIME === 'pyodide' ? '&pyruntime=pyodide' : ''}`; + await page.goto(`http://localhost:${PORT}/${query}`, { timeout: 60000 }); + await page.waitForFunction(() => window.CascadeAPI && window.CascadeAPI.isReady(), + undefined, { timeout: 90000 }); + await page.waitForFunction(() => !window.CascadeAPI.isWorking(), undefined, { timeout: 90000 }); + return page; +} + +/** Total RSS of THIS browser's renderer processes (the page and its workers + * live there together). Chromium tells us which pids are renderers over + * CDP's SystemInfo.getProcessInfo; the resident size comes from /proc. */ +async function rendererRssKb(cdp) { + let info; + try { + info = await cdp.send('SystemInfo.getProcessInfo'); + } catch (e) { return 0; } + let total = 0; + for (const proc of info.processInfo || []) { + if (proc.type !== 'renderer') { continue; } + try { + const statm = readFileSync(`/proc/${proc.id}/statm`, 'utf8').split(' '); + total += (parseInt(statm[1], 10) * 4096) / 1024; // resident pages -> KB + } catch (e) { /* the process exited between the two reads */ } + } + return Math.round(total); +} + +/** GC-settled memory picture: force collection in the page AND the worker + * (memoryStats does the worker side), then sample both. */ +async function stats(page, cdp) { + await page.evaluate(() => { if (typeof globalThis.gc === 'function') { globalThis.gc(); globalThis.gc(); } }); + const worker = await page.evaluate(() => window.CascadeAPI._memoryStats()); + await new Promise((r) => setTimeout(r, 400)); // let the OS settle the RSS + worker.rendererRssKb = await rendererRssKb(cdp); + return worker; +} + +/** Download the runtime's assets with the HTTP cache bypassed. */ +async function measureAssets(page, assets) { + return page.evaluate(async (list) => { + const out = { totalMs: 0, totalBytes: 0, files: {} }; + for (const name of list) { + const t0 = performance.now(); + const response = await fetch(name + '?nocache=' + Math.random(), { cache: 'no-store' }); + const buffer = await response.arrayBuffer(); + const ms = performance.now() - t0; + out.files[name] = { ms: +ms.toFixed(1), bytes: buffer.byteLength }; + out.totalMs += ms; + out.totalBytes += buffer.byteLength; + } + out.totalMs = +out.totalMs.toFixed(1); + return out; + }, assets); +} + +async function runPython(page, code, timeout) { + const t0 = Date.now(); + await page.evaluate(async (c) => { + window.CascadeAPI.setMode('python'); + return await window.CascadeAPI.runCode(c); + }, code); + await page.waitForFunction(() => !window.CascadeAPI.isWorking(), undefined, + { timeout: timeout || 120000 }); + return Date.now() - t0; +} + +async function main() { + const serverProc = await ensureServer(); + const browser = await chromium.launch({ + headless: !process.env.CS_TEST_HEADFUL, + args: ['--use-gl=angle', '--use-angle=swiftshader', '--js-flags=--expose-gc'], + }); + + // A browser-level CDP session is how we learn which pids are renderers. + const cdp = await browser.newBrowserCDPSession(); + const runs = []; + for (let i = 0; i < REPEAT; i++) { + // A fresh context is a fresh HTTP cache: the first page pays the cold + // download, the second one does not. + const context = await browser.newContext(); + const run = { index: i }; + + // COLD: a fresh context, so nothing of the runtime is cached yet. The + // baseline is this very page before any Python has run — same renderer + // process, so the deltas are clean. (The asset download measurement runs + // LAST, for the same reason: 13 MB of ArrayBuffers in this renderer would + // otherwise show up in the memory numbers.) + let page = await newJsModePage(context); + run.baseline = await stats(page, cdp); + run.coldFirstEvalMs = await runPython(page, TRIVIAL); + run.afterBoot = await stats(page, cdp); + run.warmEvalMs = await runPython(page, TRIVIAL); + const starter = await page.evaluate(() => + window.CascadeAPI._app.constructor.starterCode('python')); + run.starterEvalMs = await runPython(page, starter); + run.afterStarter = await stats(page, cdp); + await page.close(); + + // WARM: same context, so the runtime's assets come from the HTTP cache. + page = await newJsModePage(context); + run.baselineWarm = await stats(page, cdp); + run.warmFirstEvalMs = await runPython(page, TRIVIAL); + run.afterWarmBoot = await stats(page, cdp); + + if (WITH_CORPUS) { + const manifest = JSON.parse(readFileSync(join(HERE, 'manifest.json'), 'utf8')); + let ran = 0, failed = 0, stalled = 0; + const perScript = {}; + const t0 = Date.now(); + for (const entry of manifest) { + try { + perScript[entry.id] = await runPython(page, entry.code, CORPUS_TIMEOUT); + ran++; + } catch (e) { + failed++; + // A stuck worker would poison every later script; stop rather than + // report a memory number for half a corpus. + const idle = await page.waitForFunction(() => !window.CascadeAPI.isWorking(), + undefined, { timeout: 60000 }).then(() => true, () => false); + if (!idle) { stalled++; break; } + } + } + const sorted = Object.values(perScript).sort((a, b) => a - b); + const at = (q) => (sorted.length ? sorted[Math.min(sorted.length - 1, + Math.floor(q * sorted.length))] : 0); + run.corpus = { + ran, failed, stalled, wallMs: Date.now() - t0, + medianMs: at(0.5), p95Ms: at(0.95), perScript, + }; + run.afterCorpus = await stats(page, cdp); + } + // Last, so the downloaded bytes cannot land in any memory sample. + run.assetsCold = await measureAssets(page, RUNTIME_ASSETS[RUNTIME]); + await page.close(); + await context.close(); + + runs.push(run); + console.log(`[${RUNTIME}] run ${i + 1}/${REPEAT}: cold ${run.coldFirstEvalMs}ms, ` + + `warm ${run.warmFirstEvalMs}ms, boot ${JSON.stringify(run.afterBoot.bootTiming)}, ` + + `rss ${(run.afterBoot.rendererRssKb / 1024).toFixed(0)}MB (baseline ${(run.baseline.rendererRssKb / 1024).toFixed(0)}MB), ` + + `pyWasm ${(run.afterBoot.pythonWasm / 1048576).toFixed(1)}MB` + + (run.corpus ? `, corpus ${run.corpus.ran} scripts in ${(run.corpus.wallMs / 1000).toFixed(0)}s` : '')); + } + + await browser.close(); + if (serverProc) serverProc.kill(); + writeFileSync(OUT, JSON.stringify({ runtime: RUNTIME, runs }, null, 1)); + console.log('wrote ' + OUT); +} + +main(); diff --git a/test/b123d-validation/canonical-cross-kernel.json b/test/b123d-validation/canonical-cross-kernel.json new file mode 100644 index 00000000..537335f3 --- /dev/null +++ b/test/b123d-validation/canonical-cross-kernel.json @@ -0,0 +1,898 @@ +{ + "tolerance": 0.001, + "compared_measurements": 185, + "worst_delta": 0, + "worst_key": "", + "disagreements": [], + "kernel_input_differences": [ + { + "key": ".sphere_cylinder_reassembled.rot0.raw_start[0]", + "native": 6, + "lite": 8.124 + }, + { + "key": ".sphere_cylinder_reassembled.rot0.raw_start[1]", + "native": 0, + "lite": 5 + }, + { + "key": ".sphere_cylinder_reassembled.rot0.raw_start[2]", + "native": 8, + "lite": 3 + }, + { + "key": ".sphere_cylinder_reassembled.rot0.form_start", + "native": 1, + "lite": 0.748331 + }, + { + "key": ".sphere_cylinder_reassembled.rot0.form_sign", + "native": 1, + "lite": -1 + } + ], + "frame_consistency": { + "native": { + "arch": { + "frames": 5, + "inconsistent": [] + }, + "sphere_cylinder_reassembled": { + "frames": 3, + "inconsistent": [] + }, + "joints": { + "frames": 3, + "inconsistent": [] + } + }, + "lite": { + "arch": { + "frames": 5, + "inconsistent": [] + }, + "sphere_cylinder_reassembled": { + "frames": 3, + "inconsistent": [] + }, + "joints": { + "frames": 3, + "inconsistent": [] + } + } + }, + "native": { + "arch": { + "rot0": { + "raw_start": [ + 48.9898, + 0, + 10 + ], + "canonical": { + "length": 320.9223, + "pos0": [ + -48.9898, + 0, + 10 + ], + "pos25": [ + 0, + -49.4872, + -7.1429 + ], + "pos50": [ + 48.9898, + 0, + 10 + ], + "pos75": [ + 0, + 49.4872, + -7.1429 + ], + "tan0": [ + 0, + -1, + 0 + ] + } + }, + "rot45": { + "raw_start": [ + 35.3331, + 35.3331, + 1.7745 + ], + "canonical": { + "length": 320.9223, + "pos0": [ + -48.9898, + 0, + 10 + ], + "pos25": [ + 0, + -49.4872, + -7.1429 + ], + "pos50": [ + 48.9898, + 0, + 10 + ], + "pos75": [ + 0, + 49.4872, + -7.1429 + ], + "tan0": [ + 0, + -1, + 0 + ] + } + }, + "rot90": { + "raw_start": [ + 0, + 49.4872, + -7.1429 + ], + "canonical": { + "length": 320.9223, + "pos0": [ + -48.9898, + 0, + 10 + ], + "pos25": [ + 0, + -49.4872, + -7.1429 + ], + "pos50": [ + 48.9898, + 0, + 10 + ], + "pos75": [ + 0, + 49.4872, + -7.1429 + ], + "tan0": [ + 0, + -1, + 0 + ] + } + }, + "rot180": { + "raw_start": [ + -48.9898, + 0, + 10 + ], + "canonical": { + "length": 320.9223, + "pos0": [ + -48.9898, + 0, + 10 + ], + "pos25": [ + 0, + -49.4872, + -7.1429 + ], + "pos50": [ + 48.9898, + 0, + 10 + ], + "pos75": [ + 0, + 49.4872, + -7.1429 + ], + "tan0": [ + 0, + -1, + 0 + ] + } + }, + "rot270": { + "raw_start": [ + 0, + -49.4872, + -7.1429 + ], + "canonical": { + "length": 320.9223, + "pos0": [ + -48.9898, + 0, + 10 + ], + "pos25": [ + 0, + -49.4872, + -7.1429 + ], + "pos50": [ + 48.9898, + 0, + 10 + ], + "pos75": [ + 0, + 49.4872, + -7.1429 + ], + "tan0": [ + 0, + -1, + 0 + ] + } + } + }, + "sphere_cylinder_reassembled": { + "rot0": { + "edge_count": 4, + "loop_edge_count": 3, + "raw_start": [ + 6, + 0, + 8 + ], + "form_start": 1, + "form_sign": 1, + "canonical": { + "length": 32.5199, + "pos0": [ + 6, + 0, + 8 + ], + "pos25": [ + 8.1052, + -4.9997, + 3.0509 + ], + "pos50": [ + 9.798, + 0, + -2 + ], + "pos75": [ + 8.1052, + 4.9997, + 3.0509 + ], + "tan0": [ + 0, + -1, + 0 + ] + } + }, + "rot90": { + "edge_count": 2, + "loop_edge_count": 1, + "raw_start": [ + 8.124, + 5, + 3 + ], + "form_start": 0.251669, + "form_sign": 1, + "canonical": { + "length": 32.5199, + "pos0": [ + 6, + 0, + 8 + ], + "pos25": [ + 8.1052, + -4.9997, + 3.0509 + ], + "pos50": [ + 9.798, + 0, + -2 + ], + "pos75": [ + 8.1052, + 4.9997, + 3.0509 + ], + "tan0": [ + 0, + -1, + 0 + ] + } + }, + "rot180": { + "edge_count": 4, + "loop_edge_count": 1, + "raw_start": [ + 8.124, + 5, + 3 + ], + "form_start": 0.251669, + "form_sign": 1, + "canonical": { + "length": 32.5199, + "pos0": [ + 6, + 0, + 8 + ], + "pos25": [ + 8.1052, + -4.9997, + 3.0509 + ], + "pos50": [ + 9.798, + 0, + -2 + ], + "pos75": [ + 8.1052, + 4.9997, + 3.0509 + ], + "tan0": [ + 0, + -1, + 0 + ] + } + } + }, + "joints": { + "rot0": { + "volume": 801.7636, + "canonical_axes": [ + [ + [ + -4.4759, + -4.4759, + 10 + ], + [ + 1, + 0, + 0 + ] + ], + [ + [ + -4.4759, + 4.4759, + 10 + ], + [ + 1, + 0, + 0 + ] + ] + ], + "midway_start": [ + -4.47592, + 1.52181, + 10 + ], + "midway_end": [ + 4.47592, + 1.52181, + 10 + ] + }, + "rot90": { + "volume": 801.7636, + "canonical_axes": [ + [ + [ + -4.4759, + -4.4759, + 10 + ], + [ + 1, + 0, + 0 + ] + ], + [ + [ + -4.4759, + 4.4759, + 10 + ], + [ + 1, + 0, + 0 + ] + ] + ], + "midway_start": [ + -4.47592, + 1.52181, + 10 + ], + "midway_end": [ + 4.47592, + 1.52181, + 10 + ] + }, + "rot180": { + "volume": 801.7636, + "canonical_axes": [ + [ + [ + -4.4759, + -4.4759, + 10 + ], + [ + 1, + 0, + 0 + ] + ], + [ + [ + -4.4759, + 4.4759, + 10 + ], + [ + 1, + 0, + 0 + ] + ] + ], + "midway_start": [ + -4.47592, + 1.52181, + 10 + ], + "midway_end": [ + 4.47592, + 1.52181, + 10 + ] + } + } + }, + "lite": { + "arch": { + "rot0": { + "raw_start": [ + 48.9898, + 0, + 10 + ], + "canonical": { + "length": 320.9223, + "pos0": [ + -48.9898, + 0, + 10 + ], + "pos25": [ + 0, + -49.4872, + -7.1429 + ], + "pos50": [ + 48.9898, + 0, + 10 + ], + "pos75": [ + 0, + 49.4872, + -7.1429 + ], + "tan0": [ + 0, + -1, + 0 + ] + } + }, + "rot45": { + "raw_start": [ + 35.3331, + 35.3331, + 1.7745 + ], + "canonical": { + "length": 320.9223, + "pos0": [ + -48.9898, + 0, + 10 + ], + "pos25": [ + 0, + -49.4872, + -7.1429 + ], + "pos50": [ + 48.9898, + 0, + 10 + ], + "pos75": [ + 0, + 49.4872, + -7.1429 + ], + "tan0": [ + 0, + -1, + 0 + ] + } + }, + "rot90": { + "raw_start": [ + 0, + 49.4872, + -7.1429 + ], + "canonical": { + "length": 320.9223, + "pos0": [ + -48.9898, + 0, + 10 + ], + "pos25": [ + 0, + -49.4872, + -7.1429 + ], + "pos50": [ + 48.9898, + 0, + 10 + ], + "pos75": [ + 0, + 49.4872, + -7.1429 + ], + "tan0": [ + 0, + -1, + 0 + ] + } + }, + "rot180": { + "raw_start": [ + -48.9898, + 0, + 10 + ], + "canonical": { + "length": 320.9223, + "pos0": [ + -48.9898, + 0, + 10 + ], + "pos25": [ + 0, + -49.4872, + -7.1429 + ], + "pos50": [ + 48.9898, + 0, + 10 + ], + "pos75": [ + 0, + 49.4872, + -7.1429 + ], + "tan0": [ + 0, + -1, + 0 + ] + } + }, + "rot270": { + "raw_start": [ + 0, + -49.4872, + -7.1429 + ], + "canonical": { + "length": 320.9223, + "pos0": [ + -48.9898, + 0, + 10 + ], + "pos25": [ + 0, + -49.4872, + -7.1429 + ], + "pos50": [ + 48.9898, + 0, + 10 + ], + "pos75": [ + 0, + 49.4872, + -7.1429 + ], + "tan0": [ + 0, + -1, + 0 + ] + } + } + }, + "sphere_cylinder_reassembled": { + "rot0": { + "edge_count": 4, + "loop_edge_count": 3, + "raw_start": [ + 8.124, + 5, + 3 + ], + "form_start": 0.748331, + "form_sign": -1, + "canonical": { + "length": 32.5199, + "pos0": [ + 6, + 0, + 8 + ], + "pos25": [ + 8.1052, + -4.9997, + 3.0509 + ], + "pos50": [ + 9.798, + 0, + -2 + ], + "pos75": [ + 8.1052, + 4.9997, + 3.0509 + ], + "tan0": [ + 0, + -1, + 0 + ] + } + }, + "rot90": { + "edge_count": 2, + "loop_edge_count": 1, + "raw_start": [ + 8.124, + 5, + 3 + ], + "form_start": 0.251669, + "form_sign": 1, + "canonical": { + "length": 32.5199, + "pos0": [ + 6, + 0, + 8 + ], + "pos25": [ + 8.1052, + -4.9997, + 3.0509 + ], + "pos50": [ + 9.798, + 0, + -2 + ], + "pos75": [ + 8.1052, + 4.9997, + 3.0509 + ], + "tan0": [ + 0, + -1, + 0 + ] + } + }, + "rot180": { + "edge_count": 4, + "loop_edge_count": 1, + "raw_start": [ + 8.124, + 5, + 3 + ], + "form_start": 0.251669, + "form_sign": 1, + "canonical": { + "length": 32.5199, + "pos0": [ + 6, + 0, + 8 + ], + "pos25": [ + 8.1052, + -4.9997, + 3.0509 + ], + "pos50": [ + 9.798, + 0, + -2 + ], + "pos75": [ + 8.1052, + 4.9997, + 3.0509 + ], + "tan0": [ + 0, + -1, + 0 + ] + } + } + }, + "joints": { + "rot0": { + "volume": 801.7636, + "canonical_axes": [ + [ + [ + -4.4759, + -4.4759, + 10 + ], + [ + 1, + 0, + 0 + ] + ], + [ + [ + -4.4759, + 4.4759, + 10 + ], + [ + 1, + 0, + 0 + ] + ] + ], + "midway_start": [ + -4.47592, + 1.52181, + 10 + ], + "midway_end": [ + 4.47592, + 1.52181, + 10 + ] + }, + "rot90": { + "volume": 801.7636, + "canonical_axes": [ + [ + [ + -4.4759, + -4.4759, + 10 + ], + [ + 1, + 0, + 0 + ] + ], + [ + [ + -4.4759, + 4.4759, + 10 + ], + [ + 1, + 0, + 0 + ] + ] + ], + "midway_start": [ + -4.47592, + 1.52181, + 10 + ], + "midway_end": [ + 4.47592, + 1.52181, + 10 + ] + }, + "rot180": { + "volume": 801.7636, + "canonical_axes": [ + [ + [ + -4.4759, + -4.4759, + 10 + ], + [ + 1, + 0, + 0 + ] + ], + [ + [ + -4.4759, + 4.4759, + 10 + ], + [ + 1, + 0, + 0 + ] + ] + ], + "midway_start": [ + -4.47592, + 1.52181, + 10 + ], + "midway_end": [ + 4.47592, + 1.52181, + 10 + ] + } + } + } +} diff --git a/test/b123d-validation/canonical-cross-kernel.mjs b/test/b123d-validation/canonical-cross-kernel.mjs new file mode 100644 index 00000000..48d39c25 --- /dev/null +++ b/test/b123d-validation/canonical-cross-kernel.mjs @@ -0,0 +1,288 @@ +// canonical-cross-kernel.mjs — does build123d-lite's canonical() on OCCT 8.0.1 +// agree with PATCHED upstream build123d's canonical() on OCP 7.9.3? +// +// The rule (Build123dLite.js `canonical_form`, ported from the upstream +// canonical-free-edges proposal) is defined from geometry alone, so the answer +// must be yes even though the two kernels seam and orient free edges +// differently. This runs the SAME three constructions the native reference +// script runs and diffs the numbers. +// +// # reference (patched build123d 0.11.1 + OCP 7.9.3), regenerate on demand: +// (research lives on zalo/build123d branch canonical-research, research/) +// cd /experiments && ./repatch.sh +// PYTHONPATH=/tmp/b123d-0111 ~/Desktop/ocjs-deps/b123d-ref-venv/bin/python \ +// lite_cross_kernel.py > canonical-lite-reference.json +// +// # lite side + comparison (needs `npm run build` first): +// CS_TEST_HEADFUL=1 DISPLAY=:99 node test/b123d-validation/canonical-cross-kernel.mjs +// +// Exit code 0 when every measurement agrees within --tol (default 1e-3 mm). + +import { chromium } from 'playwright'; +import { spawn } from 'node:child_process'; +import { readFileSync, writeFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import http from 'node:http'; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const ROOT = join(HERE, '..', '..'); +const args = process.argv.slice(2); +const argVal = (name, dflt) => { + const i = args.indexOf(name); + return i >= 0 ? args[i + 1] : dflt; +}; +const PORT = parseInt(argVal('--port', process.env.CS_TEST_PORT || '8517'), 10); +const TOL = parseFloat(argVal('--tol', '1e-3')); +// The reference JSON is produced by the research repo (zalo/build123d branch +// canonical-research, research/experiments/lite_cross_kernel.py); point +// --reference at the checkout, or set B123D_CANONICAL_REFERENCE. +const REFERENCE = argVal('--reference', process.env.B123D_CANONICAL_REFERENCE || + join(ROOT, '..', 'build123d-canonical-research', 'research', 'experiments', + 'canonical-lite-reference.json')); +const OUT = argVal('--out', join(HERE, 'canonical-cross-kernel.json')); + +// The lite side of the comparison: the same script as +// the research repo's experiments/lite_cross_kernel.py, in lite's +// dialect (build123d-lite implements the same public API). +const SCRIPT = ` +from build123d import * + +def _j(x): + # build123d-lite runs on Brython, which has no json module + if isinstance(x, bool): + return 'true' if x else 'false' + if isinstance(x, (int, float)): + return repr(float(x)) + if isinstance(x, str): + return '"' + x + '"' + if isinstance(x, (list, tuple)): + return '[' + ','.join([_j(v) for v in x]) + ']' + if isinstance(x, dict): + return '{' + ','.join(['"' + k + '":' + _j(v) for k, v in x.items()]) + '}' + raise TypeError(str(type(x))) + +def brief(shape): + return { + "length": round(shape.length, 4), + "pos0": [round(v, 4) for v in tuple(shape.position_at(0))], + "pos25": [round(v, 4) for v in tuple(shape.position_at(0.25))], + "pos50": [round(v, 4) for v in tuple(shape.position_at(0.50))], + "pos75": [round(v, 4) for v in tuple(shape.position_at(0.75))], + "tan0": [round(v, 4) for v in tuple(shape.tangent_at(0))], + } + +def positive_x_loop(wires): + return [wr for wr in wires + if min([wr.position_at(i / 64.0).X for i in range(64)]) > 0][0] + +out = {} + +arch = {} +for rotation in (0, 45, 90, 180, 270): + sphere = Solid.make_sphere(50) + if rotation: + sphere = sphere.rotate(Axis.Z, rotation) + cutter = Solid.make_cylinder(80, 100, Plane.YZ).locate(Location((-50, 0, -70))) + edge = sphere.cut(cutter).edges().sort_by(Axis.Z)[0] + arch["rot" + str(rotation)] = { + "raw_start": [round(v, 4) for v in tuple(edge.position_at(0))], + "canonical": brief(edge.canonical()), + } +out["arch"] = arch + +reassembled = {} +for rotation in (0, 90, 180): + sphere = Solid.make_sphere(10) + if rotation: + sphere = sphere.rotate(Axis.Z, rotation) + cutter = Solid.make_cylinder(5, 40, Plane.YZ).locate(Location((-20, 0, 3))) + edges = sphere.cut(cutter).edges().filter_by(GeomType.BSPLINE) + loop = positive_x_loop(edges_to_wires(edges)) + form = loop.canonical_form() + reassembled["rot" + str(rotation)] = { + "edge_count": len(edges), + "loop_edge_count": len(loop.edges()), + "raw_start": [round(v, 4) for v in tuple(loop.position_at(0))], + "form_start": round(form.start, 6), + "form_sign": form.sign, + "canonical": brief(loop.canonical()), + } +out["sphere_cylinder_reassembled"] = reassembled + +joints = {} +for rotation in (0, 90, 180): + with BuildPart() as part: + with BuildSketch(): + Rectangle(10, 10) + extrude(amount=10, taper=3) + Cylinder(2.5, 10, rotation=(0, 90, rotation), mode=Mode.SUBTRACT) + solid = part.part + top = solid.edges().filter_by(Axis.X, tolerance=30).sort_by(Axis.Z, tie_break=True)[-2:] + midway = Edge.make_mid_way(top[0], top[1], 0.67) + joints["rot" + str(rotation)] = { + "volume": round(solid.volume, 4), + "canonical_axes": [ + [[round(v, 4) for v in tuple(Axis(e, canonical=True).position)], + [round(v, 4) for v in tuple(Axis(e, canonical=True).direction)]] + for e in top + ], + "midway_start": [round(v, 5) for v in tuple(midway.position_at(0))], + "midway_end": [round(v, 5) for v in tuple(midway.position_at(1))], + } +out["joints"] = joints + +print("CANON_JSON " + _j(out)) +`; + +async function ensureServer() { + const alive = await new Promise((res) => { + const req = http.get({ host: 'localhost', port: PORT, path: '/' }, + (r) => { r.resume(); res(true); }); + req.on('error', () => res(false)); + req.setTimeout(2000, () => { req.destroy(); res(false); }); + }); + if (alive) return null; + const proc = spawn('npx', ['http-server', './packages/cascade-studio/dist', + '-p', String(PORT), '-c-1', '--silent'], { cwd: ROOT, stdio: 'ignore' }); + await new Promise((r) => setTimeout(r, 2500)); + return proc; +} + +// The "before" columns: how each kernel happened to seam and orient the free +// edge on the way in. They are RECORDED, not compared - a kernel difference +// here is the premise of the whole exercise, not a failure. +const INPUT_COLUMN = /\.(raw_start|form_start|form_sign|edge_count|loop_edge_count)\b/; + +/** Every leaf number in an object, keyed by its path. */ +function flatten(value, prefix, into) { + if (Array.isArray(value)) { + value.forEach((v, i) => flatten(v, `${prefix}[${i}]`, into)); + } else if (value && typeof value === 'object') { + for (const key of Object.keys(value)) flatten(value[key], `${prefix}.${key}`, into); + } else { + into[prefix] = value; + } + return into; +} + +const serverProc = await ensureServer(); +const browser = await chromium.launch({ + headless: !process.env.CS_TEST_HEADFUL, + args: ['--use-gl=angle', '--use-angle=swiftshader'], +}); +const page = await browser.newPage(); +page.on('pageerror', () => {}); +await page.goto(`http://localhost:${PORT}/`, { timeout: 60000 }); +await page.waitForFunction(() => window.CascadeAPI && window.CascadeAPI.isReady(), + undefined, { timeout: 90000 }); +await page.waitForFunction(() => !window.CascadeAPI.isWorking(), + undefined, { timeout: 90000 }); +await page.evaluate(() => window.CascadeAPI.setMode('python')); +await page.evaluate(async (code) => await window.CascadeAPI.runCode(code), SCRIPT); +await page.waitForFunction( + () => window.CascadeAPI.getConsoleLog().some((l) => l.includes('CANON_JSON')) || + window.CascadeAPI.getErrors().length > 0, + undefined, { timeout: 180000 }); +const logs = await page.evaluate(() => window.CascadeAPI.getConsoleLog()); +const errors = await page.evaluate(() => window.CascadeAPI.getErrors()); +await browser.close(); +if (serverProc) serverProc.kill(); + +const line = logs.find((l) => l.includes('CANON_JSON')); +if (!line) { + console.error('lite produced no measurements:\n' + errors.join('\n')); + process.exit(1); +} +// worker console strings arrive with escaped quotes +const payload = line.slice(line.indexOf('CANON_JSON') + 'CANON_JSON '.length) + .replace(/\\"/g, '"'); +const lite = JSON.parse(payload); +const native = JSON.parse(readFileSync(REFERENCE, 'utf8')); + +const liteFlat = flatten(lite, '', {}); +const nativeFlat = flatten(native, '', {}); +const disagreements = []; +const inputDeltas = []; +let worst = 0, worstKey = ''; +let compared = 0; +for (const key of Object.keys(nativeFlat)) { + const a = nativeFlat[key], b = liteFlat[key]; + const differs = (typeof a === 'number' && typeof b === 'number') + ? Math.abs(a - b) > TOL : a !== b; + const delta = (typeof a === 'number' && typeof b === 'number') + ? Math.abs(a - b) : null; + if (INPUT_COLUMN.test(key)) { + if (differs) inputDeltas.push({ key, native: a, lite: b }); + continue; + } + compared += 1; + if (delta !== null && delta > worst) { worst = delta; worstKey = key; } + if (differs) disagreements.push({ key, native: a, lite: b, delta }); +} + +/** Do all frames of one case canonicalize to the same traversal? That is the + * property canonical() exists for, and it is checked INSIDE each kernel. */ +function frameConsistency(data) { + const report = {}; + for (const [caseName, frames] of Object.entries(data)) { + const names = Object.keys(frames); + const base = flatten(frames[names[0]].canonical ?? frames[names[0]], '', {}); + const off = []; + for (const name of names.slice(1)) { + const other = flatten(frames[name].canonical ?? frames[name], '', {}); + const worstHere = Math.max(...Object.keys(base).map((k) => + typeof base[k] === 'number' && typeof other[k] === 'number' + ? Math.abs(base[k] - other[k]) : (base[k] === other[k] ? 0 : Infinity))); + if (worstHere > TOL) off.push({ frame: name, worst: worstHere }); + } + report[caseName] = { frames: names.length, inconsistent: off }; + } + return report; +} +const nativeFrames = frameConsistency(native); +const liteFrames = frameConsistency(lite); + +writeFileSync(OUT, JSON.stringify({ + tolerance: TOL, + compared_measurements: compared, + worst_delta: worst, worst_key: worstKey, + disagreements, + kernel_input_differences: inputDeltas, + frame_consistency: { native: nativeFrames, lite: liteFrames }, + native, lite, +}, null, 1) + '\n'); + +console.log(`compared ${compared} canonical measurements ` + + '(patched build123d / OCP 7.9.3 vs build123d-lite / OCCT 8.0.1 wasm)'); +console.log(`worst delta ${worst.toExponential(2)} mm at ${worstKey || '-'}`); +console.log(`${inputDeltas.length} raw (pre-canonical) kernel differences recorded ` + + '— the premise, not a failure'); +for (const row of disagreements) { + console.log(` DISAGREE ${row.key}: native ${row.native} vs lite ${row.lite}` + + (row.delta === null ? '' : ` (d=${row.delta.toExponential(2)})`)); +} +console.log('\nframe consistency (all frames of a case must canonicalize alike):'); +let frameFail = false; +for (const caseName of Object.keys(nativeFrames)) { + const n = nativeFrames[caseName], l = liteFrames[caseName]; + const same = JSON.stringify(n.inconsistent.map((x) => x.frame)) === + JSON.stringify(l.inconsistent.map((x) => x.frame)); + console.log(` ${caseName}: native ${n.inconsistent.length}/${n.frames - 1} off, ` + + `lite ${l.inconsistent.length}/${l.frames - 1} off` + + (n.inconsistent.length ? ` [${n.inconsistent.map((x) => x.frame).join(',')}]` : '') + + (same ? '' : ' <-- KERNELS BEHAVE DIFFERENTLY')); + if (!same) frameFail = true; +} +if (Object.values(nativeFrames).some((c) => c.inconsistent.length) || + Object.values(liteFrames).some((c) => c.inconsistent.length)) { + console.log('\nA frame-inconsistent case means canonical() is not doing its job in\n' + + ' that kernel. All three cases were consistent as of the patch fixes in\n' + + ' REPORT.md 3.3 (circular already-canonical test, tangent-before-gap ranking\n' + + ' in _walk_loop, local-minima band discovery with quantised ranking).'); +} +console.log(disagreements.length === 0 && !frameFail + ? '\nAGREE — canonical() is kernel independent' + : `\n${disagreements.length} disagreement(s)`); +console.log(`-> ${OUT}`); +process.exit(disagreements.length === 0 && !frameFail ? 0 : 1); diff --git a/test/b123d-validation/collect.py b/test/b123d-validation/collect.py new file mode 100644 index 00000000..7179f596 --- /dev/null +++ b/test/b123d-validation/collect.py @@ -0,0 +1,481 @@ +#!/usr/bin/env python3 +"""collect.py - enumerate candidate build123d scripts for lite-mode validation. + +Sources (override the clone location with B123D_SRC): + * /examples/*.py - one script each + * /docs/general_examples.py - split into numbered snippets + * /docs/general_examples_algebra.py - split into numbered snippets + * /docs/*.py - the standalone documentation + scripts (objects_1d/2d/3d, + tutorial_joints, slide_latch, + rod_end, line_types, ...) + * /docs/objects/examples/*.py - per-page object examples + * /docs/topology_selection/examples/*.py - selector examples + * /docs/assets/ttt/*.py - the "Too Tall Toby" challenge + parts (each ends in a mass + assert - a hard PASS bar) + * /docs/**/*.rst - every ``code-block:: build123d`` + / ``python`` snippet, plus one + cumulative script per page + (what a reader pasting a whole + tutorial page actually runs) + +Each candidate is lightly sanitized (viewer imports/calls removed, doctest +prompts unwrapped) but geometry code is untouched. Output is a single +manifest.json: [{id, source, kind, code, data_dir?}]. + +Most .rst snippets are prose fragments (``...`` placeholders, undefined names) +rather than runnable scripts, so the manifest is produced in two stages: + + collect.py -> manifest-all.json (every candidate) + reference.py --manifest ... -> reference-all.json (native ground truth) + collect.py prune -> manifest.json + reference.json + (only candidates that natively produce + at least one measurable shape) + +Scripts that ARE real scripts but fail natively stay in the corpus and are +recorded as SKIP by the lite runner (see reference.py); pruning only drops +candidates that were never runnable code in the first place. + +Usage: + python3 collect.py [--out manifest-all.json] + python3 collect.py prune [--manifest manifest-all.json] + [--reference reference-all.json] + [--out manifest.json] [--out-reference reference.json] +""" + +import argparse +import json +import os +import re +import sys + +B123D_SRC = os.environ.get("B123D_SRC", "/tmp/b123d") +HERE = os.path.dirname(os.path.abspath(__file__)) + +# Scripts that cannot be meaningfully validated headlessly (interactive +# viewers, file exports of external assets, network, ...). Everything else is +# included even if we EXPECT it to fail - failures are data, not noise. +SKIP_EXAMPLES = { + "benchy.py", # imports an external STL via Mesher() - no asset in scope + "benchy_v2024.py", # same +} + +# docs/*.py that are not build123d scripts at all. +SKIP_DOCS_SCRIPTS = { + "conf.py", # sphinx configuration + "build123d_lexer.py", # pygments lexer for the docs build + # split into numbered snippets by collect_general() instead + "general_examples.py", + "general_examples_algebra.py", +} + +# Line-level sanitizers: viewer/plot machinery that is not geometry. +# NOTE: SVG/DXF export machinery is deliberately NOT stripped - build123d-lite +# ships a no-op ExportSVG, and the reference run has a scratch cwd with an +# assets/ directory, so both sides tolerate it verbatim. +DROP_LINE_PATTERNS = [ + r"^\s*from\s+ocp_vscode\s+import\b", + r"^\s*import\s+ocp_vscode\b", + r"^\s*set_port\s*\(", + r"^\s*set_defaults\s*\(", + r"^\s*set_colormap\s*\(", + # ocp_vscode viewer control that has no geometry meaning at all + r"^\s*save_screenshot\s*\(", + r"^\s*set_viewer_config\s*\(", + r"^\s*reset_show\s*\(", + r"^\s*push_object\s*\(", + # SVG/export helpers in docs scripts + r"^\s*write_svg\s*\(", + r"^\s*svgout\s*\(", +] +DROP_LINE_RE = [re.compile(p) for p in DROP_LINE_PATTERNS] + + +def _bracket_delta(line): + """Net (/[/{ nesting change of a line, ignoring strings and comments.""" + depth = 0 + quote = None + i = 0 + while i < len(line): + ch = line[i] + if quote: + if ch == "\\": + i += 2 + continue + if line.startswith(quote, i): + i += len(quote) + quote = None + continue + i += 1 + continue + if ch in "\"'": + for q in ('"""', "'''"): + if line.startswith(q, i): + quote = q + break + else: + quote = ch + i += len(quote) + continue + if ch == "#": + break + if ch in "([{": + depth += 1 + elif ch in ")]}": + depth -= 1 + i += 1 + return depth + + +def sanitize(code): + """Remove viewer/export statements; keep everything else verbatim. + + Statement-aware: docs scripts call `write_svg(\\n ... \\n)` across several + lines, so once a drop pattern matches, the continuation lines are removed + with it (commenting only the first line used to leave a dangling `)` and + turn the whole script into a SyntaxError). Line COUNT is preserved so + tracebacks still map onto the upstream source visually. + """ + out = [] + lines = code.split("\n") + i = 0 + while i < len(lines): + line = lines[i] + if not any(p.match(line) for p in DROP_LINE_RE): + out.append(line) + i += 1 + continue + indent = line[:len(line) - len(line.lstrip())] + depth = _bracket_delta(line) + # inside a block the statement cannot just vanish, or the block body + # becomes empty (IndentationError) - keep a `pass` in its place + out.append("%s%s# [removed by collect.py] %s" + % (indent, "pass " if indent else "", line.strip())) + i += 1 + while depth > 0 and i < len(lines): + out.append("# [removed by collect.py] " + lines[i].strip()) + depth += _bracket_delta(lines[i]) + i += 1 + return "\n".join(out) + + +def strip_module_docstring(code): + """Drop a leading module docstring (license boilerplate) if present.""" + m = re.match(r'\s*(?:"""(?:.|\n)*?"""|\'\'\'(?:.|\n)*?\'\'\')\s*\n', code) + if m: + return code[m.end():] + return code + + +def collect_examples(manifest): + exdir = os.path.join(B123D_SRC, "examples") + for fname in sorted(os.listdir(exdir)): + if not fname.endswith(".py") or fname in SKIP_EXAMPLES: + continue + code = open(os.path.join(exdir, fname)).read() + code = sanitize(strip_module_docstring(code)) + manifest.append({ + "id": "examples/" + fname[:-3], + "source": "examples/" + fname, + "kind": "example", + "code": code, + }) + + +GENERAL_HELPER_RE = re.compile( + r"def write_svg\(.*?\n(?: .*\n|\n)*", re.MULTILINE) +SECTION_SPLIT_RE = re.compile(r"^#{10,}\s*$", re.MULTILINE) +SECTION_TITLE_RE = re.compile(r"^#\s*(\d+)\.\s*(.+)$", re.MULTILINE) + + +def collect_general(manifest, fname, kind): + """Split docs/general_examples*.py into one snippet per numbered example.""" + path = os.path.join(B123D_SRC, "docs", fname) + code = open(path).read() + code = strip_module_docstring(code) + code = GENERAL_HELPER_RE.sub("", code) # remove the write_svg() helper + + sections = SECTION_SPLIT_RE.split(code) + for section in sections: + m = SECTION_TITLE_RE.search(section) + if not m: + continue # preamble (imports) or trailing junk + num, title = int(m.group(1)), m.group(2).strip() + body = sanitize(section).strip("\n") + # Every snippet gets the same minimal header the source file had. + snippet = "from build123d import *\nfrom math import *\n\n" + body + "\n" + manifest.append({ + "id": "%s/ex%02d" % (fname[:-3], num), + "source": "docs/" + fname + " #%d (%s)" % (num, title), + "kind": kind, + "code": snippet, + }) + + +# -------------------------------------------------------------------------- +# Standalone .py scripts living under docs/ (the documentation's own sources) +# -------------------------------------------------------------------------- + +# (relative directory under the clone, manifest id prefix, kind) +SCRIPT_DIRS = [ + ("docs", "docs", "docs-script"), + ("docs/objects/examples", "docs-objects", "docs-script"), + ("docs/topology_selection/examples", "docs-selectors", "docs-script"), + ("docs/assets/ttt", "ttt", "ttt"), +] + + +ASSET_EXTS = (".step", ".stp", ".stl", ".iges", ".igs") + + +def script_assets(reldir, code): + """CAD files the script imports from its own directory. + + The reference run gets these as symlinks; the lite run has no filesystem, + so run-lite.mjs reads them out of the clone and hands the content to the + worker (CascadeAPI.loadExternalFiles) before evaluating. Only files whose + name actually appears in the source are listed.""" + absdir = os.path.join(B123D_SRC, reldir) + if not os.path.isdir(absdir): + return [] + return sorted(f for f in os.listdir(absdir) + if f.lower().endswith(ASSET_EXTS) and f in code) + + +def collect_script_dirs(manifest): + for reldir, prefix, kind in SCRIPT_DIRS: + absdir = os.path.join(B123D_SRC, reldir) + if not os.path.isdir(absdir): + continue + for fname in sorted(os.listdir(absdir)): + if not fname.endswith(".py") or fname in SKIP_DOCS_SCRIPTS: + continue + code = open(os.path.join(absdir, fname)).read() + code = sanitize(strip_module_docstring(code)) + manifest.append({ + "id": "%s/%s" % (prefix, fname[:-3]), + "source": "%s/%s" % (reldir, fname), + "kind": kind, + "code": code, + # data files the script may open relative to its own location + # (STEP assets, output directories) are symlinked next to the + # script by reference.py + "data_dir": reldir, + # CAD assets the script imports, delivered to the worker by + # run-lite.mjs (the worker has no filesystem) + "assets": script_assets(reldir, code), + }) + + +# -------------------------------------------------------------------------- +# ``code-block::`` snippets embedded in the .rst documentation pages +# -------------------------------------------------------------------------- + +CODE_DIRECTIVE_RE = re.compile( + r"^([ \t]*)\.\.\s+code-block::\s*(build123d|python)\s*$") +RST_OPTION_RE = re.compile(r"^[ \t]*:[A-Za-z0-9_-]+:") + + +def _indent_width(line): + return len(line) - len(line.lstrip()) + + +def extract_rst_blocks(path): + """Yield the dedented source of every python/build123d code-block.""" + lines = open(path).read().split("\n") + i = 0 + while i < len(lines): + m = CODE_DIRECTIVE_RE.match(lines[i]) + if not m: + i += 1 + continue + base_indent = len(m.group(1).expandtabs(8)) + i += 1 + # directive options (:linenos:, :emphasize-lines: 3, ...) and blanks + while i < len(lines) and (lines[i].strip() == "" or + RST_OPTION_RE.match(lines[i])): + if lines[i].strip() != "" and not RST_OPTION_RE.match(lines[i]): + break + i += 1 + body = [] + while i < len(lines): + line = lines[i] + if line.strip() == "": + body.append("") + i += 1 + continue + if _indent_width(line.expandtabs(8)) <= base_indent: + break + body.append(line.expandtabs(8)) + i += 1 + while body and body[-1] == "": + body.pop() + if not body: + continue + strip = min(_indent_width(l) for l in body if l.strip()) + yield "\n".join(l[strip:] if l.strip() else "" for l in body) + + +DOCTEST_RE = re.compile(r"^(>>>|\.\.\.)\s?") + + +def unwrap_doctest(code): + """Turn a >>> doctest transcript into a plain script. + + Interactive-echo lines (the expected repr output) are dropped; statements + keep their order. A block with no >>> prompt is returned unchanged. + """ + lines = code.split("\n") + if not any(l.lstrip().startswith(">>>") for l in lines): + return code + out = [] + in_stmt = False + for line in lines: + stripped = line.lstrip() + if stripped.startswith(">>>"): + out.append(DOCTEST_RE.sub("", stripped)) + in_stmt = True + elif stripped.startswith("..."): + out.append(DOCTEST_RE.sub("", stripped)) + elif stripped == "": + in_stmt = False + out.append("") + elif in_stmt: + # expected output of the previous statement - not code + continue + else: + out.append(line) + return "\n".join(out) + + +NEEDS_HEADER_RE = re.compile(r"^\s*(from|import)\s+build123d\b", re.M) + + +def prepare_snippet(code): + """Sanitize + doctest-unwrap + ensure the build123d import is present.""" + code = unwrap_doctest(code) + code = sanitize(code) + if not NEEDS_HEADER_RE.search(code): + code = "from build123d import *\nfrom math import *\n\n" + code + return code.rstrip("\n") + "\n" + + +def rst_files(): + docs = os.path.join(B123D_SRC, "docs") + for root, dirs, files in os.walk(docs): + dirs[:] = [d for d in sorted(dirs) if d not in ("_static", "assets")] + for fname in sorted(files): + if fname.endswith(".rst"): + yield os.path.join(root, fname) + + +def collect_rst(manifest): + docs = os.path.join(B123D_SRC, "docs") + for path in rst_files(): + rel = os.path.relpath(path, docs) + stem = rel[:-4].replace("/", "-") + blocks = list(extract_rst_blocks(path)) + if not blocks: + continue + for n, block in enumerate(blocks, 1): + manifest.append({ + "id": "docs-rst/%s/b%02d" % (stem, n), + "source": "docs/%s code-block #%d" % (rel, n), + "kind": "docs-rst", + "code": prepare_snippet(block), + }) + if len(blocks) > 1: + # What a reader following the page top-to-bottom actually runs. + joined = "\n\n".join(unwrap_doctest(b) for b in blocks) + manifest.append({ + "id": "docs-rst/%s/all" % stem, + "source": "docs/%s (all %d code-blocks)" % (rel, len(blocks)), + "kind": "docs-rst-page", + "code": prepare_snippet(joined), + }) + + +# -------------------------------------------------------------------------- +# Pruning: keep only candidates that natively produce measurable geometry +# -------------------------------------------------------------------------- + +# Real scripts stay in the corpus even when they fail natively (the lite runner +# reports them as SKIP); prose fragments never were code and are dropped. +ALWAYS_KEEP_KINDS = {"example", "docs-builder", "docs-algebra", "docs-script", + "ttt"} + + +def prune(args): + manifest = json.load(open(args.manifest)) + reference = json.load(open(args.reference)) + + kept, dropped = [], {} + for entry in manifest: + ref = reference.get(entry["id"]) + status = ref["status"] if ref else "missing" + if entry["kind"] in ALWAYS_KEEP_KINDS or status == "ok": + kept.append(entry) + else: + dropped[status] = dropped.get(status, 0) + 1 + + kept_ids = {e["id"] for e in kept} + ref_out = {k: v for k, v in reference.items() if k in kept_ids} + + with open(args.out, "w") as f: + json.dump(kept, f, indent=1) + with open(args.out_reference, "w") as f: + json.dump(ref_out, f, indent=1, sort_keys=True) + + print("kept %d/%d candidates -> %s" % (len(kept), len(manifest), args.out)) + for status, n in sorted(dropped.items(), key=lambda kv: -kv[1]): + print(" dropped %4d non-runnable snippets (native status: %s)" % (n, status)) + scored = sum(1 for e in kept + if reference.get(e["id"], {}).get("status") == "ok") + print(" %d scored, %d excluded (real build123d fails natively)" + % (scored, len(kept) - scored)) + + +def main(): + ap = argparse.ArgumentParser() + sub = ap.add_subparsers(dest="cmd") + ap.add_argument("--out", default=os.path.join(HERE, "manifest-all.json")) + + p = sub.add_parser("prune", help="drop candidates with no native geometry") + p.add_argument("--manifest", default=os.path.join(HERE, "manifest-all.json")) + p.add_argument("--reference", default=os.path.join(HERE, "reference-all.json")) + p.add_argument("--out", default=os.path.join(HERE, "manifest.json")) + p.add_argument("--out-reference", default=os.path.join(HERE, "reference.json")) + args = ap.parse_args() + + if args.cmd == "prune": + return prune(args) + + if not os.path.isdir(B123D_SRC): + sys.exit("build123d clone not found at %s (set B123D_SRC)" % B123D_SRC) + + manifest = [] + collect_examples(manifest) + collect_general(manifest, "general_examples.py", "docs-builder") + collect_general(manifest, "general_examples_algebra.py", "docs-algebra") + collect_script_dirs(manifest) + collect_rst(manifest) + + seen = set() + for entry in manifest: + if entry["id"] in seen: + sys.exit("duplicate manifest id: " + entry["id"]) + seen.add(entry["id"]) + + with open(args.out, "w") as f: + json.dump(manifest, f, indent=1) + counts = {} + for entry in manifest: + counts[entry["kind"]] = counts.get(entry["kind"], 0) + 1 + print("wrote %d candidates to %s" % (len(manifest), args.out)) + for kind, n in sorted(counts.items()): + print(" %-16s %d" % (kind, n)) + + +if __name__ == "__main__": + main() diff --git a/test/b123d-validation/defaults-audit.md b/test/b123d-validation/defaults-audit.md new file mode 100644 index 00000000..2256812e --- /dev/null +++ b/test/b123d-validation/defaults-audit.md @@ -0,0 +1,69 @@ +# Defaults audit (every remaining non-PASS, root-caused) + +Hand-maintained (`defaults-audit.md`, appended to this report by run-lite.mjs). +For each remaining non-PASS script: the root cause, the upstream (build123d +0.11.1 / OCP 7.x) defaults compared against build123d-lite's (OCCT 8.0.1 +wasm), and an honest verdict. Closed-this-round rows are kept where the +investigation itself is the record. + +| Script(s) | Root cause | Upstream defaults vs lite | Verdict | +|---|---|---|---| +| examples/joints, examples/joints_algebra (MISMATCH) | Slider/pin positions are measured along `Axis(edge)` of slot edges selected after booleans; the parts land at the other end of the (geometrically correct) slot. | Both sides map `position_at(u)` orientation-aware (`u -> 1-u` when the edge is not FORWARD): upstream `Mixin1D._occt_param_at`, lite `Edge.position_at`. Verified the underlying curves agree; only the sub-edge TopAbs orientation flag differs (OCP 7.x vs 8.0.1 wasm construction history). | Same defaults, kernel construction-history difference — COMPROMISE(edge-orientation). Canonicalising `Edge.make_mid_way`'s references (the upstream canonical free-edge rule, default-on) shrank pin_arm 8.16 -> 2.69 mm and slider_arm 11.80 -> 9.11 mm; the remainder needs the example itself to select its two TIED top edges with `sort_by(Axis.Z, tie_break=True)`, which is opt-in upstream too. | +| examples/projection, examples/projection_algebra (MISMATCH, `projected_text` only, d=0.12) | The text wraps the *opposite way* around the sphere: the arch path (closed sphere-cylinder intersection edge) is TopAbs_REVERSED in OCP 7.x but FORWARD in 8.0.1 wasm over the SAME geometric parametrization (verified: raw curve at 25% is +Y on both; upstream's flag flips traversal to -Y first, lite's does not). | Everything else now byte-matches: `make_text` align default fixed to `None`, `position_at` switched to arc-length fraction via GCPnts_AbscissaPoint, and per-glyph text faces split disjoint outer contours (i/j dots) into separate faces (40 faces == upstream). | Same defaults, kernel edge-orientation history on a closed intersection curve — COMPROMISE(edge-orientation). Not honestly closable. | +| docs-selectors/sort_axis (MISMATCH, -14.7% volume) | `revolve(face, -Axis(edge), 90)` sweeps the OTHER WAY: the slot edge selected off the extruded solid has raw parametrization `(34,16,4) -> (34,16,0)` in this kernel and `(34,16,0) -> (34,16,4)` in OCP 7.x, so `Axis(edge)` points -Z instead of +Z. The profile face, its edge, its length and midpoint all agree exactly. | `Axis(edge)` is RAW-curve on both sides by design (`canonical=True` is opt-in, exactly as in the upstream patch), so neither side canonicalizes here. | Same defaults, kernel construction-history difference — COMPROMISE(edge-orientation), the same species as joints/projection. Opting the example into `Axis(edge, canonical=True)` would close it, and the canonical rule does give upstream's direction (open edge from the lexicographically smaller end). | +| docs-selectors/filter_all_edges_circle (MISMATCH, `f` only) | The script keeps the loop variable of `for i, f in enumerate(faces)`, i.e. THE LAST of a mirror-symmetric pair of bearing-bore faces at y = ±21. Every other measured shape (including all 53 sorted `faces[i]`) matches. | Not a defaults difference: `part.faces()` is the kernel's face traversal order, and the mirrored pair comes out in the opposite order here. | COMPROMISE(traversal-order). | +| docs-rst/tips/b04 (MISMATCH, bbox 0.2 mm) | `vertices().group_by(Axis.X)[-1].sort_by(Axis.Z)[-1]` inside `BuildSketch(Plane.XZ)`: the sketch's LOCAL vertices all have z = 0, so `sort_by(Axis.Z)` is a COMPLETE TIE between (0.5, ±0.5) and the stable sort hands back whichever the kernel enumerated last. Verified natively: upstream's rectangle enumerates [(0.5, 0.5), (0.5, -0.5)] and picks (0.5, -0.5). | Same default (a plain stable sort; `tie_break=True` is opt-in upstream too, and is exactly the fix the canonical-edges work proposes for this). | COMPROMISE(traversal-order): lite builds its rectangle from a different first corner, so the tie resolves the other way. | +| docs/objects_1d (MISMATCH, `scene` bbox 0.07 mm) | `scene = Compound(...) + Compound.make_triad(2)`. | The triad's axes and spline arrow heads are built exactly; upstream also draws 'X'/'Y'/'Z' with the **`singleline` STROKE font**, which this build does not ship (only the outline font FreeSans). | COMPROMISE(triad-labels). The triad is a viewer symbol; the deviation is confined to scripts that measure it. (`l1`/`l3`/`l4` also differ because the file reuses those names across nine examples and the harness compares the last binding.) | +| examples/cast_bearing_unit (**was ERROR, now PASS**) | The previous verdict — "genuine kernel fault in the 8.0.1 wasm fillet" — was WRONG. `FilletEdges` was aborting the wasm heap because lite's `make_hull` handed it a POLYLINE boundary (hundreds of micro-edges) instead of the trimmed arcs upstream produces. | `make_hull` is now a statement-for-statement port of `Wire.make_convex_hull` (sample -> 2-D hull -> connecting lines + trimmed source edges), so the fillet sees the same topology upstream's does. | Closed: lite bug (a simplified hull), not a kernel fault. The same fix closed docs-rst/tips/b01 (ChamferEdges "internal OCCT error"). | +| examples/toy_truck (ERROR) | `FilletEdges` raises "INTERNAL OPENCASCADE ERROR" (caught, no heap corruption) on the truck's body fillet. Unlike cast_bearing_unit this input has no hull in it. | Upstream `Solid.fillet` = `BRepFilletAPI_MakeFillet(shape)` (default ChFi3d_Rational) + `Add(radius, edge)`; lite is identical (explicit ChFi3d_Rational, same Add). No tolerance/continuity knobs differ. | Same defaults, kernel behaviour on this input. | +| examples/dual_color_3mf (ERROR, geometry closed) | All six measured shapes match the reference exactly; the script fails on its last statement, `Mesher.write("dual_color.3mf")`. | Upstream's `Wire.offset_2d` open-mode branch is ported exactly. | COMPROMISE(mesher): there is no lib3mf in this wasm build, so only STL export exists. | +| ttt/ttt-ppp0110 (ERROR) | The KNOWN 8.0.1 coplanar-BSpline fuse fault, in the one shape where the General-Fuse rebuild cannot recover the dropped operand (result volume 0). | Upstream fuse defaults reproduce the drop on this kernel; see the ex34 row below. | Genuine kernel fault, detected and raised (COMPROMISE(kernel-guard) cannot recover this one). | +| general_examples/ex34, general_examples_algebra/ex34 (PASS since the kernel-guard round) | `BRepAlgoAPI_Fuse` silently DROPS an operand when coplanar faces meet along BSpline edges (glyph solids fused onto a box face); result was the bare box. | Upstream fuse defaults — no fuzzy value, glue off, NonDestructive unset — reproduce the drop identically on this kernel; it is the fuse *result-assembly* phase that is broken, the General-Fuse *split* phase is correct on the same inputs. Lite's `Union` detects the drop and rebuilds from the `BOPAlgo_Builder` partition; see COMPROMISE(kernel-guard). | Genuine kernel fault (8.0.1 wasm), worked around via the exact GF partition. | +| examples/bracelet, examples/bicycle_tire, examples/build123d_logo*, examples/maker_coin (PASS) | Freeform-surface, wrap, Text-normal and `new_edges` rounds — see the git history of this file for the full write-ups; kept here only as the record that they are closed. | — | Closed in earlier rounds. | +| SKIP x10 (python_logo, tea_cup builder, general_examples_algebra/ex10, docs/line_types, docs/constraint_examples, docs/rigid_joints_pipe, docs/rod_end, docs/technical_drawing, docs-objects/text, docs-selectors/sort_distance_from) | Real build123d 0.11.1 fails natively on these: no module-level shapes, or an import/API that 0.11.1 does not have (`bd_warehouse` x3, `ImageFace`, `tcv_screenshots`, `ColorMap`). | n/a | Excluded from scoring by the harness. | + +### Selectors, 1-D solvers and GUI-doc round (this round) + +177 PASS -> **204 PASS**, 34 ERROR -> 9, 11 MISMATCH -> 8, and every script +that passed before still passes. Buckets worked in order: +topology-selection properties, 1-D constrained objects, the +"deliberate but tractable" items, then a triage pass over the MISMATCHes. + +| Script(s) | Root cause | Upstream defaults vs lite | Verdict | +|---|---|---|---| +| docs-selectors/filter_nested, /filter_shape_properties, /filter_all_edges_circle, /group_axis, /group_hole_area, /sort_along_wire, /sort_sortby, /group_properties_with_keys (ERROR x8 -> 7 PASS + 1 traversal-order MISMATCH) | The selector surface the topology-selection docs exercise: `ShapeList.wires`, `Face.is_circular_convex/_concave`, `Face.center_location`/`position_at`, `Mixin1D.normal`, `Edge`/`Wire.param_at_point`, `sort_by()`, `Shape.distance`/`distance_to`/`closest_points`, `GroupBy.group(key)`, iterating a Builder in `add()`, and fillet/chamfer over edges pooled from SEVERAL intermediate shapes. | All ported from 0.11.1. Three defaults had to change to match: `sort_by_distance` sorts by the MINIMAL distance (`distance_to`, BRepExtrema_DistShapeShape) rather than centre distance, `filter_by_position` returns its survivors SORTED along the axis, and `group_by` passes non-numeric keys through unrounded. fillet/chamfer now take their target from the ACTIVE BUILDER like upstream (`target = context._obj`) and map each edge onto it geometrically. | Closed. `Face._curvature_sign` is a substitution, not a behaviour compromise: gp_Cylinder/gp_Sphere/gp_Torus are unbound here, so the reference distance comes from the second fundamental form (`S_dd . N < 0` is exactly `normal . (P - reference) > 0` for these three quadrics) — COMPROMISE(curvature-sign). | +| docs/objects_1d_airfoil, _blend_curve, _bspline, _ellipticalstartarc, _parabolic_hyperbolic, docs-rst/tutorial_constraints/b03, /b05, /b09, /b10 (ERROR x9 -> PASS) | The 1-D CONSTRAINED/analytic objects: `BSpline`, `ParabolicCenterArc`/`HyperbolicCenterArc` (incl. the LIMIT form of `arc_size`), `EllipticalStartArc`, `BlendCurve`, `Airfoil`, `Triangle`, plus `derivative_at`, `curvature_comb`, `Edge.trim` by point, `trim_to_other` and `ArrowHead`. | Each is now the upstream construction on bound OCCT classes: `Geom_BSplineCurve` from poles/knots/multiplicities, `gp_Parab`/`gp_Hypr` trimmed by `GC_MakeArcOf*` (including make_hyperbola's major>=minor swap with the matching angle-range shift), the ellipse frame from the start tangent, and the cubic/quintic Bezier control points from `derivative_at(1)`/`derivative_at(2)`. `Triangle` carries a port of the `trianglesolver` package's law-of-sines/cosines solver. Airfoil's point dedup has to round to GEOM_KEY_DIGITS the way `Vector.__hash__` does — without it the two trailing-edge points differ by 1.8e-17 and OCCT's `BSplCLib::Interpolate` fails on the periodic spline. | Closed. | +| docs/objects_1d_constrained, docs-rst/tutorial_constraints/b13 (ERROR x2 -> PASS) | `ConstrainedArcs` / `ConstrainedLines`: circles and lines constrained by tangency to other geometry, with GccEnt qualifiers, `Sagitta` arc selection and a user `selector`. | Upstream is a thin wrapper over OCCT's 2-D geometric constraint solvers (`Geom2dGcc_Circ2d2TanRad`, `_Circ2d2TanOn`, `_Circ2d3Tan`, `_Circ2dTanCen`, `_Circ2dTanOnRad`, `_Lin2d2Tan`, `_Lin2dTanObl`) plus `Geom2dGcc_QualifiedCurve`. **None of that family exists in this wasm build** — the .d.ts declares them, but the module exposes no such property at runtime, and neither does `GccEnt`. The two cases the docs exercise (circle/point targets) are therefore solved in CLOSED FORM here, with upstream's semantics kept intact: the centre loci are circles of radius `R ± r` per qualifier (OUTSIDE = external contact, ENCLOSING = the solution contains the target, ENCLOSED = the reverse), a solution is rejected when its contact point falls outside the target's TRIMMED range (upstream's `_param_in_trim`), and both arcs between the contact parameters are built so `Sagitta.SHORT/LONG/BOTH` picks the same one. | Closed for the documented cases, 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. The `center=`/`center_on=`/three-tangency/oriented-line overloads still raise, with the reason: their solution SETS feed a user selector, so guessing an enumeration would be guessing the answer. | +| docs/objects_3d (`Wedge`), docs-rst/topology_selection/b12 (`topo_distance_to`), docs-rst/objects-text/b10 (text along a path) (ERROR x3 -> PASS) | Individually missing objects/operations. objects_3d also needed `ConvexPolyhedron`. | `Wedge` is `BRepPrimAPI_MakeWedge`'s min/max form (bound as `_3`), `ConvexPolyhedron` sews the quickhull3d facets, `topo_distance_to` is a 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, and `Text(path=)` places each glyph exactly like `Compound.make_text`'s `position_glyph`. | Closed. | +| docs/spitfire_wing_gordon (was `ImportError: pytest`, now TIMEOUT) | The script asserts `wing.volume / 1e9 == pytest.approx(1.9879945989)`. | Added a REAL `pytest.approx` (documented defaults rel 1e-6 / abs 1e-12, sequences and dicts); every other pytest attribute raises. The script now runs: `Vector(())` is the origin (`0 * (x, y, z)` is how the docs write a conditional offset) and `intersect(Axis)` on a 1-D shape returns the ShapeList of Vertex upstream returns. | Open, measured: the wing's Gordon surface takes **386 s** in this wasm build (the harness budget is 60 s) and then comes back null. The blocker is the cost/robustness of COMPROMISE(gordon-surface-realization) at wing scale, not the missing shim. | +| docs/objects_2d (ERROR, `Draft`) | `Draft` here is **not** the draft-angle operation (lite has had `draft()`/BRepOffsetAPI_DraftAngle for rounds) — it is `drafting.Draft`, the dimension-styling dataclass, and the script goes on to use `ExtensionLine`, `DimensionLine` and `TechnicalDrawing`. | n/a — the blocker is the whole `drafting` module (dimension lines with arrows, extension lines, label text and the drawing frame), 42 measured shapes deep. `ArrowHead`/`HeadType` are now implemented; the rest is not. | Deliberate gap, with the misidentification corrected: no kernel binding is missing here. | +| ttt/ttt-24-SPO-06-Buffer_Stand (ERROR, `full_round`) | `full_round` replaces an edge with the arc of the largest empty circle that fits in the face. | Upstream generates the CANDIDATE centres with `scipy.spatial.Voronoi` (2-D) over 100 samples per edge and then averages the best three candidates — so the result depends on the exact candidate set. Lite's scipy shim raises for 2-D `Voronoi`/`ConvexHull` (qhull is not available; the 3-D hull is served by the bundled quickhull3d). | Deliberate gap, with the reason: it needs a 2-D Voronoi diagram. The honest route is a Delaunay triangulation (circumcentres ARE the Voronoi vertices), which would reproduce the same candidate SET; it is the next numerical method worth adding, not a defaults difference. | +| ttt/ttt-23-02-02-sm_hanger (ERROR, was "no edges given" and un-triaged) | Two real gaps, in order: (1) `fillet(side_line.vertices(), 7)` is the **1-D** corner fillet of an open line (upstream's `Wire.fillet_2d` -> ChFi2d/Geom2dGcc), and (2) the script's shape comes from `make_brake_formed`, sheet-metal brake forming, which lite does not implement at all. | The misleading "no edges given" was itself a lite bug: `Builder.vertices()` read `self._obj`, which for a BuildLine only exists after `__exit__`, so a mid-context `side_line.vertices()` came back empty. The selectors now read the line built so far, and the fillet raises a message naming `Wire.fillet_2d`. | Triaged: two missing features (1-D wire fillet, brake forming), not a selector-result difference. | +| ttt/ttt-23-t-24-curved_support (ERROR, `sympy`) | The part's dimensions are derived with sympy's symbolic solver. | n/a | Deliberate gap: shimming a symbolic algebra system is out of scope. | +| docs/slide_latch (was MISMATCH, now PASS) | The open question — "does 0.11.1 localize `add()` inside a face-workplane BuildSketch?" — is answered: **yes, conditionally.** `BuildSketch._add_to_context` expresses a face that is NOT coplanar with Plane.XY in its own plane's frame and drops it onto z = 0 (keeping the in-plane x/y offset), and then orients EVERY incoming face +Z. | Lite now performs the same two steps in `_combine`. | Closed: lite bug (missing sketch-face alignment). | +| docs/heart_token (was MISMATCH, bbox 2.0 mm, now PASS) | Two lite bugs in one script: `offset(amount=2, kind=Kind.INTERSECTION)` on a SKETCH ran a 3-D `MakeOffsetShape` (thickening the sketch by ±2 in z) instead of upstream's 2-D wire offset, and `mirror(about=Plane.YZ)` inside a BuildSketch left TWO half faces because a mirrored face has a -Z normal and coplanar faces with opposite normals are not the same domain, so they never fused. | `offset()` now offsets the outer wire by +amount and each inner wire by -amount and rebuilds the planar face (upstream's face branch), and the sketch-face alignment above supplies the +Z orientation that lets the halves fuse (1 face, area 200.20972988622623 == upstream). | Closed: two lite bugs. | +| docs-selectors/group_properties_with_keys (was ERROR then MISMATCH, now PASS) | After `Mixin1D.normal` and `GroupBy.group(key)` landed, two deeper differences remained: (1) `copy.copy()` returned the SAME builder, so `before_fillet`/`after_fillet`/`after_holes` all reported the FINAL geometry, and (2) lite built a full `CenterArc` as TWO half arcs, which changed `group_by(Edge.length)` keys and the per-edge sampling of `make_hull` (hull area 490.92205 vs upstream 490.921953, and 11 selected edges instead of 12). | `copy.copy` now shallow-copies the builder like upstream's (later operations rebind `_obj`, so the copy IS the snapshot), and a full circle is ONE closed edge. The hull is now bit-identical (490.921953150644) and the length groups and 12 selected edges match exactly; before_fillet 9751.639 / after_fillet 9730.739 == upstream. | Closed: two lite bugs. | +| docs-selectors/selectors_operators (was MISMATCH, bbox 6.0 mm, now PASS) | `line @ 2/3` parses as `(line @ 2) / 3` — Python's `@` has the same precedence as `/` — so the docs place objects at twice the line's end point divided by three. Lite CLAMPED `position_at` to [0, 1] and returned the end point. | Upstream extrapolates (`param_at`: "positions outside [0, 1] are not validated and yield OCCT-dependent results"); lite now does too. | Closed: lite bug. | +| ttt/ttt-ppp0107 (was MISMATCH, -1.0% / -0.9%, now PASS) | The audit's guess ("two `extrude(until=)` intermediates") was WRONG: `zz`/`zz2` are a TAPERED extrude, `extrude(amount=15, taper=-10)`. Lite always used `LocOpe_DPrism`. | `Solid.extrude_taper` uses TWO algorithms: DPrism only for a POSITIVE taper along the profile normal with no holes, otherwise a LOFT between the profile wires and their 2-D offsets (`-length * tan(taper)`, Kind.INTERSECTION, inner wires flipped). A bare `taper=-10` rectangle now measures 2957.1391331767363 — bit-identical to the reference. | Closed: lite bug (one algorithm instead of two). | +| every raw kernel error, everywhere (infrastructure, earlier round) | Emscripten throws OCCT's C++ exceptions as bare pointer NUMBERS. | The fork binds `OCJS::getStandard_FailureData` for exactly this, but it is UNCALLABLE here ("unbound types: St9exception"). | COMPROMISE(failure-decode): CascadeWorker keeps the wasm `Memory` via Emscripten's `instantiateWasm` hook and StandardUtils reads `Standard_Failure`'s message out of it directly. | + +### OCCT binding round: Geom2dGcc, quadrics, STEP assets, Voronoi, brake forming (this round) + +204 PASS -> **207 PASS**, 9 ERROR -> 6, and the two remaining +`import_step`/`sm_hanger` scripts went ERROR -> MISMATCH. Four of the nine +errors were blocked on the WASM build rather than on lite, so this round +started in the fork: `builds/cascadestudio.yml`, +`src/filter/filterMethodOrProperties.py` and a new hand-registered `OCJS_Out` +helper class (see the fork's CHANGELOG). + +| Script(s) | Root cause | Upstream defaults vs lite | Verdict | +|---|---|---|---| +| docs/objects_1d_constrained, docs-rst/tutorial_constraints/b13 (PASS -> PASS, now on the REAL solvers) | Last round's verdict — "none of the `Geom2dGcc` family exists in this wasm build" — was right about the symptom and wrong about the cause. The classes were in the yml; every binding file in the `Geom2dGcc`/`GccAna` packages failed to COMPILE on one method, `WhichQualifier(Standard_Integer, GccEnt_Position&, GccEnt_Position&)`, whose non-const enum out-params Embind cannot bind (`bind.h:531`). One bad method takes the whole file down, and the build tolerated the failure silently. | The fork now filters any method with a non-const `GccEnt_Position&` parameter (the BSplCLib enum-out-param precedent), so `Geom2dGcc_Circ2d2TanRad`, `_Circ2d2TanOn`, `_Circ2d3Tan`, `_Circ2dTanCen`, `_Circ2dTanOnRad`, `_Lin2d2Tan` and `_Lin2dTanObl` are real here. `ConstrainedArcs`/`ConstrainedLines` are now a statement-for-statement port of build123d's `topology/constrained_lines.py` (kernel side in `StandardLibrary.js`: `ConstrainedArcs2D` / `ConstrainedLines2D`), including `_param_in_trim`, `_enclosed_circ_param_offset` and the Sagitta arc pair. The Tangency parameters come back through `OCJS_Out._Tangency()`, because `Standard_Real&` out-params are passed BY VALUE through Embind. | Closed, and the closed-form stand-in is retired. **All five arc overloads and all three line overloads** were verified against the reference venv on the doc examples (`radius=`, `center_on=`, three-tangency, `center=`, `radius=`+`center_on=`, two-tangent lines, tangent+point, oriented line): worst bbox delta **1.8e-15 mm** over 8 result sets, with identical edge counts. | +| docs-selectors/filter_nested & friends — COMPROMISE(curvature-sign) | `Face.is_circular_convex/_concave` needed the surface's own reference geometry, and `gp_Cylinder`/`gp_Sphere`/`gp_Torus` were unbound, so the sign came from the second fundamental form instead. | The three quadrics are bound now, so `_faceCurvatureSign` reads upstream's own reference (cylinder axis, sphere centre, torus core circle) and dots it against the oriented normal. The second-fundamental-form path is kept as the fallback for kernels without them. | COMPROMISE(curvature-sign) **retired**. | +| Face.normal_at / location_at — COMPROMISE(point-projection) | `GeomAPI_ProjectPointOnSurf` was registered but not constructible: every constructor takes an `Extrema_ExtAlgo`, and the enum was unbound. Lite ran a 24x24 UV grid search refined by Newton. | `Extrema_ExtAlgo`/`Extrema_ExtFlag` are bound, and `LowerDistanceParameters(u&, v&)` is read back through `OCJS_Out`. The grid+Newton search is kept only as a fallback for the cases OCCT reports no solution for. | COMPROMISE(point-projection) **retired**. | +| docs/tutorial_joints, docs-selectors/filter_inner_wire_count (ERROR x2, `import_step`) | Both import a STEP asset from a path next to `__file__`. The CAD worker has no filesystem. | The asset is now delivered ahead of the run instead of being read: `collect.py` records the CAD files a script names, `run-lite.mjs` reads them out of the clone, and `CascadeAPI.loadExternalFiles()` hands them to the worker's existing STEP-import path (MEMFS + `STEPControl_Reader`) and **awaits the import** before evaluating. `import_step` resolves the requested path by base name. | filter_inner_wire_count **PASS** (53 shapes; also needed `Face.radius`, `Face.axis_of_rotation`, `ShapeList.edge()/face()/wire()/vertex()/solid()`, and `Location(position, angles, Intrinsic/Extrinsic order)`). tutorial_joints **MISMATCH on `m6_screw` alone** — the other 7 shapes match to 1e-9; the screw is placed by `CylindricalJoint.relative_to(..., position=5, angle=30)` off `hole2`, and lite's hole-location enumeration puts it on a different hole frame. Joints now survive `Shape.moved` and `Compound(joints=)`, and `Joint.symbol`, `Shape.show_topology` and `Compound.do_children_intersect` are implemented. | +| ttt/ttt-24-SPO-06-Buffer_Stand (ERROR, `full_round`) -> **PASS** | `full_round` picks the largest empty circle from the VORONOI VERTICES of 101 samples per edge over the target edge and its two neighbours, averages the best three, and rebuilds the face. | The scipy shim now has a real 2-D `Voronoi`: a Bowyer-Watson Delaunay whose circumcentres, deduplicated the way qhull's `Qbb Qc` merges cocircular ones, ARE the finite Voronoi vertices. Verified against scipy 1.18 on full_round's own inputs — the vertex SETS are identical (220 and 210 vertices, max deviation 2e-13) and the resulting circle centres agree to 1e-14. `full_round` itself is a statement-for-statement port, including the strict `<` best-three loop. Only `.vertices` is offered; the ridge/region attributes raise. | Closed. The script's own mass assert (3.923 lb ± 0.02) passes. | +| ttt/ttt-23-02-02-sm_hanger (ERROR) -> MISMATCH | Two missing features: the 1-D corner fillet of an OPEN line (`Wire.fillet_2d`) and `make_brake_formed`. | Both ported. `Wire.fillet_2d` maps the wire into its own plane (upstream's `common_plane` + `to_local_coords`), fillets one corner at a time on **`ChFi2d_FilletAlgo`** — upstream's primary solver, now bound in the fork — and splices the arc between the two trimmed edges in connection order, with the Geom2dGcc tangent-arc solver as upstream's fallback. `make_brake_formed` is the upstream algorithm: `offset_2d(thickness, side)` for the section, a station edge per line vertex (the offset vertex exactly `thickness` away), `Face.extrude` by each width along the section plane's normal, and `sweep_multi` between consecutive stations, fused. | The part is now exact where it counts: the filleted `side_line` is 187.2428359925111 mm (bit-identical), the brake-formed side solid is **33201.973161 mm³ / 16 faces** against upstream's 33201.97324 / 16, and the script's own mass assert (1028 g ± 10) passes. The remaining MISMATCH is `l1`/`l2` only — a BuildLine on a non-XY workplane leaves its module-level line variables in LOCAL coordinates in lite, and the harness compares the last binding of a reused name. | +| offset_2d Side.LEFT/RIGHT on a wire that is not parallel to Plane.XY (lite bug found by sm_hanger) | Upstream picks the side with `tangent.get_signed_angle(centre - start)`, a signed angle taken about the FIXED `-Z` reference. For a wire in Plane.XZ the cross product has no `-Z` component, so OCCT's `gp_Vec::AngleWithRef` falls back to the UNSIGNED angle (antiparallel is +180). Python's `atan2` returns **-180** for a negative zero, which flipped every LEFT/RIGHT pick on such wires. | `Vector.get_signed_angle` now returns the unsigned angle when the reference component is negligible, exactly as `AngleWithRef` does. | Closed: lite bug. The tab section then offsets to upstream's side (65.70796326552919 mm, bit-identical). | +| PipeShellSweep profile wires (lite bug found by make_brake_formed) | `PipeShellSweep` rebuilt each profile wire by adding its edges ONE AT A TIME from a `TopExp_Explorer`, which is storage order — `BRepBuilderAPI_MakeWire` silently drops any edge that does not touch the wire built so far. A brake-formed section came out as a 3-face open shell instead of a 6-face solid. | The edges are added as a `TopTools_ListOfShape` so the builder can connect them in any order (the same call `WireFromEdgesFixed` already used). | Closed: lite bug. | +| docs/objects_2d (ERROR, `Draft`) | Unchanged: the `drafting` module. | Scoped-out this round after measuring it: the port is ~450 code lines and its accuracy rides entirely on `Compound.make_text` glyph metrics, since `label_length = Text(...).bounding_box().size.X` feeds every arrow position and the 3-candidate label-placement score in `DimensionLine`. No OCCT binding is missing. | Deliberate gap, now sized. | diff --git a/test/b123d-validation/kernel-orientation-experiment.md b/test/b123d-validation/kernel-orientation-experiment.md new file mode 100644 index 00000000..7868457e --- /dev/null +++ b/test/b123d-validation/kernel-orientation-experiment.md @@ -0,0 +1,67 @@ +# Why "edge orientation" differs between OCP 7.9 and OCCT 8.0.1 wasm + +Controlled experiment (2026-08-13): identical sphere(R10) ∩ cylinder(r5 at +x=+6, axis Z) `BRepAlgoAPI_Section` on both kernels, dumping every section +edge's TopAbs orientation, curve parameter range, and midpoint/derivative. + +| | OCP 7.9.3 (native venv) | OCCT 8.0.1 (CascadeStudio wasm) | +|---|---|---| +| edges | 2, both FORWARD | 2, both FORWARD | +| curve ranges | (0, 1) — normalized approx curves | (0, 2480) / (0, 1318) | +| split (seam) points | near (1, 0, ±9.95) | different points entirely | + +Findings: + +1. On a raw Section, the orientation flag does NOT differ. What changed in + OCCT 8.0 is the intersection-curve construction itself: parametrization + (normalized 0..1 in 7.9 vs knot-count-scale ranges in 8.0.1) and the + choice of where the closed intersection curve is split into edges. +2. The REVERSED-vs-FORWARD difference recorded in defaults-audit.md for the + projection scripts arises in the BRepProj / curve-on-surface path — a + downstream artifact of the same rewritten intersection machinery. +3. Root cause statement: orientation, seam placement, and parametrization of + free section/projection edges are implementation-defined outputs, not API + contracts (orientation is only meaningful relative to a face). Upstream + build123d's `Axis(edge)` / `position_at` on such edges inherits OCP 7.x's + incidental choices. Our geometry is identical; the traversal start/ + direction consumed by those APIs is not. +4. Consequence: a principled "match 7.x" canonicalization is not derivable — + it would mean reverse-engineering incidental internals case by case. The + 4 affected scripts remain COMPROMISE(edge-orientation), with this file as + the mechanism record. + +Repro scripts: the native and wasm dumps are one-liners embedded in the +session history; reconstruct with BRepAlgoAPI_Section + BRep_Tool.Curve as +above (wasm ctor variants: gp_Dir_5(x,y,z), gp_Ax2_4(P,V), +BRepPrimAPI_MakeCylinder_3(ax,r,h), BRepAlgoAPI_Section_3(s1,s2,true)). + +## Addendum: locus identity measurement + +Two-sided discrete Hausdorff distance between 800-point samplings of the +section curves from both kernels: **1.93e-14 in both directions** — machine +epsilon. The intersection loci are geometrically identical; ALL divergence +between kernels is parametric (seam placement, parameter scale, traversal +direction). XOR of the raw curves/shapes would leave zero residual volume; +XOR of the final mismatched scenes shows residual only because scripts +consume the parametrization for *placement* (slot-end axis direction, +position_at from the seam), relocating identical components. + +Implication: canonicalizing seams/orientation in lite would make results +kernel-stable going forward but cannot reproduce OCP 7.x's incidental +choices, so the 4 reference mismatches remain COMPROMISE(edge-orientation). + +## CORRECTION (see REPORT.md in the canonical-edges research record — zalo/build123d +## branch canonical-research, research/ — for the full story) + +The parametrization claim above — "(0,1) native vs (0,2480) wasm" — was an +API artefact, NOT a kernel difference: BRepAlgoAPI_Section defaults +Approximation(false) (degree-1 polyline, knots 0..N-1) while the BOP used +inside Cut/Fuse/Common defaults to approximated 0..1 curves. Native 7.9.3 +produces BOTH forms depending on the call. The kernels agree on the full +13-case battery (two walk-line point-count deltas aside). The REAL leaks are: +(1) seam splits where the surface/surface walk exits a surface's parametric +domain (primitive local frames decide!), (2) build123d's orientation- +insensitive entity dedup making "first face explored" decide FORWARD vs +REVERSED, and (3) ShapeList.sort_by tie order falling back to kernel +traversal order. Mechanism citations and the canonicalization patch live in +the canonical-edges research record (zalo/build123d branch canonical-research, research/). diff --git a/test/b123d-validation/manifest-all.json b/test/b123d-validation/manifest-all.json new file mode 100644 index 00000000..8628f358 --- /dev/null +++ b/test/b123d-validation/manifest-all.json @@ -0,0 +1,2396 @@ +[ + { + "id": "examples/bicycle_tire", + "source": "examples/bicycle_tire.py", + "kind": "example", + "code": "# [Code]\nimport copy\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nwheel_diameter = 740 * MM\n\nwith BuildSketch() as tire_profile:\n with BuildLine() as build_profile:\n l00 = Bezier((0.0, 0.0), (7.05, 0.0), (12.18, 1.54), (15.13, 4.54))\n l01 = Bezier(l00 @ 1, (15.81, 5.22), (15.98, 5.44), (16.5, 6.23))\n l02 = Bezier(l01 @ 1, (18.45, 9.19), (19.61, 13.84), (19.94, 20.06))\n l03 = Bezier(l02 @ 1, (20.1, 23.24), (19.93, 27.48), (19.56, 29.45))\n l04 = Bezier(l03 @ 1, (19.13, 31.69), (18.23, 33.67), (16.91, 35.32))\n l05 = Bezier(l04 @ 1, (16.26, 36.12), (15.57, 36.77), (14.48, 37.58))\n l06 = Bezier(l05 @ 1, (12.77, 38.85), (11.51, 40.28), (10.76, 41.78))\n l07 = Bezier(l06 @ 1, (10.07, 43.16), (10.15, 43.81), (11.03, 43.98))\n l08 = Bezier(l07 @ 1, (11.82, 44.13), (12.15, 44.55), (12.08, 45.33))\n l09 = Bezier(l08 @ 1, (12.01, 46.07), (11.84, 46.43), (11.43, 46.69))\n l10 = Bezier(l09 @ 1, (10.98, 46.97), (10.07, 46.7), (9.47, 46.1))\n l11 = Bezier(l10 @ 1, (9.03, 45.65), (8.88, 45.31), (8.84, 44.65))\n l12 = Bezier(l11 @ 1, (8.78, 43.6), (9.11, 42.26), (9.72, 41.0))\n l13 = Bezier(l12 @ 1, (10.43, 39.54), (11.52, 38.2), (12.78, 37.22))\n l14 = Bezier(l13 @ 1, (15.36, 35.23), (16.58, 33.76), (17.45, 31.62))\n l15 = Bezier(l14 @ 1, (17.91, 30.49), (18.22, 29.27), (18.4, 27.8))\n l16 = Bezier(l15 @ 1, (18.53, 26.78), (18.52, 23.69), (18.37, 22.61))\n l17 = Bezier(l16 @ 1, (17.8, 18.23), (16.15, 14.7), (13.39, 11.94))\n l18 = Bezier(l17 @ 1, (11.89, 10.45), (10.19, 9.31), (8.09, 8.41))\n l19 = Bezier(l18 @ 1, (3.32, 6.35), (0.0, 6.64))\n mirror(about=Plane.YZ)\n make_face()\n\ntire = revolve(Pos(Y=-wheel_diameter / 2) * tire_profile.face(), Axis.X)\n\nwith BuildSketch() as tread_pattern:\n with Locations((1, 1)):\n Trapezoid(15, 12, 60, 120, align=Align.MIN)\n with Locations((1, 8)):\n with GridLocations(0, 5, 1, 2):\n Rectangle(50, 2, mode=Mode.SUBTRACT)\n\n# Define the surface and path that the tread pattern will be wrapped onto\nhalf_road_surface = Face.revolve(Pos(Y=-wheel_diameter / 2) * l00, 360, Axis.X)\ntread_path = half_road_surface.edges().sort_by(Axis.X)[0]\n\n# Wrap the planar tread pattern onto the tire's outside surface\ntread_faces = half_road_surface.wrap_faces(tread_pattern.faces(), tread_path)\n\n# Mirror the faces to the other half of the tire\ntread_faces.extend([mirror(t, Plane.YZ) for t in tread_faces])\n\n# Thicken the tread to become solid nubs\n# tread_prime = [Solid.thicken(f, 3 * MM) for f in tread_faces]\ntread_prime = [thicken(f, 3 * MM) for f in tread_faces]\n\n# Copy the nubs around the whole tire\ntread = [Rot(X=r) * copy.copy(t) for t in tread_prime for r in range(0, 360, 2)]\n\nshow(tire, tread)\n# [End]\n" + }, + { + "id": "examples/boxes_on_faces", + "source": "examples/boxes_on_faces.py", + "kind": "example", + "code": "# [Imports]\nimport build123d as bd\n# [removed by collect.py] from ocp_vscode import *\n\n# [Code]\nwith bd.BuildPart() as bp:\n bd.Box(3, 3, 3)\n with bd.BuildSketch(*bp.faces()):\n bd.Rectangle(1, 2, rotation=45)\n bd.extrude(amount=0.1)\n\nassert abs(bp.part.volume - (3**3 + 6 * (1 * 2 * 0.1)) < 1e-3)\n\nif \"show_object\" in locals():\n show_object(bp.part.wrapped, name=\"box on faces\")\n# [End]" + }, + { + "id": "examples/boxes_on_faces_algebra", + "source": "examples/boxes_on_faces_algebra.py", + "kind": "example", + "code": "# license see [build123d_license](../LICENSE)\n# [Imports]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n# [Code]\nb = Box(3, 3, 3)\nb2 = Rot(0, 0, 45) * extrude(Rectangle(1, 2), 0.1)\nfor plane in [Plane(f) for f in b.faces()]:\n b += plane * b2\n\nif \"show_object\" in locals():\n show_object(b, name=\"box on faces\")\n# [End]" + }, + { + "id": "examples/bracelet", + "source": "examples/bracelet.py", + "kind": "example", + "code": "# [Code]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n# Define input parameters\n# - radii: ellipse radii (X, Y) controlling the bracelet centerline shape\n# - width: bracelet width (along Z for the center sweep)\n# - thickness: bracelet thickness (radial thickness of the cross section)\n# - opening_angle: the missing angle that creates the wrist opening\n# - label_str: optional text to emboss on the outside surface\n# - Define input parameters\n# radii, width, thickness, opening_angle, label_str = (45, 30), 25, 5, 80, \"build123d\"\nradii, width, thickness, opening_angle, label_str = (45, 30), 25, 5, 80, \"\"\n\n# Step 1: Create an elliptical arc defining the *centerline* of the bracelet.\n# The arc is truncated to leave an opening (the \"gap\" where the bracelet goes on).\n# Angles are in degrees; 270\u00b0 points downward, which keeps the opening centered at the bottom.\ncenter_arc = EllipticalCenterArc(\n (0, 0), *radii, 270 + opening_angle / 2, arc_size=360 - opening_angle\n)\n\n# Step 2: Create HALF of the end cross-section, positioned at the end of the arc.\n# We build only half so we can later mirror it to enforce symmetry and reduce\n# curve-network complexity when building the freeform tip.\n#\n# location_at(1) returns a local coordinate frame at the arc end (tangent-aware).\n# x_dir is chosen so the section\u2019s local \"X\" is well-defined and stable.\nend_center_arc = center_arc.location_at(1, x_dir=(0, 0, 1))\nhalf_x_section = EllipticalCenterArc(\n (0, 0), width / 2, thickness / 2, 90, arc_size=180\n).locate(end_center_arc)\n\n# Step 3: Create a doubly-curved \"tip edge\" curve.\n# The tip edge must live in 3D and conform to the outside of the bracelet.\n# To do that, we:\n# 1) create a surface by extruding the center_arc into a sheet (a ribbon surface)\n# 2) build a planar arc in a local frame at the end of that surface\n# 3) project the planar arc onto the curved surface to get a true 3D curve\n#\n# The resulting tip_arc is a 3D edge that naturally matches the bracelet curvature.\ncenter_surface = -Face.extrude(center_arc, (0, 0, 2 * width)).moved(\n Location((0, 0, -width), (0, 0, 180))\n)\ntip_center_loc = -center_surface.location_at(center_arc @ 1, x_dir=(1, 0, 0))\nnormal_at_tip_center = tip_center_loc.z_axis.direction\n\n# A planar arc that would represent the outer boundary of the tip *if* the surface\n# were flat. We immediately project it to make it truly conformal in 3D.\nplanar_tip_arc = CenterArc((0, 0), width / 2, 270, 180).locate(tip_center_loc).edge()\ntip_arc = planar_tip_arc.project_to_shape(center_surface, -normal_at_tip_center)[0]\n\n# Step 4: Build the tip as a Gordon surface (a surface fit through a curve network).\n# Gordon surfaces are ideal when:\n# - you don\u2019t have an obvious analytic surface\n# - curvature changes in two directions (doubly-curved \"cap\")\n# - you can define a consistent set of profile curves + guide curves\n#\n# Here:\n# - profiles define \"across the tip\" shape (section -> bulged spline -> mirrored section)\n# - guides define \"along the tip\" rails (start point -> projected 3D arc -> end point)\n#\n# Tangents are used to encourage smoothness where the tip joins the swept center section.\nprofile = Spline(\n half_x_section @ 0,\n tip_arc @ 0.5,\n half_x_section @ 1,\n tangents=(center_arc % 1, -(center_arc % 1)),\n)\ntip_surface = Face.make_gordon_surface(\n profiles=[half_x_section, profile, half_x_section.mirror(Plane.XY)],\n guides=[half_x_section @ 0, tip_arc, half_x_section @ 1],\n)\n\n# Step 5: Close the tip surface into a watertight Solid.\n# tip_surface is the outer \"skin\"; we create a side face from its boundary wire\n# and make a shell, then a solid.\ntip_side = Face(tip_surface.wire())\ntip = Solid(Shell([tip_side, tip_surface]))\n\n# Step 6: Sweep the *flat end face* of the tip around the center arc.\n# This is the trick that makes the center section compatible with the freeform tip:\n# the sweep profile is the same face that bounds the tip, so the join is naturally aligned.\ncenter_section = sweep(tip_side, center_arc).solid()\n\n# Step 7: Assemble the bracelet from the center and two mirrored tips.\n# Mirror across YZ to create the opposite end cap.\nbracelet = Solid() + [tip, center_section, tip.mirror(Plane.YZ)]\n\n# Step 8: Add an embossed label.\n# This is often the hardest operation for OCCT in this model:\n# projecting text onto a doubly-curved surface can create many small faces/edges,\n# and thickening them adds even more boolean complexity.\nif label_str:\n label = Text(label_str, font_size=width * 0.8, align=Align.CENTER)\n\n # Project the text onto the bracelet using a path-based placement along center_arc.\n # The parameter offsets the label so it sits centered along arc-length.\n p_labels = bracelet.project_faces(\n label, center_arc, 0.5 - 0.5 * (label.bounding_box().size.X) / center_arc.length\n )\n # Turn the projected faces into solids via thickening (embossing).\n embossed_label = [Solid.thicken(f, 0.5) for f in p_labels.faces()]\n bracelet += embossed_label\n\n# Step 9: Add alignment holes to aid assembly after 3D printing in two halves.\n# These are placed at evenly spaced locations along the arc (including both ends).\n# A small clearance (+0.15) is included for typical FDM tolerances.\nalignment_holes = [\n Pos(p) * Cylinder(1.75 / 2 + 0.15, 8)\n for p in [center_arc.position_at(i / 4) for i in range(5)]\n]\nbracelet -= alignment_holes\n\nshow(bracelet)\n# [End]\n" + }, + { + "id": "examples/build123d_customizable_logo", + "source": "examples/build123d_customizable_logo.py", + "kind": "example", + "code": "# [Imports]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n# [Parameters]\n# - none\n\n# [Code]\nwith BuildSketch() as logo_text:\n Text(\"123d\", font_size=10, align=(Align.MIN, Align.MIN))\n font_height = logo_text.vertices().sort_by(Axis.Y)[-1].Y\n\nwith BuildSketch() as build_text:\n Text(\"build\", font_size=5, align=(Align.CENTER, Align.CENTER))\n build_bb = bounding_box(build_text.sketch, mode=Mode.PRIVATE)\n build_vertices = build_bb.vertices().sort_by(Axis.X)\n build_width = build_vertices[-1].X - build_vertices[0].X\n\nwith BuildSketch() as cust_text:\n Text(\n \"customizable\",\n font_size=2.9,\n align=(Align.CENTER, Align.CENTER),\n font_style=FontStyle.BOLD,\n )\n cust_bb = cust_text.sketch.bounding_box()\n cust_width = cust_bb.size.X\n\nwith BuildLine() as one:\n l1 = Line((font_height * 0.3, 0), (font_height * 0.3, font_height))\n TangentArc(l1 @ 1, (0, font_height * 0.7), tangent=(l1 % 1) * -1)\n\nwith BuildSketch() as two:\n with Locations((font_height * 0.35, 0)):\n Text(\"2\", font_size=10, align=(Align.MIN, Align.MIN))\n\nwith BuildPart() as three_d:\n with BuildSketch(Plane((font_height * 1.1, 0))):\n Text(\"3d\", font_size=10, align=(Align.MIN, Align.MIN))\n extrude(amount=font_height * 0.3)\n logo_width = three_d.vertices().sort_by(Axis.X)[-1].X\n\nwith BuildLine() as arrow_left:\n t1 = TangentArc((0, 0), (1, 0.75), tangent=(1, 0))\n mirror(t1, Plane.XZ)\n\next_line_length = font_height * 0.5\ndim_line_length = (logo_width - build_width - 2 * font_height * 0.05) / 2\nwith BuildLine() as extension_lines:\n l1 = Line((0, -font_height * 0.1), (0, -ext_line_length - font_height * 0.1))\n l2 = Line(\n (logo_width, -font_height * 0.1),\n (logo_width, -ext_line_length - font_height * 0.1),\n )\n with Locations(l1 @ 0.5):\n add(arrow_left.line)\n with Locations(l2 @ 0.5):\n add(arrow_left.line, rotation=180.0)\n Line(l1 @ 0.5, l1 @ 0.5 + Vector(dim_line_length, 0))\n Line(l2 @ 0.5, l2 @ 0.5 - Vector(dim_line_length, 0))\n\n# Precisely center the build Faces\nwith BuildSketch() as build:\n with Locations(\n (l1 @ 0.5 + l2 @ 0.5) / 2\n - Vector((build_vertices[-1].X + build_vertices[0].X) / 2, 0)\n ):\n add(build_text.sketch)\n with Locations((logo_width / 2, -6)):\n add(cust_text.sketch)\n\ncmpd = Compound(\n [three_d.part, two.sketch, one.line, build.sketch, extension_lines.line]\n)\n\nvisible, _hidden = cmpd.project_to_viewport((10, -10, 60))\nmax_dimension = max(*Compound(children=visible).bounding_box().size)\nexporter = ExportSVG(scale=100 / max_dimension)\nexporter.add_shape(visible)\nexporter.write(f\"cmpd.svg\")\n\nshow_object(cmpd, name=\"compound\")\n# show_object(one.line.wrapped, name=\"one\")\n# show_object(two.sketch.wrapped, name=\"two\")\n# show_object(three_d.part.wrapped, name=\"three_d\")\n# show_object(extension_lines.line.wrapped, name=\"extension_lines\")\n# show_object(build.sketch.wrapped, name=\"build\")\n\n# [End]\n" + }, + { + "id": "examples/build123d_customizable_logo_algebra", + "source": "examples/build123d_customizable_logo_algebra.py", + "kind": "example", + "code": "# [Imports]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n# [Parameters]\n\n# [Code]\nlogo_text = Text(\"123d\", font_size=10, align=Align.MIN)\nfont_height = logo_text.vertices().sort_by(Axis.Y)[-1].Y\n\nbuild_text = Text(\"build\", font_size=5, align=Align.CENTER)\nbuild_bb = build_text.bounding_box()\nbuild_width = build_bb.max.X - build_bb.min.X\n\ncust_text = Text(\n \"customizable\",\n font_size=2.9,\n align=Align.CENTER,\n font_style=FontStyle.BOLD,\n)\ncust_bb = cust_text.bounding_box()\ncust_width = cust_bb.max.X - cust_bb.min.X\n\nl1 = Line((font_height * 0.3, 0), (font_height * 0.3, font_height))\none = l1 + TangentArc(l1 @ 1, (0, font_height * 0.7), tangent=(l1 % 1) * -1)\n\ntwo = Pos(font_height * 0.35, 0) * Text(\"2\", font_size=10, align=Align.MIN)\n\nthree_d = Text(\"3d\", font_size=10, align=Align.MIN)\nthree_d = Pos(font_height * 1.1, 0) * extrude(three_d, amount=font_height * 0.3)\nlogo_width = three_d.vertices().sort_by(Axis.X)[-1].X\n\nt1 = TangentArc((0, 0), (1, 0.75), tangent=(1, 0))\narrow_left = t1 + mirror(t1, Plane.XZ)\n\next_line_length = font_height * 0.5\ndim_line_length = (logo_width - build_width - 2 * font_height * 0.05) / 2\n\nl1 = Line((0, -font_height * 0.1), (0, -ext_line_length - font_height * 0.1))\nl2 = Line(\n (logo_width, -font_height * 0.1),\n (logo_width, -ext_line_length - font_height * 0.1),\n)\nextension_lines = Curve() + (l1 + l2)\nextension_lines += Pos(*(l1 @ 0.5)) * arrow_left\nextension_lines += (Pos(*(l2 @ 0.5)) * Rot(Z=180)) * arrow_left\nextension_lines += Line(l1 @ 0.5, l1 @ 0.5 + Vector(dim_line_length, 0))\nextension_lines += Line(l2 @ 0.5, l2 @ 0.5 - Vector(dim_line_length, 0))\n\n# Precisely center the build Faces\np1 = Pos((l1 @ 0.5 + l2 @ 0.5) / 2 - Vector((build_bb.max.X + build_bb.min.X) / 2, 0))\nbuild = p1 * build_text\n\n# add the customizable text to the build text sketch\np2 = Pos((l1 @ 1 + l2 @ 1) / 2 - Vector(cust_bb.max.X + cust_bb.min.X, 1.4))\nbuild += p2 * cust_text\n\ncmpd = Compound([three_d, two, one, build, extension_lines])\n\nif \"show_object\" in locals():\n show_object(cmpd, name=\"compound\")\n # show_object(one.line.wrapped, name=\"one\")\n # show_object(two.sketch.wrapped, name=\"two\")\n # show_object(three_d.part.wrapped, name=\"three_d\")\n # show_object(extension_lines.line.wrapped, name=\"extension_lines\")\n # show_object(build.sketch.wrapped, name=\"build\")\n# [End]\n" + }, + { + "id": "examples/build123d_logo", + "source": "examples/build123d_logo.py", + "kind": "example", + "code": "# [Imports]\nfrom build123d import *\nfrom build123d import Shape\n# [removed by collect.py] from ocp_vscode import *\n\n# [Parameters]\n# - none\n\n# [Code]\nwith BuildSketch() as logo_text:\n Text(\"123d\", font_size=10, align=(Align.MIN, Align.MIN))\n font_height = logo_text.vertices().sort_by(Axis.Y)[-1].Y\n\nwith BuildSketch() as build_text:\n Text(\"build\", font_size=5, align=(Align.CENTER, Align.CENTER))\n build_bb = bounding_box(build_text.sketch, mode=Mode.PRIVATE)\n build_vertices = build_bb.vertices().sort_by(Axis.X)\n build_width = build_vertices[-1].X - build_vertices[0].X\n\nwith BuildLine() as one:\n l1 = Line((font_height * 0.3, 0), (font_height * 0.3, font_height))\n TangentArc(l1 @ 1, (0, font_height * 0.7), tangent=(l1 % 1) * -1)\n\nwith BuildSketch() as two:\n with Locations((font_height * 0.35, 0)):\n Text(\"2\", font_size=10, align=(Align.MIN, Align.MIN))\n\nwith BuildPart() as three_d:\n with BuildSketch(Plane((font_height * 1.1, 0))):\n Text(\"3d\", font_size=10, align=(Align.MIN, Align.MIN))\n extrude(amount=font_height * 0.3)\n logo_width = three_d.vertices().sort_by(Axis.X)[-1].X\n\nwith BuildLine() as arrow_left:\n t1 = TangentArc((0, 0), (1, 0.75), tangent=(1, 0))\n mirror(t1, Plane.XZ)\n\next_line_length = font_height * 0.5\ndim_line_length = (logo_width - build_width - 2 * font_height * 0.05) / 2\nwith BuildLine() as extension_lines:\n l1 = Line((0, -font_height * 0.1), (0, -ext_line_length - font_height * 0.1))\n l2 = Line(\n (logo_width, -font_height * 0.1),\n (logo_width, -ext_line_length - font_height * 0.1),\n )\n with Locations(l1 @ 0.5):\n add(arrow_left.line)\n with Locations(l2 @ 0.5):\n add(arrow_left.line, rotation=180.0)\n Line(l1 @ 0.5, l1 @ 0.5 + Vector(dim_line_length, 0))\n Line(l2 @ 0.5, l2 @ 0.5 - Vector(dim_line_length, 0))\n\n# Precisely center the build Faces\nwith BuildSketch() as build:\n with Locations(\n (l1 @ 0.5 + l2 @ 0.5) / 2\n - Vector((build_vertices[-1].X + build_vertices[0].X) / 2, 0)\n ):\n add(build_text.sketch)\n\n\nif True:\n logo = Compound(\n children=[\n one.line,\n two.sketch,\n three_d.part,\n extension_lines.line,\n build.sketch,\n ]\n )\n\n # logo.export_step(\"logo.step\")\n def add_svg_shape(svg: ExportSVG, shape: Shape, color: tuple[float, float, float]):\n global counter\n try:\n counter += 1\n except:\n counter = 1\n\n visible, _hidden = shape.project_to_viewport(\n (-5, 1, 10), viewport_up=(0, 1, 0), look_at=(0, 0, 0)\n )\n if color is not None:\n svg.add_layer(str(counter), fill_color=color, line_weight=1)\n else:\n svg.add_layer(str(counter), line_weight=1)\n svg.add_shape(visible, layer=str(counter))\n\n svg = ExportSVG(scale=20)\n add_svg_shape(svg, logo, None)\n # add_svg_shape(svg, Compound(children=[one.line, extension_lines.line]), None)\n # add_svg_shape(svg, Compound(children=[two.sketch, build.sketch]), (170, 204, 255))\n # add_svg_shape(svg, three_d.part, (85, 153, 255))\n svg.write(\"logo.svg\")\n\nshow_object(one, name=\"one\")\nshow_object(two, name=\"two\")\nshow_object(three_d, name=\"three_d\")\nshow_object(extension_lines, name=\"extension_lines\")\nshow_object(build, name=\"build\")\n# [End]" + }, + { + "id": "examples/build123d_logo_algebra", + "source": "examples/build123d_logo_algebra.py", + "kind": "example", + "code": "# [Imports]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n# [Parameters]\n# - none\n\n# [Code]\nlogo_text = Text(\"123d\", font_size=10, align=Align.MIN)\nfont_height = logo_text.vertices().sort_by(Axis.Y).last.Y\n\nbuild_text = Text(\"build\", font_size=5, align=Align.CENTER)\nbuild_bb = build_text.bounding_box()\nbuild_width = build_bb.max.X - build_bb.min.X\n\nl1 = Line((font_height * 0.3, 0), (font_height * 0.3, font_height))\none = l1 + TangentArc(l1 @ 1, (0, font_height * 0.7), tangent=(l1 % 1) * -1)\n\ntwo = Pos(font_height * 0.35, 0) * Text(\"2\", font_size=10, align=Align.MIN)\n\nthree_d = Text(\"3d\", font_size=10, align=Align.MIN)\nthree_d = Pos(font_height * 1.1, 0) * extrude(three_d, amount=font_height * 0.3)\nlogo_width = three_d.vertices().sort_by(Axis.X).last.X\n\nt1 = TangentArc((0, 0), (1, 0.75), tangent=(1, 0))\narrow_left = t1 + mirror(t1, Plane.XZ)\n\next_line_length = font_height * 0.5\ndim_line_length = (logo_width - build_width - 2 * font_height * 0.05) / 2\n\nl1 = Line((0, -font_height * 0.1), (0, -ext_line_length - font_height * 0.1))\nl2 = Line(\n (logo_width, -font_height * 0.1),\n (logo_width, -ext_line_length - font_height * 0.1),\n)\nextension_lines = Curve() + (l1 + l2)\nextension_lines += Pos(*(l1 @ 0.5)) * arrow_left\nextension_lines += (Pos(*(l2 @ 0.5)) * Rot(Z=180)) * arrow_left\nextension_lines += Line(l1 @ 0.5, l1 @ 0.5 + Vector(dim_line_length, 0))\nextension_lines += Line(l2 @ 0.5, l2 @ 0.5 - Vector(dim_line_length, 0))\n\n# Precisely center the build Faces\np1 = Pos((l1 @ 0.5 + l2 @ 0.5) / 2 - Vector((build_bb.max.X + build_bb.min.X) / 2, 0))\nbuild = p1 * build_text\n\ncmpd = Compound([three_d, two, one, build, extension_lines])\n\nshow_object(cmpd, name=\"compound\")\n\n# [End]\n" + }, + { + "id": "examples/canadian_flag", + "source": "examples/canadian_flag.py", + "kind": "example", + "code": "# [Imports]\nfrom math import sin, cos, pi\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show_object, show, show_all\n\n# [Parameters]\n# Canadian Flags have a 2:1 aspect ratio\nheight = 50\nwidth = 2 * height\nwave_amplitude = 3\n\n# [Code]\n\n\ndef surface(amplitude, u, v):\n \"\"\"Calculate the surface displacement of the flag at a given position\"\"\"\n return v * amplitude / 20 * cos(3.5 * pi * u) + amplitude / 10 * v * sin(\n 1.1 * pi * v\n )\n\n\n# Note that the surface to project on must be a little larger than the faces\n# being projected onto it to create valid projected faces\nthe_wind = Face.make_surface_from_array_of_points(\n [\n [\n Vector(\n width * (v * 1.1 / 40 - 0.05),\n height * (u * 1.2 / 40 - 0.1),\n height * surface(wave_amplitude, u / 40, v / 40) / 2,\n )\n for u in range(41)\n ]\n for v in range(41)\n ]\n)\nwith BuildSketch(Plane.XY.offset(10)) as west_field_builder:\n Rectangle(width / 4, height, align=(Align.MIN, Align.MIN))\nwest_field_planar = west_field_builder.sketch.faces()[0]\neast_field_planar = west_field_planar.mirror(Plane.YZ.offset(width / 2))\n\nwith BuildSketch(Plane((width / 2, 0, 10))) as center_field_builder:\n Rectangle(width / 2, height, align=(Align.CENTER, Align.MIN))\n with BuildLine() as outline:\n l1 = Polyline((0.0000, 0.0771), (0.0187, 0.0771), (0.0094, 0.2569))\n l2 = Polyline((0.0325, 0.2773), (0.2115, 0.2458), (0.1873, 0.3125))\n RadiusArc(l1 @ 1, l2 @ 0, 0.0271)\n l3 = Polyline((0.1915, 0.3277), (0.3875, 0.4865), (0.3433, 0.5071))\n TangentArc(l2 @ 1, l3 @ 0, tangent=l2 % 1)\n l4 = Polyline((0.3362, 0.5235), (0.375, 0.6427), (0.2621, 0.6188))\n SagittaArc(l3 @ 1, l4 @ 0, 0.003)\n l5 = Polyline((0.2469, 0.6267), (0.225, 0.6781), (0.1369, 0.5835))\n ThreePointArc(l4 @ 1, (l4 @ 1 + l5 @ 0) * 0.5 + Vector(-0.002, -0.002), l5 @ 0)\n l6 = Polyline((0.1138, 0.5954), (0.1562, 0.8146), (0.0881, 0.7752))\n Spline(\n l5 @ 1,\n l6 @ 0,\n tangents=(l5 % 1, l6 % 0),\n tangent_scalars=(2, 2),\n )\n l7 = Line((0.0692, 0.7808), (0.0000, 0.9167))\n TangentArc(l6 @ 1, l7 @ 0, tangent=l6 % 1)\n mirror(about=Plane.YZ)\n scale(by=height)\n maple_leaf_planar = make_face(mode=Mode.SUBTRACT).face()\n\nmaple_leaf_planar.position += (width / 2, 0, 10) # Created on local Plane.XY\ncenter_field_planar = center_field_builder.sketch.faces()[0]\n\nwest_field = west_field_planar.project_to_shape(the_wind, (0, 0, -1))[0]\nwest_field.color = Color(\"red\")\neast_field = east_field_planar.project_to_shape(the_wind, (0, 0, -1))[0]\neast_field.color = Color(\"red\")\ncenter_field = center_field_planar.project_to_shape(the_wind, (0, 0, -1))[0]\ncenter_field.color = Color(\"white\")\nmaple_leaf = maple_leaf_planar.project_to_shape(the_wind, (0, 0, -1))[0]\nmaple_leaf.color = Color(\"red\")\n\ncanadian_flag = Compound(children=[west_field, east_field, center_field, maple_leaf])\nshow(Rot(90, 0, 0) * canadian_flag)\n# [End]\n" + }, + { + "id": "examples/canadian_flag_algebra", + "source": "examples/canadian_flag_algebra.py", + "kind": "example", + "code": "# [Imports]\nfrom math import sin, cos, pi\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n# [Parameters]\n# Canadian Flags have a 2:1 aspect ratio\nheight = 50\nwidth = 2 * height\nwave_amplitude = 3\n\n\n# [Code]\ndef surface(amplitude, u, v):\n \"\"\"Calculate the surface displacement of the flag at a given position\"\"\"\n return v * amplitude / 20 * cos(3.5 * pi * u) + amplitude / 10 * v * sin(\n 1.1 * pi * v\n )\n\n\n# Note that the surface to project on must be a little larger than the faces\n# being projected onto it to create valid projected faces\nthe_wind = Face.make_surface_from_array_of_points(\n [\n [\n Vector(\n width * (v * 1.1 / 40 - 0.05),\n height * (u * 1.2 / 40 - 0.1),\n height * surface(wave_amplitude, u / 40, v / 40) / 2,\n )\n for u in range(41)\n ]\n for v in range(41)\n ]\n)\n\nfield_planar = Plane.XY.offset(10) * Rectangle(width / 4, height, align=Align.MIN)\nwest_field_planar = field_planar.faces()[0]\neast_field_planar = mirror(west_field_planar, Plane.YZ.offset(width / 2))\n\nl1 = Polyline((0.0000, 0.0771), (0.0187, 0.0771), (0.0094, 0.2569))\nl2 = Polyline((0.0325, 0.2773), (0.2115, 0.2458), (0.1873, 0.3125))\nr1 = RadiusArc(l1 @ 1, l2 @ 0, 0.0271)\nl3 = Polyline((0.1915, 0.3277), (0.3875, 0.4865), (0.3433, 0.5071))\nr2 = TangentArc(l2 @ 1, l3 @ 0, tangent=l2 % 1)\nl4 = Polyline((0.3362, 0.5235), (0.375, 0.6427), (0.2621, 0.6188))\nr3 = SagittaArc(l3 @ 1, l4 @ 0, 0.003)\nl5 = Polyline((0.2469, 0.6267), (0.225, 0.6781), (0.1369, 0.5835))\nr4 = ThreePointArc(l4 @ 1, (l4 @ 1 + l5 @ 0) * 0.5 + Vector(-0.002, -0.002), l5 @ 0)\nl6 = Polyline((0.1138, 0.5954), (0.1562, 0.8146), (0.0881, 0.7752))\ns = Spline(\n l5 @ 1,\n l6 @ 0,\n tangents=(l5 % 1, l6 % 0),\n tangent_scalars=(2, 2),\n)\nl7 = Line((0.0692, 0.7808), (0.0000, 0.9167))\nr5 = TangentArc(l6 @ 1, l7 @ 0, tangent=l6 % 1)\n\noutline = l1 + [l2, r1, l3, r2, l4, r3, l5, r4, l6, s, l7, r5]\noutline += mirror(outline, Plane.YZ)\n\nmaple_leaf_planar = make_face(outline)\n\ncenter_field_planar = (\n Rectangle(1, 1, align=(Align.CENTER, Align.MIN)) - maple_leaf_planar\n)\n\n\ndef scale_move(obj):\n return Plane((width / 2, 0, 10)) * scale(obj, height)\n\n\ndef project(obj):\n return obj.faces()[0].project_to_shape(the_wind, (0, 0, -1))[0]\n\n\nmaple_leaf_planar = scale_move(maple_leaf_planar)\ncenter_field_planar = scale_move(center_field_planar)\n\nwest_field = project(west_field_planar)\nwest_field.color = Color(\"red\")\neast_field = project(east_field_planar)\neast_field.color = Color(\"red\")\ncenter_field = project(center_field_planar)\ncenter_field.color = Color(\"white\")\nmaple_leaf = project(maple_leaf_planar)\nmaple_leaf.color = Color(\"red\")\n\ncanadian_flag = Compound(children=[west_field, east_field, center_field, maple_leaf])\nshow(Rot(90, 0, 0) * canadian_flag)\n# [End]\n" + }, + { + "id": "examples/cast_bearing_unit", + "source": "examples/cast_bearing_unit.py", + "kind": "example", + "code": "# [Code]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nA, A1, Db2, H, J = 26, 11, 57, 98.5, 76.5\nwith BuildPart() as oval_flanged_bearing_unit:\n with BuildSketch() as plan:\n housing = Circle(Db2 / 2)\n with GridLocations(J, 0, 2, 1) as bolt_centers:\n Circle((H - J) / 2)\n make_hull()\n extrude(amount=A1)\n extrude(housing, amount=A)\n drafted_faces = oval_flanged_bearing_unit.faces().filter_by(Axis.Z, reverse=True)\n draft(drafted_faces, Plane.XY, 4)\n fillet(oval_flanged_bearing_unit.edges(), 1)\n with Locations(oval_flanged_bearing_unit.faces().sort_by(Axis.Z)[-1]):\n CounterBoreHole(14 / 2, 47 / 2, 14)\n with Locations(*bolt_centers):\n Hole(5)\n\noval_flanged_bearing_unit.part.color = Color(0x4C6377)\n\nshow(oval_flanged_bearing_unit)\n# [End]\n" + }, + { + "id": "examples/circuit_board", + "source": "examples/circuit_board.py", + "kind": "example", + "code": "# [Imports]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n# [Parameters]\npcb_length = 70 * MM\npcb_width = 30 * MM\npcb_height = 3 * MM\n\n# [Code]\nwith BuildPart() as pcb:\n with BuildSketch():\n Rectangle(pcb_length, pcb_width)\n\n for i in range(65 // 5):\n x = i * 5 - 30\n with Locations((x, -15), (x, -10), (x, 10), (x, 15)):\n Circle(1, mode=Mode.SUBTRACT)\n for i in range(30 // 5 - 1):\n y = i * 5 - 10\n with Locations((30, y), (35, y)):\n Circle(1, mode=Mode.SUBTRACT)\n with GridLocations(60, 20, 2, 2):\n Circle(2, mode=Mode.SUBTRACT)\n extrude(amount=pcb_height)\n\nshow_object(pcb.part.wrapped)\n# [End]" + }, + { + "id": "examples/circuit_board_algebra", + "source": "examples/circuit_board_algebra.py", + "kind": "example", + "code": "# [Imports]\nfrom itertools import product\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n# [Parameters]\npcb_length = 70 * MM\npcb_width = 30 * MM\npcb_height = 3 * MM\n\n# [Code]\nx_coords = product(range(65 // 5), (-15, -10, 10, 15))\ny_coords = product((30, 35), range(30 // 5 - 1))\n\npcb = Rectangle(pcb_length, pcb_width)\npcb -= [Pos(i * 5 - 30, y) * Circle(1) for i, y in x_coords]\npcb -= [Pos(x, i * 5 - 10) * Circle(1) for x, i in y_coords]\npcb -= [loc * Circle(2) for loc in GridLocations(60, 20, 2, 2)]\n\npcb = extrude(pcb, pcb_height)\n\nshow(pcb)\n# [End]" + }, + { + "id": "examples/clock", + "source": "examples/clock.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nclock_radius = 10\nwith BuildSketch() as minute_indicator:\n with BuildLine() as outline:\n l1 = CenterArc((0, 0), clock_radius * 0.975, 0.75, 4.5)\n l2 = CenterArc((0, 0), clock_radius * 0.925, 0.75, 4.5)\n Line(l1 @ 0, l2 @ 0)\n Line(l1 @ 1, l2 @ 1)\n make_face()\n fillet(minute_indicator.vertices(), radius=clock_radius * 0.01)\n\nwith BuildSketch() as clock_face:\n Circle(clock_radius)\n with PolarLocations(0, 60):\n add(minute_indicator.sketch, mode=Mode.SUBTRACT)\n with PolarLocations(clock_radius * 0.875, 12):\n SlotOverall(clock_radius * 0.05, clock_radius * 0.025, mode=Mode.SUBTRACT)\n for hour in range(1, 13):\n with PolarLocations(clock_radius * 0.75, 1, -hour * 30 + 90, 360, rotate=False):\n Text(\n str(hour),\n font_size=clock_radius * 0.175,\n font_style=FontStyle.BOLD,\n mode=Mode.SUBTRACT,\n )\n\nshow(clock_face)\n# [End]\n" + }, + { + "id": "examples/clock_algebra", + "source": "examples/clock_algebra.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nclock_radius = 10\n\nl1 = CenterArc((0, 0), clock_radius * 0.975, 0.75, 4.5)\nl2 = CenterArc((0, 0), clock_radius * 0.925, 0.75, 4.5)\nl3 = Line(l1 @ 0, l2 @ 0)\nl4 = Line(l1 @ 1, l2 @ 1)\nminute_indicator = make_face([l1, l3, l2, l4])\nminute_indicator = fillet(minute_indicator.vertices(), radius=clock_radius * 0.01)\n\nclock_face = Circle(clock_radius)\nclock_face -= PolarLocations(0, 60) * minute_indicator\nclock_face -= PolarLocations(clock_radius * 0.875, 12) * SlotOverall(\n clock_radius * 0.05, clock_radius * 0.025\n)\n\nclock_face -= [\n loc\n * Text(\n str(hour + 1),\n font_size=clock_radius * 0.175,\n font_style=FontStyle.BOLD,\n align=Align.CENTER,\n )\n for hour, loc in enumerate(\n PolarLocations(clock_radius * 0.75, 12, 60, -360, rotate=False)\n )\n]\n\nshow(clock_face)\n# [End]\n" + }, + { + "id": "examples/custom_sketch_objects", + "source": "examples/custom_sketch_objects.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n\nclass Club(BaseSketchObject):\n \"\"\"Sketch Object: Club\n\n The club suit symbol from a playing card.\n\n Args:\n height (float): size along the Y-axis\n rotation (float, optional): angle from X-axis. Defaults to 0.\n align (tuple[Align, Align], optional): align min, center, or max of object.\n Defaults to (Align.CENTER, Align.CENTER).\n mode (Mode, optional): combination mode. Defaults to Mode.ADD.\n \"\"\"\n\n def __init__(\n self,\n height: float,\n rotation: float = 0,\n align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),\n mode: Mode = Mode.ADD,\n ):\n # Create the club shape\n # Note: The workplane and mode must be set here to avoid interactions with\n # builders in difference scopes.\n with BuildSketch(Plane.XY, mode=Mode.PRIVATE) as club:\n with BuildLine():\n l0 = Line((0, -188), (76, -188))\n b0 = Bezier(l0 @ 1, (61, -185), (33, -173), (17, -81))\n b1 = Bezier(b0 @ 1, (49, -128), (146, -145), (167, -67))\n b2 = Bezier(b1 @ 1, (187, 9), (94, 52), (32, 18))\n b3 = Bezier(b2 @ 1, (92, 57), (113, 188), (0, 188))\n mirror(about=Plane.YZ)\n make_face()\n scale(by=height / club.sketch.bounding_box().size.Y)\n\n # Pass the shape to the BaseSketchObject class to create a new Club object\n super().__init__(obj=club.sketch, rotation=rotation, align=align, mode=mode)\n\n\nclass Spade(BaseSketchObject):\n def __init__(\n self,\n height: float,\n rotation: float = 0,\n align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),\n mode: Mode = Mode.ADD,\n ):\n with BuildSketch(Plane.XY, mode=Mode.PRIVATE) as spade:\n with BuildLine():\n b0 = Bezier((0, 198), (6, 190), (41, 127), (112, 61))\n b1 = Bezier(b0 @ 1, (242, -72), (114, -168), (11, -105))\n b2 = Bezier(b1 @ 1, (31, -174), (42, -179), (53, -198))\n l0 = Line(b2 @ 1, (0, -198))\n mirror(about=Plane.YZ)\n make_face()\n scale(by=height / spade.sketch.bounding_box().size.Y)\n super().__init__(obj=spade.sketch, rotation=rotation, align=align, mode=mode)\n\n\nclass Heart(BaseSketchObject):\n def __init__(\n self,\n height: float,\n rotation: float = 0,\n align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),\n mode: Mode = Mode.ADD,\n ):\n with BuildSketch(Plane.XY, mode=Mode.PRIVATE) as heart:\n with BuildLine():\n b1 = Bezier((0, 146), (20, 169), (67, 198), (97, 198))\n b2 = Bezier(b1 @ 1, (125, 198), (151, 186), (168, 167))\n b3 = Bezier(b2 @ 1, (197, 133), (194, 88), (158, 31))\n b4 = Bezier(b3 @ 1, (126, -13), (94, -48), (62, -95))\n b5 = Bezier(b4 @ 1, (40, -128), (0, -198))\n mirror(about=Plane.YZ)\n make_face()\n scale(by=height / heart.sketch.bounding_box().size.Y)\n super().__init__(obj=heart.sketch, rotation=rotation, align=align, mode=mode)\n\n\nclass Diamond(BaseSketchObject):\n def __init__(\n self,\n height: float,\n rotation: float = 0,\n align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),\n mode: Mode = Mode.ADD,\n ):\n with BuildSketch(Plane.XY, mode=Mode.PRIVATE) as diamond:\n with BuildLine():\n Bezier((135, 0), (94, 69), (47, 134), (0, 198))\n mirror(about=Plane.XZ)\n mirror(about=Plane.YZ)\n make_face()\n scale(by=height / diamond.sketch.bounding_box().size.Y)\n super().__init__(obj=diamond.sketch, rotation=rotation, align=align, mode=mode)\n\n\n# The inside of the box fits 2.5x3.5\" playing card deck with a small gap\npocket_w = 2.5 * IN + 2 * MM\npocket_l = 3.5 * IN + 2 * MM\npocket_t = 0.5 * IN + 2 * MM\nwall_t = 3 * MM # Wall thickness\nbottom_t = wall_t / 2 # Top and bottom thickness\nlid_gap = 0.5 * MM # Spacing between base and lid\nlip_t = wall_t / 2 - lid_gap / 2 # Lip thickness\n\n\nwith BuildPart() as box_builder:\n with BuildSketch() as box_plan:\n RectangleRounded(pocket_w + 2 * wall_t, pocket_l + 2 * wall_t, pocket_w / 15)\n extrude(amount=bottom_t + pocket_t / 2)\n base_top = box_builder.faces().sort_by(Axis.Z)[-1]\n with BuildSketch(base_top) as walls:\n offset(box_plan.sketch, amount=-lip_t, mode=Mode.ADD)\n extrude(amount=pocket_t / 2)\n with BuildSketch(Plane.XY.offset(wall_t / 2)):\n offset(box_plan.sketch, amount=-wall_t, mode=Mode.ADD)\n extrude(amount=pocket_t, mode=Mode.SUBTRACT)\nbox = box_builder.part\n\nwith BuildPart() as lid_builder:\n add(box_plan.sketch)\n extrude(amount=pocket_t / 2 + bottom_t)\n with BuildSketch() as pocket:\n offset(box_plan.sketch, amount=-(wall_t - lip_t), mode=Mode.ADD)\n extrude(amount=pocket_t / 2, mode=Mode.SUBTRACT)\n\n with BuildSketch(lid_builder.faces().sort_by(Axis.Z)[-1]) as suits:\n with Locations((-0.3 * pocket_w, 0.3 * pocket_l)):\n Heart(pocket_l / 5)\n with Locations((-0.3 * pocket_w, -0.3 * pocket_l)):\n Diamond(pocket_l / 5)\n with Locations((0.3 * pocket_w, 0.3 * pocket_l)):\n Spade(pocket_l / 5)\n with Locations((0.3 * pocket_w, -0.3 * pocket_l)):\n Club(pocket_l / 5)\n extrude(amount=-wall_t, mode=Mode.SUBTRACT)\nlid = lid_builder.part.moved(Location((0, 0, (wall_t + pocket_t) / 2)))\n\nshow(box, lid, names=[\"box\", \"lid\"], alphas=[1.0, 0.6])\n" + }, + { + "id": "examples/custom_sketch_objects_algebra", + "source": "examples/custom_sketch_objects_algebra.py", + "kind": "example", + "code": "from typing import Union\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n\nclass Club(Sketch):\n def __init__(\n self,\n height: float,\n align: Union[Align, tuple[Align, Align]] = None,\n ):\n l0 = Line((0, -188), (76, -188))\n b0 = Bezier(l0 @ 1, (61, -185), (33, -173), (17, -81))\n b1 = Bezier(b0 @ 1, (49, -128), (146, -145), (167, -67))\n b2 = Bezier(b1 @ 1, (187, 9), (94, 52), (32, 18))\n b3 = Bezier(b2 @ 1, (92, 57), (113, 188), (0, 188))\n club = l0 + b0 + b1 + b2 + b3\n club += mirror(club, Plane.YZ)\n club = make_face(club)\n club = scale(club, height / club.bounding_box().size.Y)\n\n super().__init__(club.wrapped)\n # self._align(align)\n\n\nclass Spade(Sketch):\n def __init__(\n self,\n height: float,\n align: Union[Align, tuple[Align, Align]] = None,\n ):\n b0 = Bezier((0, 198), (6, 190), (41, 127), (112, 61))\n b1 = Bezier(b0 @ 1, (242, -72), (114, -168), (11, -105))\n b2 = Bezier(b1 @ 1, (31, -174), (42, -179), (53, -198))\n l0 = Line(b2 @ 1, (0, -198))\n spade = b0 + b1 + b2 + l0\n spade += mirror(spade, Plane.YZ)\n spade = make_face(spade)\n spade = scale(spade, height / spade.bounding_box().size.Y)\n\n super().__init__(spade.wrapped)\n # self._align(align)\n\n\nclass Heart(Sketch):\n def __init__(\n self,\n height: float,\n align: Union[Align, tuple[Align, Align]] = None,\n ):\n b1 = Bezier((0, 146), (20, 169), (67, 198), (97, 198))\n b2 = Bezier(b1 @ 1, (125, 198), (151, 186), (168, 167))\n b3 = Bezier(b2 @ 1, (197, 133), (194, 88), (158, 31))\n b4 = Bezier(b3 @ 1, (126, -13), (94, -48), (62, -95))\n b5 = Bezier(b4 @ 1, (40, -128), (0, -198))\n heart = b1 + b2 + b3 + b4 + b5\n heart += mirror(heart, Plane.YZ)\n heart = make_face(heart)\n heart = scale(heart, height / heart.bounding_box().size.Y)\n\n super().__init__(heart.wrapped)\n # self._align(align)\n\n\nclass Diamond(Sketch):\n def __init__(\n self,\n height: float,\n align: Union[Align, tuple[Align, Align]] = None,\n ):\n diamond = Bezier((135, 0), (94, 69), (47, 134), (0, 198))\n diamond += mirror(diamond, Plane.XZ)\n diamond += mirror(diamond, Plane.YZ)\n diamond = make_face(diamond)\n diamond = scale(diamond, height / diamond.bounding_box().size.Y)\n\n super().__init__(diamond.wrapped)\n # self._align(align)\n\n\n# The inside of the box fits 2.5x3.5\" playing card deck with a small gap\npocket_w = 2.5 * IN + 2 * MM\npocket_l = 3.5 * IN + 2 * MM\npocket_t = 0.5 * IN + 2 * MM\nwall_t = 3 * MM # Wall thickness\nbottom_t = wall_t / 2 # Top and bottom thickness\nlid_gap = 0.5 * MM # Spacing between base and lid\nlip_t = wall_t / 2 - lid_gap / 2 # Lip thickness\n\n\nbox_plan = RectangleRounded(pocket_w + 2 * wall_t, pocket_l + 2 * wall_t, pocket_w / 15)\nbox = extrude(box_plan, amount=bottom_t + pocket_t / 2)\nbase_top = box.faces().sort_by(Axis.Z).last\nwalls = Plane(base_top) * offset(box_plan, -lip_t)\nbox += extrude(walls, amount=pocket_t / 2)\ntop = Plane.XY.offset(wall_t / 2) * offset(box_plan, -wall_t)\nbox -= extrude(top, amount=pocket_t)\n\n\npocket = extrude(box_plan, amount=pocket_t / 2 + bottom_t)\nlid_bottom = offset(box_plan, -(wall_t - lip_t))\npocket -= extrude(lid_bottom, amount=pocket_t / 2)\npocket = Pos(0, 0, (wall_t + pocket_t) / 2) * pocket\n\nplane = Plane(pocket.faces().sort_by().last)\nsuites = Pos(-0.3 * pocket_w, 0.3 * pocket_l) * Heart(pocket_l / 5)\nsuites += Pos(-0.3 * pocket_w, -0.3 * pocket_l) * Diamond(pocket_l / 5)\nsuites += Pos(0.3 * pocket_w, 0.3 * pocket_l) * Spade(pocket_l / 5)\nsuites += Pos(0.3 * pocket_w, -0.3 * pocket_l) * Club(pocket_l / 5)\nsuites = plane * suites\n\nlid = pocket - extrude(suites, dir=(0, 0, 1), amount=-wall_t)\n\nshow(box, lid, names=[\"box\", \"lid\"], alphas=[1.0, 0.6])\n" + }, + { + "id": "examples/din_rail", + "source": "examples/din_rail.py", + "kind": "example", + "code": "import logging\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nlogging.basicConfig(\n filename=\"din_rail.log\",\n level=logging.INFO,\n format=\"%(name)s-%(levelname)s %(asctime)s - [%(filename)s:%(lineno)s - %(funcName)20s() ] - %(message)s\",\n)\nlogging.info(\"Starting to create din rail\")\n\n# 35x7.5mm DIN Rail Dimensions\noverall_width, top_width, height, thickness, fillet_radius = 35, 27, 7.5, 1, 0.8\nrail_length = 1000\nslot_width, slot_length, slot_pitch = 6.2, 15, 25\n\nwith BuildPart() as rail:\n with BuildSketch(Plane.XZ) as din:\n Rectangle(overall_width, thickness, align=(Align.CENTER, Align.MIN))\n Rectangle(top_width, height, align=(Align.CENTER, Align.MIN))\n Rectangle(\n top_width - 2 * thickness,\n height - thickness,\n align=(Align.CENTER, Align.MIN),\n mode=Mode.SUBTRACT,\n )\n inside_vertices = (\n din.vertices()\n .filter_by_position(Axis.Y, 0.0, height, inclusive=(False, False))\n .filter_by_position(\n Axis.X,\n -overall_width / 2,\n overall_width / 2,\n inclusive=(False, False),\n )\n )\n fillet(inside_vertices, radius=fillet_radius)\n outside_vertices = filter(\n lambda v: (v.Y == 0.0 or v.Y == height)\n and -overall_width / 2 < v.X < overall_width / 2,\n din.vertices(),\n )\n fillet(outside_vertices, radius=fillet_radius + thickness)\n extrude(amount=rail_length / 2, both=True)\n\n with BuildSketch(Plane.XY) as slots:\n with GridLocations(\n 0,\n slot_pitch,\n 1,\n rail_length // slot_pitch - 1,\n ):\n SlotOverall(slot_length, slot_width, rotation=90)\n extrude(amount=height, mode=Mode.SUBTRACT)\n\n# assert abs(rail.part.volume - 42462.863388694714) < 1e-3\nshow(rail, names=[\"rail\"])\n" + }, + { + "id": "examples/din_rail_algebra", + "source": "examples/din_rail_algebra.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n# 35x7.5mm DIN Rail Dimensions\noverall_width, top_width, height, thickness, fillet_radius = 35, 27, 7.5, 1, 0.8\nrail_length = 1000\nslot_width, slot_length, slot_pitch = 6.2, 15, 25\n\ndin = Rectangle(overall_width, thickness, align=(Align.CENTER, Align.MIN))\ndin += Rectangle(top_width, height, align=(Align.CENTER, Align.MIN))\ndin -= Rectangle(\n top_width - 2 * thickness,\n height - thickness,\n align=(Align.CENTER, Align.MIN),\n)\n\ninside_vertices = (\n din.vertices()\n .filter_by_position(Axis.Y, 0.0, height, inclusive=(False, False))\n .filter_by_position(\n Axis.X,\n -overall_width / 2,\n overall_width / 2,\n inclusive=(False, False),\n )\n)\n\ndin = fillet(inside_vertices, radius=fillet_radius)\n\noutside_vertices = filter(\n lambda v: (v.Y == 0.0 or v.Y == height)\n and -overall_width / 2 < v.X < overall_width / 2,\n din.vertices(),\n)\ndin = fillet(outside_vertices, radius=fillet_radius + thickness)\n\nrail = extrude(din, rail_length)\n\nplane = Plane(rail.faces().sort_by(Axis.Y).last)\n\nslot_faces = [\n (plane * loc * Rot(0, 0, 90) * SlotOverall(slot_length, slot_width)).faces()[0]\n for loc in GridLocations(0, slot_pitch, 1, rail_length // slot_pitch - 1)\n]\n\nrail -= extrude(slot_faces, -height)\nrail = Plane.XZ * rail\n\nshow(rail, names=[\"rail\"])\n" + }, + { + "id": "examples/dual_color_3mf", + "source": "examples/dual_color_3mf.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n\n# Create a simple tile pattern\nwith BuildSketch() as inset_pattern:\n with BuildLine() as bl:\n Polyline((9, 9), (1, 5), (-0.5, 0))\n offset(amount=1, side=Side.LEFT)\n make_face()\n split(bisect_by=Plane(origin=(0, 0, 0), z_dir=(-1, 1, 0)))\n mirror(about=Plane(origin=(0, 0, 0), z_dir=(-1, 1, 0)))\n mirror(about=Plane.YZ)\n mirror(about=Plane.XZ)\n\n# Create the background field object for the tile\nwith BuildPart() as outset_builder:\n with BuildSketch():\n Rectangle(20, 20)\n add(inset_pattern.sketch, mode=Mode.SUBTRACT)\n extrude(amount=1)\n\n# Create the inset object for the tile\nwith BuildPart() as inset_builder:\n add(inset_pattern.sketch)\n extrude(amount=1)\n\n# Assign colors to the tile parts\noutset = outset_builder.part\noutset.color = Color(0.137, 0.306, 0.439) # Tealish\ninset = inset_builder.part\ninset.color = Color(0.980, 0.973, 0.749) # Goldish\n\nshow(inset, outset)\n\n# Export the tile with the units as CM\nexporter = Mesher(unit=Unit.CM)\nexporter.add_shape([inset, outset])\nexporter.write(\"dual_color.3mf\")\n" + }, + { + "id": "examples/extrude", + "source": "examples/extrude.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n# Extrude pending face by amount\nwith BuildPart() as simple:\n with BuildSketch():\n Text(\"O\", font_size=10)\n extrude(amount=5)\n\n# Extrude pending face in both directions by amount\nwith BuildPart() as both:\n with BuildSketch():\n Text(\"O\", font_size=10)\n extrude(amount=5, both=True)\n\n# Extrude multiple pending faces on multiple faces\nwith BuildPart() as multiple:\n Box(10, 10, 10)\n with BuildSketch(*multiple.faces()):\n with GridLocations(5, 5, 2, 2):\n Text(\"\u03a9\", font_size=3)\n extrude(amount=1)\n\n# Non-planar surface\nwith BuildPart() as non_planar:\n Cylinder(10, 20, rotation=(90, 0, 0), align=(Align.CENTER, Align.MIN, Align.CENTER))\n Box(10, 10, 10, align=(Align.CENTER, Align.CENTER, Align.MIN), mode=Mode.INTERSECT)\n extrude(\n non_planar.part.faces().sort_by(Axis.Z)[0],\n amount=2,\n dir=(0, 0, 1),\n mode=Mode.REPLACE,\n )\n\n\nrad, rev = 3, 25\n\n# Extrude last\nwith BuildPart() as ex26:\n with BuildSketch() as ex26_sk:\n with Locations((0, rev)):\n Circle(rad)\n revolve(axis=Axis.X, revolution_arc=180)\n with BuildSketch() as ex26_sk2:\n Rectangle(rad, rev)\n ex26_target = ex26.part\n extrude(until=Until.LAST, clean=False, mode=Mode.REPLACE)\n\n# Extrude next\nwith BuildPart() as ex27:\n with BuildSketch():\n with Locations((0, rev)):\n Circle(rad)\n revolve(axis=Axis.X, revolution_arc=90)\n with BuildSketch(Plane.XZ):\n with Locations((0, rev)):\n Circle(rad)\n revolve(axis=Axis.X, revolution_arc=150)\n with BuildSketch(Plane.XY.offset(-60)):\n Rectangle(rad, rev + 25)\n extrusion27 = extrude(until=Until.NEXT, mode=Mode.ADD)\n\n# Extrude next both\n# with BuildPart() as ex28:\n# Torus(25, 5, rotation=(0, 90, 0))\n# with BuildSketch():\n# Rectangle(rad, rev)\n# extrusion28 = extrude(until=Until.NEXT, both=True)\n\nshow_object(simple.part.translate((-15, 0, 0)).wrapped, name=\"simple pending extrude\")\nshow_object(both.part.translate((20, 10, 0)).wrapped, name=\"simple both\")\nshow_object(\n multiple.part.translate((0, -20, 0)).wrapped, name=\"multiple pending extrude\"\n)\nshow_object(non_planar.part.translate((20, -10, 0)).wrapped, name=\"non planar\")\nshow_object(\n ex26_target.translate((-40, 0, 0)).wrapped,\n name=\"extrude until last target\",\n options={\"alpha\": 0.8},\n)\nshow_object(\n ex26.part.translate((-40, 0, 0)).wrapped,\n name=\"extrude until last\",\n)\nshow_object(\n ex27.part.rotate(Axis.Z, 90).translate((0, 50, 0)).wrapped,\n name=\"extrude until next target\",\n options={\"alpha\": 0.8},\n)\nshow_object(\n extrusion27.rotate(Axis.Z, 90).translate((0, 50, 0)).wrapped,\n name=\"extrude until next\",\n)\n# show_object(\n# ex28.part.rotate(Axis.Z, -90).translate((0, -50, 0)).wrapped,\n# name=\"extrude until next both target\",\n# options={\"alpha\": 0.8},\n# )\n# show_object(\n# extrusion28.rotate(Axis.Z, -90).translate((0, -50, 0)).wrapped,\n# name=\"extrude until next both\",\n# )\n" + }, + { + "id": "examples/extrude_algebra", + "source": "examples/extrude_algebra.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\n# Extrude pending face by amount\nsimple = extrude(Text(\"O\", font_size=10), amount=5)\n\n# Extrude pending face in both directions by amount\nboth = extrude(Text(\"O\", font_size=10), amount=5, both=True)\n\n# Extrude multiple pending faces on multiple faces\nmultiple = Box(10, 10, 10)\nfaces = [\n Plane(face) * loc * Text(\"\u03a9\", font_size=3)\n for face in multiple.faces()\n for loc in GridLocations(5, 5, 2, 2)\n]\nmultiple += [extrude(face, amount=1) for face in faces]\n\n# Non-planar surface\nnon_planar = Rot(90, 0, 0) * Cylinder(\n 10, 20, align=(Align.CENTER, Align.MIN, Align.CENTER)\n)\nnon_planar &= Box(10, 10, 10, align=(Align.CENTER, Align.CENTER, Align.MIN))\nnon_planar = extrude(non_planar.faces().sort_by(Axis.Z).first, amount=2, dir=(0, 0, 1))\nrad, rev = 3, 25\n\n# Extrude last\ncircle = Pos(0, rev) * Circle(rad)\nex26_target = revolve(circle, Axis.X, revolution_arc=180)\nex26_target = ex26_target\n\nrect = Rectangle(rad, rev)\n\nex26 = extrude(rect, until=Until.LAST, target=ex26_target, clean=False)\n\n# Extrude next\ncircle = Pos(0, rev) * Circle(rad)\nex27 = revolve(circle, Axis.X, revolution_arc=90)\n\ncircle2 = Plane.XZ * Pos(0, rev) * Circle(rad)\nex27 += revolve(circle2, Axis.X, revolution_arc=150)\nrect = Plane.XY.offset(-60) * Rectangle(rad, rev + 25)\nextrusion27 = extrude(rect, until=Until.NEXT, target=ex27, mode=Mode.ADD)\n\n\n# Extrude next both\n# ex28 = Rot(0, 90, 0) * Torus(25, 5)\n# rect = Rectangle(rad, rev)\n# extrusion28 = extrude(rect, until=Until.NEXT, target=ex28, both=True, clean=False)\n\nshow_object(simple.translate((-15, 0, 0)), name=\"simple pending extrude\")\nshow_object(both.translate((20, 10, 0)), name=\"simple both\")\nshow_object(multiple.translate((0, -20, 0)), name=\"multiple pending extrude\")\nshow_object(non_planar.translate((20, -10, 0)), name=\"non planar\")\nshow_object(\n ex26_target.translate((-40, 0, 0)),\n name=\"extrude until last target\",\n options={\"alpha\": 0.8},\n)\nshow_object(\n ex26.translate((-40, 0, 0)),\n name=\"extrude until last\",\n)\nshow_object(\n ex27.rotate(Axis.Z, 90).translate((0, 50, 0)),\n name=\"extrude until next target\",\n options={\"alpha\": 0.8},\n)\nshow_object(\n extrusion27.rotate(Axis.Z, 90).translate((0, 50, 0)),\n name=\"extrude until next\",\n)\n# show_object(\n# ex28.rotate(Axis.Z, -90).translate((0, -50, 0)),\n# name=\"extrude until next both target\",\n# options={\"alpha\": 0.8},\n# )\n# show_object(\n# extrusion28.rotate(Axis.Z, -90).translate((0, -50, 0)),\n# name=\"extrude until next both\",\n# )\n" + }, + { + "id": "examples/fast_grid_holes", + "source": "examples/fast_grid_holes.py", + "kind": "example", + "code": "# [Code]\nimport timeit\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nstart_time = timeit.default_timer()\n\n# Calculate the locations of 625 holes\nmajor_r = 10\nhole_locs = HexLocations(major_r, 25, 25)\n\n# Create wires for both the perimeter and all the holes\nface_perimeter = Rectangle(500, 600).wire()\nhex_hole = RegularPolygon(major_r - 1, 6, major_radius=True).wire()\nholes = hole_locs * hex_hole\n\n# Create a new Face from the perimeter and hole wires\ngrid_pattern = Face(face_perimeter, holes)\n\n# Extrude to a 3D part\ngrid = extrude(grid_pattern, 1)\n\nprint(f\"Time: {timeit.default_timer() - start_time:0.3f}s\")\nshow(grid)\n# [End]\n" + }, + { + "id": "examples/handle", + "source": "examples/handle.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\nsegment_count = 6\n\nwith BuildPart() as handle:\n # Create a path for the sweep along the handle - added to pending_edges\n with BuildLine() as handle_center_line:\n Spline(\n (-10, 0, 0),\n (0, 0, 5),\n (10, 0, 0),\n tangents=((0, 0, 1), (0, 0, -1)),\n tangent_scalars=(1.5, 1.5),\n )\n\n # Create the cross sections - added to pending_faces\n for i in range(segment_count + 1):\n with BuildSketch(handle_center_line.line ^ (i / segment_count)) as section:\n if i % segment_count == 0:\n Circle(1)\n else:\n Rectangle(1.25, 3)\n fillet(section.vertices(), radius=0.2)\n # Record the sections for display\n sections = handle.pending_faces\n\n # Create the handle by sweeping along the path\n sweep(multisection=True)\n\nassert abs(handle.part.volume - 94.77361455046953) < 1e-3\n\nshow_object(handle_center_line.line, name=\"handle_center_line\")\nfor i, section in enumerate(sections):\n show_object(section, name=\"section\" + str(i))\nshow_object(handle.part, name=\"handle\", options=dict(alpha=0.6))\n# [End]\n" + }, + { + "id": "examples/handle_algebra", + "source": "examples/handle_algebra.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\nsegment_count = 6\n\n# Create a path for the sweep along the handle - added to pending_edges\nhandle_center_line = Spline(\n (-10, 0, 0),\n (0, 0, 5),\n (10, 0, 0),\n tangents=((0, 0, 1), (0, 0, -1)),\n tangent_scalars=(1.5, 1.5),\n)\n\n# Create the cross sections - added to pending_faces\nsections = Sketch()\nfor i in range(segment_count + 1):\n location = handle_center_line ^ (i / segment_count)\n if i % segment_count == 0:\n circle = location * Circle(1)\n else:\n circle = location * Rectangle(1.25, 3)\n circle = fillet(circle.vertices(), radius=0.2)\n sections += circle\n\n# Create the handle by sweeping along the path\nhandle = sweep(sections, path=handle_center_line, multisection=True)\n\nshow_object(handle_center_line, name=\"handle_path\")\nfor i, circle in enumerate(sections):\n show_object(circle, name=\"section\" + str(i))\nshow_object(handle, name=\"handle\", options=dict(alpha=0.6))\n# [End]\n" + }, + { + "id": "examples/heat_exchanger", + "source": "examples/heat_exchanger.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nexchanger_diameter = 10 * CM\nexchanger_length = 30 * CM\nplate_thickness = 5 * MM\n# 149 tubes\ntube_diameter = 5 * MM\ntube_spacing = 2 * MM\ntube_wall_thickness = 0.5 * MM\ntube_extension = 3 * MM\nbundle_diameter = exchanger_diameter - 2 * tube_diameter\nfillet_radius = tube_spacing / 3\nassert tube_extension > fillet_radius\n\n# Build the heat exchanger\nwith BuildPart() as heat_exchanger:\n # Generate list of tube locations\n tube_locations = [\n l\n for l in HexLocations(\n radius=(tube_diameter + tube_spacing) / 2,\n x_count=exchanger_diameter // tube_diameter,\n y_count=exchanger_diameter // tube_diameter,\n )\n if l.position.length < bundle_diameter / 2\n ]\n tube_count = len(tube_locations)\n with BuildSketch() as tube_plan:\n with Locations(*tube_locations):\n Circle(radius=tube_diameter / 2)\n Circle(radius=tube_diameter / 2 - tube_wall_thickness, mode=Mode.SUBTRACT)\n extrude(amount=exchanger_length / 2)\n with BuildSketch(\n Plane(\n origin=(0, 0, exchanger_length / 2 - tube_extension - plate_thickness),\n z_dir=(0, 0, 1),\n )\n ) as plate_plan:\n Circle(radius=exchanger_diameter / 2)\n with Locations(*tube_locations):\n Circle(radius=tube_diameter / 2 - tube_wall_thickness, mode=Mode.SUBTRACT)\n extrude(amount=plate_thickness)\n half_volume_before_fillet = heat_exchanger.part.volume\n # Simulate welded tubes by adding a fillet to the outside radius of the tubes\n fillet(\n heat_exchanger.edges()\n .filter_by(GeomType.CIRCLE)\n .sort_by(SortBy.RADIUS)\n .sort_by(Axis.Z, reverse=True)[2 * tube_count : 3 * tube_count],\n radius=fillet_radius,\n )\n half_volume_after_fillet = heat_exchanger.part.volume\n mirror(about=Plane.XY)\n\nfillet_volume = 2 * (half_volume_after_fillet - half_volume_before_fillet)\nassert abs(fillet_volume - 469.88331045553787) < 1e-3\n\nshow(heat_exchanger)\n# [End]\n" + }, + { + "id": "examples/heat_exchanger_algebra", + "source": "examples/heat_exchanger_algebra.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nexchanger_diameter = 10 * CM\nexchanger_length = 30 * CM\nplate_thickness = 5 * MM\n# 149 tubes\ntube_diameter = 5 * MM\ntube_spacing = 2 * MM\ntube_wall_thickness = 0.5 * MM\ntube_extension = 3 * MM\nbundle_diameter = exchanger_diameter - 2 * tube_diameter\nfillet_radius = tube_spacing / 3\nassert tube_extension > fillet_radius\n\n# Build the heat exchanger\ntube_locations = [\n l\n for l in HexLocations(\n radius=(tube_diameter + tube_spacing) / 2,\n x_count=exchanger_diameter // tube_diameter,\n y_count=exchanger_diameter // tube_diameter,\n )\n if l.position.length < bundle_diameter / 2\n]\n\nring = Circle(tube_diameter / 2) - Circle(tube_diameter / 2 - tube_wall_thickness)\ntube_plan = Sketch() + tube_locations * ring\n\nheat_exchanger = extrude(tube_plan, exchanger_length / 2)\n\nplate_plane = Plane(\n origin=(0, 0, exchanger_length / 2 - tube_extension - plate_thickness),\n z_dir=(0, 0, 1),\n)\nplate = Circle(radius=exchanger_diameter / 2) - tube_locations * Circle(\n radius=tube_diameter / 2 - tube_wall_thickness\n)\n\nheat_exchanger += extrude(plate_plane * plate, plate_thickness)\nedges = (\n heat_exchanger.edges()\n .filter_by(GeomType.CIRCLE)\n .group_by(SortBy.RADIUS)[1]\n .group_by()[2]\n)\nhalf_volume_before_fillet = heat_exchanger.volume\nheat_exchanger = fillet(edges, radius=fillet_radius)\nhalf_volume_after_fillet = heat_exchanger.volume\nheat_exchanger += mirror(heat_exchanger, Plane.XY)\n\nfillet_volume = 2 * (half_volume_after_fillet - half_volume_before_fillet)\nassert abs(fillet_volume - 469.88331045553787) < 1e-3\n\nshow(heat_exchanger)\n# [End]\n" + }, + { + "id": "examples/holes", + "source": "examples/holes.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\n# Simple through hole\nwith BuildPart() as thru_hole:\n Cylinder(radius=3, height=2)\n Hole(radius=1)\n\n# Recessed counter bore hole (hole location @ (0,0,0))\nwith BuildPart() as recessed_counter_bore:\n with Locations((10, 0)):\n Cylinder(radius=3, height=2)\n CounterBoreHole(radius=1, counter_bore_radius=1.5, counter_bore_depth=0.5)\n\n# Recessed counter sink hole (hole location @ (0,0,0))\nwith BuildPart() as recessed_counter_sink:\n with Locations((0, 10)):\n Cylinder(radius=3, height=2)\n CounterSinkHole(radius=1, counter_sink_radius=1.5)\n\n# Flush counter sink hole (hole location @ (0,0,2))\nwith BuildPart() as flush_counter_sink:\n with Locations((10, 10)):\n Cylinder(radius=3, height=2)\n with Locations(\n (0, 0, flush_counter_sink.part.faces().sort_by(Axis.Z)[-1].center().Z)\n ):\n CounterSinkHole(radius=1, counter_sink_radius=1.5)\n\nshow_object(thru_hole.part.wrapped, name=\"though hole\")\nshow_object(recessed_counter_bore.part.wrapped, name=\"recessed counter bore\")\nshow_object(recessed_counter_sink.part.wrapped, name=\"recessed counter sink\")\nshow_object(flush_counter_sink.part.wrapped, name=\"flush counter sink\")\n" + }, + { + "id": "examples/holes_algebra", + "source": "examples/holes_algebra.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\nthru_hole = Cylinder(radius=3, height=2)\nthru_hole -= Hole(radius=1, depth=2)\n\n# Recessed counter bore hole (hole location = (0,0,0))\nrecessed_counter_bore = Cylinder(radius=3, height=2)\nrecessed_counter_bore -= CounterBoreHole(\n radius=1, depth=2, counter_bore_radius=1.5, counter_bore_depth=0.5\n)\n\n# Recessed counter sink hole (hole location = (0,0,0))\nrecessed_counter_sink = Cylinder(radius=3, height=2)\nrecessed_counter_sink -= CounterSinkHole(radius=1, depth=2, counter_sink_radius=1.5)\n\n# Flush counter sink hole (hole location = (0,0,2))\nflush_counter_sink = Cylinder(radius=3, height=2)\nplane = Plane(flush_counter_sink.faces().sort_by().last)\nflush_counter_sink -= plane * CounterSinkHole(\n radius=1, depth=2, counter_sink_radius=1.5\n)\n\nshow_object(thru_hole, name=\"though hole\")\nshow_object(Pos(10, 0) * recessed_counter_bore, name=\"recessed counter bore\")\nshow_object(Pos(0, 10) * recessed_counter_sink, name=\"recessed counter sink\")\nshow_object(Pos(10, 10) * flush_counter_sink, name=\"flush counter sink\")\n" + }, + { + "id": "examples/intersecting_chamfers", + "source": "examples/intersecting_chamfers.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nwith BuildPart() as blocks:\n with Locations((-1, -1, 0)):\n Box(1, 2, 1, align=(Align.CENTER, Align.MIN, Align.MIN))\n Box(1, 1, 2, align=(Align.CENTER, Align.MIN, Align.MIN))\n with Locations((1, -1, 0)):\n Box(1, 2, 1, align=(Align.CENTER, Align.MIN, Align.MIN))\n bottom_edges = blocks.edges().filter_by_position(\n Axis.Z, 0, 1, inclusive=(True, False)\n )\n chamfer(bottom_edges, length=0.1)\n top_edges = blocks.edges().filter_by_position(Axis.Z, 1, 2, inclusive=(False, True))\n chamfer(top_edges, length=0.1)\n\n\nshow(blocks)\n" + }, + { + "id": "examples/intersecting_chamfers_algebra", + "source": "examples/intersecting_chamfers_algebra.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nblocks = Pos(-1, -1, 0) * Box(1, 2, 1, align=(Align.CENTER, Align.MIN, Align.MIN))\nblocks += Box(1, 1, 2, align=(Align.CENTER, Align.MIN, Align.MIN))\nblocks += Pos(1, -1, 0) * Box(1, 2, 1, align=(Align.CENTER, Align.MIN, Align.MIN))\n\nbottom_edges = blocks.edges().filter_by_position(Axis.Z, 0, 1, inclusive=(True, False))\nblocks2 = chamfer(bottom_edges, length=0.1)\n\ntop_edges = blocks2.edges().filter_by_position(Axis.Z, 1, 2, inclusive=(False, True))\nblocks2 = chamfer(top_edges, length=0.1)\n\n\nshow(blocks2)\n" + }, + { + "id": "examples/intersecting_pipes", + "source": "examples/intersecting_pipes.py", + "kind": "example", + "code": "import logging\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n# logging.basicConfig(\n# filename=\"intersecting_pipes.log\",\n# level=logging.DEBUG,\n# format=\"%(name)s-%(levelname)5s %(asctime)s - [%(filename)s:%(lineno)s - %(funcName)20s() ] - %(message)s\",\n# )\n# logging.info(\"Starting pipes test\")\n\nwith BuildPart() as pipes:\n box = Box(10, 10, 10, rotation=(10, 20, 30))\n with BuildSketch(*box.faces()) as pipe:\n Circle(4)\n extrude(amount=-5, mode=Mode.SUBTRACT)\n with BuildSketch(*box.faces()) as pipe:\n Circle(4.5)\n Circle(4, mode=Mode.SUBTRACT)\n extrude(amount=10)\n fillet(pipes.edges(Select.LAST), 0.2)\n\nassert abs(pipes.part.volume - 1015.939005681509) < 1e-3\n\nshow(pipes, names=[\"intersecting pipes\"])\n" + }, + { + "id": "examples/joints", + "source": "examples/joints.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n\nclass JointBox(Solid):\n \"\"\"A filleted box with joints\n\n A box of the given dimensions with all of the edges filleted.\n\n Args:\n length (float): box length\n width (float): box width\n height (float): box height\n radius (float): edge radius\n taper (float): vertical taper in degrees\n \"\"\"\n\n def __init__(\n self,\n length: float,\n width: float,\n height: float,\n radius: float = 0.0,\n taper: float = 0.0,\n ):\n # Create the object\n with BuildPart() as obj:\n with BuildSketch():\n Rectangle(length, width)\n extrude(amount=height, taper=taper)\n if radius != 0.0:\n fillet(obj.part.edges(), radius=radius)\n Cylinder(width / 4, length, rotation=(0, 90, 0), mode=Mode.SUBTRACT)\n # Initialize the Solid class with the new OCCT object\n super().__init__(obj.part.wrapped)\n\n\n#\n# Base Object\n#\n# base = JointBox(10, 10, 10)\n# base = JointBox(10, 10, 10).locate(Location(Vector(1, 1, 1)))\n# base = JointBox(10, 10, 10).locate(Location(Vector(1, 1, 1), (1, 0, 0), 5))\nbase: JointBox = JointBox(10, 10, 10, taper=3).locate(\n Location(Vector(1, 1, 1), (1, 1, 1), 30)\n)\nbase_top_edges: ShapeList[Edge] = (\n base.edges().filter_by(Axis.X, tolerance=30).sort_by(Axis.Z)[-2:]\n)\n#\n# Rigid Joint\n#\nfixed_arm = JointBox(1, 1, 5, 0.2)\nj1 = RigidJoint(\"side\", base, Plane(base.faces().sort_by(Axis.X)[-1]).location)\nj2 = RigidJoint(\n \"top\", fixed_arm, (-Plane(fixed_arm.faces().sort_by(Axis.Z)[-1])).location\n)\nbase.joints[\"side\"].connect_to(fixed_arm.joints[\"top\"])\n# or\n# j1.connect_to(j2)\n\n#\n# Hinge\n#\nhinge_arm = JointBox(2, 1, 10, taper=1)\nswing_arm_hinge_edge: Edge = (\n hinge_arm.edges()\n .group_by(SortBy.LENGTH)[-1]\n .sort_by(Axis.X)[-2:]\n .sort_by(Axis.Y)[0]\n)\nswing_arm_hinge_axis = Axis(swing_arm_hinge_edge)\nbase_corner_edge = base.edges().sort_by(Axis((0, 0, 0), (1, 1, 0)))[-1]\nbase_hinge_axis = Axis(base_corner_edge)\nj3 = RevoluteJoint(\"hinge\", base, axis=base_hinge_axis, angular_range=(0, 180))\nj4 = RigidJoint(\"corner\", hinge_arm, swing_arm_hinge_axis.location)\nbase.joints[\"hinge\"].connect_to(hinge_arm.joints[\"corner\"], angle=90)\n\n#\n# Slider\n#\nslider_arm = JointBox(4, 1, 2, 0.2)\ns1 = LinearJoint(\n \"slide\",\n base,\n axis=Axis(Edge.make_mid_way(*base_top_edges, 0.67)),\n linear_range=(0, base_top_edges[0].length),\n)\ns2 = RigidJoint(\"slide\", slider_arm, Location(Vector(0, 0, 0)))\nbase.joints[\"slide\"].connect_to(slider_arm.joints[\"slide\"], position=8)\n# s1.connect_to(s2,8)\n\n#\n# Cylindrical\n#\nhole_axis = Axis(\n base.faces().sort_by(Axis.Y)[0].center(),\n -base.faces().sort_by(Axis.Y)[0].normal_at(),\n)\nscrew_arm = JointBox(1, 1, 10, 0.49)\nj5 = CylindricalJoint(\"hole\", base, hole_axis, linear_range=(-10, 10))\nj6 = RigidJoint(\"screw\", screw_arm, screw_arm.faces().sort_by(Axis.Z)[-1].location)\nj5.connect_to(j6, position=-1, angle=90)\n\n#\n# PinSlotJoint\n#\nj7 = LinearJoint(\n \"slot\",\n base,\n axis=Axis(Edge.make_mid_way(*base_top_edges, 0.33)),\n linear_range=(0, base_top_edges[0].length),\n)\npin_arm = JointBox(2, 1, 2)\nj8 = RevoluteJoint(\"pin\", pin_arm, axis=Axis.Z, angular_range=(0, 360))\nj7.connect_to(j8, position=6, angle=60)\n\n#\n# BallJoint\n#\nj9 = BallJoint(\"socket\", base, Plane(base.faces().sort_by(Axis.X)[0]).location)\nball = JointBox(2, 2, 2, 0.99)\nj10 = RigidJoint(\"ball\", ball, Location(Vector(0, 0, 1)))\nj9.connect_to(j10, angles=(10, 20, 30))\n\nshow_all(render_joints=True, transparent=True)\n" + }, + { + "id": "examples/joints_algebra", + "source": "examples/joints_algebra.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n\nclass JointBox(Part):\n \"\"\"A filleted box with joints\n\n A box of the given dimensions with all of the edges filleted.\n\n Args:\n length (float): box length\n width (float): box width\n height (float): box height\n radius (float): edge radius\n taper (float): vertical taper in degrees\n \"\"\"\n\n def __init__(\n self,\n length: float,\n width: float,\n height: float,\n radius: float = 0.0,\n taper: float = 0.0,\n ):\n # Create the object\n obj = extrude(Rectangle(length, width), amount=height, taper=taper)\n if radius != 0.0:\n obj = fillet(obj.edges(), radius=radius)\n obj -= Rot(0, 90, 0) * Cylinder(width / 4, length)\n # Initialize the Part class with the new OCCT object\n super().__init__(obj.wrapped)\n\n\n#\n# Base Object\n#\n# base = JointBox(10, 10, 10)\n# base = JointBox(10, 10, 10).locate(Location(Vector(1, 1, 1)))\n# base = JointBox(10, 10, 10).locate(Location(Vector(1, 1, 1), (1, 0, 0), 5))\nloc = Location(Vector(1, 1, 1), (1, 1, 1), 30)\nbase = loc * JointBox(10, 10, 10, taper=3)\n\nbase_top_edges = base.edges().filter_by(loc.x_axis).group_by(loc.z_axis)[-1]\n#\n# Rigid Joint\n#\nfixed_arm = JointBox(1, 1, 5, 0.2)\nj1 = RigidJoint(\"side\", base, Plane(base.faces().sort_by(loc.x_axis).last).location)\nj2 = RigidJoint(\"top\", fixed_arm, (-Plane(fixed_arm.faces().sort_by().last)).location)\nbase.joints[\"side\"].connect_to(fixed_arm.joints[\"top\"])\n# or\n# j1.connect_to(j2)\n\n#\n# Hinge\n#\nhinge_arm = JointBox(2, 1, 10, taper=1)\nswing_arm_hinge_edge = (\n hinge_arm.edges()\n .group_by(SortBy.LENGTH)[-1]\n .sort_by(Axis.X)[-2:]\n .sort_by(Axis.Y)[0]\n)\nswing_arm_hinge_axis = Axis(swing_arm_hinge_edge)\nbase_corner_edge = base.edges().sort_by(Axis((0, 0, 0), (1, 1, 0)))[-1]\nbase_hinge_axis = Axis(base_corner_edge)\nj3 = RevoluteJoint(\"hinge\", base, axis=base_hinge_axis, angular_range=(0, 180))\nj4 = RigidJoint(\"corner\", hinge_arm, swing_arm_hinge_axis.location)\nbase.joints[\"hinge\"].connect_to(hinge_arm.joints[\"corner\"], angle=90)\n\n\n#\n# Slider\n#\nslider_arm = JointBox(4, 1, 2, 0.2)\ns1 = LinearJoint(\n \"slide\",\n base,\n axis=Axis(Edge.make_mid_way(*base_top_edges, 0.67)),\n linear_range=(0, base_top_edges[0].length),\n)\ns2 = RigidJoint(\"slide\", slider_arm, Location(Vector(0, 0, 0)))\nbase.joints[\"slide\"].connect_to(slider_arm.joints[\"slide\"], position=8)\n# s1.connect_to(s2,8)\n\n#\n# Cylindrical\n#\nhole_axis = Axis(\n base.faces().sort_by(Axis.Y)[0].center(),\n -base.faces().sort_by(Axis.Y)[0].normal_at(),\n)\nscrew_arm = JointBox(1, 1, 10, 0.49)\nj5 = CylindricalJoint(\"hole\", base, hole_axis, linear_range=(-10, 10))\nj6 = RigidJoint(\"screw\", screw_arm, screw_arm.faces().sort_by(Axis.Z)[-1].location)\nj5.connect_to(j6, position=-1, angle=90)\n\n#\n# PinSlotJoint\n#\nj7 = LinearJoint(\n \"slot\",\n base,\n axis=Axis(Edge.make_mid_way(*base_top_edges, 0.33)),\n linear_range=(0, base_top_edges[0].length),\n)\npin_arm = JointBox(2, 1, 2)\nj8 = RevoluteJoint(\"pin\", pin_arm, axis=Axis.Z, angular_range=(0, 360))\nj7.connect_to(j8, position=6, angle=60)\n\n#\n# BallJoint\n#\nj9 = BallJoint(\"socket\", base, Plane(base.faces().sort_by(Axis.X)[0]).location)\nball = JointBox(2, 2, 2, 0.99)\nj10 = RigidJoint(\"ball\", ball, Location(Vector(0, 0, 1)))\nj9.connect_to(j10, angles=(10, 20, 30))\n\nshow_all(render_joints=True, transparent=True)\n" + }, + { + "id": "examples/key_cap", + "source": "examples/key_cap.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nwith BuildPart() as key_cap:\n # Start with the plan of the key cap and extrude it\n with BuildSketch() as plan:\n Rectangle(18 * MM, 18 * MM)\n extrude(amount=10 * MM, taper=15)\n # Create a dished top\n with Locations((0, -3 * MM, 47 * MM)):\n Sphere(40 * MM, mode=Mode.SUBTRACT, rotation=(90, 0, 0))\n # Fillet all the edges except the bottom\n fillet(\n key_cap.edges().filter_by_position(Axis.Z, 0, 30 * MM, inclusive=(False, True)),\n radius=1 * MM,\n )\n # Hollow out the key by subtracting a scaled version\n scale(by=(0.925, 0.925, 0.85), mode=Mode.SUBTRACT)\n\n # First find the size of the internal cavity at 4*MM\n key_cap_section = section(key_cap.part, Plane.XY.offset(4 * MM)).face()\n key_cap_internal_size = key_cap_section.inner_wires()[0].bounding_box().size\n\n # Add supporting ribs while leaving room for switch activation\n with BuildSketch(Plane(origin=(0, 0, 4 * MM))):\n Rectangle(key_cap_internal_size.X, 0.5 * MM)\n Rectangle(0.5 * MM, key_cap_internal_size.Y)\n Circle(radius=5.5 * MM / 2)\n # Extrude the mount and ribs to the key cap underside\n extrude(until=Until.NEXT)\n # Find the face on the bottom of the ribs to build onto\n rib_bottom = key_cap.faces().filter_by_position(Axis.Z, 4 * MM, 4 * MM)[0]\n # Add the switch socket\n with BuildSketch(rib_bottom) as cruciform:\n Circle(radius=5.5 * MM / 2)\n Rectangle(4.1 * MM, 1.17 * MM, mode=Mode.SUBTRACT)\n Rectangle(1.17 * MM, 4.1 * MM, mode=Mode.SUBTRACT)\n extrude(amount=3.5 * MM, mode=Mode.ADD)\n\nassert abs(key_cap.part.volume - 644.8900473617498) < 1e-3\n\nshow(key_cap, alphas=[0.3])\n# [End]\n" + }, + { + "id": "examples/key_cap_algebra", + "source": "examples/key_cap_algebra.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n# Taper Extrude and Extrude to \"next\" while creating a Cherry MX key cap\n# See: https://www.cherrymx.de/en/dev.html\n\nplan = Rectangle(18 * MM, 18 * MM)\nkey_cap = extrude(plan, amount=10 * MM, taper=15)\n\n# Create a dished top\nkey_cap -= Location((0, -3 * MM, 47 * MM), (90, 0, 0)) * Sphere(40 * MM)\n\n# Fillet all the edges except the bottom\nkey_cap = fillet(\n key_cap.edges().filter_by_position(Axis.Z, 0, 30 * MM, inclusive=(False, True)),\n radius=1 * MM,\n)\n\n# Hollow out the key by subtracting a scaled version\nkey_cap -= scale(key_cap, (0.925, 0.925, 0.85))\n\n\n# Add supporting ribs while leaving room for switch activation\n# First find the size of the internal cavity at 4*MM\nkey_cap_section = section(key_cap, Plane.XY.offset(4 * MM)).face()\nkey_cap_internal_size = key_cap_section.inner_wires()[0].bounding_box().size\n# Use this size to ensure the ribs fit within the keycap cavity\nribs = Rectangle(key_cap_internal_size.X, 0.5 * MM)\nribs += Rectangle(0.5 * MM, key_cap_internal_size.Y)\nribs += Circle(radius=5.51 * MM / 2)\n\n# Extrude the mount and ribs to the key cap underside\nkey_cap += extrude(Pos(0, 0, 4 * MM) * ribs, until=Until.NEXT, target=key_cap)\n\n# Add the switch socket\nsocket = Circle(radius=5.5 * MM / 2)\nsocket -= Rectangle(4.1 * MM, 1.17 * MM)\nsocket -= Rectangle(1.17 * MM, 4.1 * MM)\nkey_cap += extrude(Plane.XY.offset(4 * MM) * socket, amount=-3.5 * MM)\n\nshow(key_cap, alphas=[0.3])\n# [End]\n" + }, + { + "id": "examples/lego", + "source": "examples/lego.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\nGEN_DOCS = False\npip_count = 6\n\nlego_unit_size = 8\npip_height = 1.8\npip_diameter = 4.8\nblock_length = lego_unit_size * pip_count\nblock_width = 16\nbase_height = 9.6\nblock_height = base_height + pip_height\nsupport_outer_diameter = 6.5\nsupport_inner_diameter = 4.8\nridge_width = 0.6\nridge_depth = 0.3\nwall_thickness = 1.2\n\nwith BuildPart() as lego:\n # Draw the bottom of the block\n with BuildSketch() as plan:\n # Start with a Rectangle the size of the block\n perimeter = Rectangle(width=block_length, height=block_width)\n if GEN_DOCS:\n exporter = ExportSVG(scale=6)\n exporter.add_shape(plan.sketch)\n exporter.write(\"assets/lego_step4.svg\")\n # Subtract an offset to create the block walls\n offset(\n perimeter,\n -wall_thickness,\n kind=Kind.INTERSECTION,\n mode=Mode.SUBTRACT,\n )\n if GEN_DOCS:\n exporter = ExportSVG(scale=6)\n exporter.add_shape(plan.sketch)\n exporter.write(\"assets/lego_step5.svg\")\n # Add a grid of lengthwise and widthwise bars\n with GridLocations(x_spacing=0, y_spacing=lego_unit_size, x_count=1, y_count=2):\n Rectangle(width=block_length, height=ridge_width)\n with GridLocations(lego_unit_size, 0, pip_count, 1):\n Rectangle(width=ridge_width, height=block_width)\n if GEN_DOCS:\n exporter = ExportSVG(scale=6)\n exporter.add_shape(plan.sketch)\n exporter.write(\"assets/lego_step6.svg\")\n # Subtract a rectangle leaving ribs on the block walls\n Rectangle(\n block_length - 2 * (wall_thickness + ridge_depth),\n block_width - 2 * (wall_thickness + ridge_depth),\n mode=Mode.SUBTRACT,\n )\n if GEN_DOCS:\n exporter = ExportSVG(scale=6)\n exporter.add_shape(plan.sketch)\n exporter.write(\"assets/lego_step7.svg\")\n # Add a row of hollow circles to the center\n with GridLocations(\n x_spacing=lego_unit_size, y_spacing=0, x_count=pip_count - 1, y_count=1\n ):\n Circle(radius=support_outer_diameter / 2)\n Circle(radius=support_inner_diameter / 2, mode=Mode.SUBTRACT)\n if GEN_DOCS:\n exporter = ExportSVG(scale=6)\n exporter.add_shape(plan.sketch)\n exporter.write(\"assets/lego_step8.svg\")\n # Extrude this base sketch to the height of the walls\n extrude(amount=base_height - wall_thickness)\n if GEN_DOCS:\n visible, hidden = lego.part.project_to_viewport((-5, -30, 50))\n exporter = ExportSVG(scale=6)\n exporter.add_layer(\"Visible\")\n exporter.add_layer(\n \"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT\n )\n exporter.add_shape(visible, layer=\"Visible\")\n exporter.add_shape(hidden, layer=\"Hidden\")\n exporter.write(\"assets/lego_step9.svg\")\n # Create a box on the top of the walls\n with Locations((0, 0, lego.vertices().sort_by(Axis.Z)[-1].Z)):\n # Create the top of the block\n Box(\n length=block_length,\n width=block_width,\n height=wall_thickness,\n align=(Align.CENTER, Align.CENTER, Align.MIN),\n )\n if GEN_DOCS:\n visible, hidden = lego.part.project_to_viewport((-5, -30, 50))\n exporter = ExportSVG(scale=6)\n exporter.add_layer(\"Visible\")\n exporter.add_layer(\n \"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT\n )\n exporter.add_shape(visible, layer=\"Visible\")\n exporter.add_shape(hidden, layer=\"Hidden\")\n exporter.write(\"assets/lego_step10.svg\")\n # Create a workplane on the top of the block\n with BuildPart(lego.faces().sort_by(Axis.Z)[-1]):\n # Create a grid of pips\n with GridLocations(lego_unit_size, lego_unit_size, pip_count, 2):\n Cylinder(\n radius=pip_diameter / 2,\n height=pip_height,\n align=(Align.CENTER, Align.CENTER, Align.MIN),\n )\n if GEN_DOCS:\n visible, hidden = lego.part.project_to_viewport((-100, -100, 50))\n exporter = ExportSVG(scale=6)\n exporter.add_layer(\"Visible\")\n exporter.add_layer(\n \"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT\n )\n exporter.add_shape(visible, layer=\"Visible\")\n exporter.add_shape(hidden, layer=\"Hidden\")\n exporter.write(\"assets/lego.svg\")\n\nassert abs(lego.part.volume - 3212.187337781355) < 1e-3\n\nshow_object(lego.part, name=\"lego\")\n" + }, + { + "id": "examples/lego_algebra", + "source": "examples/lego_algebra.py", + "kind": "example", + "code": "from build123d import *\n\npip_count = 6\n\nlego_unit_size = 8\npip_height = 1.8\npip_diameter = 4.8\nblock_length = lego_unit_size * pip_count\nblock_width = 16\nbase_height = 9.6\nblock_height = base_height + pip_height\nsupport_outer_diameter = 6.5\nsupport_inner_diameter = 4.8\nridge_width = 0.6\nridge_depth = 0.3\nwall_thickness = 1.2\n\n\n# Draw the bottom of the block\n\n# Start with a Rectangle the size of the block\nplan = Rectangle(width=block_length, height=block_width)\n\n# Subtract an offset to create the block walls\nplan -= offset(\n plan,\n -wall_thickness,\n kind=Kind.INTERSECTION,\n)\n# Add a grid of lengthwise and widthwise bars\nlocs = GridLocations(x_spacing=0, y_spacing=lego_unit_size, x_count=1, y_count=2)\nplan += locs * Rectangle(width=block_length, height=ridge_width)\n\nlocs = GridLocations(lego_unit_size, 0, pip_count, 1)\nplan += locs * Rectangle(width=ridge_width, height=block_width)\n\n# Subtract a rectangle leaving ribs on the block walls\nplan -= Rectangle(\n block_length - 2 * (wall_thickness + ridge_depth),\n block_width - 2 * (wall_thickness + ridge_depth),\n)\n\n# Add a row of hollow circles to the center\nlocs = GridLocations(\n x_spacing=lego_unit_size, y_spacing=0, x_count=pip_count - 1, y_count=1\n)\nring = Circle(support_outer_diameter / 2) - Circle(support_inner_diameter / 2)\nplan += locs * ring\n\n# Extrude this base sketch to the height of the walls\nlego = extrude(plan, amount=base_height - wall_thickness)\n\n# Create a box on the top of the walls and the top of the block\nlego += Pos(0, 0, lego.vertices().sort_by().last.Z) * Box(\n length=block_length,\n width=block_width,\n height=wall_thickness,\n align=(Align.CENTER, Align.CENTER, Align.MIN),\n)\n\n# Create a workplane on the top of the block\nplane = Plane(lego.faces().sort_by().last)\n\n# Create a grid of pips\n\nlocs = GridLocations(lego_unit_size, lego_unit_size, pip_count, 2)\nlego += (\n plane\n * locs\n * Cylinder(\n radius=pip_diameter / 2,\n height=pip_height,\n align=(Align.CENTER, Align.CENTER, Align.MIN),\n )\n)\n\nif \"show_object\" in locals():\n show_object(lego, name=\"lego\")\n" + }, + { + "id": "examples/loft", + "source": "examples/loft.py", + "kind": "example", + "code": "# [Code]\n\nfrom math import pi, sin\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nwith BuildPart() as art:\n slice_count = 10\n for i in range(slice_count + 1):\n with BuildSketch(Plane(origin=(0, 0, i * 3), z_dir=(0, 0, 1))) as slice:\n Circle(10 * sin(i * pi / slice_count) + 5)\n loft()\n top_bottom = art.faces().filter_by(GeomType.PLANE)\n offset(openings=top_bottom, amount=0.5)\n\nwant = 1306.3405290344635\ngot = art.part.volume\ndelta = abs(got - want)\ntolerance = want * 1e-5\nassert delta < tolerance, f\"{delta=} is greater than {tolerance=}; {got=}, {want=}\"\n\nshow(art, names=[\"art\"])\n# [End]\n" + }, + { + "id": "examples/loft_algebra", + "source": "examples/loft_algebra.py", + "kind": "example", + "code": "# [Code]\n\nfrom math import pi, sin\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nslice_count = 10\n\nart = Sketch()\nfor i in range(slice_count + 1):\n plane = Plane(origin=(0, 0, i * 3), z_dir=(0, 0, 1))\n art += plane * Circle(10 * sin(i * pi / slice_count) + 5)\n\nart = loft(art)\ntop_bottom = art.faces().filter_by(GeomType.PLANE)\nart = offset(art, openings=top_bottom, amount=0.5)\n\nshow(art, names=[\"art\"])\n# [End]\n" + }, + { + "id": "examples/maker_coin", + "source": "examples/maker_coin.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n# [Code]\n# Coin Parameters\ndiameter, thickness = 50 * MM, 10 * MM\n\nwith BuildPart() as maker_coin:\n # On XZ plane draw the profile of half the coin\n with BuildSketch(Plane.XZ) as profile:\n with BuildLine() as outline:\n l1 = Polyline((0, thickness * 0.6), (0, 0), ((diameter - thickness) / 2, 0))\n l2 = JernArc(\n start=l1 @ 1, tangent=l1 % 1, radius=thickness / 2, arc_size=300\n ) # extend the arc beyond the intersection but not closed\n l3 = DoubleTangentArc(l1 @ 0, tangent=(1, 0), other=l2)\n make_face() # make it a 2D shape\n revolve() # revolve 360\u00b0\n\n # Pattern the detents around the coin\n with BuildSketch() as detents:\n with PolarLocations(radius=(diameter + 5) / 2, count=8):\n Circle(thickness * 1.4 / 2)\n extrude(amount=thickness, mode=Mode.SUBTRACT) # cut away the detents\n\n fillet(maker_coin.edges(Select.NEW), 2) # fillet the cut edges\n\n # Add an embossed label\n with BuildSketch(Plane.XY.offset(thickness)) as label: # above coin\n Text(\"OS\", font_size=15)\n project() # label on top of coin\n extrude(amount=-thickness / 5, mode=Mode.SUBTRACT) # emboss label\n\nshow(maker_coin)\n# [End]\n" + }, + { + "id": "examples/mixed_algebra_context", + "source": "examples/mixed_algebra_context.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\n# Mix context and algebra api for parts\n\nb = Box(1, 2, 3) + Cylinder(0.75, 2.5)\n\nwith BuildPart() as bp:\n add(b)\n Cylinder(0.4, 6, mode=Mode.SUBTRACT)\n\nc = bp.part - Plane.YZ * Cylinder(0.2, 6)\n\n# Mix context and algebra api for sketches\n\nr = Rectangle(1, 2) + Circle(0.75)\n\nwith BuildSketch() as bs:\n add(r)\n Circle(0.4, mode=Mode.SUBTRACT)\n\nd = bs.sketch - Pos(0, 1) * Circle(0.2)\n\n# Mix context and algebra api for sketches\n\nl1 = Line((-1, 0), (1, 1)) + Line((1, 1), (2, 4))\n\nwith BuildLine() as bl:\n add(l1)\n Line((2, 4), (-1, 1))\n\ne = bl.line + ThreePointArc((-1, 0), (-1.5, 0.5), (-1, 1))\n\nshow_object(Pos(0, -2, 0) * c, \"part\")\nshow_object(Pos(0, 2, 0) * d, \"sketch\")\nshow_object(Pos(0, 0, 2) * e, \"curve\")\n" + }, + { + "id": "examples/multiple_workplanes", + "source": "examples/multiple_workplanes.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nwith BuildPart() as obj:\n Box(5, 5, 1)\n with BuildPart(*obj.faces().filter_by(Axis.Z), mode=Mode.SUBTRACT):\n Sphere(1.8)\n\nassert abs(obj.part.volume - 15.083039190168236) < 1e-3\n\nshow(obj)\n" + }, + { + "id": "examples/multiple_workplanes_algebra", + "source": "examples/multiple_workplanes_algebra.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nobj = Box(5, 5, 1)\nplanes = [Plane(f) for f in obj.faces().filter_by(Axis.Z)]\nobj -= planes * Sphere(1.8)\n\nshow(obj)\n" + }, + { + "id": "examples/packed_boxes", + "source": "examples/packed_boxes.py", + "kind": "example", + "code": "import functools\nimport operator\nimport random\nimport build123d as bd\n\nGEN_DOCS = False\n\nrandom.seed(123456)\ntest_boxes = [bd.Box(random.randint(1, 20), random.randint(1, 20), random.randint(1, 5))\n for _ in range(50)]\npacked = bd.pack(test_boxes, 3)\n\n# Lifted from https://build123d.readthedocs.io/en/latest/import_export.html#d-to-2d-projection\ndef export_svg(parts, name):\n part = functools.reduce(operator.add, parts, bd.Part())\n view_port_origin=(0, 0, 150)\n visible, hidden = part.project_to_viewport(view_port_origin)\n max_dimension = max(*bd.Compound(children=visible + hidden).bounding_box().size)\n exporter = bd.ExportSVG(scale=100 / max_dimension)\n exporter.add_layer(\"Visible\")\n exporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=bd.LineType.ISO_DOT)\n exporter.add_shape(visible, layer=\"Visible\")\n exporter.add_shape(hidden, layer=\"Hidden\")\n if GEN_DOCS:\n exporter.write(f\"../docs/assets/{name}.svg\")\n\nexport_svg(test_boxes, \"packed_boxes_input\")\nexport_svg(packed, \"packed_boxes_output\")\n" + }, + { + "id": "examples/pegboard_j_hook", + "source": "examples/pegboard_j_hook.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\npegd = 6.35 + 0.1 # mm ~0.25inch\nc2c = 25.4 # mm 1.0inch\narcd = 7.2\nboth = 10\ntopx = 6\nmidx = 8\nmaind = 0.82 * pegd\nmidd = 1.0 * pegd\nhookd = 23\nhookx = 10\nsplitz = maind / 2 - 0.1\ntopangs = 70\n\nwith BuildPart() as mainp:\n with BuildLine(mode=Mode.PRIVATE) as sprof:\n l1 = Line((-both, 0), (c2c - arcd / 2 - 0.5, 0))\n l2 = JernArc(start=l1 @ 1, tangent=l1 % 1, radius=arcd / 2, arc_size=topangs)\n l3 = PolarLine(\n start=l2 @ 1,\n length=topx,\n direction=l2 % 1,\n )\n l4 = JernArc(start=l3 @ 1, tangent=l3 % 1, radius=arcd / 2, arc_size=-topangs)\n l5 = PolarLine(\n start=l4 @ 1,\n length=topx,\n direction=l4 % 1,\n )\n l6 = JernArc(\n start=l1 @ 0, tangent=(l1 % 0).reverse(), radius=hookd / 2, arc_size=170\n )\n l7 = PolarLine(\n start=l6 @ 1,\n length=hookx,\n direction=l6 % 1,\n )\n with BuildSketch(Plane.YZ):\n Circle(radius=maind / 2)\n sweep(path=sprof.wires()[0])\n with BuildLine(mode=Mode.PRIVATE) as stub:\n l7 = Line((0, 0), (0, midx + maind / 2))\n with BuildSketch(Plane.XZ):\n Circle(radius=midd / 2)\n sweep(path=stub.wires()[0])\n # splits help keep the object 3d printable by reducing overhang\n split(bisect_by=Plane(origin=(0, 0, -splitz)))\n split(bisect_by=Plane(origin=(0, 0, splitz)), keep=Keep.BOTTOM)\n\nshow(mainp)\n# [End]\n" + }, + { + "id": "examples/pegboard_j_hook_algebra", + "source": "examples/pegboard_j_hook_algebra.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\npegd = 6.35 + 0.1 # mm ~0.25inch\nc2c = 25.4 # mm 1.0inch\narcd = 7.2\nboth = 10\ntopx = 6\nmidx = 8\nmaind = 0.82 * pegd\nmidd = 1.0 * pegd\nhookd = 23\nhookx = 10\nsplitz = maind / 2 - 0.1\ntopangs = 70\n\nl1 = Line((-both, 0), (c2c - arcd / 2 - 0.5, 0))\nl2 = JernArc(start=l1 @ 1, tangent=l1 % 1, radius=arcd / 2, arc_size=topangs)\nl3 = PolarLine(\n start=l2 @ 1,\n length=topx,\n direction=l2 % 1,\n)\nl4 = JernArc(start=l3 @ 1, tangent=l3 % 1, radius=arcd / 2, arc_size=-topangs)\nl5 = PolarLine(\n start=l4 @ 1,\n length=topx,\n direction=l4 % 1,\n)\nl6 = JernArc(start=l1 @ 0, tangent=(l1 % 0).reverse(), radius=hookd / 2, arc_size=170)\nl7 = PolarLine(\n start=l6 @ 1,\n length=hookx,\n direction=l6 % 1,\n)\nsprof = Curve() + (l1, l2, l3, l4, l5, l6, l7)\nwire = Wire(sprof.edges()) # TODO sprof.wires() fails\nmainp = sweep(Plane.YZ * Circle(radius=maind / 2), path=wire)\n\nstub = Line((0, 0), (0, midx + maind / 2))\nmainp += sweep(Plane.XZ * Circle(radius=midd / 2), path=stub)\n\n\n# splits help keep the object 3d printable by reducing overhang\nmainp = split(mainp, Plane(origin=(0, 0, -splitz)))\nmainp = split(mainp, Plane(origin=(0, 0, splitz)), keep=Keep.BOTTOM)\n\nshow(mainp)\n# [End]\n" + }, + { + "id": "examples/pillow_block", + "source": "examples/pillow_block.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nheight, width, thickness, padding = 60, 80, 10, 12\nscrew_shaft_radius, screw_head_radius, screw_head_height = 1.5, 3, 3\nbearing_axle_radius, bearing_radius, bearing_thickness = 4, 11, 7\n\n# Build pillow block as an extruded sketch with counter bore holes\nwith BuildPart() as pillow_block:\n with BuildSketch() as plan:\n Rectangle(width, height)\n fillet(plan.vertices(), radius=5)\n extrude(amount=thickness)\n # with Locations((0, 0, thickness)):\n with Locations(pillow_block.faces().sort_by(Axis.Z)[-1]):\n CounterBoreHole(bearing_axle_radius, bearing_radius, bearing_thickness)\n with GridLocations(width - 2 * padding, height - 2 * padding, 2, 2):\n CounterBoreHole(screw_shaft_radius, screw_head_radius, screw_head_height)\n\n# Render the part\nshow(pillow_block)\n" + }, + { + "id": "examples/pillow_block_algebra", + "source": "examples/pillow_block_algebra.py", + "kind": "example", + "code": "from build123d import *\n\nheight, width, thickness, padding = 60, 80, 10, 12\nscrew_shaft_radius, screw_head_radius, screw_head_height = 1.5, 3, 3\nbearing_axle_radius, bearing_radius, bearing_thickness = 4, 11, 7\n\n# Build pillow block as an extruded sketch with counter bore holes\nplan = Rectangle(width, height)\nplan = fillet(plan.vertices(), radius=5)\npillow_block = extrude(plan, thickness)\n\nplane = Plane(pillow_block.faces().sort_by().last)\n\npillow_block -= plane * CounterBoreHole(\n bearing_axle_radius, bearing_radius, bearing_thickness, height\n)\nlocs = GridLocations(width - 2 * padding, height - 2 * padding, 2, 2)\npillow_block -= (\n plane\n * locs\n * CounterBoreHole(screw_shaft_radius, screw_head_radius, screw_head_height, height)\n)\n\n# Render the part\nif \"show_object\" in locals():\n show_object(pillow_block)\n" + }, + { + "id": "examples/platonic_solids", + "source": "examples/platonic_solids.py", + "kind": "example", + "code": "# [Code]\nfrom build123d import *\nfrom math import sqrt\nfrom typing import Union, Literal\nfrom scipy.spatial import ConvexHull\n\n# [removed by collect.py] from ocp_vscode import show\n\nPHI = (1 + sqrt(5)) / 2 # The Golden Ratio\n\n\nclass PlatonicSolid(BasePartObject):\n \"\"\"Part Object: Platonic Solid\n\n Create one of the five convex Platonic solids.\n\n Args:\n face_count (Literal[4,6,8,12,20]): number of faces\n diameter (float): double distance to vertices, i.e. maximum size\n rotation (RotationLike, optional): angles to rotate about axes. Defaults to (0, 0, 0).\n align (Union[None, Align, tuple[Align, Align, Align]], optional): align min, center,\n or max of object. Defaults to None.\n mode (Mode, optional): combine mode. Defaults to Mode.ADD.\n \"\"\"\n\n tetrahedron_vertices = [(1, 1, 1), (1, -1, -1), (-1, 1, -1), (-1, -1, 1)]\n\n cube_vertices = [(i, j, k) for i in [-1, 1] for j in [-1, 1] for k in [-1, 1]]\n\n octahedron_vertices = (\n [(i, 0, 0) for i in [-1, 1]]\n + [(0, i, 0) for i in [-1, 1]]\n + [(0, 0, i) for i in [-1, 1]]\n )\n\n dodecahedron_vertices = (\n [(i, j, k) for i in [-1, 1] for j in [-1, 1] for k in [-1, 1]]\n + [(0, i / PHI, j * PHI) for i in [-1, 1] for j in [-1, 1]]\n + [(i / PHI, j * PHI, 0) for i in [-1, 1] for j in [-1, 1]]\n + [(i * PHI, 0, j / PHI) for i in [-1, 1] for j in [-1, 1]]\n )\n\n icosahedron_vertices = (\n [(0, i, j * PHI) for i in [-1, 1] for j in [-1, 1]]\n + [(i, j * PHI, 0) for i in [-1, 1] for j in [-1, 1]]\n + [(i * PHI, 0, j) for i in [-1, 1] for j in [-1, 1]]\n )\n\n vertices_lookup = {\n 4: tetrahedron_vertices,\n 6: cube_vertices,\n 8: octahedron_vertices,\n 12: dodecahedron_vertices,\n 20: icosahedron_vertices,\n }\n _applies_to = [BuildPart._tag]\n\n def __init__(\n self,\n face_count: Literal[4, 6, 8, 12, 20],\n diameter: float = 1.0,\n rotation: RotationLike = (0, 0, 0),\n align: Union[None, Align, tuple[Align, Align, Align]] = None,\n mode: Mode = Mode.ADD,\n ):\n try:\n platonic_vertices = PlatonicSolid.vertices_lookup[face_count]\n except KeyError:\n raise ValueError(\n f\"face_count must be one of 4, 6, 8, 12, or 20 not {face_count}\"\n )\n\n # Create a convex hull from the vertices\n hull = ConvexHull(platonic_vertices).simplices.tolist()\n\n # Create faces from the vertex indices\n platonic_faces = []\n for face_vertex_indices in hull:\n corner_vertices = [platonic_vertices[i] for i in face_vertex_indices]\n platonic_faces.append(Face(Wire.make_polygon(corner_vertices)))\n\n # Create the solid from the Faces\n platonic_solid = Solid(Shell(platonic_faces)).clean()\n\n # By definition, all vertices are the same distance from the origin so\n # scale proportionally to this distance\n platonic_solid = platonic_solid.scale(\n (diameter / 2) / Vector(platonic_solid.vertices()[0]).length\n )\n\n super().__init__(part=platonic_solid, rotation=rotation, align=align, mode=mode)\n\n\nsolids = [\n Rot(0, 0, 72 * i) * Pos(1, 0, 0) * PlatonicSolid(faces)\n for i, faces in enumerate([4, 6, 8, 12, 20])\n]\nshow(solids)\n\n# [End]\n" + }, + { + "id": "examples/playing_cards", + "source": "examples/playing_cards.py", + "kind": "example", + "code": "# [Code]\n\nfrom typing import Literal\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\n\n# [Club]\nclass Club(BaseSketchObject):\n def __init__(\n self,\n height: float,\n rotation: float = 0,\n align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),\n mode: Mode = Mode.ADD,\n ):\n with BuildSketch() as club:\n with BuildLine():\n l0 = Line((0, -188), (76, -188))\n b0 = Bezier(l0 @ 1, (61, -185), (33, -173), (17, -81))\n b1 = Bezier(b0 @ 1, (49, -128), (146, -145), (167, -67))\n b2 = Bezier(b1 @ 1, (187, 9), (94, 52), (32, 18))\n b3 = Bezier(b2 @ 1, (92, 57), (113, 188), (0, 188))\n mirror(about=Plane.YZ)\n make_face()\n scale(by=height / club.sketch.bounding_box().size.Y)\n super().__init__(obj=club.sketch, rotation=rotation, align=align, mode=mode)\n\n\n# [Club]\n\n\nclass Spade(BaseSketchObject):\n def __init__(\n self,\n height: float,\n rotation: float = 0,\n align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),\n mode: Mode = Mode.ADD,\n ):\n with BuildSketch() as spade:\n with BuildLine():\n b0 = Bezier((0, 198), (6, 190), (41, 127), (112, 61))\n b1 = Bezier(b0 @ 1, (242, -72), (114, -168), (11, -105))\n b2 = Bezier(b1 @ 1, (31, -174), (42, -179), (53, -198))\n l0 = Line(b2 @ 1, (0, -198))\n mirror(about=Plane.YZ)\n make_face()\n scale(by=height / spade.sketch.bounding_box().size.Y)\n super().__init__(obj=spade.sketch, rotation=rotation, align=align, mode=mode)\n\n\nclass Heart(BaseSketchObject):\n def __init__(\n self,\n height: float,\n rotation: float = 0,\n align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),\n mode: Mode = Mode.ADD,\n ):\n with BuildSketch() as heart:\n with BuildLine():\n b1 = Bezier((0, 146), (20, 169), (67, 198), (97, 198))\n b2 = Bezier(b1 @ 1, (125, 198), (151, 186), (168, 167))\n b3 = Bezier(b2 @ 1, (197, 133), (194, 88), (158, 31))\n b4 = Bezier(b3 @ 1, (126, -13), (94, -48), (62, -95))\n b5 = Bezier(b4 @ 1, (40, -128), (0, -198))\n mirror(about=Plane.YZ)\n make_face()\n scale(by=height / heart.sketch.bounding_box().size.Y)\n super().__init__(obj=heart.sketch, rotation=rotation, align=align, mode=mode)\n\n\nclass Diamond(BaseSketchObject):\n def __init__(\n self,\n height: float,\n rotation: float = 0,\n align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),\n mode: Mode = Mode.ADD,\n ):\n with BuildSketch() as diamond:\n with BuildLine():\n Bezier((135, 0), (94, 69), (47, 134), (0, 198))\n mirror(about=Plane.XZ)\n mirror(about=Plane.YZ)\n make_face()\n scale(by=height / diamond.sketch.bounding_box().size.Y)\n super().__init__(obj=diamond.sketch, rotation=rotation, align=align, mode=mode)\n\n\ncard_width = 2.5 * IN\ncard_length = 3.5 * IN\ndeck = 0.5 * IN\nwall = 4 * MM\ngap = 0.5 * MM\n\nwith BuildPart() as box_builder:\n with BuildSketch() as plan:\n Rectangle(card_width + 2 * wall, card_length + 2 * wall)\n fillet(plan.vertices(), radius=card_width / 15)\n extrude(amount=wall / 2)\n with BuildSketch(box_builder.faces().sort_by(Axis.Z)[-1]) as walls:\n add(plan.sketch)\n offset(plan.sketch, amount=-wall, mode=Mode.SUBTRACT)\n extrude(amount=deck / 2)\n with BuildSketch(box_builder.faces().sort_by(Axis.Z)[-1]) as inset_walls:\n offset(plan.sketch, amount=-(wall + gap) / 2, mode=Mode.ADD)\n offset(plan.sketch, amount=-wall, mode=Mode.SUBTRACT)\n extrude(amount=deck / 2)\n\nwith BuildPart() as lid_builder:\n with BuildSketch() as outset_walls:\n add(plan.sketch)\n offset(plan.sketch, amount=-(wall - gap) / 2, mode=Mode.SUBTRACT)\n extrude(amount=deck / 2)\n with BuildSketch(lid_builder.faces().sort_by(Axis.Z)[-1]) as top:\n add(plan.sketch)\n extrude(amount=wall / 2)\n with BuildSketch(lid_builder.faces().sort_by(Axis.Z)[-1]):\n holes = GridLocations(\n 3 * card_width / 5, 3 * card_length / 5, 2, 2\n ).local_locations\n for i, hole in enumerate(holes):\n with Locations(hole) as hole_loc:\n if i == 0:\n Heart(card_length / 5)\n elif i == 1:\n Diamond(card_length / 5)\n elif i == 2:\n Spade(card_length / 5)\n elif i == 3:\n Club(card_length / 5)\n extrude(amount=-wall, mode=Mode.SUBTRACT)\n\nbox = Compound(\n [box_builder.part, lid_builder.part.moved(Location((0, 0, (wall + deck) / 2)))]\n)\nvisible, hidden = box.project_to_viewport((70, -50, 120))\nmax_dimension = max(*Compound(children=visible + hidden).bounding_box().size)\nexporter = ExportSVG(scale=100 / max_dimension)\nexporter.add_layer(\"Visible\")\nexporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)\nexporter.add_shape(visible, layer=\"Visible\")\nexporter.add_shape(hidden, layer=\"Hidden\")\n# exporter.write(f\"assets/card_box.svg\")\n\n\nclass PlayingCard(BaseSketchObject):\n \"\"\"PlayingCard\n\n A standard playing card modelled as a Face.\n\n Args:\n rank (Literal['A', '2' .. '10', 'J', 'Q', 'K']): card rank\n suit (Literal['Clubs', 'Spades', 'Hearts', 'Diamonds']): card suit\n \"\"\"\n\n width = 2.5 * IN\n height = 3.5 * IN\n suits = {\"Clubs\": Club, \"Spades\": Spade, \"Hearts\": Heart, \"Diamonds\": Diamond}\n ranks = [\"A\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\", \"J\", \"Q\", \"K\"]\n\n def __init__(\n self,\n rank: Literal[\"A\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\", \"J\", \"Q\", \"K\"],\n suit: Literal[\"Clubs\", \"Spades\", \"Hearts\", \"Diamonds\"],\n rotation: float = 0,\n align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),\n mode: Mode = Mode.ADD,\n ):\n with BuildSketch() as playing_card:\n Rectangle(\n PlayingCard.width, PlayingCard.height, align=(Align.MIN, Align.MIN)\n )\n fillet(playing_card.vertices(), radius=PlayingCard.width / 15)\n with Locations(\n (\n PlayingCard.width / 7,\n 8 * PlayingCard.height / 9,\n )\n ):\n Text(\n txt=rank,\n font_size=PlayingCard.width / 7,\n mode=Mode.SUBTRACT,\n )\n with Locations(\n (\n PlayingCard.width / 7,\n 7 * PlayingCard.height / 9,\n )\n ):\n PlayingCard.suits[suit](\n height=PlayingCard.width / 12, mode=Mode.SUBTRACT\n )\n with Locations(\n (\n 6 * PlayingCard.width / 7,\n 1 * PlayingCard.height / 9,\n )\n ):\n Text(\n txt=rank,\n font_size=PlayingCard.width / 7,\n rotation=180,\n mode=Mode.SUBTRACT,\n )\n with Locations(\n (\n 6 * PlayingCard.width / 7,\n 2 * PlayingCard.height / 9,\n )\n ):\n PlayingCard.suits[suit](\n height=PlayingCard.width / 12, rotation=180, mode=Mode.SUBTRACT\n )\n rank_int = PlayingCard.ranks.index(rank) + 1\n rank_int = rank_int if rank_int < 10 else 1\n with Locations((PlayingCard.width / 2, PlayingCard.height / 2)):\n center_radius = 0 if rank_int == 1 else PlayingCard.width / 3.5\n suit_rotation = 0 if rank_int == 1 else -90\n suit_height = (\n 0.00159 * rank_int**2 - 0.0380 * rank_int + 0.37\n ) * PlayingCard.width\n with PolarLocations(\n radius=center_radius,\n count=rank_int,\n start_angle=90 if rank_int > 1 else 0,\n ):\n PlayingCard.suits[suit](\n height=suit_height,\n rotation=suit_rotation,\n mode=Mode.SUBTRACT,\n )\n super().__init__(\n obj=playing_card.sketch, rotation=rotation, align=align, mode=mode\n )\n\n\nace_spades = PlayingCard(rank=\"A\", suit=\"Spades\", align=Align.MIN)\nace_spades.color = Color(\"white\")\nking_hearts = PlayingCard(rank=\"K\", suit=\"Hearts\", align=Align.MIN)\nking_hearts.color = Color(\"white\")\nqueen_clubs = PlayingCard(rank=\"Q\", suit=\"Clubs\", align=Align.MIN)\nqueen_clubs.color = Color(\"white\")\njack_diamonds = PlayingCard(rank=\"J\", suit=\"Diamonds\", align=Align.MIN)\njack_diamonds.color = Color(\"white\")\nten_spades = PlayingCard(rank=\"10\", suit=\"Spades\", align=Align.MIN)\nten_spades.color = Color(\"white\")\n\nhand = Compound(\n children=[\n Rot(0, 0, -20) * Pos(0, 0, 0) * ace_spades,\n Rot(0, 0, -10) * Pos(0, 0, -1) * king_hearts,\n Rot(0, 0, 0) * Pos(0, 0, -2) * queen_clubs,\n Rot(0, 0, 10) * Pos(0, 0, -3) * jack_diamonds,\n Rot(0, 0, 20) * Pos(0, 0, -4) * ten_spades,\n ]\n)\n\nshow_object(Pos(-20, 40) * hand)\nshow_object(box_builder.part, \"box_builder\")\nshow_object(\n Pos(0, 0, (wall + deck) / 2) * lid_builder.part,\n \"lid_builder\",\n options={\"alpha\": 0.7},\n)\n# [End]\n" + }, + { + "id": "examples/projection", + "source": "examples/projection.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\n# A sphere used as a projection target\nsphere = Solid.make_sphere(50, angle1=-90)\n\n\"\"\"Example 1 - Mapping A Face on Sphere\"\"\"\nprojection_direction = Vector(0, 1, 0)\n\nsquare = Face.make_rect(20, 20, Plane.ZX.offset(-80))\nsquare_projected = square.project_to_shape(sphere, projection_direction)\nsquare_solids = Compound([Solid.thicken(f, 2) for f in square_projected])\nprojection_beams = [\n Solid.make_loft(\n [\n square.outer_wire(),\n square.outer_wire().translate(Vector(0, 160, 0)),\n ]\n )\n]\n\n\"\"\"Example 2 - Flat Projection of Text on Sphere\"\"\"\nprojection_direction = Vector(0, -1, 0)\nflat_planar_text_faces = (\n Compound.make_text(\"Flat\", font_size=30).rotate(Axis.X, 90).faces()\n)\nflat_projected_text_faces = Compound(\n [\n f.project_to_shape(sphere, projection_direction)[0]\n for f in flat_planar_text_faces\n ]\n).moved(Location((-100, -100)))\nflat_projection_beams = Compound(\n [Solid.extrude(f, projection_direction * 80) for f in flat_planar_text_faces]\n).moved(Location((-100, -100)))\n\n\n\"\"\"Example 3 - Project a text string along a path onto a shape\"\"\"\narch_path: Edge = (\n sphere.cut(Solid.make_cylinder(80, 100, Plane.YZ).locate(Location((-50, 0, -70))))\n .edges()\n .sort_by(Axis.Z)[0]\n)\narch_path_start = Vertex(arch_path.position_at(0))\ntext = Compound.make_text(\n txt=\"'the quick brown fox jumped over the lazy dog'\",\n font_size=15,\n align=(Align.MIN, Align.CENTER),\n)\nprojected_text = Sketch(sphere.project_faces(text, path=arch_path))\n\n# Example 1\nshow_object(sphere, name=\"sphere_solid\", options={\"alpha\": 0.8})\nshow_object(square, name=\"square\")\nshow_object(square_solids, name=\"square_solids\")\nshow_object(\n Compound(projection_beams),\n name=\"projection_beams\",\n options={\"alpha\": 0.9, \"color\": (170 / 255, 170 / 255, 255 / 255)},\n)\n\n# Example 2\nshow_object(\n sphere.moved(Location((-100, -100))),\n name=\"sphere_solid for text\",\n options={\"alpha\": 0.8},\n)\nshow_object(flat_projected_text_faces, name=\"flat_projected_text_faces\")\nshow_object(\n flat_projection_beams,\n name=\"flat_projection_beams\",\n options={\"alpha\": 0.95, \"color\": (170 / 255, 170 / 255, 255 / 255)},\n)\n\n# Example 3\nshow_object(\n sphere.moved(Location((100, 100))),\n name=\"sphere_solid for text on path\",\n options={\"alpha\": 0.8},\n)\nshow_object(projected_text.moved(Location((100, 100))), name=\"projected_text on path\")\n" + }, + { + "id": "examples/projection_algebra", + "source": "examples/projection_algebra.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\n# A sphere used as a projection target\nsphere = Sphere(50)\n\n\"\"\"Example 1 - Mapping A Face on Sphere\"\"\"\nprojection_direction = Vector(0, 1, 0)\n\nsquare = Plane.ZX.offset(-80) * Rectangle(20, 20)\nsquare_projected = square.faces()[0].project_to_shape(sphere, projection_direction)\nsquare_solids = Part() + [Solid.thicken(f, 2) for f in square_projected]\nface = square.faces()[0]\nprojection_beams = loft([face, Pos(0, 160, 0) * face])\n\n\n\"\"\"Example 2 - Flat Projection of Text on Sphere\"\"\"\nprojection_direction = Vector(0, -1, 0)\n\nflat_planar_text = Rot(90, 0, 0) * Text(\"Flat\", font_size=30)\nflat_projected_text_faces = Sketch() + [\n f.project_to_shape(sphere, projection_direction)[0]\n for f in flat_planar_text.faces()\n]\nflat_projection_beams = Part() + [\n extrude(f, dir=projection_direction, amount=80) for f in flat_planar_text.faces()\n]\n\n\n\"\"\"Example 3 - Project a text string along a path onto a shape\"\"\"\ncyl = Plane.YZ * Cylinder(80, 100, align=(Align.CENTER, Align.CENTER, Align.MIN))\nobj = sphere - Pos(-50, 0, -70) * cyl\n\narch_path: Edge = obj.edges().sort_by().first\n\narch_path_start = Vertex(arch_path.position_at(0))\ntext = Text(\n \"'the quick brown fox jumped over the lazy dog'\",\n font_size=15,\n align=(Align.MIN, Align.CENTER),\n)\nprojected_text = Sketch(sphere.project_faces(text.faces(), path=arch_path))\n\n# Example 1\nshow_object(sphere, name=\"sphere_solid\", options={\"alpha\": 0.8})\nshow_object(square, name=\"square\")\nshow_object(square_solids, name=\"square_solids\")\nshow_object(\n Compound(projection_beams),\n name=\"projection_beams\",\n options={\"alpha\": 0.9, \"color\": (170 / 255, 170 / 255, 255 / 255)},\n)\n\n# Example 2\nshow_object(\n Pos(-100, -100) * sphere,\n name=\"sphere_solid for text\",\n options={\"alpha\": 0.8},\n)\nshow_object(\n Pos(-100, -100) * flat_projected_text_faces, name=\"flat_projected_text_faces\"\n)\nshow_object(\n Pos(-100, -100) * flat_projection_beams,\n name=\"flat_projection_beams\",\n options={\"alpha\": 0.95, \"color\": (170 / 255, 170 / 255, 255 / 255)},\n)\n\n# Example 3\nshow_object(\n sphere.moved(Location((100, 100))),\n name=\"sphere_solid for text on path\",\n options={\"alpha\": 0.8},\n)\nshow_object(projected_text.moved(Location((100, 100))), name=\"projected_text on path\")\n" + }, + { + "id": "examples/python_logo", + "source": "examples/python_logo.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n\nclass PythonLogo(BaseSketchObject):\n \"\"\"PythonLogo\n\n Args:\n size (float): max size (Y direction - although the logo is almost square)\n rotation (float, optional): angles to rotate objects. Defaults to 0.\n align (Union[Align, tuple[Align, Align]], optional): align min, center, or max of object.\n Defaults to None.\n mode (Mode, optional): combination mode. Defaults to Mode.ADD.\n \"\"\"\n\n _applies_to = [BuildSketch._tag]\n _logo_colors = {\n \"Cyan-Blue Azure\": Color(75 / 255, 139 / 255, 190 / 255),\n \"Lapis Lazuli\": Color(48 / 255, 105 / 255, 152 / 255),\n \"Shandy\": Color(255 / 255, 232 / 255, 115 / 255),\n \"Sunglow\": Color(255 / 255, 212 / 255, 59 / 255),\n \"Granite Gray\": Color(100 / 255, 100 / 255, 100 / 255),\n }\n\n def __init__(\n self,\n size: float,\n rotation: float = 0,\n align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),\n mode: Mode = Mode.ADD,\n ):\n center = Vector(55.5806770629664, 56.194501214517224)\n\n with BuildSketch() as logo:\n with BuildLine(mode=Mode.PRIVATE) as snake:\n l1 = Bezier(\n (54.918785, 0.00091927389),\n (50.335132, 0.02221727),\n (45.957846, 0.41313697),\n (42.106285, 1.0946693),\n )\n l2 = Bezier(\n l1 @ 1,\n (30.760069, 3.0991731),\n (28.700036, 7.2947714),\n (28.700035, 15.032169),\n )\n l3 = Polyline(\n l2 @ 1,\n (28.700035, 25.250919),\n (55.512535, 25.250919),\n (55.512535, 28.657169),\n (28.700035, 28.657169),\n (18.637535, 28.657169),\n )\n l4 = Bezier(\n l3 @ 1,\n (10.845076, 28.657169),\n (4.0217762, 33.340886),\n (1.8875352, 42.250919),\n )\n l5 = Bezier(\n l4 @ 1,\n (-0.57428478, 52.463885),\n (-0.68347988, 58.836942),\n (1.8875352, 69.500919),\n )\n l6 = Bezier(\n l5 @ 1,\n (3.7934635, 77.438771),\n (8.3450784, 83.094667),\n (16.137535, 83.094669),\n )\n l7 = Polyline(l6 @ 1, (25.356285, 83.094669), (25.356285, 70.844669))\n l8 = Bezier(\n l7 @ 1,\n (25.356285, 61.994767),\n (33.013429, 54.188421),\n (42.106285, 54.188419),\n )\n l9 = Line(l8 @ 1, (68.887535, 54.188419))\n l10 = Bezier(\n l9 @ 1,\n (76.342486, 54.188419),\n (82.293788, 48.050255),\n (82.293785, 40.563419),\n )\n l11 = Line(l10 @ 1, (82.293785, 15.032169))\n l12 = Bezier(\n l11 @ 1,\n (82.293785, 7.7658304),\n (76.163805, 2.3073919),\n (68.887535, 1.0946693),\n )\n l13 = Bezier(\n l12 @ 1,\n (64.281548, 0.32794397),\n (59.502438, -0.02037903),\n (54.918785, 0.00091927389),\n )\n\n with Locations(-center):\n add(snake)\n make_face()\n with Locations(Vector(40.418785, 13.3290442) - center):\n Ellipse(10.0625002 / 2, 10.2187498 / 2, mode=Mode.SUBTRACT)\n add(logo.sketch, rotation=180)\n mirror(about=Plane.YZ, mode=Mode.REPLACE)\n current_size = max(*tuple(logo.sketch.bounding_box().size))\n scale(by=size / current_size)\n\n super().__init__(obj=logo.sketch, rotation=rotation, align=align, mode=mode)\n\n\nif __name__ == \"__main__\":\n show(PythonLogo(10))\n" + }, + { + "id": "examples/roller_coaster", + "source": "examples/roller_coaster.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\nwith BuildLine() as roller_coaster:\n powerup = Spline(\n (0, 0, 0),\n (50, 0, 50),\n (100, 0, 0),\n tangents=((1, 0, 0), (1, 0, 0)),\n tangent_scalars=(0.5, 2),\n )\n corner = RadiusArc(powerup @ 1, (100, 60, 0), -30)\n screw = Helix(75, 150, 15, center=(75, 40, 15), direction=(-1, 0, 0))\n Spline(corner @ 1, screw @ 0, tangents=(corner % 1, screw % 0))\n Spline(screw @ 1, (-100, 30, 10), powerup @ 0, tangents=(screw % 1, powerup % 0))\n\nshow_object(roller_coaster, name=\"roller_coaster\")\n" + }, + { + "id": "examples/roller_coaster_algebra", + "source": "examples/roller_coaster_algebra.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\npowerup = Spline(\n (0, 0, 0),\n (50, 0, 50),\n (100, 0, 0),\n tangents=((1, 0, 0), (1, 0, 0)),\n tangent_scalars=(0.5, 2),\n)\ncorner = RadiusArc(powerup @ 1, (100, 60, 0), -30)\nscrew = Helix(75, 150, 15, center=(75, 40, 15), direction=(-1, 0, 0))\n\nroller_coaster = Curve() + (powerup + corner + screw)\nroller_coaster += Spline(corner @ 1, screw @ 0, tangents=(corner % 1, screw % 0))\nroller_coaster += Spline(\n screw @ 1, (-100, 30, 10), powerup @ 0, tangents=(screw % 1, powerup % 0)\n)\n\nshow_object(roller_coaster)\n" + }, + { + "id": "examples/shamrock", + "source": "examples/shamrock.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n\nclass Shamrock(BaseSketchObject):\n \"\"\"Sketch Object: Shamrock\n\n Adds a four leaf clover\n\n Args:\n height (float): y axis dimension\n rotation (float, optional): angle in degrees. Defaults to 0.\n align (tuple[Align, Align], optional): alignment. Defaults to (Align.CENTER, Align.CENTER).\n mode (Mode, optional): combination mode. Defaults to Mode.ADD.\n \"\"\"\n\n def __init__(\n self,\n height: float,\n rotation: float = 0,\n align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),\n mode: Mode = Mode.ADD,\n ):\n with BuildSketch() as shamrock:\n with BuildLine():\n b0 = Bezier((240, 310), (112, 325), (162, 438), (252, 470))\n b1 = Bezier(b0 @ 1, (136, 431), (73, 589), (179, 643))\n b2 = Bezier(b1 @ 1, (151, 747), (293, 770), (360, 679))\n b3 = Bezier(b2 @ 1, (358, 736), (366, 789), (392, 840))\n l0 = Line(b3 @ 1, (420, 820))\n b4 = Bezier(l0 @ 1, (366, 781), (374, 670), (380, 670))\n b5 = Bezier(b4 @ 1, (400, 794), (506, 789), (528, 727))\n b6 = Bezier(b5 @ 1, (636, 733), (638, 578), (507, 541))\n b7 = Bezier(b6 @ 1, (628, 559), (651, 380), (575, 365))\n b8 = Bezier(b7 @ 1, (592, 269), (420, 268), (417, 361))\n b9 = Bezier(b8 @ 1, (410, 253), (262, 222), b0 @ 0)\n mirror(about=Plane.XZ, mode=Mode.REPLACE)\n make_face()\n scale(by=height / shamrock.sketch.bounding_box().size.Y)\n super().__init__(\n obj=shamrock.sketch.translate(\n -shamrock.sketch.center(CenterOf.BOUNDING_BOX)\n ),\n rotation=rotation,\n align=align,\n mode=mode,\n )\n\n\nwith BuildSketch() as shamrock_example:\n Shamrock(10)\n\nshow(shamrock_example)\n" + }, + { + "id": "examples/stud_wall", + "source": "examples/stud_wall.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\nfrom typing import Union\nimport copy\n\n\n# [Code]\nclass Stud(BasePartObject):\n \"\"\"Part Object: Stud\n\n Create a dimensional framing stud.\n\n Args:\n length (float): stud size\n width (float): stud size\n thickness (float): stud size\n rotation (RotationLike, optional): angles to rotate about axes. Defaults to (0, 0, 0).\n align (Union[Align, tuple[Align, Align, Align]], optional): align min, center,\n or max of object. Defaults to (Align.CENTER, Align.CENTER, Align.MIN).\n mode (Mode, optional): combine mode. Defaults to Mode.ADD.\n \"\"\"\n\n _applies_to = [BuildPart._tag]\n\n def __init__(\n self,\n length: float = 8 * FT,\n width: float = 3.5 * IN,\n thickness: float = 1.5 * IN,\n rotation: RotationLike = (0, 0, 0),\n align: Union[None, Align, tuple[Align, Align, Align]] = (\n Align.CENTER,\n Align.CENTER,\n Align.MIN,\n ),\n mode: Mode = Mode.ADD,\n ):\n self.length = length\n self.width = width\n self.thickness = thickness\n\n # Create the basic shape\n with BuildPart() as stud:\n with BuildSketch():\n RectangleRounded(thickness, width, 0.25 * IN)\n extrude(amount=length)\n\n # Create a Part object with appropriate alignment and rotation\n super().__init__(part=stud.part, rotation=rotation, align=align, mode=mode)\n\n # Add joints to the ends of the stud\n RigidJoint(\"end0\", self, Location())\n RigidJoint(\"end1\", self, Location((0, 0, length), (1, 0, 0), 180))\n\n\nclass StudWall(Compound):\n \"\"\"StudWall\n\n A simple stud wall assembly with top and sole plates.\n\n Args:\n length (float): wall length\n depth (float, optional): stud width. Defaults to 3.5*IN.\n height (float, optional): wall height. Defaults to 8*FT.\n stud_spacing (float, optional): center-to-center. Defaults to 16*IN.\n stud_thickness (float, optional): Defaults to 1.5*IN.\n \"\"\"\n\n def __init__(\n self,\n length: float,\n depth: float = 3.5 * IN,\n height: float = 8 * FT,\n stud_spacing: float = 16 * IN,\n stud_thickness: float = 1.5 * IN,\n ):\n # Create the object that will be used for top and sole plates\n plate = Stud(\n length,\n depth,\n rotation=(0, -90, 0),\n align=(Align.MIN, Align.CENTER, Align.MAX),\n )\n # Define where studs will go on the plates\n stud_locations = Pos(stud_thickness / 2, 0, stud_thickness) * GridLocations(\n stud_spacing, 0, int(length / stud_spacing) + 1, 1, align=Align.MIN\n )\n stud_locations.append(Pos(length - stud_thickness / 2, 0, stud_thickness))\n\n # Create a single stud that will be copied for efficiency\n stud = Stud(height - 2 * stud_thickness, depth, stud_thickness)\n\n # For efficiency studs in the walls are copies with their own position\n studs = []\n for i, loc in enumerate(stud_locations):\n stud_joint = RigidJoint(f\"stud{i}\", plate, loc)\n stud_copy = copy.copy(stud)\n stud_joint.connect_to(stud_copy.joints[\"end0\"])\n studs.append(stud_copy)\n top_plate = copy.copy(plate)\n sole_plate = copy.copy(plate)\n\n # Position the top plate relative to the top of the first stud\n studs[0].joints[\"end1\"].connect_to(top_plate.joints[\"stud0\"])\n\n # Build the assembly of parts\n super().__init__(children=[top_plate, sole_plate] + studs)\n\n # Add joints to the wall\n RigidJoint(\"inside0\", self, Location((depth / 2, depth / 2, 0), (0, 0, 1), 90))\n RigidJoint(\"end0\", self, Location())\n\n\nx_wall = StudWall(13 * FT)\ny_wall = StudWall(9 * FT)\nx_wall.joints[\"inside0\"].connect_to(y_wall.joints[\"end0\"])\n\nshow(x_wall, y_wall, render_joints=False)\n# [End]\n" + }, + { + "id": "examples/tea_cup", + "source": "examples/tea_cup.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nwall_thickness = 3 * MM\nfillet_radius = wall_thickness * 0.49\n\nwith BuildPart() as tea_cup:\n # Create the bowl of the cup as a revolved cross section\n with BuildSketch(Plane.XZ) as bowl_section:\n with BuildLine():\n # Start & end points with control tangents\n s = Spline(\n (30 * MM, 10 * MM),\n (69 * MM, 105 * MM),\n tangents=((1, 0.5), (0.7, 1)),\n tangent_scalars=(1.75, 1),\n )\n # Lines to finish creating \u00bd the bowl shape\n Polyline(s @ 0, s @ 0 + (10 * MM, -10 * MM), (0, 0), (0, (s @ 1).Y), s @ 1)\n make_face() # Create a filled 2D shape\n revolve(axis=Axis.Z)\n # Hollow out the bowl with openings on the top and bottom\n offset(amount=-wall_thickness, openings=tea_cup.faces().filter_by(GeomType.PLANE))\n # Add a bottom to the bowl\n with Locations((0, 0, (s @ 0).Y)):\n Cylinder(radius=(s @ 0).X, height=wall_thickness)\n # Smooth out all the edges\n fillet(tea_cup.edges(), radius=fillet_radius)\n\n # Determine where the handle contacts the bowl\n handle_intersections = [\n tea_cup.part.find_intersection_points(\n Axis(origin=(0, 0, vertical_offset), direction=(1, 0, 0))\n )[-1][0]\n for vertical_offset in [35 * MM, 80 * MM]\n ]\n # Create a path for handle creation\n with BuildLine(Plane.XZ) as handle_path:\n handle_points = [\n Plane.XZ.to_local_coords(point) for point in handle_intersections\n ]\n Spline(\n handle_points[0] - (wall_thickness / 2, 0),\n handle_points[0] + (35 * MM, 30 * MM),\n handle_points[0] + (40 * MM, 60 * MM),\n handle_points[1] - (wall_thickness / 2, 0),\n tangents=((1, 1.25), (-0.2, -1)),\n )\n # Align the cross section to the beginning of the path\n with BuildSketch(handle_path.line ^ 0) as handle_cross_section:\n RectangleRounded(wall_thickness, 8 * MM, fillet_radius)\n sweep() # Sweep handle cross section along path\n\nassert abs(tea_cup.part.volume - 130326) < 1\n\nshow(tea_cup, names=[\"tea cup\"])\n# [End]\ntea_cup.part.color = Color(0xDFDCDA) # Porcelain\nexport_gltf(\n tea_cup.part,\n \"tea_cup.glb\",\n linear_deflection=0.1,\n angular_deflection=1,\n)\n" + }, + { + "id": "examples/tea_cup_algebra", + "source": "examples/tea_cup_algebra.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nwall_thickness = 3 * MM\nfillet_radius = wall_thickness * 0.49\n\n# Create the bowl of the cup as a revolved cross section\n\n# Start & end points with control tangents\ns = Spline(\n (30 * MM, 10 * MM),\n (69 * MM, 105 * MM),\n tangents=((1, 0.5), (0.7, 1)),\n tangent_scalars=(1.75, 1),\n)\n# Lines to finish creating \u00bd the bowl shape\ns += Polyline(s @ 0, s @ 0 + (10 * MM, -10 * MM), (0, 0), (0, (s @ 1).Y), s @ 1)\nbowl_section = Plane.XZ * make_face(s) # Create a filled 2D shape\ntea_cup = revolve(bowl_section, axis=Axis.Z)\n\n# Hollow out the bowl with openings on the top and bottom\ntea_cup = offset(\n tea_cup, -wall_thickness, openings=tea_cup.faces().filter_by(GeomType.PLANE)\n)\n\n# Add a bottom to the bowl\ntea_cup += Pos(0, 0, (s @ 0).Y) * Cylinder(radius=(s @ 0).X, height=wall_thickness)\n\n# Smooth out all the edges\ntea_cup = fillet(tea_cup.edges(), radius=fillet_radius)\n\n# Determine where the handle contacts the bowl\nhandle_intersections = [\n tea_cup.find_intersection_points(\n Axis(origin=(0, 0, vertical_offset), direction=(1, 0, 0))\n )[-1][0]\n for vertical_offset in [35 * MM, 80 * MM]\n]\n\n# Create a path for handle creation\npath_spline = Spline(\n handle_intersections[0] - (wall_thickness / 2, 0, 0),\n handle_intersections[0] + (35 * MM, 0, 30 * MM),\n handle_intersections[0] + (40 * MM, 0, 60 * MM),\n handle_intersections[1] - (wall_thickness / 2, 0, 0),\n tangents=((1, 0, 1.25), (-0.2, 0, -1)),\n)\n\n# Align the cross section to the beginning of the path\nlocation = path_spline ^ 0\nhandle_cross_section = location * RectangleRounded(wall_thickness, 8 * MM, fillet_radius)\n\n# Sweep handle cross section along path\ntea_cup += sweep(handle_cross_section, path=path_spline)\n\n# assert abs(tea_cup.part.volume - 130326.77052487945) < 1e-3\n\nshow(tea_cup, names=[\"tea cup\"])\n# [End]\n" + }, + { + "id": "examples/toy_truck", + "source": "examples/toy_truck.py", + "kind": "example", + "code": "# [Code]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n# Toy Truck Blue\ntruck_color = Color(0x4683CE)\n\n# Create the main truck body \u2014 from bumper to bed, excluding the cab\nwith BuildPart() as body:\n # The body has two axes of symmetry, so we start with a centered sketch.\n # The default workplane is Plane.XY.\n with BuildSketch() as body_skt:\n Rectangle(20, 35)\n # Fillet all the corners of the sketch.\n # Alternatively, you could use RectangleRounded.\n fillet(body_skt.vertices(), 1)\n\n # Extrude the body shape upward\n extrude(amount=10, taper=4)\n # Reuse the sketch by accessing it explicitly\n extrude(body_skt.sketch, amount=8, taper=2)\n\n # Create symmetric fenders on Plane.YZ\n with BuildSketch(Plane.YZ) as fender:\n # The trapezoid has asymmetric angles (80\u00b0, 88\u00b0)\n Trapezoid(18, 6, 80, 88, align=Align.MIN)\n # Fillet top edge vertices (Y-direction highest group)\n fillet(fender.vertices().group_by(Axis.Y)[-1], 1.5)\n\n # Extrude the fender in both directions\n extrude(amount=10.5, both=True)\n\n # Create wheel wells with a shifted sketch on Plane.YZ\n with BuildSketch(Plane.YZ.shift_origin((0, 3.5, 0))) as wheel_well:\n Trapezoid(12, 4, 70, 85, align=Align.MIN)\n fillet(wheel_well.vertices().group_by(Axis.Y)[-1], 2)\n\n # Subtract the wheel well geometry\n extrude(amount=10.5, both=True, mode=Mode.SUBTRACT)\n\n # Fillet the top edges of the body\n fillet(body.edges().group_by(Axis.Z)[-1], 1)\n\n # Isolate a set of body edges and preview before filleting\n body_edges = body.edges().group_by(Axis.Z)[-6]\n fillet(body_edges, 0.1)\n\n # Combine edge groups from both sides of the fender and fillet them\n fender_edges = body.edges().group_by(Axis.X)[0] + body.edges().group_by(Axis.X)[-1]\n fender_edges = fender_edges.group_by(Axis.Z)[1:]\n fillet(fender_edges, 0.4)\n\n # Create a sketch on the front of the truck for the grill\n with BuildSketch(\n Plane.XZ.offset(-body.vertices().sort_by(Axis.Y)[-1].Y - 0.5)\n ) as grill:\n Rectangle(16, 8.5, align=(Align.CENTER, Align.MIN))\n fillet(grill.vertices().group_by(Axis.Y)[-1], 1)\n\n # Add headlights (subtractive circles)\n with Locations((0, 6.5)):\n with GridLocations(12, 0, 2, 1):\n Circle(1, mode=Mode.SUBTRACT)\n\n # Add air vents (subtractive slots)\n with Locations((0, 3)):\n with GridLocations(0, 0.8, 1, 4):\n SlotOverall(10, 0.5, mode=Mode.SUBTRACT)\n\n # Extrude the grill forward\n extrude(amount=2)\n\n # Fillet only the outer grill edges (exclude headlight/vent cuts)\n grill_perimeter = body.faces().sort_by(Axis.Y)[-1].outer_wire()\n fillet(grill_perimeter.edges(), 0.2)\n\n # Create the bumper as a separate part inside the body\n with BuildPart() as bumper:\n # Find the midpoint of a front edge and shift slightly to position the bumper\n front_cnt = body.edges().group_by(Axis.Z)[0].sort_by(Axis.Y)[-1] @ 0.5 - (0, 3)\n\n with BuildSketch() as bumper_plan:\n # Use BuildLine to draw an elliptical arc and offset\n with BuildLine():\n EllipticalCenterArc(front_cnt, 20, 4, start_angle=60, arc_size=60)\n offset(amount=1)\n make_face()\n\n # Extrude the bumper symmetrically\n extrude(amount=1, both=True)\n fillet(bumper.edges(), 0.25)\n\n # Define a joint on top of the body to connect the cab later\n RigidJoint(\"body_top\", joint_location=Location((0, -7.5, 10)))\n body.part.color = truck_color\n\n# Create the cab as an independent part to mount on the body\nwith BuildPart() as cab:\n with BuildSketch() as cab_plan:\n RectangleRounded(16, 16, 1)\n # Split the sketch to work on one symmetric half\n split(bisect_by=Plane.YZ)\n\n # Extrude the cab forward and upward at an angle\n extrude(amount=7, dir=(0, 0.15, 1))\n fillet(cab.edges().group_by(Axis.Z)[-1].group_by(Axis.X)[1:], 1)\n\n # Rear window\n with BuildSketch(Plane.XZ.shift_origin((0, 0, 3))) as rear_window:\n RectangleRounded(8, 4, 0.75)\n extrude(amount=10, mode=Mode.SUBTRACT)\n\n # Front window\n with BuildSketch(Plane.XZ) as front_window:\n RectangleRounded(15.2, 11, 0.75)\n extrude(amount=-10, mode=Mode.SUBTRACT)\n\n # Side windows\n with BuildSketch(Plane.YZ) as side_window:\n with Locations((3.5, 0)):\n with GridLocations(10, 0, 2, 1):\n Trapezoid(9, 5.5, 80, 100, align=(Align.CENTER, Align.MIN))\n fillet(side_window.vertices().group_by(Axis.Y)[-1], 0.5)\n extrude(amount=12, both=True, mode=Mode.SUBTRACT)\n\n # Mirror to complete the cab\n mirror(about=Plane.YZ)\n\n # Define joint on cab base\n RigidJoint(\"cab_base\", joint_location=Location((0, 0, 0)))\n cab.part.color = truck_color\n\n# Attach the cab to the truck body using joints\nbody.joints[\"body_top\"].connect_to(cab.joints[\"cab_base\"])\n\n# Show the result\nshow(body.part, cab.part)\n# [End]\n" + }, + { + "id": "examples/twist_extrude", + "source": "examples/twist_extrude.py", + "kind": "example", + "code": "# [removed by collect.py] from ocp_vscode import show\n\nfrom build123d import *\n\nhex_sketch = RegularPolygon(radius=1, side_count=6)\n\ntwist_extrude = Solid.extrude_linear_with_rotation(\n section=hex_sketch.face(),\n center=(0, 0),\n normal=(0, 0, 5), # extrusion direction and distance\n angle=360 / 5, # 72 degrees of rotation over the extrusion height\n)\n\nshow(twist_extrude)\n" + }, + { + "id": "examples/vase", + "source": "examples/vase.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\nwith BuildPart() as vase:\n with BuildSketch() as profile:\n with BuildLine() as outline:\n l1 = Line((0, 0), (12, 0))\n l2 = RadiusArc(l1 @ 1, (15, 20), 50)\n l3 = Spline(l2 @ 1, (22, 40), (20, 50), tangents=(l2 % 1, (-0.75, 1)))\n l4 = RadiusArc(l3 @ 1, l3 @ 1 + Vector(0, 5), 5)\n l5 = Spline(\n l4 @ 1,\n l4 @ 1 + Vector(2.5, 2.5),\n l4 @ 1 + Vector(0, 5),\n tangents=(l4 % 1, (-1, 0)),\n )\n Polyline(\n l5 @ 1,\n l5 @ 1 + Vector(0, 1),\n (0, (l5 @ 1).Y + 1),\n l1 @ 0,\n )\n make_face()\n revolve(axis=Axis.Y)\n offset(openings=vase.faces().filter_by(Axis.Y)[-1], amount=-1)\n top_edges = (\n vase.edges().filter_by_position(Axis.Y, 60, 62).filter_by(GeomType.CIRCLE)\n )\n fillet(top_edges, radius=0.25)\n fillet(vase.edges().sort_by(Axis.Y)[0], radius=0.5)\n\n\nshow_object(Rot(90, 0, 0) * vase.part, name=\"vase\")\n# [End]\n" + }, + { + "id": "examples/vase_algebra", + "source": "examples/vase_algebra.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\nl1 = Line((0, 0), (12, 0))\nl2 = RadiusArc(l1 @ 1, (15, 20), 50)\nl3 = Spline(l2 @ 1, (22, 40), (20, 50), tangents=(l2 % 1, (-0.75, 1)))\nl4 = RadiusArc(l3 @ 1, l3 @ 1 + Vector(0, 5), 5)\nl5 = Spline(\n l4 @ 1,\n l4 @ 1 + Vector(2.5, 2.5),\n l4 @ 1 + Vector(0, 5),\n tangents=(l4 % 1, (-1, 0)),\n)\noutline = l1 + l2 + l3 + l4 + l5\noutline += Polyline(\n l5 @ 1,\n l5 @ 1 + Vector(0, 1),\n (0, (l5 @ 1).Y + 1),\n l1 @ 0,\n)\nprofile = make_face(outline.edges())\nvase = revolve(profile, Axis.Y)\nvase = offset(vase, openings=vase.faces().sort_by(Axis.Y).last, amount=-1)\n\ntop_edges = vase.edges().filter_by(GeomType.CIRCLE).filter_by_position(Axis.Y, 60, 62)\nvase = fillet(top_edges, radius=0.25)\n\nvase = fillet(vase.edges().sort_by(Axis.Y).first, radius=0.5)\n\nshow_object(Rot(90, 0, 0) * vase, name=\"vase\")\n# [End]\n" + }, + { + "id": "general_examples/ex01", + "source": "docs/general_examples.py #1 (Simple Rectangular Plate)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 1. Simple Rectangular Plate\n# [Ex. 1]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex1:\n Box(length, width, thickness)\n # [Ex. 1]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex1.part)\n" + }, + { + "id": "general_examples/ex02", + "source": "docs/general_examples.py #2 (Plane with Hole)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 2. Plane with Hole\n# [Ex. 2]\nlength, width, thickness = 80.0, 60.0, 10.0\ncenter_hole_dia = 22.0\n\nwith BuildPart() as ex2:\n Box(length, width, thickness)\n Cylinder(radius=center_hole_dia / 2, height=thickness, mode=Mode.SUBTRACT)\n # [Ex. 2]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex2.part)\n" + }, + { + "id": "general_examples/ex03", + "source": "docs/general_examples.py #3 (An extruded prismatic solid)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 3. An extruded prismatic solid\n# [Ex. 3]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex3:\n with BuildSketch() as ex3_sk:\n Circle(width)\n Rectangle(length / 2, width / 2, mode=Mode.SUBTRACT)\n extrude(amount=2 * thickness)\n # [Ex. 3]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex3.part)\n" + }, + { + "id": "general_examples/ex08", + "source": "docs/general_examples.py #8 (Polylines)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 8. Polylines\n# [Ex. 8]\n(L, H, W, t) = (100.0, 20.0, 20.0, 1.0)\npts = [\n (0, H / 2.0),\n (W / 2.0, H / 2.0),\n (W / 2.0, (H / 2.0 - t)),\n (t / 2.0, (H / 2.0 - t)),\n (t / 2.0, (t - H / 2.0)),\n (W / 2.0, (t - H / 2.0)),\n (W / 2.0, H / -2.0),\n (0, H / -2.0),\n]\n\nwith BuildPart() as ex8:\n with BuildSketch(Plane.YZ) as ex8_sk:\n with BuildLine() as ex8_ln:\n Polyline(pts)\n mirror(ex8_ln.line, about=Plane.YZ)\n make_face()\n extrude(amount=L)\n # [Ex. 8]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex8.part)\n" + }, + { + "id": "general_examples/ex09", + "source": "docs/general_examples.py #9 (Selectors, fillets, and chamfers)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 9. Selectors, fillets, and chamfers\n# [Ex. 9]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex9:\n Box(length, width, thickness)\n chamfer(ex9.edges().group_by(Axis.Z)[-1], length=4)\n fillet(ex9.edges().filter_by(Axis.Z), radius=5)\n # [Ex. 9]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex9.part)\n" + }, + { + "id": "general_examples/ex10", + "source": "docs/general_examples.py #10 (Select Last and Hole)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 10. Select Last and Hole\n# [Ex. 10]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex10:\n Box(length, width, thickness)\n Hole(radius=width / 4)\n fillet(ex10.edges(Select.LAST).group_by(Axis.Z)[-1], radius=2)\n # [Ex. 10]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex10.part)\n" + }, + { + "id": "general_examples/ex11", + "source": "docs/general_examples.py #11 (Use a face as workplane for BuildSketch and introduce GridLocations)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 11. Use a face as workplane for BuildSketch and introduce GridLocations\n# [Ex. 11]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex11:\n Box(length, width, thickness)\n chamfer(ex11.edges().group_by(Axis.Z)[-1], length=4)\n fillet(ex11.edges().filter_by(Axis.Z), radius=5)\n Hole(radius=width / 4)\n fillet(ex11.edges(Select.LAST).sort_by(Axis.Z)[-1], radius=2)\n with BuildSketch(ex11.faces().sort_by(Axis.Z)[-1]) as ex11_sk:\n with GridLocations(length / 2, width / 2, 2, 2):\n RegularPolygon(radius=5, side_count=5)\n extrude(amount=-thickness, mode=Mode.SUBTRACT)\n # [Ex. 11]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex11)\n" + }, + { + "id": "general_examples/ex12", + "source": "docs/general_examples.py #12 (Defining an Edge with a Spline)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 12. Defining an Edge with a Spline\n# [Ex. 12]\npts = [\n (55, 30),\n (50, 35),\n (40, 30),\n (30, 20),\n (20, 25),\n (10, 20),\n (0, 20),\n]\n\nwith BuildPart() as ex12:\n with BuildSketch() as ex12_sk:\n with BuildLine() as ex12_ln:\n l1 = Spline(pts)\n l2 = Line((55, 30), (60, 0))\n l3 = Line((60, 0), (0, 0))\n l4 = Line((0, 0), (0, 20))\n make_face()\n extrude(amount=10)\n # [Ex. 12]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex12.part)\n" + }, + { + "id": "general_examples/ex13", + "source": "docs/general_examples.py #13 (CounterBoreHoles, CounterSinkHoles and PolarLocations)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 13. CounterBoreHoles, CounterSinkHoles and PolarLocations\n# [Ex. 13]\na, b = 40, 4\nwith BuildPart() as ex13:\n Cylinder(radius=50, height=10)\n with Locations(ex13.faces().sort_by(Axis.Z)[-1]):\n with PolarLocations(radius=a, count=4):\n CounterSinkHole(radius=b, counter_sink_radius=2 * b)\n with PolarLocations(radius=a, count=4, start_angle=45, angular_range=360):\n CounterBoreHole(radius=b, counter_bore_radius=2 * b, counter_bore_depth=b)\n # [Ex. 13]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex13.part)\n" + }, + { + "id": "general_examples/ex14", + "source": "docs/general_examples.py #14 (Position on a line with '@', '%' and introduce sweep)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 14. Position on a line with '@', '%' and introduce sweep\n# [Ex. 14]\na, b = 40, 20\n\nwith BuildPart() as ex14:\n with BuildLine() as ex14_ln:\n l1 = JernArc(start=(0, 0), tangent=(0, 1), radius=a, arc_size=180)\n l2 = JernArc(start=l1 @ 1, tangent=l1 % 1, radius=a, arc_size=-90)\n l3 = Line(l2 @ 1, l2 @ 1 + (-a, a))\n with BuildSketch(Plane.XZ) as ex14_sk:\n Rectangle(b, b)\n sweep()\n # [Ex. 14]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex14.part)\n" + }, + { + "id": "general_examples/ex15", + "source": "docs/general_examples.py #15 (Mirroring Symmetric Geometry)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 15. Mirroring Symmetric Geometry\n# [Ex. 15]\na, b, c = 80, 40, 20\n\nwith BuildPart() as ex15:\n with BuildSketch() as ex15_sk:\n with BuildLine() as ex15_ln:\n l1 = Line((0, 0), (a, 0))\n l2 = Line(l1 @ 1, l1 @ 1 + (0, b))\n l3 = Line(l2 @ 1, l2 @ 1 + (-c, 0))\n l4 = Line(l3 @ 1, l3 @ 1 + (0, -c))\n l5 = Line(l4 @ 1, (0, (l4 @ 1).Y))\n mirror(ex15_ln.line, about=Plane.YZ)\n make_face()\n extrude(amount=c)\n # [Ex. 15]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex15.part)\n" + }, + { + "id": "general_examples/ex16", + "source": "docs/general_examples.py #16 (Mirroring 3D Objects)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 16. Mirroring 3D Objects\n# same concept as CQ docs, but different object\n# [Ex. 16]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex16_single:\n with BuildSketch(Plane.XZ) as ex16_sk:\n Rectangle(length, width)\n fillet(ex16_sk.vertices(), radius=length / 10)\n with GridLocations(x_spacing=length / 4, y_spacing=0, x_count=3, y_count=1):\n Circle(length / 12, mode=Mode.SUBTRACT)\n Rectangle(length, width, align=(Align.MIN, Align.MIN), mode=Mode.SUBTRACT)\n extrude(amount=length)\n\nwith BuildPart() as ex16:\n add(ex16_single.part)\n mirror(ex16_single.part, about=Plane.XY.offset(width))\n mirror(ex16_single.part, about=Plane.YX.offset(width))\n mirror(ex16_single.part, about=Plane.YZ.offset(width))\n mirror(ex16_single.part, about=Plane.YZ.offset(-width))\n # [Ex. 16]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex16.part)\n" + }, + { + "id": "general_examples/ex17", + "source": "docs/general_examples.py #17 (Mirroring From Faces)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 17. Mirroring From Faces\n# [Ex. 17]\na, b = 30, 20\n\nwith BuildPart() as ex17:\n with BuildSketch() as ex17_sk:\n RegularPolygon(radius=a, side_count=5)\n extrude(amount=b)\n mirror(ex17.part, about=Plane(ex17.faces().group_by(Axis.Y)[0][0]))\n # [Ex. 17]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex17.part)\n" + }, + { + "id": "general_examples/ex18", + "source": "docs/general_examples.py #18 (Creating Workplanes on Faces)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 18. Creating Workplanes on Faces\n# based on Ex. 9\n# [Ex. 18]\nlength, width, thickness = 80.0, 60.0, 10.0\na, b = 4, 5\n\nwith BuildPart() as ex18:\n Box(length, width, thickness)\n chamfer(ex18.edges().group_by(Axis.Z)[-1], length=a)\n fillet(ex18.edges().filter_by(Axis.Z), radius=b)\n with BuildSketch(ex18.faces().sort_by(Axis.Z)[-1]):\n Rectangle(2 * b, 2 * b)\n extrude(amount=-thickness, mode=Mode.SUBTRACT)\n # [Ex. 18]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex18.part)\n" + }, + { + "id": "general_examples/ex19", + "source": "docs/general_examples.py #19 (Locating a Workplane on a vertex)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 19. Locating a Workplane on a vertex\n# [Ex. 19]\nlength, thickness = 80.0, 10.0\n\nwith BuildPart() as ex19:\n with BuildSketch() as ex19_sk:\n RegularPolygon(radius=length / 2, side_count=7)\n extrude(amount=thickness)\n topf = ex19.faces().sort_by(Axis.Z)[-1]\n vtx = topf.vertices().group_by(Axis.X)[-1][0]\n vtx2Axis = Axis((0, 0, 0), (-1, -0.5, 0))\n vtx2 = topf.vertices().sort_by(vtx2Axis)[-1]\n with BuildSketch(topf) as ex19_sk2:\n with Locations((vtx.X, vtx.Y), (vtx2.X, vtx2.Y)):\n Circle(radius=length / 8)\n extrude(amount=-thickness, mode=Mode.SUBTRACT)\n # [Ex. 19]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex19.part)\n" + }, + { + "id": "general_examples/ex20", + "source": "docs/general_examples.py #20 (Offset Sketch Workplane)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 20. Offset Sketch Workplane\n# [Ex. 20]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex20:\n Box(length, width, thickness)\n plane = Plane(ex20.faces().group_by(Axis.X)[0][0])\n with BuildSketch(plane.offset(2 * thickness)):\n Circle(width / 3)\n extrude(amount=width)\n # [Ex. 20]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex20.part)\n" + }, + { + "id": "general_examples/ex21", + "source": "docs/general_examples.py #21 (Copying Workplanes)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 21. Copying Workplanes\n# [Ex. 21]\nwidth, length = 10.0, 60.0\n\nwith BuildPart() as ex21:\n with BuildSketch() as ex21_sk:\n Circle(width / 2)\n extrude(amount=length)\n with BuildSketch(Plane(origin=ex21.part.center(), z_dir=(-1, 0, 0))):\n Circle(width / 2)\n extrude(amount=length)\n # [Ex. 21]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex21.part)\n" + }, + { + "id": "general_examples/ex22", + "source": "docs/general_examples.py #22 (Rotated Workplanes)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 22. Rotated Workplanes\n# [Ex. 22]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex22:\n Box(length, width, thickness)\n pln = Plane(ex22.faces().group_by(Axis.Z)[0][0]).rotated((0, -50, 0))\n with BuildSketch(pln) as ex22_sk:\n with GridLocations(length / 4, width / 4, 2, 2):\n Circle(thickness / 4)\n extrude(amount=-100, both=True, mode=Mode.SUBTRACT)\n # [Ex. 22]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex22.part)\n" + }, + { + "id": "general_examples/ex23", + "source": "docs/general_examples.py #23 (Revolve)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 23. Revolve\n# [Ex. 23]\npts = [\n (-25, 35),\n (-25, 0),\n (-20, 0),\n (-20, 5),\n (-15, 10),\n (-15, 35),\n]\n\nwith BuildPart() as ex23:\n with BuildSketch(Plane.XZ) as ex23_sk:\n with BuildLine() as ex23_ln:\n l1 = Polyline(pts)\n l2 = Line(l1 @ 1, l1 @ 0)\n make_face()\n with Locations((0, 35)):\n Circle(25)\n split(bisect_by=Plane.ZY)\n revolve(axis=Axis.Z)\n # [Ex. 23]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex23.part)\n" + }, + { + "id": "general_examples/ex24", + "source": "docs/general_examples.py #24 (Lofts)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 24. Lofts\n# [Ex. 24]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex24:\n Box(length, length, thickness)\n with BuildSketch(ex24.faces().group_by(Axis.Z)[0][0]) as ex24_sk:\n Circle(length / 3)\n with BuildSketch(ex24_sk.faces()[0].offset(length / 2)) as ex24_sk2:\n Rectangle(length / 6, width / 6)\n loft()\n # [Ex. 24]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex24.part)\n" + }, + { + "id": "general_examples/ex25", + "source": "docs/general_examples.py #25 (Offset Sketch)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 25. Offset Sketch\n# [Ex. 25]\nrad, offs = 50, 10\n\nwith BuildPart() as ex25:\n with BuildSketch() as ex25_sk1:\n RegularPolygon(radius=rad, side_count=5)\n with BuildSketch(Plane.XY.offset(15)) as ex25_sk2:\n RegularPolygon(radius=rad, side_count=5)\n offset(amount=offs)\n with BuildSketch(Plane.XY.offset(30)) as ex25_sk3:\n RegularPolygon(radius=rad, side_count=5)\n offset(amount=offs, kind=Kind.INTERSECTION)\n extrude(amount=1)\n # [Ex. 25]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex25.part)\n" + }, + { + "id": "general_examples/ex26", + "source": "docs/general_examples.py #26 (Offset Part To Create Thin features)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 26. Offset Part To Create Thin features\n# [Ex. 26]\nlength, width, thickness, wall = 80.0, 60.0, 10.0, 2.0\n\nwith BuildPart() as ex26:\n Box(length, width, thickness)\n topf = ex26.faces().sort_by(Axis.Z)[-1]\n offset(amount=-wall, openings=topf)\n # [Ex. 26]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex26.part)\n" + }, + { + "id": "general_examples/ex27", + "source": "docs/general_examples.py #27 (Splitting an Object)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 27. Splitting an Object\n# [Ex. 27]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex27:\n Box(length, width, thickness)\n with BuildSketch(ex27.faces().sort_by(Axis.Z)[0]) as ex27_sk:\n Circle(width / 4)\n extrude(amount=-thickness, mode=Mode.SUBTRACT)\n split(bisect_by=Plane(ex27.faces().sort_by(Axis.Y)[-1]).offset(-width / 2))\n # [Ex. 27]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex27.part)\n" + }, + { + "id": "general_examples/ex28", + "source": "docs/general_examples.py #28 (Locating features based on Faces)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 28. Locating features based on Faces\n# [Ex. 28]\nwidth, thickness = 80.0, 10.0\n\nwith BuildPart() as ex28:\n with BuildSketch() as ex28_sk:\n RegularPolygon(radius=width / 4, side_count=3)\n ex28_ex = extrude(amount=thickness, mode=Mode.PRIVATE)\n midfaces = ex28_ex.faces().group_by(Axis.Z)[1]\n Sphere(radius=width / 2)\n for face in midfaces:\n with Locations(face):\n Hole(thickness / 2)\n # [Ex. 28]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex28.part)\n" + }, + { + "id": "general_examples/ex29", + "source": "docs/general_examples.py #29 (The Classic OCC Bottle)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 29. The Classic OCC Bottle\n# [Ex. 29]\nL, w, t, b, h, n = 60.0, 18.0, 9.0, 0.9, 90.0, 6.0\n\nwith BuildPart() as ex29:\n with BuildSketch(Plane.XY.offset(-b)) as ex29_ow_sk:\n with BuildLine() as ex29_ow_ln:\n l1 = Line((0, 0), (0, w / 2))\n l2 = ThreePointArc(l1 @ 1, (L / 2.0, w / 2.0 + t), (L, w / 2.0))\n l3 = Line(l2 @ 1, ((l2 @ 1).X, 0, 0))\n mirror(ex29_ow_ln.line)\n make_face()\n extrude(amount=h + b)\n fillet(ex29.edges(), radius=w / 6)\n with BuildSketch(ex29.faces().sort_by(Axis.Z)[-1]):\n Circle(t)\n extrude(amount=n)\n necktopf = ex29.faces().sort_by(Axis.Z)[-1]\n offset(ex29.solids()[0], amount=-b, openings=necktopf)\n # [Ex. 29]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex29.part)\n" + }, + { + "id": "general_examples/ex30", + "source": "docs/general_examples.py #30 (Bezier Curve)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 30. Bezier Curve\n# [Ex. 30]\npts = [\n (0, 0),\n (20, 20),\n (40, 0),\n (0, -40),\n (-60, 0),\n (0, 100),\n (100, 0),\n]\n\nwts = [\n 1.0,\n 1.0,\n 2.0,\n 3.0,\n 4.0,\n 2.0,\n 1.0,\n]\n\nwith BuildPart() as ex30:\n with BuildSketch() as ex30_sk:\n with BuildLine() as ex30_ln:\n l0 = Polyline(pts)\n l1 = Bezier(pts, weights=wts)\n make_face()\n extrude(amount=10)\n # [Ex. 30]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex30.part)\n" + }, + { + "id": "general_examples/ex31", + "source": "docs/general_examples.py #31 (Nesting Locations)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 31. Nesting Locations\n# [Ex. 31]\na, b, c = 80.0, 5.0, 3.0\n\nwith BuildPart() as ex31:\n with BuildSketch() as ex31_sk:\n with PolarLocations(a / 2, 6):\n with GridLocations(3 * b, 3 * b, 2, 2):\n RegularPolygon(b, 3)\n RegularPolygon(b, 4)\n RegularPolygon(3 * b, 6, rotation=30)\n extrude(amount=c)\n # [Ex. 31]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex31.part)\n" + }, + { + "id": "general_examples/ex32", + "source": "docs/general_examples.py #32 (Python for-loop)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 32. Python for-loop\n# [Ex. 32]\na, b, c = 80.0, 10.0, 1.0\n\nwith BuildPart() as ex32:\n with BuildSketch(mode=Mode.PRIVATE) as ex32_sk:\n RegularPolygon(2 * b, 6, rotation=30)\n with PolarLocations(a / 2, 6):\n RegularPolygon(b, 4)\n for idx, obj in enumerate(ex32_sk.sketch.faces()):\n add(obj)\n extrude(amount=c + 3 * idx)\n # [Ex. 32]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex32.part)\n" + }, + { + "id": "general_examples/ex33", + "source": "docs/general_examples.py #33 (Python function and for-loop)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 33. Python function and for-loop\n# [Ex. 33]\na, b, c = 80.0, 5.0, 1.0\n\n\ndef square(rad, loc):\n with BuildSketch() as sk:\n with Locations(loc):\n RegularPolygon(rad, 4)\n return sk.sketch\n\n\nwith BuildPart() as ex33:\n with BuildSketch(mode=Mode.PRIVATE) as ex33_sk:\n locs = PolarLocations(a / 2, 6)\n for i, j in enumerate(locs):\n add(square(b + 2 * i, j))\n for idx, obj in enumerate(ex33_sk.sketch.faces()):\n add(obj)\n extrude(amount=c + 2 * idx)\n # [Ex. 33]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex33.part)\n" + }, + { + "id": "general_examples/ex34", + "source": "docs/general_examples.py #34 (Embossed and Debossed Text)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 34. Embossed and Debossed Text\n# [Ex. 34]\nlength, width, thickness, fontsz, fontht = 80.0, 60.0, 10.0, 25.0, 4.0\n\nwith BuildPart() as ex34:\n Box(length, width, thickness)\n topf = ex34.faces().sort_by(Axis.Z)[-1]\n with BuildSketch(topf) as ex34_sk:\n Text(\"Hello\", font_size=fontsz, align=(Align.CENTER, Align.MIN))\n extrude(amount=fontht)\n with BuildSketch(topf) as ex34_sk2:\n Text(\"World\", font_size=fontsz, align=(Align.CENTER, Align.MAX))\n extrude(amount=-fontht, mode=Mode.SUBTRACT)\n # [Ex. 34]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex34.part)\n" + }, + { + "id": "general_examples/ex35", + "source": "docs/general_examples.py #35 (Slots)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 35. Slots\n# [Ex. 35]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex35:\n Box(length, length, thickness)\n topf = ex35.faces().sort_by(Axis.Z)[-1]\n with BuildSketch(topf) as ex35_sk:\n SlotCenterToCenter(width / 2, 10)\n with BuildLine(mode=Mode.PRIVATE) as ex35_ln:\n RadiusArc((-width / 2, 0), (0, width / 2), radius=width / 2)\n SlotArc(arc=ex35_ln.edges()[0], height=thickness, rotation=0)\n with BuildLine(mode=Mode.PRIVATE) as ex35_ln2:\n RadiusArc((0, -width / 2), (width / 2, 0), radius=-width / 2)\n SlotArc(arc=ex35_ln2.edges()[0], height=thickness, rotation=0)\n extrude(amount=-thickness, mode=Mode.SUBTRACT)\n # [Ex. 35]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex35.part)\n" + }, + { + "id": "general_examples/ex36", + "source": "docs/general_examples.py #36 (Extrude-Until)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 36. Extrude-Until\n# [Ex. 36]\nrad, rev = 6, 50\n\nwith BuildPart() as ex36:\n with BuildSketch() as ex36_sk:\n with Locations((0, rev)):\n Circle(rad)\n revolve(axis=Axis.X, revolution_arc=180)\n with BuildSketch() as ex36_sk2:\n Rectangle(rad, rev)\n extrude(until=Until.NEXT)\n # [Ex. 36]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex36.part)\n" + }, + { + "id": "general_examples/ex37", + "source": "docs/general_examples.py #37 (Positioning Sketches Within a Plane)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 37. Positioning Sketches Within a Plane\n# [Ex. 37]\nwith BuildPart() as ex37:\n with BuildSketch() as ex37_sk:\n Rectangle(1, 2, align=(Align.CENTER, Align.MIN))\n with BuildSketch(\n Plane.XY.shift_origin(ex37_sk.vertices().group_by(Axis.Y)[0].sort_by(Axis.X)[0])\n ):\n Circle(1)\n with BuildSketch(Plane((0.5, 2))):\n Ellipse(0.5, 1)\n extrude(amount=1)\n # [Ex. 37]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex37.part)\n" + }, + { + "id": "general_examples_algebra/ex01", + "source": "docs/general_examples_algebra.py #1 (Simple Rectangular Plate)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 1. Simple Rectangular Plate\n# [Ex. 1]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nex1 = Box(length, width, thickness)\n# [Ex. 1]\n# show_object(ex1)\n" + }, + { + "id": "general_examples_algebra/ex02", + "source": "docs/general_examples_algebra.py #2 (Plane with hole)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 2. Plane with hole\n# [Ex. 2]\nlength, width, thickness = 80.0, 60.0, 10.0\ncenter_hole_dia = 22.0\n\nex2 = Box(length, width, thickness)\nex2 -= Cylinder(center_hole_dia / 2, height=thickness)\n# [Ex. 2]\n# show_object(ex2)\n" + }, + { + "id": "general_examples_algebra/ex03", + "source": "docs/general_examples_algebra.py #3 (An extruded prismatic solid)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 3. An extruded prismatic solid\n# [Ex. 3]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nsk3 = Circle(width) - Rectangle(length / 2, width / 2)\nex3 = extrude(sk3, amount=2 * thickness)\n# [Ex. 3]\n# show_object(ex3)\n" + }, + { + "id": "general_examples_algebra/ex08", + "source": "docs/general_examples_algebra.py #8 (Polylines)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 8. Polylines\n# [Ex. 8]\n(L, H, W, t) = (100.0, 20.0, 20.0, 1.0)\npts = [\n (0, H / 2.0),\n (W / 2.0, H / 2.0),\n (W / 2.0, (H / 2.0 - t)),\n (t / 2.0, (H / 2.0 - t)),\n (t / 2.0, (t - H / 2.0)),\n (W / 2.0, (t - H / 2.0)),\n (W / 2.0, H / -2.0),\n (0, H / -2.0),\n]\n\nln = Polyline(pts)\nln += mirror(ln, Plane.YZ)\n\nsk8 = make_face(Plane.YZ * ln)\nex8 = extrude(sk8, -L).clean()\n# [Ex. 8]\n# show_object(ex8)\n" + }, + { + "id": "general_examples_algebra/ex09", + "source": "docs/general_examples_algebra.py #9 (Selectors, fillets, and chamfers)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 9. Selectors, fillets, and chamfers\n# [Ex. 9]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nex9 = Part() + Box(length, width, thickness)\nex9 = chamfer(ex9.edges().group_by(Axis.Z)[-1], length=4)\nex9 = fillet(ex9.edges().filter_by(Axis.Z), radius=5)\n# [Ex. 9]\n# show_object(ex9)\n" + }, + { + "id": "general_examples_algebra/ex10", + "source": "docs/general_examples_algebra.py #10 (Select last edges and Hole)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 10. Select last edges and Hole\n# [Ex. 10]\nex10 = Part() + Box(length, width, thickness)\n\nsnapshot = ex10.edges()\nex10 -= Hole(radius=width / 4, depth=thickness)\nlast_edges = ex10.edges() - snapshot\nex10 = fillet(last_edges.group_by(Axis.Z)[-1], 2)\n# [Ex. 10]\n# show_object(ex10)\n" + }, + { + "id": "general_examples_algebra/ex11", + "source": "docs/general_examples_algebra.py #11 (Use a face as workplane for BuildSketch and introduce GridLocations)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 11. Use a face as workplane for BuildSketch and introduce GridLocations\n# [Ex. 11]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nex11 = Part() + Box(length, width, thickness)\nex11 = chamfer(ex11.edges().group_by()[-1], 4)\nex11 = fillet(ex11.edges().filter_by(Axis.Z), 5)\nlast = ex11.edges()\nex11 -= Hole(radius=width / 4, depth=thickness)\nex11 = fillet((ex11.edges() - last).sort_by().last, 2)\n\nplane = Plane(ex11.faces().sort_by().last)\npolygons = Sketch() + [\n plane * loc * RegularPolygon(radius=5, side_count=5)\n for loc in GridLocations(length / 2, width / 2, 2, 2)\n]\nex11 -= extrude(polygons, -thickness)\n# [Ex. 11]\n# show_object(ex11)\n" + }, + { + "id": "general_examples_algebra/ex12", + "source": "docs/general_examples_algebra.py #12 (Defining an Edge with a Spline)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 12. Defining an Edge with a Spline\n# [Ex. 12]\npts = [\n (55, 30),\n (50, 35),\n (40, 30),\n (30, 20),\n (20, 25),\n (10, 20),\n (0, 20),\n]\n\nl1 = Spline(pts)\nl2 = Line(l1 @ 0, (60, 0))\nl3 = Line(l2 @ 1, (0, 0))\nl4 = Line(l3 @ 1, l1 @ 1)\n\nsk12 = make_face([l1, l2, l3, l4])\nex12 = extrude(sk12, 10)\n# [Ex. 12]\n# show_object(ex12)\n" + }, + { + "id": "general_examples_algebra/ex13", + "source": "docs/general_examples_algebra.py #13 (CounterBoreHoles, CounterSinkHoles and PolarLocations)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 13. CounterBoreHoles, CounterSinkHoles and PolarLocations\n# [Ex. 13]\na, b = 40, 4\n\nex13 = Cylinder(radius=50, height=10)\nplane = Plane(ex13.faces().sort_by().last)\n\nex13 -= (\n plane\n * PolarLocations(radius=a, count=4)\n * CounterSinkHole(radius=b, counter_sink_radius=2 * b, depth=10)\n)\nex13 -= (\n plane\n * PolarLocations(radius=a, count=4, start_angle=45, angular_range=360)\n * CounterBoreHole(\n radius=b, counter_bore_radius=2 * b, depth=10, counter_bore_depth=b\n )\n)\n# [Ex. 13]\n# show_object(ex13)\n" + }, + { + "id": "general_examples_algebra/ex14", + "source": "docs/general_examples_algebra.py #14 (Position on a line with '@', '%' and introduce Sweep)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 14. Position on a line with '@', '%' and introduce Sweep\n# [Ex. 14]\na, b = 40, 20\n\nl1 = JernArc(start=(0, 0), tangent=(0, 1), radius=a, arc_size=180)\nl2 = JernArc(start=l1 @ 1, tangent=l1 % 1, radius=a, arc_size=-90)\nl3 = Line(l2 @ 1, l2 @ 1 + (-a, a))\nex14_ln = l1 + l2 + l3\n\nsk14 = Plane.XZ * Rectangle(b, b)\nex14 = sweep(sk14, path=ex14_ln)\n# [Ex. 14]\n# show_object(ex14)\n" + }, + { + "id": "general_examples_algebra/ex15", + "source": "docs/general_examples_algebra.py #15 (Mirroring Symmetric Geometry)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 15. Mirroring Symmetric Geometry\n# [Ex. 15]\na, b, c = 80, 40, 20\n\nl1 = Line((0, 0), (a, 0))\nl2 = Line(l1 @ 1, l1 @ 1 + (0, b))\nl3 = Line(l2 @ 1, l2 @ 1 + (-c, 0))\nl4 = Line(l3 @ 1, l3 @ 1 + (0, -c))\nl5 = Line(l4 @ 1, (0, (l4 @ 1).Y))\nln = Curve() + [l1, l2, l3, l4, l5]\nln += mirror(ln, Plane.YZ)\n\nsk15 = make_face(ln)\nex15 = extrude(sk15, c)\n# [Ex. 15]\n# show_object(ex15)\n" + }, + { + "id": "general_examples_algebra/ex16", + "source": "docs/general_examples_algebra.py #16 (Mirroring 3D Objects)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 16. Mirroring 3D Objects\n# same concept as CQ docs, but different object\n# [Ex. 16]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nsk16 = Rectangle(length, width)\nsk16 = fillet(sk16.vertices(), length / 10)\n\ncircles = [loc * Circle(length / 12) for loc in GridLocations(length / 4, 0, 3, 1)]\n\nsk16 = sk16 - circles - Rectangle(length, width, align=(Align.MIN, Align.MIN))\nex16_single = extrude(Plane.XZ * sk16, length)\n\nplanes = [\n Plane.XY.offset(width),\n Plane.YX.offset(width),\n Plane.YZ.offset(width),\n Plane.YZ.offset(-width),\n]\nobjs = [mirror(ex16_single, plane) for plane in planes]\nex16 = ex16_single + objs\n# [Ex. 16]\n# show_object(ex16)\n" + }, + { + "id": "general_examples_algebra/ex17", + "source": "docs/general_examples_algebra.py #17 (Mirroring From Faces)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 17. Mirroring From Faces\n# [Ex. 17]\na, b = 30, 20\n\nsk17 = RegularPolygon(radius=a, side_count=5)\nex17 = extrude(sk17, amount=b)\nex17 += mirror(ex17, Plane(ex17.faces().sort_by(Axis.Y).first))\n# [Ex. 17]\n# show_object(ex17)\n" + }, + { + "id": "general_examples_algebra/ex18", + "source": "docs/general_examples_algebra.py #18 (Creating Workplanes on Faces)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 18. Creating Workplanes on Faces\n# based on Ex. 9\n# [Ex. 18]\nlength, width, thickness = 80.0, 60.0, 10.0\na, b = 4, 5\n\nex18 = Part() + Box(length, width, thickness)\nex18 = chamfer(ex18.edges().group_by()[-1], a)\nex18 = fillet(ex18.edges().filter_by(Axis.Z), b)\n\nsk18 = Plane(ex18.faces().sort_by().first) * Rectangle(2 * b, 2 * b)\nex18 -= extrude(sk18, -thickness)\n# [Ex. 18]\n# show_object(ex18)\n" + }, + { + "id": "general_examples_algebra/ex19", + "source": "docs/general_examples_algebra.py #19 (Locating a Workplane on a vertex)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 19. Locating a Workplane on a vertex\n# [Ex. 19]\nlength, thickness = 80.0, 10.0\n\nex19_sk = RegularPolygon(radius=length / 2, side_count=7)\nex19 = extrude(ex19_sk, thickness)\n\ntopf = ex19.faces().sort_by().last\n\nvtx = topf.vertices().group_by(Axis.X)[-1][0]\n\nvtx2Axis = Axis((0, 0, 0), (-1, -0.5, 0))\nvtx2 = topf.vertices().sort_by(vtx2Axis)[-1]\n\nex19_sk2 = Circle(radius=length / 8)\nex19_sk2 = Pos(vtx.X, vtx.Y) * ex19_sk2 + Pos(vtx2.X, vtx2.Y) * ex19_sk2\n\nex19 -= extrude(ex19_sk2, thickness)\n# [Ex. 19]\n# show_object(ex19)\n" + }, + { + "id": "general_examples_algebra/ex20", + "source": "docs/general_examples_algebra.py #20 (Offset Sketch Workplane)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 20. Offset Sketch Workplane\n# [Ex. 20]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nex20 = Box(length, width, thickness)\nplane = Plane(ex20.faces().sort_by(Axis.X).first).offset(2 * thickness)\n\nsk20 = plane * Circle(width / 3)\nex20 += extrude(sk20, width)\n# [Ex. 20]\n# show_object(ex20)\n" + }, + { + "id": "general_examples_algebra/ex21", + "source": "docs/general_examples_algebra.py #21 (Copying Workplanes)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 21. Copying Workplanes\n# [Ex. 21]\nwidth, length = 10.0, 60.0\n\nex21 = extrude(Circle(width / 2), length)\nplane = Plane(origin=ex21.center(), z_dir=(-1, 0, 0))\nex21 += plane * extrude(Circle(width / 2), length)\n# [Ex. 21]\n# show_object(ex21)\n" + }, + { + "id": "general_examples_algebra/ex22", + "source": "docs/general_examples_algebra.py #22 (Rotated Workplanes)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 22. Rotated Workplanes\n# [Ex. 22]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nex22 = Box(length, width, thickness)\nplane = Plane((ex22.faces().group_by(Axis.Z)[0])[0]) * Rot(0, 50, 0)\n\nholes = Sketch() + [\n plane * loc * Circle(thickness / 4)\n for loc in GridLocations(length / 4, width / 4, 2, 2)\n]\nex22 -= extrude(holes, -100, both=True)\n# [Ex. 22]\n# show_object(ex22)\n" + }, + { + "id": "general_examples_algebra/ex23", + "source": "docs/general_examples_algebra.py #23 (Revolve)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 23. Revolve\n# [Ex. 23]\npts = [\n (-25, 35),\n (-25, 0),\n (-20, 0),\n (-20, 5),\n (-15, 10),\n (-15, 35),\n]\n\nl1 = Polyline(pts)\nl2 = Line(l1 @ 1, l1 @ 0)\nsk23 = make_face([l1, l2])\n\nsk23 += Pos(0, 35) * Circle(25)\nsk23 = Plane.XZ * split(sk23, bisect_by=Plane.ZY)\n\nex23 = revolve(sk23, Axis.Z)\n# [Ex. 23]\n# show_object(ex23)\n" + }, + { + "id": "general_examples_algebra/ex24", + "source": "docs/general_examples_algebra.py #24 (Lofts)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 24. Lofts\n# [Ex. 24]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nex24 = Box(length, length, thickness)\nplane = Plane(ex24.faces().sort_by().last)\n\nfaces = Sketch() + [\n plane * Circle(length / 3),\n plane.offset(length / 2) * Rectangle(length / 6, width / 6),\n]\n\nex24 += loft(faces)\n# [Ex. 24]\n# show_object(ex24)\n" + }, + { + "id": "general_examples_algebra/ex25", + "source": "docs/general_examples_algebra.py #25 (Offset Sketch)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 25. Offset Sketch\n# [Ex. 25]\nrad, offs = 50, 10\n\nsk25_1 = RegularPolygon(radius=rad, side_count=5)\nsk25_2 = Plane.XY.offset(15) * RegularPolygon(radius=rad, side_count=5)\nsk25_2 = offset(sk25_2, offs)\nsk25_3 = Plane.XY.offset(30) * RegularPolygon(radius=rad, side_count=5)\nsk25_3 = offset(sk25_3, offs, kind=Kind.INTERSECTION)\n\nsk25 = Sketch() + [sk25_1, sk25_2, sk25_3]\nex25 = extrude(sk25, 1)\n# [Ex. 25]\n# show_object(ex25)\n" + }, + { + "id": "general_examples_algebra/ex26", + "source": "docs/general_examples_algebra.py #26 (Offset Part To Create Thin features)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 26. Offset Part To Create Thin features\n# [Ex. 26]\nlength, width, thickness, wall = 80.0, 60.0, 10.0, 2.0\n\nex26 = Box(length, width, thickness)\ntopf = ex26.faces().sort_by().last\nex26 = offset(ex26, amount=-wall, openings=topf)\n# [Ex. 26]\n# show_object(ex26)\n" + }, + { + "id": "general_examples_algebra/ex27", + "source": "docs/general_examples_algebra.py #27 (Splitting an Object)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 27. Splitting an Object\n# [Ex. 27]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nex27 = Box(length, width, thickness)\nsk27 = Plane(ex27.faces().sort_by().first) * Circle(width / 4)\nex27 -= extrude(sk27, -thickness)\nex27 = split(ex27, Plane(ex27.faces().sort_by(Axis.Y).last).offset(-width / 2))\n# [Ex. 27]\n# show_object(ex27)\n" + }, + { + "id": "general_examples_algebra/ex28", + "source": "docs/general_examples_algebra.py #28 (Locating features based on Faces)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 28. Locating features based on Faces\n# [Ex. 28]\nwidth, thickness = 80.0, 10.0\n\nsk28 = RegularPolygon(radius=width / 4, side_count=3)\ntmp28 = extrude(sk28, thickness)\nex28 = Sphere(radius=width / 2)\nfor p in [Plane(face) for face in tmp28.faces().group_by(Axis.Z)[1]]:\n ex28 -= p * Hole(thickness / 2, depth=width)\n# [Ex. 28]\n# show_object(ex28)\n" + }, + { + "id": "general_examples_algebra/ex29", + "source": "docs/general_examples_algebra.py #29 (The Classic OCC Bottle)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 29. The Classic OCC Bottle\n# [Ex. 29]\nL, w, t, b, h, n = 60.0, 18.0, 9.0, 0.9, 90.0, 8.0\n\nl1 = Line((0, 0), (0, w / 2))\nl2 = ThreePointArc(l1 @ 1, (L / 2.0, w / 2.0 + t), (L, w / 2.0))\nl3 = Line(l2 @ 1, ((l2 @ 1).X, 0, 0))\nln29 = l1 + l2 + l3\nln29 += mirror(ln29)\nsk29 = make_face(ln29)\nex29 = extrude(sk29, -(h + b))\nex29 = fillet(ex29.edges(), radius=w / 6)\n\nneck = Plane(ex29.faces().sort_by().last) * Circle(t)\nex29 += extrude(neck, n)\nnecktopf = ex29.faces().sort_by().last\nex29 = offset(ex29, -b, openings=necktopf)\n# [Ex. 29]\n# show_object(ex29)\n" + }, + { + "id": "general_examples_algebra/ex30", + "source": "docs/general_examples_algebra.py #30 (Bezier Curve)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 30. Bezier Curve\n# [Ex. 30]\npts = [\n (0, 0),\n (20, 20),\n (40, 0),\n (0, -40),\n (-60, 0),\n (0, 100),\n (100, 0),\n]\n\nwts = [\n 1.0,\n 1.0,\n 2.0,\n 3.0,\n 4.0,\n 2.0,\n 1.0,\n]\n\nex30_ln = Polyline(pts) + Bezier(pts, weights=wts)\nex30_sk = make_face(ex30_ln)\nex30 = extrude(ex30_sk, -10)\n# [Ex. 30]\n# show_object(ex30)\n" + }, + { + "id": "general_examples_algebra/ex31", + "source": "docs/general_examples_algebra.py #31 (Nesting Locations)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 31. Nesting Locations\n# [Ex. 31]\na, b, c = 80.0, 5.0, 3.0\n\nex31 = Rot(Z=30) * RegularPolygon(3 * b, 6)\nex31 += PolarLocations(a / 2, 6) * (\n RegularPolygon(b, 4) + GridLocations(3 * b, 3 * b, 2, 2) * RegularPolygon(b, 3)\n)\nex31 = extrude(ex31, 3)\n# [Ex. 31]\n# show_object(ex31)\n" + }, + { + "id": "general_examples_algebra/ex32", + "source": "docs/general_examples_algebra.py #32 (Python for-loop)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 32. Python for-loop\n# [Ex. 32]\na, b, c = 80.0, 10.0, 1.0\n\nex32_sk = RegularPolygon(2 * b, 6, rotation=30)\nex32_sk += PolarLocations(a / 2, 6) * RegularPolygon(b, 4)\nex32 = Part() + [extrude(obj, c + 3 * idx) for idx, obj in enumerate(ex32_sk.faces())]\n# [Ex. 32]\n# show_object(ex32)\n" + }, + { + "id": "general_examples_algebra/ex33", + "source": "docs/general_examples_algebra.py #33 (Python function and for-loop)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 33. Python function and for-loop\n# [Ex. 33]\na, b, c = 80.0, 5.0, 1.0\n\n\ndef square(rad, loc):\n return loc * RegularPolygon(rad, 4)\n\n\nex33 = Part() + [\n extrude(square(b + 2 * i, loc), c + 2 * i)\n for i, loc in enumerate(PolarLocations(a / 2, 6))\n]\n# [Ex. 33]\n# show_object(ex33)\n" + }, + { + "id": "general_examples_algebra/ex34", + "source": "docs/general_examples_algebra.py #34 (Embossed and Debossed Text)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 34. Embossed and Debossed Text\n# [Ex. 34]\nlength, width, thickness, fontsz, fontht = 80.0, 60.0, 10.0, 25.0, 4.0\n\nex34 = Box(length, width, thickness)\nplane = Plane(ex34.faces().sort_by().last)\nex34_sk = plane * Text(\"Hello\", font_size=fontsz, align=(Align.CENTER, Align.MIN))\nex34 += extrude(ex34_sk, amount=fontht)\nex34_sk2 = plane * Text(\"World\", font_size=fontsz, align=(Align.CENTER, Align.MAX))\nex34 -= extrude(ex34_sk2, amount=-fontht)\n# [Ex. 34]\n# show_object(ex34)\n" + }, + { + "id": "general_examples_algebra/ex35", + "source": "docs/general_examples_algebra.py #35 (Slots)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 35. Slots\n# [Ex. 35]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nex35 = Box(length, length, thickness)\nplane = Plane(ex35.faces().sort_by().last)\nex35_sk = SlotCenterToCenter(width / 2, 10)\nex35_ln = RadiusArc((-width / 2, 0), (0, width / 2), radius=width / 2)\nex35_sk += SlotArc(arc=ex35_ln.edges()[0], height=thickness)\nex35_ln2 = RadiusArc((0, -width / 2), (width / 2, 0), radius=-width / 2)\nex35_sk += SlotArc(arc=ex35_ln2.edges()[0], height=thickness)\nex35 -= extrude(plane * ex35_sk, -thickness)\n# [Ex. 35]\n# show_object(ex35)\n" + }, + { + "id": "general_examples_algebra/ex36", + "source": "docs/general_examples_algebra.py #36 (Extrude-Until)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 36. Extrude-Until\n# [Ex. 36]\nrad, rev = 6, 50\n\nex36_sk = Pos(0, rev) * Circle(rad)\nex36 = revolve(axis=Axis.X, profiles=ex36_sk, revolution_arc=180)\nex36_sk2 = Rectangle(rad, rev)\nex36 += extrude(ex36_sk2, until=Until.NEXT, target=ex36)\n# [Ex. 36]\n# show_object(ex36)\n" + }, + { + "id": "docs/center", + "source": "docs/center.py", + "kind": "docs-script", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nsize = 50\n#\n# Symbols\n#\nbbox_symbol = Rectangle(4, 4)\ngeom_symbol = RegularPolygon(2, 3)\nmass_symbol = Circle(2)\n\n#\n# 2D Center Options\n#\ntriangle = RegularPolygon(size / 1.866, 3, rotation=90)\nsvg = ExportSVG(margin=5)\nsvg.add_layer(\"bbox\", line_type=LineType.DASHED)\nsvg.add_shape(bounding_box(triangle), \"bbox\")\nsvg.add_shape(triangle)\nsvg.add_shape(bbox_symbol.located(Location(triangle.center(CenterOf.BOUNDING_BOX))))\nsvg.add_shape(mass_symbol.located(Location(triangle.center(CenterOf.MASS))))\nsvg.write(\"assets/center.svg\")\n\n#\n# 1D Center Options\n#\nline = TangentArc((0, 0), (size, size), tangent=(1, 0))\nsvg = ExportSVG(margin=5)\nsvg.add_layer(\"bbox\", line_type=LineType.DASHED)\nsvg.add_shape(line)\nsvg.add_shape(Polyline((0, 0), (size, 0), (size, size), (0, size), (0, 0)), \"bbox\")\nsvg.add_shape(bbox_symbol.located(Location(line.center(CenterOf.BOUNDING_BOX))))\nsvg.add_shape(mass_symbol.located(Location(line.center(CenterOf.MASS))))\nsvg.add_shape(geom_symbol.located(Location(line.center(CenterOf.GEOMETRY))))\nsvg.write(\"assets/one_d_center.svg\")\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/constraint_examples", + "source": "docs/constraint_examples.py", + "kind": "docs-script", + "code": "from build123d import *\nfrom build123d.exporters import ColorIndex\n# [removed by collect.py] from ocp_vscode import show, show_all, ImageFace\n\n# 2D Axes\naxes2 = Compound.make_triad(2).edges().group_by(Axis.Z)[0]\n\n\n#\n# BlendCurve\n#\nm1 = CenterArc((-2, 0.6), 1, -10, 200).reversed()\nm2 = Spline((0.4, -0.6), (1, -1.6), (2, 0))\nconnector = BlendCurve(m1, m2, tangent_scalars=(2, 1), continuity=ContinuityLevel.C2)\ncomb = Curve(Wire([m1, connector, m2]).curvature_comb(200))\n\ns = 120 / max(*Curve(axes2 + [m1, m2]).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"m1\", line_color=(214, 40, 40), line_type=LineType.ISO_DASH_SPACE)\nsvg.add_layer(\"m2\", line_color=(252, 191, 73), line_type=LineType.ISO_DASH_SPACE)\nsvg.add_layer(\"connector\", line_color=(247, 127, 0))\nsvg.add_layer(\"comb\", line_color=(172, 172, 172))\nsvg.add_shape(axes2)\nsvg.add_shape(m1, \"m1\")\nsvg.add_shape(m2, \"m2\")\nsvg.add_shape(connector, \"connector\")\nsvg.add_shape(comb, \"comb\")\nsvg.write(\"assets/blend_curve_ex.svg\")\n\n\n#\n# Coincident\n#\nwith BuildLine() as coincident_ex:\n l1 = Line((0, 0), (1, 2))\n l2 = Line(l1 @ 1, l1 @ 1 + (1, 0))\n\ns = 50 / max(*Curve(axes2 + coincident_ex.edges()).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(l1, \"dashed\")\nsvg.add_shape(l2)\nsvg.write(\"assets/coincident_ex.svg\")\n\n#\n# Tangent\n#\nwith BuildLine() as tangent_ex:\n l1 = Line((0, 0), (1, 1))\n l2 = JernArc(start=l1 @ 1, tangent=l1 % 1, radius=1, arc_size=70)\n\ns = 50 / max(*Curve(axes2 + tangent_ex.edges()).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(l1, \"dashed\")\nsvg.add_shape(l2)\nsvg.write(\"assets/tangent_ex.svg\")\n\n#\n# Perpendicular\n#\nwith BuildLine() as perpendicular_ex:\n l1 = CenterArc((0, 0), 1.5, 0, 45)\n l2 = PolarLine(\n start=l1 @ 1, length=1, direction=l1.tangent_at(1).rotate(Axis.Z, -90)\n )\n\ns = 50 / max(*Curve(axes2 + perpendicular_ex.edges()).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(l1, \"dashed\")\nsvg.add_shape(l2)\nsvg.write(\"assets/perpendicular_ex.svg\")\n\n#\n# Intersection\n#\nwith BuildLine() as intersect_ex:\n c_l1 = EllipticalCenterArc((0, 0), 1.2, 1.8, 0, arc_size=90, mode=Mode.PRIVATE)\n l1 = IntersectingLine(\n start=(0, 0), direction=Vector(1, 0).rotate(Axis.Z, 10), other=c_l1\n )\n l2 = IntersectingLine(\n start=(0, 0), direction=Vector(1, 0).rotate(Axis.Z, 80), other=c_l1\n )\n l3 = add(c_l1.trim(l1 @ 1, l2 @ 1))\n\ns = 50 / max(*Curve(axes2 + intersect_ex.edges()).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(c_l1, \"dashed\")\nsvg.add_shape(l1)\nsvg.add_shape(l2)\nsvg.add_shape(l3)\nsvg.write(\"assets/intersect_ex.svg\")\n\n#\n# Offset\n#\ninside = FilletPolyline((1.5, 0), (1.5, 1), (-1.5, 1), (-1.5, 0), radius=0.2)\ninside.color = \"Grey\"\nperimeter = offset(inside, amount=0.2, side=Side.RIGHT)\n\ns = 100 / max(*Curve(axes2 + [inside, perimeter]).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(perimeter)\nsvg.add_shape(inside, \"dashed\")\nsvg.write(\"assets/offset_ex.svg\")\n\n#\n# Tangency Outside/Enclosing\n#\nwith BuildLine() as egg_plant:\n # Construction Geometry\n c_l1 = CenterArc((-2, 0), 0.75, 80, 240, mode=Mode.PRIVATE)\n c_l4 = CenterArc((2, 0), 1, 220, 250, mode=Mode.PRIVATE)\n\n # egg_plant perimeter\n l1 = ConstrainedArcs((c_l4, Tangency.OUTSIDE), (c_l1, Tangency.OUTSIDE), radius=6)\n l2 = ConstrainedArcs(\n (c_l4, Tangency.ENCLOSING),\n (c_l1, Tangency.ENCLOSING),\n radius=8,\n selector=lambda a: a.sort_by(Axis.Y)[-1],\n )\n l3 = add(c_l1.trim(l1 @ 1, l2 @ 1))\n l5 = add(c_l4.trim(l1 @ 0, l2 @ 0))\n\ns = 100 / max(*Curve(axes2 + egg_plant.edges()).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape([l1, l2, l3, l5])\nsvg.add_shape([c_l1, c_l4], \"dashed\")\nsvg.write(\"assets/enclosing_ex.svg\")\n\n#\n# Complex Sketch\n#\nimage = ImageFace(\n \"assets/complex_sketch.png\",\n scale=29 / 264,\n origin_pixels=(297, 390),\n location=Location((0, 0, -0.1)),\n)\naxes5 = Compound.make_triad(5).edges().group_by(Axis.Z)[0]\n\nwith BuildSketch() as sketch:\n with BuildLine() as perimeter:\n c_l1 = PolarLine((0, 32 - 14), 50, -10, mode=Mode.PRIVATE)\n a19 = ConstrainedArcs(c_l1, (-14 + 81 - 29, -14 - 19 + 57), radius=19)\n l2 = Polyline(a19 @ 1, a19 @ 1 + (29 - 5, 0), a19 @ 1 + (29, -5), (-14 + 81, 0))\n l3 = Line(l2 @ 1, (-14 + 81 - 29, (-14 - 19)))\n c_l4 = Line((-14, -14), (-14 + 81, -14), mode=Mode.PRIVATE)\n c_a29_arc_center = l3.intersect(c_l4)[0]\n c_a29 = CenterArc(c_a29_arc_center, 29, 180, 50, mode=Mode.PRIVATE)\n l5 = IntersectingLine(l3 @ 1, (-1, 0), c_a29)\n a5 = ConstrainedArcs(\n c_a29, c_l4, radius=5, selector=lambda a: a.sort_by(Axis.X)[0]\n )\n a29 = add(c_a29.trim(l5 @ 1, a5 @ 0))\n l6 = Polyline(\n a5 @ 1,\n (-14 + 7, -14),\n (-14, -14 + 7),\n (-14, -14 + 32 - 7),\n (-14 + 7, -14 + 32),\n (0, -14 + 32),\n a19 @ 0,\n )\n make_face()\n a14 = Circle(14 / 2, mode=Mode.SUBTRACT)\n\ns = 150 / max(*Curve(axes5 + perimeter.edges()).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes5)\nsvg.add_shape(perimeter.edges() + [a14.edge()])\nsvg.add_shape([c_l1, c_l4, c_a29], \"dashed\")\nsvg.write(\"assets/complex_ex.svg\")\n\n#\n# Tangent Circles\n#\na1 = CenterArc((-7, 0), 10, 0, 360)\na2 = CenterArc((7, 0), 10, 0, 360)\ntangents = ConstrainedArcs(a1, a2, radius=2).edges()\ntangent_circles = [CenterArc(e.arc_center, 2, 0, 360) for e in tangents]\n\ns = 100 / max(*Curve([a1, a2] + tangent_circles).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(tangent_circles)\nsvg.add_shape([a1, a2], \"dashed\")\nsvg.write(\"assets/tangent_circles.svg\")\n\n#\n# ConstrainedArcs - two constraints & radius\n#\ne1 = Line((0, 1), (2, 1))\ne2 = Line((1, 0), (1, 2))\ntan2_rad_edges = ConstrainedArcs(e1, e2, radius=0.75).edges()\n\ns = 50 / max(*Curve([e1, e2] + axes2 + tan2_rad_edges).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(tan2_rad_edges)\nsvg.add_shape([e1, e2], \"dashed\")\nsvg.write(\"assets/tan2_rad_ex.svg\")\n\n#\n# ConstrainedArcs - two constraints & center-on\n#\n# c1 = PolarLine((0, 0), 4, -20, length_mode=LengthMode.HORIZONTAL)\nc1 = PolarLine((0, 0), 2, 40, length_mode=LengthMode.HORIZONTAL)\nc2 = Line((1.8, 0), (1.8, 2))\nc3_center_on = Line((1, -0.5), (1, 2.5))\ntan2_on_edge = ConstrainedArcs(\n c1, c2, center_on=c3_center_on, sagitta=Sagitta.BOTH\n).edges()\n\ns = 50 / max(*Curve([c1, c2, c3_center_on] + axes2 + tan2_on_edge).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(tan2_on_edge)\nsvg.add_shape([c1, c2, c3_center_on], \"dashed\")\nsvg.write(\"assets/tan2_on_ex.svg\")\n\n#\n# ConstrainedArcs - three constraints\n#\nc5 = PolarLine((0, 0), 1.8, 60)\nc6 = PolarLine((0, 0), 1.8, 40)\nc7 = CenterArc((0, 0), 1.8, 0, 90)\ntan3 = ConstrainedArcs(c5, c6, c7).edge()\n\ns = 50 / max(*Curve([c5, c6, c7, tan3] + axes2).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(tan3)\nsvg.add_shape([c5, c6, c7], \"dashed\")\nsvg.write(\"assets/tan3_ex.svg\")\n\n#\n# ConstrainedArcs - one constraint + center\n#\npnt = CenterArc((1.5, 1.5), 0.05, 0, 360)\ncenter_pnt = CenterArc((1, 1), 0.05, 0, 360)\npnt_center = ConstrainedArcs(pnt.arc_center, center=center_pnt.arc_center).edge()\n\ns = 50 / max(*Curve([pnt, center_pnt, pnt_center] + axes2).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(axes2)\nsvg.add_shape([pnt, center_pnt, pnt_center])\nsvg.write(\"assets/pnt_center_ex.svg\")\n\n#\n# ConstrainedArcs - One constraint + radius + center_on\n#\ntan_rad_on = ConstrainedArcs(c1, radius=0.5, center_on=c3_center_on).edges()\n\ns = 50 / max(*Curve([c1, c3_center_on] + tan_rad_on + axes2).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(tan_rad_on)\nsvg.add_shape([c1, c3_center_on], \"dashed\")\nsvg.write(\"assets/tan_rad_on_ex.svg\")\n\n#\n# ConstrainedLines - two constraints\n#\na1 = CenterArc((-1, 1), 1, 0, 360)\na2 = CenterArc((1, 1), 0.5, 0, 360)\nl1 = Line((0, 0), (2, 2))\nlines_tan2_ex = ConstrainedLines(a1, a2).edges()\n\ns = 50 / max(*Curve([a1, a1] + lines_tan2_ex + axes2).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(lines_tan2_ex)\nsvg.add_shape([a1, a2], \"dashed\")\nsvg.write(\"assets/lines_tan2_ex.svg\")\n\n\npnt_line = CenterArc((1, 1), 0.05, 0, 360)\nlines_tan_pnt = ConstrainedLines(a1, pnt_line.arc_center).edges()\n\ns = 50 / max(*Curve([pnt_line, a1] + lines_tan_pnt + axes2).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(lines_tan_pnt)\nsvg.add_shape(pnt_line)\nsvg.add_shape([a1], \"dashed\")\nsvg.write(\"assets/lines_tan_pnt_ex.svg\")\n\ny_axis = Line((0, 0), (0, 2.5))\nlines_angle = ConstrainedLines(a2, Axis.Y, angle=55).edges()\n\ns = 50 / max(*Curve([y_axis, a2] + lines_angle + axes2).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(lines_angle)\nsvg.add_shape([y_axis, a2], \"dashed\")\nsvg.write(\"assets/lines_angle_ex.svg\")\n\nshow_all()\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/heart_token", + "source": "docs/heart_token.py", + "kind": "docs-script", + "code": "# [Code]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n# Create the edges of one half the heart surface\nl1 = JernArc((0, 0), (1, 1.4), 40, -17)\nl2 = JernArc(l1 @ 1, l1 % 1, 4.5, 175)\nl3 = IntersectingLine(l2 @ 1, l2 % 1, other=Edge.make_line((0, 0), (0, 20)))\nl4 = ThreePointArc(l3 @ 1, (0, 0, 1.5) + (l3 @ 1 + l1 @ 0) / 2, l1 @ 0)\nheart_half = Wire([l1, l2, l3, l4])\n# [SurfaceEdges]\n\n# Create a point elevated off the center\nsurface_pnt = l2.arc_center + (0, 0, 1.5)\n# [SurfacePoint]\n\n# Create the surface from the edges and point\ntop_right_surface = Pos(Z=0.5) * -Face.make_surface(heart_half, [surface_pnt])\n# [Surface]\n\n# Use the mirror method to create the other top and bottom surfaces\ntop_left_surface = top_right_surface.mirror(Plane.YZ)\nbottom_right_surface = top_right_surface.mirror(Plane.XY)\nbottom_left_surface = -top_left_surface.mirror(Plane.XY)\n# [Surfaces]\n\n# Create the left and right sides\nleft_wire = Wire([l3, l2, l1])\nleft_side = Pos(Z=-0.5) * Shell.extrude(left_wire, (0, 0, 1))\nright_side = left_side.mirror(Plane.YZ)\n# [Sides]\n\n# Put all of the faces together into a Shell/Solid\nheart = Solid(\n Shell(\n [\n top_right_surface,\n top_left_surface,\n bottom_right_surface,\n bottom_left_surface,\n left_side,\n right_side,\n ]\n )\n)\n# [Solid]\n\n# Build a frame around the heart\nwith BuildPart() as heart_token:\n with BuildSketch() as outline:\n with BuildLine():\n add(l1)\n add(l2)\n add(l3)\n Line(l3 @ 1, l1 @ 0)\n make_face()\n mirror(about=Plane.YZ)\n center = outline.sketch\n offset(amount=2, kind=Kind.INTERSECTION)\n add(center, mode=Mode.SUBTRACT)\n extrude(amount=2, both=True)\n add(heart)\n\nheart_token.part.color = \"Red\"\n\nshow(heart_token)\n# [End]\n# export_gltf(heart_token.part, \"heart_token.glb\", binary=True)\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/line_types", + "source": "docs/line_types.py", + "kind": "docs-script", + "code": "from build123d import *\n\nexporter = ExportSVG(scale=1)\nexporter.add_layer(name=\"Text\", fill_color=(0, 0, 0))\nline_types = [l for l in LineType.__members__]\ntext_locs = Pos((100, 0, 0)) * GridLocations(0, 6, 1, len(line_types)).locations\nline_locs = Pos((105, 0, 0)) * GridLocations(0, 6, 1, len(line_types)).locations\nfor line_type, text_loc, line_loc in zip(line_types, text_locs, line_locs):\n exporter.add_layer(name=line_type, line_type=getattr(LineType, line_type))\n exporter.add_shape(\n Compound.make_text(\n \"LineType.\" + line_type,\n font_size=5,\n align=(Align.MAX, Align.CENTER),\n ).locate(text_loc),\n layer=\"Text\",\n )\n exporter.add_shape(\n Edge.make_line((0, 0), (100, 0)).locate(line_loc), layer=line_type\n )\nexporter.write(\"assets/line_types.svg\")\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/objects_1d", + "source": "docs/objects_1d.py", + "kind": "docs-script", + "code": "# [Setup]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndot = Circle(0.05)\n\n# [Ex. 1]\nwith BuildLine() as example_1:\n Line((0, 0), (2, 0))\n ThreePointArc((0, 0), (1, 1), (2, 0))\n# [Ex. 1]\ns = 100 / max(*example_1.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(example_1.line)\nsvg.write(\"assets/buildline_example_1.svg\")\n# [Ex. 2]\nwith BuildLine() as example_2:\n l1 = Line((0, 0), (2, 0))\n l2 = ThreePointArc(l1 @ 0, (1, 1), l1 @ 1)\n# [Ex. 2]\n\n# [Ex. 3]\nwith BuildLine() as example_3:\n l1 = Line((0, 0), (2, 0))\n l2 = ThreePointArc(l1 @ 0, l1 @ 0.5 + (0, 1), l1 @ 1)\n# [Ex. 3]\n\n# [Ex. 4]\nwith BuildLine() as example_4:\n l1 = Line((0, 0), (2, 0))\n l2 = ThreePointArc(l1 @ 0, l1 @ 0.5 + (0, l1.length / 2), l1 @ 1)\n# [Ex. 4]\n\n# [Ex. 5]\nwith BuildLine() as example_5:\n l1 = Line((0, 0), (5, 0))\n l2 = Line(l1 @ 1, l1 @ 1 + (0, l1.length - 1))\n l3 = JernArc(start=l2 @ 1, tangent=l2 % 1, radius=0.5, arc_size=90)\n l4 = Line(l3 @ 1, (0, l2.length + l3.radius))\n# [Ex. 5]\ns = 100 / max(*example_5.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(example_5.line)\nsvg.add_shape(dot.moved(Location(l1 @ 1)))\nsvg.add_shape(dot.moved(Location(l2 @ 1)))\nsvg.add_shape(dot.moved(Location(l3 @ 1)))\nsvg.add_shape(PolarLine(l2 @ 1, 0.5, direction=l2 % 1), \"dashed\")\nsvg.write(\"assets/buildline_example_5.svg\")\n# [Ex. 6]\nwith BuildSketch() as example_6:\n with BuildLine() as club_outline:\n l0 = Line((0, -188), (76, -188))\n b0 = Bezier(l0 @ 1, (61, -185), (33, -173), (17, -81))\n b1 = Bezier(b0 @ 1, (49, -128), (146, -145), (167, -67))\n b2 = Bezier(b1 @ 1, (187, 9), (94, 52), (32, 18))\n b3 = Bezier(b2 @ 1, (92, 57), (113, 188), (0, 188))\n mirror(about=Plane.YZ)\n make_face()\n # [Ex. 6]\ns = 100 / max(*example_6.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s, margin=5)\nsvg.add_shape(example_6.sketch)\nsvg.write(\"assets/buildline_example_6.svg\")\n\n# [Ex. 7]\nwith BuildPart() as example_7:\n with BuildLine() as example_7_path:\n l1 = RadiusArc((0, 0), (1, 1), 2)\n l2 = Spline(l1 @ 1, (2, 3), (3, 3), tangents=(l1 % 1, (0, -1)))\n l3 = Line(l2 @ 1, (3, 0))\n with BuildSketch(Plane(origin=l1 @ 0, z_dir=l1 % 0)) as example_7_section:\n Circle(0.1)\n sweep()\n# [Ex. 7]\nvisible, hidden = example_7.part.project_to_viewport((100, -50, 100))\ns = 100 / max(*Compound(children=visible + hidden).bounding_box().size)\nexporter = ExportSVG(scale=s)\nexporter.add_layer(\"Visible\")\nexporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)\nexporter.add_shape(visible, layer=\"Visible\")\nexporter.add_shape(hidden, layer=\"Hidden\")\nexporter.write(\"assets/buildline_example_7.svg\")\n# [Ex. 8]\nwith BuildLine(Plane.YZ) as example_8:\n l1 = Line((0, 0), (5, 0))\n l2 = Line(l1 @ 1, l1 @ 1 + (0, l1.length - 1))\n l3 = JernArc(start=l2 @ 1, tangent=l2 % 1, radius=0.5, arc_size=90)\n l4 = Line(l3 @ 1, (0, l2.length + l3.radius))\n# [Ex. 8]\nscene = Compound(example_8.line) + Compound.make_triad(2)\nvisible, _hidden = scene.project_to_viewport((100, -50, 100))\ns = 100 / max(*Compound(children=visible + hidden).bounding_box().size)\nexporter = ExportSVG(scale=s)\nexporter.add_layer(\"Visible\")\nexporter.add_shape(visible, layer=\"Visible\")\nexporter.write(\"assets/buildline_example_8.svg\")\n\n\npts = [(0, 0), (2 / 3, 2 / 3), (0, 4 / 3), (-4 / 3, 0), (0, -2), (4, 0), (0, 3)]\nwts = [1.0, 1.0, 2.0, 3.0, 4.0, 2.0, 1.0]\nwith BuildLine() as bezier_curve:\n Bezier(*pts, weights=wts)\n\ns = 100 / max(*bezier_curve.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(bezier_curve.line)\nfor pt in pts:\n svg.add_shape(dot.moved(Location(Vector(pt))))\nsvg.write(\"assets/bezier_curve_example.svg\")\n\nwith BuildLine() as center_arc:\n CenterArc((0, 0), 3, 0, 90)\ns = 100 / max(*center_arc.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(center_arc.line)\nsvg.add_shape(dot.moved(Location(Vector((0, 0)))))\nsvg.write(\"assets/center_arc_example.svg\")\n\nwith BuildLine() as elliptical_center_arc:\n EllipticalCenterArc((0, 0), 2, 3, 0, arc_size=90)\ns = 100 / max(*elliptical_center_arc.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(elliptical_center_arc.line)\nsvg.add_shape(dot.moved(Location(Vector((0, 0)))))\nsvg.write(\"assets/elliptical_center_arc_example.svg\")\n\n\nwith BuildLine() as helix:\n Helix(1, 3, 1)\nscene = Compound(helix.line) + Compound.make_triad(0.5)\nvisible, _hidden = scene.project_to_viewport((1, 1, 1))\ns = 100 / max(*Compound(children=visible).bounding_box().size)\nexporter = ExportSVG(scale=s)\nexporter.add_layer(\"Visible\")\nexporter.add_shape(visible, layer=\"Visible\")\nexporter.write(\"assets/helix_example.svg\")\n\nwith BuildLine() as jern_arc:\n JernArc((1, 1), (1, 0.5), 2, 100)\ns = 100 / max(*jern_arc.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(jern_arc.line)\nsvg.add_shape(dot.moved(Location(Vector((1, 1)))))\nsvg.add_shape(PolarLine((1, 1), 0.5, direction=(1, 0.5)), \"dashed\")\nsvg.write(\"assets/jern_arc_example.svg\")\n\nwith BuildLine() as line:\n Line((1, 1), (3, 3))\ns = 100 / max(*line.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(line.line)\nsvg.add_shape(dot.moved(Location(Vector((1, 1)))))\nsvg.add_shape(dot.moved(Location(Vector((3, 3)))))\nsvg.write(\"assets/line_example.svg\")\n\nwith BuildLine() as polar_line:\n PolarLine((1, 1), 2.5, 60)\ns = 100 / max(*polar_line.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(polar_line.line)\nsvg.add_shape(dot.moved(Location(Vector((1, 1)))))\nsvg.add_shape(PolarLine((1, 1), 4, angle=60), \"dashed\")\nsvg.write(\"assets/polar_line_example.svg\")\n\nwith BuildLine() as polyline:\n Polyline((1, 1), (1.5, 2.5), (3, 3))\ns = 100 / max(*polyline.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(polyline.line)\nsvg.add_shape(dot.moved(Location(Vector((1, 1)))))\nsvg.add_shape(dot.moved(Location(Vector((1.5, 2.5)))))\nsvg.add_shape(dot.moved(Location(Vector((3, 3)))))\nsvg.write(\"assets/polyline_example.svg\")\n\nwith BuildLine(Plane.YZ) as filletpolyline:\n FilletPolyline((0, 0, 0), (0, 10, 2), (0, 10, 10), (5, 20, 10), radius=2)\nscene = Compound(filletpolyline.line) + Compound.make_triad(2)\nvisible, _hidden = scene.project_to_viewport((0, 0, 1), (0, 1, 0))\ns = 100 / max(*Compound(children=visible).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(visible)\nsvg.write(\"assets/filletpolyline_example.svg\")\n\nwith BuildLine() as radius_arc:\n RadiusArc((1, 1), (3, 3), 2)\ns = 100 / max(*radius_arc.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(radius_arc.line)\nsvg.add_shape(dot.moved(Location(Vector((1, 1)))))\nsvg.add_shape(dot.moved(Location(Vector((3, 3)))))\nsvg.write(\"assets/radius_arc_example.svg\")\n\nwith BuildLine() as sagitta_arc:\n SagittaArc((1, 1), (3, 1), 1)\ns = 100 / max(*sagitta_arc.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(sagitta_arc.line)\nsvg.add_shape(dot.moved(Location(Vector((1, 1)))))\nsvg.add_shape(dot.moved(Location(Vector((3, 1)))))\nsvg.write(\"assets/sagitta_arc_example.svg\")\n\nwith BuildLine() as spline:\n Spline((1, 1), (2, 1.5), (1, 2), (2, 2.5), (1, 3))\ns = 100 / max(*spline.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(spline.line)\nsvg.add_shape(dot.moved(Location(Vector((1, 1)))))\nsvg.add_shape(dot.moved(Location(Vector((2, 1.5)))))\nsvg.add_shape(dot.moved(Location(Vector((1, 2)))))\nsvg.add_shape(dot.moved(Location(Vector((2, 2.5)))))\nsvg.add_shape(dot.moved(Location(Vector((1, 3)))))\nsvg.write(\"assets/spline_example.svg\")\n\nwith BuildLine() as tangent_arc:\n TangentArc((1, 1), (3, 3), tangent=(1, 0))\ns = 100 / max(*tangent_arc.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(tangent_arc.line)\nsvg.add_shape(dot.moved(Location(Vector((1, 1)))))\nsvg.add_shape(dot.moved(Location(Vector((3, 3)))))\nsvg.add_shape(PolarLine((1, 1), 1, direction=(1, 0)), \"dashed\")\nsvg.write(\"assets/tangent_arc_example.svg\")\n\nwith BuildLine() as three_point_arc:\n ThreePointArc((1, 1), (1.5, 2), (3, 3))\ns = 100 / max(*three_point_arc.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(three_point_arc.line)\nsvg.add_shape(dot.moved(Location(Vector((1, 1)))))\nsvg.add_shape(dot.moved(Location(Vector((1.5, 2)))))\nsvg.add_shape(dot.moved(Location(Vector((3, 3)))))\nsvg.write(\"assets/three_point_arc_example.svg\")\n\nwith BuildLine() as intersecting_line:\n other = Line((2, 0), (2, 2), mode=Mode.PRIVATE)\n IntersectingLine((1, 0), (1, 1), other)\ns = 100 / max(*intersecting_line.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(other, \"dashed\")\nsvg.add_shape(intersecting_line.line)\nsvg.add_shape(dot.moved(Location(Vector((1, 0)))))\nsvg.write(\"assets/intersecting_line_example.svg\")\n\nwith BuildLine() as double_tangent:\n p1 = (6, 0)\n d1 = (0, 1)\n l2 = Spline((0, 10), (3, 8), (7, 7), (10, 10))\n show_object([p1, l2])\n l3 = DoubleTangentArc(p1, tangent=d1, other=l2)\ns = 100 / max(*double_tangent.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(l2, \"dashed\")\nsvg.add_shape(l3)\nsvg.add_shape(dot.scale(5).moved(Pos(p1)))\nsvg.add_shape(PolarLine(p1, 1, direction=d1), \"dashed\")\nsvg.write(\"assets/double_tangent_line_example.svg\")\n\n# show_object(example_1.line, name=\"Ex. 1\")\n# show_object(example_2.line, name=\"Ex. 2\")\n# show_object(example_3.line, name=\"Ex. 3\")\n# show_object(example_4.line, name=\"Ex. 4\")\n# show_object(example_5.line, name=\"Ex. 5\")\n# show_object(example_6.line, name=\"Ex. 6\")\n# show_object(example_7_path.line, name=\"Ex. 7 path\")\n# show_object(example_8.line, name=\"Ex. 8\")\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/objects_1d_airfoil", + "source": "docs/objects_1d_airfoil.py", + "kind": "docs-script", + "code": "from build123d import *\n\n# from ocp_vscode import show_all, set_defaults, Camera\n\n# set_defaults(reset_camera=Camera.KEEP)\n\nwith BuildLine() as airfoil:\n l1 = Airfoil(\"2213\")\ns = 100 / max(*airfoil.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(l1)\nsvg.write(\"assets/example_airfoil.svg\")\n# show_all()\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/objects_1d_blend_curve", + "source": "docs/objects_1d_blend_curve.py", + "kind": "docs-script", + "code": "from build123d import *\n\n# from ocp_vscode import show_all, set_defaults, Camera\n\n# set_defaults(reset_camera=Camera.KEEP)\n\nwith BuildLine() as blend_curve:\n l1 = CenterArc((0, 0), 5, 135, -135)\n l2 = Spline((0, -5), (-3, -8), (0, -11))\n l3 = BlendCurve(l1, l2, tangent_scalars=(2, 5))\ns = 100 / max(*blend_curve.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(l1, \"dashed\")\nsvg.add_shape(l2, \"dashed\")\nsvg.add_shape(l3)\nsvg.write(\"assets/example_blend_curve.svg\")\n# show_all()\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/objects_1d_bspline", + "source": "docs/objects_1d_bspline.py", + "kind": "docs-script", + "code": "from build123d import *\n\n# from ocp_vscode import show_all\n\ndot = Circle(0.05)\n\ncontrol_points = [(0, 0), (1, 2), (3, 2), (4, 0), (5, 1)]\nknots = [0.0, 0.0, 0.0, 0.0, 1.0, 2.0, 2.0, 2.0, 2.0]\nspline = BSpline(control_points, knots, degree=3)\n\ns = 100 / max(*spline.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(spline)\nfor p in control_points:\n svg.add_shape(Pos(*p) * dot.scale(1))\nsvg.write(\"assets/example_bspline.svg\")\n# show_all()\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/objects_1d_constrained", + "source": "docs/objects_1d_constrained.py", + "kind": "docs-script", + "code": "# [Setup]\nfrom build123d import *\n\n# from ocp_vscode import *\n\ndot = Circle(0.05)\n\nwith BuildLine() as arcs:\n c1 = CenterArc((4, 0), 2, 0, 360)\n c2 = CenterArc((0, 2), 1.5, 0, 360)\n a1 = ConstrainedArcs(c1, c2, radius=6)\n\ns = 100 / max(*arcs.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(c1, \"dashed\")\nsvg.add_shape(c2, \"dashed\")\nsvg.add_shape(a1)\nsvg.write(\"assets/constrained_arcs_example.svg\")\n\n\nwith BuildLine() as lines:\n c1 = CenterArc((4, 0), 2, 0, 360)\n c2 = CenterArc((0, 2), 1.5, 0, 360)\n l1 = ConstrainedLines(c1, c2)\n\ns = 100 / max(*lines.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(c1, \"dashed\")\nsvg.add_shape(c2, \"dashed\")\nsvg.add_shape(l1)\nsvg.write(\"assets/constrained_lines_example.svg\")\n\n# show_all()\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/objects_1d_ellipticalstartarc", + "source": "docs/objects_1d_ellipticalstartarc.py", + "kind": "docs-script", + "code": "# [Setup]\nfrom build123d import *\nfrom math import atan2, degrees\n# [removed by collect.py] from ocp_vscode import *\n\ndot = Circle(0.05)\n\ne_dir = Vector(0.2, 1)\nwith BuildLine() as arcs:\n a = EllipticalStartArc((1, 1), (0, 1), 3, 1, 160, major_axis_dir=e_dir)\n d = PolarLine(a.arc_center, 0.5, direction=e_dir)\n\n\nprint(a.arc_center)\ns = 100 / max(*arcs.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(Pos(1, 1) * dot.scale(1), \"dashed\")\nsvg.add_shape(PolarLine((1, 1), 0.5, 90), \"dashed\")\nsvg.add_shape(d, \"dashed\")\nsvg.add_shape(\n ArrowHead(0.2, rotation=degrees(atan2(e_dir.Y, e_dir.X))).moved(Pos(d @ 1)),\n \"dashed\",\n)\nsvg.add_shape(a)\nsvg.write(\"assets/elliptical_start_arc_example.svg\")\n\n\nshow_all()\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/objects_1d_parabolic_hyperbolic", + "source": "docs/objects_1d_parabolic_hyperbolic.py", + "kind": "docs-script", + "code": "# [Setup]\nfrom build123d import *\n\n# from ocp_vscode import *\n\ndot = Circle(0.05)\n\nwith BuildLine() as parabolic_center_arc:\n ParabolicCenterArc((0, 0), 0.25, -60, arc_size=120)\ns = 100 / max(*parabolic_center_arc.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(parabolic_center_arc.line)\nsvg.add_shape(dot.moved(Location(Vector((0, 0)))))\nsvg.write(\"assets/parabolic_center_arc_example.svg\")\n\nwith BuildLine() as hyperbolic_center_arc:\n HyperbolicCenterArc((0, 0), 0.5, 1, 0, arc_size=180)\ns = 100 / max(*hyperbolic_center_arc.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(hyperbolic_center_arc.line)\nsvg.add_shape(dot.moved(Location(Vector((0, 0)))))\nsvg.write(\"assets/hyperbolic_center_arc_example.svg\")\n\n# show_all()\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/objects_2d", + "source": "docs/objects_2d.py", + "kind": "docs-script", + "code": "# [Setup]\nfrom build123d import *\n\ndot = Circle(0.05)\n\n# [Setup]\nsvg_opts1 = {\"pixel_scale\": 100, \"show_axes\": False, \"show_hidden\": False}\nsvg_opts2 = {\"pixel_scale\": 300, \"show_axes\": True, \"show_hidden\": False}\nsvg_opts3 = {\"pixel_scale\": 2, \"show_axes\": False, \"show_hidden\": False}\nsvg_opts4 = {\"pixel_scale\": 5, \"show_axes\": False, \"show_hidden\": False}\n\n# [Ex. 1]\nwith BuildSketch() as example_1:\n Circle(1)\n# [Ex. 1]\ns = 100 / max(*example_1.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(example_1.sketch)\nsvg.write(\"assets/circle_example.svg\")\n\n# [Ex. 2]\nwith BuildSketch() as example_2:\n Ellipse(1.5, 1)\n# [Ex. 2]\ns = 100 / max(*example_2.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(example_2.sketch)\nsvg.write(\"assets/ellipse_example.svg\")\n\n# [Ex. 3]\nwith BuildSketch() as example_3:\n inner = PolarLocations(0.5, 5, 0).local_locations\n outer = PolarLocations(1.5, 5, 36).local_locations\n points = [p.position for pair in zip(inner, outer) for p in pair]\n Polygon(*points)\n# [Ex. 3]\ns = 100 / max(*example_3.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(example_3.sketch)\nsvg.write(\"assets/polygon_example.svg\")\n\n# [Ex. 4]\nwith BuildSketch() as example_4:\n Rectangle(2, 1)\n# [Ex. 4]\ns = 100 / max(*example_4.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(example_4.sketch)\nsvg.write(\"assets/rectangle_example.svg\")\n\n# [Ex. 5]\nwith BuildSketch() as example_5:\n RectangleRounded(2, 1, 0.25)\n# [Ex. 5]\ns = 100 / max(*example_5.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(example_5.sketch)\nsvg.write(\"assets/rectangle_rounded_example.svg\")\n\n# [Ex. 6]\nwith BuildSketch() as example_6:\n RegularPolygon(1, 6)\n# [Ex. 6]\ns = 100 / max(*example_6.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(example_6.sketch)\nsvg.write(\"assets/regular_polygon_example.svg\")\n\n# [Ex. 7]\nwith BuildSketch() as example_7:\n arc = Edge.make_circle(1, start_angle=0, end_angle=45)\n SlotArc(arc, 0.25)\n# [Ex. 7]\ns = 100 / max(*example_7.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.DASHED)\nsvg.add_shape(example_7.sketch)\nsvg.add_shape(arc, \"dashed\")\nsvg.write(\"assets/slot_arc_example.svg\")\n\n# [Ex. 8]\nwith BuildSketch() as example_8:\n c = (0, 0)\n p = (0, 1)\n SlotCenterPoint(c, p, 0.25)\n# [Ex. 8]\ns = 100 / max(*example_8.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.DASHED)\nsvg.add_shape(example_8.sketch)\nsvg.add_shape(dot.moved(Location(c)), \"dashed\")\nsvg.add_shape(dot.moved(Location(p)), \"dashed\")\nsvg.write(\"assets/slot_center_point_example.svg\")\n\n# [Ex. 9]\nwith BuildSketch() as example_9:\n SlotCenterToCenter(1, 0.25, rotation=90)\n# [Ex. 9]\ns = 100 / max(*example_9.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(example_9.sketch)\nsvg.write(\"assets/slot_center_to_center_example.svg\")\n\n# [Ex. 10]\nwith BuildSketch() as example_10:\n SlotOverall(1, 0.25)\n# [Ex. 10]\ns = 100 / max(*example_10.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(example_10.sketch)\nsvg.write(\"assets/slot_overall_example.svg\")\n\n# [Ex. 11]\nwith BuildSketch() as example_11:\n Text(\"text\", 1)\n# [Ex. 11]\ns = 100 / max(*example_11.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(example_11.sketch)\nsvg.write(\"assets/text_example.svg\")\n\n# [Ex. 12]\nwith BuildSketch() as example_12:\n t = Trapezoid(2, 1, 80)\n with Locations((-0.6, -0.3)):\n Text(\"80\u00b0\", 0.3, mode=Mode.SUBTRACT)\n# [Ex. 12]\ns = 100 / max(*example_12.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.DASHED)\nsvg.add_shape(\n Edge.make_circle(\n 0.75,\n Plane(tuple(t.vertices().group_by(Axis.Y)[0].sort_by(Axis.X)[0])),\n start_angle=0,\n end_angle=80,\n ),\n \"dashed\",\n)\nsvg.add_shape(example_12.sketch)\nsvg.write(\"assets/trapezoid_example.svg\")\n\n# [Ex. 13]\nlength, radius = 40.0, 60.0\n\nwith BuildSketch() as circle_with_hole:\n Circle(radius=radius)\n Rectangle(width=length, height=length, mode=Mode.SUBTRACT)\n# [Ex. 13]\ns = 100 / max(*circle_with_hole.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(circle_with_hole.sketch)\nsvg.write(\"assets/circle_with_hole.svg\")\n\n# [Ex. 14]\nwith BuildPart() as controller:\n # Create the side view of the controller\n with BuildSketch(Plane.YZ) as profile:\n with BuildLine():\n Polyline((0, 0), (0, 40), (20, 80), (40, 80), (40, 0), (0, 0))\n # Create a filled face from the perimeter drawing\n make_face()\n # Extrude to create the basis controller shape\n extrude(amount=30, both=True)\n # Round off all the edges\n fillet(controller.edges(), radius=3)\n # Hollow out the controller\n offset(amount=-1, mode=Mode.SUBTRACT)\n # Extract the face that will house the display\n display_face = (\n controller.faces()\n .filter_by(GeomType.PLANE)\n .filter_by_position(Axis.Z, 50, 70)[0]\n )\n # Create a workplane from the face\n display_workplane = Plane(\n origin=display_face.center(), x_dir=(1, 0, 0), z_dir=display_face.normal_at()\n )\n # Place the sketch directly on the controller\n with BuildSketch(display_workplane) as display:\n RectangleRounded(40, 30, 2)\n with GridLocations(45, 35, 2, 2):\n Circle(1)\n # Cut the display sketch through the controller\n extrude(amount=-1, mode=Mode.SUBTRACT)\n# [Ex. 14]\nvisible, hidden = controller.part.project_to_viewport((70, -50, 120))\nmax_dimension = max(*Compound(children=visible + hidden).bounding_box().size)\nexporter = ExportSVG(scale=100 / max_dimension)\nexporter.add_layer(\"Visible\")\nexporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)\nexporter.add_shape(visible, layer=\"Visible\")\nexporter.add_shape(hidden, layer=\"Hidden\")\nexporter.write(f\"assets/controller.svg\")\n\nd = Draft(line_width=0.1)\n# [Ex. 15]\nwith BuildSketch() as isosceles_triangle:\n t = Triangle(a=30, b=40, c=40)\n # [Ex. 15]\n ExtensionLine(t.edges().sort_by(Axis.Y)[0], 6, d, label=\"a\")\n ExtensionLine(t.edges().sort_by(Axis.X)[-1], 6, d, label=\"b\")\n ExtensionLine(t.edges().sort_by(SortBy.LENGTH)[-1], 6, d, label=\"c\")\na1 = CenterArc(t.vertices().group_by(Axis.Y)[0].sort_by(Axis.X)[0], 5, 0, t.B)\na2 = CenterArc(t.vertices().group_by(Axis.Y)[0].sort_by(Axis.X)[-1], 5, 180 - t.C, t.C)\na3 = CenterArc(t.vertices().sort_by(Axis.Y)[-1], 5, 270 - t.A / 2, t.A)\np1 = CenterArc(t.vertices().group_by(Axis.Y)[0].sort_by(Axis.X)[0], 8, 0, t.B)\np2 = CenterArc(t.vertices().group_by(Axis.Y)[0].sort_by(Axis.X)[-1], 8, 180 - t.C, t.C)\np3 = CenterArc(t.vertices().sort_by(Axis.Y)[-1], 8, 270 - t.A / 2, t.A)\nt1 = Text(\"B\", font_size=d.font_size).moved(Pos(p1 @ 0.5))\nt2 = Text(\"C\", font_size=d.font_size).moved(Pos(p2 @ 0.5))\nt3 = Text(\"A\", font_size=d.font_size).moved(Pos(p3 @ 0.5))\n\ns = 100 / max(*isosceles_triangle.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.DASHED)\nsvg.add_shape([a1, a2, a3], \"dashed\")\nsvg.add_shape(isosceles_triangle.sketch)\nsvg.add_shape([t1, t2, t3])\nsvg.write(\"assets/triangle_example.svg\")\n\n\n# [Align]\nwith BuildSketch() as align:\n with GridLocations(1, 1, 2, 2):\n Circle(0.5)\n Circle(0.49, mode=Mode.SUBTRACT)\n with GridLocations(1, 1, 1, 2):\n Circle(0.5)\n Circle(0.49, mode=Mode.SUBTRACT)\n with GridLocations(1, 1, 2, 1):\n Circle(0.5)\n Circle(0.49, mode=Mode.SUBTRACT)\n with Locations((0, 0)):\n Circle(0.5)\n Circle(0.49, mode=Mode.SUBTRACT)\n\n # Top Right: (MIN, MIN)\n with Locations((0.75, 0.75)):\n Text(\"MIN\\nMIN\", font=\"FreeSerif\", font_size=0.07)\n # Top Center: (CENTER, MIN)\n with Locations((0.0, 0.75 + 0.07 / 2)):\n Text(\"CENTER\", font=\"FreeSerif\", font_size=0.07)\n with Locations((0.0, 0.75 - 0.07 / 2)):\n Text(\"MIN\", font=\"FreeSerif\", font_size=0.07)\n # Top Left: (MAX, MIN)\n with Locations((-0.75, 0.75 + 0.07 / 2)):\n Text(\"MAX\", font=\"FreeSerif\", font_size=0.07)\n with Locations((-0.75, 0.75 - 0.07 / 2)):\n Text(\"MIN\", font=\"FreeSerif\", font_size=0.07)\n # Center Right: (MIN, CENTER)\n with Locations((0.75, 0.07 / 2)):\n Text(\"MIN\", font=\"FreeSerif\", font_size=0.07)\n with Locations((0.75, -0.07 / 2)):\n Text(\"CENTER\", font=\"FreeSerif\", font_size=0.07)\n # Center: (CENTER, CENTER)\n with Locations((0, 0)):\n Text(\"CENTER\\nCENTER\", font=\"FreeSerif\", font_size=0.07)\n # Center Left: (MAX, CENTER)\n with Locations((-0.75, 0.07 / 2)):\n Text(\"MAX\", font=\"FreeSerif\", font_size=0.07)\n with Locations((-0.75, -0.07 / 2)):\n Text(\"CENTER\", font=\"FreeSerif\", font_size=0.07)\n # Bottom Right: (MIN, MAX)\n with Locations((0.75, -0.75 + 0.07 / 2)):\n Text(\"MIN\", font=\"FreeSerif\", font_size=0.07)\n with Locations((0.75, -0.75 - 0.07 / 2)):\n Text(\"MAX\", font=\"FreeSerif\", font_size=0.07)\n # Bottom Center: (CENTER, MAX)\n with Locations((0.0, -0.75 + 0.07 / 2)):\n Text(\"CENTER\", font=\"FreeSerif\", font_size=0.07)\n with Locations((0.0, -0.75 - 0.07 / 2)):\n Text(\"MAX\", font=\"FreeSerif\", font_size=0.07)\n # Bottom Left: (MAx, MAX)\n with Locations((-0.75, -0.75)):\n Text(\"MAX\\nMAX\", font=\"FreeSerif\", font_size=0.07)\n\ns = 100 / max(*align.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(align.sketch)\nsvg.write(\"assets/align.svg\")\n\n# [DimensionLine]\nstd = Draft()\nwith BuildSketch() as d_line:\n Rectangle(100, 100)\n c = Circle(45, mode=Mode.SUBTRACT)\n DimensionLine([c.edge() @ 0, c.edge() @ 0.5], draft=std)\ns = 100 / max(*d_line.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(d_line.sketch)\nsvg.write(\"assets/d_line.svg\")\n\n# [ExtensionLine]\nwith BuildSketch() as e_line:\n with BuildLine():\n l1 = Polyline((20, 40), (-40, 40), (-40, -40), (20, -40))\n RadiusArc(l1 @ 0, l1 @ 1, 50)\n make_face()\n ExtensionLine(border=e_line.edges().sort_by(Axis.X)[0], offset=10, draft=std)\n outside_curve = e_line.edges().sort_by(Axis.X)[-1]\n ExtensionLine(border=outside_curve, offset=10, label_angle=True, draft=std)\ns = 100 / max(*e_line.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(e_line.sketch)\nsvg.write(\"assets/e_line.svg\")\n\n# [TechnicalDrawing]\nwith BuildSketch() as tech_drawing:\n with Locations((0, 20)):\n add(e_line)\n TechnicalDrawing()\ns = 100 / max(*tech_drawing.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(tech_drawing.sketch)\nsvg.write(\"assets/tech_drawing.svg\")\n\n# [ArrowHead]\narrow_head_types = [HeadType.CURVED, HeadType.STRAIGHT, HeadType.FILLETED]\narrow_heads = [ArrowHead(50, a_type) for a_type in arrow_head_types]\ns = 100 / max(*arrow_heads[0].bounding_box().size)\nsvg = ExportSVG(scale=s)\nfor i, arrow_head in enumerate(arrow_heads):\n svg.add_shape(arrow_head.moved(Location((0, -i * 40))))\n svg.add_shape(Text(arrow_head_types[i].name, 5).moved(Location((-25, -i * 40))))\nsvg.write(\"assets/arrow_head.svg\")\n\n# [Arrow]\narrow = Arrow(\n 10, shaft_path=Edge.make_circle(100, start_angle=0, end_angle=10), shaft_width=1\n)\ns = 100 / max(*arrow.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(arrow)\nsvg.write(\"assets/arrow.svg\")\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/objects_3d", + "source": "docs/objects_3d.py", + "kind": "docs-script", + "code": "# [Setup]\nfrom build123d import *\n\n# [Setup]\n\n\ndef write_svg(filename: str, view_port_origin=(-100, -50, 30)):\n \"\"\"Save an image of the BuildPart object as SVG\"\"\"\n builder: BuildPart = BuildPart._get_context()\n\n visible, hidden = builder.part.project_to_viewport(view_port_origin)\n max_dimension = max(*Compound(children=visible + hidden).bounding_box().size)\n exporter = ExportSVG(scale=100 / max_dimension)\n exporter.add_layer(\"Visible\")\n exporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)\n exporter.add_shape(visible, layer=\"Visible\")\n exporter.add_shape(hidden, layer=\"Hidden\")\n exporter.write(f\"assets/{filename}.svg\")\n\n\n# [Ex. 1]\nwith BuildPart() as example_1:\n Box(3, 2, 1)\n # [Ex. 1]\n pass # [removed by collect.py] write_svg(\"box_example\")\n\n# [Ex. 2]\nwith BuildPart() as example_2:\n Cone(2, 1, 2)\n # [Ex. 2]\n pass # [removed by collect.py] write_svg(\"cone_example\")\n\n# [Ex. 3]\nwith BuildPart() as example_3:\n Box(3, 2, 1)\n with Locations(example_3.faces().sort_by(Axis.Z)[-1]):\n CounterBoreHole(0.2, 0.4, 0.5, 0.9)\n # [Ex. 3]\n pass # [removed by collect.py] write_svg(\"counter_bore_hole_example\")\n\n\n# [Ex. 4]\nwith BuildPart() as example_4:\n Box(3, 2, 1)\n with Locations(example_3.faces().sort_by(Axis.Z)[-1]):\n CounterSinkHole(0.2, 0.4, 0.9)\n # [Ex. 4]\n pass # [removed by collect.py] write_svg(\"counter_sink_hole_example\")\n\n# [Ex. 5]\nwith BuildPart() as example_5:\n Cylinder(1, 2)\n # [Ex. 5]\n pass # [removed by collect.py] write_svg(\"cylinder_example\")\n\n# [Ex. 6]\nwith BuildPart() as example_6:\n Box(3, 2, 1)\n Hole(0.4)\n # [Ex. 6]\n pass # [removed by collect.py] write_svg(\"hole_example\")\n\n# [Ex. 7]\nwith BuildPart() as example_7:\n Sphere(1, 0)\n # [Ex. 7]\n pass # [removed by collect.py] write_svg(\"sphere_example\")\n\n# [Ex. 8]\nwith BuildPart() as example_8:\n Torus(1, 0.2)\n # [Ex. 8]\n pass # [removed by collect.py] write_svg(\"torus_example\")\n\n# [Ex. 9]\nwith BuildPart() as example_9:\n Wedge(1, 1, 1, 0, 0, 0.5, 0.5)\n # [Ex. 9]\n pass # [removed by collect.py] write_svg(\"wedge_example\")\n\n# [Ex. 10]\nwith BuildPart() as example_10:\n Box(30, 20, 20)\n Box(20, 30, 20)\n Box(20, 20, 30)\n with Locations((-10, 0, 0)):\n Box(40, 23, 23)\n ConvexPolyhedron(example_10.vertices())\n # [Ex. 10]\n pass # [removed by collect.py] write_svg(\"convex_polyhedron_example\")\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/pack_demo", + "source": "docs/pack_demo.py", + "kind": "docs-script", + "code": "# [import]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n\n# [initial space]\nb1 = Box(100, 100, 100, align=(Align.CENTER, Align.CENTER, Align.MIN))\nb2 = Box(54, 54, 54, align=(Align.CENTER, Align.CENTER, Align.MAX), mode=Mode.SUBTRACT)\nb3 = Box(34, 34, 34, align=(Align.MIN, Align.MIN, Align.CENTER), mode=Mode.SUBTRACT)\nb4 = Box(24, 24, 24, align=(Align.MAX, Align.MAX, Align.CENTER), mode=Mode.SUBTRACT)\n\n\n\n\n# [Export SVG files]\ndef write_svg(part, filename: str, view_port_origin=(-100, 100, 150)):\n \"\"\"Save an image of the BuildPart object as SVG\"\"\"\n visible, hidden = part.project_to_viewport(view_port_origin)\n max_dimension = max(*Compound(children=visible + hidden).bounding_box().size)\n exporter = ExportSVG(scale=100 / max_dimension)\n exporter.add_layer(\"Visible\", line_weight=0.2)\n exporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)\n exporter.add_shape(visible, layer=\"Visible\")\n exporter.add_shape(hidden, layer=\"Hidden\")\n exporter.write(f\"assets/{filename}.svg\")\n\n\n\n\n# [removed by collect.py] write_svg(\n# [removed by collect.py] Compound(\n# [removed by collect.py] [b1, b2, b3, b4,],\n# [removed by collect.py] \"pack_demo_initial_state\"\n# [removed by collect.py] ),\n# [removed by collect.py] \"pack_demo_initial_state.svg\",\n# [removed by collect.py] (50, 0, 100),\n# [removed by collect.py] )\n\n# [pack 2D]\n\nxy_pack = pack(\n [b1, b2, b3, b4],\n padding=5,\n align_z=False\n)\n\n# [removed by collect.py] write_svg(Compound(xy_pack), \"pack_demo_packed_xy.svg\", (50, 0, 100))\n\n\n# [Pack and align_z]\n\n\nz_pack = pack(\n [b1, b2, b3, b4],\n padding=5,\n align_z=True\n)\n\n# [removed by collect.py] write_svg(Compound(z_pack), \"pack_demo_packed_z.svg\", (50, 0, 100))\n\n\n# [bounding box]\nprint(Compound(xy_pack).bounding_box())\nprint(Compound(z_pack).bounding_box())", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/rigid_joints_pipe", + "source": "docs/rigid_joints_pipe.py", + "kind": "docs-script", + "code": "import copy\nfrom build123d import *\nfrom bd_warehouse.flange import WeldNeckFlange\nfrom bd_warehouse.pipe import PipeSection\n# [removed by collect.py] from ocp_vscode import *\n\nflange_inlet = WeldNeckFlange(nps=\"10\", flange_class=300)\nflange_outlet = copy.copy(flange_inlet)\n\nwith BuildPart() as pipe_builder:\n # Create the pipe\n with BuildLine():\n path = TangentArc((0, 0, 0), (2 * FT, 0, 1 * FT), tangent=(1, 0, 0))\n with BuildSketch(Plane(origin=path @ 0, z_dir=path % 0)):\n PipeSection(\"10\", material=\"stainless\", identifier=\"40S\")\n sweep()\n\n # Add the joints\n RigidJoint(label=\"inlet\", joint_location=-path.location_at(0))\n RigidJoint(label=\"outlet\", joint_location=path.location_at(1))\n\n# Place the flanges at the ends of the pipe\npipe_builder.part.joints[\"inlet\"].connect_to(flange_inlet.joints[\"pipe\"])\npipe_builder.part.joints[\"outlet\"].connect_to(flange_outlet.joints[\"pipe\"])\n\nshow(pipe_builder, flange_inlet, flange_outlet, render_joints=True)\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/rod_end", + "source": "docs/rod_end.py", + "kind": "docs-script", + "code": "from build123d import *\nfrom bd_warehouse.thread import IsoThread\n# [removed by collect.py] from ocp_vscode import *\n\n# Create the thread so the min radius is available below\nthread = IsoThread(major_diameter=6, pitch=1, length=20, end_finishes=(\"fade\", \"raw\"))\ninner_radius = 15.89 / 2\ninner_gap = 0.2\n\nwith BuildPart() as rod_end:\n # Create the outer shape\n with BuildSketch():\n Circle(22.25 / 2)\n with Locations((0, -12)):\n Rectangle(8, 1)\n make_hull()\n split(bisect_by=Plane.YZ)\n revolve(axis=Axis.Y)\n # Refine the shape\n with BuildSketch(Plane.YZ) as s2:\n Rectangle(25, 8, align=(Align.MIN, Align.CENTER))\n Rectangle(9, 10, align=(Align.MIN, Align.CENTER))\n chamfer(s2.vertices(), 0.5)\n revolve(axis=Axis.Z, mode=Mode.INTERSECT)\n # Add the screw shaft\n Cylinder(\n thread.min_radius,\n 30,\n rotation=(90, 0, 0),\n align=(Align.CENTER, Align.CENTER, Align.MIN),\n )\n # Cutout the ball socket\n Sphere(inner_radius, mode=Mode.SUBTRACT)\n # Add thread\n with Locations((0, -30, 0)):\n add(thread, rotation=(-90, 0, 0))\n # Create the ball joint\n BallJoint(\n \"socket\",\n joint_location=Location(),\n angular_range=((-14, 14), (-14, 14), (0, 360)),\n )\n\nwith BuildPart() as ball:\n Sphere(inner_radius - inner_gap)\n Box(50, 50, 13, mode=Mode.INTERSECT)\n Hole(4)\n ball.part.color = Color(\"aliceblue\")\n RigidJoint(\"ball\", joint_location=Location())\n\nrod_end.part.joints[\"socket\"].connect_to(ball.part.joints[\"ball\"], angles=(5, 10, 0))\n\nshow(rod_end.part, ball.part, s2)\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/selector_example", + "source": "docs/selector_example.py", + "kind": "docs-script", + "code": "# [Code]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nwith BuildPart() as example:\n Cylinder(radius=10, height=3)\n with BuildSketch(example.faces().sort_by(Axis.Z)[-1]):\n RegularPolygon(radius=7, side_count=6)\n Circle(radius=4, mode=Mode.SUBTRACT)\n extrude(amount=-2, mode=Mode.SUBTRACT)\n visible, hidden = example.part.project_to_viewport((-100, 100, 100))\n exporter = ExportSVG(scale=6)\n exporter.add_layer(\"Visible\")\n exporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)\n exporter.add_shape(visible, layer=\"Visible\")\n exporter.add_shape(hidden, layer=\"Hidden\")\n exporter.write(\"assets/selector_before.svg\")\n\n fillet(\n example.edges()\n .filter_by(GeomType.CIRCLE)\n .sort_by(SortBy.RADIUS)[-2:]\n .sort_by(Axis.Z)[-1],\n radius=1,\n )\n\nvisible, hidden = example.part.project_to_viewport((-100, 100, 100))\nexporter = ExportSVG(scale=6)\nexporter.add_layer(\"Visible\")\nexporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)\nexporter.add_shape(visible, layer=\"Visible\")\nexporter.add_shape(hidden, layer=\"Hidden\")\nexporter.write(\"assets/selector_after.svg\")\n\nshow(example)\n# [End]\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/slide_latch", + "source": "docs/slide_latch.py", + "kind": "docs-script", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nwith BuildPart() as latch:\n # Basic box shape to start with filleted corners\n Box(70, 30, 14)\n end = latch.faces().sort_by(Axis.X)[-1] # save the end with the hole\n fillet(latch.edges().filter_by(Axis.Z), 2)\n fillet(latch.edges().sort_by(Axis.Z)[-1], 1)\n # Make screw tabs\n with BuildSketch(latch.faces().sort_by(Axis.Z)[0]) as l4:\n with Locations((-30, 0), (30, 0)):\n SlotOverall(50, 10, rotation=90)\n Rectangle(50, 30)\n fillet(l4.vertices(Select.LAST), radius=2)\n extrude(amount=-2)\n with GridLocations(60, 40, 2, 2):\n Hole(2)\n # Create the hole from the end saved previously\n with BuildSketch(end) as slide_hole:\n add(end)\n offset(amount=-2)\n fillet(slide_hole.vertices(), 1)\n extrude(amount=-68, mode=Mode.SUBTRACT)\n # Slot for the handle to slide in\n with BuildSketch(latch.faces().sort_by(Axis.Z)[-1]):\n SlotOverall(32, 8)\n extrude(amount=-2, mode=Mode.SUBTRACT)\n # The slider will move align the x axis 12mm in each direction\n LinearJoint(\"latch\", axis=Axis.X, linear_range=(-12, 12))\n\nwith BuildPart() as slide:\n # The slide will be a little smaller than the hole\n with BuildSketch() as s1:\n add(slide_hole.sketch)\n offset(amount=-0.25)\n # The extrusions aren't symmetric\n extrude(amount=46)\n extrude(slide.faces().sort_by(Axis.Z)[0], amount=20)\n # Round off the ends\n fillet(slide.edges().group_by(Axis.Z)[0], 1)\n fillet(slide.edges().group_by(Axis.Z)[-1], 1)\n # Create the knob\n with BuildSketch() as s2:\n with Locations((12, 0)):\n SlotOverall(15, 4, rotation=90)\n Rectangle(12, 7, align=(Align.MIN, Align.CENTER))\n fillet(s2.vertices(Select.LAST), 1)\n split(bisect_by=Plane.XZ)\n revolve(axis=Axis.X)\n # Align the joint to Plane.ZY flipped\n RigidJoint(\"slide\", joint_location=Location(-Plane.ZY))\n\n# Position the slide in the latch: -12 >= position <= 12\nlatch.part.joints[\"latch\"].connect_to(slide.part.joints[\"slide\"], position=12)\n\n# show(latch.part, render_joints=True)\n# show(slide.part, render_joints=True)\nshow(latch.part, slide.part, render_joints=True)\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/spitfire_wing_gordon", + "source": "docs/spitfire_wing_gordon.py", + "kind": "docs-script", + "code": "import pytest\n\n# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nwing_span = 36 * FT + 10 * IN\nwing_leading = 2.5 * FT\nwing_trailing = wing_span / 4 - wing_leading\nwing_leading_fraction = wing_leading / (wing_leading + wing_trailing)\nwing_tip_section = wing_span / 2 - 1 * IN # distance from root to last section\n\n# Create leading and trailing edges\nleading_edge = EllipticalCenterArc(\n (0, 0), wing_span / 2, wing_leading, start_angle=270, arc_size=90\n)\ntrailing_edge = EllipticalCenterArc(\n (0, 0), wing_span / 2, wing_trailing, start_angle=0, arc_size=90\n)\n\n# [AirfoilSizes]\n# Calculate the airfoil sizes from the leading/trailing edges\nairfoil_sizes = []\nfor i in [0, 1]:\n tip_axis = Axis(i * (wing_tip_section, 0, 0), (0, 1, 0))\n leading_pnt = leading_edge.intersect(tip_axis)[0]\n trailing_pnt = trailing_edge.intersect(tip_axis)[0]\n airfoil_sizes.append(trailing_pnt.Y - leading_pnt.Y)\n\n# [Airfoils]\n# Create the root and tip airfoils - note that they are different NACA profiles\nairfoil_root = Plane.YZ * scale(\n Airfoil(\"2213\").move(Pos(-wing_leading_fraction, 0, 0)),\n airfoil_sizes[0],\n about=(0, 0, 0),\n)\nairfoil_tip = (\n Plane.YZ\n * Pos(Z=wing_tip_section)\n * scale(\n Airfoil(\"2205\").move(Pos(-wing_leading_fraction, 0, 0)),\n airfoil_sizes[1],\n about=(0, 0, 0),\n )\n)\n\n# [Profiles]\n# Create the Gordon surface profiles and guides\nprofiles = airfoil_root.edges() + airfoil_tip.edges()\nprofiles.append(leading_edge @ 1) # wing tip\nguides = [leading_edge, trailing_edge]\n\n# Create the wing surface as a Gordon Surface\nwing_surface = -Face.make_gordon_surface(profiles, guides)\n# Create the root of the wing\nwing_root = -Face(Wire(wing_surface.edges().filter_by(Edge.is_closed)))\n\n# [Solid]\n# Create the wing Solid\nwing = Solid(Shell([wing_surface, wing_root]))\nwing.color = 0x99A3B9 # Azure Blue\n\nshow(wing)\n# [End]\n\nassert wing.volume / 1e9 == pytest.approx(1.9879945989)\n\n# Documentation artifact generation\n# wing_control_edges = Curve(\n# [airfoil_root, airfoil_tip, Vertex(leading_edge @ 1), leading_edge, trailing_edge]\n# )\n# visible, _ = wing_control_edges.project_to_viewport((50 * FT, -50 * FT, 50 * FT))\n# max_dimension = max(*Compound(children=visible).bounding_box().size)\n# svg = ExportSVG(scale=100 / max_dimension)\n# svg.add_shape(visible)\n# svg.write(\"assets/surface_modeling/spitfire_wing_profiles_guides.svg\")\n\n# export_gltf(\n# wing,\n# \"assets/surface_modeling/spitfire_wing.glb\",\n# binary=True,\n# linear_deflection=0.1,\n# angular_deflection=1,\n# )\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/technical_drawing", + "source": "docs/technical_drawing.py", + "kind": "docs-script", + "code": "# [code]\nfrom datetime import date\n\nfrom bd_warehouse.open_builds import StepperMotor\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n\ndef project_to_2d(\n part: Part,\n viewport_origin: VectorLike,\n viewport_up: VectorLike,\n page_origin: VectorLike,\n scale_factor: float = 1.0,\n) -> tuple[ShapeList[Edge], ShapeList[Edge]]:\n \"\"\"project_to_2d\n\n Helper function to generate 2d views translated on the 2d page.\n\n Args:\n part (Part): 3d object\n viewport_origin (VectorLike): location of viewport\n viewport_up (VectorLike): direction of the viewport Y axis\n page_origin (VectorLike): center of 2d object on page\n scale_factor (float, optional): part scalar. Defaults to 1.0.\n\n Returns:\n tuple[ShapeList[Edge], ShapeList[Edge]]: visible & hidden edges\n \"\"\"\n scaled_part = part if scale_factor == 1.0 else scale(part, scale_factor)\n visible, hidden = scaled_part.project_to_viewport(\n viewport_origin, viewport_up, look_at=(0, 0, 0)\n )\n visible = [Pos(*page_origin) * e for e in visible]\n hidden = [Pos(*page_origin) * e for e in hidden]\n\n return ShapeList(visible), ShapeList(hidden)\n\n\n# The object that appearing in the drawing\nstepper: Part = StepperMotor(\"Nema23\")\n\n# Create a standard technical drawing border on A4 paper\nborder = TechnicalDrawing(\n designed_by=\"build123d\",\n design_date=date.fromisoformat(\"2025-05-23\"),\n page_size=PageSize.A4,\n title=\"Nema 23 Stepper\",\n sub_title=\"Units: mm\",\n drawing_number=\"BD-1\",\n sheet_number=1,\n drawing_scale=1,\n)\npage_size = border.bounding_box().size\n\n# Specify the drafting options for extension lines\ndrafting_options = Draft(font_size=3.5, decimal_precision=1, display_units=False)\n\n# Lists used to store the 2d visible and hidden lines\nvisible_lines, hidden_lines = [], []\n\n# Isometric Projection - A 3D view where the part is rotated to reveal three\n# dimensions equally.\niso_v, iso_h = project_to_2d(\n stepper,\n (100, 100, 100),\n (0, 0, 1),\n page_size * 0.3,\n 0.75,\n)\nvisible_lines.extend(iso_v)\nhidden_lines.extend(iso_h)\n\n# Plan View (Top) - The view from directly above the part (looking down along\n# the Z-axis).\nvis, _ = project_to_2d(\n stepper,\n (0, 0, 100),\n (0, 1, 0),\n (page_size.X * -0.3, page_size.Y * 0.25),\n)\nvisible_lines.extend(vis)\n\n# Dimension the top of the stepper\ntop_bbox = Curve(vis).bounding_box()\nperimeter = Pos(*top_bbox.center()) * Rectangle(top_bbox.size.X, top_bbox.size.Y)\nd1 = ExtensionLine(\n border=perimeter.edges().sort_by(Axis.X)[-1], offset=1 * CM, draft=drafting_options\n)\nd2 = ExtensionLine(\n border=perimeter.edges().sort_by(Axis.Y)[0], offset=1 * CM, draft=drafting_options\n)\n# Add a label\nl1 = Text(\"Plan View\", 6)\nl1.position = vis.sort_by(Axis.Y)[-1].center() + (0, 5 * MM)\n\n# Front Elevation - The primary view, typically looking along the Y-axis,\n# showing the height.\nvis, _ = project_to_2d(\n stepper,\n (0, -100, 0),\n (0, 0, 1),\n (page_size.X * -0.3, page_size.Y * -0.125),\n)\nvisible_lines.extend(vis)\nd3 = ExtensionLine(\n border=vis.sort_by(Axis.Y)[-1], offset=-5 * MM, draft=drafting_options\n)\nl2 = Text(\"Front Elevation\", 6)\nl2.position = vis.group_by(Axis.Y)[0].sort_by(Edge.length)[-1].center() + (0, -5 * MM)\n\n# Side Elevation - Often refers to the Right Side View, looking along the X-axis.\nvis, _ = project_to_2d(\n stepper,\n (100, 0, 0),\n (0, 0, 1),\n (0, page_size.Y * 0.15),\n)\nvisible_lines.extend(vis)\nside_bbox = Curve(vis).bounding_box()\nshaft_top_corner = vis.edges().sort_by(Axis.Y)[-1].vertices().sort_by(Axis.X)[-1]\nbody_bottom_corner = (side_bbox.max.X, side_bbox.min.Y)\nd4 = ExtensionLine(\n border=(shaft_top_corner, body_bottom_corner),\n offset=-(side_bbox.max.X - shaft_top_corner.X) - 1 * CM, # offset to outside view.\n measurement_direction=(0, 1, 0),\n draft=drafting_options,\n)\nl3 = Text(\"Side Elevation\", 6)\nl3.position = vis.group_by(Axis.Y)[0].sort_by(Edge.length)[-1].center() + (0, -5 * MM)\n\n\n# Initialize the SVG exporter\nexporter = ExportSVG(unit=Unit.MM)\n# Define visible and hidden line layers\nexporter.add_layer(\"Visible\")\nexporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)\n# Add the objects to the appropriate layer\nexporter.add_shape(visible_lines, layer=\"Visible\")\nexporter.add_shape(hidden_lines, layer=\"Hidden\")\nexporter.add_shape(border, layer=\"Visible\")\nexporter.add_shape([d1, d2, d3, d4], layer=\"Visible\")\nexporter.add_shape([l1, l2, l3], layer=\"Visible\")\n# Write the file\nexporter.write(f\"assets/stepper_drawing.svg\")\n\nshow(border, visible_lines, d1, d2, d3, d4, l1, l2, l3)\n# [end]\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/tutorial_joints", + "source": "docs/tutorial_joints.py", + "kind": "docs-script", + "code": "# [import]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n\n# [Hinge Class]\nclass Hinge(Compound):\n \"\"\"Hinge\n\n Half a simple hinge with several joints. The joints are:\n - \"leaf\": RigidJoint where hinge attaches to object\n - \"hinge_axis\": RigidJoint (inner) or RevoluteJoint (outer)\n - \"hole0\", \"hole1\", \"hole2\": CylindricalJoints for attachment screws\n\n Args:\n width (float): width of one leaf\n length (float): hinge length\n barrel_diameter (float): size of hinge pin barrel\n thickness (float): hinge leaf thickness\n pin_diameter (float): hinge pin diameter\n inner (bool, optional): inner or outer half of hinge . Defaults to True.\n \"\"\"\n\n def __init__(\n self,\n width: float,\n length: float,\n barrel_diameter: float,\n thickness: float,\n pin_diameter: float,\n inner: bool = True,\n ):\n # The profile of the hinge used to create the tabs\n with BuildPart() as hinge_profile:\n with BuildSketch():\n for i, loc in enumerate(\n GridLocations(0, length / 5, 1, 5, align=(Align.MIN, Align.MIN))\n ):\n if i % 2 == inner:\n with Locations(loc):\n Rectangle(width, length / 5, align=(Align.MIN, Align.MIN))\n Rectangle(\n width - barrel_diameter,\n length,\n align=(Align.MIN, Align.MIN),\n )\n extrude(amount=-barrel_diameter)\n\n # The hinge pin\n with BuildPart() as pin:\n Cylinder(\n radius=pin_diameter / 2,\n height=length,\n align=(Align.CENTER, Align.CENTER, Align.MIN),\n )\n with BuildPart(pin.part.faces().sort_by(Axis.Z)[-1]) as pin_head:\n Cylinder(\n radius=barrel_diameter / 2,\n height=pin_diameter,\n align=(Align.CENTER, Align.CENTER, Align.MIN),\n )\n fillet(\n pin.edges(Select.LAST).filter_by(GeomType.CIRCLE),\n radius=pin_diameter / 3,\n )\n\n # Either the external and internal leaf with joints\n with BuildPart() as leaf_builder:\n with BuildSketch():\n with BuildLine():\n l1 = Line((0, 0), (width - barrel_diameter / 2, 0))\n l2 = RadiusArc(\n l1 @ 1,\n l1 @ 1 + Vector(0, barrel_diameter),\n -barrel_diameter / 2,\n )\n l3 = RadiusArc(\n l2 @ 1,\n (\n width - barrel_diameter,\n barrel_diameter / 2,\n ),\n -barrel_diameter / 2,\n )\n l4 = Line(l3 @ 1, (width - barrel_diameter, thickness))\n l5 = Line(l4 @ 1, (0, thickness))\n Line(l5 @ 1, l1 @ 0)\n make_face()\n with Locations(\n (width - barrel_diameter / 2, barrel_diameter / 2)\n ) as pin_center:\n Circle(pin_diameter / 2 + 0.1 * MM, mode=Mode.SUBTRACT)\n extrude(amount=length)\n add(hinge_profile.part, rotation=(90, 0, 0), mode=Mode.INTERSECT)\n\n # Create holes for fasteners\n with Locations(leaf_builder.part.faces().filter_by(Axis.Y)[-1]):\n with GridLocations(0, length / 3, 1, 3):\n holes = CounterSinkHole(3 * MM, 5 * MM)\n # Add the hinge pin to the external leaf\n if not inner:\n with Locations(pin_center.locations[0]):\n add(pin.part)\n\n # [Create the Joints]\n #\n # Leaf attachment\n RigidJoint(\n label=\"leaf\",\n joint_location=Location(\n (width - barrel_diameter, 0, length / 2), (90, 0, 0)\n ),\n )\n # [Hinge Axis] (fixed with inner)\n if inner:\n RigidJoint(\n \"hinge_axis\",\n joint_location=Location(\n (width - barrel_diameter / 2, barrel_diameter / 2, 0)\n ),\n )\n else:\n RevoluteJoint(\n \"hinge_axis\",\n axis=Axis(\n (width - barrel_diameter / 2, barrel_diameter / 2, 0), (0, 0, 1)\n ),\n angular_range=(90, 270),\n )\n # [Fastener holes]\n hole_locations = [hole.location for hole in holes]\n for hole, hole_location in enumerate(hole_locations):\n CylindricalJoint(\n label=\"hole\" + str(hole),\n axis=Axis(hole_location),\n linear_range=(-2 * CM, 2 * CM),\n angular_range=(0, 360),\n )\n # [End Fastener holes]\n super().__init__(leaf_builder.part.wrapped, joints=leaf_builder.part.joints)\n # [Hinge Class]\n\n\n# [Create instances of the two leaves of the hinge]\nhinge_inner = Hinge(\n width=5 * CM,\n length=12 * CM,\n barrel_diameter=1 * CM,\n thickness=2 * MM,\n pin_diameter=4 * MM,\n)\nhinge_outer = Hinge(\n width=5 * CM,\n length=12 * CM,\n barrel_diameter=1 * CM,\n thickness=2 * MM,\n pin_diameter=4 * MM,\n inner=False,\n)\n\n# [Create the box with a RigidJoint to mount the hinge]\nwith BuildPart() as box_builder:\n box = Box(30 * CM, 30 * CM, 10 * CM)\n offset(amount=-1 * CM, openings=box_builder.faces().sort_by(Axis.Z)[-1])\n # Create a notch for the hinge\n with Locations((-15 * CM, 0, 5 * CM)):\n Box(2 * CM, 12 * CM, 4 * MM, mode=Mode.SUBTRACT)\n bbox = box.bounding_box()\n with Locations(\n Plane(origin=(bbox.min.X, 0, bbox.max.Z - 30 * MM), z_dir=(-1, 0, 0))\n ):\n with GridLocations(0, 40 * MM, 1, 3):\n Hole(3 * MM, 1 * CM)\n RigidJoint(\n \"hinge_attachment\",\n joint_location=Location((-15 * CM, 0, 4 * CM), (180, 90, 0)),\n )\n# [Demonstrate that objects with Joints can be moved and the joints follow]\nbox = box_builder.part.moved(Location((0, 0, 5 * CM)))\n\n# [The lid with a RigidJoint for the hinge]\nwith BuildPart() as lid_builder:\n Box(30 * CM, 30 * CM, 1 * CM, align=(Align.MIN, Align.CENTER, Align.MIN))\n with Locations((2 * CM, 0, 0)):\n with GridLocations(0, 40 * MM, 1, 3):\n Hole(3 * MM, 1 * CM)\n RigidJoint(\n \"hinge_attachment\",\n joint_location=Location((0, 0, 0), (0, 0, 180)),\n )\nlid = lid_builder.part\n\n# [A screw to attach the hinge to the box]\nm6_screw = import_step(\"M6-1x12-countersunk-screw.step\")\nm6_joint = RigidJoint(\"head\", m6_screw, Location((0, 0, 0), (0, 0, 0)))\n# [End of screw creation]\n\n\n# [Export SVG files]\ndef write_svg(part, filename: str, view_port_origin=(-100, 100, 150)):\n \"\"\"Save an image of the BuildPart object as SVG\"\"\"\n visible, hidden = part.project_to_viewport(view_port_origin)\n max_dimension = max(*Compound(children=visible + hidden).bounding_box().size)\n exporter = ExportSVG(scale=100 / max_dimension)\n exporter.add_layer(\"Visible\")\n exporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)\n exporter.add_shape(visible, layer=\"Visible\")\n exporter.add_shape(hidden, layer=\"Hidden\")\n exporter.write(f\"assets/{filename}.svg\")\n\n\n#\n# SVG Export options\n# [removed by collect.py] write_svg(\n# [removed by collect.py] Compound([box, box.joints[\"hinge_attachment\"].symbol]),\n# [removed by collect.py] \"tutorial_joint_box\",\n# [removed by collect.py] )\n# [removed by collect.py] write_svg(\n# [removed by collect.py] Compound(\n# [removed by collect.py] [\n# [removed by collect.py] hinge_inner,\n# [removed by collect.py] hinge_inner.joints[\"leaf\"].symbol,\n# [removed by collect.py] hinge_inner.joints[\"hinge_axis\"].symbol,\n# [removed by collect.py] hinge_inner.joints[\"hole0\"].symbol,\n# [removed by collect.py] hinge_inner.joints[\"hole1\"].symbol,\n# [removed by collect.py] hinge_inner.joints[\"hole2\"].symbol,\n# [removed by collect.py] ]\n# [removed by collect.py] ),\n# [removed by collect.py] \"tutorial_joint_inner_leaf\",\n# [removed by collect.py] (100, 100, -50),\n# [removed by collect.py] )\n# [removed by collect.py] write_svg(\n# [removed by collect.py] Compound(\n# [removed by collect.py] [\n# [removed by collect.py] hinge_outer,\n# [removed by collect.py] hinge_outer.joints[\"leaf\"].symbol,\n# [removed by collect.py] hinge_outer.joints[\"hinge_axis\"].symbol,\n# [removed by collect.py] hinge_outer.joints[\"hole0\"].symbol,\n# [removed by collect.py] hinge_outer.joints[\"hole1\"].symbol,\n# [removed by collect.py] hinge_outer.joints[\"hole2\"].symbol,\n# [removed by collect.py] ]\n# [removed by collect.py] ),\n# [removed by collect.py] \"tutorial_joint_outer_leaf\",\n# [removed by collect.py] (100, 100, -50),\n# [removed by collect.py] )\n# [removed by collect.py] write_svg(\n# [removed by collect.py] Compound([box, hinge_outer]),\n# [removed by collect.py] \"tutorial_joint_box_outer\",\n# [removed by collect.py] (-100, -100, 50),\n# [removed by collect.py] )\n# [removed by collect.py] write_svg(\n# [removed by collect.py] Compound([lid, lid.joints[\"hinge_attachment\"].symbol]),\n# [removed by collect.py] \"tutorial_joint_lid\",\n# [removed by collect.py] (-100, 100, 150),\n# [removed by collect.py] )\n# [removed by collect.py] write_svg(\n# [removed by collect.py] Compound([m6_screw, m6_joint.symbol]),\n# [removed by collect.py] \"tutorial_joint_m6_screw\",\n# [removed by collect.py] (-100, 100, 150),\n# [removed by collect.py] )\n\n# [Connect Box to Outer Hinge]\nbox.joints[\"hinge_attachment\"].connect_to(hinge_outer.joints[\"leaf\"])\n# [Connect Box to Outer Hinge]\n# [removed by collect.py] write_svg(\n# [removed by collect.py] Compound([box, hinge_outer]),\n# [removed by collect.py] \"tutorial_joint_box_outer\",\n# [removed by collect.py] (-100, -100, 50),\n# [removed by collect.py] )\n# [Connect Hinge Leaves]\nhinge_outer.joints[\"hinge_axis\"].connect_to(hinge_inner.joints[\"hinge_axis\"], angle=120)\n# [Connect Hinge Leaves]\n# [removed by collect.py] write_svg(\n# [removed by collect.py] Compound([box, hinge_outer, hinge_inner]),\n# [removed by collect.py] \"tutorial_joint_box_outer_inner\",\n# [removed by collect.py] (-100, -100, 50),\n# [removed by collect.py] )\n# [Connect Hinge to Lid]\nhinge_inner.joints[\"leaf\"].connect_to(lid.joints[\"hinge_attachment\"])\n# [Connect Hinge to Lid]\n# [removed by collect.py] write_svg(\n# [removed by collect.py] Compound([box, hinge_outer, hinge_inner, lid]),\n# [removed by collect.py] \"tutorial_joint_box_outer_inner_lid\",\n# [removed by collect.py] (-100, -100, 50),\n# [removed by collect.py] )\n# [Connect Screw to Hole]\nhinge_outer.joints[\"hole2\"].connect_to(m6_joint, position=5 * MM, angle=30)\n# [Connect Screw to Hole]\n\n# [Add labels]\nbox.label = \"box\"\nlid.label = \"lid\"\nhinge_outer.label = \"outer hinge\"\nhinge_inner.label = \"inner hinge\"\nm6_screw.label = \"M6 screw\"\n\n# [Create assembly]\nbox_assembly = Compound(label=\"assembly\", children=[box, lid, hinge_inner, hinge_outer])\n# [Display assembly]\nprint(box_assembly.show_topology())\n\n# [Add to the assembly by assigning the parent attribute of an object]\nm6_screw.parent = box_assembly\nprint(box_assembly.show_topology())\n\n# [Check that the components in the assembly don't intersect]\nchild_intersect, children, volume = box_assembly.do_children_intersect(\n include_parent=False\n)\nprint(f\"do children intersect: {child_intersect}\")\nif child_intersect:\n print(f\"{children} by {volume:0.3f} mm^3\")\n\n# [Export Final SVG file]\n# [removed by collect.py] write_svg(box_assembly, \"tutorial_joint\", (-100, -100, 50))\n\n\nshow_object(box, name=\"box\", options={\"alpha\": 0.8})\n# show_object(box.joints[\"hinge_attachment\"].symbol, name=\"box attachment point\")\nshow_object(hinge_outer, name=\"hinge_outer\")\n# show_object(hinge_outer.joints[\"leaf\"].symbol, name=\"hinge_outer leaf joint\")\n# show_object(hinge_outer.joints[\"hinge_axis\"].symbol, name=\"hinge_outer hinge axis\")\nshow_object(lid, name=\"lid\")\n# show_object(lid.joints[\"hinge_attachment\"].symbol, name=\"lid attachment point\")\nshow_object(hinge_inner, name=\"hinge_inner\")\n# show_object(hinge_inner.joints[\"leaf\"].symbol, name=\"hinge_inner leaf joint\")\n# show_object(hinge_inner.joints[\"hinge_axis\"].symbol, name=\"hinge_inner hinge axis\")\nfor hole in [0, 1, 2]:\n show_object(\n hinge_inner.joints[\"hole\" + str(hole)].symbol,\n name=\"hinge_inner hole \" + str(hole),\n )\n show_object(\n hinge_outer.joints[\"hole\" + str(hole)].symbol,\n name=\"hinge_outer hole \" + str(hole),\n )\nshow_object(m6_screw, name=\"m6 screw\")\nshow_object(m6_joint.symbol, name=\"m6 screw symbol\")\nshow_object(box_assembly, name=\"box assembly\")\n", + "data_dir": "docs", + "assets": [ + "M6-1x12-countersunk-screw.step" + ] + }, + { + "id": "docs-objects/text", + "source": "docs/objects/examples/text.py", + "kind": "docs-script", + "code": "from build123d import Text, Pos, Compound, TextAlign, Align, Location, RadiusArc\nfrom tcv_screenshots import save_model\n\n\ntext = \"The quick brown fox\"\nsave_model(Text(text, 10), \"text\", {\"reset_camera\": \"top\"})\npath = RadiusArc((-50, 0), (50, 0), 100)\nsave_model([path, Text(text, 10, path=path, position_on_path=.5, text_align=(TextAlign.CENTER, TextAlign.BOTTOM))], \"path\", {\"reset_camera\": \"top\"})\nsave_model([Pos(Y=10) * Text(text, 10, \"singleline\"), Text(text, 10, \"singleline\", single_line_width=1)], \"outline\", {\"reset_camera\": \"top\"})\nsave_model(Compound.make_text(text, 10, \"singleline\"), \"singleline\", {\"reset_camera\": \"top\"})\n\ntext = \"The quick brown\\nfox jumped over\\nthe lazy dog.\"\nsave_model([Location(), Text(text, 2, text_align=(TextAlign.LEFT, TextAlign.TOPFIRSTLINE))], \"text_align\", {\"reset_camera\": \"top\"})\nsave_model([Location(), Text(text, 2, align=(Align.MIN, Align.MIN))], \"align\", {\"reset_camera\": \"top\"})\n\nt = Text(\"The\", 10, \"Source Sans 3 Black\")\nsave_model([(Pos(Y=10) * t).wires(), t], \"missing_glyph\", {\"reset_camera\": \"top\"})\n", + "data_dir": "docs/objects/examples", + "assets": [] + }, + { + "id": "docs-selectors/filter_all_edges_circle", + "source": "docs/topology_selection/examples/filter_all_edges_circle.py", + "kind": "docs-script", + "code": "import os\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nwith BuildPart() as part:\n with BuildSketch() as s:\n Rectangle(115, 50)\n with Locations((5 / 2, 0)):\n SlotOverall(90, 12, mode=Mode.SUBTRACT)\n extrude(amount=15)\n\n with BuildSketch(Plane.XZ.offset(50 / 2)) as s3:\n with Locations((-115 / 2 + 26, 15)):\n SlotOverall(42 + 2 * 26 + 12, 2 * 26, rotation=90)\n zz = extrude(amount=-12)\n split(bisect_by=Plane.XY)\n edgs = part.part.edges().filter_by(Axis.Y).group_by(Axis.X)[-2]\n fillet(edgs, 9)\n\n with Locations(zz.faces().sort_by(Axis.Y)[0]):\n with Locations((42 / 2 + 6, 0)):\n CounterBoreHole(24 / 2, 34 / 2, 4)\n mirror(about=Plane.XZ)\n\n with BuildSketch() as s4:\n RectangleRounded(115, 50, 6)\n extrude(amount=80, mode=Mode.INTERSECT)\n # fillet does not work right, mode intersect is safer\n\n with BuildSketch(Plane.YZ) as s4:\n with BuildLine() as bl:\n l1 = Line((0, 0), (18 / 2, 0))\n l2 = PolarLine(l1 @ 1, 8, 60, length_mode=LengthMode.VERTICAL)\n l3 = Line(l2 @ 1, (0, 8))\n mirror(about=Plane.YZ)\n make_face()\n extrude(amount=115 / 2, both=True, mode=Mode.SUBTRACT)\n\n faces = part.faces().filter_by(\n lambda f: all(e.geom_type == GeomType.CIRCLE for e in f.edges())\n )\n for i, f in enumerate(faces):\n RigidJoint(f\"bearing_bore_{i}\", joint_location=f.center_location)\n\nshow(part, [f.translate(f.normal_at() * 0.01) for f in faces], render_joints=True)\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"filter_all_edges_circle.png\"))\n", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/filter_axisplane", + "source": "docs/topology_selection/examples/filter_axisplane.py", + "kind": "docs-script", + "code": "import os\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\naxis = Axis.Z\nplane = Plane.XY\nwith BuildPart() as part:\n with BuildSketch(Plane.XY.shift_origin((1, 1))) as plane_rep:\n Rectangle(2, 2)\n with Locations((-.9, -.9)):\n Text(\"Plane.XY\", .2, align=(Align.MIN, Align.MIN), mode=Mode.SUBTRACT)\n plane_rep = plane_rep.sketch\n plane_rep.color = Color(0, .55, .55, .1)\n\n with Locations((-1, -1, 0)):\n b = Box(1, 1, 1)\n f = b.faces()\n res = f.filter_by(axis)\n axis_rep = [Axis(f.center(), f.normal_at()) for f in res]\n show_object([b, res, axis_rep])\n\n with Locations((1, 1, 0)):\n b = Box(1, 1, 1)\n f = b.faces()\n res = f.filter_by(plane)\n show_object([b, res, plane_rep])\n\n pass # [removed by collect.py] save_screenshot(os.path.join(filedir, \"filter_axisplane.png\"))\n pass # [removed by collect.py] reset_show()\n\n with Locations((-1, -1, 0)):\n b = Box(1, 1, 1)\n f = b.faces()\n res = f.filter_by(lambda f: abs(f.normal_at().dot(axis.direction)) < 1e-6)\n show_object([b, res, axis_rep])\n\n with Locations((1, 1, 0)):\n b = Box(1, 1, 1)\n f = b.faces()\n res = f.filter_by(lambda f: abs(f.normal_at().dot(plane.z_dir)) < 1e-6)\n show_object([b, res, plane_rep])\n\n pass # [removed by collect.py] save_screenshot(os.path.join(filedir, \"filter_dot_axisplane.png\"))", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/filter_geomtype", + "source": "docs/topology_selection/examples/filter_geomtype.py", + "kind": "docs-script", + "code": "import os\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nwith BuildPart() as part:\n Box(5, 5, 1)\n Cylinder(2, 5)\n edges = part.edges().filter_by(lambda a: a.length == 1)\n fillet(edges, 1)\n\npart.edges().filter_by(GeomType.LINE)\n\npart.faces().filter_by(GeomType.CYLINDER)\n\nshow(part, part.edges().filter_by(GeomType.LINE))\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"filter_geomtype_line.png\"))\n\nshow(part, part.faces().filter_by(GeomType.CYLINDER))\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"filter_geomtype_cylinder.png\"))", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/filter_inner_wire_count", + "source": "docs/topology_selection/examples/filter_inner_wire_count.py", + "kind": "docs-script", + "code": "from copy import copy\nimport os\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nbracket = import_step(os.path.join(working_path, \"nema-17-bracket.step\"))\nfaces = bracket.faces()\n\nmotor_mounts = faces.filter_by(GeomType.CYLINDER).filter_by(lambda f: f.radius == 3.3/2)\nfor i, f in enumerate(motor_mounts):\n location = f.axis_of_rotation.location\n RigidJoint(f\"motor_m3_{i}\", bracket, joint_location=location)\n\nmotor_face = faces.filter_by(lambda f: len(f.inner_wires()) == 5).sort_by(Axis.X)[-1]\nmotor_bore = motor_face.inner_wires().edges().filter_by(lambda e: e.radius == 16).edge()\nlocation = Location(motor_bore.arc_center, motor_bore.normal() * 90, Intrinsic.YXZ)\nRigidJoint(f\"motor\", bracket, joint_location=location)\n\nbefore_linear = copy(bracket)\n\nmount_face = faces.filter_by(lambda f: len(f.inner_wires()) == 6).sort_by(Axis.Z)[-1]\nmount_slots = mount_face.inner_wires().edges().filter_by(GeomType.CIRCLE)\njoint_edges = [\n Line(mount_slots[i].arc_center, mount_slots[i + 1].arc_center)\n for i in range(0, len(mount_slots), 2)\n]\nfor i, e in enumerate(joint_edges):\n LinearJoint(f\"mount_m4_{i}\", bracket, axis=Axis(e), linear_range=(0, e.length / 2))\n\nshow(before_linear, render_joints=True)\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"filter_inner_wire_count.png\"))\n\nshow(bracket, render_joints=True)\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"filter_inner_wire_count_linear.png\"))", + "data_dir": "docs/topology_selection/examples", + "assets": [ + "nema-17-bracket.step" + ] + }, + { + "id": "docs-selectors/filter_nested", + "source": "docs/topology_selection/examples/filter_nested.py", + "kind": "docs-script", + "code": "from copy import copy\nimport os\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nwith BuildPart() as part:\n Cylinder(15, 2, align=(Align.CENTER, Align.CENTER, Align.MIN))\n with BuildSketch():\n RectangleRounded(10, 10, 2.5)\n extrude(amount=15)\n\n with BuildSketch():\n Circle(2.5)\n Rectangle(4, 5, mode=Mode.INTERSECT)\n extrude(amount=15, mode=Mode.SUBTRACT)\n\n with GridLocations(20, 0, 2, 1):\n Hole(3.5 / 2)\n\n before = copy(part)\n\n faces = part.faces().filter_by(\n lambda f: len(f.inner_wires().edges().filter_by(GeomType.LINE)) == 2\n )\n wires = faces.wires().filter_by(\n lambda w: any(e.geom_type == GeomType.LINE for e in w.edges())\n )\n chamfer(wires.edges(), 0.5)\n\nlocation = Location((-25, -25))\nb = before.part.moved(location)\nf = [f.moved(location) for f in faces]\n\nshow(b, f, part)\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"filter_nested.png\"))\n", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/filter_shape_properties", + "source": "docs/topology_selection/examples/filter_shape_properties.py", + "kind": "docs-script", + "code": "import os\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nwith BuildPart() as open_box_builder:\n Box(20, 20, 5)\n offset(amount=-2, openings=open_box_builder.faces().sort_by(Axis.Z)[-1])\n inside_edges = open_box_builder.edges().filter_by(Edge.is_interior)\n fillet(inside_edges, 1.5)\n outside_edges = open_box_builder.edges().filter_by(Edge.is_interior, reverse=True)\n fillet(outside_edges, 0.5)\n\nopen_box = open_box_builder.part\nopen_box.color = Color(0xEDAE49)\noutside_fillets = Compound(open_box.faces().filter_by(Face.is_circular_convex))\noutside_fillets.color = Color(0xD1495B)\ninside_fillets = Compound(open_box.faces().filter_by(Face.is_circular_concave))\ninside_fillets.color = Color(0x00798C)\n\nshow(open_box, inside_fillets, outside_fillets)\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"filter_shape_properties.png\"))", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/group_axis", + "source": "docs/topology_selection/examples/group_axis.py", + "kind": "docs-script", + "code": "import os\nfrom copy import copy\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nwith BuildPart() as fins:\n with GridLocations(4, 6, 4, 4):\n Box(2, 3, 10, align=(Align.CENTER, Align.CENTER, Align.MIN))\n\nwith BuildPart() as part:\n Box(34, 48, 5, align=(Align.CENTER, Align.CENTER, Align.MAX))\n with GridLocations(20, 27, 2, 2):\n add(fins)\n\n without = copy(part)\n\n target = part.edges().group_by(Axis.Z)[-1].group_by(Edge.length)[-1]\n fillet(target, .75)\n\nshow(without)\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"group_axis_without.png\"))\n\nshow(part)\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"group_axis_with.png\"))", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/group_hole_area", + "source": "docs/topology_selection/examples/group_hole_area.py", + "kind": "docs-script", + "code": "from copy import copy\nimport os\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nwith BuildPart() as part:\n Cylinder(10, 30, rotation=(90, 0, 0))\n Cylinder(8, 40, rotation=(90, 0, 0), align=(Align.CENTER, Align.CENTER, Align.MAX))\n Cylinder(8, 23, rotation=(90, 0, 0), align=(Align.CENTER, Align.CENTER, Align.MIN))\n Cylinder(5, 40, rotation=(90, 0, 0), align=(Align.CENTER, Align.CENTER, Align.MIN))\n with BuildSketch(Plane.XY.offset(8)) as s:\n SlotCenterPoint((0, 38), (0, 48), 5)\n extrude(amount=2.5, both=True, mode=Mode.SUBTRACT)\n\n before = copy(part)\n\n faces = part.faces().group_by(\n lambda f: Face(f.inner_wires()[0]).area if f.inner_wires() else 0\n )\n chamfer([f.outer_wire().edges() for f in faces[-1]], 0.5)\n\nshow(\n before,\n [f.translate(f.normal_at() * 0.01) for group in faces for f in group],\n part.part.translate((40, 40)),\n)\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"group_hole_area.png\"))\n", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/group_properties_with_keys", + "source": "docs/topology_selection/examples/group_properties_with_keys.py", + "kind": "docs-script", + "code": "import os\nfrom copy import copy\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nwith BuildPart() as part:\n with BuildSketch(Plane.XZ) as sketch:\n with BuildLine():\n CenterArc((-6, 12), 10, 0, 360)\n Line((-16, 0), (16, 0))\n make_hull()\n Rectangle(50, 5, align=(Align.CENTER, Align.MAX))\n\n extrude(amount=12)\n\n Box(38, 6, 22, align=(Align.CENTER, Align.MAX, Align.MIN), mode=Mode.SUBTRACT)\n\n circle = part.edges().filter_by(GeomType.CIRCLE).sort_by(Axis.Y)[0]\n with Locations(Plane(circle.arc_center, z_dir=circle.normal())):\n CounterBoreHole(13 / 2, 16 / 2, 4)\n\n mirror(about=Plane.XZ)\n\n before_fillet = copy(part)\n\n length_groups = part.edges().group_by(Edge.length)\n fillet(length_groups.group(6) + length_groups.group(5), 4)\n\n after_fillet = copy(part)\n\n with BuildSketch() as pins:\n with Locations((-21, 0)):\n Circle(3 / 2)\n with Locations((21, 0)):\n SlotCenterToCenter(1, 3)\n extrude(amount=-12, mode=Mode.SUBTRACT)\n\n with GridLocations(42, 16, 2, 2):\n CounterBoreHole(3.5 / 2, 3.5, 0)\n\n after_holes = copy(part)\n\n radius_groups = part.edges().filter_by(GeomType.CIRCLE).group_by(Edge.radius)\n bearing_edges = radius_groups.group(8).group_by(SortBy.DISTANCE)[-1]\n pin_edges = radius_groups.group(1.5).filter_by_position(Axis.Z, -5, -5)\n chamfer([pin_edges, bearing_edges], .5)\n\nlocation = Location((-20, -20))\nitems = [before_fillet.part] + length_groups.group(6) + length_groups.group(5)\nbefore = Compound(items).move(location)\nshow(before, after_fillet.part.move(Location((20, 20))))\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"group_length_key.png\"))\n\nlocation = Location((-20, -20), (180, 0, 0))\nafter = Compound([after_holes.part] + pin_edges + bearing_edges).move(location)\nshow(after, part.part.move(Location((20, 20), (180, 0, 0))))\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"group_radius_key.png\"))", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/selectors_operators", + "source": "docs/topology_selection/examples/selectors_operators.py", + "kind": "docs-script", + "code": "from copy import copy\nimport os\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nselectors = [solids, vertices, edges, faces]\nline = Line((-9, -9), (9, 9))\nfor i, selector in enumerate(selectors):\n u = i / (len(selectors) - 1)\n with BuildPart() as part:\n with Locations(line @ u):\n Box(5, 5, 1)\n Cylinder(2, 5)\n show_object([part, selector()])\n\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"selectors_select_all.png\"))\n# [removed by collect.py] reset_show()\n\nfor i, selector in enumerate(selectors[1:4]):\n u = i / (len(selectors) - 1)\n with BuildPart() as part:\n with Locations(line @ u):\n Box(5, 5, 1)\n Cylinder(2, 5)\n show_object([part, selector(Select.LAST)])\n\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"selectors_select_last.png\"))\n# [removed by collect.py] reset_show()\n\nwith BuildPart() as part:\n with Locations(line @ 1/3):\n Box(5, 5, 1)\n Cylinder(2, 5)\n edges = part.edges(Select.NEW)\n part_copy = copy(part)\n\n with Locations(line @ 2/3):\n b = Box(5, 5, 1)\n c = Cylinder(2, 5)\n c.color = Color(\"DarkTurquoise\")\n\n show(part_copy, edges, b, c, alphas=[.5, 1, .5, 1])\n\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"selectors_select_new.png\"))\n# [removed by collect.py] reset_show()\n\nwith BuildPart() as part:\n with Locations(line @ 1/3):\n Box(5, 5, 1, align=(Align.CENTER, Align.CENTER, Align.MAX))\n Cylinder(2, 2, align=(Align.CENTER, Align.CENTER, Align.MIN))\n edges = part.edges(Select.NEW)\n part_copy = copy(part)\n\n with Locations(line @ 2/3):\n b = Box(5, 5, 1, align=(Align.CENTER, Align.CENTER, Align.MAX), mode=Mode.PRIVATE)\n c = Cylinder(2, 2, align=(Align.CENTER, Align.CENTER, Align.MIN), mode=Mode.PRIVATE)\n c.color = Color(\"DarkTurquoise\")\n show(part_copy, edges, b, c, alphas=[.5, 1, .5, 1])\n\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"selectors_select_new_none.png\"))\n# [removed by collect.py] reset_show()\n\nwith BuildPart() as part:\n with Locations(line @ 1/3):\n Box(5, 5, 1)\n Cylinder(2, 5)\n edges = part.edges().filter_by(lambda a: a.length == 1)\n fillet(edges, 1)\n show_object([part, part.edges(Select.NEW)])\n\nwith BuildPart() as part:\n with Locations(line @ 2/3):\n Box(5, 5, 1)\n Cylinder(2, 5)\n edges = part.edges().filter_by(lambda a: a.length == 1)\n fillet(edges, 1)\n show_object([part, part.edges(Select.LAST)])\n\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"selectors_select_new_fillet.png\"))\n\nshow(part, part.vertices().sort_by(Axis.X)[-4:])\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"operators_sort_x.png\"))\n\nshow(part, part.faces().group_by(SortBy.AREA)[0].edges())\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"operators_group_area.png\"))\n\nfaces = part.faces().filter_by(lambda f: f.normal_at() == Vector(0, 0, 1))\nshow(part, [f.translate(f.normal_at() * 0.01) for f in faces])\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"operators_filter_z_normal.png\"))\n\nbox = Box(5, 5, 1)\ncircle = Cylinder(2, 5)\npart = box + circle\nedges = new_edges(box, circle, combined=part)\nshow(part, edges)\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"selectors_new_edges.png\"))", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/sort_along_wire", + "source": "docs/topology_selection/examples/sort_along_wire.py", + "kind": "docs-script", + "code": "import os\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nwith BuildSketch() as along_wire:\n Rectangle(48, 16, align=Align.MIN)\n Rectangle(16, 48, align=Align.MIN)\n Rectangle(32, 32, align=Align.MIN)\n\n for i, v in enumerate(along_wire.vertices()):\n fillet(v, i + 1)\n\nshow(along_wire)\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"sort_not_along_wire.png\"))\n\n\nwith BuildSketch() as along_wire:\n Rectangle(48, 16, align=Align.MIN)\n Rectangle(16, 48, align=Align.MIN)\n Rectangle(32, 32, align=Align.MIN)\n\n sorted_verts = along_wire.vertices().sort_by(along_wire.wire())\n for i, v in enumerate(sorted_verts):\n fillet(v, i + 1)\n\nshow(along_wire)\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"sort_along_wire.png\"))", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/sort_axis", + "source": "docs/topology_selection/examples/sort_axis.py", + "kind": "docs-script", + "code": "from copy import copy\nimport os\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nwith BuildPart() as part:\n with BuildSketch(Plane.YZ) as profile:\n with BuildLine():\n l1 = FilletPolyline((16, 0), (32, 0), (32, 25), radius=12)\n l2 = FilletPolyline((16, 4), (28, 4), (28, 15), radius=8)\n Line(l1 @ 0, l2 @ 0)\n Polyline(l1 @ 1, l1 @ 1 - Vector(2, 0), l2 @ 1 + Vector(2, 0), l2 @ 1)\n make_face()\n extrude(amount=34)\n\n before = copy(part).part\n\n face = part.faces().sort_by(Axis.X)[-1]\n edge = face.edges().sort_by(Axis.Y)[0]\n revolve(face, -Axis(edge), 90)\n\nf = face.translate(face.normal_at() * 0.01)\nshow(before, f, edge, part.part.translate((25, 33)))\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"sort_axis.png\"))\n", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/sort_distance_from", + "source": "docs/topology_selection/examples/sort_distance_from.py", + "kind": "docs-script", + "code": "import os\nfrom itertools import product\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nboxes = ShapeList(\n Box(1, 1, 1).scale(0.75 if (i, j) == (1, 2) else 0.25).translate((i, j, 0))\n for i, j in product(range(-3, 4), repeat=2)\n)\n\nboxes = boxes.sort_by_distance(Vertex())\nshow(*boxes, colors=ColorMap.listed(len(boxes)))\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"sort_distance_from_origin.png\"))\n\nboxes = boxes.sort_by_distance(boxes.sort_by(Solid.volume).last)\nshow(*boxes, colors=ColorMap.listed(len(boxes)))\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"sort_distance_from_largest.png\"))", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/sort_sortby", + "source": "docs/topology_selection/examples/sort_sortby.py", + "kind": "docs-script", + "code": "import os\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nwith BuildPart() as part:\n Box(5, 5, 1)\n Cylinder(2, 5)\n edges = part.edges().filter_by(lambda a: a.length == 1)\n fillet(edges, 1)\n\nbox = Box(5, 5, 5).move(Location((-6, -6)))\nsphere = Sphere(5 / 2).move(Location((6, 6)))\nsolids = ShapeList([part.part, box, sphere])\n\npart.wires().sort_by(SortBy.LENGTH)[:4]\n\npart.wires().sort_by(Wire.length)[:4]\npart.wires().group_by(SortBy.LENGTH)[0]\n\npart.vertices().sort_by(SortBy.DISTANCE)[-2:]\n\npart.vertices().sort_by_distance(Vertex())[-2:]\npart.vertices().group_by(Vertex().distance)[-1]\n\n\nshow(part, part.wires().sort_by(SortBy.LENGTH)[:4])\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"sort_sortby_length.png\"))\n\n# show(part, part.faces().sort_by(SortBy.AREA)[-2:])\n# save_screenshot(os.path.join(filedir, \"sort_sortby_area.png\"))\n\n# solid = solids.sort_by(SortBy.VOLUME)[-1]\n# solid.color = \"violet\"\n# show([part, box, sphere], solid)\n# save_screenshot(os.path.join(filedir, \"sort_sortby_volume.png\"))\n\n# show(part, part.edges().filter_by(GeomType.CIRCLE).sort_by(SortBy.RADIUS)[-4:])\n# save_screenshot(os.path.join(filedir, \"sort_sortby_radius.png\"))\n\nshow(part, part.vertices().sort_by(SortBy.DISTANCE)[-2:])\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"sort_sortby_distance.png\"))", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "ttt/ttt-23-02-02-sm_hanger", + "source": "docs/assets/ttt/ttt-23-02-02-sm_hanger.py", + "kind": "ttt", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nsheet_thickness = 4 * MM\n\n# Create the main body from a side profile\nwith BuildPart() as side:\n with BuildLine(Plane.XZ) as side_line:\n l1 = Line((0, 65), (170 / 2, 65))\n l2 = PolarLine(\n l1 @ 1,\n length=65,\n direction=(0.5, -0.866025403784),\n length_mode=LengthMode.VERTICAL,\n )\n l3 = Line(l2 @ 1, (170 / 2, 0))\n fillet(side_line.vertices(), 7)\n make_brake_formed(\n thickness=sheet_thickness,\n station_widths=[40, 40, 40, 112.52 / 2, 112.52 / 2, 112.52 / 2],\n side=Side.RIGHT,\n )\n # Ensure the part is always on the +ve side of Plane.YZ\n if side.vertices().sort_by(Axis.X)[0].X < -sheet_thickness:\n mirror(about=Plane.YZ, mode=Mode.REPLACE)\n fe = side.edges().filter_by(Axis.Z).group_by(Axis.Z)[0].sort_by(Axis.Y)[-1]\n fillet(fe, radius=7)\n\n# Create the \"wings\" at the top\nwith BuildPart() as wing:\n with BuildLine(Plane.YZ) as wing_line:\n l1 = Line((0, 65), (80 / 2 + 1.526 * sheet_thickness, 65))\n PolarLine(l1 @ 1, 20.371288916, direction=(0.258819045103, -0.965925826289))\n fillet(wing_line.vertices(), 7)\n make_brake_formed(\n thickness=sheet_thickness,\n station_widths=110 / 2,\n side=Side.RIGHT,\n )\n # Ensure the part is always on the +ve side of Plane.YZ\n if wing.vertices().sort_by(Axis.X)[0].X < -sheet_thickness:\n mirror(about=Plane.YZ, mode=Mode.REPLACE)\n bottom_edge = wing.edges().group_by(Axis.X)[-1].sort_by(Axis.Z)[0]\n fillet(bottom_edge, radius=7)\n\n# Create the tab at the top in Algebra mode\ntab_line = Plane.XZ * Polyline(\n (20, 65 - sheet_thickness), (56 / 2, 65 - sheet_thickness), (56 / 2, 88)\n)\ntab_line = fillet(tab_line.vertices(), 7)\ntab = make_brake_formed(sheet_thickness, 8, tab_line, Side.RIGHT)\n# Ensure the tab is always on the +ve side of Plane.XZ\nif tab.vertices().sort_by(Axis.Y)[0].Y < -sheet_thickness:\n tab = mirror(tab, about=Plane.XZ)\ntab = fillet(tab.edges().filter_by(Axis.X).group_by(Axis.Z)[-1].sort_by(Axis.Y)[-1], 5)\ntab -= Pos((0, 0, 80)) * Rot(0, 90, 0) * Hole(5, 100)\n\n# Combine the parts together\nwith BuildPart() as sm_hanger:\n add([side.part, wing.part])\n mirror(about=Plane.XZ)\n with BuildSketch(Plane.XY.offset(65)) as h1:\n with Locations((20, 0)):\n Rectangle(30, 30, align=(Align.MIN, Align.CENTER))\n fillet(h1.vertices().group_by(Axis.X)[-1], 7)\n SlotCenterPoint((154, 0), (154 / 2, 0), 20)\n extrude(amount=-40, mode=Mode.SUBTRACT)\n with BuildSketch() as h2:\n SlotCenterPoint((206, 0), (206 / 2, 0), 20)\n extrude(amount=40, mode=Mode.SUBTRACT)\n add(tab)\n mirror(about=Plane.YZ)\n mirror(about=Plane.XZ)\n\ngot_mass = sm_hanger.part.volume * 7800 * 1e-6\nwant_mass = 1028\ntolerance = 10\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.1f} g\")\nassert delta < tolerance, f\"{got_mass=}, {want_mass=}, {delta=}, {tolerance=}\"\n\n# assert abs(got_mass - 1028) < 10, f\"{got_mass=}, want=1028, tolerance=10\"\n\nshow(sm_hanger)\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-23-t-24-curved_support", + "source": "docs/assets/ttt/ttt-23-t-24-curved_support.py", + "kind": "ttt", + "code": "from math import sin, cos, tan, radians\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\nimport sympy\n\n# This problem uses the sympy symbolic math solver\n\n# Define the symbols for the unknowns\n# - the center of the radius 30 arc (x30, y30)\n# - the center of the radius 66 arc (x66, y66)\n# - end of the 8\u00b0 line (l8x, l8y)\n# - the point with the radius 30 and 66 arc meet i30_66\n# - the start of the horizontal line lh\ny30, x66, xl8, yl8 = sympy.symbols(\"y30 x66 xl8 yl8\")\nx30 = 77 - 55 / 2\ny66 = 66 + 32\n\n# There are 4 unknowns so we need 4 equations\nequations = [\n (x66 - x30) ** 2 + (y66 - y30) ** 2 - (66 + 30) ** 2, # distance between centers\n xl8 - (x30 + 30 * sin(radians(8))), # 8 degree slope\n yl8 - (y30 + 30 * cos(radians(8))), # 8 degree slope\n (yl8 - 50) / (55 / 2 - xl8) - tan(radians(8)), # 8 degree slope\n]\n# There are two solutions but we want the 2nd one\nsolution = {k: float(v) for k,v in sympy.solve(equations, dict=True)[1].items()}\n\n# Create the critical points\nc30 = Vector(x30, solution[y30])\nc66 = Vector(solution[x66], y66)\nl8 = Vector(solution[xl8], solution[yl8])\ni30_66 = Line(c30, c66) @ (30 / (30 + 66))\nlh = Vector(c66.X, 32)\n\nwith BuildLine() as profile:\n l1 = Line((55 / 2, 50), l8)\n l2 = RadiusArc(l1 @ 1, i30_66, 30)\n l3 = RadiusArc(l2 @ 1, lh, -66)\n l4 = Polyline(l3 @ 1, (125, 32), (125, 0), (0, 0), (0, (l1 @ 0).Y), l1 @ 0)\n\nwith BuildPart() as curved_support:\n with BuildSketch() as base_plan:\n c_8_degrees = Circle(55 / 2)\n with Locations((0, 125)):\n Circle(30 / 2)\n base_hull = make_hull(mode=Mode.PRIVATE)\n extrude(amount=32)\n extrude(c_8_degrees, amount=60)\n extrude(base_hull, amount=11)\n with BuildSketch(Plane.YZ) as bridge:\n make_face(profile.edges())\n extrude(amount=11 / 2, both=True)\n Hole(35 / 2)\n with Locations((0, 125)):\n Hole(20 / 2)\n\ngot_mass = curved_support.part.volume * 7800e-6\nwant_mass = 1294\ndelta = abs(got_mass - want_mass)\ntolerance = 3\nprint(f\"Mass: {got_mass:0.1f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n\nshow(curved_support)\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-24-SPO-06-Buffer_Stand", + "source": "docs/assets/ttt/ttt-24-SPO-06-Buffer_Stand.py", + "kind": "ttt", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nwith BuildPart() as p:\n with BuildSketch() as xy:\n with BuildLine():\n l1 = ThreePointArc((5 / 2, -1.25), (5.5 / 2, 0), (5 / 2, 1.25))\n Polyline(l1 @ 0, (0, -1.25), (0, 1.25), l1 @ 1)\n make_face()\n extrude(amount=4)\n\n with BuildSketch(Plane.YZ) as yz:\n Trapezoid(2.5, 4, 90 - 6, align=(Align.CENTER, Align.MIN))\n full_round(yz.edges().sort_by(SortBy.LENGTH)[0])\n circle_edge = yz.edges().filter_by(GeomType.CIRCLE)[0]\n arc_center = circle_edge.arc_center\n arc_radius = circle_edge.radius\n extrude(amount=10, mode=Mode.INTERSECT)\n\n # To avoid OCCT problems, don't attempt to extend the top arc, remove instead\n with BuildPart(mode=Mode.SUBTRACT) as internals:\n y = p.edges().filter_by(Axis.X).sort_by(Axis.Z)[-1].center().Z\n\n with BuildSketch(Plane.YZ.offset(4.25 / 2)) as yz:\n Trapezoid(2.5, y, 90 - 6, align=(Align.CENTER, Align.MIN))\n with Locations(arc_center):\n Circle(arc_radius, mode=Mode.SUBTRACT)\n extrude(amount=-(4.25 - 3.5) / 2)\n\n with BuildSketch(Plane.YZ.offset(3.5 / 2)) as yz:\n Trapezoid(2.5, 4, 90 - 6, align=(Align.CENTER, Align.MIN))\n extrude(amount=-3.5 / 2)\n\n with BuildSketch(Plane.XZ.offset(-2)) as xz:\n with Locations((0, 4)):\n RectangleRounded(4.25, 7.5, 0.5)\n extrude(amount=4, mode=Mode.INTERSECT)\n\n with Locations(p.faces(Select.LAST).filter_by(GeomType.PLANE).sort_by(Axis.Z)[-1]):\n CounterBoreHole(0.625 / 2, 1.25 / 2, 0.5)\n\n with BuildSketch(Plane.YZ) as rib:\n with Locations((0, 0.25)):\n Trapezoid(0.5, 1, 90 - 8, align=(Align.CENTER, Align.MIN))\n full_round(rib.edges().sort_by(SortBy.LENGTH)[0])\n extrude(amount=4.25 / 2)\n\n mirror(about=Plane.YZ)\n\npart = scale(p.part, IN)\n\n\ngot_mass = part.volume * 7800e-6 / LB\nwant_mass = 3.923\ntolerance = 0.02\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.1f} lbs\")\nassert delta < tolerance, f\"{got_mass=}, {want_mass=}, {delta=}, {tolerance=}\"\n\nshow(p)\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-ppp0101", + "source": "docs/assets/ttt/ttt-ppp0101.py", + "kind": "ttt", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\nwith BuildPart() as p:\n with BuildSketch() as s:\n Rectangle(115, 50)\n with Locations((5 / 2, 0)):\n SlotOverall(90, 12, mode=Mode.SUBTRACT)\n extrude(amount=15)\n\n with BuildSketch(Plane.XZ.offset(50 / 2)) as s3:\n with Locations((-115 / 2 + 26, 15)):\n SlotOverall(42 + 2 * 26 + 12, 2 * 26, rotation=90)\n zz = extrude(amount=-12)\n split(bisect_by=Plane.XY)\n edgs = p.part.edges().filter_by(Axis.Y).group_by(Axis.X)[-2]\n fillet(edgs, 9)\n\n with Locations(zz.faces().sort_by(Axis.Y)[0]):\n with Locations((42 / 2 + 6, 0)):\n CounterBoreHole(24 / 2, 34 / 2, 4)\n mirror(about=Plane.XZ)\n\n with BuildSketch() as s4:\n RectangleRounded(115, 50, 6)\n extrude(amount=80, mode=Mode.INTERSECT)\n # fillet does not work right, mode intersect is safer\n\n with BuildSketch(Plane.YZ) as s4:\n with BuildLine() as bl:\n l1 = Line((0, 0), (18 / 2, 0))\n l2 = PolarLine(l1 @ 1, 8, 60, length_mode=LengthMode.VERTICAL)\n l3 = Line(l2 @ 1, (0, 8))\n mirror(about=Plane.YZ)\n make_face()\n extrude(amount=115/2, both=True, mode=Mode.SUBTRACT)\n\nshow_object(p)\n\n\ngot_mass = p.part.volume*densa\nwant_mass = 797.15\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-ppp0102", + "source": "docs/assets/ttt/ttt-ppp0102.py", + "kind": "ttt", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\n\n# TTT Party Pack 01: PPP0102, mass(abs) = 43.09g\nwith BuildPart() as p:\n with BuildSketch(Plane.XZ) as sk1:\n Rectangle(49, 48 - 8, align=(Align.CENTER, Align.MIN))\n Rectangle(9, 48, align=(Align.CENTER, Align.MIN))\n with Locations((9 / 2, 40)):\n Ellipse(20, 8)\n split(bisect_by=Plane.YZ)\n revolve(axis=Axis.Z)\n\n with BuildSketch(Plane.YZ.offset(-15)) as xc1:\n with Locations((0, 40 / 2 - 17)):\n Ellipse(10 / 2, 4 / 2)\n with BuildLine(Plane.XZ) as l1:\n CenterArc((-15, 40 / 2), 17, 90, 180)\n sweep(path=l1)\n\n fillet(p.edges().filter_by(GeomType.CIRCLE, reverse=True).group_by(Axis.X)[0], 1)\n\n with BuildLine(mode=Mode.PRIVATE) as lc1:\n PolarLine(\n (42 / 2, 0), 37, 94, length_mode=LengthMode.VERTICAL\n ) # construction line\n\n pts = [\n (0, 0),\n (42 / 2, 0),\n ((lc1.line @ 1).X, (lc1.line @ 1).Y),\n (0, (lc1.line @ 1).Y),\n ]\n with BuildSketch(Plane.XZ) as sk2:\n Polygon(*pts, align=None)\n fillet(sk2.vertices().group_by(Axis.X)[1], 3)\n revolve(axis=Axis.Z, mode=Mode.SUBTRACT)\n\nshow(p)\n\n\ngot_mass = p.part.volume*densc\nwant_mass = 43.09\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-ppp0103", + "source": "docs/assets/ttt/ttt-ppp0103.py", + "kind": "ttt", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\n\nwith BuildPart() as ppp0103:\n with BuildSketch() as sk1:\n RectangleRounded(34 * 2, 95, 18)\n with Locations((0, -2)):\n RectangleRounded((34 - 16) * 2, 95 - 18 - 14, 7, mode=Mode.SUBTRACT)\n with Locations((-34 / 2, 0)):\n Rectangle(34, 95, 0, mode=Mode.SUBTRACT)\n extrude(amount=16)\n with BuildSketch(Plane.XZ.offset(-95 / 2)) as cyl1:\n with Locations((0, 16 / 2)):\n Circle(16 / 2)\n extrude(amount=18)\n with BuildSketch(Plane.XZ.offset(95 / 2 - 14)) as cyl2:\n with Locations((0, 16 / 2)):\n Circle(16 / 2)\n extrude(amount=23)\n with Locations(Plane.XZ.offset(95 / 2 + 9)):\n with Locations((0, 16 / 2)):\n CounterSinkHole(5.5 / 2, 11.2 / 2, None, 90)\n\nshow(ppp0103)\n\ngot_mass = ppp0103.part.volume*densb\nwant_mass = 96.13\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-ppp0104", + "source": "docs/assets/ttt/ttt-ppp0104.py", + "kind": "ttt", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\nd1, d2, d3 = 38, 26, 16\nh1, h2, h3, h4 = 20, 8, 7, 23\nw1, w2, w3 = 80, 10, 5\nf1, f2, f3 = 4, 10, 5\nsloth1, sloth2 = 18, 12\nslotw1, slotw2 = 17, 14\n\nwith BuildPart() as p:\n with BuildSketch() as s:\n Circle(d1 / 2)\n extrude(amount=h1)\n with BuildSketch(Plane.XY.offset(h1)) as s2:\n Circle(d2 / 2)\n extrude(amount=h2)\n with BuildSketch(Plane.YZ) as s3:\n Rectangle(d1 + 15, h3, align=(Align.CENTER, Align.MIN))\n extrude(amount=w1 - d1 / 2)\n # fillet workaround \\/\n ped = p.part.edges().group_by(Axis.Z)[2].filter_by(GeomType.CIRCLE)\n fillet(ped, f1)\n with BuildSketch(Plane.YZ) as s3a:\n Rectangle(d1 + 15, 15, align=(Align.CENTER, Align.MIN))\n Rectangle(d1, 15, mode=Mode.SUBTRACT, align=(Align.CENTER, Align.MIN))\n extrude(amount=w1 - d1 / 2, mode=Mode.SUBTRACT)\n # end fillet workaround /\\\n with BuildSketch() as s4:\n Circle(d3 / 2)\n extrude(amount=h1 + h2, mode=Mode.SUBTRACT)\n with BuildSketch() as s5:\n with Locations((w1 - d1 / 2 - w2 / 2, 0)):\n Rectangle(w2, d1)\n extrude(amount=-h4)\n fillet(p.part.edges().group_by(Axis.X)[-1].sort_by(Axis.Z)[-1], f2)\n fillet(p.part.edges().group_by(Axis.X)[-4].sort_by(Axis.Z)[-2], f3)\n pln = Plane.YZ.offset(w1 - d1 / 2)\n with BuildSketch(pln) as s6:\n with Locations((0, -h4)):\n SlotOverall(slotw1 * 2, sloth1, 90)\n extrude(amount=-w3, mode=Mode.SUBTRACT)\n with BuildSketch(pln) as s6b:\n with Locations((0, -h4)):\n SlotOverall(slotw2 * 2, sloth2, 90)\n extrude(amount=-w2, mode=Mode.SUBTRACT)\n\nshow(p)\n\n\ngot_mass = p.part.volume*densa\nwant_mass = 310\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-ppp0105", + "source": "docs/assets/ttt/ttt-ppp0105.py", + "kind": "ttt", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\nwith BuildPart() as p:\n with BuildSketch() as s:\n SlotOverall(45, 38)\n offset(amount=3)\n with BuildSketch(Plane.XY.offset(133 - 30)) as s2:\n SlotOverall(60, 4)\n offset(amount=3)\n loft()\n\n with BuildSketch() as s3:\n SlotOverall(45, 38)\n with BuildSketch(Plane.XY.offset(133 - 30)) as s4:\n SlotOverall(60, 4)\n loft(mode=Mode.SUBTRACT)\n\n extrude(p.part.faces().sort_by(Axis.Z)[0], amount=30)\n\nshow(p)\n\n\ngot_mass = p.part.volume*densc\nwant_mass = 57.08\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-ppp0106", + "source": "docs/assets/ttt/ttt-ppp0106.py", + "kind": "ttt", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\nr1, r2, r3, r4, r5 = 30 / 2, 13 / 2, 12 / 2, 10, 6 # radii used\nx1 = 44 # lengths used\ny1, y2, y3, y4, y_tot = 36, 36 - 22 / 2, 22 / 2, 42, 69 # widths used\n\nwith BuildSketch(Location((0, -r1, y3))) as sk_body:\n with BuildLine() as l:\n c1 = Line((r1, 0), (r1, y_tot), mode=Mode.PRIVATE) # construction line\n m1 = Line((0, y_tot), (x1 / 2, y_tot))\n m2 = JernArc(m1 @ 1, m1 % 1, r4, -90 - 45)\n m3 = IntersectingLine(m2 @ 1, m2 % 1, c1)\n m4 = Line(m3 @ 1, (r1, r1))\n m5 = JernArc(m4 @ 1, m4 % 1, r1, -90)\n mirror(about=Plane.YZ)\n make_face()\n fillet(sk_body.vertices().group_by(Axis.Y)[1], 12)\n with Locations((x1 / 2, y_tot - 10), (-x1 / 2, y_tot - 10)):\n Circle(r2, mode=Mode.SUBTRACT)\n # Keyway\n with Locations((0, r1)):\n Circle(r3, mode=Mode.SUBTRACT)\n Rectangle(4, 3 + 6, align=(Align.CENTER, Align.MIN), mode=Mode.SUBTRACT)\n\nwith BuildPart() as p:\n Box(200, 200, 22) # Oversized plate\n # Cylinder underneath\n Cylinder(r1, y2, align=(Align.CENTER, Align.CENTER, Align.MAX))\n fillet(p.edges(Select.NEW), r5) # Weld together\n extrude(sk_body.sketch, amount=-y1, mode=Mode.INTERSECT) # Cut to shape\n # Remove slot\n with Locations((0, y_tot - r1 - y4, 0)):\n Box(\n y_tot,\n y_tot,\n 10,\n align=(Align.CENTER, Align.MIN, Align.CENTER),\n mode=Mode.SUBTRACT,\n )\n\nshow(p)\n\n\ngot_mass = p.part.volume*densa\nwant_mass = 328.02\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-ppp0107", + "source": "docs/assets/ttt/ttt-ppp0107.py", + "kind": "ttt", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\nwith BuildPart() as p:\n with BuildSketch() as s:\n Circle(130 / 2)\n extrude(amount=8)\n with BuildSketch(Plane.XY.offset(8)) as s2:\n Circle(84 / 2)\n extrude(amount=25 - 8)\n with BuildSketch(Plane.XY.offset(25)) as s3:\n Circle(35 / 2)\n extrude(amount=52 - 25)\n with BuildSketch() as s4:\n Circle(73 / 2)\n extrude(amount=18, mode=Mode.SUBTRACT)\n pln2 = p.part.faces().sort_by(Axis.Z)[5]\n with BuildSketch(Plane.XY.offset(52)) as s5:\n Circle(20 / 2)\n extrude(amount=-52, mode=Mode.SUBTRACT)\n fillet(\n p.part.edges()\n .filter_by(GeomType.CIRCLE)\n .sort_by(Axis.Z)[2:-2]\n .sort_by(SortBy.RADIUS)[1:],\n 3,\n )\n pln = Plane(pln2)\n pln.origin = pln.origin + Vector(20 / 2, 0, 0)\n pln = pln.rotated((0, 45, 0))\n pln = pln.offset(-25 + 3 + 0.10)\n with BuildSketch(pln) as s6:\n Rectangle((73 - 35) / 2 * 1.414 + 5, 3)\n zz = extrude(amount=15, taper=-20 / 2, mode=Mode.PRIVATE)\n zz2 = split(zz, bisect_by=Plane.XY.offset(25), mode=Mode.PRIVATE)\n zz3 = split(zz2, bisect_by=Plane.YZ.offset(35 / 2 - 1), mode=Mode.PRIVATE)\n with PolarLocations(0, 3):\n add(zz3)\n with Locations(Plane.XY.offset(8)):\n with PolarLocations(107.95 / 2, 6):\n CounterBoreHole(6 / 2, 13 / 2, 4)\n\nshow(p)\n\n\ngot_mass = p.part.volume*densb\nwant_mass = 372.99\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-ppp0108", + "source": "docs/assets/ttt/ttt-ppp0108.py", + "kind": "ttt", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\nwith BuildPart() as p:\n with BuildSketch() as s1:\n Rectangle(188 / 2 - 33, 162, align=(Align.MIN, Align.CENTER))\n with Locations((188 / 2 - 33, 0)):\n SlotOverall(190, 33 * 2, rotation=90)\n mirror(about=Plane.YZ)\n with GridLocations(188 - 2 * 33, 190 - 2 * 33, 2, 2):\n Circle(29 / 2, mode=Mode.SUBTRACT)\n Circle(84 / 2, mode=Mode.SUBTRACT)\n extrude(amount=16)\n\n with BuildPart() as p2:\n with BuildSketch(Plane.XZ) as s2:\n with BuildLine() as l1:\n l1 = Polyline(\n (222 / 2 + 14 - 40 - 40, 0),\n (222 / 2 + 14 - 40, -35 + 16),\n (222 / 2 + 14, -35 + 16),\n (222 / 2 + 14, -35 + 16 + 30),\n (222 / 2 + 14 - 40 - 40, -35 + 16 + 30),\n close=True,\n )\n make_face()\n with Locations((222 / 2, -35 + 16 + 14)):\n Circle(11 / 2, mode=Mode.SUBTRACT)\n extrude(amount=20 / 2, both=True)\n with BuildSketch() as s3:\n with Locations(l1 @ 0):\n Rectangle(40 + 40, 8, align=(Align.MIN, Align.CENTER))\n with Locations((40, 0)):\n Rectangle(40, 20, align=(Align.MIN, Align.CENTER))\n extrude(amount=30, both=True, mode=Mode.INTERSECT)\n mirror(about=Plane.YZ)\n\nshow(p)\n\n\ngot_mass = p.part.volume*densa\nwant_mass = 3387.06\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-ppp0109", + "source": "docs/assets/ttt/ttt-ppp0109.py", + "kind": "ttt", + "code": "from math import sqrt\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\nwith BuildPart() as ppp109:\n with BuildSketch() as one:\n Rectangle(69, 75, align=(Align.MAX, Align.CENTER))\n fillet(one.vertices().group_by(Axis.X)[0], 17)\n extrude(amount=13)\n centers = [\n arc.arc_center\n for arc in ppp109.edges().filter_by(GeomType.CIRCLE).group_by(Axis.Z)[-1]\n ]\n with Locations(*centers):\n CounterBoreHole(radius=8 / 2, counter_bore_radius=15 / 2, counter_bore_depth=4)\n\n with BuildSketch(Plane.YZ) as two:\n with Locations((0, 45)):\n Circle(15)\n with BuildLine() as bl:\n c = Line((75 / 2, 0), (75 / 2, 60), mode=Mode.PRIVATE)\n u = two.edge().find_tangent(75 / 2 + 90)[0] # where is the slope 75/2?\n l1 = IntersectingLine(\n two.edge().position_at(u), -two.edge().tangent_at(u), other=c\n )\n Line(l1 @ 0, (0, 45))\n Polyline((0, 0), c @ 0, l1 @ 1)\n mirror(about=Plane.YZ)\n make_face()\n with Locations((0, 45)):\n Circle(12 / 2, mode=Mode.SUBTRACT)\n extrude(amount=-13)\n\n with BuildSketch(Plane((0, 0, 0), x_dir=(1, 0, 0), z_dir=(1, 0, 1))) as three:\n Rectangle(45 * 2 / sqrt(2) - 37.5, 75, align=(Align.MIN, Align.CENTER))\n with Locations(three.edges().sort_by(Axis.X)[-1].center()):\n Circle(37.5)\n Circle(33 / 2, mode=Mode.SUBTRACT)\n split(bisect_by=Plane.YZ)\n extrude(amount=6)\n f = ppp109.faces().filter_by(Axis((0, 0, 0), (-1, 0, 1)))[0]\n extrude(f, until=Until.NEXT)\n fillet(ppp109.edges().filter_by(Axis.Y).sort_by(Axis.Z)[2], 16)\n # extrude(f, amount=10)\n # fillet(ppp109.edges(Select.NEW), 16)\n\n\nshow(ppp109)\n\ngot_mass = ppp109.part.volume * densb\nwant_mass = 307.23\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f\"{got_mass=}, {want_mass=}, {delta=}, {tolerance=}\"\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-ppp0110", + "source": "docs/assets/ttt/ttt-ppp0110.py", + "kind": "ttt", + "code": "from math import sqrt, asin, pi\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\n# The smaller cross-section is defined as having R40, height 46,\n# and base width 84, so clearly it's not entirely a half-circle or\n# similar; the base's extreme points need to connect via tangents\n# to the R40 arc centered 6mm above the baseline.\n#\n# Compute the angle of the tangent line (working with the\n# left/negativeX side, given symmetry) by observing the tangent\n# point (T), the circle's center (O), and the baseline's edge (P)\n# form a right triangle, so:\n\nOT=40\nOP=sqrt((-84/2)**2+(-6)**2)\nTP=sqrt(OP**2-40**2)\nOPT_degrees = asin(OT/OP) * 180/pi\n# Correct for the fact that OP isn't horizontal.\nOP_to_X_axis_degrees = asin(6/OP) * 180/pi\nleft_tangent_degrees = OPT_degrees + OP_to_X_axis_degrees\nleft_tangent_length = TP\nwith BuildPart() as outer:\n with BuildSketch(Plane.XZ) as sk:\n with BuildLine():\n l1 = PolarLine(start=(-84/2, 0), length=left_tangent_length, angle=left_tangent_degrees)\n l2 = TangentArc(l1@1, (0, 46), tangent=l1%1)\n l3 = offset(amount=-8, side=Side.RIGHT, closed=False, mode=Mode.ADD)\n l4 = Line(l1@0, l3@1)\n l5 = Line(l3@0, l2@1)\n make_face()\n\n with BuildLine():\n l6 = Line(l2 @ 1, (0, 46 - 16))\n l7 = IntersectingLine(start=l6 @ 1, direction=(-1, 0), other=l3)\n l8 = TangentArc(l7 @ 1, l2 @ 1, tangent=(-1, 0), tangent_from_first=False)\n\n make_face()\n \n revolve(axis=Axis.Z)\nsk = sk.sketch & Plane.XZ*Rectangle(1000, 1000, align=[Align.CENTER, Align.MIN])\npositive_Z = Box(100, 100, 100, align=[Align.CENTER, Align.MIN, Align.MIN])\np = outer.part & positive_Z\ncross_section = sk + mirror(sk, about=Plane.YZ)\np += extrude(cross_section, amount=50)\np += mirror(p, about=Plane.XZ.offset(50))\np += fillet(p.edges().filter_by(GeomType.LINE).filter_by(Axis.Y).group_by(Axis.Z)[-1], radius=8)\nppp0110 = p\n\ngot_mass = ppp0110.volume*densc\nwant_mass = 211.30\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.1f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n\nshow(ppp0110)\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "docs-rst/OpenSCAD/b01", + "source": "docs/OpenSCAD.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\n# Builder mode\nwith BuildPart() as angle_iron:\n with BuildSketch() as profile:\n Rectangle(3 * CM, 4 * MM, align=Align.MIN)\n Rectangle(4 * MM, 3 * CM, align=Align.MIN)\n extrude(amount=10 * CM)\n fillet(angle_iron.edges().filter_by(lambda e: e.is_interior), 5 * MM)\n" + }, + { + "id": "docs-rst/OpenSCAD/b02", + "source": "docs/OpenSCAD.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\n# Algebra mode\nprofile = Rectangle(3 * CM, 4 * MM, align=Align.MIN)\nprofile += Rectangle(4 * MM, 3 * CM, align=Align.MIN)\nangle_iron = extrude(profile, 10 * CM)\nangle_iron = fillet(angle_iron.edges().filter_by(lambda e: e.is_interior), 5 * MM)\n" + }, + { + "id": "docs-rst/OpenSCAD/all", + "source": "docs/OpenSCAD.rst (all 2 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\nfrom math import *\n\n# Builder mode\nwith BuildPart() as angle_iron:\n with BuildSketch() as profile:\n Rectangle(3 * CM, 4 * MM, align=Align.MIN)\n Rectangle(4 * MM, 3 * CM, align=Align.MIN)\n extrude(amount=10 * CM)\n fillet(angle_iron.edges().filter_by(lambda e: e.is_interior), 5 * MM)\n\n# Algebra mode\nprofile = Rectangle(3 * CM, 4 * MM, align=Align.MIN)\nprofile += Rectangle(4 * MM, 3 * CM, align=Align.MIN)\nangle_iron = extrude(profile, 10 * CM)\nangle_iron = fillet(angle_iron.edges().filter_by(lambda e: e.is_interior), 5 * MM)\n" + }, + { + "id": "docs-rst/advantages/b01", + "source": "docs/advantages.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\n# CadQuery Fluent API\npillow_block = (cq.Workplane(\"XY\")\n .box(height, width, thickness)\n .edges(\"|Z\")\n .fillet(fillet)\n .faces(\">Z\")\n .workplane()\n ...\n)\n" + }, + { + "id": "docs-rst/advantages/b02", + "source": "docs/advantages.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\n# build123d API\nwith BuildPart() as pillow_block:\n with BuildSketch() as plan:\n Rectangle(width, height)\n fillet(plan.vertices(), radius=fillet)\n extrude(thickness)\n ...\n" + }, + { + "id": "docs-rst/advantages/b03", + "source": "docs/advantages.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildSketch() as plan:\n r = Rectangle(width, height)\n print(r.area)\n ...\n" + }, + { + "id": "docs-rst/advantages/b04", + "source": "docs/advantages.rst code-block #4", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildLine() as outline:\n ...\n l5 = Polyline(...)\n l6 = Polyline(...)\n Spline(l5 @ 1, l6 @ 0, tangents=(l5 % 1, l6 % 0))\n" + }, + { + "id": "docs-rst/advantages/b05", + "source": "docs/advantages.rst code-block #5", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\ntop = rail.faces().filter_by(Axis.Z)[-1]\n...\noutside_vertices = filter(\n lambda v: (v.Y == 0.0 or v.Y == height) and -width / 2 < v.X < width / 2,\n din.vertices(),\n)\n" + }, + { + "id": "docs-rst/advantages/all", + "source": "docs/advantages.rst (all 5 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\nfrom math import *\n\n# CadQuery Fluent API\npillow_block = (cq.Workplane(\"XY\")\n .box(height, width, thickness)\n .edges(\"|Z\")\n .fillet(fillet)\n .faces(\">Z\")\n .workplane()\n ...\n)\n\n# build123d API\nwith BuildPart() as pillow_block:\n with BuildSketch() as plan:\n Rectangle(width, height)\n fillet(plan.vertices(), radius=fillet)\n extrude(thickness)\n ...\n\nwith BuildSketch() as plan:\n r = Rectangle(width, height)\n print(r.area)\n ...\n\nwith BuildLine() as outline:\n ...\n l5 = Polyline(...)\n l6 = Polyline(...)\n Spline(l5 @ 1, l6 @ 0, tangents=(l5 % 1, l6 % 0))\n\ntop = rail.faces().filter_by(Axis.Z)[-1]\n...\noutside_vertices = filter(\n lambda v: (v.Y == 0.0 or v.Y == height) and -width / 2 < v.X < width / 2,\n din.vertices(),\n)\n" + }, + { + "id": "docs-rst/algebra_performance/b01", + "source": "docs/algebra_performance.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\ndiam = 80\nholes = Sketch()\nr = Rectangle(2, 2)\nfor loc in GridLocations(4, 4, 20, 20):\n if loc.position.X**2 + loc.position.Y**2 < (diam / 2 - 1.8) ** 2:\n holes += loc * r\n\nc = Circle(diam / 2) - holes\n" + }, + { + "id": "docs-rst/algebra_performance/b02", + "source": "docs/algebra_performance.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nr = Rectangle(2, 2)\nholes = [\n loc * r\n for loc in GridLocations(4, 4, 20, 20).locations\n if loc.position.X**2 + loc.position.Y**2 < (diam / 2 - 1.8) ** 2\n]\n\nc = Circle(diam / 2) - holes\n" + }, + { + "id": "docs-rst/algebra_performance/b03", + "source": "docs/algebra_performance.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\npolygons = Sketch() + [\n loc * RegularPolygon(radius=5, side_count=5)\n for loc in GridLocations(40, 30, 2, 2)\n]\n" + }, + { + "id": "docs-rst/algebra_performance/all", + "source": "docs/algebra_performance.rst (all 3 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\nfrom math import *\n\ndiam = 80\nholes = Sketch()\nr = Rectangle(2, 2)\nfor loc in GridLocations(4, 4, 20, 20):\n if loc.position.X**2 + loc.position.Y**2 < (diam / 2 - 1.8) ** 2:\n holes += loc * r\n\nc = Circle(diam / 2) - holes\n\nr = Rectangle(2, 2)\nholes = [\n loc * r\n for loc in GridLocations(4, 4, 20, 20).locations\n if loc.position.X**2 + loc.position.Y**2 < (diam / 2 - 1.8) ** 2\n]\n\nc = Circle(diam / 2) - holes\n\npolygons = Sketch() + [\n loc * RegularPolygon(radius=5, side_count=5)\n for loc in GridLocations(40, 30, 2, 2)\n]\n" + }, + { + "id": "docs-rst/build_line/b01", + "source": "docs/build_line.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildLine(Plane.YZ) as path:\n Line((0, 0), (1, 0))\n\nshow_object(path.line_local) # line from (0, 0, 0) to (1, 0, 0)\nshow_object(path.line) # same line published to Plane.YZ\n" + }, + { + "id": "docs-rst/build_sketch/b01", + "source": "docs/build_sketch.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nshow_object(display.sketch_local, name=\"sketch on Plane.XY\")\n" + }, + { + "id": "docs-rst/build_sketch/b02", + "source": "docs/build_sketch.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nshow_object(display.sketch, name=\"sketch on target placement(s)\")\n" + }, + { + "id": "docs-rst/build_sketch/b03", + "source": "docs/build_sketch.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith GridLocations(20, 20, 2, 2):\n with BuildSketch() as repeated:\n Rectangle(8, 4)\n\nshow_object(repeated.sketch_local, name=\"one local rectangle\")\nshow_object(repeated.sketch, name=\"four placed rectangles\")\n" + }, + { + "id": "docs-rst/build_sketch/b04", + "source": "docs/build_sketch.rst code-block #4", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nreoriented_face = plane.to_local_coords(face)\n" + }, + { + "id": "docs-rst/build_sketch/all", + "source": "docs/build_sketch.rst (all 4 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\nfrom math import *\n\nshow_object(display.sketch_local, name=\"sketch on Plane.XY\")\n\nshow_object(display.sketch, name=\"sketch on target placement(s)\")\n\nwith GridLocations(20, 20, 2, 2):\n with BuildSketch() as repeated:\n Rectangle(8, 4)\n\nshow_object(repeated.sketch_local, name=\"one local rectangle\")\nshow_object(repeated.sketch, name=\"four placed rectangles\")\n\nreoriented_face = plane.to_local_coords(face)\n" + }, + { + "id": "docs-rst/debugging_logging/b01", + "source": "docs/debugging_logging.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nlogging.getLogger(\"build123d\").addHandler(logging.NullHandler())\nlogger = logging.getLogger(\"build123d\")\n" + }, + { + "id": "docs-rst/debugging_logging/b02", + "source": "docs/debugging_logging.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nlogging.basicConfig(\n filename=\"myapp.log\",\n level=logging.INFO,\n format=\"%(name)s-%(levelname)s %(asctime)s - [%(filename)s:%(lineno)s - \\\n %(funcName)20s() ] - %(message)s\",\n)\n" + }, + { + "id": "docs-rst/debugging_logging/b03", + "source": "docs/debugging_logging.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nlogger.info(\"Exiting %s\", type(self).__name__)\n" + }, + { + "id": "docs-rst/debugging_logging/b04", + "source": "docs/debugging_logging.rst code-block #4", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nplane = Plane.XY.offset(1)\nprint(f\"{plane=}\")\n" + }, + { + "id": "docs-rst/debugging_logging/all", + "source": "docs/debugging_logging.rst (all 4 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\nfrom math import *\n\nlogging.getLogger(\"build123d\").addHandler(logging.NullHandler())\nlogger = logging.getLogger(\"build123d\")\n\nlogging.basicConfig(\n filename=\"myapp.log\",\n level=logging.INFO,\n format=\"%(name)s-%(levelname)s %(asctime)s - [%(filename)s:%(lineno)s - \\\n %(funcName)20s() ] - %(message)s\",\n)\n\nlogger.info(\"Exiting %s\", type(self).__name__)\n\nplane = Plane.XY.offset(1)\nprint(f\"{plane=}\")\n" + }, + { + "id": "docs-rst/import_export/b01", + "source": "docs/import_export.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as box_builder:\n Box(1, 1, 1)\nexport_step(box_builder.part, \"box.step\")\n" + }, + { + "id": "docs-rst/import_export/b02", + "source": "docs/import_export.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nbox = Box(20, 10, 5)\nexport_obj(box, \"box.obj\", atlas_gutter=0.002)\n" + }, + { + "id": "docs-rst/import_export/b03", + "source": "docs/import_export.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nview_port_origin=(-100, -50, 30)\nvisible, hidden = part.project_to_viewport(view_port_origin)\nmax_dimension = max(*Compound(children=visible + hidden).bounding_box().size)\nexporter = ExportSVG(scale=100 / max_dimension)\nexporter.add_layer(\"Visible\")\nexporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)\nexporter.add_shape(visible, layer=\"Visible\")\nexporter.add_shape(hidden, layer=\"Hidden\")\nexporter.write(\"part_projection.svg\")\n" + }, + { + "id": "docs-rst/import_export/b04", + "source": "docs/import_export.rst code-block #4", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\n# Create the shapes and assign attributes\nblue_shape = Solid.make_cone(20, 0, 50)\nblue_shape.color = Color(\"blue\")\nblue_shape.label = \"blue\"\nblue_uuid = uuid.uuid1()\nred_shape = Solid.make_cylinder(5, 50).move(Location((0, -30, 0)))\nred_shape.color = Color(\"red\")\nred_shape.label = \"red\"\n\n# Create a Mesher instance as an exporter, add shapes and write\nexporter = Mesher()\nexporter.add_shape(blue_shape, part_number=\"blue-1234-5\", uuid_value=blue_uuid)\nexporter.add_shape(red_shape)\nexporter.add_meta_data(\n name_space=\"custom\",\n name=\"test_meta_data\",\n value=\"hello world\",\n metadata_type=\"str\",\n must_preserve=False,\n)\nexporter.add_code_to_metadata()\nexporter.write(\"example.3mf\")\nexporter.write(\"example.stl\")\n" + }, + { + "id": "docs-rst/import_export/b05", + "source": "docs/import_export.rst code-block #5", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nimporter = Mesher()\ncone, cyl = importer.read(\"example.3mf\")\nprint(\n f\"{importer.mesh_count=}, {importer.vertex_counts=}, {importer.triangle_counts=}\"\n)\nprint(f\"Imported model unit: {importer.model_unit}\")\nprint(f\"{cone.label=}\")\nprint(f\"{cone.color.to_tuple()=}\")\nprint(f\"{cyl.label=}\")\nprint(f\"{cyl.color.to_tuple()=}\")\n" + }, + { + "id": "docs-rst/import_export/all", + "source": "docs/import_export.rst (all 5 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as box_builder:\n Box(1, 1, 1)\nexport_step(box_builder.part, \"box.step\")\n\nbox = Box(20, 10, 5)\nexport_obj(box, \"box.obj\", atlas_gutter=0.002)\n\nview_port_origin=(-100, -50, 30)\nvisible, hidden = part.project_to_viewport(view_port_origin)\nmax_dimension = max(*Compound(children=visible + hidden).bounding_box().size)\nexporter = ExportSVG(scale=100 / max_dimension)\nexporter.add_layer(\"Visible\")\nexporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)\nexporter.add_shape(visible, layer=\"Visible\")\nexporter.add_shape(hidden, layer=\"Hidden\")\nexporter.write(\"part_projection.svg\")\n\n# Create the shapes and assign attributes\nblue_shape = Solid.make_cone(20, 0, 50)\nblue_shape.color = Color(\"blue\")\nblue_shape.label = \"blue\"\nblue_uuid = uuid.uuid1()\nred_shape = Solid.make_cylinder(5, 50).move(Location((0, -30, 0)))\nred_shape.color = Color(\"red\")\nred_shape.label = \"red\"\n\n# Create a Mesher instance as an exporter, add shapes and write\nexporter = Mesher()\nexporter.add_shape(blue_shape, part_number=\"blue-1234-5\", uuid_value=blue_uuid)\nexporter.add_shape(red_shape)\nexporter.add_meta_data(\n name_space=\"custom\",\n name=\"test_meta_data\",\n value=\"hello world\",\n metadata_type=\"str\",\n must_preserve=False,\n)\nexporter.add_code_to_metadata()\nexporter.write(\"example.3mf\")\nexporter.write(\"example.stl\")\n\nimporter = Mesher()\ncone, cyl = importer.read(\"example.3mf\")\nprint(\n f\"{importer.mesh_count=}, {importer.vertex_counts=}, {importer.triangle_counts=}\"\n)\nprint(f\"Imported model unit: {importer.model_unit}\")\nprint(f\"{cone.label=}\")\nprint(f\"{cone.color.to_tuple()=}\")\nprint(f\"{cyl.label=}\")\nprint(f\"{cyl.color.to_tuple()=}\")\n" + }, + { + "id": "docs-rst/joints/b01", + "source": "docs/joints.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nRigidJoint(label=\"outlet\", to_part=pipe, joint_location=path.location_at(1))\n" + }, + { + "id": "docs-rst/joints/b02", + "source": "docs/joints.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\npipe.joints[\"outlet\"].connect_to(flange_outlet.joints[\"pipe\"])\n" + }, + { + "id": "docs-rst/joints/b03", + "source": "docs/joints.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nhinge_outer.joints[\"hole2\"].connect_to(m6_joint, position=5 * MM, angle=30)\n" + }, + { + "id": "docs-rst/joints/all", + "source": "docs/joints.rst (all 3 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\nfrom math import *\n\nRigidJoint(label=\"outlet\", to_part=pipe, joint_location=path.location_at(1))\n\npipe.joints[\"outlet\"].connect_to(flange_outlet.joints[\"pipe\"])\n\nhinge_outer.joints[\"hole2\"].connect_to(m6_joint, position=5 * MM, angle=30)\n" + }, + { + "id": "docs-rst/key_concepts_algebra/b01", + "source": "docs/key_concepts_algebra.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nb = Box(1, 2, 3)\nc = Cylinder(0.2, 5)\n" + }, + { + "id": "docs-rst/key_concepts_algebra/b02", + "source": "docs/key_concepts_algebra.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nr = Box(1, 2, 3) + Cylinder(0.2, 5)\n" + }, + { + "id": "docs-rst/key_concepts_algebra/b03", + "source": "docs/key_concepts_algebra.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nr = Box(1, 2, 3) - Cylinder(0.2, 5)\n" + }, + { + "id": "docs-rst/key_concepts_algebra/b04", + "source": "docs/key_concepts_algebra.rst code-block #4", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nr = Box(1, 2, 3) & Cylinder(0.2, 5)\n" + }, + { + "id": "docs-rst/key_concepts_algebra/b05", + "source": "docs/key_concepts_algebra.rst code-block #5", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nplane * alg_compound\nlocation * alg_compound\n" + }, + { + "id": "docs-rst/key_concepts_algebra/b06", + "source": "docs/key_concepts_algebra.rst code-block #6", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nplane * location * alg_compound\n" + }, + { + "id": "docs-rst/key_concepts_algebra/b07", + "source": "docs/key_concepts_algebra.rst code-block #7", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nPlane.XY * Box(1, 2, 3)\n\nBox(1, 2, 3)\n" + }, + { + "id": "docs-rst/key_concepts_algebra/b08", + "source": "docs/key_concepts_algebra.rst code-block #8", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nPlane.XY * Pos(0, 1, 0) * Box(1, 2, 3)\n\nPos(0, 1, 0) * Box(1, 2, 3) \n\nPos(Y=1) * Box(1, 2, 3)\n" + }, + { + "id": "docs-rst/key_concepts_algebra/b09", + "source": "docs/key_concepts_algebra.rst code-block #9", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nPlane.XZ * Box(1, 2, 3)\n" + }, + { + "id": "docs-rst/key_concepts_algebra/b10", + "source": "docs/key_concepts_algebra.rst code-block #10", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nPlane.XZ * Pos(1, 2, 3) * Box(1, 2, 3)\n" + }, + { + "id": "docs-rst/key_concepts_algebra/b11", + "source": "docs/key_concepts_algebra.rst code-block #11", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nPlane.XZ * Pos(1, 2, 3) * Rot(0, 100, 45) * Box(1, 2, 3)\n\nLocation((1, 2, 3), (0, 100, 45)) * Box(1, 2, 3)\n" + }, + { + "id": "docs-rst/key_concepts_algebra/b12", + "source": "docs/key_concepts_algebra.rst code-block #12", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nPlane.XZ * Rot(0, 100, 45) * Pos(0,1,2) * Box(1, 2, 3)\n" + }, + { + "id": "docs-rst/key_concepts_algebra/b13", + "source": "docs/key_concepts_algebra.rst code-block #13", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nb = Plane.XZ * Rot(X=30) * Box(1, 2, 3) + Plane.YZ * Pos(X=-1) * Cylinder(0.2, 5)\n" + }, + { + "id": "docs-rst/key_concepts_algebra/all", + "source": "docs/key_concepts_algebra.rst (all 13 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\nfrom math import *\n\nb = Box(1, 2, 3)\nc = Cylinder(0.2, 5)\n\nr = Box(1, 2, 3) + Cylinder(0.2, 5)\n\nr = Box(1, 2, 3) - Cylinder(0.2, 5)\n\nr = Box(1, 2, 3) & Cylinder(0.2, 5)\n\nplane * alg_compound\nlocation * alg_compound\n\nplane * location * alg_compound\n\nPlane.XY * Box(1, 2, 3)\n\nBox(1, 2, 3)\n\nPlane.XY * Pos(0, 1, 0) * Box(1, 2, 3)\n\nPos(0, 1, 0) * Box(1, 2, 3) \n\nPos(Y=1) * Box(1, 2, 3)\n\nPlane.XZ * Box(1, 2, 3)\n\nPlane.XZ * Pos(1, 2, 3) * Box(1, 2, 3)\n\nPlane.XZ * Pos(1, 2, 3) * Rot(0, 100, 45) * Box(1, 2, 3)\n\nLocation((1, 2, 3), (0, 100, 45)) * Box(1, 2, 3)\n\nPlane.XZ * Rot(0, 100, 45) * Pos(0,1,2) * Box(1, 2, 3)\n\nb = Plane.XZ * Rot(X=30) * Box(1, 2, 3) + Plane.YZ * Pos(X=-1) * Cylinder(0.2, 5)\n" + }, + { + "id": "docs-rst/key_concepts_builder/b01", + "source": "docs/key_concepts_builder.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\n\n# Using BuildPart to create a 3D model\nwith BuildPart() as example_part:\n with BuildSketch() as base_sketch:\n Rectangle(20, 20)\n extrude(amount=10) # Create a base block\n with BuildSketch(Plane(example_part.faces().sort_by(Axis.Z).last)) as cut_sketch:\n Circle(5)\n extrude(amount=-5, mode=Mode.SUBTRACT) # Subtract a cylinder\n\n# Access the final part\nresult_part = example_part.part\n" + }, + { + "id": "docs-rst/key_concepts_builder/b02", + "source": "docs/key_concepts_builder.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as invalid:\n Cylinder(1, 2).moved(Location((1, 2, 3)))\n" + }, + { + "id": "docs-rst/key_concepts_builder/b03", + "source": "docs/key_concepts_builder.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as valid:\n with Locations((1, 2, 3)):\n Cylinder(1, 2)\n" + }, + { + "id": "docs-rst/key_concepts_builder/b04", + "source": "docs/key_concepts_builder.rst code-block #4", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith open(\"test.txt\", \"w\") as f:\n f.write(\"text\").to_bytes(1, \"big\")\n" + }, + { + "id": "docs-rst/key_concepts_builder/b05", + "source": "docs/key_concepts_builder.rst code-block #5", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as my_part:\n ...\n\nshow(my_part.part) # placed output\nshow(my_part.part_local) # local construction result\n" + }, + { + "id": "docs-rst/key_concepts_builder/b06", + "source": "docs/key_concepts_builder.rst code-block #6", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildSketch() as my_sketch:\n ...\n\nshow(my_sketch.sketch) # placed output\nshow(my_sketch.sketch_local) # local construction result\n" + }, + { + "id": "docs-rst/key_concepts_builder/b07", + "source": "docs/key_concepts_builder.rst code-block #7", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildLine() as my_line:\n ...\n\nshow(my_line.line) # placed output\nshow(my_line.line_local) # local construction result\n" + }, + { + "id": "docs-rst/key_concepts_builder/b08", + "source": "docs/key_concepts_builder.rst code-block #8", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as part_builder:\n Box(part_builder, 10,10,10)\n" + }, + { + "id": "docs-rst/key_concepts_builder/b09", + "source": "docs/key_concepts_builder.rst code-block #9", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as part_builder:\n Box(10,10,10)\n with BuildSketch() as sketch_builder:\n Circle(2)\n" + }, + { + "id": "docs-rst/key_concepts_builder/b10", + "source": "docs/key_concepts_builder.rst code-block #10", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildSketch(Plane.XZ) as profile:\n Circle(5)\n\nshow(profile.sketch_local) # circle on local Plane.XY\nshow(profile.sketch) # circle placed on Plane.XZ\n" + }, + { + "id": "docs-rst/key_concepts_builder/b11", + "source": "docs/key_concepts_builder.rst code-block #11", + "kind": "docs-rst", + "code": "import build123d as bd\n\nwith bd.BuildPart() as bp:\n bd.Box(3, 3, 3)\n with bd.BuildSketch(*bp.faces()):\n bd.Rectangle(1, 2, rotation=45)\n bd.extrude(amount=0.1)\n" + }, + { + "id": "docs-rst/key_concepts_builder/b12", + "source": "docs/key_concepts_builder.rst code-block #12", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart():\n with Locations((0,10),(0,-10)):\n Box(1,1,1)\n with GridLocations(x_spacing=5, y_spacing=5, x_count=2, y_count=2):\n Sphere(1)\n Cylinder(1,1)\n" + }, + { + "id": "docs-rst/key_concepts_builder/b13", + "source": "docs/key_concepts_builder.rst code-block #13", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as model:\n with Locations((-20, 0), (20, 0)):\n with BuildSketch() as holes:\n Circle(3)\n extrude(amount=5)\n" + }, + { + "id": "docs-rst/key_concepts_builder/b14", + "source": "docs/key_concepts_builder.rst code-block #14", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith Locations((-10, 0), (10, 0)):\n with BuildPart() as placed_parts:\n Box(5, 5, 5)\n" + }, + { + "id": "docs-rst/key_concepts_builder/b15", + "source": "docs/key_concepts_builder.rst code-block #15", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith Locations(Plane.XY, Plane.XZ):\n locs = GridLocations(1, 1, 2, 2)\n for l in locs:\n print(l)\n" + }, + { + "id": "docs-rst/key_concepts_builder/b16", + "source": "docs/key_concepts_builder.rst code-block #16", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\ndef fillet(\n objects: Edge | Vertex | Iterable[Edge | Vertex],\n radius: float,\n):\n" + }, + { + "id": "docs-rst/key_concepts_builder/b17", + "source": "docs/key_concepts_builder.rst code-block #17", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as pipes:\n Box(10, 10, 10, rotation=(10, 20, 30))\n ...\n fillet(pipes.edges(Select.LAST), radius=0.2)\n" + }, + { + "id": "docs-rst/key_concepts_builder/b18", + "source": "docs/key_concepts_builder.rst code-block #18", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nclass Mode(Enum):\n ADD = auto()\n SUBTRACT = auto()\n INTERSECT = auto()\n REPLACE = auto()\n PRIVATE = auto()\n" + }, + { + "id": "docs-rst/key_concepts_builder/b19", + "source": "docs/key_concepts_builder.rst code-block #19", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as pipes:\n Box(10, 10, 10, rotation=(10, 20, 30))\n" + }, + { + "id": "docs-rst/key_concepts_builder/b20", + "source": "docs/key_concepts_builder.rst code-block #20", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as pipes:\n with Locations((-10, -10, -10), (10, 10, 10)):\n Box(10, 10, 10, rotation=(10, 20, 30))\n" + }, + { + "id": "docs-rst/key_concepts_builder/b21", + "source": "docs/key_concepts_builder.rst code-block #21", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nheight, width, thickness, f_rad = 60, 80, 20, 10\n\nwith BuildPart() as pillow_block:\n with BuildSketch() as plan:\n Rectangle(width, height)\n fillet(plan.vertices(), radius=f_rad)\n extrude(amount=thickness)\n" + }, + { + "id": "docs-rst/key_concepts_builder/all", + "source": "docs/key_concepts_builder.rst (all 21 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\n\n# Using BuildPart to create a 3D model\nwith BuildPart() as example_part:\n with BuildSketch() as base_sketch:\n Rectangle(20, 20)\n extrude(amount=10) # Create a base block\n with BuildSketch(Plane(example_part.faces().sort_by(Axis.Z).last)) as cut_sketch:\n Circle(5)\n extrude(amount=-5, mode=Mode.SUBTRACT) # Subtract a cylinder\n\n# Access the final part\nresult_part = example_part.part\n\nwith BuildPart() as invalid:\n Cylinder(1, 2).moved(Location((1, 2, 3)))\n\nwith BuildPart() as valid:\n with Locations((1, 2, 3)):\n Cylinder(1, 2)\n\nwith open(\"test.txt\", \"w\") as f:\n f.write(\"text\").to_bytes(1, \"big\")\n\nwith BuildPart() as my_part:\n ...\n\nshow(my_part.part) # placed output\nshow(my_part.part_local) # local construction result\n\nwith BuildSketch() as my_sketch:\n ...\n\nshow(my_sketch.sketch) # placed output\nshow(my_sketch.sketch_local) # local construction result\n\nwith BuildLine() as my_line:\n ...\n\nshow(my_line.line) # placed output\nshow(my_line.line_local) # local construction result\n\nwith BuildPart() as part_builder:\n Box(part_builder, 10,10,10)\n\nwith BuildPart() as part_builder:\n Box(10,10,10)\n with BuildSketch() as sketch_builder:\n Circle(2)\n\nwith BuildSketch(Plane.XZ) as profile:\n Circle(5)\n\nshow(profile.sketch_local) # circle on local Plane.XY\nshow(profile.sketch) # circle placed on Plane.XZ\n\nimport build123d as bd\n\nwith bd.BuildPart() as bp:\n bd.Box(3, 3, 3)\n with bd.BuildSketch(*bp.faces()):\n bd.Rectangle(1, 2, rotation=45)\n bd.extrude(amount=0.1)\n\nwith BuildPart():\n with Locations((0,10),(0,-10)):\n Box(1,1,1)\n with GridLocations(x_spacing=5, y_spacing=5, x_count=2, y_count=2):\n Sphere(1)\n Cylinder(1,1)\n\nwith BuildPart() as model:\n with Locations((-20, 0), (20, 0)):\n with BuildSketch() as holes:\n Circle(3)\n extrude(amount=5)\n\nwith Locations((-10, 0), (10, 0)):\n with BuildPart() as placed_parts:\n Box(5, 5, 5)\n\nwith Locations(Plane.XY, Plane.XZ):\n locs = GridLocations(1, 1, 2, 2)\n for l in locs:\n print(l)\n\ndef fillet(\n objects: Edge | Vertex | Iterable[Edge | Vertex],\n radius: float,\n):\n\nwith BuildPart() as pipes:\n Box(10, 10, 10, rotation=(10, 20, 30))\n ...\n fillet(pipes.edges(Select.LAST), radius=0.2)\n\nclass Mode(Enum):\n ADD = auto()\n SUBTRACT = auto()\n INTERSECT = auto()\n REPLACE = auto()\n PRIVATE = auto()\n\nwith BuildPart() as pipes:\n Box(10, 10, 10, rotation=(10, 20, 30))\n\nwith BuildPart() as pipes:\n with Locations((-10, -10, -10), (10, 10, 10)):\n Box(10, 10, 10, rotation=(10, 20, 30))\n\nheight, width, thickness, f_rad = 60, 80, 20, 10\n\nwith BuildPart() as pillow_block:\n with BuildSketch() as plan:\n Rectangle(width, height)\n fillet(plan.vertices(), radius=f_rad)\n extrude(amount=thickness)\n" + }, + { + "id": "docs-rst/location_arithmetic/b01", + "source": "docs/location_arithmetic.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\ndef location_symbol(location: Location, scale: float = 1) -> Compound:\n return Compound.make_triad(axes_scale=scale).locate(location)\n\ndef plane_symbol(plane: Plane, scale: float = 1) -> Compound:\n triad = Compound.make_triad(axes_scale=scale)\n circle = Circle(scale * .8).edge()\n return (triad + circle).locate(plane.location)\n" + }, + { + "id": "docs-rst/location_arithmetic/b02", + "source": "docs/location_arithmetic.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nloc = Location((0.1, 0.2, 0.3), (10, 20, 30))\n\nface = loc * Rectangle(1, 2)\n\nshow_object(face, name=\"face\")\nshow_object(location_symbol(loc), name=\"location\")\n" + }, + { + "id": "docs-rst/location_arithmetic/b03", + "source": "docs/location_arithmetic.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nplane = Plane.XZ\n\nface = plane * Rectangle(1, 2)\n\nshow_object(face, name=\"face\")\nshow_object(plane_symbol(plane), name=\"plane\")\n" + }, + { + "id": "docs-rst/location_arithmetic/b04", + "source": "docs/location_arithmetic.rst code-block #4", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nloc = Location((0.1, 0.2, 0.3), (10, 20, 30))\n\nface = loc * Rectangle(1,2)\n\nbox = Plane(loc) * Pos(0.2, 0.4, 0.1) * Box(0.2, 0.2, 0.2)\n# box = Plane(face.location) * Pos(0.2, 0.4, 0.1) * Box(0.2, 0.2, 0.2)\n# box = loc * Pos(0.2, 0.4, 0.1) * Box(0.2, 0.2, 0.2)\n\nshow_object(face, name=\"face\")\nshow_object(location_symbol(loc), name=\"location\")\nshow_object(box, name=\"box\")\n" + }, + { + "id": "docs-rst/location_arithmetic/b05", + "source": "docs/location_arithmetic.rst code-block #5", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nloc = Location((0.1, 0.2, 0.3), (10, 20, 30))\n\nface = loc * Rectangle(1,2)\n\nbox = Plane(loc) * Rot(Z=80) * Box(0.2, 0.2, 0.2)\n\nshow_object(face, name=\"face\")\nshow_object(location_symbol(loc), name=\"location\")\nshow_object(box, name=\"box\")\n" + }, + { + "id": "docs-rst/location_arithmetic/b06", + "source": "docs/location_arithmetic.rst code-block #6", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nloc = Location((0.1, 0.2, 0.3), (10, 20, 30))\n\nface = loc * Rectangle(1,2)\n\nbox = loc * Rot(20, 40, 80) * Box(0.2, 0.2, 0.2)\n\nshow_object(face, name=\"face\")\nshow_object(location_symbol(loc), name=\"location\")\nshow_object(box, name=\"box\")\n" + }, + { + "id": "docs-rst/location_arithmetic/b07", + "source": "docs/location_arithmetic.rst code-block #7", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nloc = Location((0.1, 0.2, 0.3), (10, 20, 30))\n\nface = loc * Rectangle(1,2)\n\nbox = loc * Rot(20, 40, 80) * Pos(0.2, 0.4, 0.1) * Box(0.2, 0.2, 0.2)\n\nshow_object(face, name=\"face\")\nshow_object(location_symbol(loc), name=\"location\")\nshow_object(box, name=\"box\")\nshow_object(location_symbol(loc * Rot(20, 40, 80), 0.5), options={\"color\":(0, 255, 255)}, name=\"local_location\")\n" + }, + { + "id": "docs-rst/location_arithmetic/b08", + "source": "docs/location_arithmetic.rst code-block #8", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nloc = Location((0.1, 0.2, 0.3), (10, 20, 30))\n\nface = loc * Rectangle(1,2)\n\nbox = loc * Pos(0.2, 0.4, 0.1) * Rot(20, 40, 80) * Box(0.2, 0.2, 0.2)\n\nshow_object(face, name=\"face\")\nshow_object(location_symbol(loc), name=\"location\")\nshow_object(box, name=\"box\")\nshow_object(location_symbol(loc * Pos(0.2, 0.4, 0.1), 0.5), options={\"color\":(0, 255, 255)}, name=\"local_location\")\n" + }, + { + "id": "docs-rst/location_arithmetic/all", + "source": "docs/location_arithmetic.rst (all 8 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\nfrom math import *\n\ndef location_symbol(location: Location, scale: float = 1) -> Compound:\n return Compound.make_triad(axes_scale=scale).locate(location)\n\ndef plane_symbol(plane: Plane, scale: float = 1) -> Compound:\n triad = Compound.make_triad(axes_scale=scale)\n circle = Circle(scale * .8).edge()\n return (triad + circle).locate(plane.location)\n\nloc = Location((0.1, 0.2, 0.3), (10, 20, 30))\n\nface = loc * Rectangle(1, 2)\n\nshow_object(face, name=\"face\")\nshow_object(location_symbol(loc), name=\"location\")\n\nplane = Plane.XZ\n\nface = plane * Rectangle(1, 2)\n\nshow_object(face, name=\"face\")\nshow_object(plane_symbol(plane), name=\"plane\")\n\nloc = Location((0.1, 0.2, 0.3), (10, 20, 30))\n\nface = loc * Rectangle(1,2)\n\nbox = Plane(loc) * Pos(0.2, 0.4, 0.1) * Box(0.2, 0.2, 0.2)\n# box = Plane(face.location) * Pos(0.2, 0.4, 0.1) * Box(0.2, 0.2, 0.2)\n# box = loc * Pos(0.2, 0.4, 0.1) * Box(0.2, 0.2, 0.2)\n\nshow_object(face, name=\"face\")\nshow_object(location_symbol(loc), name=\"location\")\nshow_object(box, name=\"box\")\n\nloc = Location((0.1, 0.2, 0.3), (10, 20, 30))\n\nface = loc * Rectangle(1,2)\n\nbox = Plane(loc) * Rot(Z=80) * Box(0.2, 0.2, 0.2)\n\nshow_object(face, name=\"face\")\nshow_object(location_symbol(loc), name=\"location\")\nshow_object(box, name=\"box\")\n\nloc = Location((0.1, 0.2, 0.3), (10, 20, 30))\n\nface = loc * Rectangle(1,2)\n\nbox = loc * Rot(20, 40, 80) * Box(0.2, 0.2, 0.2)\n\nshow_object(face, name=\"face\")\nshow_object(location_symbol(loc), name=\"location\")\nshow_object(box, name=\"box\")\n\nloc = Location((0.1, 0.2, 0.3), (10, 20, 30))\n\nface = loc * Rectangle(1,2)\n\nbox = loc * Rot(20, 40, 80) * Pos(0.2, 0.4, 0.1) * Box(0.2, 0.2, 0.2)\n\nshow_object(face, name=\"face\")\nshow_object(location_symbol(loc), name=\"location\")\nshow_object(box, name=\"box\")\nshow_object(location_symbol(loc * Rot(20, 40, 80), 0.5), options={\"color\":(0, 255, 255)}, name=\"local_location\")\n\nloc = Location((0.1, 0.2, 0.3), (10, 20, 30))\n\nface = loc * Rectangle(1,2)\n\nbox = loc * Pos(0.2, 0.4, 0.1) * Rot(20, 40, 80) * Box(0.2, 0.2, 0.2)\n\nshow_object(face, name=\"face\")\nshow_object(location_symbol(loc), name=\"location\")\nshow_object(box, name=\"box\")\nshow_object(location_symbol(loc * Pos(0.2, 0.4, 0.1), 0.5), options={\"color\":(0, 255, 255)}, name=\"local_location\")\n" + }, + { + "id": "docs-rst/moving_objects/b01", + "source": "docs/moving_objects.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith Locations((10, 20, 30)):\n Box(5, 5, 5)\n" + }, + { + "id": "docs-rst/moving_objects/b02", + "source": "docs/moving_objects.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nrotated_box = Rotation(45, 0, 0) * box\n" + }, + { + "id": "docs-rst/moving_objects/b03", + "source": "docs/moving_objects.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nshape.position = (x, y, z)\n" + }, + { + "id": "docs-rst/moving_objects/b04", + "source": "docs/moving_objects.rst code-block #4", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nshape.position += (x, y, z)\nshape.position -= (x, y, z)\n" + }, + { + "id": "docs-rst/moving_objects/b05", + "source": "docs/moving_objects.rst code-block #5", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nshape.orientation = (X, Y, Z)\n" + }, + { + "id": "docs-rst/moving_objects/b06", + "source": "docs/moving_objects.rst code-block #6", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nshape.orientation += (X, Y, Z)\nshape.orientation -= (X, Y, Z)\n" + }, + { + "id": "docs-rst/moving_objects/b07", + "source": "docs/moving_objects.rst code-block #7", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nshape.move(Location)\n" + }, + { + "id": "docs-rst/moving_objects/b08", + "source": "docs/moving_objects.rst code-block #8", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nrelocated_shape = shape.moved(Location)\n" + }, + { + "id": "docs-rst/moving_objects/b09", + "source": "docs/moving_objects.rst code-block #9", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nshape.locate(Location)\n" + }, + { + "id": "docs-rst/moving_objects/b10", + "source": "docs/moving_objects.rst code-block #10", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nrelocated_shape = shape.located(Location)\n" + }, + { + "id": "docs-rst/moving_objects/b11", + "source": "docs/moving_objects.rst code-block #11", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nrelocated_shape = shape.translate((x, y, z))\n" + }, + { + "id": "docs-rst/moving_objects/b12", + "source": "docs/moving_objects.rst code-block #12", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nrotated_shape = shape.rotate(Axis, angle_in_degrees)\n" + }, + { + "id": "docs-rst/moving_objects/all", + "source": "docs/moving_objects.rst (all 12 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\nfrom math import *\n\nwith Locations((10, 20, 30)):\n Box(5, 5, 5)\n\nrotated_box = Rotation(45, 0, 0) * box\n\nshape.position = (x, y, z)\n\nshape.position += (x, y, z)\nshape.position -= (x, y, z)\n\nshape.orientation = (X, Y, Z)\n\nshape.orientation += (X, Y, Z)\nshape.orientation -= (X, Y, Z)\n\nshape.move(Location)\n\nrelocated_shape = shape.moved(Location)\n\nshape.locate(Location)\n\nrelocated_shape = shape.located(Location)\n\nrelocated_shape = shape.translate((x, y, z))\n\nrotated_shape = shape.rotate(Axis, angle_in_degrees)\n" + }, + { + "id": "docs-rst/objects/b01", + "source": "docs/objects.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as disk:\n with BuildSketch():\n Circle(a)\n with Locations((b, 0.0)):\n Rectangle(c, c, mode=Mode.SUBTRACT)\n with Locations((0, b)):\n Circle(d, mode=Mode.SUBTRACT)\n extrude(amount=c)\n" + }, + { + "id": "docs-rst/objects/b02", + "source": "docs/objects.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nsketch = Circle(a) - Pos(b, 0.0) * Rectangle(c, c) - Pos(0.0, b) * Circle(d)\ndisk = extrude(sketch, c)\n" + }, + { + "id": "docs-rst/objects/b03", + "source": "docs/objects.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildSketch():\n Circle(1, align=(Align.MIN, Align.MIN))\n" + }, + { + "id": "docs-rst/objects/b04", + "source": "docs/objects.rst code-block #4", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildSketch():\n Circle(1, align=Align.MIN)\n" + }, + { + "id": "docs-rst/objects/all", + "source": "docs/objects.rst (all 4 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as disk:\n with BuildSketch():\n Circle(a)\n with Locations((b, 0.0)):\n Rectangle(c, c, mode=Mode.SUBTRACT)\n with Locations((0, b)):\n Circle(d, mode=Mode.SUBTRACT)\n extrude(amount=c)\n\nsketch = Circle(a) - Pos(b, 0.0) * Rectangle(c, c) - Pos(0.0, b) * Circle(d)\ndisk = extrude(sketch, c)\n\nwith BuildSketch():\n Circle(1, align=(Align.MIN, Align.MIN))\n\nwith BuildSketch():\n Circle(1, align=Align.MIN)\n" + }, + { + "id": "docs-rst/operations/b01", + "source": "docs/operations.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as cylinder:\n with BuildSketch():\n Circle(radius)\n extrude(amount=height)\n" + }, + { + "id": "docs-rst/operations/b02", + "source": "docs/operations.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\ncylinder = extrude(Circle(radius), amount=height)\n" + }, + { + "id": "docs-rst/operations/all", + "source": "docs/operations.rst (all 2 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as cylinder:\n with BuildSketch():\n Circle(radius)\n extrude(amount=height)\n\ncylinder = extrude(Circle(radius), amount=height)\n" + }, + { + "id": "docs-rst/selectors/b01", + "source": "docs/selectors.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildSketch() as din:\n ...\n outside_vertices = filter(\n lambda v: (v.Y == 0.0 or v.Y == height)\n and -overall_width / 2 < v.X < overall_width / 2,\n din.vertices(),\n )\n" + }, + { + "id": "docs-rst/selectors/b02", + "source": "docs/selectors.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nobj = Box(1, 1, 1) - Cylinder(0.2, 1)\nfaces_with_holes = obj.faces().filter_by(lambda f: f.inner_wires())\n" + }, + { + "id": "docs-rst/selectors/all", + "source": "docs/selectors.rst (all 2 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\nfrom math import *\n\nwith BuildSketch() as din:\n ...\n outside_vertices = filter(\n lambda v: (v.Y == 0.0 or v.Y == height)\n and -overall_width / 2 < v.X < overall_width / 2,\n din.vertices(),\n )\n\nobj = Box(1, 1, 1) - Cylinder(0.2, 1)\nfaces_with_holes = obj.faces().filter_by(lambda f: f.inner_wires())\n" + }, + { + "id": "docs-rst/tips/b01", + "source": "docs/tips.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\n\nsvg_opts = {\"pixel_scale\": 5, \"show_axes\": False, \"show_hidden\": True}\n\nlength, width, thickness = 80.0, 60.0, 10.0\nhole_dia = 6.0\n\nwith BuildPart() as plate:\n Box(length, width, thickness)\n with GridLocations(length - 20, width - 20, 2, 2):\n Hole(radius=hole_dia / 2)\n top_face: Face = plate.faces().sort_by(Axis.Z)[-1]\n hole_edges = top_face.edges().filter_by(GeomType.CIRCLE)\n chamfer(hole_edges, length=1)\n" + }, + { + "id": "docs-rst/tips/b02", + "source": "docs/tips.rst code-block #2", + "kind": "docs-rst", + "code": "import build123d as b3d\nb3d_solid = b3d.Solid.make_box(1,1,1)\n\n... some cadquery stuff ...\n\nb3d_solid.wrapped = cq_solid.wrapped\n" + }, + { + "id": "docs-rst/tips/b03", + "source": "docs/tips.rst code-block #3", + "kind": "docs-rst", + "code": "import build123d as b3d\nimport cadquery as cq\n\nwith b3d.BuildPart() as b123d_box:\n b3d.Box(1,2,3)\n\ncq_solid = cq.Solid.makeBox(1,1,1)\ncq_solid.wrapped = b123d_box.part.solid().wrapped\n" + }, + { + "id": "docs-rst/tips/b04", + "source": "docs/tips.rst code-block #4", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildSketch(Plane.XZ) as vertical_sketch:\n Rectangle(1, 1)\n with Locations(vertices().group_by(Axis.X)[-1].sort_by(Axis.Z)[-1]):\n Circle(0.2)\n" + }, + { + "id": "docs-rst/tips/b05", + "source": "docs/tips.rst code-block #5", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildSketch(Plane.YZ.rotated((123, 45, 6))) as custom_plane:\n Rectangle(1, 1, align=Align.MIN)\n with Locations(vertices().group_by(Axis.X)[-1].sort_by(Axis.Y)[-1]):\n Circle(0.2)\n" + }, + { + "id": "docs-rst/tips/b06", + "source": "docs/tips.rst code-block #6", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildSketch() as sketch:\n with BuildLine(Plane.XZ):\n Polyline(...)\n make_face()\n" + }, + { + "id": "docs-rst/tips/all", + "source": "docs/tips.rst (all 6 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\n\nsvg_opts = {\"pixel_scale\": 5, \"show_axes\": False, \"show_hidden\": True}\n\nlength, width, thickness = 80.0, 60.0, 10.0\nhole_dia = 6.0\n\nwith BuildPart() as plate:\n Box(length, width, thickness)\n with GridLocations(length - 20, width - 20, 2, 2):\n Hole(radius=hole_dia / 2)\n top_face: Face = plate.faces().sort_by(Axis.Z)[-1]\n hole_edges = top_face.edges().filter_by(GeomType.CIRCLE)\n chamfer(hole_edges, length=1)\n\nimport build123d as b3d\nb3d_solid = b3d.Solid.make_box(1,1,1)\n\n... some cadquery stuff ...\n\nb3d_solid.wrapped = cq_solid.wrapped\n\nimport build123d as b3d\nimport cadquery as cq\n\nwith b3d.BuildPart() as b123d_box:\n b3d.Box(1,2,3)\n\ncq_solid = cq.Solid.makeBox(1,1,1)\ncq_solid.wrapped = b123d_box.part.solid().wrapped\n\nwith BuildSketch(Plane.XZ) as vertical_sketch:\n Rectangle(1, 1)\n with Locations(vertices().group_by(Axis.X)[-1].sort_by(Axis.Z)[-1]):\n Circle(0.2)\n\nwith BuildSketch(Plane.YZ.rotated((123, 45, 6))) as custom_plane:\n Rectangle(1, 1, align=Align.MIN)\n with Locations(vertices().group_by(Axis.X)[-1].sort_by(Axis.Y)[-1]):\n Circle(0.2)\n\nwith BuildSketch() as sketch:\n with BuildLine(Plane.XZ):\n Polyline(...)\n make_face()\n" + }, + { + "id": "docs-rst/topology_selection/b01", + "source": "docs/topology_selection.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\n# In context\nwith BuildSketch() as context:\n Rectangle(1, 1)\n context.edges()\n\n # Build context implicitly has access to the selector\n edges()\n\n# Taking the sketch out of context\ncontext.sketch.edges()\n\n# Create sketch out of context\nRectangle(1, 1).edges()\n" + }, + { + "id": "docs-rst/topology_selection/b02", + "source": "docs/topology_selection.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\n# In context\nwith BuildPart() as context:\n Box(2, 2, 1)\n Cylinder(1, 2)\n context.edges(Select.LAST)\n\n# Does not work out of context!\ncontext.part.edges(Select.LAST)\n(Box(2, 2, 1) + Cylinder(1, 2)).edges(Select.LAST)\n" + }, + { + "id": "docs-rst/topology_selection/b03", + "source": "docs/topology_selection.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as part:\n Box(5, 5, 1)\n Cylinder(1, 5)\n\n part.vertices()\n part.edges()\n part.faces()\n\n # Is the same as\n part.vertices(Select.ALL)\n part.edges(Select.ALL)\n part.faces(Select.ALL)\n" + }, + { + "id": "docs-rst/topology_selection/b04", + "source": "docs/topology_selection.rst code-block #4", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as part:\n Box(5, 5, 1)\n Cylinder(1, 5)\n\n part.vertices(Select.LAST)\n part.edges(Select.LAST)\n part.faces(Select.LAST)\n" + }, + { + "id": "docs-rst/topology_selection/b05", + "source": "docs/topology_selection.rst code-block #5", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as part:\n Box(5, 5, 1)\n Cylinder(1, 5)\n\n part.edges(Select.NEW)\n" + }, + { + "id": "docs-rst/topology_selection/b06", + "source": "docs/topology_selection.rst code-block #6", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as part:\n Box(5, 5, 1, align=(Align.CENTER, Align.CENTER, Align.MAX))\n Cylinder(2, 2, align=(Align.CENTER, Align.CENTER, Align.MIN))\n\n part.edges(Select.NEW)\n" + }, + { + "id": "docs-rst/topology_selection/b07", + "source": "docs/topology_selection.rst code-block #7", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as part:\n Box(5, 5, 1)\n Cylinder(1, 5)\n edges = part.edges().filter_by(lambda a: a.length == 1)\n fillet(edges, 1)\n\n part.edges(Select.NEW)\n" + }, + { + "id": "docs-rst/topology_selection/b08", + "source": "docs/topology_selection.rst code-block #8", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nbox = Box(5, 5, 1)\ncircle = Cylinder(2, 5)\npart = box + circle\nedges = new_edges(box, circle, combined=part)\n" + }, + { + "id": "docs-rst/topology_selection/b09", + "source": "docs/topology_selection.rst code-block #9", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nbox = Box(5, 5, 1)\ncircle = Cylinder(2, 5)\npart_before = box + circle\nedges = part_before.edges().filter_by(lambda a: a.length == 1)\npart = fillet(edges, 1)\nedges = new_edges(part_before, combined=part)\n" + }, + { + "id": "docs-rst/topology_selection/b10", + "source": "docs/topology_selection.rst code-block #10", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\npart.vertices().sort_by(Axis.X)[-4:]\n" + }, + { + "id": "docs-rst/topology_selection/b11", + "source": "docs/topology_selection.rst code-block #11", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\npart.faces().group_by(SortBy.AREA)[0].edges())\n" + }, + { + "id": "docs-rst/topology_selection/b12", + "source": "docs/topology_selection.rst code-block #12", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nbox = Box(1, 1, 1)\nfaces = box.faces()\ntop_face = faces.sort_by(Axis.Z)[-1]\n\nface_rings = faces.group_by(topo_distance_to(top_face))\n\ntop = face_rings[0]\nsides = face_rings[1]\nbottom = face_rings[2]\n" + }, + { + "id": "docs-rst/topology_selection/b13", + "source": "docs/topology_selection.rst code-block #13", + "kind": "docs-rst", + "code": "from build123d import *\nfrom pathlib import Path\nfrom tempfile import TemporaryDirectory\n\n# [removed by collect.py] from ocp_vscode import ColorMap, show\n\nmesher = Mesher()\nmesher.add_shape(Sphere(1), linear_deflection=0.05, angular_deflection=1)\n\nwith TemporaryDirectory() as tmp_dir:\n mesh_path = Path(tmp_dir) / \"sphere.stl\"\n mesher.write(mesh_path)\n mesh_sphere = Mesher().read(mesh_path)[0]\n\nsphere_faces = mesh_sphere.faces()\n\nvertical_groups = sphere_faces.group_by(Axis.Z)\nstarting_ring = vertical_groups[len(vertical_groups) // 2]\nface_rings = sphere_faces.group_by(topo_distance_to(starting_ring))\n\nshow(*face_rings, colors=ColorMap.listed(len(face_rings)))\n" + }, + { + "id": "docs-rst/topology_selection/b14", + "source": "docs/topology_selection.rst code-block #14", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nsphere_edges = mesh_sphere.edges()\nreference_edge = choice(sphere_edges)\nedge_rings = sphere_edges.group_by(topo_distance_to(reference_edge))\n" + }, + { + "id": "docs-rst/topology_selection/b15", + "source": "docs/topology_selection.rst code-block #15", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\npart.faces().filter_by(lambda f: f.normal_at() == Vector(0, 0, 1))\n" + }, + { + "id": "docs-rst/topology_selection/all", + "source": "docs/topology_selection.rst (all 15 code-blocks)", + "kind": "docs-rst-page", + "code": "# In context\nwith BuildSketch() as context:\n Rectangle(1, 1)\n context.edges()\n\n # Build context implicitly has access to the selector\n edges()\n\n# Taking the sketch out of context\ncontext.sketch.edges()\n\n# Create sketch out of context\nRectangle(1, 1).edges()\n\n# In context\nwith BuildPart() as context:\n Box(2, 2, 1)\n Cylinder(1, 2)\n context.edges(Select.LAST)\n\n# Does not work out of context!\ncontext.part.edges(Select.LAST)\n(Box(2, 2, 1) + Cylinder(1, 2)).edges(Select.LAST)\n\nwith BuildPart() as part:\n Box(5, 5, 1)\n Cylinder(1, 5)\n\n part.vertices()\n part.edges()\n part.faces()\n\n # Is the same as\n part.vertices(Select.ALL)\n part.edges(Select.ALL)\n part.faces(Select.ALL)\n\nwith BuildPart() as part:\n Box(5, 5, 1)\n Cylinder(1, 5)\n\n part.vertices(Select.LAST)\n part.edges(Select.LAST)\n part.faces(Select.LAST)\n\nwith BuildPart() as part:\n Box(5, 5, 1)\n Cylinder(1, 5)\n\n part.edges(Select.NEW)\n\nwith BuildPart() as part:\n Box(5, 5, 1, align=(Align.CENTER, Align.CENTER, Align.MAX))\n Cylinder(2, 2, align=(Align.CENTER, Align.CENTER, Align.MIN))\n\n part.edges(Select.NEW)\n\nwith BuildPart() as part:\n Box(5, 5, 1)\n Cylinder(1, 5)\n edges = part.edges().filter_by(lambda a: a.length == 1)\n fillet(edges, 1)\n\n part.edges(Select.NEW)\n\nbox = Box(5, 5, 1)\ncircle = Cylinder(2, 5)\npart = box + circle\nedges = new_edges(box, circle, combined=part)\n\nbox = Box(5, 5, 1)\ncircle = Cylinder(2, 5)\npart_before = box + circle\nedges = part_before.edges().filter_by(lambda a: a.length == 1)\npart = fillet(edges, 1)\nedges = new_edges(part_before, combined=part)\n\npart.vertices().sort_by(Axis.X)[-4:]\n\npart.faces().group_by(SortBy.AREA)[0].edges())\n\nbox = Box(1, 1, 1)\nfaces = box.faces()\ntop_face = faces.sort_by(Axis.Z)[-1]\n\nface_rings = faces.group_by(topo_distance_to(top_face))\n\ntop = face_rings[0]\nsides = face_rings[1]\nbottom = face_rings[2]\n\nfrom build123d import *\nfrom pathlib import Path\nfrom tempfile import TemporaryDirectory\n\n# [removed by collect.py] from ocp_vscode import ColorMap, show\n\nmesher = Mesher()\nmesher.add_shape(Sphere(1), linear_deflection=0.05, angular_deflection=1)\n\nwith TemporaryDirectory() as tmp_dir:\n mesh_path = Path(tmp_dir) / \"sphere.stl\"\n mesher.write(mesh_path)\n mesh_sphere = Mesher().read(mesh_path)[0]\n\nsphere_faces = mesh_sphere.faces()\n\nvertical_groups = sphere_faces.group_by(Axis.Z)\nstarting_ring = vertical_groups[len(vertical_groups) // 2]\nface_rings = sphere_faces.group_by(topo_distance_to(starting_ring))\n\nshow(*face_rings, colors=ColorMap.listed(len(face_rings)))\n\nsphere_edges = mesh_sphere.edges()\nreference_edge = choice(sphere_edges)\nedge_rings = sphere_edges.group_by(topo_distance_to(reference_edge))\n\npart.faces().filter_by(lambda f: f.normal_at() == Vector(0, 0, 1))\n" + }, + { + "id": "docs-rst/tutorial_constraints/b01", + "source": "docs/tutorial_constraints.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\narcs = ConstrainedArcs(..., sagitta=Sagitta.BOTH)\nchosen = arcs.edges().sort_by(Edge.length)[0]\n" + }, + { + "id": "docs-rst/tutorial_constraints/b02", + "source": "docs/tutorial_constraints.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildLine():\n ConstrainedArcs(\n ...,\n selector=lambda edges: edges.sort_by_distance((0, 0))[0],\n )\n" + }, + { + "id": "docs-rst/tutorial_constraints/b03", + "source": "docs/tutorial_constraints.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nisosceles = Triangle(a=30, b=30, C=60)\nisosceles.c\nisosceles.A\nisosceles.B\nisosceles.vertex_A\n" + }, + { + "id": "docs-rst/tutorial_constraints/b04", + "source": "docs/tutorial_constraints.rst code-block #4", + "kind": "docs-rst", + "code": "from math import sin, cos, tan, radians\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\nimport sympy\n\n# This problem uses the sympy symbolic math solver\n\n# Define the symbols for the unknowns\n# - the center of the radius 30 arc (x30, y30)\n# - the center of the radius 66 arc (x66, y66)\n# - end of the 8\u00b0 line (l8x, l8y)\n# - the point with the radius 30 and 66 arc meet i30_66\n# - the start of the horizontal line lh\ny30, x66, xl8, yl8 = sympy.symbols(\"y30 x66 xl8 yl8\")\nx30 = 77 - 55 / 2\ny66 = 66 + 32\n\n# There are 4 unknowns so we need 4 equations\nequations = [\n (x66 - x30) ** 2 + (y66 - y30) ** 2 - (66 + 30) ** 2, # distance between centers\n xl8 - (x30 + 30 * sin(radians(8))), # 8 degree slope\n yl8 - (y30 + 30 * cos(radians(8))), # 8 degree slope\n (yl8 - 50) / (55 / 2 - xl8) - tan(radians(8)), # 8 degree slope\n]\n# There are two solutions but we want the 2nd one\nsolution = {k: float(v) for k,v in sympy.solve(equations, dict=True)[1].items()}\n\n# Create the critical points\nc30 = Vector(x30, solution[y30])\nc66 = Vector(solution[x66], y66)\nl8 = Vector(solution[xl8], solution[yl8])\n\n...\n" + }, + { + "id": "docs-rst/tutorial_constraints/b05", + "source": "docs/tutorial_constraints.rst code-block #5", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nm1 = CenterArc((-2, 0.6), 1, -10, 200).reversed()\nm2 = Spline((0.4, -0.6), (1, -1.6), (2, 0))\nconnector = BlendCurve(m1, m2, tangent_scalars=(2, 1), continuity=ContinuityLevel.C2)\ncomb = Curve(Wire([m1, connector, m2]).curvature_comb(200))\n" + }, + { + "id": "docs-rst/tutorial_constraints/b06", + "source": "docs/tutorial_constraints.rst code-block #6", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildLine() as coincident_ex:\n l1 = Line((0, 0), (1, 2))\n l2 = Line(l1 @ 1, l1 @ 1 + (1, 0))\n" + }, + { + "id": "docs-rst/tutorial_constraints/b07", + "source": "docs/tutorial_constraints.rst code-block #7", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildLine() as tangent_ex:\n l1 = Line((0, 0), (1, 1))\n l2 = JernArc(start=l1 @ 1, tangent=l1 % 1, radius=1, arc_size=70)\n" + }, + { + "id": "docs-rst/tutorial_constraints/b08", + "source": "docs/tutorial_constraints.rst code-block #8", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildLine() as perpendicular_ex:\n l1 = CenterArc((0, 0), 1.5, 0, 45)\n l2 = PolarLine(\n start=l1 @ 1, length=1, direction=l1.tangent_at(1).rotate(Axis.Z, -90)\n )\n" + }, + { + "id": "docs-rst/tutorial_constraints/b09", + "source": "docs/tutorial_constraints.rst code-block #9", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildLine() as intersect_ex:\n c1 = EllipticalCenterArc((0, 0), 1.2, 1.8, 0, arc_size=120, mode=Mode.PRIVATE)\n l1 = PolarLine(start=(-0.2, 0.1), length=c1, angle=10)\n l2 = PolarLine(start=(-0.2, 0.1), length=c1, angle=70)\n l3 = add(c1.trim(l1 @ 1, l2 @ 1))\n" + }, + { + "id": "docs-rst/tutorial_constraints/b10", + "source": "docs/tutorial_constraints.rst code-block #10", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\np1 = ParabolicCenterArc((0, 0), 0.5, 0, arc_size=Line((0, 1), (5, 1)))\nh1 = HyperbolicCenterArc((0, 0), 2, 1, 0, arc_size=Axis((0, 1), (1, 0)))\n" + }, + { + "id": "docs-rst/tutorial_constraints/b11", + "source": "docs/tutorial_constraints.rst code-block #11", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\ninside = FilletPolyline((1.5, 0), (1.5, 1), (-1.5, 1), (-1.5, 0), radius=0.2)\nperimeter = offset(inside, amount=0.2, side=Side.RIGHT)\n" + }, + { + "id": "docs-rst/tutorial_constraints/b12", + "source": "docs/tutorial_constraints.rst code-block #12", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\n# Keep all solutions\nConstrainedArcs(..., selector=lambda arcs: arcs)\n\n# Keep first\nConstrainedArcs(..., selector=lambda arcs: arcs[0])\n\n# Keep shortest\nConstrainedArcs(..., selector=lambda arcs: arcs.sort_by(Edge.length)[0])\n" + }, + { + "id": "docs-rst/tutorial_constraints/b13", + "source": "docs/tutorial_constraints.rst code-block #13", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildLine() as egg_plant:\n # Construction Geometry\n c1 = CenterArc((-2, 0), 0.75, 80, 240, mode=Mode.PRIVATE)\n c2 = CenterArc((2, 0), 1, 220, 250, mode=Mode.PRIVATE)\n\n # egg_plant perimeter\n l1 = ConstrainedArcs((c2, Tangency.OUTSIDE), (c1, Tangency.OUTSIDE), radius=6)\n l2 = ConstrainedArcs(\n (c2, Tangency.ENCLOSING),\n (c1, Tangency.ENCLOSING),\n radius=8,\n selector=lambda a: a.sort_by(Axis.Y)[-1],\n )\n l3 = add(c1.trim(l1 @ 1, l2 @ 1))\n l4 = add(c2.trim(l1 @ 0, l2 @ 0))\n" + }, + { + "id": "docs-rst/tutorial_constraints/b14", + "source": "docs/tutorial_constraints.rst code-block #14", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nConstrainedArcs(\n tangency_one,\n tangency_two,\n radius=...,\n sagitta=Sagitta.SHORT,\n selector=lambda arcs: arcs,\n)\n" + }, + { + "id": "docs-rst/tutorial_constraints/b15", + "source": "docs/tutorial_constraints.rst code-block #15", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nConstrainedArcs(\n tangency_one,\n tangency_two,\n center_on=Axis(...), # or Edge\n sagitta=Sagitta.SHORT,\n selector=lambda arcs: arcs,\n)\n" + }, + { + "id": "docs-rst/tutorial_constraints/b16", + "source": "docs/tutorial_constraints.rst code-block #16", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nConstrainedArcs(\n tangency_one,\n tangency_two,\n tangency_three,\n sagitta=Sagitta.BOTH,\n selector=lambda arcs: arcs,\n)\n" + }, + { + "id": "docs-rst/tutorial_constraints/b17", + "source": "docs/tutorial_constraints.rst code-block #17", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nConstrainedArcs(\n tangency_one,\n center=(x, y),\n selector=lambda arcs: arcs[0],\n)\n" + }, + { + "id": "docs-rst/tutorial_constraints/b18", + "source": "docs/tutorial_constraints.rst code-block #18", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nConstrainedArcs(\n tangency_one,\n radius=...,\n center_on=some_edge,\n selector=lambda arcs: arcs,\n)\n" + }, + { + "id": "docs-rst/tutorial_constraints/b19", + "source": "docs/tutorial_constraints.rst code-block #19", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nConstrainedLines(\n tangency_one,\n tangency_two,\n selector=lambda lines: lines,\n)\n" + }, + { + "id": "docs-rst/tutorial_constraints/b20", + "source": "docs/tutorial_constraints.rst code-block #20", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nConstrainedLines(\n tangency_one,\n (x, y), # through point\n selector=lambda lines: lines,\n)\n" + }, + { + "id": "docs-rst/tutorial_constraints/b21", + "source": "docs/tutorial_constraints.rst code-block #21", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nConstrainedLines(\n tangency_one,\n Axis.Y,\n angle=30, # OR direction=(dx, dy)\n selector=lambda lines: lines,\n)\n" + }, + { + "id": "docs-rst/tutorial_constraints/b22", + "source": "docs/tutorial_constraints.rst code-block #22", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\narcs = ConstrainedArcs(..., sagitta=Sagitta.BOTH)\nchosen = arcs.edges().sort_by(Edge.length)[0]\n" + }, + { + "id": "docs-rst/tutorial_constraints/b23", + "source": "docs/tutorial_constraints.rst code-block #23", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildLine() as bl:\n ConstrainedArcs(\n ...,\n sagitta=Sagitta.BOTH,\n selector=lambda arcs: arcs.sort_by(Edge.length)[0],\n )\n" + }, + { + "id": "docs-rst/tutorial_constraints/b24", + "source": "docs/tutorial_constraints.rst code-block #24", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\n# Nearest to point\nselector=lambda edges: edges.sort_by_distance((0, 0))[0]\n\n# Longest\nselector=lambda edges: edges.sort_by(Edge.length)[-1]\n\n# Right most\nselector=lambda edges: edges.sort_by(Axis.X)[-1]\n\n# Keep two branches\nselector=lambda edges: edges[:2]\n" + }, + { + "id": "docs-rst/tutorial_constraints/b25", + "source": "docs/tutorial_constraints.rst code-block #25", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nimage = ImageFace(\n \"complex_sketch.png\",\n scale=29 / 264,\n origin_pixels=(297, 390),\n location=Location((0, 0, -0.1)),\n)\n\nwith BuildSketch() as sketch:\n with BuildLine() as perimeter:\n c_l1 = PolarLine((0, 32 - 14), 50, -10, mode=Mode.PRIVATE)\n a19 = ConstrainedArcs(c_l1, (-14 + 81 - 29, -14 - 19 + 57), radius=19)\n l2 = Polyline(a19 @ 1, a19 @ 1 + (29 - 5, 0), a19 @ 1 + (29, -5), (-14 + 81, 0))\n l3 = Line(l2 @ 1, (-14 + 81 - 29, (-14 - 19)))\n c_l4 = Line((-14, -14), (-14 + 81, -14), mode=Mode.PRIVATE)\n c_a29_arc_center = l3.intersect(c_l4)[0]\n c_a29 = CenterArc(c_a29_arc_center, 29, 180, 50, mode=Mode.PRIVATE)\n l5 = PolarLine(l3 @ 1, length=c_a29, direction=(-1, 0))\n a5 = ConstrainedArcs(\n c_a29, c_l4, radius=5, selector=lambda a: a.sort_by(Axis.X)[0]\n )\n a29 = add(c_a29.trim(l5 @ 1, a5 @ 0))\n l6 = Polyline(\n a5 @ 1,\n (-14 + 7, -14),\n (-14, -14 + 7),\n (-14, -14 + 32 - 7),\n (-14 + 7, -14 + 32),\n (0, -14 + 32),\n a19 @ 0,\n )\n make_face()\n a14 = Circle(14 / 2, mode=Mode.SUBTRACT)\n" + }, + { + "id": "docs-rst/tutorial_constraints/all", + "source": "docs/tutorial_constraints.rst (all 25 code-blocks)", + "kind": "docs-rst-page", + "code": "arcs = ConstrainedArcs(..., sagitta=Sagitta.BOTH)\nchosen = arcs.edges().sort_by(Edge.length)[0]\n\nwith BuildLine():\n ConstrainedArcs(\n ...,\n selector=lambda edges: edges.sort_by_distance((0, 0))[0],\n )\n\nisosceles = Triangle(a=30, b=30, C=60)\nisosceles.c\nisosceles.A\nisosceles.B\nisosceles.vertex_A\n\nfrom math import sin, cos, tan, radians\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\nimport sympy\n\n# This problem uses the sympy symbolic math solver\n\n# Define the symbols for the unknowns\n# - the center of the radius 30 arc (x30, y30)\n# - the center of the radius 66 arc (x66, y66)\n# - end of the 8\u00b0 line (l8x, l8y)\n# - the point with the radius 30 and 66 arc meet i30_66\n# - the start of the horizontal line lh\ny30, x66, xl8, yl8 = sympy.symbols(\"y30 x66 xl8 yl8\")\nx30 = 77 - 55 / 2\ny66 = 66 + 32\n\n# There are 4 unknowns so we need 4 equations\nequations = [\n (x66 - x30) ** 2 + (y66 - y30) ** 2 - (66 + 30) ** 2, # distance between centers\n xl8 - (x30 + 30 * sin(radians(8))), # 8 degree slope\n yl8 - (y30 + 30 * cos(radians(8))), # 8 degree slope\n (yl8 - 50) / (55 / 2 - xl8) - tan(radians(8)), # 8 degree slope\n]\n# There are two solutions but we want the 2nd one\nsolution = {k: float(v) for k,v in sympy.solve(equations, dict=True)[1].items()}\n\n# Create the critical points\nc30 = Vector(x30, solution[y30])\nc66 = Vector(solution[x66], y66)\nl8 = Vector(solution[xl8], solution[yl8])\n\n...\n\nm1 = CenterArc((-2, 0.6), 1, -10, 200).reversed()\nm2 = Spline((0.4, -0.6), (1, -1.6), (2, 0))\nconnector = BlendCurve(m1, m2, tangent_scalars=(2, 1), continuity=ContinuityLevel.C2)\ncomb = Curve(Wire([m1, connector, m2]).curvature_comb(200))\n\nwith BuildLine() as coincident_ex:\n l1 = Line((0, 0), (1, 2))\n l2 = Line(l1 @ 1, l1 @ 1 + (1, 0))\n\nwith BuildLine() as tangent_ex:\n l1 = Line((0, 0), (1, 1))\n l2 = JernArc(start=l1 @ 1, tangent=l1 % 1, radius=1, arc_size=70)\n\nwith BuildLine() as perpendicular_ex:\n l1 = CenterArc((0, 0), 1.5, 0, 45)\n l2 = PolarLine(\n start=l1 @ 1, length=1, direction=l1.tangent_at(1).rotate(Axis.Z, -90)\n )\n\nwith BuildLine() as intersect_ex:\n c1 = EllipticalCenterArc((0, 0), 1.2, 1.8, 0, arc_size=120, mode=Mode.PRIVATE)\n l1 = PolarLine(start=(-0.2, 0.1), length=c1, angle=10)\n l2 = PolarLine(start=(-0.2, 0.1), length=c1, angle=70)\n l3 = add(c1.trim(l1 @ 1, l2 @ 1))\n\np1 = ParabolicCenterArc((0, 0), 0.5, 0, arc_size=Line((0, 1), (5, 1)))\nh1 = HyperbolicCenterArc((0, 0), 2, 1, 0, arc_size=Axis((0, 1), (1, 0)))\n\ninside = FilletPolyline((1.5, 0), (1.5, 1), (-1.5, 1), (-1.5, 0), radius=0.2)\nperimeter = offset(inside, amount=0.2, side=Side.RIGHT)\n\n# Keep all solutions\nConstrainedArcs(..., selector=lambda arcs: arcs)\n\n# Keep first\nConstrainedArcs(..., selector=lambda arcs: arcs[0])\n\n# Keep shortest\nConstrainedArcs(..., selector=lambda arcs: arcs.sort_by(Edge.length)[0])\n\nwith BuildLine() as egg_plant:\n # Construction Geometry\n c1 = CenterArc((-2, 0), 0.75, 80, 240, mode=Mode.PRIVATE)\n c2 = CenterArc((2, 0), 1, 220, 250, mode=Mode.PRIVATE)\n\n # egg_plant perimeter\n l1 = ConstrainedArcs((c2, Tangency.OUTSIDE), (c1, Tangency.OUTSIDE), radius=6)\n l2 = ConstrainedArcs(\n (c2, Tangency.ENCLOSING),\n (c1, Tangency.ENCLOSING),\n radius=8,\n selector=lambda a: a.sort_by(Axis.Y)[-1],\n )\n l3 = add(c1.trim(l1 @ 1, l2 @ 1))\n l4 = add(c2.trim(l1 @ 0, l2 @ 0))\n\nConstrainedArcs(\n tangency_one,\n tangency_two,\n radius=...,\n sagitta=Sagitta.SHORT,\n selector=lambda arcs: arcs,\n)\n\nConstrainedArcs(\n tangency_one,\n tangency_two,\n center_on=Axis(...), # or Edge\n sagitta=Sagitta.SHORT,\n selector=lambda arcs: arcs,\n)\n\nConstrainedArcs(\n tangency_one,\n tangency_two,\n tangency_three,\n sagitta=Sagitta.BOTH,\n selector=lambda arcs: arcs,\n)\n\nConstrainedArcs(\n tangency_one,\n center=(x, y),\n selector=lambda arcs: arcs[0],\n)\n\nConstrainedArcs(\n tangency_one,\n radius=...,\n center_on=some_edge,\n selector=lambda arcs: arcs,\n)\n\nConstrainedLines(\n tangency_one,\n tangency_two,\n selector=lambda lines: lines,\n)\n\nConstrainedLines(\n tangency_one,\n (x, y), # through point\n selector=lambda lines: lines,\n)\n\nConstrainedLines(\n tangency_one,\n Axis.Y,\n angle=30, # OR direction=(dx, dy)\n selector=lambda lines: lines,\n)\n\narcs = ConstrainedArcs(..., sagitta=Sagitta.BOTH)\nchosen = arcs.edges().sort_by(Edge.length)[0]\n\nwith BuildLine() as bl:\n ConstrainedArcs(\n ...,\n sagitta=Sagitta.BOTH,\n selector=lambda arcs: arcs.sort_by(Edge.length)[0],\n )\n\n# Nearest to point\nselector=lambda edges: edges.sort_by_distance((0, 0))[0]\n\n# Longest\nselector=lambda edges: edges.sort_by(Edge.length)[-1]\n\n# Right most\nselector=lambda edges: edges.sort_by(Axis.X)[-1]\n\n# Keep two branches\nselector=lambda edges: edges[:2]\n\nimage = ImageFace(\n \"complex_sketch.png\",\n scale=29 / 264,\n origin_pixels=(297, 390),\n location=Location((0, 0, -0.1)),\n)\n\nwith BuildSketch() as sketch:\n with BuildLine() as perimeter:\n c_l1 = PolarLine((0, 32 - 14), 50, -10, mode=Mode.PRIVATE)\n a19 = ConstrainedArcs(c_l1, (-14 + 81 - 29, -14 - 19 + 57), radius=19)\n l2 = Polyline(a19 @ 1, a19 @ 1 + (29 - 5, 0), a19 @ 1 + (29, -5), (-14 + 81, 0))\n l3 = Line(l2 @ 1, (-14 + 81 - 29, (-14 - 19)))\n c_l4 = Line((-14, -14), (-14 + 81, -14), mode=Mode.PRIVATE)\n c_a29_arc_center = l3.intersect(c_l4)[0]\n c_a29 = CenterArc(c_a29_arc_center, 29, 180, 50, mode=Mode.PRIVATE)\n l5 = PolarLine(l3 @ 1, length=c_a29, direction=(-1, 0))\n a5 = ConstrainedArcs(\n c_a29, c_l4, radius=5, selector=lambda a: a.sort_by(Axis.X)[0]\n )\n a29 = add(c_a29.trim(l5 @ 1, a5 @ 0))\n l6 = Polyline(\n a5 @ 1,\n (-14 + 7, -14),\n (-14, -14 + 7),\n (-14, -14 + 32 - 7),\n (-14 + 7, -14 + 32),\n (0, -14 + 32),\n a19 @ 0,\n )\n make_face()\n a14 = Circle(14 / 2, mode=Mode.SUBTRACT)\n" + }, + { + "id": "docs-rst/tutorial_design/b01", + "source": "docs/tutorial_design.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildSketch() as sketch:\n with BuildLine() as profile:\n FilletPolyline(\n (0, 0), (length / 2, 0), (length / 2, height), radius=bend_radius\n )\n offset(amount=thickness, side=Side.LEFT)\n make_face()\n mirror(about=Plane.YZ)\n" + }, + { + "id": "docs-rst/tutorial_design/b02", + "source": "docs/tutorial_design.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as bracket:\n with BuildSketch() as sketch:\n with BuildLine() as profile:\n FilletPolyline(\n (0, 0), (length / 2, 0), (length / 2, height), radius=bend_radius\n )\n offset(amount=thickness, side=Side.LEFT)\n make_face()\n mirror(about=Plane.YZ)\n extrude(amount=width / 2)\n mirror(about=Plane.XY)\n" + }, + { + "id": "docs-rst/tutorial_design/b03", + "source": "docs/tutorial_design.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\ncorners = bracket.edges().filter_by(Axis.X).group_by(Axis.Y)[-1]\nfillet(corners, fillet_radius)\n" + }, + { + "id": "docs-rst/tutorial_design/b04", + "source": "docs/tutorial_design.rst code-block #4", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith Locations(bracket.faces().sort_by(Axis.X)[-1]):\n Hole(hole_diameter / 2)\n" + }, + { + "id": "docs-rst/tutorial_design/b05", + "source": "docs/tutorial_design.rst code-block #5", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildSketch(bracket.faces().sort_by(Axis.Y)[0]):\n SlotOverall(20 * MM, hole_diameter)\nextrude(amount=-thickness, mode=Mode.SUBTRACT)\n" + }, + { + "id": "docs-rst/tutorial_design/b06", + "source": "docs/tutorial_design.rst code-block #6", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nthickness = 3 * MM\nwidth = 25 * MM\nlength = 50 * MM\nheight = 25 * MM\nhole_diameter = 5 * MM\nbend_radius = 5 * MM\nfillet_radius = 2 * MM\n" + }, + { + "id": "docs-rst/tutorial_design/b07", + "source": "docs/tutorial_design.rst code-block #7", + "kind": "docs-rst", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show_all\n\nthickness = 3 * MM\nwidth = 25 * MM\nlength = 50 * MM\nheight = 25 * MM\nhole_diameter = 5 * MM\nbend_radius = 5 * MM\nfillet_radius = 2 * MM\n\nwith BuildPart() as bracket:\n with BuildSketch() as sketch:\n with BuildLine() as profile:\n FilletPolyline(\n (0, 0), (length / 2, 0), (length / 2, height), radius=bend_radius\n )\n offset(amount=thickness, side=Side.LEFT)\n make_face()\n mirror(about=Plane.YZ)\n extrude(amount=width / 2)\n mirror(about=Plane.XY)\n corners = bracket.edges().filter_by(Axis.X).group_by(Axis.Y)[-1]\n fillet(corners, fillet_radius)\n with Locations(bracket.faces().sort_by(Axis.X)[-1]):\n Hole(hole_diameter / 2)\n with BuildSketch(bracket.faces().sort_by(Axis.Y)[0]):\n SlotOverall(20 * MM, hole_diameter)\n extrude(amount=-thickness, mode=Mode.SUBTRACT)\n\nshow_all()\n" + }, + { + "id": "docs-rst/tutorial_design/all", + "source": "docs/tutorial_design.rst (all 7 code-blocks)", + "kind": "docs-rst-page", + "code": "with BuildSketch() as sketch:\n with BuildLine() as profile:\n FilletPolyline(\n (0, 0), (length / 2, 0), (length / 2, height), radius=bend_radius\n )\n offset(amount=thickness, side=Side.LEFT)\n make_face()\n mirror(about=Plane.YZ)\n\nwith BuildPart() as bracket:\n with BuildSketch() as sketch:\n with BuildLine() as profile:\n FilletPolyline(\n (0, 0), (length / 2, 0), (length / 2, height), radius=bend_radius\n )\n offset(amount=thickness, side=Side.LEFT)\n make_face()\n mirror(about=Plane.YZ)\n extrude(amount=width / 2)\n mirror(about=Plane.XY)\n\ncorners = bracket.edges().filter_by(Axis.X).group_by(Axis.Y)[-1]\nfillet(corners, fillet_radius)\n\nwith Locations(bracket.faces().sort_by(Axis.X)[-1]):\n Hole(hole_diameter / 2)\n\nwith BuildSketch(bracket.faces().sort_by(Axis.Y)[0]):\n SlotOverall(20 * MM, hole_diameter)\nextrude(amount=-thickness, mode=Mode.SUBTRACT)\n\nthickness = 3 * MM\nwidth = 25 * MM\nlength = 50 * MM\nheight = 25 * MM\nhole_diameter = 5 * MM\nbend_radius = 5 * MM\nfillet_radius = 2 * MM\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show_all\n\nthickness = 3 * MM\nwidth = 25 * MM\nlength = 50 * MM\nheight = 25 * MM\nhole_diameter = 5 * MM\nbend_radius = 5 * MM\nfillet_radius = 2 * MM\n\nwith BuildPart() as bracket:\n with BuildSketch() as sketch:\n with BuildLine() as profile:\n FilletPolyline(\n (0, 0), (length / 2, 0), (length / 2, height), radius=bend_radius\n )\n offset(amount=thickness, side=Side.LEFT)\n make_face()\n mirror(about=Plane.YZ)\n extrude(amount=width / 2)\n mirror(about=Plane.XY)\n corners = bracket.edges().filter_by(Axis.X).group_by(Axis.Y)[-1]\n fillet(corners, fillet_radius)\n with Locations(bracket.faces().sort_by(Axis.X)[-1]):\n Hole(hole_diameter / 2)\n with BuildSketch(bracket.faces().sort_by(Axis.Y)[0]):\n SlotOverall(20 * MM, hole_diameter)\n extrude(amount=-thickness, mode=Mode.SUBTRACT)\n\nshow_all()\n" + }, + { + "id": "docs-rst/tutorial_materials/b01", + "source": "docs/tutorial_materials.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nbox.label = \"box\"\nlid.label = \"lid\"\nhinge_outer.label = \"outer hinge\"\nhinge_inner.label = \"inner hinge\"\nm6_screw.label = \"M6 screw\"\n" + }, + { + "id": "docs-rst/tutorial_materials/b02", + "source": "docs/tutorial_materials.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nfrom bd_materials import metals, wood, finishes\n\nbox.material = wood.walnut()\nlid.material = wood.walnut()\n\nhinge_inner.material = metals.brass()\nhinge_outer.material = metals.brass()\nm6_screw.material = metals.brass()\n" + }, + { + "id": "docs-rst/tutorial_materials/b03", + "source": "docs/tutorial_materials.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nhinge_inner.material = metals.brass(finish=finishes.brushed())\nhinge_outer.material = metals.brass(finish=finishes.fine_sanding())\n" + }, + { + "id": "docs-rst/tutorial_materials/b04", + "source": "docs/tutorial_materials.rst code-block #4", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nhinge_inner.material.material\n\n# MetalMaterial(\n# name='Brass_C360_HALF_HARD',\n# density=8500,\n# family='brass',\n# transparent=False,\n# tensile_strength=Range(min=380, max=450),\n# modulus_of_elasticity=Range(min=100, max=110),\n# shear_modulus=Range(min=37, max=40),\n# poisson_ratio=Range(min=0.32, max=0.35),\n# specific_heat_capacity=Range(min=380, max=390),\n# max_service_temp=Range(min=150, max=250),\n# thermal_expansion=Range(min=1.9e-05, max=2.1e-05),\n# thermal_conductivity=Range(min=110, max=130),\n# yield_strength=Range(min=200, max=250),\n# shear_strength=Range(min=210, max=270),\n# hardness=Range(min=90, max=120),\n# hardness_scale='HB',\n# melting_temperature=Range(min=880, max=950)\n# ) \n" + }, + { + "id": "docs-rst/tutorial_materials/b05", + "source": "docs/tutorial_materials.rst code-block #5", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nfrom pprint import pprint\nfrom bd_materials.core import PROPERTY_UNITS as pu\n\npprint(pu)\n\n# {\n# 'areal_density': 'g/m\u00b2',\n# 'compressive_strength_parallel': 'MPa',\n# 'density': 'kg/m\u00b3',\n# 'elongation_at_break': '%',\n# 'glass_transition_temperature': '\u00b0C',\n# 'hardness': 'per hardness_scale',\n# 'heat_deflection_temperature': '\u00b0C',\n# 'janka_hardness': 'N',\n# 'max_service_temp': '\u00b0C',\n# 'melting_temperature': '\u00b0C',\n# 'modulus_of_elasticity': 'GPa',\n# 'modulus_of_rupture': 'MPa',\n# 'poisson_ratio': '',\n# 'shear_modulus': 'GPa',\n# 'shear_strength': 'MPa',\n# 'specific_heat_capacity': 'J/(kg\u00b7K)',\n# 'tensile_strength': 'MPa',\n# 'thermal_conductivity': 'W/(m\u00b7K)',\n# 'thermal_expansion': '1/K',\n# 'thickness': 'mm',\n# 'yield_strength': 'MPa'\n# }\n" + }, + { + "id": "docs-rst/tutorial_materials/b06", + "source": "docs/tutorial_materials.rst code-block #6", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nhinge_inner.material.finish\n\n# AppliedFinish(\n# finish=Finish(name='Brushed', notes=None),\n# color=None,\n# sheen=None,\n# scale=(1.0, 1.0),\n# rotation=0.0\n# )\n" + }, + { + "id": "docs-rst/tutorial_materials/b07", + "source": "docs/tutorial_materials.rst code-block #7", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nhinge_inner.material.pbr\n\n# PbrProperties(name='brass_brushed', source='physicallybased', license='CC0 1.0')\n# values: PbrValues(\n# color=[0.9593465889662697, 0.8952268365504931, 0.6821586160863968], \n# metalness=1.0, \n# roughness=1.0, \n# specular_intensity=1.0, \n# specular_color=[0.952, 0.979, 1.021]\n# )\n# maps: PbrMaps(roughness='roughness.png', normal='normal.png')\n# maps_dir: .venv/lib/python3.13/site-packages/threejs_materials/pbr_properties/_assets/_brush\n" + }, + { + "id": "docs-rst/tutorial_materials/b08", + "source": "docs/tutorial_materials.rst code-block #8", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nprint(hinge_inner.material.material.density, pu[\"density\"])\n# 8500 kg/m\u00b3\n\nprint(hinge_inner.material.material.tensile_strength, pu[\"tensile_strength\"])\n# Range(min=380, max=450) MPa\n" + }, + { + "id": "docs-rst/tutorial_materials/b09", + "source": "docs/tutorial_materials.rst code-block #9", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nprint(hinge_inner.material.material.tensile_strength.value_at(0.2), pu[\"tensile_strength\"])\n394.0 MPa\n" + }, + { + "id": "docs-rst/tutorial_materials/b10", + "source": "docs/tutorial_materials.rst code-block #10", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nprint(f\"{hinge_outer.volume=:9.3f}\")\n# hinge_outer.volume=16116.838\n" + }, + { + "id": "docs-rst/tutorial_materials/b11", + "source": "docs/tutorial_materials.rst code-block #11", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nprint(f\"{hinge_outer.mass()=:9.3f} g\")\n# hinge_outer.mass()= 136.993 g\n" + }, + { + "id": "docs-rst/tutorial_materials/b12", + "source": "docs/tutorial_materials.rst code-block #12", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nprint(f\"{hinge_outer.mass(Unit.LB, Unit.IN)=:9.3f} lb\")\n# hinge_outer.mass(Unit.LB, Unit.IN)= 4949.191 lb\n" + }, + { + "id": "docs-rst/tutorial_materials/b13", + "source": "docs/tutorial_materials.rst code-block #13", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nprint(f\"{box.volume=:9.3f}\")\n# box.volume=1940751.770\n\nprint(f\"{box.mass()=:9.3f} g\") # volume read as mm\u00b3\n# box.mass()= 1242.081 g\n\nprint(f\"{box.mass(Unit.LB, Unit.IN)=:9.3f} lb\") # volume read as in\u00b3\n# box.mass(Unit.LB, Unit.IN)=44873.028 lb\n" + }, + { + "id": "docs-rst/tutorial_materials/b14", + "source": "docs/tutorial_materials.rst code-block #14", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nshow(box, lid, hinge_outer, hinge_inner, m6_screw)\n" + }, + { + "id": "docs-rst/tutorial_materials/b15", + "source": "docs/tutorial_materials.rst code-block #15", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nb = Compound(label=\"Box\", children=[box, lid, hinge_outer, hinge_inner, m6_screw])\nexport_gltf(b, \"box.glb\")\n" + }, + { + "id": "docs-rst/tutorial_materials/b16", + "source": "docs/tutorial_materials.rst code-block #16", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nfrom threejs_materials import PbrProperties\nfrom bd_materials import plastics\n\nmat = plastics.custom_plastic(\n \"carbon fiber\", \n density=1500, # kg/m^3\n pbr=PbrProperties.from_gpuopen(\"Carbon biColor Coat\")\n)\n\nbox.material = mat\nlid.material = mat\nhinge_inner.material = metals.mild_steel(finish=finishes.black_oxide())\nhinge_outer.material = metals.mild_steel(finish=finishes.black_oxide())\nm6_screw.material = metals.stainless()\n" + }, + { + "id": "docs-rst/tutorial_materials/b17", + "source": "docs/tutorial_materials.rst code-block #17", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nshow(box, lid, hinge_outer, hinge_inner, m6_screw)\n" + }, + { + "id": "docs-rst/tutorial_materials/b18", + "source": "docs/tutorial_materials.rst code-block #18", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nprint(f\"{hinge_outer.material.material.density=} {pu['density']}\")\n# hinge_outer.material.material.density=7800 kg/m\u00b3\n\nprint(f\"{box.material.material.density=} {pu['density']}\")\n# box.material.material.density=1500 kg/m\u00b3\n\nprint(f\"{hinge_outer.volume=:9.3f}\")\n# hinge_outer.volume=16116.838\n\nprint(f\"{hinge_outer.mass()=:9.3f} g\")\n# hinge_outer.mass()= 125.711 g\n\nprint(f\"{hinge_outer.mass(Unit.LB, Unit.IN)=:9.3f} lb\")\n# hinge_outer.mass(Unit.LB, Unit.IN)= 4541.610 lb\n\nprint(f\"{box.volume=:9.3f}\")\n# box.volume=1940751.770\n\nprint(f\"{box.mass()=:9.3f} g\")\n# box.mass()= 2911.128 g\n\nprint(f\"{box.mass(Unit.LB, Unit.IN)=:9.3f} lb\")\n# box.mass(Unit.LB, Unit.IN)=105171.159 lb\n" + }, + { + "id": "docs-rst/tutorial_materials/all", + "source": "docs/tutorial_materials.rst (all 18 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\nfrom math import *\n\nbox.label = \"box\"\nlid.label = \"lid\"\nhinge_outer.label = \"outer hinge\"\nhinge_inner.label = \"inner hinge\"\nm6_screw.label = \"M6 screw\"\n\nfrom bd_materials import metals, wood, finishes\n\nbox.material = wood.walnut()\nlid.material = wood.walnut()\n\nhinge_inner.material = metals.brass()\nhinge_outer.material = metals.brass()\nm6_screw.material = metals.brass()\n\nhinge_inner.material = metals.brass(finish=finishes.brushed())\nhinge_outer.material = metals.brass(finish=finishes.fine_sanding())\n\nhinge_inner.material.material\n\n# MetalMaterial(\n# name='Brass_C360_HALF_HARD',\n# density=8500,\n# family='brass',\n# transparent=False,\n# tensile_strength=Range(min=380, max=450),\n# modulus_of_elasticity=Range(min=100, max=110),\n# shear_modulus=Range(min=37, max=40),\n# poisson_ratio=Range(min=0.32, max=0.35),\n# specific_heat_capacity=Range(min=380, max=390),\n# max_service_temp=Range(min=150, max=250),\n# thermal_expansion=Range(min=1.9e-05, max=2.1e-05),\n# thermal_conductivity=Range(min=110, max=130),\n# yield_strength=Range(min=200, max=250),\n# shear_strength=Range(min=210, max=270),\n# hardness=Range(min=90, max=120),\n# hardness_scale='HB',\n# melting_temperature=Range(min=880, max=950)\n# ) \n\nfrom pprint import pprint\nfrom bd_materials.core import PROPERTY_UNITS as pu\n\npprint(pu)\n\n# {\n# 'areal_density': 'g/m\u00b2',\n# 'compressive_strength_parallel': 'MPa',\n# 'density': 'kg/m\u00b3',\n# 'elongation_at_break': '%',\n# 'glass_transition_temperature': '\u00b0C',\n# 'hardness': 'per hardness_scale',\n# 'heat_deflection_temperature': '\u00b0C',\n# 'janka_hardness': 'N',\n# 'max_service_temp': '\u00b0C',\n# 'melting_temperature': '\u00b0C',\n# 'modulus_of_elasticity': 'GPa',\n# 'modulus_of_rupture': 'MPa',\n# 'poisson_ratio': '',\n# 'shear_modulus': 'GPa',\n# 'shear_strength': 'MPa',\n# 'specific_heat_capacity': 'J/(kg\u00b7K)',\n# 'tensile_strength': 'MPa',\n# 'thermal_conductivity': 'W/(m\u00b7K)',\n# 'thermal_expansion': '1/K',\n# 'thickness': 'mm',\n# 'yield_strength': 'MPa'\n# }\n\nhinge_inner.material.finish\n\n# AppliedFinish(\n# finish=Finish(name='Brushed', notes=None),\n# color=None,\n# sheen=None,\n# scale=(1.0, 1.0),\n# rotation=0.0\n# )\n\nhinge_inner.material.pbr\n\n# PbrProperties(name='brass_brushed', source='physicallybased', license='CC0 1.0')\n# values: PbrValues(\n# color=[0.9593465889662697, 0.8952268365504931, 0.6821586160863968], \n# metalness=1.0, \n# roughness=1.0, \n# specular_intensity=1.0, \n# specular_color=[0.952, 0.979, 1.021]\n# )\n# maps: PbrMaps(roughness='roughness.png', normal='normal.png')\n# maps_dir: .venv/lib/python3.13/site-packages/threejs_materials/pbr_properties/_assets/_brush\n\nprint(hinge_inner.material.material.density, pu[\"density\"])\n# 8500 kg/m\u00b3\n\nprint(hinge_inner.material.material.tensile_strength, pu[\"tensile_strength\"])\n# Range(min=380, max=450) MPa\n\nprint(hinge_inner.material.material.tensile_strength.value_at(0.2), pu[\"tensile_strength\"])\n394.0 MPa\n\nprint(f\"{hinge_outer.volume=:9.3f}\")\n# hinge_outer.volume=16116.838\n\nprint(f\"{hinge_outer.mass()=:9.3f} g\")\n# hinge_outer.mass()= 136.993 g\n\nprint(f\"{hinge_outer.mass(Unit.LB, Unit.IN)=:9.3f} lb\")\n# hinge_outer.mass(Unit.LB, Unit.IN)= 4949.191 lb\n\nprint(f\"{box.volume=:9.3f}\")\n# box.volume=1940751.770\n\nprint(f\"{box.mass()=:9.3f} g\") # volume read as mm\u00b3\n# box.mass()= 1242.081 g\n\nprint(f\"{box.mass(Unit.LB, Unit.IN)=:9.3f} lb\") # volume read as in\u00b3\n# box.mass(Unit.LB, Unit.IN)=44873.028 lb\n\nshow(box, lid, hinge_outer, hinge_inner, m6_screw)\n\nb = Compound(label=\"Box\", children=[box, lid, hinge_outer, hinge_inner, m6_screw])\nexport_gltf(b, \"box.glb\")\n\nfrom threejs_materials import PbrProperties\nfrom bd_materials import plastics\n\nmat = plastics.custom_plastic(\n \"carbon fiber\", \n density=1500, # kg/m^3\n pbr=PbrProperties.from_gpuopen(\"Carbon biColor Coat\")\n)\n\nbox.material = mat\nlid.material = mat\nhinge_inner.material = metals.mild_steel(finish=finishes.black_oxide())\nhinge_outer.material = metals.mild_steel(finish=finishes.black_oxide())\nm6_screw.material = metals.stainless()\n\nshow(box, lid, hinge_outer, hinge_inner, m6_screw)\n\nprint(f\"{hinge_outer.material.material.density=} {pu['density']}\")\n# hinge_outer.material.material.density=7800 kg/m\u00b3\n\nprint(f\"{box.material.material.density=} {pu['density']}\")\n# box.material.material.density=1500 kg/m\u00b3\n\nprint(f\"{hinge_outer.volume=:9.3f}\")\n# hinge_outer.volume=16116.838\n\nprint(f\"{hinge_outer.mass()=:9.3f} g\")\n# hinge_outer.mass()= 125.711 g\n\nprint(f\"{hinge_outer.mass(Unit.LB, Unit.IN)=:9.3f} lb\")\n# hinge_outer.mass(Unit.LB, Unit.IN)= 4541.610 lb\n\nprint(f\"{box.volume=:9.3f}\")\n# box.volume=1940751.770\n\nprint(f\"{box.mass()=:9.3f} g\")\n# box.mass()= 2911.128 g\n\nprint(f\"{box.mass(Unit.LB, Unit.IN)=:9.3f} lb\")\n# box.mass(Unit.LB, Unit.IN)=105171.159 lb\n" + }, + { + "id": "docs-rst/tutorial_stl_reconstruction/b01", + "source": "docs/tutorial_stl_reconstruction.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\n\nimporter = Mesher()\nfull_mesh = importer.read(\"target_part.stl\")[0]\n\n# Example: reduce the work to one quarter of a symmetric model\nquarter_mesh = split(full_mesh, Plane.YZ)\nquarter_mesh = split(quarter_mesh, Plane.XZ)\n\nexport_brep(quarter_mesh, \"target_part_quarter.brep\")\n" + }, + { + "id": "docs-rst/tutorial_stl_reconstruction/b02", + "source": "docs/tutorial_stl_reconstruction.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\n\nworking_mesh = import_brep(\"target_part_quarter.brep\")\n\nprimitives, leftovers, code_lines = detect_primitives(working_mesh)\n\nprint(*code_lines, sep=\"\\n\")\n" + }, + { + "id": "docs-rst/tutorial_stl_reconstruction/b03", + "source": "docs/tutorial_stl_reconstruction.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nfillet_box = fillet(Box(1, 1, 1).edges(), 0.1)\n" + }, + { + "id": "docs-rst/tutorial_stl_reconstruction/b04", + "source": "docs/tutorial_stl_reconstruction.rst code-block #4", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nr00 = Plane.XY.offset(-0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)\nc01 = Plane.XY.offset(-0.4) * Pos(0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc02 = Plane.XY.offset(-0.4) * Pos(-0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc03 = Plane.XY.offset(-0.4) * Pos(-0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc04 = Plane.XY.offset(-0.4) * Pos(0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nr05 = Plane.XY.offset(0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)\nr06 = Plane.YZ.offset(-0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)\nc07 = Plane.YZ.offset(-0.4) * Pos(-0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc08 = Plane.YZ.offset(-0.4) * Pos(-0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc09 = Plane.YZ.offset(-0.4) * Pos(0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc10 = Plane.YZ.offset(-0.4) * Pos(0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nr11 = Plane.YZ.offset(0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)\nr12 = Plane.ZX.offset(-0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)\nc13 = Plane.ZX.offset(-0.4) * Pos(-0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc14 = Plane.ZX.offset(-0.4) * Pos(-0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc15 = Plane.ZX.offset(-0.4) * Pos(0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc16 = Plane.ZX.offset(-0.4) * Pos(0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nr17 = Plane.ZX.offset(0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)\ns18 = Pos((0.399999, -0.399999, 0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\ns19 = Pos((-0.399999, 0.399999, -0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\ns20 = Pos((-0.399999, -0.399999, -0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\ns21 = Pos((0.399999, 0.399999, -0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\ns22 = Pos((-0.399999, 0.400026, 0.399999)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\ns23 = Pos((-0.399999, -0.399999, 0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\ns24 = Pos((0.399999, 0.400026, 0.399999)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\ns25 = Pos((0.399999, -0.399999, -0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\n" + }, + { + "id": "docs-rst/tutorial_stl_reconstruction/b05", + "source": "docs/tutorial_stl_reconstruction.rst code-block #5", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nfillet(Box(1, 1, 1).edges(), 0.1)\n" + }, + { + "id": "docs-rst/tutorial_stl_reconstruction/all", + "source": "docs/tutorial_stl_reconstruction.rst (all 5 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\n\nimporter = Mesher()\nfull_mesh = importer.read(\"target_part.stl\")[0]\n\n# Example: reduce the work to one quarter of a symmetric model\nquarter_mesh = split(full_mesh, Plane.YZ)\nquarter_mesh = split(quarter_mesh, Plane.XZ)\n\nexport_brep(quarter_mesh, \"target_part_quarter.brep\")\n\nfrom build123d import *\n\nworking_mesh = import_brep(\"target_part_quarter.brep\")\n\nprimitives, leftovers, code_lines = detect_primitives(working_mesh)\n\nprint(*code_lines, sep=\"\\n\")\n\nfillet_box = fillet(Box(1, 1, 1).edges(), 0.1)\n\nr00 = Plane.XY.offset(-0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)\nc01 = Plane.XY.offset(-0.4) * Pos(0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc02 = Plane.XY.offset(-0.4) * Pos(-0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc03 = Plane.XY.offset(-0.4) * Pos(-0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc04 = Plane.XY.offset(-0.4) * Pos(0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nr05 = Plane.XY.offset(0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)\nr06 = Plane.YZ.offset(-0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)\nc07 = Plane.YZ.offset(-0.4) * Pos(-0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc08 = Plane.YZ.offset(-0.4) * Pos(-0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc09 = Plane.YZ.offset(-0.4) * Pos(0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc10 = Plane.YZ.offset(-0.4) * Pos(0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nr11 = Plane.YZ.offset(0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)\nr12 = Plane.ZX.offset(-0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)\nc13 = Plane.ZX.offset(-0.4) * Pos(-0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc14 = Plane.ZX.offset(-0.4) * Pos(-0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc15 = Plane.ZX.offset(-0.4) * Pos(0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc16 = Plane.ZX.offset(-0.4) * Pos(0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nr17 = Plane.ZX.offset(0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)\ns18 = Pos((0.399999, -0.399999, 0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\ns19 = Pos((-0.399999, 0.399999, -0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\ns20 = Pos((-0.399999, -0.399999, -0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\ns21 = Pos((0.399999, 0.399999, -0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\ns22 = Pos((-0.399999, 0.400026, 0.399999)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\ns23 = Pos((-0.399999, -0.399999, 0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\ns24 = Pos((0.399999, 0.400026, 0.399999)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\ns25 = Pos((0.399999, -0.399999, -0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\n\nfillet(Box(1, 1, 1).edges(), 0.1)\n" + }, + { + "id": "docs-rst/objects-text/b01", + "source": "docs/objects/text.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\ntext = \"The quick brown fox jumped over the lazy dog.\"\nText(text, 10)\n" + }, + { + "id": "docs-rst/objects-text/b02", + "source": "docs/objects/text.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nText(text, 10, \"Arial\", font_style=FontStyle.BOLD)\n" + }, + { + "id": "docs-rst/objects-text/b03", + "source": "docs/objects/text.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nfrom pprint import pprint\npprint(available_fonts())\n" + }, + { + "id": "docs-rst/objects-text/b04", + "source": "docs/objects/text.rst code-block #4", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nText(text, 10, \"Arial Black\")\n" + }, + { + "id": "docs-rst/objects-text/b05", + "source": "docs/objects/text.rst code-block #5", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nText(text, 10, font_path=\"DejaVuSans.ttf\")\n" + }, + { + "id": "docs-rst/objects-text/b06", + "source": "docs/objects/text.rst code-block #6", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nText(text, 10, font_path=\"SourceSans3-VariableFont_wght.ttf\")\npprint([f.name for f in available_fonts() if \"Source Sans\" in f.name])\nText(text, 10, \"Source Sans 3 Medium\")\n" + }, + { + "id": "docs-rst/objects-text/b07", + "source": "docs/objects/text.rst code-block #7", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nnew_font_faces = FontManager().register_font(\"Roboto-VariableFont_wdth,wght.ttf\")\npprint(new_font_faces)\nText(text, 10, \"Roboto\")\nText(text, 10, \"Roboto Black\")\n" + }, + { + "id": "docs-rst/objects-text/b08", + "source": "docs/objects/text.rst code-block #8", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nText(text, 10, text_align=(TextAlign.LEFT, TextAlign.TOPFIRSTLINE))\n" + }, + { + "id": "docs-rst/objects-text/b09", + "source": "docs/objects/text.rst code-block #9", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\ntext = \"The quick brown\\nfox jumped over\\nthe lazy dog.\"\nText(text, 10, align=(Align.MIN, Align.MIN))\n" + }, + { + "id": "docs-rst/objects-text/b10", + "source": "docs/objects/text.rst code-block #10", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\ntext = \"The quick brown fox\"\npath = RadiusArc((-50, 0), (50, 0), 100)\nText(\n text,\n 10,\n path=path,\n position_on_path=.5,\n text_align=(TextAlign.CENTER, TextAlign.BOTTOM)\n)\n" + }, + { + "id": "docs-rst/objects-text/b11", + "source": "docs/objects/text.rst code-block #11", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nText(text, 10, \"singleline\")\nText(text, 10, \"singleline\", single_line_width=1)\n" + }, + { + "id": "docs-rst/objects-text/b12", + "source": "docs/objects/text.rst code-block #12", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nCompound.make_text(text, 10, \"singleline\")\n" + }, + { + "id": "docs-rst/objects-text/b13", + "source": "docs/objects/text.rst code-block #13", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nText(\"The\", 10, \"Source Sans 3 Black\")\n" + }, + { + "id": "docs-rst/objects-text/all", + "source": "docs/objects/text.rst (all 13 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\nfrom math import *\n\ntext = \"The quick brown fox jumped over the lazy dog.\"\nText(text, 10)\n\nText(text, 10, \"Arial\", font_style=FontStyle.BOLD)\n\nfrom pprint import pprint\npprint(available_fonts())\n\nText(text, 10, \"Arial Black\")\n\nText(text, 10, font_path=\"DejaVuSans.ttf\")\n\nText(text, 10, font_path=\"SourceSans3-VariableFont_wght.ttf\")\npprint([f.name for f in available_fonts() if \"Source Sans\" in f.name])\nText(text, 10, \"Source Sans 3 Medium\")\n\nnew_font_faces = FontManager().register_font(\"Roboto-VariableFont_wdth,wght.ttf\")\npprint(new_font_faces)\nText(text, 10, \"Roboto\")\nText(text, 10, \"Roboto Black\")\n\nText(text, 10, text_align=(TextAlign.LEFT, TextAlign.TOPFIRSTLINE))\n\ntext = \"The quick brown\\nfox jumped over\\nthe lazy dog.\"\nText(text, 10, align=(Align.MIN, Align.MIN))\n\ntext = \"The quick brown fox\"\npath = RadiusArc((-50, 0), (50, 0), 100)\nText(\n text,\n 10,\n path=path,\n position_on_path=.5,\n text_align=(TextAlign.CENTER, TextAlign.BOTTOM)\n)\n\nText(text, 10, \"singleline\")\nText(text, 10, \"singleline\", single_line_width=1)\n\nCompound.make_text(text, 10, \"singleline\")\n\nText(\"The\", 10, \"Source Sans 3 Black\")\n" + }, + { + "id": "docs-rst/topology_selection-filter_examples/b01", + "source": "docs/topology_selection/filter_examples.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\n\nwith BuildPart() as part:\n Box(1, 1, 1)\n" + }, + { + "id": "docs-rst/topology_selection-filter_examples/b02", + "source": "docs/topology_selection/filter_examples.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\npart.faces().filter_by(Axis.Z)\npart.faces().filter_by(Plane.XY)\n" + }, + { + "id": "docs-rst/topology_selection-filter_examples/b03", + "source": "docs/topology_selection/filter_examples.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\npart.faces().filter_by(lambda f: abs(f.normal_at().dot(Axis.Z.direction) < 1e-6)\npart.faces().filter_by(lambda f: abs(f.normal_at().dot(Plane.XY.z_dir)) < 1e-6)\n" + }, + { + "id": "docs-rst/topology_selection-filter_examples/all", + "source": "docs/topology_selection/filter_examples.rst (all 3 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\n\nwith BuildPart() as part:\n Box(1, 1, 1)\n\npart.faces().filter_by(Axis.Z)\npart.faces().filter_by(Plane.XY)\n\npart.faces().filter_by(lambda f: abs(f.normal_at().dot(Axis.Z.direction) < 1e-6)\npart.faces().filter_by(lambda f: abs(f.normal_at().dot(Plane.XY.z_dir)) < 1e-6)\n" + }, + { + "id": "docs-rst/topology_selection-group_examples/b01", + "source": "docs/topology_selection/group_examples.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nradius_groups = part.edges().filter_by(GeomType.CIRCLE)\nbearing_edges = radius_groups.filter_by(lambda e: e.radius == 8)\npin_edges = radius_groups.filter_by(lambda e: e.radius == 1.5)\n" + } +] \ No newline at end of file diff --git a/test/b123d-validation/manifest.json b/test/b123d-validation/manifest.json new file mode 100644 index 00000000..fb1a24f5 --- /dev/null +++ b/test/b123d-validation/manifest.json @@ -0,0 +1,1496 @@ +[ + { + "id": "examples/bicycle_tire", + "source": "examples/bicycle_tire.py", + "kind": "example", + "code": "# [Code]\nimport copy\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nwheel_diameter = 740 * MM\n\nwith BuildSketch() as tire_profile:\n with BuildLine() as build_profile:\n l00 = Bezier((0.0, 0.0), (7.05, 0.0), (12.18, 1.54), (15.13, 4.54))\n l01 = Bezier(l00 @ 1, (15.81, 5.22), (15.98, 5.44), (16.5, 6.23))\n l02 = Bezier(l01 @ 1, (18.45, 9.19), (19.61, 13.84), (19.94, 20.06))\n l03 = Bezier(l02 @ 1, (20.1, 23.24), (19.93, 27.48), (19.56, 29.45))\n l04 = Bezier(l03 @ 1, (19.13, 31.69), (18.23, 33.67), (16.91, 35.32))\n l05 = Bezier(l04 @ 1, (16.26, 36.12), (15.57, 36.77), (14.48, 37.58))\n l06 = Bezier(l05 @ 1, (12.77, 38.85), (11.51, 40.28), (10.76, 41.78))\n l07 = Bezier(l06 @ 1, (10.07, 43.16), (10.15, 43.81), (11.03, 43.98))\n l08 = Bezier(l07 @ 1, (11.82, 44.13), (12.15, 44.55), (12.08, 45.33))\n l09 = Bezier(l08 @ 1, (12.01, 46.07), (11.84, 46.43), (11.43, 46.69))\n l10 = Bezier(l09 @ 1, (10.98, 46.97), (10.07, 46.7), (9.47, 46.1))\n l11 = Bezier(l10 @ 1, (9.03, 45.65), (8.88, 45.31), (8.84, 44.65))\n l12 = Bezier(l11 @ 1, (8.78, 43.6), (9.11, 42.26), (9.72, 41.0))\n l13 = Bezier(l12 @ 1, (10.43, 39.54), (11.52, 38.2), (12.78, 37.22))\n l14 = Bezier(l13 @ 1, (15.36, 35.23), (16.58, 33.76), (17.45, 31.62))\n l15 = Bezier(l14 @ 1, (17.91, 30.49), (18.22, 29.27), (18.4, 27.8))\n l16 = Bezier(l15 @ 1, (18.53, 26.78), (18.52, 23.69), (18.37, 22.61))\n l17 = Bezier(l16 @ 1, (17.8, 18.23), (16.15, 14.7), (13.39, 11.94))\n l18 = Bezier(l17 @ 1, (11.89, 10.45), (10.19, 9.31), (8.09, 8.41))\n l19 = Bezier(l18 @ 1, (3.32, 6.35), (0.0, 6.64))\n mirror(about=Plane.YZ)\n make_face()\n\ntire = revolve(Pos(Y=-wheel_diameter / 2) * tire_profile.face(), Axis.X)\n\nwith BuildSketch() as tread_pattern:\n with Locations((1, 1)):\n Trapezoid(15, 12, 60, 120, align=Align.MIN)\n with Locations((1, 8)):\n with GridLocations(0, 5, 1, 2):\n Rectangle(50, 2, mode=Mode.SUBTRACT)\n\n# Define the surface and path that the tread pattern will be wrapped onto\nhalf_road_surface = Face.revolve(Pos(Y=-wheel_diameter / 2) * l00, 360, Axis.X)\ntread_path = half_road_surface.edges().sort_by(Axis.X)[0]\n\n# Wrap the planar tread pattern onto the tire's outside surface\ntread_faces = half_road_surface.wrap_faces(tread_pattern.faces(), tread_path)\n\n# Mirror the faces to the other half of the tire\ntread_faces.extend([mirror(t, Plane.YZ) for t in tread_faces])\n\n# Thicken the tread to become solid nubs\n# tread_prime = [Solid.thicken(f, 3 * MM) for f in tread_faces]\ntread_prime = [thicken(f, 3 * MM) for f in tread_faces]\n\n# Copy the nubs around the whole tire\ntread = [Rot(X=r) * copy.copy(t) for t in tread_prime for r in range(0, 360, 2)]\n\nshow(tire, tread)\n# [End]\n" + }, + { + "id": "examples/boxes_on_faces", + "source": "examples/boxes_on_faces.py", + "kind": "example", + "code": "# [Imports]\nimport build123d as bd\n# [removed by collect.py] from ocp_vscode import *\n\n# [Code]\nwith bd.BuildPart() as bp:\n bd.Box(3, 3, 3)\n with bd.BuildSketch(*bp.faces()):\n bd.Rectangle(1, 2, rotation=45)\n bd.extrude(amount=0.1)\n\nassert abs(bp.part.volume - (3**3 + 6 * (1 * 2 * 0.1)) < 1e-3)\n\nif \"show_object\" in locals():\n show_object(bp.part.wrapped, name=\"box on faces\")\n# [End]" + }, + { + "id": "examples/boxes_on_faces_algebra", + "source": "examples/boxes_on_faces_algebra.py", + "kind": "example", + "code": "# license see [build123d_license](../LICENSE)\n# [Imports]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n# [Code]\nb = Box(3, 3, 3)\nb2 = Rot(0, 0, 45) * extrude(Rectangle(1, 2), 0.1)\nfor plane in [Plane(f) for f in b.faces()]:\n b += plane * b2\n\nif \"show_object\" in locals():\n show_object(b, name=\"box on faces\")\n# [End]" + }, + { + "id": "examples/bracelet", + "source": "examples/bracelet.py", + "kind": "example", + "code": "# [Code]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n# Define input parameters\n# - radii: ellipse radii (X, Y) controlling the bracelet centerline shape\n# - width: bracelet width (along Z for the center sweep)\n# - thickness: bracelet thickness (radial thickness of the cross section)\n# - opening_angle: the missing angle that creates the wrist opening\n# - label_str: optional text to emboss on the outside surface\n# - Define input parameters\n# radii, width, thickness, opening_angle, label_str = (45, 30), 25, 5, 80, \"build123d\"\nradii, width, thickness, opening_angle, label_str = (45, 30), 25, 5, 80, \"\"\n\n# Step 1: Create an elliptical arc defining the *centerline* of the bracelet.\n# The arc is truncated to leave an opening (the \"gap\" where the bracelet goes on).\n# Angles are in degrees; 270\u00b0 points downward, which keeps the opening centered at the bottom.\ncenter_arc = EllipticalCenterArc(\n (0, 0), *radii, 270 + opening_angle / 2, arc_size=360 - opening_angle\n)\n\n# Step 2: Create HALF of the end cross-section, positioned at the end of the arc.\n# We build only half so we can later mirror it to enforce symmetry and reduce\n# curve-network complexity when building the freeform tip.\n#\n# location_at(1) returns a local coordinate frame at the arc end (tangent-aware).\n# x_dir is chosen so the section\u2019s local \"X\" is well-defined and stable.\nend_center_arc = center_arc.location_at(1, x_dir=(0, 0, 1))\nhalf_x_section = EllipticalCenterArc(\n (0, 0), width / 2, thickness / 2, 90, arc_size=180\n).locate(end_center_arc)\n\n# Step 3: Create a doubly-curved \"tip edge\" curve.\n# The tip edge must live in 3D and conform to the outside of the bracelet.\n# To do that, we:\n# 1) create a surface by extruding the center_arc into a sheet (a ribbon surface)\n# 2) build a planar arc in a local frame at the end of that surface\n# 3) project the planar arc onto the curved surface to get a true 3D curve\n#\n# The resulting tip_arc is a 3D edge that naturally matches the bracelet curvature.\ncenter_surface = -Face.extrude(center_arc, (0, 0, 2 * width)).moved(\n Location((0, 0, -width), (0, 0, 180))\n)\ntip_center_loc = -center_surface.location_at(center_arc @ 1, x_dir=(1, 0, 0))\nnormal_at_tip_center = tip_center_loc.z_axis.direction\n\n# A planar arc that would represent the outer boundary of the tip *if* the surface\n# were flat. We immediately project it to make it truly conformal in 3D.\nplanar_tip_arc = CenterArc((0, 0), width / 2, 270, 180).locate(tip_center_loc).edge()\ntip_arc = planar_tip_arc.project_to_shape(center_surface, -normal_at_tip_center)[0]\n\n# Step 4: Build the tip as a Gordon surface (a surface fit through a curve network).\n# Gordon surfaces are ideal when:\n# - you don\u2019t have an obvious analytic surface\n# - curvature changes in two directions (doubly-curved \"cap\")\n# - you can define a consistent set of profile curves + guide curves\n#\n# Here:\n# - profiles define \"across the tip\" shape (section -> bulged spline -> mirrored section)\n# - guides define \"along the tip\" rails (start point -> projected 3D arc -> end point)\n#\n# Tangents are used to encourage smoothness where the tip joins the swept center section.\nprofile = Spline(\n half_x_section @ 0,\n tip_arc @ 0.5,\n half_x_section @ 1,\n tangents=(center_arc % 1, -(center_arc % 1)),\n)\ntip_surface = Face.make_gordon_surface(\n profiles=[half_x_section, profile, half_x_section.mirror(Plane.XY)],\n guides=[half_x_section @ 0, tip_arc, half_x_section @ 1],\n)\n\n# Step 5: Close the tip surface into a watertight Solid.\n# tip_surface is the outer \"skin\"; we create a side face from its boundary wire\n# and make a shell, then a solid.\ntip_side = Face(tip_surface.wire())\ntip = Solid(Shell([tip_side, tip_surface]))\n\n# Step 6: Sweep the *flat end face* of the tip around the center arc.\n# This is the trick that makes the center section compatible with the freeform tip:\n# the sweep profile is the same face that bounds the tip, so the join is naturally aligned.\ncenter_section = sweep(tip_side, center_arc).solid()\n\n# Step 7: Assemble the bracelet from the center and two mirrored tips.\n# Mirror across YZ to create the opposite end cap.\nbracelet = Solid() + [tip, center_section, tip.mirror(Plane.YZ)]\n\n# Step 8: Add an embossed label.\n# This is often the hardest operation for OCCT in this model:\n# projecting text onto a doubly-curved surface can create many small faces/edges,\n# and thickening them adds even more boolean complexity.\nif label_str:\n label = Text(label_str, font_size=width * 0.8, align=Align.CENTER)\n\n # Project the text onto the bracelet using a path-based placement along center_arc.\n # The parameter offsets the label so it sits centered along arc-length.\n p_labels = bracelet.project_faces(\n label, center_arc, 0.5 - 0.5 * (label.bounding_box().size.X) / center_arc.length\n )\n # Turn the projected faces into solids via thickening (embossing).\n embossed_label = [Solid.thicken(f, 0.5) for f in p_labels.faces()]\n bracelet += embossed_label\n\n# Step 9: Add alignment holes to aid assembly after 3D printing in two halves.\n# These are placed at evenly spaced locations along the arc (including both ends).\n# A small clearance (+0.15) is included for typical FDM tolerances.\nalignment_holes = [\n Pos(p) * Cylinder(1.75 / 2 + 0.15, 8)\n for p in [center_arc.position_at(i / 4) for i in range(5)]\n]\nbracelet -= alignment_holes\n\nshow(bracelet)\n# [End]\n" + }, + { + "id": "examples/build123d_customizable_logo", + "source": "examples/build123d_customizable_logo.py", + "kind": "example", + "code": "# [Imports]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n# [Parameters]\n# - none\n\n# [Code]\nwith BuildSketch() as logo_text:\n Text(\"123d\", font_size=10, align=(Align.MIN, Align.MIN))\n font_height = logo_text.vertices().sort_by(Axis.Y)[-1].Y\n\nwith BuildSketch() as build_text:\n Text(\"build\", font_size=5, align=(Align.CENTER, Align.CENTER))\n build_bb = bounding_box(build_text.sketch, mode=Mode.PRIVATE)\n build_vertices = build_bb.vertices().sort_by(Axis.X)\n build_width = build_vertices[-1].X - build_vertices[0].X\n\nwith BuildSketch() as cust_text:\n Text(\n \"customizable\",\n font_size=2.9,\n align=(Align.CENTER, Align.CENTER),\n font_style=FontStyle.BOLD,\n )\n cust_bb = cust_text.sketch.bounding_box()\n cust_width = cust_bb.size.X\n\nwith BuildLine() as one:\n l1 = Line((font_height * 0.3, 0), (font_height * 0.3, font_height))\n TangentArc(l1 @ 1, (0, font_height * 0.7), tangent=(l1 % 1) * -1)\n\nwith BuildSketch() as two:\n with Locations((font_height * 0.35, 0)):\n Text(\"2\", font_size=10, align=(Align.MIN, Align.MIN))\n\nwith BuildPart() as three_d:\n with BuildSketch(Plane((font_height * 1.1, 0))):\n Text(\"3d\", font_size=10, align=(Align.MIN, Align.MIN))\n extrude(amount=font_height * 0.3)\n logo_width = three_d.vertices().sort_by(Axis.X)[-1].X\n\nwith BuildLine() as arrow_left:\n t1 = TangentArc((0, 0), (1, 0.75), tangent=(1, 0))\n mirror(t1, Plane.XZ)\n\next_line_length = font_height * 0.5\ndim_line_length = (logo_width - build_width - 2 * font_height * 0.05) / 2\nwith BuildLine() as extension_lines:\n l1 = Line((0, -font_height * 0.1), (0, -ext_line_length - font_height * 0.1))\n l2 = Line(\n (logo_width, -font_height * 0.1),\n (logo_width, -ext_line_length - font_height * 0.1),\n )\n with Locations(l1 @ 0.5):\n add(arrow_left.line)\n with Locations(l2 @ 0.5):\n add(arrow_left.line, rotation=180.0)\n Line(l1 @ 0.5, l1 @ 0.5 + Vector(dim_line_length, 0))\n Line(l2 @ 0.5, l2 @ 0.5 - Vector(dim_line_length, 0))\n\n# Precisely center the build Faces\nwith BuildSketch() as build:\n with Locations(\n (l1 @ 0.5 + l2 @ 0.5) / 2\n - Vector((build_vertices[-1].X + build_vertices[0].X) / 2, 0)\n ):\n add(build_text.sketch)\n with Locations((logo_width / 2, -6)):\n add(cust_text.sketch)\n\ncmpd = Compound(\n [three_d.part, two.sketch, one.line, build.sketch, extension_lines.line]\n)\n\nvisible, _hidden = cmpd.project_to_viewport((10, -10, 60))\nmax_dimension = max(*Compound(children=visible).bounding_box().size)\nexporter = ExportSVG(scale=100 / max_dimension)\nexporter.add_shape(visible)\nexporter.write(f\"cmpd.svg\")\n\nshow_object(cmpd, name=\"compound\")\n# show_object(one.line.wrapped, name=\"one\")\n# show_object(two.sketch.wrapped, name=\"two\")\n# show_object(three_d.part.wrapped, name=\"three_d\")\n# show_object(extension_lines.line.wrapped, name=\"extension_lines\")\n# show_object(build.sketch.wrapped, name=\"build\")\n\n# [End]\n" + }, + { + "id": "examples/build123d_customizable_logo_algebra", + "source": "examples/build123d_customizable_logo_algebra.py", + "kind": "example", + "code": "# [Imports]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n# [Parameters]\n\n# [Code]\nlogo_text = Text(\"123d\", font_size=10, align=Align.MIN)\nfont_height = logo_text.vertices().sort_by(Axis.Y)[-1].Y\n\nbuild_text = Text(\"build\", font_size=5, align=Align.CENTER)\nbuild_bb = build_text.bounding_box()\nbuild_width = build_bb.max.X - build_bb.min.X\n\ncust_text = Text(\n \"customizable\",\n font_size=2.9,\n align=Align.CENTER,\n font_style=FontStyle.BOLD,\n)\ncust_bb = cust_text.bounding_box()\ncust_width = cust_bb.max.X - cust_bb.min.X\n\nl1 = Line((font_height * 0.3, 0), (font_height * 0.3, font_height))\none = l1 + TangentArc(l1 @ 1, (0, font_height * 0.7), tangent=(l1 % 1) * -1)\n\ntwo = Pos(font_height * 0.35, 0) * Text(\"2\", font_size=10, align=Align.MIN)\n\nthree_d = Text(\"3d\", font_size=10, align=Align.MIN)\nthree_d = Pos(font_height * 1.1, 0) * extrude(three_d, amount=font_height * 0.3)\nlogo_width = three_d.vertices().sort_by(Axis.X)[-1].X\n\nt1 = TangentArc((0, 0), (1, 0.75), tangent=(1, 0))\narrow_left = t1 + mirror(t1, Plane.XZ)\n\next_line_length = font_height * 0.5\ndim_line_length = (logo_width - build_width - 2 * font_height * 0.05) / 2\n\nl1 = Line((0, -font_height * 0.1), (0, -ext_line_length - font_height * 0.1))\nl2 = Line(\n (logo_width, -font_height * 0.1),\n (logo_width, -ext_line_length - font_height * 0.1),\n)\nextension_lines = Curve() + (l1 + l2)\nextension_lines += Pos(*(l1 @ 0.5)) * arrow_left\nextension_lines += (Pos(*(l2 @ 0.5)) * Rot(Z=180)) * arrow_left\nextension_lines += Line(l1 @ 0.5, l1 @ 0.5 + Vector(dim_line_length, 0))\nextension_lines += Line(l2 @ 0.5, l2 @ 0.5 - Vector(dim_line_length, 0))\n\n# Precisely center the build Faces\np1 = Pos((l1 @ 0.5 + l2 @ 0.5) / 2 - Vector((build_bb.max.X + build_bb.min.X) / 2, 0))\nbuild = p1 * build_text\n\n# add the customizable text to the build text sketch\np2 = Pos((l1 @ 1 + l2 @ 1) / 2 - Vector(cust_bb.max.X + cust_bb.min.X, 1.4))\nbuild += p2 * cust_text\n\ncmpd = Compound([three_d, two, one, build, extension_lines])\n\nif \"show_object\" in locals():\n show_object(cmpd, name=\"compound\")\n # show_object(one.line.wrapped, name=\"one\")\n # show_object(two.sketch.wrapped, name=\"two\")\n # show_object(three_d.part.wrapped, name=\"three_d\")\n # show_object(extension_lines.line.wrapped, name=\"extension_lines\")\n # show_object(build.sketch.wrapped, name=\"build\")\n# [End]\n" + }, + { + "id": "examples/build123d_logo", + "source": "examples/build123d_logo.py", + "kind": "example", + "code": "# [Imports]\nfrom build123d import *\nfrom build123d import Shape\n# [removed by collect.py] from ocp_vscode import *\n\n# [Parameters]\n# - none\n\n# [Code]\nwith BuildSketch() as logo_text:\n Text(\"123d\", font_size=10, align=(Align.MIN, Align.MIN))\n font_height = logo_text.vertices().sort_by(Axis.Y)[-1].Y\n\nwith BuildSketch() as build_text:\n Text(\"build\", font_size=5, align=(Align.CENTER, Align.CENTER))\n build_bb = bounding_box(build_text.sketch, mode=Mode.PRIVATE)\n build_vertices = build_bb.vertices().sort_by(Axis.X)\n build_width = build_vertices[-1].X - build_vertices[0].X\n\nwith BuildLine() as one:\n l1 = Line((font_height * 0.3, 0), (font_height * 0.3, font_height))\n TangentArc(l1 @ 1, (0, font_height * 0.7), tangent=(l1 % 1) * -1)\n\nwith BuildSketch() as two:\n with Locations((font_height * 0.35, 0)):\n Text(\"2\", font_size=10, align=(Align.MIN, Align.MIN))\n\nwith BuildPart() as three_d:\n with BuildSketch(Plane((font_height * 1.1, 0))):\n Text(\"3d\", font_size=10, align=(Align.MIN, Align.MIN))\n extrude(amount=font_height * 0.3)\n logo_width = three_d.vertices().sort_by(Axis.X)[-1].X\n\nwith BuildLine() as arrow_left:\n t1 = TangentArc((0, 0), (1, 0.75), tangent=(1, 0))\n mirror(t1, Plane.XZ)\n\next_line_length = font_height * 0.5\ndim_line_length = (logo_width - build_width - 2 * font_height * 0.05) / 2\nwith BuildLine() as extension_lines:\n l1 = Line((0, -font_height * 0.1), (0, -ext_line_length - font_height * 0.1))\n l2 = Line(\n (logo_width, -font_height * 0.1),\n (logo_width, -ext_line_length - font_height * 0.1),\n )\n with Locations(l1 @ 0.5):\n add(arrow_left.line)\n with Locations(l2 @ 0.5):\n add(arrow_left.line, rotation=180.0)\n Line(l1 @ 0.5, l1 @ 0.5 + Vector(dim_line_length, 0))\n Line(l2 @ 0.5, l2 @ 0.5 - Vector(dim_line_length, 0))\n\n# Precisely center the build Faces\nwith BuildSketch() as build:\n with Locations(\n (l1 @ 0.5 + l2 @ 0.5) / 2\n - Vector((build_vertices[-1].X + build_vertices[0].X) / 2, 0)\n ):\n add(build_text.sketch)\n\n\nif True:\n logo = Compound(\n children=[\n one.line,\n two.sketch,\n three_d.part,\n extension_lines.line,\n build.sketch,\n ]\n )\n\n # logo.export_step(\"logo.step\")\n def add_svg_shape(svg: ExportSVG, shape: Shape, color: tuple[float, float, float]):\n global counter\n try:\n counter += 1\n except:\n counter = 1\n\n visible, _hidden = shape.project_to_viewport(\n (-5, 1, 10), viewport_up=(0, 1, 0), look_at=(0, 0, 0)\n )\n if color is not None:\n svg.add_layer(str(counter), fill_color=color, line_weight=1)\n else:\n svg.add_layer(str(counter), line_weight=1)\n svg.add_shape(visible, layer=str(counter))\n\n svg = ExportSVG(scale=20)\n add_svg_shape(svg, logo, None)\n # add_svg_shape(svg, Compound(children=[one.line, extension_lines.line]), None)\n # add_svg_shape(svg, Compound(children=[two.sketch, build.sketch]), (170, 204, 255))\n # add_svg_shape(svg, three_d.part, (85, 153, 255))\n svg.write(\"logo.svg\")\n\nshow_object(one, name=\"one\")\nshow_object(two, name=\"two\")\nshow_object(three_d, name=\"three_d\")\nshow_object(extension_lines, name=\"extension_lines\")\nshow_object(build, name=\"build\")\n# [End]" + }, + { + "id": "examples/build123d_logo_algebra", + "source": "examples/build123d_logo_algebra.py", + "kind": "example", + "code": "# [Imports]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n# [Parameters]\n# - none\n\n# [Code]\nlogo_text = Text(\"123d\", font_size=10, align=Align.MIN)\nfont_height = logo_text.vertices().sort_by(Axis.Y).last.Y\n\nbuild_text = Text(\"build\", font_size=5, align=Align.CENTER)\nbuild_bb = build_text.bounding_box()\nbuild_width = build_bb.max.X - build_bb.min.X\n\nl1 = Line((font_height * 0.3, 0), (font_height * 0.3, font_height))\none = l1 + TangentArc(l1 @ 1, (0, font_height * 0.7), tangent=(l1 % 1) * -1)\n\ntwo = Pos(font_height * 0.35, 0) * Text(\"2\", font_size=10, align=Align.MIN)\n\nthree_d = Text(\"3d\", font_size=10, align=Align.MIN)\nthree_d = Pos(font_height * 1.1, 0) * extrude(three_d, amount=font_height * 0.3)\nlogo_width = three_d.vertices().sort_by(Axis.X).last.X\n\nt1 = TangentArc((0, 0), (1, 0.75), tangent=(1, 0))\narrow_left = t1 + mirror(t1, Plane.XZ)\n\next_line_length = font_height * 0.5\ndim_line_length = (logo_width - build_width - 2 * font_height * 0.05) / 2\n\nl1 = Line((0, -font_height * 0.1), (0, -ext_line_length - font_height * 0.1))\nl2 = Line(\n (logo_width, -font_height * 0.1),\n (logo_width, -ext_line_length - font_height * 0.1),\n)\nextension_lines = Curve() + (l1 + l2)\nextension_lines += Pos(*(l1 @ 0.5)) * arrow_left\nextension_lines += (Pos(*(l2 @ 0.5)) * Rot(Z=180)) * arrow_left\nextension_lines += Line(l1 @ 0.5, l1 @ 0.5 + Vector(dim_line_length, 0))\nextension_lines += Line(l2 @ 0.5, l2 @ 0.5 - Vector(dim_line_length, 0))\n\n# Precisely center the build Faces\np1 = Pos((l1 @ 0.5 + l2 @ 0.5) / 2 - Vector((build_bb.max.X + build_bb.min.X) / 2, 0))\nbuild = p1 * build_text\n\ncmpd = Compound([three_d, two, one, build, extension_lines])\n\nshow_object(cmpd, name=\"compound\")\n\n# [End]\n" + }, + { + "id": "examples/canadian_flag", + "source": "examples/canadian_flag.py", + "kind": "example", + "code": "# [Imports]\nfrom math import sin, cos, pi\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show_object, show, show_all\n\n# [Parameters]\n# Canadian Flags have a 2:1 aspect ratio\nheight = 50\nwidth = 2 * height\nwave_amplitude = 3\n\n# [Code]\n\n\ndef surface(amplitude, u, v):\n \"\"\"Calculate the surface displacement of the flag at a given position\"\"\"\n return v * amplitude / 20 * cos(3.5 * pi * u) + amplitude / 10 * v * sin(\n 1.1 * pi * v\n )\n\n\n# Note that the surface to project on must be a little larger than the faces\n# being projected onto it to create valid projected faces\nthe_wind = Face.make_surface_from_array_of_points(\n [\n [\n Vector(\n width * (v * 1.1 / 40 - 0.05),\n height * (u * 1.2 / 40 - 0.1),\n height * surface(wave_amplitude, u / 40, v / 40) / 2,\n )\n for u in range(41)\n ]\n for v in range(41)\n ]\n)\nwith BuildSketch(Plane.XY.offset(10)) as west_field_builder:\n Rectangle(width / 4, height, align=(Align.MIN, Align.MIN))\nwest_field_planar = west_field_builder.sketch.faces()[0]\neast_field_planar = west_field_planar.mirror(Plane.YZ.offset(width / 2))\n\nwith BuildSketch(Plane((width / 2, 0, 10))) as center_field_builder:\n Rectangle(width / 2, height, align=(Align.CENTER, Align.MIN))\n with BuildLine() as outline:\n l1 = Polyline((0.0000, 0.0771), (0.0187, 0.0771), (0.0094, 0.2569))\n l2 = Polyline((0.0325, 0.2773), (0.2115, 0.2458), (0.1873, 0.3125))\n RadiusArc(l1 @ 1, l2 @ 0, 0.0271)\n l3 = Polyline((0.1915, 0.3277), (0.3875, 0.4865), (0.3433, 0.5071))\n TangentArc(l2 @ 1, l3 @ 0, tangent=l2 % 1)\n l4 = Polyline((0.3362, 0.5235), (0.375, 0.6427), (0.2621, 0.6188))\n SagittaArc(l3 @ 1, l4 @ 0, 0.003)\n l5 = Polyline((0.2469, 0.6267), (0.225, 0.6781), (0.1369, 0.5835))\n ThreePointArc(l4 @ 1, (l4 @ 1 + l5 @ 0) * 0.5 + Vector(-0.002, -0.002), l5 @ 0)\n l6 = Polyline((0.1138, 0.5954), (0.1562, 0.8146), (0.0881, 0.7752))\n Spline(\n l5 @ 1,\n l6 @ 0,\n tangents=(l5 % 1, l6 % 0),\n tangent_scalars=(2, 2),\n )\n l7 = Line((0.0692, 0.7808), (0.0000, 0.9167))\n TangentArc(l6 @ 1, l7 @ 0, tangent=l6 % 1)\n mirror(about=Plane.YZ)\n scale(by=height)\n maple_leaf_planar = make_face(mode=Mode.SUBTRACT).face()\n\nmaple_leaf_planar.position += (width / 2, 0, 10) # Created on local Plane.XY\ncenter_field_planar = center_field_builder.sketch.faces()[0]\n\nwest_field = west_field_planar.project_to_shape(the_wind, (0, 0, -1))[0]\nwest_field.color = Color(\"red\")\neast_field = east_field_planar.project_to_shape(the_wind, (0, 0, -1))[0]\neast_field.color = Color(\"red\")\ncenter_field = center_field_planar.project_to_shape(the_wind, (0, 0, -1))[0]\ncenter_field.color = Color(\"white\")\nmaple_leaf = maple_leaf_planar.project_to_shape(the_wind, (0, 0, -1))[0]\nmaple_leaf.color = Color(\"red\")\n\ncanadian_flag = Compound(children=[west_field, east_field, center_field, maple_leaf])\nshow(Rot(90, 0, 0) * canadian_flag)\n# [End]\n" + }, + { + "id": "examples/canadian_flag_algebra", + "source": "examples/canadian_flag_algebra.py", + "kind": "example", + "code": "# [Imports]\nfrom math import sin, cos, pi\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n# [Parameters]\n# Canadian Flags have a 2:1 aspect ratio\nheight = 50\nwidth = 2 * height\nwave_amplitude = 3\n\n\n# [Code]\ndef surface(amplitude, u, v):\n \"\"\"Calculate the surface displacement of the flag at a given position\"\"\"\n return v * amplitude / 20 * cos(3.5 * pi * u) + amplitude / 10 * v * sin(\n 1.1 * pi * v\n )\n\n\n# Note that the surface to project on must be a little larger than the faces\n# being projected onto it to create valid projected faces\nthe_wind = Face.make_surface_from_array_of_points(\n [\n [\n Vector(\n width * (v * 1.1 / 40 - 0.05),\n height * (u * 1.2 / 40 - 0.1),\n height * surface(wave_amplitude, u / 40, v / 40) / 2,\n )\n for u in range(41)\n ]\n for v in range(41)\n ]\n)\n\nfield_planar = Plane.XY.offset(10) * Rectangle(width / 4, height, align=Align.MIN)\nwest_field_planar = field_planar.faces()[0]\neast_field_planar = mirror(west_field_planar, Plane.YZ.offset(width / 2))\n\nl1 = Polyline((0.0000, 0.0771), (0.0187, 0.0771), (0.0094, 0.2569))\nl2 = Polyline((0.0325, 0.2773), (0.2115, 0.2458), (0.1873, 0.3125))\nr1 = RadiusArc(l1 @ 1, l2 @ 0, 0.0271)\nl3 = Polyline((0.1915, 0.3277), (0.3875, 0.4865), (0.3433, 0.5071))\nr2 = TangentArc(l2 @ 1, l3 @ 0, tangent=l2 % 1)\nl4 = Polyline((0.3362, 0.5235), (0.375, 0.6427), (0.2621, 0.6188))\nr3 = SagittaArc(l3 @ 1, l4 @ 0, 0.003)\nl5 = Polyline((0.2469, 0.6267), (0.225, 0.6781), (0.1369, 0.5835))\nr4 = ThreePointArc(l4 @ 1, (l4 @ 1 + l5 @ 0) * 0.5 + Vector(-0.002, -0.002), l5 @ 0)\nl6 = Polyline((0.1138, 0.5954), (0.1562, 0.8146), (0.0881, 0.7752))\ns = Spline(\n l5 @ 1,\n l6 @ 0,\n tangents=(l5 % 1, l6 % 0),\n tangent_scalars=(2, 2),\n)\nl7 = Line((0.0692, 0.7808), (0.0000, 0.9167))\nr5 = TangentArc(l6 @ 1, l7 @ 0, tangent=l6 % 1)\n\noutline = l1 + [l2, r1, l3, r2, l4, r3, l5, r4, l6, s, l7, r5]\noutline += mirror(outline, Plane.YZ)\n\nmaple_leaf_planar = make_face(outline)\n\ncenter_field_planar = (\n Rectangle(1, 1, align=(Align.CENTER, Align.MIN)) - maple_leaf_planar\n)\n\n\ndef scale_move(obj):\n return Plane((width / 2, 0, 10)) * scale(obj, height)\n\n\ndef project(obj):\n return obj.faces()[0].project_to_shape(the_wind, (0, 0, -1))[0]\n\n\nmaple_leaf_planar = scale_move(maple_leaf_planar)\ncenter_field_planar = scale_move(center_field_planar)\n\nwest_field = project(west_field_planar)\nwest_field.color = Color(\"red\")\neast_field = project(east_field_planar)\neast_field.color = Color(\"red\")\ncenter_field = project(center_field_planar)\ncenter_field.color = Color(\"white\")\nmaple_leaf = project(maple_leaf_planar)\nmaple_leaf.color = Color(\"red\")\n\ncanadian_flag = Compound(children=[west_field, east_field, center_field, maple_leaf])\nshow(Rot(90, 0, 0) * canadian_flag)\n# [End]\n" + }, + { + "id": "examples/cast_bearing_unit", + "source": "examples/cast_bearing_unit.py", + "kind": "example", + "code": "# [Code]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nA, A1, Db2, H, J = 26, 11, 57, 98.5, 76.5\nwith BuildPart() as oval_flanged_bearing_unit:\n with BuildSketch() as plan:\n housing = Circle(Db2 / 2)\n with GridLocations(J, 0, 2, 1) as bolt_centers:\n Circle((H - J) / 2)\n make_hull()\n extrude(amount=A1)\n extrude(housing, amount=A)\n drafted_faces = oval_flanged_bearing_unit.faces().filter_by(Axis.Z, reverse=True)\n draft(drafted_faces, Plane.XY, 4)\n fillet(oval_flanged_bearing_unit.edges(), 1)\n with Locations(oval_flanged_bearing_unit.faces().sort_by(Axis.Z)[-1]):\n CounterBoreHole(14 / 2, 47 / 2, 14)\n with Locations(*bolt_centers):\n Hole(5)\n\noval_flanged_bearing_unit.part.color = Color(0x4C6377)\n\nshow(oval_flanged_bearing_unit)\n# [End]\n" + }, + { + "id": "examples/circuit_board", + "source": "examples/circuit_board.py", + "kind": "example", + "code": "# [Imports]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n# [Parameters]\npcb_length = 70 * MM\npcb_width = 30 * MM\npcb_height = 3 * MM\n\n# [Code]\nwith BuildPart() as pcb:\n with BuildSketch():\n Rectangle(pcb_length, pcb_width)\n\n for i in range(65 // 5):\n x = i * 5 - 30\n with Locations((x, -15), (x, -10), (x, 10), (x, 15)):\n Circle(1, mode=Mode.SUBTRACT)\n for i in range(30 // 5 - 1):\n y = i * 5 - 10\n with Locations((30, y), (35, y)):\n Circle(1, mode=Mode.SUBTRACT)\n with GridLocations(60, 20, 2, 2):\n Circle(2, mode=Mode.SUBTRACT)\n extrude(amount=pcb_height)\n\nshow_object(pcb.part.wrapped)\n# [End]" + }, + { + "id": "examples/circuit_board_algebra", + "source": "examples/circuit_board_algebra.py", + "kind": "example", + "code": "# [Imports]\nfrom itertools import product\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n# [Parameters]\npcb_length = 70 * MM\npcb_width = 30 * MM\npcb_height = 3 * MM\n\n# [Code]\nx_coords = product(range(65 // 5), (-15, -10, 10, 15))\ny_coords = product((30, 35), range(30 // 5 - 1))\n\npcb = Rectangle(pcb_length, pcb_width)\npcb -= [Pos(i * 5 - 30, y) * Circle(1) for i, y in x_coords]\npcb -= [Pos(x, i * 5 - 10) * Circle(1) for x, i in y_coords]\npcb -= [loc * Circle(2) for loc in GridLocations(60, 20, 2, 2)]\n\npcb = extrude(pcb, pcb_height)\n\nshow(pcb)\n# [End]" + }, + { + "id": "examples/clock", + "source": "examples/clock.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nclock_radius = 10\nwith BuildSketch() as minute_indicator:\n with BuildLine() as outline:\n l1 = CenterArc((0, 0), clock_radius * 0.975, 0.75, 4.5)\n l2 = CenterArc((0, 0), clock_radius * 0.925, 0.75, 4.5)\n Line(l1 @ 0, l2 @ 0)\n Line(l1 @ 1, l2 @ 1)\n make_face()\n fillet(minute_indicator.vertices(), radius=clock_radius * 0.01)\n\nwith BuildSketch() as clock_face:\n Circle(clock_radius)\n with PolarLocations(0, 60):\n add(minute_indicator.sketch, mode=Mode.SUBTRACT)\n with PolarLocations(clock_radius * 0.875, 12):\n SlotOverall(clock_radius * 0.05, clock_radius * 0.025, mode=Mode.SUBTRACT)\n for hour in range(1, 13):\n with PolarLocations(clock_radius * 0.75, 1, -hour * 30 + 90, 360, rotate=False):\n Text(\n str(hour),\n font_size=clock_radius * 0.175,\n font_style=FontStyle.BOLD,\n mode=Mode.SUBTRACT,\n )\n\nshow(clock_face)\n# [End]\n" + }, + { + "id": "examples/clock_algebra", + "source": "examples/clock_algebra.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nclock_radius = 10\n\nl1 = CenterArc((0, 0), clock_radius * 0.975, 0.75, 4.5)\nl2 = CenterArc((0, 0), clock_radius * 0.925, 0.75, 4.5)\nl3 = Line(l1 @ 0, l2 @ 0)\nl4 = Line(l1 @ 1, l2 @ 1)\nminute_indicator = make_face([l1, l3, l2, l4])\nminute_indicator = fillet(minute_indicator.vertices(), radius=clock_radius * 0.01)\n\nclock_face = Circle(clock_radius)\nclock_face -= PolarLocations(0, 60) * minute_indicator\nclock_face -= PolarLocations(clock_radius * 0.875, 12) * SlotOverall(\n clock_radius * 0.05, clock_radius * 0.025\n)\n\nclock_face -= [\n loc\n * Text(\n str(hour + 1),\n font_size=clock_radius * 0.175,\n font_style=FontStyle.BOLD,\n align=Align.CENTER,\n )\n for hour, loc in enumerate(\n PolarLocations(clock_radius * 0.75, 12, 60, -360, rotate=False)\n )\n]\n\nshow(clock_face)\n# [End]\n" + }, + { + "id": "examples/custom_sketch_objects", + "source": "examples/custom_sketch_objects.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n\nclass Club(BaseSketchObject):\n \"\"\"Sketch Object: Club\n\n The club suit symbol from a playing card.\n\n Args:\n height (float): size along the Y-axis\n rotation (float, optional): angle from X-axis. Defaults to 0.\n align (tuple[Align, Align], optional): align min, center, or max of object.\n Defaults to (Align.CENTER, Align.CENTER).\n mode (Mode, optional): combination mode. Defaults to Mode.ADD.\n \"\"\"\n\n def __init__(\n self,\n height: float,\n rotation: float = 0,\n align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),\n mode: Mode = Mode.ADD,\n ):\n # Create the club shape\n # Note: The workplane and mode must be set here to avoid interactions with\n # builders in difference scopes.\n with BuildSketch(Plane.XY, mode=Mode.PRIVATE) as club:\n with BuildLine():\n l0 = Line((0, -188), (76, -188))\n b0 = Bezier(l0 @ 1, (61, -185), (33, -173), (17, -81))\n b1 = Bezier(b0 @ 1, (49, -128), (146, -145), (167, -67))\n b2 = Bezier(b1 @ 1, (187, 9), (94, 52), (32, 18))\n b3 = Bezier(b2 @ 1, (92, 57), (113, 188), (0, 188))\n mirror(about=Plane.YZ)\n make_face()\n scale(by=height / club.sketch.bounding_box().size.Y)\n\n # Pass the shape to the BaseSketchObject class to create a new Club object\n super().__init__(obj=club.sketch, rotation=rotation, align=align, mode=mode)\n\n\nclass Spade(BaseSketchObject):\n def __init__(\n self,\n height: float,\n rotation: float = 0,\n align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),\n mode: Mode = Mode.ADD,\n ):\n with BuildSketch(Plane.XY, mode=Mode.PRIVATE) as spade:\n with BuildLine():\n b0 = Bezier((0, 198), (6, 190), (41, 127), (112, 61))\n b1 = Bezier(b0 @ 1, (242, -72), (114, -168), (11, -105))\n b2 = Bezier(b1 @ 1, (31, -174), (42, -179), (53, -198))\n l0 = Line(b2 @ 1, (0, -198))\n mirror(about=Plane.YZ)\n make_face()\n scale(by=height / spade.sketch.bounding_box().size.Y)\n super().__init__(obj=spade.sketch, rotation=rotation, align=align, mode=mode)\n\n\nclass Heart(BaseSketchObject):\n def __init__(\n self,\n height: float,\n rotation: float = 0,\n align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),\n mode: Mode = Mode.ADD,\n ):\n with BuildSketch(Plane.XY, mode=Mode.PRIVATE) as heart:\n with BuildLine():\n b1 = Bezier((0, 146), (20, 169), (67, 198), (97, 198))\n b2 = Bezier(b1 @ 1, (125, 198), (151, 186), (168, 167))\n b3 = Bezier(b2 @ 1, (197, 133), (194, 88), (158, 31))\n b4 = Bezier(b3 @ 1, (126, -13), (94, -48), (62, -95))\n b5 = Bezier(b4 @ 1, (40, -128), (0, -198))\n mirror(about=Plane.YZ)\n make_face()\n scale(by=height / heart.sketch.bounding_box().size.Y)\n super().__init__(obj=heart.sketch, rotation=rotation, align=align, mode=mode)\n\n\nclass Diamond(BaseSketchObject):\n def __init__(\n self,\n height: float,\n rotation: float = 0,\n align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),\n mode: Mode = Mode.ADD,\n ):\n with BuildSketch(Plane.XY, mode=Mode.PRIVATE) as diamond:\n with BuildLine():\n Bezier((135, 0), (94, 69), (47, 134), (0, 198))\n mirror(about=Plane.XZ)\n mirror(about=Plane.YZ)\n make_face()\n scale(by=height / diamond.sketch.bounding_box().size.Y)\n super().__init__(obj=diamond.sketch, rotation=rotation, align=align, mode=mode)\n\n\n# The inside of the box fits 2.5x3.5\" playing card deck with a small gap\npocket_w = 2.5 * IN + 2 * MM\npocket_l = 3.5 * IN + 2 * MM\npocket_t = 0.5 * IN + 2 * MM\nwall_t = 3 * MM # Wall thickness\nbottom_t = wall_t / 2 # Top and bottom thickness\nlid_gap = 0.5 * MM # Spacing between base and lid\nlip_t = wall_t / 2 - lid_gap / 2 # Lip thickness\n\n\nwith BuildPart() as box_builder:\n with BuildSketch() as box_plan:\n RectangleRounded(pocket_w + 2 * wall_t, pocket_l + 2 * wall_t, pocket_w / 15)\n extrude(amount=bottom_t + pocket_t / 2)\n base_top = box_builder.faces().sort_by(Axis.Z)[-1]\n with BuildSketch(base_top) as walls:\n offset(box_plan.sketch, amount=-lip_t, mode=Mode.ADD)\n extrude(amount=pocket_t / 2)\n with BuildSketch(Plane.XY.offset(wall_t / 2)):\n offset(box_plan.sketch, amount=-wall_t, mode=Mode.ADD)\n extrude(amount=pocket_t, mode=Mode.SUBTRACT)\nbox = box_builder.part\n\nwith BuildPart() as lid_builder:\n add(box_plan.sketch)\n extrude(amount=pocket_t / 2 + bottom_t)\n with BuildSketch() as pocket:\n offset(box_plan.sketch, amount=-(wall_t - lip_t), mode=Mode.ADD)\n extrude(amount=pocket_t / 2, mode=Mode.SUBTRACT)\n\n with BuildSketch(lid_builder.faces().sort_by(Axis.Z)[-1]) as suits:\n with Locations((-0.3 * pocket_w, 0.3 * pocket_l)):\n Heart(pocket_l / 5)\n with Locations((-0.3 * pocket_w, -0.3 * pocket_l)):\n Diamond(pocket_l / 5)\n with Locations((0.3 * pocket_w, 0.3 * pocket_l)):\n Spade(pocket_l / 5)\n with Locations((0.3 * pocket_w, -0.3 * pocket_l)):\n Club(pocket_l / 5)\n extrude(amount=-wall_t, mode=Mode.SUBTRACT)\nlid = lid_builder.part.moved(Location((0, 0, (wall_t + pocket_t) / 2)))\n\nshow(box, lid, names=[\"box\", \"lid\"], alphas=[1.0, 0.6])\n" + }, + { + "id": "examples/custom_sketch_objects_algebra", + "source": "examples/custom_sketch_objects_algebra.py", + "kind": "example", + "code": "from typing import Union\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n\nclass Club(Sketch):\n def __init__(\n self,\n height: float,\n align: Union[Align, tuple[Align, Align]] = None,\n ):\n l0 = Line((0, -188), (76, -188))\n b0 = Bezier(l0 @ 1, (61, -185), (33, -173), (17, -81))\n b1 = Bezier(b0 @ 1, (49, -128), (146, -145), (167, -67))\n b2 = Bezier(b1 @ 1, (187, 9), (94, 52), (32, 18))\n b3 = Bezier(b2 @ 1, (92, 57), (113, 188), (0, 188))\n club = l0 + b0 + b1 + b2 + b3\n club += mirror(club, Plane.YZ)\n club = make_face(club)\n club = scale(club, height / club.bounding_box().size.Y)\n\n super().__init__(club.wrapped)\n # self._align(align)\n\n\nclass Spade(Sketch):\n def __init__(\n self,\n height: float,\n align: Union[Align, tuple[Align, Align]] = None,\n ):\n b0 = Bezier((0, 198), (6, 190), (41, 127), (112, 61))\n b1 = Bezier(b0 @ 1, (242, -72), (114, -168), (11, -105))\n b2 = Bezier(b1 @ 1, (31, -174), (42, -179), (53, -198))\n l0 = Line(b2 @ 1, (0, -198))\n spade = b0 + b1 + b2 + l0\n spade += mirror(spade, Plane.YZ)\n spade = make_face(spade)\n spade = scale(spade, height / spade.bounding_box().size.Y)\n\n super().__init__(spade.wrapped)\n # self._align(align)\n\n\nclass Heart(Sketch):\n def __init__(\n self,\n height: float,\n align: Union[Align, tuple[Align, Align]] = None,\n ):\n b1 = Bezier((0, 146), (20, 169), (67, 198), (97, 198))\n b2 = Bezier(b1 @ 1, (125, 198), (151, 186), (168, 167))\n b3 = Bezier(b2 @ 1, (197, 133), (194, 88), (158, 31))\n b4 = Bezier(b3 @ 1, (126, -13), (94, -48), (62, -95))\n b5 = Bezier(b4 @ 1, (40, -128), (0, -198))\n heart = b1 + b2 + b3 + b4 + b5\n heart += mirror(heart, Plane.YZ)\n heart = make_face(heart)\n heart = scale(heart, height / heart.bounding_box().size.Y)\n\n super().__init__(heart.wrapped)\n # self._align(align)\n\n\nclass Diamond(Sketch):\n def __init__(\n self,\n height: float,\n align: Union[Align, tuple[Align, Align]] = None,\n ):\n diamond = Bezier((135, 0), (94, 69), (47, 134), (0, 198))\n diamond += mirror(diamond, Plane.XZ)\n diamond += mirror(diamond, Plane.YZ)\n diamond = make_face(diamond)\n diamond = scale(diamond, height / diamond.bounding_box().size.Y)\n\n super().__init__(diamond.wrapped)\n # self._align(align)\n\n\n# The inside of the box fits 2.5x3.5\" playing card deck with a small gap\npocket_w = 2.5 * IN + 2 * MM\npocket_l = 3.5 * IN + 2 * MM\npocket_t = 0.5 * IN + 2 * MM\nwall_t = 3 * MM # Wall thickness\nbottom_t = wall_t / 2 # Top and bottom thickness\nlid_gap = 0.5 * MM # Spacing between base and lid\nlip_t = wall_t / 2 - lid_gap / 2 # Lip thickness\n\n\nbox_plan = RectangleRounded(pocket_w + 2 * wall_t, pocket_l + 2 * wall_t, pocket_w / 15)\nbox = extrude(box_plan, amount=bottom_t + pocket_t / 2)\nbase_top = box.faces().sort_by(Axis.Z).last\nwalls = Plane(base_top) * offset(box_plan, -lip_t)\nbox += extrude(walls, amount=pocket_t / 2)\ntop = Plane.XY.offset(wall_t / 2) * offset(box_plan, -wall_t)\nbox -= extrude(top, amount=pocket_t)\n\n\npocket = extrude(box_plan, amount=pocket_t / 2 + bottom_t)\nlid_bottom = offset(box_plan, -(wall_t - lip_t))\npocket -= extrude(lid_bottom, amount=pocket_t / 2)\npocket = Pos(0, 0, (wall_t + pocket_t) / 2) * pocket\n\nplane = Plane(pocket.faces().sort_by().last)\nsuites = Pos(-0.3 * pocket_w, 0.3 * pocket_l) * Heart(pocket_l / 5)\nsuites += Pos(-0.3 * pocket_w, -0.3 * pocket_l) * Diamond(pocket_l / 5)\nsuites += Pos(0.3 * pocket_w, 0.3 * pocket_l) * Spade(pocket_l / 5)\nsuites += Pos(0.3 * pocket_w, -0.3 * pocket_l) * Club(pocket_l / 5)\nsuites = plane * suites\n\nlid = pocket - extrude(suites, dir=(0, 0, 1), amount=-wall_t)\n\nshow(box, lid, names=[\"box\", \"lid\"], alphas=[1.0, 0.6])\n" + }, + { + "id": "examples/din_rail", + "source": "examples/din_rail.py", + "kind": "example", + "code": "import logging\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nlogging.basicConfig(\n filename=\"din_rail.log\",\n level=logging.INFO,\n format=\"%(name)s-%(levelname)s %(asctime)s - [%(filename)s:%(lineno)s - %(funcName)20s() ] - %(message)s\",\n)\nlogging.info(\"Starting to create din rail\")\n\n# 35x7.5mm DIN Rail Dimensions\noverall_width, top_width, height, thickness, fillet_radius = 35, 27, 7.5, 1, 0.8\nrail_length = 1000\nslot_width, slot_length, slot_pitch = 6.2, 15, 25\n\nwith BuildPart() as rail:\n with BuildSketch(Plane.XZ) as din:\n Rectangle(overall_width, thickness, align=(Align.CENTER, Align.MIN))\n Rectangle(top_width, height, align=(Align.CENTER, Align.MIN))\n Rectangle(\n top_width - 2 * thickness,\n height - thickness,\n align=(Align.CENTER, Align.MIN),\n mode=Mode.SUBTRACT,\n )\n inside_vertices = (\n din.vertices()\n .filter_by_position(Axis.Y, 0.0, height, inclusive=(False, False))\n .filter_by_position(\n Axis.X,\n -overall_width / 2,\n overall_width / 2,\n inclusive=(False, False),\n )\n )\n fillet(inside_vertices, radius=fillet_radius)\n outside_vertices = filter(\n lambda v: (v.Y == 0.0 or v.Y == height)\n and -overall_width / 2 < v.X < overall_width / 2,\n din.vertices(),\n )\n fillet(outside_vertices, radius=fillet_radius + thickness)\n extrude(amount=rail_length / 2, both=True)\n\n with BuildSketch(Plane.XY) as slots:\n with GridLocations(\n 0,\n slot_pitch,\n 1,\n rail_length // slot_pitch - 1,\n ):\n SlotOverall(slot_length, slot_width, rotation=90)\n extrude(amount=height, mode=Mode.SUBTRACT)\n\n# assert abs(rail.part.volume - 42462.863388694714) < 1e-3\nshow(rail, names=[\"rail\"])\n" + }, + { + "id": "examples/din_rail_algebra", + "source": "examples/din_rail_algebra.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n# 35x7.5mm DIN Rail Dimensions\noverall_width, top_width, height, thickness, fillet_radius = 35, 27, 7.5, 1, 0.8\nrail_length = 1000\nslot_width, slot_length, slot_pitch = 6.2, 15, 25\n\ndin = Rectangle(overall_width, thickness, align=(Align.CENTER, Align.MIN))\ndin += Rectangle(top_width, height, align=(Align.CENTER, Align.MIN))\ndin -= Rectangle(\n top_width - 2 * thickness,\n height - thickness,\n align=(Align.CENTER, Align.MIN),\n)\n\ninside_vertices = (\n din.vertices()\n .filter_by_position(Axis.Y, 0.0, height, inclusive=(False, False))\n .filter_by_position(\n Axis.X,\n -overall_width / 2,\n overall_width / 2,\n inclusive=(False, False),\n )\n)\n\ndin = fillet(inside_vertices, radius=fillet_radius)\n\noutside_vertices = filter(\n lambda v: (v.Y == 0.0 or v.Y == height)\n and -overall_width / 2 < v.X < overall_width / 2,\n din.vertices(),\n)\ndin = fillet(outside_vertices, radius=fillet_radius + thickness)\n\nrail = extrude(din, rail_length)\n\nplane = Plane(rail.faces().sort_by(Axis.Y).last)\n\nslot_faces = [\n (plane * loc * Rot(0, 0, 90) * SlotOverall(slot_length, slot_width)).faces()[0]\n for loc in GridLocations(0, slot_pitch, 1, rail_length // slot_pitch - 1)\n]\n\nrail -= extrude(slot_faces, -height)\nrail = Plane.XZ * rail\n\nshow(rail, names=[\"rail\"])\n" + }, + { + "id": "examples/dual_color_3mf", + "source": "examples/dual_color_3mf.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n\n# Create a simple tile pattern\nwith BuildSketch() as inset_pattern:\n with BuildLine() as bl:\n Polyline((9, 9), (1, 5), (-0.5, 0))\n offset(amount=1, side=Side.LEFT)\n make_face()\n split(bisect_by=Plane(origin=(0, 0, 0), z_dir=(-1, 1, 0)))\n mirror(about=Plane(origin=(0, 0, 0), z_dir=(-1, 1, 0)))\n mirror(about=Plane.YZ)\n mirror(about=Plane.XZ)\n\n# Create the background field object for the tile\nwith BuildPart() as outset_builder:\n with BuildSketch():\n Rectangle(20, 20)\n add(inset_pattern.sketch, mode=Mode.SUBTRACT)\n extrude(amount=1)\n\n# Create the inset object for the tile\nwith BuildPart() as inset_builder:\n add(inset_pattern.sketch)\n extrude(amount=1)\n\n# Assign colors to the tile parts\noutset = outset_builder.part\noutset.color = Color(0.137, 0.306, 0.439) # Tealish\ninset = inset_builder.part\ninset.color = Color(0.980, 0.973, 0.749) # Goldish\n\nshow(inset, outset)\n\n# Export the tile with the units as CM\nexporter = Mesher(unit=Unit.CM)\nexporter.add_shape([inset, outset])\nexporter.write(\"dual_color.3mf\")\n" + }, + { + "id": "examples/extrude", + "source": "examples/extrude.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n# Extrude pending face by amount\nwith BuildPart() as simple:\n with BuildSketch():\n Text(\"O\", font_size=10)\n extrude(amount=5)\n\n# Extrude pending face in both directions by amount\nwith BuildPart() as both:\n with BuildSketch():\n Text(\"O\", font_size=10)\n extrude(amount=5, both=True)\n\n# Extrude multiple pending faces on multiple faces\nwith BuildPart() as multiple:\n Box(10, 10, 10)\n with BuildSketch(*multiple.faces()):\n with GridLocations(5, 5, 2, 2):\n Text(\"\u03a9\", font_size=3)\n extrude(amount=1)\n\n# Non-planar surface\nwith BuildPart() as non_planar:\n Cylinder(10, 20, rotation=(90, 0, 0), align=(Align.CENTER, Align.MIN, Align.CENTER))\n Box(10, 10, 10, align=(Align.CENTER, Align.CENTER, Align.MIN), mode=Mode.INTERSECT)\n extrude(\n non_planar.part.faces().sort_by(Axis.Z)[0],\n amount=2,\n dir=(0, 0, 1),\n mode=Mode.REPLACE,\n )\n\n\nrad, rev = 3, 25\n\n# Extrude last\nwith BuildPart() as ex26:\n with BuildSketch() as ex26_sk:\n with Locations((0, rev)):\n Circle(rad)\n revolve(axis=Axis.X, revolution_arc=180)\n with BuildSketch() as ex26_sk2:\n Rectangle(rad, rev)\n ex26_target = ex26.part\n extrude(until=Until.LAST, clean=False, mode=Mode.REPLACE)\n\n# Extrude next\nwith BuildPart() as ex27:\n with BuildSketch():\n with Locations((0, rev)):\n Circle(rad)\n revolve(axis=Axis.X, revolution_arc=90)\n with BuildSketch(Plane.XZ):\n with Locations((0, rev)):\n Circle(rad)\n revolve(axis=Axis.X, revolution_arc=150)\n with BuildSketch(Plane.XY.offset(-60)):\n Rectangle(rad, rev + 25)\n extrusion27 = extrude(until=Until.NEXT, mode=Mode.ADD)\n\n# Extrude next both\n# with BuildPart() as ex28:\n# Torus(25, 5, rotation=(0, 90, 0))\n# with BuildSketch():\n# Rectangle(rad, rev)\n# extrusion28 = extrude(until=Until.NEXT, both=True)\n\nshow_object(simple.part.translate((-15, 0, 0)).wrapped, name=\"simple pending extrude\")\nshow_object(both.part.translate((20, 10, 0)).wrapped, name=\"simple both\")\nshow_object(\n multiple.part.translate((0, -20, 0)).wrapped, name=\"multiple pending extrude\"\n)\nshow_object(non_planar.part.translate((20, -10, 0)).wrapped, name=\"non planar\")\nshow_object(\n ex26_target.translate((-40, 0, 0)).wrapped,\n name=\"extrude until last target\",\n options={\"alpha\": 0.8},\n)\nshow_object(\n ex26.part.translate((-40, 0, 0)).wrapped,\n name=\"extrude until last\",\n)\nshow_object(\n ex27.part.rotate(Axis.Z, 90).translate((0, 50, 0)).wrapped,\n name=\"extrude until next target\",\n options={\"alpha\": 0.8},\n)\nshow_object(\n extrusion27.rotate(Axis.Z, 90).translate((0, 50, 0)).wrapped,\n name=\"extrude until next\",\n)\n# show_object(\n# ex28.part.rotate(Axis.Z, -90).translate((0, -50, 0)).wrapped,\n# name=\"extrude until next both target\",\n# options={\"alpha\": 0.8},\n# )\n# show_object(\n# extrusion28.rotate(Axis.Z, -90).translate((0, -50, 0)).wrapped,\n# name=\"extrude until next both\",\n# )\n" + }, + { + "id": "examples/extrude_algebra", + "source": "examples/extrude_algebra.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\n# Extrude pending face by amount\nsimple = extrude(Text(\"O\", font_size=10), amount=5)\n\n# Extrude pending face in both directions by amount\nboth = extrude(Text(\"O\", font_size=10), amount=5, both=True)\n\n# Extrude multiple pending faces on multiple faces\nmultiple = Box(10, 10, 10)\nfaces = [\n Plane(face) * loc * Text(\"\u03a9\", font_size=3)\n for face in multiple.faces()\n for loc in GridLocations(5, 5, 2, 2)\n]\nmultiple += [extrude(face, amount=1) for face in faces]\n\n# Non-planar surface\nnon_planar = Rot(90, 0, 0) * Cylinder(\n 10, 20, align=(Align.CENTER, Align.MIN, Align.CENTER)\n)\nnon_planar &= Box(10, 10, 10, align=(Align.CENTER, Align.CENTER, Align.MIN))\nnon_planar = extrude(non_planar.faces().sort_by(Axis.Z).first, amount=2, dir=(0, 0, 1))\nrad, rev = 3, 25\n\n# Extrude last\ncircle = Pos(0, rev) * Circle(rad)\nex26_target = revolve(circle, Axis.X, revolution_arc=180)\nex26_target = ex26_target\n\nrect = Rectangle(rad, rev)\n\nex26 = extrude(rect, until=Until.LAST, target=ex26_target, clean=False)\n\n# Extrude next\ncircle = Pos(0, rev) * Circle(rad)\nex27 = revolve(circle, Axis.X, revolution_arc=90)\n\ncircle2 = Plane.XZ * Pos(0, rev) * Circle(rad)\nex27 += revolve(circle2, Axis.X, revolution_arc=150)\nrect = Plane.XY.offset(-60) * Rectangle(rad, rev + 25)\nextrusion27 = extrude(rect, until=Until.NEXT, target=ex27, mode=Mode.ADD)\n\n\n# Extrude next both\n# ex28 = Rot(0, 90, 0) * Torus(25, 5)\n# rect = Rectangle(rad, rev)\n# extrusion28 = extrude(rect, until=Until.NEXT, target=ex28, both=True, clean=False)\n\nshow_object(simple.translate((-15, 0, 0)), name=\"simple pending extrude\")\nshow_object(both.translate((20, 10, 0)), name=\"simple both\")\nshow_object(multiple.translate((0, -20, 0)), name=\"multiple pending extrude\")\nshow_object(non_planar.translate((20, -10, 0)), name=\"non planar\")\nshow_object(\n ex26_target.translate((-40, 0, 0)),\n name=\"extrude until last target\",\n options={\"alpha\": 0.8},\n)\nshow_object(\n ex26.translate((-40, 0, 0)),\n name=\"extrude until last\",\n)\nshow_object(\n ex27.rotate(Axis.Z, 90).translate((0, 50, 0)),\n name=\"extrude until next target\",\n options={\"alpha\": 0.8},\n)\nshow_object(\n extrusion27.rotate(Axis.Z, 90).translate((0, 50, 0)),\n name=\"extrude until next\",\n)\n# show_object(\n# ex28.rotate(Axis.Z, -90).translate((0, -50, 0)),\n# name=\"extrude until next both target\",\n# options={\"alpha\": 0.8},\n# )\n# show_object(\n# extrusion28.rotate(Axis.Z, -90).translate((0, -50, 0)),\n# name=\"extrude until next both\",\n# )\n" + }, + { + "id": "examples/fast_grid_holes", + "source": "examples/fast_grid_holes.py", + "kind": "example", + "code": "# [Code]\nimport timeit\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nstart_time = timeit.default_timer()\n\n# Calculate the locations of 625 holes\nmajor_r = 10\nhole_locs = HexLocations(major_r, 25, 25)\n\n# Create wires for both the perimeter and all the holes\nface_perimeter = Rectangle(500, 600).wire()\nhex_hole = RegularPolygon(major_r - 1, 6, major_radius=True).wire()\nholes = hole_locs * hex_hole\n\n# Create a new Face from the perimeter and hole wires\ngrid_pattern = Face(face_perimeter, holes)\n\n# Extrude to a 3D part\ngrid = extrude(grid_pattern, 1)\n\nprint(f\"Time: {timeit.default_timer() - start_time:0.3f}s\")\nshow(grid)\n# [End]\n" + }, + { + "id": "examples/handle", + "source": "examples/handle.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\nsegment_count = 6\n\nwith BuildPart() as handle:\n # Create a path for the sweep along the handle - added to pending_edges\n with BuildLine() as handle_center_line:\n Spline(\n (-10, 0, 0),\n (0, 0, 5),\n (10, 0, 0),\n tangents=((0, 0, 1), (0, 0, -1)),\n tangent_scalars=(1.5, 1.5),\n )\n\n # Create the cross sections - added to pending_faces\n for i in range(segment_count + 1):\n with BuildSketch(handle_center_line.line ^ (i / segment_count)) as section:\n if i % segment_count == 0:\n Circle(1)\n else:\n Rectangle(1.25, 3)\n fillet(section.vertices(), radius=0.2)\n # Record the sections for display\n sections = handle.pending_faces\n\n # Create the handle by sweeping along the path\n sweep(multisection=True)\n\nassert abs(handle.part.volume - 94.77361455046953) < 1e-3\n\nshow_object(handle_center_line.line, name=\"handle_center_line\")\nfor i, section in enumerate(sections):\n show_object(section, name=\"section\" + str(i))\nshow_object(handle.part, name=\"handle\", options=dict(alpha=0.6))\n# [End]\n" + }, + { + "id": "examples/handle_algebra", + "source": "examples/handle_algebra.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\nsegment_count = 6\n\n# Create a path for the sweep along the handle - added to pending_edges\nhandle_center_line = Spline(\n (-10, 0, 0),\n (0, 0, 5),\n (10, 0, 0),\n tangents=((0, 0, 1), (0, 0, -1)),\n tangent_scalars=(1.5, 1.5),\n)\n\n# Create the cross sections - added to pending_faces\nsections = Sketch()\nfor i in range(segment_count + 1):\n location = handle_center_line ^ (i / segment_count)\n if i % segment_count == 0:\n circle = location * Circle(1)\n else:\n circle = location * Rectangle(1.25, 3)\n circle = fillet(circle.vertices(), radius=0.2)\n sections += circle\n\n# Create the handle by sweeping along the path\nhandle = sweep(sections, path=handle_center_line, multisection=True)\n\nshow_object(handle_center_line, name=\"handle_path\")\nfor i, circle in enumerate(sections):\n show_object(circle, name=\"section\" + str(i))\nshow_object(handle, name=\"handle\", options=dict(alpha=0.6))\n# [End]\n" + }, + { + "id": "examples/heat_exchanger", + "source": "examples/heat_exchanger.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nexchanger_diameter = 10 * CM\nexchanger_length = 30 * CM\nplate_thickness = 5 * MM\n# 149 tubes\ntube_diameter = 5 * MM\ntube_spacing = 2 * MM\ntube_wall_thickness = 0.5 * MM\ntube_extension = 3 * MM\nbundle_diameter = exchanger_diameter - 2 * tube_diameter\nfillet_radius = tube_spacing / 3\nassert tube_extension > fillet_radius\n\n# Build the heat exchanger\nwith BuildPart() as heat_exchanger:\n # Generate list of tube locations\n tube_locations = [\n l\n for l in HexLocations(\n radius=(tube_diameter + tube_spacing) / 2,\n x_count=exchanger_diameter // tube_diameter,\n y_count=exchanger_diameter // tube_diameter,\n )\n if l.position.length < bundle_diameter / 2\n ]\n tube_count = len(tube_locations)\n with BuildSketch() as tube_plan:\n with Locations(*tube_locations):\n Circle(radius=tube_diameter / 2)\n Circle(radius=tube_diameter / 2 - tube_wall_thickness, mode=Mode.SUBTRACT)\n extrude(amount=exchanger_length / 2)\n with BuildSketch(\n Plane(\n origin=(0, 0, exchanger_length / 2 - tube_extension - plate_thickness),\n z_dir=(0, 0, 1),\n )\n ) as plate_plan:\n Circle(radius=exchanger_diameter / 2)\n with Locations(*tube_locations):\n Circle(radius=tube_diameter / 2 - tube_wall_thickness, mode=Mode.SUBTRACT)\n extrude(amount=plate_thickness)\n half_volume_before_fillet = heat_exchanger.part.volume\n # Simulate welded tubes by adding a fillet to the outside radius of the tubes\n fillet(\n heat_exchanger.edges()\n .filter_by(GeomType.CIRCLE)\n .sort_by(SortBy.RADIUS)\n .sort_by(Axis.Z, reverse=True)[2 * tube_count : 3 * tube_count],\n radius=fillet_radius,\n )\n half_volume_after_fillet = heat_exchanger.part.volume\n mirror(about=Plane.XY)\n\nfillet_volume = 2 * (half_volume_after_fillet - half_volume_before_fillet)\nassert abs(fillet_volume - 469.88331045553787) < 1e-3\n\nshow(heat_exchanger)\n# [End]\n" + }, + { + "id": "examples/heat_exchanger_algebra", + "source": "examples/heat_exchanger_algebra.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nexchanger_diameter = 10 * CM\nexchanger_length = 30 * CM\nplate_thickness = 5 * MM\n# 149 tubes\ntube_diameter = 5 * MM\ntube_spacing = 2 * MM\ntube_wall_thickness = 0.5 * MM\ntube_extension = 3 * MM\nbundle_diameter = exchanger_diameter - 2 * tube_diameter\nfillet_radius = tube_spacing / 3\nassert tube_extension > fillet_radius\n\n# Build the heat exchanger\ntube_locations = [\n l\n for l in HexLocations(\n radius=(tube_diameter + tube_spacing) / 2,\n x_count=exchanger_diameter // tube_diameter,\n y_count=exchanger_diameter // tube_diameter,\n )\n if l.position.length < bundle_diameter / 2\n]\n\nring = Circle(tube_diameter / 2) - Circle(tube_diameter / 2 - tube_wall_thickness)\ntube_plan = Sketch() + tube_locations * ring\n\nheat_exchanger = extrude(tube_plan, exchanger_length / 2)\n\nplate_plane = Plane(\n origin=(0, 0, exchanger_length / 2 - tube_extension - plate_thickness),\n z_dir=(0, 0, 1),\n)\nplate = Circle(radius=exchanger_diameter / 2) - tube_locations * Circle(\n radius=tube_diameter / 2 - tube_wall_thickness\n)\n\nheat_exchanger += extrude(plate_plane * plate, plate_thickness)\nedges = (\n heat_exchanger.edges()\n .filter_by(GeomType.CIRCLE)\n .group_by(SortBy.RADIUS)[1]\n .group_by()[2]\n)\nhalf_volume_before_fillet = heat_exchanger.volume\nheat_exchanger = fillet(edges, radius=fillet_radius)\nhalf_volume_after_fillet = heat_exchanger.volume\nheat_exchanger += mirror(heat_exchanger, Plane.XY)\n\nfillet_volume = 2 * (half_volume_after_fillet - half_volume_before_fillet)\nassert abs(fillet_volume - 469.88331045553787) < 1e-3\n\nshow(heat_exchanger)\n# [End]\n" + }, + { + "id": "examples/holes", + "source": "examples/holes.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\n# Simple through hole\nwith BuildPart() as thru_hole:\n Cylinder(radius=3, height=2)\n Hole(radius=1)\n\n# Recessed counter bore hole (hole location @ (0,0,0))\nwith BuildPart() as recessed_counter_bore:\n with Locations((10, 0)):\n Cylinder(radius=3, height=2)\n CounterBoreHole(radius=1, counter_bore_radius=1.5, counter_bore_depth=0.5)\n\n# Recessed counter sink hole (hole location @ (0,0,0))\nwith BuildPart() as recessed_counter_sink:\n with Locations((0, 10)):\n Cylinder(radius=3, height=2)\n CounterSinkHole(radius=1, counter_sink_radius=1.5)\n\n# Flush counter sink hole (hole location @ (0,0,2))\nwith BuildPart() as flush_counter_sink:\n with Locations((10, 10)):\n Cylinder(radius=3, height=2)\n with Locations(\n (0, 0, flush_counter_sink.part.faces().sort_by(Axis.Z)[-1].center().Z)\n ):\n CounterSinkHole(radius=1, counter_sink_radius=1.5)\n\nshow_object(thru_hole.part.wrapped, name=\"though hole\")\nshow_object(recessed_counter_bore.part.wrapped, name=\"recessed counter bore\")\nshow_object(recessed_counter_sink.part.wrapped, name=\"recessed counter sink\")\nshow_object(flush_counter_sink.part.wrapped, name=\"flush counter sink\")\n" + }, + { + "id": "examples/holes_algebra", + "source": "examples/holes_algebra.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\nthru_hole = Cylinder(radius=3, height=2)\nthru_hole -= Hole(radius=1, depth=2)\n\n# Recessed counter bore hole (hole location = (0,0,0))\nrecessed_counter_bore = Cylinder(radius=3, height=2)\nrecessed_counter_bore -= CounterBoreHole(\n radius=1, depth=2, counter_bore_radius=1.5, counter_bore_depth=0.5\n)\n\n# Recessed counter sink hole (hole location = (0,0,0))\nrecessed_counter_sink = Cylinder(radius=3, height=2)\nrecessed_counter_sink -= CounterSinkHole(radius=1, depth=2, counter_sink_radius=1.5)\n\n# Flush counter sink hole (hole location = (0,0,2))\nflush_counter_sink = Cylinder(radius=3, height=2)\nplane = Plane(flush_counter_sink.faces().sort_by().last)\nflush_counter_sink -= plane * CounterSinkHole(\n radius=1, depth=2, counter_sink_radius=1.5\n)\n\nshow_object(thru_hole, name=\"though hole\")\nshow_object(Pos(10, 0) * recessed_counter_bore, name=\"recessed counter bore\")\nshow_object(Pos(0, 10) * recessed_counter_sink, name=\"recessed counter sink\")\nshow_object(Pos(10, 10) * flush_counter_sink, name=\"flush counter sink\")\n" + }, + { + "id": "examples/intersecting_chamfers", + "source": "examples/intersecting_chamfers.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nwith BuildPart() as blocks:\n with Locations((-1, -1, 0)):\n Box(1, 2, 1, align=(Align.CENTER, Align.MIN, Align.MIN))\n Box(1, 1, 2, align=(Align.CENTER, Align.MIN, Align.MIN))\n with Locations((1, -1, 0)):\n Box(1, 2, 1, align=(Align.CENTER, Align.MIN, Align.MIN))\n bottom_edges = blocks.edges().filter_by_position(\n Axis.Z, 0, 1, inclusive=(True, False)\n )\n chamfer(bottom_edges, length=0.1)\n top_edges = blocks.edges().filter_by_position(Axis.Z, 1, 2, inclusive=(False, True))\n chamfer(top_edges, length=0.1)\n\n\nshow(blocks)\n" + }, + { + "id": "examples/intersecting_chamfers_algebra", + "source": "examples/intersecting_chamfers_algebra.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nblocks = Pos(-1, -1, 0) * Box(1, 2, 1, align=(Align.CENTER, Align.MIN, Align.MIN))\nblocks += Box(1, 1, 2, align=(Align.CENTER, Align.MIN, Align.MIN))\nblocks += Pos(1, -1, 0) * Box(1, 2, 1, align=(Align.CENTER, Align.MIN, Align.MIN))\n\nbottom_edges = blocks.edges().filter_by_position(Axis.Z, 0, 1, inclusive=(True, False))\nblocks2 = chamfer(bottom_edges, length=0.1)\n\ntop_edges = blocks2.edges().filter_by_position(Axis.Z, 1, 2, inclusive=(False, True))\nblocks2 = chamfer(top_edges, length=0.1)\n\n\nshow(blocks2)\n" + }, + { + "id": "examples/intersecting_pipes", + "source": "examples/intersecting_pipes.py", + "kind": "example", + "code": "import logging\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n# logging.basicConfig(\n# filename=\"intersecting_pipes.log\",\n# level=logging.DEBUG,\n# format=\"%(name)s-%(levelname)5s %(asctime)s - [%(filename)s:%(lineno)s - %(funcName)20s() ] - %(message)s\",\n# )\n# logging.info(\"Starting pipes test\")\n\nwith BuildPart() as pipes:\n box = Box(10, 10, 10, rotation=(10, 20, 30))\n with BuildSketch(*box.faces()) as pipe:\n Circle(4)\n extrude(amount=-5, mode=Mode.SUBTRACT)\n with BuildSketch(*box.faces()) as pipe:\n Circle(4.5)\n Circle(4, mode=Mode.SUBTRACT)\n extrude(amount=10)\n fillet(pipes.edges(Select.LAST), 0.2)\n\nassert abs(pipes.part.volume - 1015.939005681509) < 1e-3\n\nshow(pipes, names=[\"intersecting pipes\"])\n" + }, + { + "id": "examples/joints", + "source": "examples/joints.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n\nclass JointBox(Solid):\n \"\"\"A filleted box with joints\n\n A box of the given dimensions with all of the edges filleted.\n\n Args:\n length (float): box length\n width (float): box width\n height (float): box height\n radius (float): edge radius\n taper (float): vertical taper in degrees\n \"\"\"\n\n def __init__(\n self,\n length: float,\n width: float,\n height: float,\n radius: float = 0.0,\n taper: float = 0.0,\n ):\n # Create the object\n with BuildPart() as obj:\n with BuildSketch():\n Rectangle(length, width)\n extrude(amount=height, taper=taper)\n if radius != 0.0:\n fillet(obj.part.edges(), radius=radius)\n Cylinder(width / 4, length, rotation=(0, 90, 0), mode=Mode.SUBTRACT)\n # Initialize the Solid class with the new OCCT object\n super().__init__(obj.part.wrapped)\n\n\n#\n# Base Object\n#\n# base = JointBox(10, 10, 10)\n# base = JointBox(10, 10, 10).locate(Location(Vector(1, 1, 1)))\n# base = JointBox(10, 10, 10).locate(Location(Vector(1, 1, 1), (1, 0, 0), 5))\nbase: JointBox = JointBox(10, 10, 10, taper=3).locate(\n Location(Vector(1, 1, 1), (1, 1, 1), 30)\n)\nbase_top_edges: ShapeList[Edge] = (\n base.edges().filter_by(Axis.X, tolerance=30).sort_by(Axis.Z)[-2:]\n)\n#\n# Rigid Joint\n#\nfixed_arm = JointBox(1, 1, 5, 0.2)\nj1 = RigidJoint(\"side\", base, Plane(base.faces().sort_by(Axis.X)[-1]).location)\nj2 = RigidJoint(\n \"top\", fixed_arm, (-Plane(fixed_arm.faces().sort_by(Axis.Z)[-1])).location\n)\nbase.joints[\"side\"].connect_to(fixed_arm.joints[\"top\"])\n# or\n# j1.connect_to(j2)\n\n#\n# Hinge\n#\nhinge_arm = JointBox(2, 1, 10, taper=1)\nswing_arm_hinge_edge: Edge = (\n hinge_arm.edges()\n .group_by(SortBy.LENGTH)[-1]\n .sort_by(Axis.X)[-2:]\n .sort_by(Axis.Y)[0]\n)\nswing_arm_hinge_axis = Axis(swing_arm_hinge_edge)\nbase_corner_edge = base.edges().sort_by(Axis((0, 0, 0), (1, 1, 0)))[-1]\nbase_hinge_axis = Axis(base_corner_edge)\nj3 = RevoluteJoint(\"hinge\", base, axis=base_hinge_axis, angular_range=(0, 180))\nj4 = RigidJoint(\"corner\", hinge_arm, swing_arm_hinge_axis.location)\nbase.joints[\"hinge\"].connect_to(hinge_arm.joints[\"corner\"], angle=90)\n\n#\n# Slider\n#\nslider_arm = JointBox(4, 1, 2, 0.2)\ns1 = LinearJoint(\n \"slide\",\n base,\n axis=Axis(Edge.make_mid_way(*base_top_edges, 0.67)),\n linear_range=(0, base_top_edges[0].length),\n)\ns2 = RigidJoint(\"slide\", slider_arm, Location(Vector(0, 0, 0)))\nbase.joints[\"slide\"].connect_to(slider_arm.joints[\"slide\"], position=8)\n# s1.connect_to(s2,8)\n\n#\n# Cylindrical\n#\nhole_axis = Axis(\n base.faces().sort_by(Axis.Y)[0].center(),\n -base.faces().sort_by(Axis.Y)[0].normal_at(),\n)\nscrew_arm = JointBox(1, 1, 10, 0.49)\nj5 = CylindricalJoint(\"hole\", base, hole_axis, linear_range=(-10, 10))\nj6 = RigidJoint(\"screw\", screw_arm, screw_arm.faces().sort_by(Axis.Z)[-1].location)\nj5.connect_to(j6, position=-1, angle=90)\n\n#\n# PinSlotJoint\n#\nj7 = LinearJoint(\n \"slot\",\n base,\n axis=Axis(Edge.make_mid_way(*base_top_edges, 0.33)),\n linear_range=(0, base_top_edges[0].length),\n)\npin_arm = JointBox(2, 1, 2)\nj8 = RevoluteJoint(\"pin\", pin_arm, axis=Axis.Z, angular_range=(0, 360))\nj7.connect_to(j8, position=6, angle=60)\n\n#\n# BallJoint\n#\nj9 = BallJoint(\"socket\", base, Plane(base.faces().sort_by(Axis.X)[0]).location)\nball = JointBox(2, 2, 2, 0.99)\nj10 = RigidJoint(\"ball\", ball, Location(Vector(0, 0, 1)))\nj9.connect_to(j10, angles=(10, 20, 30))\n\nshow_all(render_joints=True, transparent=True)\n" + }, + { + "id": "examples/joints_algebra", + "source": "examples/joints_algebra.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n\nclass JointBox(Part):\n \"\"\"A filleted box with joints\n\n A box of the given dimensions with all of the edges filleted.\n\n Args:\n length (float): box length\n width (float): box width\n height (float): box height\n radius (float): edge radius\n taper (float): vertical taper in degrees\n \"\"\"\n\n def __init__(\n self,\n length: float,\n width: float,\n height: float,\n radius: float = 0.0,\n taper: float = 0.0,\n ):\n # Create the object\n obj = extrude(Rectangle(length, width), amount=height, taper=taper)\n if radius != 0.0:\n obj = fillet(obj.edges(), radius=radius)\n obj -= Rot(0, 90, 0) * Cylinder(width / 4, length)\n # Initialize the Part class with the new OCCT object\n super().__init__(obj.wrapped)\n\n\n#\n# Base Object\n#\n# base = JointBox(10, 10, 10)\n# base = JointBox(10, 10, 10).locate(Location(Vector(1, 1, 1)))\n# base = JointBox(10, 10, 10).locate(Location(Vector(1, 1, 1), (1, 0, 0), 5))\nloc = Location(Vector(1, 1, 1), (1, 1, 1), 30)\nbase = loc * JointBox(10, 10, 10, taper=3)\n\nbase_top_edges = base.edges().filter_by(loc.x_axis).group_by(loc.z_axis)[-1]\n#\n# Rigid Joint\n#\nfixed_arm = JointBox(1, 1, 5, 0.2)\nj1 = RigidJoint(\"side\", base, Plane(base.faces().sort_by(loc.x_axis).last).location)\nj2 = RigidJoint(\"top\", fixed_arm, (-Plane(fixed_arm.faces().sort_by().last)).location)\nbase.joints[\"side\"].connect_to(fixed_arm.joints[\"top\"])\n# or\n# j1.connect_to(j2)\n\n#\n# Hinge\n#\nhinge_arm = JointBox(2, 1, 10, taper=1)\nswing_arm_hinge_edge = (\n hinge_arm.edges()\n .group_by(SortBy.LENGTH)[-1]\n .sort_by(Axis.X)[-2:]\n .sort_by(Axis.Y)[0]\n)\nswing_arm_hinge_axis = Axis(swing_arm_hinge_edge)\nbase_corner_edge = base.edges().sort_by(Axis((0, 0, 0), (1, 1, 0)))[-1]\nbase_hinge_axis = Axis(base_corner_edge)\nj3 = RevoluteJoint(\"hinge\", base, axis=base_hinge_axis, angular_range=(0, 180))\nj4 = RigidJoint(\"corner\", hinge_arm, swing_arm_hinge_axis.location)\nbase.joints[\"hinge\"].connect_to(hinge_arm.joints[\"corner\"], angle=90)\n\n\n#\n# Slider\n#\nslider_arm = JointBox(4, 1, 2, 0.2)\ns1 = LinearJoint(\n \"slide\",\n base,\n axis=Axis(Edge.make_mid_way(*base_top_edges, 0.67)),\n linear_range=(0, base_top_edges[0].length),\n)\ns2 = RigidJoint(\"slide\", slider_arm, Location(Vector(0, 0, 0)))\nbase.joints[\"slide\"].connect_to(slider_arm.joints[\"slide\"], position=8)\n# s1.connect_to(s2,8)\n\n#\n# Cylindrical\n#\nhole_axis = Axis(\n base.faces().sort_by(Axis.Y)[0].center(),\n -base.faces().sort_by(Axis.Y)[0].normal_at(),\n)\nscrew_arm = JointBox(1, 1, 10, 0.49)\nj5 = CylindricalJoint(\"hole\", base, hole_axis, linear_range=(-10, 10))\nj6 = RigidJoint(\"screw\", screw_arm, screw_arm.faces().sort_by(Axis.Z)[-1].location)\nj5.connect_to(j6, position=-1, angle=90)\n\n#\n# PinSlotJoint\n#\nj7 = LinearJoint(\n \"slot\",\n base,\n axis=Axis(Edge.make_mid_way(*base_top_edges, 0.33)),\n linear_range=(0, base_top_edges[0].length),\n)\npin_arm = JointBox(2, 1, 2)\nj8 = RevoluteJoint(\"pin\", pin_arm, axis=Axis.Z, angular_range=(0, 360))\nj7.connect_to(j8, position=6, angle=60)\n\n#\n# BallJoint\n#\nj9 = BallJoint(\"socket\", base, Plane(base.faces().sort_by(Axis.X)[0]).location)\nball = JointBox(2, 2, 2, 0.99)\nj10 = RigidJoint(\"ball\", ball, Location(Vector(0, 0, 1)))\nj9.connect_to(j10, angles=(10, 20, 30))\n\nshow_all(render_joints=True, transparent=True)\n" + }, + { + "id": "examples/key_cap", + "source": "examples/key_cap.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nwith BuildPart() as key_cap:\n # Start with the plan of the key cap and extrude it\n with BuildSketch() as plan:\n Rectangle(18 * MM, 18 * MM)\n extrude(amount=10 * MM, taper=15)\n # Create a dished top\n with Locations((0, -3 * MM, 47 * MM)):\n Sphere(40 * MM, mode=Mode.SUBTRACT, rotation=(90, 0, 0))\n # Fillet all the edges except the bottom\n fillet(\n key_cap.edges().filter_by_position(Axis.Z, 0, 30 * MM, inclusive=(False, True)),\n radius=1 * MM,\n )\n # Hollow out the key by subtracting a scaled version\n scale(by=(0.925, 0.925, 0.85), mode=Mode.SUBTRACT)\n\n # First find the size of the internal cavity at 4*MM\n key_cap_section = section(key_cap.part, Plane.XY.offset(4 * MM)).face()\n key_cap_internal_size = key_cap_section.inner_wires()[0].bounding_box().size\n\n # Add supporting ribs while leaving room for switch activation\n with BuildSketch(Plane(origin=(0, 0, 4 * MM))):\n Rectangle(key_cap_internal_size.X, 0.5 * MM)\n Rectangle(0.5 * MM, key_cap_internal_size.Y)\n Circle(radius=5.5 * MM / 2)\n # Extrude the mount and ribs to the key cap underside\n extrude(until=Until.NEXT)\n # Find the face on the bottom of the ribs to build onto\n rib_bottom = key_cap.faces().filter_by_position(Axis.Z, 4 * MM, 4 * MM)[0]\n # Add the switch socket\n with BuildSketch(rib_bottom) as cruciform:\n Circle(radius=5.5 * MM / 2)\n Rectangle(4.1 * MM, 1.17 * MM, mode=Mode.SUBTRACT)\n Rectangle(1.17 * MM, 4.1 * MM, mode=Mode.SUBTRACT)\n extrude(amount=3.5 * MM, mode=Mode.ADD)\n\nassert abs(key_cap.part.volume - 644.8900473617498) < 1e-3\n\nshow(key_cap, alphas=[0.3])\n# [End]\n" + }, + { + "id": "examples/key_cap_algebra", + "source": "examples/key_cap_algebra.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n# Taper Extrude and Extrude to \"next\" while creating a Cherry MX key cap\n# See: https://www.cherrymx.de/en/dev.html\n\nplan = Rectangle(18 * MM, 18 * MM)\nkey_cap = extrude(plan, amount=10 * MM, taper=15)\n\n# Create a dished top\nkey_cap -= Location((0, -3 * MM, 47 * MM), (90, 0, 0)) * Sphere(40 * MM)\n\n# Fillet all the edges except the bottom\nkey_cap = fillet(\n key_cap.edges().filter_by_position(Axis.Z, 0, 30 * MM, inclusive=(False, True)),\n radius=1 * MM,\n)\n\n# Hollow out the key by subtracting a scaled version\nkey_cap -= scale(key_cap, (0.925, 0.925, 0.85))\n\n\n# Add supporting ribs while leaving room for switch activation\n# First find the size of the internal cavity at 4*MM\nkey_cap_section = section(key_cap, Plane.XY.offset(4 * MM)).face()\nkey_cap_internal_size = key_cap_section.inner_wires()[0].bounding_box().size\n# Use this size to ensure the ribs fit within the keycap cavity\nribs = Rectangle(key_cap_internal_size.X, 0.5 * MM)\nribs += Rectangle(0.5 * MM, key_cap_internal_size.Y)\nribs += Circle(radius=5.51 * MM / 2)\n\n# Extrude the mount and ribs to the key cap underside\nkey_cap += extrude(Pos(0, 0, 4 * MM) * ribs, until=Until.NEXT, target=key_cap)\n\n# Add the switch socket\nsocket = Circle(radius=5.5 * MM / 2)\nsocket -= Rectangle(4.1 * MM, 1.17 * MM)\nsocket -= Rectangle(1.17 * MM, 4.1 * MM)\nkey_cap += extrude(Plane.XY.offset(4 * MM) * socket, amount=-3.5 * MM)\n\nshow(key_cap, alphas=[0.3])\n# [End]\n" + }, + { + "id": "examples/lego", + "source": "examples/lego.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\nGEN_DOCS = False\npip_count = 6\n\nlego_unit_size = 8\npip_height = 1.8\npip_diameter = 4.8\nblock_length = lego_unit_size * pip_count\nblock_width = 16\nbase_height = 9.6\nblock_height = base_height + pip_height\nsupport_outer_diameter = 6.5\nsupport_inner_diameter = 4.8\nridge_width = 0.6\nridge_depth = 0.3\nwall_thickness = 1.2\n\nwith BuildPart() as lego:\n # Draw the bottom of the block\n with BuildSketch() as plan:\n # Start with a Rectangle the size of the block\n perimeter = Rectangle(width=block_length, height=block_width)\n if GEN_DOCS:\n exporter = ExportSVG(scale=6)\n exporter.add_shape(plan.sketch)\n exporter.write(\"assets/lego_step4.svg\")\n # Subtract an offset to create the block walls\n offset(\n perimeter,\n -wall_thickness,\n kind=Kind.INTERSECTION,\n mode=Mode.SUBTRACT,\n )\n if GEN_DOCS:\n exporter = ExportSVG(scale=6)\n exporter.add_shape(plan.sketch)\n exporter.write(\"assets/lego_step5.svg\")\n # Add a grid of lengthwise and widthwise bars\n with GridLocations(x_spacing=0, y_spacing=lego_unit_size, x_count=1, y_count=2):\n Rectangle(width=block_length, height=ridge_width)\n with GridLocations(lego_unit_size, 0, pip_count, 1):\n Rectangle(width=ridge_width, height=block_width)\n if GEN_DOCS:\n exporter = ExportSVG(scale=6)\n exporter.add_shape(plan.sketch)\n exporter.write(\"assets/lego_step6.svg\")\n # Subtract a rectangle leaving ribs on the block walls\n Rectangle(\n block_length - 2 * (wall_thickness + ridge_depth),\n block_width - 2 * (wall_thickness + ridge_depth),\n mode=Mode.SUBTRACT,\n )\n if GEN_DOCS:\n exporter = ExportSVG(scale=6)\n exporter.add_shape(plan.sketch)\n exporter.write(\"assets/lego_step7.svg\")\n # Add a row of hollow circles to the center\n with GridLocations(\n x_spacing=lego_unit_size, y_spacing=0, x_count=pip_count - 1, y_count=1\n ):\n Circle(radius=support_outer_diameter / 2)\n Circle(radius=support_inner_diameter / 2, mode=Mode.SUBTRACT)\n if GEN_DOCS:\n exporter = ExportSVG(scale=6)\n exporter.add_shape(plan.sketch)\n exporter.write(\"assets/lego_step8.svg\")\n # Extrude this base sketch to the height of the walls\n extrude(amount=base_height - wall_thickness)\n if GEN_DOCS:\n visible, hidden = lego.part.project_to_viewport((-5, -30, 50))\n exporter = ExportSVG(scale=6)\n exporter.add_layer(\"Visible\")\n exporter.add_layer(\n \"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT\n )\n exporter.add_shape(visible, layer=\"Visible\")\n exporter.add_shape(hidden, layer=\"Hidden\")\n exporter.write(\"assets/lego_step9.svg\")\n # Create a box on the top of the walls\n with Locations((0, 0, lego.vertices().sort_by(Axis.Z)[-1].Z)):\n # Create the top of the block\n Box(\n length=block_length,\n width=block_width,\n height=wall_thickness,\n align=(Align.CENTER, Align.CENTER, Align.MIN),\n )\n if GEN_DOCS:\n visible, hidden = lego.part.project_to_viewport((-5, -30, 50))\n exporter = ExportSVG(scale=6)\n exporter.add_layer(\"Visible\")\n exporter.add_layer(\n \"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT\n )\n exporter.add_shape(visible, layer=\"Visible\")\n exporter.add_shape(hidden, layer=\"Hidden\")\n exporter.write(\"assets/lego_step10.svg\")\n # Create a workplane on the top of the block\n with BuildPart(lego.faces().sort_by(Axis.Z)[-1]):\n # Create a grid of pips\n with GridLocations(lego_unit_size, lego_unit_size, pip_count, 2):\n Cylinder(\n radius=pip_diameter / 2,\n height=pip_height,\n align=(Align.CENTER, Align.CENTER, Align.MIN),\n )\n if GEN_DOCS:\n visible, hidden = lego.part.project_to_viewport((-100, -100, 50))\n exporter = ExportSVG(scale=6)\n exporter.add_layer(\"Visible\")\n exporter.add_layer(\n \"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT\n )\n exporter.add_shape(visible, layer=\"Visible\")\n exporter.add_shape(hidden, layer=\"Hidden\")\n exporter.write(\"assets/lego.svg\")\n\nassert abs(lego.part.volume - 3212.187337781355) < 1e-3\n\nshow_object(lego.part, name=\"lego\")\n" + }, + { + "id": "examples/lego_algebra", + "source": "examples/lego_algebra.py", + "kind": "example", + "code": "from build123d import *\n\npip_count = 6\n\nlego_unit_size = 8\npip_height = 1.8\npip_diameter = 4.8\nblock_length = lego_unit_size * pip_count\nblock_width = 16\nbase_height = 9.6\nblock_height = base_height + pip_height\nsupport_outer_diameter = 6.5\nsupport_inner_diameter = 4.8\nridge_width = 0.6\nridge_depth = 0.3\nwall_thickness = 1.2\n\n\n# Draw the bottom of the block\n\n# Start with a Rectangle the size of the block\nplan = Rectangle(width=block_length, height=block_width)\n\n# Subtract an offset to create the block walls\nplan -= offset(\n plan,\n -wall_thickness,\n kind=Kind.INTERSECTION,\n)\n# Add a grid of lengthwise and widthwise bars\nlocs = GridLocations(x_spacing=0, y_spacing=lego_unit_size, x_count=1, y_count=2)\nplan += locs * Rectangle(width=block_length, height=ridge_width)\n\nlocs = GridLocations(lego_unit_size, 0, pip_count, 1)\nplan += locs * Rectangle(width=ridge_width, height=block_width)\n\n# Subtract a rectangle leaving ribs on the block walls\nplan -= Rectangle(\n block_length - 2 * (wall_thickness + ridge_depth),\n block_width - 2 * (wall_thickness + ridge_depth),\n)\n\n# Add a row of hollow circles to the center\nlocs = GridLocations(\n x_spacing=lego_unit_size, y_spacing=0, x_count=pip_count - 1, y_count=1\n)\nring = Circle(support_outer_diameter / 2) - Circle(support_inner_diameter / 2)\nplan += locs * ring\n\n# Extrude this base sketch to the height of the walls\nlego = extrude(plan, amount=base_height - wall_thickness)\n\n# Create a box on the top of the walls and the top of the block\nlego += Pos(0, 0, lego.vertices().sort_by().last.Z) * Box(\n length=block_length,\n width=block_width,\n height=wall_thickness,\n align=(Align.CENTER, Align.CENTER, Align.MIN),\n)\n\n# Create a workplane on the top of the block\nplane = Plane(lego.faces().sort_by().last)\n\n# Create a grid of pips\n\nlocs = GridLocations(lego_unit_size, lego_unit_size, pip_count, 2)\nlego += (\n plane\n * locs\n * Cylinder(\n radius=pip_diameter / 2,\n height=pip_height,\n align=(Align.CENTER, Align.CENTER, Align.MIN),\n )\n)\n\nif \"show_object\" in locals():\n show_object(lego, name=\"lego\")\n" + }, + { + "id": "examples/loft", + "source": "examples/loft.py", + "kind": "example", + "code": "# [Code]\n\nfrom math import pi, sin\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nwith BuildPart() as art:\n slice_count = 10\n for i in range(slice_count + 1):\n with BuildSketch(Plane(origin=(0, 0, i * 3), z_dir=(0, 0, 1))) as slice:\n Circle(10 * sin(i * pi / slice_count) + 5)\n loft()\n top_bottom = art.faces().filter_by(GeomType.PLANE)\n offset(openings=top_bottom, amount=0.5)\n\nwant = 1306.3405290344635\ngot = art.part.volume\ndelta = abs(got - want)\ntolerance = want * 1e-5\nassert delta < tolerance, f\"{delta=} is greater than {tolerance=}; {got=}, {want=}\"\n\nshow(art, names=[\"art\"])\n# [End]\n" + }, + { + "id": "examples/loft_algebra", + "source": "examples/loft_algebra.py", + "kind": "example", + "code": "# [Code]\n\nfrom math import pi, sin\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nslice_count = 10\n\nart = Sketch()\nfor i in range(slice_count + 1):\n plane = Plane(origin=(0, 0, i * 3), z_dir=(0, 0, 1))\n art += plane * Circle(10 * sin(i * pi / slice_count) + 5)\n\nart = loft(art)\ntop_bottom = art.faces().filter_by(GeomType.PLANE)\nart = offset(art, openings=top_bottom, amount=0.5)\n\nshow(art, names=[\"art\"])\n# [End]\n" + }, + { + "id": "examples/maker_coin", + "source": "examples/maker_coin.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n# [Code]\n# Coin Parameters\ndiameter, thickness = 50 * MM, 10 * MM\n\nwith BuildPart() as maker_coin:\n # On XZ plane draw the profile of half the coin\n with BuildSketch(Plane.XZ) as profile:\n with BuildLine() as outline:\n l1 = Polyline((0, thickness * 0.6), (0, 0), ((diameter - thickness) / 2, 0))\n l2 = JernArc(\n start=l1 @ 1, tangent=l1 % 1, radius=thickness / 2, arc_size=300\n ) # extend the arc beyond the intersection but not closed\n l3 = DoubleTangentArc(l1 @ 0, tangent=(1, 0), other=l2)\n make_face() # make it a 2D shape\n revolve() # revolve 360\u00b0\n\n # Pattern the detents around the coin\n with BuildSketch() as detents:\n with PolarLocations(radius=(diameter + 5) / 2, count=8):\n Circle(thickness * 1.4 / 2)\n extrude(amount=thickness, mode=Mode.SUBTRACT) # cut away the detents\n\n fillet(maker_coin.edges(Select.NEW), 2) # fillet the cut edges\n\n # Add an embossed label\n with BuildSketch(Plane.XY.offset(thickness)) as label: # above coin\n Text(\"OS\", font_size=15)\n project() # label on top of coin\n extrude(amount=-thickness / 5, mode=Mode.SUBTRACT) # emboss label\n\nshow(maker_coin)\n# [End]\n" + }, + { + "id": "examples/mixed_algebra_context", + "source": "examples/mixed_algebra_context.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\n# Mix context and algebra api for parts\n\nb = Box(1, 2, 3) + Cylinder(0.75, 2.5)\n\nwith BuildPart() as bp:\n add(b)\n Cylinder(0.4, 6, mode=Mode.SUBTRACT)\n\nc = bp.part - Plane.YZ * Cylinder(0.2, 6)\n\n# Mix context and algebra api for sketches\n\nr = Rectangle(1, 2) + Circle(0.75)\n\nwith BuildSketch() as bs:\n add(r)\n Circle(0.4, mode=Mode.SUBTRACT)\n\nd = bs.sketch - Pos(0, 1) * Circle(0.2)\n\n# Mix context and algebra api for sketches\n\nl1 = Line((-1, 0), (1, 1)) + Line((1, 1), (2, 4))\n\nwith BuildLine() as bl:\n add(l1)\n Line((2, 4), (-1, 1))\n\ne = bl.line + ThreePointArc((-1, 0), (-1.5, 0.5), (-1, 1))\n\nshow_object(Pos(0, -2, 0) * c, \"part\")\nshow_object(Pos(0, 2, 0) * d, \"sketch\")\nshow_object(Pos(0, 0, 2) * e, \"curve\")\n" + }, + { + "id": "examples/multiple_workplanes", + "source": "examples/multiple_workplanes.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nwith BuildPart() as obj:\n Box(5, 5, 1)\n with BuildPart(*obj.faces().filter_by(Axis.Z), mode=Mode.SUBTRACT):\n Sphere(1.8)\n\nassert abs(obj.part.volume - 15.083039190168236) < 1e-3\n\nshow(obj)\n" + }, + { + "id": "examples/multiple_workplanes_algebra", + "source": "examples/multiple_workplanes_algebra.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nobj = Box(5, 5, 1)\nplanes = [Plane(f) for f in obj.faces().filter_by(Axis.Z)]\nobj -= planes * Sphere(1.8)\n\nshow(obj)\n" + }, + { + "id": "examples/packed_boxes", + "source": "examples/packed_boxes.py", + "kind": "example", + "code": "import functools\nimport operator\nimport random\nimport build123d as bd\n\nGEN_DOCS = False\n\nrandom.seed(123456)\ntest_boxes = [bd.Box(random.randint(1, 20), random.randint(1, 20), random.randint(1, 5))\n for _ in range(50)]\npacked = bd.pack(test_boxes, 3)\n\n# Lifted from https://build123d.readthedocs.io/en/latest/import_export.html#d-to-2d-projection\ndef export_svg(parts, name):\n part = functools.reduce(operator.add, parts, bd.Part())\n view_port_origin=(0, 0, 150)\n visible, hidden = part.project_to_viewport(view_port_origin)\n max_dimension = max(*bd.Compound(children=visible + hidden).bounding_box().size)\n exporter = bd.ExportSVG(scale=100 / max_dimension)\n exporter.add_layer(\"Visible\")\n exporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=bd.LineType.ISO_DOT)\n exporter.add_shape(visible, layer=\"Visible\")\n exporter.add_shape(hidden, layer=\"Hidden\")\n if GEN_DOCS:\n exporter.write(f\"../docs/assets/{name}.svg\")\n\nexport_svg(test_boxes, \"packed_boxes_input\")\nexport_svg(packed, \"packed_boxes_output\")\n" + }, + { + "id": "examples/pegboard_j_hook", + "source": "examples/pegboard_j_hook.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\npegd = 6.35 + 0.1 # mm ~0.25inch\nc2c = 25.4 # mm 1.0inch\narcd = 7.2\nboth = 10\ntopx = 6\nmidx = 8\nmaind = 0.82 * pegd\nmidd = 1.0 * pegd\nhookd = 23\nhookx = 10\nsplitz = maind / 2 - 0.1\ntopangs = 70\n\nwith BuildPart() as mainp:\n with BuildLine(mode=Mode.PRIVATE) as sprof:\n l1 = Line((-both, 0), (c2c - arcd / 2 - 0.5, 0))\n l2 = JernArc(start=l1 @ 1, tangent=l1 % 1, radius=arcd / 2, arc_size=topangs)\n l3 = PolarLine(\n start=l2 @ 1,\n length=topx,\n direction=l2 % 1,\n )\n l4 = JernArc(start=l3 @ 1, tangent=l3 % 1, radius=arcd / 2, arc_size=-topangs)\n l5 = PolarLine(\n start=l4 @ 1,\n length=topx,\n direction=l4 % 1,\n )\n l6 = JernArc(\n start=l1 @ 0, tangent=(l1 % 0).reverse(), radius=hookd / 2, arc_size=170\n )\n l7 = PolarLine(\n start=l6 @ 1,\n length=hookx,\n direction=l6 % 1,\n )\n with BuildSketch(Plane.YZ):\n Circle(radius=maind / 2)\n sweep(path=sprof.wires()[0])\n with BuildLine(mode=Mode.PRIVATE) as stub:\n l7 = Line((0, 0), (0, midx + maind / 2))\n with BuildSketch(Plane.XZ):\n Circle(radius=midd / 2)\n sweep(path=stub.wires()[0])\n # splits help keep the object 3d printable by reducing overhang\n split(bisect_by=Plane(origin=(0, 0, -splitz)))\n split(bisect_by=Plane(origin=(0, 0, splitz)), keep=Keep.BOTTOM)\n\nshow(mainp)\n# [End]\n" + }, + { + "id": "examples/pegboard_j_hook_algebra", + "source": "examples/pegboard_j_hook_algebra.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\npegd = 6.35 + 0.1 # mm ~0.25inch\nc2c = 25.4 # mm 1.0inch\narcd = 7.2\nboth = 10\ntopx = 6\nmidx = 8\nmaind = 0.82 * pegd\nmidd = 1.0 * pegd\nhookd = 23\nhookx = 10\nsplitz = maind / 2 - 0.1\ntopangs = 70\n\nl1 = Line((-both, 0), (c2c - arcd / 2 - 0.5, 0))\nl2 = JernArc(start=l1 @ 1, tangent=l1 % 1, radius=arcd / 2, arc_size=topangs)\nl3 = PolarLine(\n start=l2 @ 1,\n length=topx,\n direction=l2 % 1,\n)\nl4 = JernArc(start=l3 @ 1, tangent=l3 % 1, radius=arcd / 2, arc_size=-topangs)\nl5 = PolarLine(\n start=l4 @ 1,\n length=topx,\n direction=l4 % 1,\n)\nl6 = JernArc(start=l1 @ 0, tangent=(l1 % 0).reverse(), radius=hookd / 2, arc_size=170)\nl7 = PolarLine(\n start=l6 @ 1,\n length=hookx,\n direction=l6 % 1,\n)\nsprof = Curve() + (l1, l2, l3, l4, l5, l6, l7)\nwire = Wire(sprof.edges()) # TODO sprof.wires() fails\nmainp = sweep(Plane.YZ * Circle(radius=maind / 2), path=wire)\n\nstub = Line((0, 0), (0, midx + maind / 2))\nmainp += sweep(Plane.XZ * Circle(radius=midd / 2), path=stub)\n\n\n# splits help keep the object 3d printable by reducing overhang\nmainp = split(mainp, Plane(origin=(0, 0, -splitz)))\nmainp = split(mainp, Plane(origin=(0, 0, splitz)), keep=Keep.BOTTOM)\n\nshow(mainp)\n# [End]\n" + }, + { + "id": "examples/pillow_block", + "source": "examples/pillow_block.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nheight, width, thickness, padding = 60, 80, 10, 12\nscrew_shaft_radius, screw_head_radius, screw_head_height = 1.5, 3, 3\nbearing_axle_radius, bearing_radius, bearing_thickness = 4, 11, 7\n\n# Build pillow block as an extruded sketch with counter bore holes\nwith BuildPart() as pillow_block:\n with BuildSketch() as plan:\n Rectangle(width, height)\n fillet(plan.vertices(), radius=5)\n extrude(amount=thickness)\n # with Locations((0, 0, thickness)):\n with Locations(pillow_block.faces().sort_by(Axis.Z)[-1]):\n CounterBoreHole(bearing_axle_radius, bearing_radius, bearing_thickness)\n with GridLocations(width - 2 * padding, height - 2 * padding, 2, 2):\n CounterBoreHole(screw_shaft_radius, screw_head_radius, screw_head_height)\n\n# Render the part\nshow(pillow_block)\n" + }, + { + "id": "examples/pillow_block_algebra", + "source": "examples/pillow_block_algebra.py", + "kind": "example", + "code": "from build123d import *\n\nheight, width, thickness, padding = 60, 80, 10, 12\nscrew_shaft_radius, screw_head_radius, screw_head_height = 1.5, 3, 3\nbearing_axle_radius, bearing_radius, bearing_thickness = 4, 11, 7\n\n# Build pillow block as an extruded sketch with counter bore holes\nplan = Rectangle(width, height)\nplan = fillet(plan.vertices(), radius=5)\npillow_block = extrude(plan, thickness)\n\nplane = Plane(pillow_block.faces().sort_by().last)\n\npillow_block -= plane * CounterBoreHole(\n bearing_axle_radius, bearing_radius, bearing_thickness, height\n)\nlocs = GridLocations(width - 2 * padding, height - 2 * padding, 2, 2)\npillow_block -= (\n plane\n * locs\n * CounterBoreHole(screw_shaft_radius, screw_head_radius, screw_head_height, height)\n)\n\n# Render the part\nif \"show_object\" in locals():\n show_object(pillow_block)\n" + }, + { + "id": "examples/platonic_solids", + "source": "examples/platonic_solids.py", + "kind": "example", + "code": "# [Code]\nfrom build123d import *\nfrom math import sqrt\nfrom typing import Union, Literal\nfrom scipy.spatial import ConvexHull\n\n# [removed by collect.py] from ocp_vscode import show\n\nPHI = (1 + sqrt(5)) / 2 # The Golden Ratio\n\n\nclass PlatonicSolid(BasePartObject):\n \"\"\"Part Object: Platonic Solid\n\n Create one of the five convex Platonic solids.\n\n Args:\n face_count (Literal[4,6,8,12,20]): number of faces\n diameter (float): double distance to vertices, i.e. maximum size\n rotation (RotationLike, optional): angles to rotate about axes. Defaults to (0, 0, 0).\n align (Union[None, Align, tuple[Align, Align, Align]], optional): align min, center,\n or max of object. Defaults to None.\n mode (Mode, optional): combine mode. Defaults to Mode.ADD.\n \"\"\"\n\n tetrahedron_vertices = [(1, 1, 1), (1, -1, -1), (-1, 1, -1), (-1, -1, 1)]\n\n cube_vertices = [(i, j, k) for i in [-1, 1] for j in [-1, 1] for k in [-1, 1]]\n\n octahedron_vertices = (\n [(i, 0, 0) for i in [-1, 1]]\n + [(0, i, 0) for i in [-1, 1]]\n + [(0, 0, i) for i in [-1, 1]]\n )\n\n dodecahedron_vertices = (\n [(i, j, k) for i in [-1, 1] for j in [-1, 1] for k in [-1, 1]]\n + [(0, i / PHI, j * PHI) for i in [-1, 1] for j in [-1, 1]]\n + [(i / PHI, j * PHI, 0) for i in [-1, 1] for j in [-1, 1]]\n + [(i * PHI, 0, j / PHI) for i in [-1, 1] for j in [-1, 1]]\n )\n\n icosahedron_vertices = (\n [(0, i, j * PHI) for i in [-1, 1] for j in [-1, 1]]\n + [(i, j * PHI, 0) for i in [-1, 1] for j in [-1, 1]]\n + [(i * PHI, 0, j) for i in [-1, 1] for j in [-1, 1]]\n )\n\n vertices_lookup = {\n 4: tetrahedron_vertices,\n 6: cube_vertices,\n 8: octahedron_vertices,\n 12: dodecahedron_vertices,\n 20: icosahedron_vertices,\n }\n _applies_to = [BuildPart._tag]\n\n def __init__(\n self,\n face_count: Literal[4, 6, 8, 12, 20],\n diameter: float = 1.0,\n rotation: RotationLike = (0, 0, 0),\n align: Union[None, Align, tuple[Align, Align, Align]] = None,\n mode: Mode = Mode.ADD,\n ):\n try:\n platonic_vertices = PlatonicSolid.vertices_lookup[face_count]\n except KeyError:\n raise ValueError(\n f\"face_count must be one of 4, 6, 8, 12, or 20 not {face_count}\"\n )\n\n # Create a convex hull from the vertices\n hull = ConvexHull(platonic_vertices).simplices.tolist()\n\n # Create faces from the vertex indices\n platonic_faces = []\n for face_vertex_indices in hull:\n corner_vertices = [platonic_vertices[i] for i in face_vertex_indices]\n platonic_faces.append(Face(Wire.make_polygon(corner_vertices)))\n\n # Create the solid from the Faces\n platonic_solid = Solid(Shell(platonic_faces)).clean()\n\n # By definition, all vertices are the same distance from the origin so\n # scale proportionally to this distance\n platonic_solid = platonic_solid.scale(\n (diameter / 2) / Vector(platonic_solid.vertices()[0]).length\n )\n\n super().__init__(part=platonic_solid, rotation=rotation, align=align, mode=mode)\n\n\nsolids = [\n Rot(0, 0, 72 * i) * Pos(1, 0, 0) * PlatonicSolid(faces)\n for i, faces in enumerate([4, 6, 8, 12, 20])\n]\nshow(solids)\n\n# [End]\n" + }, + { + "id": "examples/playing_cards", + "source": "examples/playing_cards.py", + "kind": "example", + "code": "# [Code]\n\nfrom typing import Literal\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\n\n# [Club]\nclass Club(BaseSketchObject):\n def __init__(\n self,\n height: float,\n rotation: float = 0,\n align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),\n mode: Mode = Mode.ADD,\n ):\n with BuildSketch() as club:\n with BuildLine():\n l0 = Line((0, -188), (76, -188))\n b0 = Bezier(l0 @ 1, (61, -185), (33, -173), (17, -81))\n b1 = Bezier(b0 @ 1, (49, -128), (146, -145), (167, -67))\n b2 = Bezier(b1 @ 1, (187, 9), (94, 52), (32, 18))\n b3 = Bezier(b2 @ 1, (92, 57), (113, 188), (0, 188))\n mirror(about=Plane.YZ)\n make_face()\n scale(by=height / club.sketch.bounding_box().size.Y)\n super().__init__(obj=club.sketch, rotation=rotation, align=align, mode=mode)\n\n\n# [Club]\n\n\nclass Spade(BaseSketchObject):\n def __init__(\n self,\n height: float,\n rotation: float = 0,\n align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),\n mode: Mode = Mode.ADD,\n ):\n with BuildSketch() as spade:\n with BuildLine():\n b0 = Bezier((0, 198), (6, 190), (41, 127), (112, 61))\n b1 = Bezier(b0 @ 1, (242, -72), (114, -168), (11, -105))\n b2 = Bezier(b1 @ 1, (31, -174), (42, -179), (53, -198))\n l0 = Line(b2 @ 1, (0, -198))\n mirror(about=Plane.YZ)\n make_face()\n scale(by=height / spade.sketch.bounding_box().size.Y)\n super().__init__(obj=spade.sketch, rotation=rotation, align=align, mode=mode)\n\n\nclass Heart(BaseSketchObject):\n def __init__(\n self,\n height: float,\n rotation: float = 0,\n align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),\n mode: Mode = Mode.ADD,\n ):\n with BuildSketch() as heart:\n with BuildLine():\n b1 = Bezier((0, 146), (20, 169), (67, 198), (97, 198))\n b2 = Bezier(b1 @ 1, (125, 198), (151, 186), (168, 167))\n b3 = Bezier(b2 @ 1, (197, 133), (194, 88), (158, 31))\n b4 = Bezier(b3 @ 1, (126, -13), (94, -48), (62, -95))\n b5 = Bezier(b4 @ 1, (40, -128), (0, -198))\n mirror(about=Plane.YZ)\n make_face()\n scale(by=height / heart.sketch.bounding_box().size.Y)\n super().__init__(obj=heart.sketch, rotation=rotation, align=align, mode=mode)\n\n\nclass Diamond(BaseSketchObject):\n def __init__(\n self,\n height: float,\n rotation: float = 0,\n align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),\n mode: Mode = Mode.ADD,\n ):\n with BuildSketch() as diamond:\n with BuildLine():\n Bezier((135, 0), (94, 69), (47, 134), (0, 198))\n mirror(about=Plane.XZ)\n mirror(about=Plane.YZ)\n make_face()\n scale(by=height / diamond.sketch.bounding_box().size.Y)\n super().__init__(obj=diamond.sketch, rotation=rotation, align=align, mode=mode)\n\n\ncard_width = 2.5 * IN\ncard_length = 3.5 * IN\ndeck = 0.5 * IN\nwall = 4 * MM\ngap = 0.5 * MM\n\nwith BuildPart() as box_builder:\n with BuildSketch() as plan:\n Rectangle(card_width + 2 * wall, card_length + 2 * wall)\n fillet(plan.vertices(), radius=card_width / 15)\n extrude(amount=wall / 2)\n with BuildSketch(box_builder.faces().sort_by(Axis.Z)[-1]) as walls:\n add(plan.sketch)\n offset(plan.sketch, amount=-wall, mode=Mode.SUBTRACT)\n extrude(amount=deck / 2)\n with BuildSketch(box_builder.faces().sort_by(Axis.Z)[-1]) as inset_walls:\n offset(plan.sketch, amount=-(wall + gap) / 2, mode=Mode.ADD)\n offset(plan.sketch, amount=-wall, mode=Mode.SUBTRACT)\n extrude(amount=deck / 2)\n\nwith BuildPart() as lid_builder:\n with BuildSketch() as outset_walls:\n add(plan.sketch)\n offset(plan.sketch, amount=-(wall - gap) / 2, mode=Mode.SUBTRACT)\n extrude(amount=deck / 2)\n with BuildSketch(lid_builder.faces().sort_by(Axis.Z)[-1]) as top:\n add(plan.sketch)\n extrude(amount=wall / 2)\n with BuildSketch(lid_builder.faces().sort_by(Axis.Z)[-1]):\n holes = GridLocations(\n 3 * card_width / 5, 3 * card_length / 5, 2, 2\n ).local_locations\n for i, hole in enumerate(holes):\n with Locations(hole) as hole_loc:\n if i == 0:\n Heart(card_length / 5)\n elif i == 1:\n Diamond(card_length / 5)\n elif i == 2:\n Spade(card_length / 5)\n elif i == 3:\n Club(card_length / 5)\n extrude(amount=-wall, mode=Mode.SUBTRACT)\n\nbox = Compound(\n [box_builder.part, lid_builder.part.moved(Location((0, 0, (wall + deck) / 2)))]\n)\nvisible, hidden = box.project_to_viewport((70, -50, 120))\nmax_dimension = max(*Compound(children=visible + hidden).bounding_box().size)\nexporter = ExportSVG(scale=100 / max_dimension)\nexporter.add_layer(\"Visible\")\nexporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)\nexporter.add_shape(visible, layer=\"Visible\")\nexporter.add_shape(hidden, layer=\"Hidden\")\n# exporter.write(f\"assets/card_box.svg\")\n\n\nclass PlayingCard(BaseSketchObject):\n \"\"\"PlayingCard\n\n A standard playing card modelled as a Face.\n\n Args:\n rank (Literal['A', '2' .. '10', 'J', 'Q', 'K']): card rank\n suit (Literal['Clubs', 'Spades', 'Hearts', 'Diamonds']): card suit\n \"\"\"\n\n width = 2.5 * IN\n height = 3.5 * IN\n suits = {\"Clubs\": Club, \"Spades\": Spade, \"Hearts\": Heart, \"Diamonds\": Diamond}\n ranks = [\"A\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\", \"J\", \"Q\", \"K\"]\n\n def __init__(\n self,\n rank: Literal[\"A\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\", \"J\", \"Q\", \"K\"],\n suit: Literal[\"Clubs\", \"Spades\", \"Hearts\", \"Diamonds\"],\n rotation: float = 0,\n align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),\n mode: Mode = Mode.ADD,\n ):\n with BuildSketch() as playing_card:\n Rectangle(\n PlayingCard.width, PlayingCard.height, align=(Align.MIN, Align.MIN)\n )\n fillet(playing_card.vertices(), radius=PlayingCard.width / 15)\n with Locations(\n (\n PlayingCard.width / 7,\n 8 * PlayingCard.height / 9,\n )\n ):\n Text(\n txt=rank,\n font_size=PlayingCard.width / 7,\n mode=Mode.SUBTRACT,\n )\n with Locations(\n (\n PlayingCard.width / 7,\n 7 * PlayingCard.height / 9,\n )\n ):\n PlayingCard.suits[suit](\n height=PlayingCard.width / 12, mode=Mode.SUBTRACT\n )\n with Locations(\n (\n 6 * PlayingCard.width / 7,\n 1 * PlayingCard.height / 9,\n )\n ):\n Text(\n txt=rank,\n font_size=PlayingCard.width / 7,\n rotation=180,\n mode=Mode.SUBTRACT,\n )\n with Locations(\n (\n 6 * PlayingCard.width / 7,\n 2 * PlayingCard.height / 9,\n )\n ):\n PlayingCard.suits[suit](\n height=PlayingCard.width / 12, rotation=180, mode=Mode.SUBTRACT\n )\n rank_int = PlayingCard.ranks.index(rank) + 1\n rank_int = rank_int if rank_int < 10 else 1\n with Locations((PlayingCard.width / 2, PlayingCard.height / 2)):\n center_radius = 0 if rank_int == 1 else PlayingCard.width / 3.5\n suit_rotation = 0 if rank_int == 1 else -90\n suit_height = (\n 0.00159 * rank_int**2 - 0.0380 * rank_int + 0.37\n ) * PlayingCard.width\n with PolarLocations(\n radius=center_radius,\n count=rank_int,\n start_angle=90 if rank_int > 1 else 0,\n ):\n PlayingCard.suits[suit](\n height=suit_height,\n rotation=suit_rotation,\n mode=Mode.SUBTRACT,\n )\n super().__init__(\n obj=playing_card.sketch, rotation=rotation, align=align, mode=mode\n )\n\n\nace_spades = PlayingCard(rank=\"A\", suit=\"Spades\", align=Align.MIN)\nace_spades.color = Color(\"white\")\nking_hearts = PlayingCard(rank=\"K\", suit=\"Hearts\", align=Align.MIN)\nking_hearts.color = Color(\"white\")\nqueen_clubs = PlayingCard(rank=\"Q\", suit=\"Clubs\", align=Align.MIN)\nqueen_clubs.color = Color(\"white\")\njack_diamonds = PlayingCard(rank=\"J\", suit=\"Diamonds\", align=Align.MIN)\njack_diamonds.color = Color(\"white\")\nten_spades = PlayingCard(rank=\"10\", suit=\"Spades\", align=Align.MIN)\nten_spades.color = Color(\"white\")\n\nhand = Compound(\n children=[\n Rot(0, 0, -20) * Pos(0, 0, 0) * ace_spades,\n Rot(0, 0, -10) * Pos(0, 0, -1) * king_hearts,\n Rot(0, 0, 0) * Pos(0, 0, -2) * queen_clubs,\n Rot(0, 0, 10) * Pos(0, 0, -3) * jack_diamonds,\n Rot(0, 0, 20) * Pos(0, 0, -4) * ten_spades,\n ]\n)\n\nshow_object(Pos(-20, 40) * hand)\nshow_object(box_builder.part, \"box_builder\")\nshow_object(\n Pos(0, 0, (wall + deck) / 2) * lid_builder.part,\n \"lid_builder\",\n options={\"alpha\": 0.7},\n)\n# [End]\n" + }, + { + "id": "examples/projection", + "source": "examples/projection.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\n# A sphere used as a projection target\nsphere = Solid.make_sphere(50, angle1=-90)\n\n\"\"\"Example 1 - Mapping A Face on Sphere\"\"\"\nprojection_direction = Vector(0, 1, 0)\n\nsquare = Face.make_rect(20, 20, Plane.ZX.offset(-80))\nsquare_projected = square.project_to_shape(sphere, projection_direction)\nsquare_solids = Compound([Solid.thicken(f, 2) for f in square_projected])\nprojection_beams = [\n Solid.make_loft(\n [\n square.outer_wire(),\n square.outer_wire().translate(Vector(0, 160, 0)),\n ]\n )\n]\n\n\"\"\"Example 2 - Flat Projection of Text on Sphere\"\"\"\nprojection_direction = Vector(0, -1, 0)\nflat_planar_text_faces = (\n Compound.make_text(\"Flat\", font_size=30).rotate(Axis.X, 90).faces()\n)\nflat_projected_text_faces = Compound(\n [\n f.project_to_shape(sphere, projection_direction)[0]\n for f in flat_planar_text_faces\n ]\n).moved(Location((-100, -100)))\nflat_projection_beams = Compound(\n [Solid.extrude(f, projection_direction * 80) for f in flat_planar_text_faces]\n).moved(Location((-100, -100)))\n\n\n\"\"\"Example 3 - Project a text string along a path onto a shape\"\"\"\narch_path: Edge = (\n sphere.cut(Solid.make_cylinder(80, 100, Plane.YZ).locate(Location((-50, 0, -70))))\n .edges()\n .sort_by(Axis.Z)[0]\n)\narch_path_start = Vertex(arch_path.position_at(0))\ntext = Compound.make_text(\n txt=\"'the quick brown fox jumped over the lazy dog'\",\n font_size=15,\n align=(Align.MIN, Align.CENTER),\n)\nprojected_text = Sketch(sphere.project_faces(text, path=arch_path))\n\n# Example 1\nshow_object(sphere, name=\"sphere_solid\", options={\"alpha\": 0.8})\nshow_object(square, name=\"square\")\nshow_object(square_solids, name=\"square_solids\")\nshow_object(\n Compound(projection_beams),\n name=\"projection_beams\",\n options={\"alpha\": 0.9, \"color\": (170 / 255, 170 / 255, 255 / 255)},\n)\n\n# Example 2\nshow_object(\n sphere.moved(Location((-100, -100))),\n name=\"sphere_solid for text\",\n options={\"alpha\": 0.8},\n)\nshow_object(flat_projected_text_faces, name=\"flat_projected_text_faces\")\nshow_object(\n flat_projection_beams,\n name=\"flat_projection_beams\",\n options={\"alpha\": 0.95, \"color\": (170 / 255, 170 / 255, 255 / 255)},\n)\n\n# Example 3\nshow_object(\n sphere.moved(Location((100, 100))),\n name=\"sphere_solid for text on path\",\n options={\"alpha\": 0.8},\n)\nshow_object(projected_text.moved(Location((100, 100))), name=\"projected_text on path\")\n" + }, + { + "id": "examples/projection_algebra", + "source": "examples/projection_algebra.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\n# A sphere used as a projection target\nsphere = Sphere(50)\n\n\"\"\"Example 1 - Mapping A Face on Sphere\"\"\"\nprojection_direction = Vector(0, 1, 0)\n\nsquare = Plane.ZX.offset(-80) * Rectangle(20, 20)\nsquare_projected = square.faces()[0].project_to_shape(sphere, projection_direction)\nsquare_solids = Part() + [Solid.thicken(f, 2) for f in square_projected]\nface = square.faces()[0]\nprojection_beams = loft([face, Pos(0, 160, 0) * face])\n\n\n\"\"\"Example 2 - Flat Projection of Text on Sphere\"\"\"\nprojection_direction = Vector(0, -1, 0)\n\nflat_planar_text = Rot(90, 0, 0) * Text(\"Flat\", font_size=30)\nflat_projected_text_faces = Sketch() + [\n f.project_to_shape(sphere, projection_direction)[0]\n for f in flat_planar_text.faces()\n]\nflat_projection_beams = Part() + [\n extrude(f, dir=projection_direction, amount=80) for f in flat_planar_text.faces()\n]\n\n\n\"\"\"Example 3 - Project a text string along a path onto a shape\"\"\"\ncyl = Plane.YZ * Cylinder(80, 100, align=(Align.CENTER, Align.CENTER, Align.MIN))\nobj = sphere - Pos(-50, 0, -70) * cyl\n\narch_path: Edge = obj.edges().sort_by().first\n\narch_path_start = Vertex(arch_path.position_at(0))\ntext = Text(\n \"'the quick brown fox jumped over the lazy dog'\",\n font_size=15,\n align=(Align.MIN, Align.CENTER),\n)\nprojected_text = Sketch(sphere.project_faces(text.faces(), path=arch_path))\n\n# Example 1\nshow_object(sphere, name=\"sphere_solid\", options={\"alpha\": 0.8})\nshow_object(square, name=\"square\")\nshow_object(square_solids, name=\"square_solids\")\nshow_object(\n Compound(projection_beams),\n name=\"projection_beams\",\n options={\"alpha\": 0.9, \"color\": (170 / 255, 170 / 255, 255 / 255)},\n)\n\n# Example 2\nshow_object(\n Pos(-100, -100) * sphere,\n name=\"sphere_solid for text\",\n options={\"alpha\": 0.8},\n)\nshow_object(\n Pos(-100, -100) * flat_projected_text_faces, name=\"flat_projected_text_faces\"\n)\nshow_object(\n Pos(-100, -100) * flat_projection_beams,\n name=\"flat_projection_beams\",\n options={\"alpha\": 0.95, \"color\": (170 / 255, 170 / 255, 255 / 255)},\n)\n\n# Example 3\nshow_object(\n sphere.moved(Location((100, 100))),\n name=\"sphere_solid for text on path\",\n options={\"alpha\": 0.8},\n)\nshow_object(projected_text.moved(Location((100, 100))), name=\"projected_text on path\")\n" + }, + { + "id": "examples/python_logo", + "source": "examples/python_logo.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n\nclass PythonLogo(BaseSketchObject):\n \"\"\"PythonLogo\n\n Args:\n size (float): max size (Y direction - although the logo is almost square)\n rotation (float, optional): angles to rotate objects. Defaults to 0.\n align (Union[Align, tuple[Align, Align]], optional): align min, center, or max of object.\n Defaults to None.\n mode (Mode, optional): combination mode. Defaults to Mode.ADD.\n \"\"\"\n\n _applies_to = [BuildSketch._tag]\n _logo_colors = {\n \"Cyan-Blue Azure\": Color(75 / 255, 139 / 255, 190 / 255),\n \"Lapis Lazuli\": Color(48 / 255, 105 / 255, 152 / 255),\n \"Shandy\": Color(255 / 255, 232 / 255, 115 / 255),\n \"Sunglow\": Color(255 / 255, 212 / 255, 59 / 255),\n \"Granite Gray\": Color(100 / 255, 100 / 255, 100 / 255),\n }\n\n def __init__(\n self,\n size: float,\n rotation: float = 0,\n align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),\n mode: Mode = Mode.ADD,\n ):\n center = Vector(55.5806770629664, 56.194501214517224)\n\n with BuildSketch() as logo:\n with BuildLine(mode=Mode.PRIVATE) as snake:\n l1 = Bezier(\n (54.918785, 0.00091927389),\n (50.335132, 0.02221727),\n (45.957846, 0.41313697),\n (42.106285, 1.0946693),\n )\n l2 = Bezier(\n l1 @ 1,\n (30.760069, 3.0991731),\n (28.700036, 7.2947714),\n (28.700035, 15.032169),\n )\n l3 = Polyline(\n l2 @ 1,\n (28.700035, 25.250919),\n (55.512535, 25.250919),\n (55.512535, 28.657169),\n (28.700035, 28.657169),\n (18.637535, 28.657169),\n )\n l4 = Bezier(\n l3 @ 1,\n (10.845076, 28.657169),\n (4.0217762, 33.340886),\n (1.8875352, 42.250919),\n )\n l5 = Bezier(\n l4 @ 1,\n (-0.57428478, 52.463885),\n (-0.68347988, 58.836942),\n (1.8875352, 69.500919),\n )\n l6 = Bezier(\n l5 @ 1,\n (3.7934635, 77.438771),\n (8.3450784, 83.094667),\n (16.137535, 83.094669),\n )\n l7 = Polyline(l6 @ 1, (25.356285, 83.094669), (25.356285, 70.844669))\n l8 = Bezier(\n l7 @ 1,\n (25.356285, 61.994767),\n (33.013429, 54.188421),\n (42.106285, 54.188419),\n )\n l9 = Line(l8 @ 1, (68.887535, 54.188419))\n l10 = Bezier(\n l9 @ 1,\n (76.342486, 54.188419),\n (82.293788, 48.050255),\n (82.293785, 40.563419),\n )\n l11 = Line(l10 @ 1, (82.293785, 15.032169))\n l12 = Bezier(\n l11 @ 1,\n (82.293785, 7.7658304),\n (76.163805, 2.3073919),\n (68.887535, 1.0946693),\n )\n l13 = Bezier(\n l12 @ 1,\n (64.281548, 0.32794397),\n (59.502438, -0.02037903),\n (54.918785, 0.00091927389),\n )\n\n with Locations(-center):\n add(snake)\n make_face()\n with Locations(Vector(40.418785, 13.3290442) - center):\n Ellipse(10.0625002 / 2, 10.2187498 / 2, mode=Mode.SUBTRACT)\n add(logo.sketch, rotation=180)\n mirror(about=Plane.YZ, mode=Mode.REPLACE)\n current_size = max(*tuple(logo.sketch.bounding_box().size))\n scale(by=size / current_size)\n\n super().__init__(obj=logo.sketch, rotation=rotation, align=align, mode=mode)\n\n\nif __name__ == \"__main__\":\n show(PythonLogo(10))\n" + }, + { + "id": "examples/roller_coaster", + "source": "examples/roller_coaster.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\nwith BuildLine() as roller_coaster:\n powerup = Spline(\n (0, 0, 0),\n (50, 0, 50),\n (100, 0, 0),\n tangents=((1, 0, 0), (1, 0, 0)),\n tangent_scalars=(0.5, 2),\n )\n corner = RadiusArc(powerup @ 1, (100, 60, 0), -30)\n screw = Helix(75, 150, 15, center=(75, 40, 15), direction=(-1, 0, 0))\n Spline(corner @ 1, screw @ 0, tangents=(corner % 1, screw % 0))\n Spline(screw @ 1, (-100, 30, 10), powerup @ 0, tangents=(screw % 1, powerup % 0))\n\nshow_object(roller_coaster, name=\"roller_coaster\")\n" + }, + { + "id": "examples/roller_coaster_algebra", + "source": "examples/roller_coaster_algebra.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\npowerup = Spline(\n (0, 0, 0),\n (50, 0, 50),\n (100, 0, 0),\n tangents=((1, 0, 0), (1, 0, 0)),\n tangent_scalars=(0.5, 2),\n)\ncorner = RadiusArc(powerup @ 1, (100, 60, 0), -30)\nscrew = Helix(75, 150, 15, center=(75, 40, 15), direction=(-1, 0, 0))\n\nroller_coaster = Curve() + (powerup + corner + screw)\nroller_coaster += Spline(corner @ 1, screw @ 0, tangents=(corner % 1, screw % 0))\nroller_coaster += Spline(\n screw @ 1, (-100, 30, 10), powerup @ 0, tangents=(screw % 1, powerup % 0)\n)\n\nshow_object(roller_coaster)\n" + }, + { + "id": "examples/shamrock", + "source": "examples/shamrock.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n\nclass Shamrock(BaseSketchObject):\n \"\"\"Sketch Object: Shamrock\n\n Adds a four leaf clover\n\n Args:\n height (float): y axis dimension\n rotation (float, optional): angle in degrees. Defaults to 0.\n align (tuple[Align, Align], optional): alignment. Defaults to (Align.CENTER, Align.CENTER).\n mode (Mode, optional): combination mode. Defaults to Mode.ADD.\n \"\"\"\n\n def __init__(\n self,\n height: float,\n rotation: float = 0,\n align: tuple[Align, Align] = (Align.CENTER, Align.CENTER),\n mode: Mode = Mode.ADD,\n ):\n with BuildSketch() as shamrock:\n with BuildLine():\n b0 = Bezier((240, 310), (112, 325), (162, 438), (252, 470))\n b1 = Bezier(b0 @ 1, (136, 431), (73, 589), (179, 643))\n b2 = Bezier(b1 @ 1, (151, 747), (293, 770), (360, 679))\n b3 = Bezier(b2 @ 1, (358, 736), (366, 789), (392, 840))\n l0 = Line(b3 @ 1, (420, 820))\n b4 = Bezier(l0 @ 1, (366, 781), (374, 670), (380, 670))\n b5 = Bezier(b4 @ 1, (400, 794), (506, 789), (528, 727))\n b6 = Bezier(b5 @ 1, (636, 733), (638, 578), (507, 541))\n b7 = Bezier(b6 @ 1, (628, 559), (651, 380), (575, 365))\n b8 = Bezier(b7 @ 1, (592, 269), (420, 268), (417, 361))\n b9 = Bezier(b8 @ 1, (410, 253), (262, 222), b0 @ 0)\n mirror(about=Plane.XZ, mode=Mode.REPLACE)\n make_face()\n scale(by=height / shamrock.sketch.bounding_box().size.Y)\n super().__init__(\n obj=shamrock.sketch.translate(\n -shamrock.sketch.center(CenterOf.BOUNDING_BOX)\n ),\n rotation=rotation,\n align=align,\n mode=mode,\n )\n\n\nwith BuildSketch() as shamrock_example:\n Shamrock(10)\n\nshow(shamrock_example)\n" + }, + { + "id": "examples/stud_wall", + "source": "examples/stud_wall.py", + "kind": "example", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\nfrom typing import Union\nimport copy\n\n\n# [Code]\nclass Stud(BasePartObject):\n \"\"\"Part Object: Stud\n\n Create a dimensional framing stud.\n\n Args:\n length (float): stud size\n width (float): stud size\n thickness (float): stud size\n rotation (RotationLike, optional): angles to rotate about axes. Defaults to (0, 0, 0).\n align (Union[Align, tuple[Align, Align, Align]], optional): align min, center,\n or max of object. Defaults to (Align.CENTER, Align.CENTER, Align.MIN).\n mode (Mode, optional): combine mode. Defaults to Mode.ADD.\n \"\"\"\n\n _applies_to = [BuildPart._tag]\n\n def __init__(\n self,\n length: float = 8 * FT,\n width: float = 3.5 * IN,\n thickness: float = 1.5 * IN,\n rotation: RotationLike = (0, 0, 0),\n align: Union[None, Align, tuple[Align, Align, Align]] = (\n Align.CENTER,\n Align.CENTER,\n Align.MIN,\n ),\n mode: Mode = Mode.ADD,\n ):\n self.length = length\n self.width = width\n self.thickness = thickness\n\n # Create the basic shape\n with BuildPart() as stud:\n with BuildSketch():\n RectangleRounded(thickness, width, 0.25 * IN)\n extrude(amount=length)\n\n # Create a Part object with appropriate alignment and rotation\n super().__init__(part=stud.part, rotation=rotation, align=align, mode=mode)\n\n # Add joints to the ends of the stud\n RigidJoint(\"end0\", self, Location())\n RigidJoint(\"end1\", self, Location((0, 0, length), (1, 0, 0), 180))\n\n\nclass StudWall(Compound):\n \"\"\"StudWall\n\n A simple stud wall assembly with top and sole plates.\n\n Args:\n length (float): wall length\n depth (float, optional): stud width. Defaults to 3.5*IN.\n height (float, optional): wall height. Defaults to 8*FT.\n stud_spacing (float, optional): center-to-center. Defaults to 16*IN.\n stud_thickness (float, optional): Defaults to 1.5*IN.\n \"\"\"\n\n def __init__(\n self,\n length: float,\n depth: float = 3.5 * IN,\n height: float = 8 * FT,\n stud_spacing: float = 16 * IN,\n stud_thickness: float = 1.5 * IN,\n ):\n # Create the object that will be used for top and sole plates\n plate = Stud(\n length,\n depth,\n rotation=(0, -90, 0),\n align=(Align.MIN, Align.CENTER, Align.MAX),\n )\n # Define where studs will go on the plates\n stud_locations = Pos(stud_thickness / 2, 0, stud_thickness) * GridLocations(\n stud_spacing, 0, int(length / stud_spacing) + 1, 1, align=Align.MIN\n )\n stud_locations.append(Pos(length - stud_thickness / 2, 0, stud_thickness))\n\n # Create a single stud that will be copied for efficiency\n stud = Stud(height - 2 * stud_thickness, depth, stud_thickness)\n\n # For efficiency studs in the walls are copies with their own position\n studs = []\n for i, loc in enumerate(stud_locations):\n stud_joint = RigidJoint(f\"stud{i}\", plate, loc)\n stud_copy = copy.copy(stud)\n stud_joint.connect_to(stud_copy.joints[\"end0\"])\n studs.append(stud_copy)\n top_plate = copy.copy(plate)\n sole_plate = copy.copy(plate)\n\n # Position the top plate relative to the top of the first stud\n studs[0].joints[\"end1\"].connect_to(top_plate.joints[\"stud0\"])\n\n # Build the assembly of parts\n super().__init__(children=[top_plate, sole_plate] + studs)\n\n # Add joints to the wall\n RigidJoint(\"inside0\", self, Location((depth / 2, depth / 2, 0), (0, 0, 1), 90))\n RigidJoint(\"end0\", self, Location())\n\n\nx_wall = StudWall(13 * FT)\ny_wall = StudWall(9 * FT)\nx_wall.joints[\"inside0\"].connect_to(y_wall.joints[\"end0\"])\n\nshow(x_wall, y_wall, render_joints=False)\n# [End]\n" + }, + { + "id": "examples/tea_cup", + "source": "examples/tea_cup.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nwall_thickness = 3 * MM\nfillet_radius = wall_thickness * 0.49\n\nwith BuildPart() as tea_cup:\n # Create the bowl of the cup as a revolved cross section\n with BuildSketch(Plane.XZ) as bowl_section:\n with BuildLine():\n # Start & end points with control tangents\n s = Spline(\n (30 * MM, 10 * MM),\n (69 * MM, 105 * MM),\n tangents=((1, 0.5), (0.7, 1)),\n tangent_scalars=(1.75, 1),\n )\n # Lines to finish creating \u00bd the bowl shape\n Polyline(s @ 0, s @ 0 + (10 * MM, -10 * MM), (0, 0), (0, (s @ 1).Y), s @ 1)\n make_face() # Create a filled 2D shape\n revolve(axis=Axis.Z)\n # Hollow out the bowl with openings on the top and bottom\n offset(amount=-wall_thickness, openings=tea_cup.faces().filter_by(GeomType.PLANE))\n # Add a bottom to the bowl\n with Locations((0, 0, (s @ 0).Y)):\n Cylinder(radius=(s @ 0).X, height=wall_thickness)\n # Smooth out all the edges\n fillet(tea_cup.edges(), radius=fillet_radius)\n\n # Determine where the handle contacts the bowl\n handle_intersections = [\n tea_cup.part.find_intersection_points(\n Axis(origin=(0, 0, vertical_offset), direction=(1, 0, 0))\n )[-1][0]\n for vertical_offset in [35 * MM, 80 * MM]\n ]\n # Create a path for handle creation\n with BuildLine(Plane.XZ) as handle_path:\n handle_points = [\n Plane.XZ.to_local_coords(point) for point in handle_intersections\n ]\n Spline(\n handle_points[0] - (wall_thickness / 2, 0),\n handle_points[0] + (35 * MM, 30 * MM),\n handle_points[0] + (40 * MM, 60 * MM),\n handle_points[1] - (wall_thickness / 2, 0),\n tangents=((1, 1.25), (-0.2, -1)),\n )\n # Align the cross section to the beginning of the path\n with BuildSketch(handle_path.line ^ 0) as handle_cross_section:\n RectangleRounded(wall_thickness, 8 * MM, fillet_radius)\n sweep() # Sweep handle cross section along path\n\nassert abs(tea_cup.part.volume - 130326) < 1\n\nshow(tea_cup, names=[\"tea cup\"])\n# [End]\ntea_cup.part.color = Color(0xDFDCDA) # Porcelain\nexport_gltf(\n tea_cup.part,\n \"tea_cup.glb\",\n linear_deflection=0.1,\n angular_deflection=1,\n)\n" + }, + { + "id": "examples/tea_cup_algebra", + "source": "examples/tea_cup_algebra.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nwall_thickness = 3 * MM\nfillet_radius = wall_thickness * 0.49\n\n# Create the bowl of the cup as a revolved cross section\n\n# Start & end points with control tangents\ns = Spline(\n (30 * MM, 10 * MM),\n (69 * MM, 105 * MM),\n tangents=((1, 0.5), (0.7, 1)),\n tangent_scalars=(1.75, 1),\n)\n# Lines to finish creating \u00bd the bowl shape\ns += Polyline(s @ 0, s @ 0 + (10 * MM, -10 * MM), (0, 0), (0, (s @ 1).Y), s @ 1)\nbowl_section = Plane.XZ * make_face(s) # Create a filled 2D shape\ntea_cup = revolve(bowl_section, axis=Axis.Z)\n\n# Hollow out the bowl with openings on the top and bottom\ntea_cup = offset(\n tea_cup, -wall_thickness, openings=tea_cup.faces().filter_by(GeomType.PLANE)\n)\n\n# Add a bottom to the bowl\ntea_cup += Pos(0, 0, (s @ 0).Y) * Cylinder(radius=(s @ 0).X, height=wall_thickness)\n\n# Smooth out all the edges\ntea_cup = fillet(tea_cup.edges(), radius=fillet_radius)\n\n# Determine where the handle contacts the bowl\nhandle_intersections = [\n tea_cup.find_intersection_points(\n Axis(origin=(0, 0, vertical_offset), direction=(1, 0, 0))\n )[-1][0]\n for vertical_offset in [35 * MM, 80 * MM]\n]\n\n# Create a path for handle creation\npath_spline = Spline(\n handle_intersections[0] - (wall_thickness / 2, 0, 0),\n handle_intersections[0] + (35 * MM, 0, 30 * MM),\n handle_intersections[0] + (40 * MM, 0, 60 * MM),\n handle_intersections[1] - (wall_thickness / 2, 0, 0),\n tangents=((1, 0, 1.25), (-0.2, 0, -1)),\n)\n\n# Align the cross section to the beginning of the path\nlocation = path_spline ^ 0\nhandle_cross_section = location * RectangleRounded(wall_thickness, 8 * MM, fillet_radius)\n\n# Sweep handle cross section along path\ntea_cup += sweep(handle_cross_section, path=path_spline)\n\n# assert abs(tea_cup.part.volume - 130326.77052487945) < 1e-3\n\nshow(tea_cup, names=[\"tea cup\"])\n# [End]\n" + }, + { + "id": "examples/toy_truck", + "source": "examples/toy_truck.py", + "kind": "example", + "code": "# [Code]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n# Toy Truck Blue\ntruck_color = Color(0x4683CE)\n\n# Create the main truck body \u2014 from bumper to bed, excluding the cab\nwith BuildPart() as body:\n # The body has two axes of symmetry, so we start with a centered sketch.\n # The default workplane is Plane.XY.\n with BuildSketch() as body_skt:\n Rectangle(20, 35)\n # Fillet all the corners of the sketch.\n # Alternatively, you could use RectangleRounded.\n fillet(body_skt.vertices(), 1)\n\n # Extrude the body shape upward\n extrude(amount=10, taper=4)\n # Reuse the sketch by accessing it explicitly\n extrude(body_skt.sketch, amount=8, taper=2)\n\n # Create symmetric fenders on Plane.YZ\n with BuildSketch(Plane.YZ) as fender:\n # The trapezoid has asymmetric angles (80\u00b0, 88\u00b0)\n Trapezoid(18, 6, 80, 88, align=Align.MIN)\n # Fillet top edge vertices (Y-direction highest group)\n fillet(fender.vertices().group_by(Axis.Y)[-1], 1.5)\n\n # Extrude the fender in both directions\n extrude(amount=10.5, both=True)\n\n # Create wheel wells with a shifted sketch on Plane.YZ\n with BuildSketch(Plane.YZ.shift_origin((0, 3.5, 0))) as wheel_well:\n Trapezoid(12, 4, 70, 85, align=Align.MIN)\n fillet(wheel_well.vertices().group_by(Axis.Y)[-1], 2)\n\n # Subtract the wheel well geometry\n extrude(amount=10.5, both=True, mode=Mode.SUBTRACT)\n\n # Fillet the top edges of the body\n fillet(body.edges().group_by(Axis.Z)[-1], 1)\n\n # Isolate a set of body edges and preview before filleting\n body_edges = body.edges().group_by(Axis.Z)[-6]\n fillet(body_edges, 0.1)\n\n # Combine edge groups from both sides of the fender and fillet them\n fender_edges = body.edges().group_by(Axis.X)[0] + body.edges().group_by(Axis.X)[-1]\n fender_edges = fender_edges.group_by(Axis.Z)[1:]\n fillet(fender_edges, 0.4)\n\n # Create a sketch on the front of the truck for the grill\n with BuildSketch(\n Plane.XZ.offset(-body.vertices().sort_by(Axis.Y)[-1].Y - 0.5)\n ) as grill:\n Rectangle(16, 8.5, align=(Align.CENTER, Align.MIN))\n fillet(grill.vertices().group_by(Axis.Y)[-1], 1)\n\n # Add headlights (subtractive circles)\n with Locations((0, 6.5)):\n with GridLocations(12, 0, 2, 1):\n Circle(1, mode=Mode.SUBTRACT)\n\n # Add air vents (subtractive slots)\n with Locations((0, 3)):\n with GridLocations(0, 0.8, 1, 4):\n SlotOverall(10, 0.5, mode=Mode.SUBTRACT)\n\n # Extrude the grill forward\n extrude(amount=2)\n\n # Fillet only the outer grill edges (exclude headlight/vent cuts)\n grill_perimeter = body.faces().sort_by(Axis.Y)[-1].outer_wire()\n fillet(grill_perimeter.edges(), 0.2)\n\n # Create the bumper as a separate part inside the body\n with BuildPart() as bumper:\n # Find the midpoint of a front edge and shift slightly to position the bumper\n front_cnt = body.edges().group_by(Axis.Z)[0].sort_by(Axis.Y)[-1] @ 0.5 - (0, 3)\n\n with BuildSketch() as bumper_plan:\n # Use BuildLine to draw an elliptical arc and offset\n with BuildLine():\n EllipticalCenterArc(front_cnt, 20, 4, start_angle=60, arc_size=60)\n offset(amount=1)\n make_face()\n\n # Extrude the bumper symmetrically\n extrude(amount=1, both=True)\n fillet(bumper.edges(), 0.25)\n\n # Define a joint on top of the body to connect the cab later\n RigidJoint(\"body_top\", joint_location=Location((0, -7.5, 10)))\n body.part.color = truck_color\n\n# Create the cab as an independent part to mount on the body\nwith BuildPart() as cab:\n with BuildSketch() as cab_plan:\n RectangleRounded(16, 16, 1)\n # Split the sketch to work on one symmetric half\n split(bisect_by=Plane.YZ)\n\n # Extrude the cab forward and upward at an angle\n extrude(amount=7, dir=(0, 0.15, 1))\n fillet(cab.edges().group_by(Axis.Z)[-1].group_by(Axis.X)[1:], 1)\n\n # Rear window\n with BuildSketch(Plane.XZ.shift_origin((0, 0, 3))) as rear_window:\n RectangleRounded(8, 4, 0.75)\n extrude(amount=10, mode=Mode.SUBTRACT)\n\n # Front window\n with BuildSketch(Plane.XZ) as front_window:\n RectangleRounded(15.2, 11, 0.75)\n extrude(amount=-10, mode=Mode.SUBTRACT)\n\n # Side windows\n with BuildSketch(Plane.YZ) as side_window:\n with Locations((3.5, 0)):\n with GridLocations(10, 0, 2, 1):\n Trapezoid(9, 5.5, 80, 100, align=(Align.CENTER, Align.MIN))\n fillet(side_window.vertices().group_by(Axis.Y)[-1], 0.5)\n extrude(amount=12, both=True, mode=Mode.SUBTRACT)\n\n # Mirror to complete the cab\n mirror(about=Plane.YZ)\n\n # Define joint on cab base\n RigidJoint(\"cab_base\", joint_location=Location((0, 0, 0)))\n cab.part.color = truck_color\n\n# Attach the cab to the truck body using joints\nbody.joints[\"body_top\"].connect_to(cab.joints[\"cab_base\"])\n\n# Show the result\nshow(body.part, cab.part)\n# [End]\n" + }, + { + "id": "examples/twist_extrude", + "source": "examples/twist_extrude.py", + "kind": "example", + "code": "# [removed by collect.py] from ocp_vscode import show\n\nfrom build123d import *\n\nhex_sketch = RegularPolygon(radius=1, side_count=6)\n\ntwist_extrude = Solid.extrude_linear_with_rotation(\n section=hex_sketch.face(),\n center=(0, 0),\n normal=(0, 0, 5), # extrusion direction and distance\n angle=360 / 5, # 72 degrees of rotation over the extrusion height\n)\n\nshow(twist_extrude)\n" + }, + { + "id": "examples/vase", + "source": "examples/vase.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\nwith BuildPart() as vase:\n with BuildSketch() as profile:\n with BuildLine() as outline:\n l1 = Line((0, 0), (12, 0))\n l2 = RadiusArc(l1 @ 1, (15, 20), 50)\n l3 = Spline(l2 @ 1, (22, 40), (20, 50), tangents=(l2 % 1, (-0.75, 1)))\n l4 = RadiusArc(l3 @ 1, l3 @ 1 + Vector(0, 5), 5)\n l5 = Spline(\n l4 @ 1,\n l4 @ 1 + Vector(2.5, 2.5),\n l4 @ 1 + Vector(0, 5),\n tangents=(l4 % 1, (-1, 0)),\n )\n Polyline(\n l5 @ 1,\n l5 @ 1 + Vector(0, 1),\n (0, (l5 @ 1).Y + 1),\n l1 @ 0,\n )\n make_face()\n revolve(axis=Axis.Y)\n offset(openings=vase.faces().filter_by(Axis.Y)[-1], amount=-1)\n top_edges = (\n vase.edges().filter_by_position(Axis.Y, 60, 62).filter_by(GeomType.CIRCLE)\n )\n fillet(top_edges, radius=0.25)\n fillet(vase.edges().sort_by(Axis.Y)[0], radius=0.5)\n\n\nshow_object(Rot(90, 0, 0) * vase.part, name=\"vase\")\n# [End]\n" + }, + { + "id": "examples/vase_algebra", + "source": "examples/vase_algebra.py", + "kind": "example", + "code": "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\nl1 = Line((0, 0), (12, 0))\nl2 = RadiusArc(l1 @ 1, (15, 20), 50)\nl3 = Spline(l2 @ 1, (22, 40), (20, 50), tangents=(l2 % 1, (-0.75, 1)))\nl4 = RadiusArc(l3 @ 1, l3 @ 1 + Vector(0, 5), 5)\nl5 = Spline(\n l4 @ 1,\n l4 @ 1 + Vector(2.5, 2.5),\n l4 @ 1 + Vector(0, 5),\n tangents=(l4 % 1, (-1, 0)),\n)\noutline = l1 + l2 + l3 + l4 + l5\noutline += Polyline(\n l5 @ 1,\n l5 @ 1 + Vector(0, 1),\n (0, (l5 @ 1).Y + 1),\n l1 @ 0,\n)\nprofile = make_face(outline.edges())\nvase = revolve(profile, Axis.Y)\nvase = offset(vase, openings=vase.faces().sort_by(Axis.Y).last, amount=-1)\n\ntop_edges = vase.edges().filter_by(GeomType.CIRCLE).filter_by_position(Axis.Y, 60, 62)\nvase = fillet(top_edges, radius=0.25)\n\nvase = fillet(vase.edges().sort_by(Axis.Y).first, radius=0.5)\n\nshow_object(Rot(90, 0, 0) * vase, name=\"vase\")\n# [End]\n" + }, + { + "id": "general_examples/ex01", + "source": "docs/general_examples.py #1 (Simple Rectangular Plate)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 1. Simple Rectangular Plate\n# [Ex. 1]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex1:\n Box(length, width, thickness)\n # [Ex. 1]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex1.part)\n" + }, + { + "id": "general_examples/ex02", + "source": "docs/general_examples.py #2 (Plane with Hole)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 2. Plane with Hole\n# [Ex. 2]\nlength, width, thickness = 80.0, 60.0, 10.0\ncenter_hole_dia = 22.0\n\nwith BuildPart() as ex2:\n Box(length, width, thickness)\n Cylinder(radius=center_hole_dia / 2, height=thickness, mode=Mode.SUBTRACT)\n # [Ex. 2]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex2.part)\n" + }, + { + "id": "general_examples/ex03", + "source": "docs/general_examples.py #3 (An extruded prismatic solid)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 3. An extruded prismatic solid\n# [Ex. 3]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex3:\n with BuildSketch() as ex3_sk:\n Circle(width)\n Rectangle(length / 2, width / 2, mode=Mode.SUBTRACT)\n extrude(amount=2 * thickness)\n # [Ex. 3]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex3.part)\n" + }, + { + "id": "general_examples/ex08", + "source": "docs/general_examples.py #8 (Polylines)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 8. Polylines\n# [Ex. 8]\n(L, H, W, t) = (100.0, 20.0, 20.0, 1.0)\npts = [\n (0, H / 2.0),\n (W / 2.0, H / 2.0),\n (W / 2.0, (H / 2.0 - t)),\n (t / 2.0, (H / 2.0 - t)),\n (t / 2.0, (t - H / 2.0)),\n (W / 2.0, (t - H / 2.0)),\n (W / 2.0, H / -2.0),\n (0, H / -2.0),\n]\n\nwith BuildPart() as ex8:\n with BuildSketch(Plane.YZ) as ex8_sk:\n with BuildLine() as ex8_ln:\n Polyline(pts)\n mirror(ex8_ln.line, about=Plane.YZ)\n make_face()\n extrude(amount=L)\n # [Ex. 8]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex8.part)\n" + }, + { + "id": "general_examples/ex09", + "source": "docs/general_examples.py #9 (Selectors, fillets, and chamfers)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 9. Selectors, fillets, and chamfers\n# [Ex. 9]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex9:\n Box(length, width, thickness)\n chamfer(ex9.edges().group_by(Axis.Z)[-1], length=4)\n fillet(ex9.edges().filter_by(Axis.Z), radius=5)\n # [Ex. 9]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex9.part)\n" + }, + { + "id": "general_examples/ex10", + "source": "docs/general_examples.py #10 (Select Last and Hole)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 10. Select Last and Hole\n# [Ex. 10]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex10:\n Box(length, width, thickness)\n Hole(radius=width / 4)\n fillet(ex10.edges(Select.LAST).group_by(Axis.Z)[-1], radius=2)\n # [Ex. 10]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex10.part)\n" + }, + { + "id": "general_examples/ex11", + "source": "docs/general_examples.py #11 (Use a face as workplane for BuildSketch and introduce GridLocations)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 11. Use a face as workplane for BuildSketch and introduce GridLocations\n# [Ex. 11]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex11:\n Box(length, width, thickness)\n chamfer(ex11.edges().group_by(Axis.Z)[-1], length=4)\n fillet(ex11.edges().filter_by(Axis.Z), radius=5)\n Hole(radius=width / 4)\n fillet(ex11.edges(Select.LAST).sort_by(Axis.Z)[-1], radius=2)\n with BuildSketch(ex11.faces().sort_by(Axis.Z)[-1]) as ex11_sk:\n with GridLocations(length / 2, width / 2, 2, 2):\n RegularPolygon(radius=5, side_count=5)\n extrude(amount=-thickness, mode=Mode.SUBTRACT)\n # [Ex. 11]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex11)\n" + }, + { + "id": "general_examples/ex12", + "source": "docs/general_examples.py #12 (Defining an Edge with a Spline)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 12. Defining an Edge with a Spline\n# [Ex. 12]\npts = [\n (55, 30),\n (50, 35),\n (40, 30),\n (30, 20),\n (20, 25),\n (10, 20),\n (0, 20),\n]\n\nwith BuildPart() as ex12:\n with BuildSketch() as ex12_sk:\n with BuildLine() as ex12_ln:\n l1 = Spline(pts)\n l2 = Line((55, 30), (60, 0))\n l3 = Line((60, 0), (0, 0))\n l4 = Line((0, 0), (0, 20))\n make_face()\n extrude(amount=10)\n # [Ex. 12]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex12.part)\n" + }, + { + "id": "general_examples/ex13", + "source": "docs/general_examples.py #13 (CounterBoreHoles, CounterSinkHoles and PolarLocations)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 13. CounterBoreHoles, CounterSinkHoles and PolarLocations\n# [Ex. 13]\na, b = 40, 4\nwith BuildPart() as ex13:\n Cylinder(radius=50, height=10)\n with Locations(ex13.faces().sort_by(Axis.Z)[-1]):\n with PolarLocations(radius=a, count=4):\n CounterSinkHole(radius=b, counter_sink_radius=2 * b)\n with PolarLocations(radius=a, count=4, start_angle=45, angular_range=360):\n CounterBoreHole(radius=b, counter_bore_radius=2 * b, counter_bore_depth=b)\n # [Ex. 13]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex13.part)\n" + }, + { + "id": "general_examples/ex14", + "source": "docs/general_examples.py #14 (Position on a line with '@', '%' and introduce sweep)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 14. Position on a line with '@', '%' and introduce sweep\n# [Ex. 14]\na, b = 40, 20\n\nwith BuildPart() as ex14:\n with BuildLine() as ex14_ln:\n l1 = JernArc(start=(0, 0), tangent=(0, 1), radius=a, arc_size=180)\n l2 = JernArc(start=l1 @ 1, tangent=l1 % 1, radius=a, arc_size=-90)\n l3 = Line(l2 @ 1, l2 @ 1 + (-a, a))\n with BuildSketch(Plane.XZ) as ex14_sk:\n Rectangle(b, b)\n sweep()\n # [Ex. 14]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex14.part)\n" + }, + { + "id": "general_examples/ex15", + "source": "docs/general_examples.py #15 (Mirroring Symmetric Geometry)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 15. Mirroring Symmetric Geometry\n# [Ex. 15]\na, b, c = 80, 40, 20\n\nwith BuildPart() as ex15:\n with BuildSketch() as ex15_sk:\n with BuildLine() as ex15_ln:\n l1 = Line((0, 0), (a, 0))\n l2 = Line(l1 @ 1, l1 @ 1 + (0, b))\n l3 = Line(l2 @ 1, l2 @ 1 + (-c, 0))\n l4 = Line(l3 @ 1, l3 @ 1 + (0, -c))\n l5 = Line(l4 @ 1, (0, (l4 @ 1).Y))\n mirror(ex15_ln.line, about=Plane.YZ)\n make_face()\n extrude(amount=c)\n # [Ex. 15]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex15.part)\n" + }, + { + "id": "general_examples/ex16", + "source": "docs/general_examples.py #16 (Mirroring 3D Objects)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 16. Mirroring 3D Objects\n# same concept as CQ docs, but different object\n# [Ex. 16]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex16_single:\n with BuildSketch(Plane.XZ) as ex16_sk:\n Rectangle(length, width)\n fillet(ex16_sk.vertices(), radius=length / 10)\n with GridLocations(x_spacing=length / 4, y_spacing=0, x_count=3, y_count=1):\n Circle(length / 12, mode=Mode.SUBTRACT)\n Rectangle(length, width, align=(Align.MIN, Align.MIN), mode=Mode.SUBTRACT)\n extrude(amount=length)\n\nwith BuildPart() as ex16:\n add(ex16_single.part)\n mirror(ex16_single.part, about=Plane.XY.offset(width))\n mirror(ex16_single.part, about=Plane.YX.offset(width))\n mirror(ex16_single.part, about=Plane.YZ.offset(width))\n mirror(ex16_single.part, about=Plane.YZ.offset(-width))\n # [Ex. 16]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex16.part)\n" + }, + { + "id": "general_examples/ex17", + "source": "docs/general_examples.py #17 (Mirroring From Faces)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 17. Mirroring From Faces\n# [Ex. 17]\na, b = 30, 20\n\nwith BuildPart() as ex17:\n with BuildSketch() as ex17_sk:\n RegularPolygon(radius=a, side_count=5)\n extrude(amount=b)\n mirror(ex17.part, about=Plane(ex17.faces().group_by(Axis.Y)[0][0]))\n # [Ex. 17]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex17.part)\n" + }, + { + "id": "general_examples/ex18", + "source": "docs/general_examples.py #18 (Creating Workplanes on Faces)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 18. Creating Workplanes on Faces\n# based on Ex. 9\n# [Ex. 18]\nlength, width, thickness = 80.0, 60.0, 10.0\na, b = 4, 5\n\nwith BuildPart() as ex18:\n Box(length, width, thickness)\n chamfer(ex18.edges().group_by(Axis.Z)[-1], length=a)\n fillet(ex18.edges().filter_by(Axis.Z), radius=b)\n with BuildSketch(ex18.faces().sort_by(Axis.Z)[-1]):\n Rectangle(2 * b, 2 * b)\n extrude(amount=-thickness, mode=Mode.SUBTRACT)\n # [Ex. 18]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex18.part)\n" + }, + { + "id": "general_examples/ex19", + "source": "docs/general_examples.py #19 (Locating a Workplane on a vertex)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 19. Locating a Workplane on a vertex\n# [Ex. 19]\nlength, thickness = 80.0, 10.0\n\nwith BuildPart() as ex19:\n with BuildSketch() as ex19_sk:\n RegularPolygon(radius=length / 2, side_count=7)\n extrude(amount=thickness)\n topf = ex19.faces().sort_by(Axis.Z)[-1]\n vtx = topf.vertices().group_by(Axis.X)[-1][0]\n vtx2Axis = Axis((0, 0, 0), (-1, -0.5, 0))\n vtx2 = topf.vertices().sort_by(vtx2Axis)[-1]\n with BuildSketch(topf) as ex19_sk2:\n with Locations((vtx.X, vtx.Y), (vtx2.X, vtx2.Y)):\n Circle(radius=length / 8)\n extrude(amount=-thickness, mode=Mode.SUBTRACT)\n # [Ex. 19]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex19.part)\n" + }, + { + "id": "general_examples/ex20", + "source": "docs/general_examples.py #20 (Offset Sketch Workplane)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 20. Offset Sketch Workplane\n# [Ex. 20]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex20:\n Box(length, width, thickness)\n plane = Plane(ex20.faces().group_by(Axis.X)[0][0])\n with BuildSketch(plane.offset(2 * thickness)):\n Circle(width / 3)\n extrude(amount=width)\n # [Ex. 20]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex20.part)\n" + }, + { + "id": "general_examples/ex21", + "source": "docs/general_examples.py #21 (Copying Workplanes)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 21. Copying Workplanes\n# [Ex. 21]\nwidth, length = 10.0, 60.0\n\nwith BuildPart() as ex21:\n with BuildSketch() as ex21_sk:\n Circle(width / 2)\n extrude(amount=length)\n with BuildSketch(Plane(origin=ex21.part.center(), z_dir=(-1, 0, 0))):\n Circle(width / 2)\n extrude(amount=length)\n # [Ex. 21]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex21.part)\n" + }, + { + "id": "general_examples/ex22", + "source": "docs/general_examples.py #22 (Rotated Workplanes)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 22. Rotated Workplanes\n# [Ex. 22]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex22:\n Box(length, width, thickness)\n pln = Plane(ex22.faces().group_by(Axis.Z)[0][0]).rotated((0, -50, 0))\n with BuildSketch(pln) as ex22_sk:\n with GridLocations(length / 4, width / 4, 2, 2):\n Circle(thickness / 4)\n extrude(amount=-100, both=True, mode=Mode.SUBTRACT)\n # [Ex. 22]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex22.part)\n" + }, + { + "id": "general_examples/ex23", + "source": "docs/general_examples.py #23 (Revolve)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 23. Revolve\n# [Ex. 23]\npts = [\n (-25, 35),\n (-25, 0),\n (-20, 0),\n (-20, 5),\n (-15, 10),\n (-15, 35),\n]\n\nwith BuildPart() as ex23:\n with BuildSketch(Plane.XZ) as ex23_sk:\n with BuildLine() as ex23_ln:\n l1 = Polyline(pts)\n l2 = Line(l1 @ 1, l1 @ 0)\n make_face()\n with Locations((0, 35)):\n Circle(25)\n split(bisect_by=Plane.ZY)\n revolve(axis=Axis.Z)\n # [Ex. 23]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex23.part)\n" + }, + { + "id": "general_examples/ex24", + "source": "docs/general_examples.py #24 (Lofts)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 24. Lofts\n# [Ex. 24]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex24:\n Box(length, length, thickness)\n with BuildSketch(ex24.faces().group_by(Axis.Z)[0][0]) as ex24_sk:\n Circle(length / 3)\n with BuildSketch(ex24_sk.faces()[0].offset(length / 2)) as ex24_sk2:\n Rectangle(length / 6, width / 6)\n loft()\n # [Ex. 24]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex24.part)\n" + }, + { + "id": "general_examples/ex25", + "source": "docs/general_examples.py #25 (Offset Sketch)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 25. Offset Sketch\n# [Ex. 25]\nrad, offs = 50, 10\n\nwith BuildPart() as ex25:\n with BuildSketch() as ex25_sk1:\n RegularPolygon(radius=rad, side_count=5)\n with BuildSketch(Plane.XY.offset(15)) as ex25_sk2:\n RegularPolygon(radius=rad, side_count=5)\n offset(amount=offs)\n with BuildSketch(Plane.XY.offset(30)) as ex25_sk3:\n RegularPolygon(radius=rad, side_count=5)\n offset(amount=offs, kind=Kind.INTERSECTION)\n extrude(amount=1)\n # [Ex. 25]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex25.part)\n" + }, + { + "id": "general_examples/ex26", + "source": "docs/general_examples.py #26 (Offset Part To Create Thin features)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 26. Offset Part To Create Thin features\n# [Ex. 26]\nlength, width, thickness, wall = 80.0, 60.0, 10.0, 2.0\n\nwith BuildPart() as ex26:\n Box(length, width, thickness)\n topf = ex26.faces().sort_by(Axis.Z)[-1]\n offset(amount=-wall, openings=topf)\n # [Ex. 26]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex26.part)\n" + }, + { + "id": "general_examples/ex27", + "source": "docs/general_examples.py #27 (Splitting an Object)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 27. Splitting an Object\n# [Ex. 27]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex27:\n Box(length, width, thickness)\n with BuildSketch(ex27.faces().sort_by(Axis.Z)[0]) as ex27_sk:\n Circle(width / 4)\n extrude(amount=-thickness, mode=Mode.SUBTRACT)\n split(bisect_by=Plane(ex27.faces().sort_by(Axis.Y)[-1]).offset(-width / 2))\n # [Ex. 27]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex27.part)\n" + }, + { + "id": "general_examples/ex28", + "source": "docs/general_examples.py #28 (Locating features based on Faces)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 28. Locating features based on Faces\n# [Ex. 28]\nwidth, thickness = 80.0, 10.0\n\nwith BuildPart() as ex28:\n with BuildSketch() as ex28_sk:\n RegularPolygon(radius=width / 4, side_count=3)\n ex28_ex = extrude(amount=thickness, mode=Mode.PRIVATE)\n midfaces = ex28_ex.faces().group_by(Axis.Z)[1]\n Sphere(radius=width / 2)\n for face in midfaces:\n with Locations(face):\n Hole(thickness / 2)\n # [Ex. 28]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex28.part)\n" + }, + { + "id": "general_examples/ex29", + "source": "docs/general_examples.py #29 (The Classic OCC Bottle)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 29. The Classic OCC Bottle\n# [Ex. 29]\nL, w, t, b, h, n = 60.0, 18.0, 9.0, 0.9, 90.0, 6.0\n\nwith BuildPart() as ex29:\n with BuildSketch(Plane.XY.offset(-b)) as ex29_ow_sk:\n with BuildLine() as ex29_ow_ln:\n l1 = Line((0, 0), (0, w / 2))\n l2 = ThreePointArc(l1 @ 1, (L / 2.0, w / 2.0 + t), (L, w / 2.0))\n l3 = Line(l2 @ 1, ((l2 @ 1).X, 0, 0))\n mirror(ex29_ow_ln.line)\n make_face()\n extrude(amount=h + b)\n fillet(ex29.edges(), radius=w / 6)\n with BuildSketch(ex29.faces().sort_by(Axis.Z)[-1]):\n Circle(t)\n extrude(amount=n)\n necktopf = ex29.faces().sort_by(Axis.Z)[-1]\n offset(ex29.solids()[0], amount=-b, openings=necktopf)\n # [Ex. 29]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex29.part)\n" + }, + { + "id": "general_examples/ex30", + "source": "docs/general_examples.py #30 (Bezier Curve)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 30. Bezier Curve\n# [Ex. 30]\npts = [\n (0, 0),\n (20, 20),\n (40, 0),\n (0, -40),\n (-60, 0),\n (0, 100),\n (100, 0),\n]\n\nwts = [\n 1.0,\n 1.0,\n 2.0,\n 3.0,\n 4.0,\n 2.0,\n 1.0,\n]\n\nwith BuildPart() as ex30:\n with BuildSketch() as ex30_sk:\n with BuildLine() as ex30_ln:\n l0 = Polyline(pts)\n l1 = Bezier(pts, weights=wts)\n make_face()\n extrude(amount=10)\n # [Ex. 30]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex30.part)\n" + }, + { + "id": "general_examples/ex31", + "source": "docs/general_examples.py #31 (Nesting Locations)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 31. Nesting Locations\n# [Ex. 31]\na, b, c = 80.0, 5.0, 3.0\n\nwith BuildPart() as ex31:\n with BuildSketch() as ex31_sk:\n with PolarLocations(a / 2, 6):\n with GridLocations(3 * b, 3 * b, 2, 2):\n RegularPolygon(b, 3)\n RegularPolygon(b, 4)\n RegularPolygon(3 * b, 6, rotation=30)\n extrude(amount=c)\n # [Ex. 31]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex31.part)\n" + }, + { + "id": "general_examples/ex32", + "source": "docs/general_examples.py #32 (Python for-loop)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 32. Python for-loop\n# [Ex. 32]\na, b, c = 80.0, 10.0, 1.0\n\nwith BuildPart() as ex32:\n with BuildSketch(mode=Mode.PRIVATE) as ex32_sk:\n RegularPolygon(2 * b, 6, rotation=30)\n with PolarLocations(a / 2, 6):\n RegularPolygon(b, 4)\n for idx, obj in enumerate(ex32_sk.sketch.faces()):\n add(obj)\n extrude(amount=c + 3 * idx)\n # [Ex. 32]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex32.part)\n" + }, + { + "id": "general_examples/ex33", + "source": "docs/general_examples.py #33 (Python function and for-loop)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 33. Python function and for-loop\n# [Ex. 33]\na, b, c = 80.0, 5.0, 1.0\n\n\ndef square(rad, loc):\n with BuildSketch() as sk:\n with Locations(loc):\n RegularPolygon(rad, 4)\n return sk.sketch\n\n\nwith BuildPart() as ex33:\n with BuildSketch(mode=Mode.PRIVATE) as ex33_sk:\n locs = PolarLocations(a / 2, 6)\n for i, j in enumerate(locs):\n add(square(b + 2 * i, j))\n for idx, obj in enumerate(ex33_sk.sketch.faces()):\n add(obj)\n extrude(amount=c + 2 * idx)\n # [Ex. 33]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex33.part)\n" + }, + { + "id": "general_examples/ex34", + "source": "docs/general_examples.py #34 (Embossed and Debossed Text)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 34. Embossed and Debossed Text\n# [Ex. 34]\nlength, width, thickness, fontsz, fontht = 80.0, 60.0, 10.0, 25.0, 4.0\n\nwith BuildPart() as ex34:\n Box(length, width, thickness)\n topf = ex34.faces().sort_by(Axis.Z)[-1]\n with BuildSketch(topf) as ex34_sk:\n Text(\"Hello\", font_size=fontsz, align=(Align.CENTER, Align.MIN))\n extrude(amount=fontht)\n with BuildSketch(topf) as ex34_sk2:\n Text(\"World\", font_size=fontsz, align=(Align.CENTER, Align.MAX))\n extrude(amount=-fontht, mode=Mode.SUBTRACT)\n # [Ex. 34]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex34.part)\n" + }, + { + "id": "general_examples/ex35", + "source": "docs/general_examples.py #35 (Slots)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 35. Slots\n# [Ex. 35]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex35:\n Box(length, length, thickness)\n topf = ex35.faces().sort_by(Axis.Z)[-1]\n with BuildSketch(topf) as ex35_sk:\n SlotCenterToCenter(width / 2, 10)\n with BuildLine(mode=Mode.PRIVATE) as ex35_ln:\n RadiusArc((-width / 2, 0), (0, width / 2), radius=width / 2)\n SlotArc(arc=ex35_ln.edges()[0], height=thickness, rotation=0)\n with BuildLine(mode=Mode.PRIVATE) as ex35_ln2:\n RadiusArc((0, -width / 2), (width / 2, 0), radius=-width / 2)\n SlotArc(arc=ex35_ln2.edges()[0], height=thickness, rotation=0)\n extrude(amount=-thickness, mode=Mode.SUBTRACT)\n # [Ex. 35]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex35.part)\n" + }, + { + "id": "general_examples/ex36", + "source": "docs/general_examples.py #36 (Extrude-Until)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 36. Extrude-Until\n# [Ex. 36]\nrad, rev = 6, 50\n\nwith BuildPart() as ex36:\n with BuildSketch() as ex36_sk:\n with Locations((0, rev)):\n Circle(rad)\n revolve(axis=Axis.X, revolution_arc=180)\n with BuildSketch() as ex36_sk2:\n Rectangle(rad, rev)\n extrude(until=Until.NEXT)\n # [Ex. 36]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex36.part)\n" + }, + { + "id": "general_examples/ex37", + "source": "docs/general_examples.py #37 (Positioning Sketches Within a Plane)", + "kind": "docs-builder", + "code": "from build123d import *\nfrom math import *\n\n# 37. Positioning Sketches Within a Plane\n# [Ex. 37]\nwith BuildPart() as ex37:\n with BuildSketch() as ex37_sk:\n Rectangle(1, 2, align=(Align.CENTER, Align.MIN))\n with BuildSketch(\n Plane.XY.shift_origin(ex37_sk.vertices().group_by(Axis.Y)[0].sort_by(Axis.X)[0])\n ):\n Circle(1)\n with BuildSketch(Plane((0.5, 2))):\n Ellipse(0.5, 1)\n extrude(amount=1)\n # [Ex. 37]\n pass # [removed by collect.py] write_svg()\n\n# show_object(ex37.part)\n" + }, + { + "id": "general_examples_algebra/ex01", + "source": "docs/general_examples_algebra.py #1 (Simple Rectangular Plate)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 1. Simple Rectangular Plate\n# [Ex. 1]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nex1 = Box(length, width, thickness)\n# [Ex. 1]\n# show_object(ex1)\n" + }, + { + "id": "general_examples_algebra/ex02", + "source": "docs/general_examples_algebra.py #2 (Plane with hole)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 2. Plane with hole\n# [Ex. 2]\nlength, width, thickness = 80.0, 60.0, 10.0\ncenter_hole_dia = 22.0\n\nex2 = Box(length, width, thickness)\nex2 -= Cylinder(center_hole_dia / 2, height=thickness)\n# [Ex. 2]\n# show_object(ex2)\n" + }, + { + "id": "general_examples_algebra/ex03", + "source": "docs/general_examples_algebra.py #3 (An extruded prismatic solid)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 3. An extruded prismatic solid\n# [Ex. 3]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nsk3 = Circle(width) - Rectangle(length / 2, width / 2)\nex3 = extrude(sk3, amount=2 * thickness)\n# [Ex. 3]\n# show_object(ex3)\n" + }, + { + "id": "general_examples_algebra/ex08", + "source": "docs/general_examples_algebra.py #8 (Polylines)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 8. Polylines\n# [Ex. 8]\n(L, H, W, t) = (100.0, 20.0, 20.0, 1.0)\npts = [\n (0, H / 2.0),\n (W / 2.0, H / 2.0),\n (W / 2.0, (H / 2.0 - t)),\n (t / 2.0, (H / 2.0 - t)),\n (t / 2.0, (t - H / 2.0)),\n (W / 2.0, (t - H / 2.0)),\n (W / 2.0, H / -2.0),\n (0, H / -2.0),\n]\n\nln = Polyline(pts)\nln += mirror(ln, Plane.YZ)\n\nsk8 = make_face(Plane.YZ * ln)\nex8 = extrude(sk8, -L).clean()\n# [Ex. 8]\n# show_object(ex8)\n" + }, + { + "id": "general_examples_algebra/ex09", + "source": "docs/general_examples_algebra.py #9 (Selectors, fillets, and chamfers)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 9. Selectors, fillets, and chamfers\n# [Ex. 9]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nex9 = Part() + Box(length, width, thickness)\nex9 = chamfer(ex9.edges().group_by(Axis.Z)[-1], length=4)\nex9 = fillet(ex9.edges().filter_by(Axis.Z), radius=5)\n# [Ex. 9]\n# show_object(ex9)\n" + }, + { + "id": "general_examples_algebra/ex10", + "source": "docs/general_examples_algebra.py #10 (Select last edges and Hole)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 10. Select last edges and Hole\n# [Ex. 10]\nex10 = Part() + Box(length, width, thickness)\n\nsnapshot = ex10.edges()\nex10 -= Hole(radius=width / 4, depth=thickness)\nlast_edges = ex10.edges() - snapshot\nex10 = fillet(last_edges.group_by(Axis.Z)[-1], 2)\n# [Ex. 10]\n# show_object(ex10)\n" + }, + { + "id": "general_examples_algebra/ex11", + "source": "docs/general_examples_algebra.py #11 (Use a face as workplane for BuildSketch and introduce GridLocations)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 11. Use a face as workplane for BuildSketch and introduce GridLocations\n# [Ex. 11]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nex11 = Part() + Box(length, width, thickness)\nex11 = chamfer(ex11.edges().group_by()[-1], 4)\nex11 = fillet(ex11.edges().filter_by(Axis.Z), 5)\nlast = ex11.edges()\nex11 -= Hole(radius=width / 4, depth=thickness)\nex11 = fillet((ex11.edges() - last).sort_by().last, 2)\n\nplane = Plane(ex11.faces().sort_by().last)\npolygons = Sketch() + [\n plane * loc * RegularPolygon(radius=5, side_count=5)\n for loc in GridLocations(length / 2, width / 2, 2, 2)\n]\nex11 -= extrude(polygons, -thickness)\n# [Ex. 11]\n# show_object(ex11)\n" + }, + { + "id": "general_examples_algebra/ex12", + "source": "docs/general_examples_algebra.py #12 (Defining an Edge with a Spline)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 12. Defining an Edge with a Spline\n# [Ex. 12]\npts = [\n (55, 30),\n (50, 35),\n (40, 30),\n (30, 20),\n (20, 25),\n (10, 20),\n (0, 20),\n]\n\nl1 = Spline(pts)\nl2 = Line(l1 @ 0, (60, 0))\nl3 = Line(l2 @ 1, (0, 0))\nl4 = Line(l3 @ 1, l1 @ 1)\n\nsk12 = make_face([l1, l2, l3, l4])\nex12 = extrude(sk12, 10)\n# [Ex. 12]\n# show_object(ex12)\n" + }, + { + "id": "general_examples_algebra/ex13", + "source": "docs/general_examples_algebra.py #13 (CounterBoreHoles, CounterSinkHoles and PolarLocations)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 13. CounterBoreHoles, CounterSinkHoles and PolarLocations\n# [Ex. 13]\na, b = 40, 4\n\nex13 = Cylinder(radius=50, height=10)\nplane = Plane(ex13.faces().sort_by().last)\n\nex13 -= (\n plane\n * PolarLocations(radius=a, count=4)\n * CounterSinkHole(radius=b, counter_sink_radius=2 * b, depth=10)\n)\nex13 -= (\n plane\n * PolarLocations(radius=a, count=4, start_angle=45, angular_range=360)\n * CounterBoreHole(\n radius=b, counter_bore_radius=2 * b, depth=10, counter_bore_depth=b\n )\n)\n# [Ex. 13]\n# show_object(ex13)\n" + }, + { + "id": "general_examples_algebra/ex14", + "source": "docs/general_examples_algebra.py #14 (Position on a line with '@', '%' and introduce Sweep)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 14. Position on a line with '@', '%' and introduce Sweep\n# [Ex. 14]\na, b = 40, 20\n\nl1 = JernArc(start=(0, 0), tangent=(0, 1), radius=a, arc_size=180)\nl2 = JernArc(start=l1 @ 1, tangent=l1 % 1, radius=a, arc_size=-90)\nl3 = Line(l2 @ 1, l2 @ 1 + (-a, a))\nex14_ln = l1 + l2 + l3\n\nsk14 = Plane.XZ * Rectangle(b, b)\nex14 = sweep(sk14, path=ex14_ln)\n# [Ex. 14]\n# show_object(ex14)\n" + }, + { + "id": "general_examples_algebra/ex15", + "source": "docs/general_examples_algebra.py #15 (Mirroring Symmetric Geometry)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 15. Mirroring Symmetric Geometry\n# [Ex. 15]\na, b, c = 80, 40, 20\n\nl1 = Line((0, 0), (a, 0))\nl2 = Line(l1 @ 1, l1 @ 1 + (0, b))\nl3 = Line(l2 @ 1, l2 @ 1 + (-c, 0))\nl4 = Line(l3 @ 1, l3 @ 1 + (0, -c))\nl5 = Line(l4 @ 1, (0, (l4 @ 1).Y))\nln = Curve() + [l1, l2, l3, l4, l5]\nln += mirror(ln, Plane.YZ)\n\nsk15 = make_face(ln)\nex15 = extrude(sk15, c)\n# [Ex. 15]\n# show_object(ex15)\n" + }, + { + "id": "general_examples_algebra/ex16", + "source": "docs/general_examples_algebra.py #16 (Mirroring 3D Objects)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 16. Mirroring 3D Objects\n# same concept as CQ docs, but different object\n# [Ex. 16]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nsk16 = Rectangle(length, width)\nsk16 = fillet(sk16.vertices(), length / 10)\n\ncircles = [loc * Circle(length / 12) for loc in GridLocations(length / 4, 0, 3, 1)]\n\nsk16 = sk16 - circles - Rectangle(length, width, align=(Align.MIN, Align.MIN))\nex16_single = extrude(Plane.XZ * sk16, length)\n\nplanes = [\n Plane.XY.offset(width),\n Plane.YX.offset(width),\n Plane.YZ.offset(width),\n Plane.YZ.offset(-width),\n]\nobjs = [mirror(ex16_single, plane) for plane in planes]\nex16 = ex16_single + objs\n# [Ex. 16]\n# show_object(ex16)\n" + }, + { + "id": "general_examples_algebra/ex17", + "source": "docs/general_examples_algebra.py #17 (Mirroring From Faces)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 17. Mirroring From Faces\n# [Ex. 17]\na, b = 30, 20\n\nsk17 = RegularPolygon(radius=a, side_count=5)\nex17 = extrude(sk17, amount=b)\nex17 += mirror(ex17, Plane(ex17.faces().sort_by(Axis.Y).first))\n# [Ex. 17]\n# show_object(ex17)\n" + }, + { + "id": "general_examples_algebra/ex18", + "source": "docs/general_examples_algebra.py #18 (Creating Workplanes on Faces)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 18. Creating Workplanes on Faces\n# based on Ex. 9\n# [Ex. 18]\nlength, width, thickness = 80.0, 60.0, 10.0\na, b = 4, 5\n\nex18 = Part() + Box(length, width, thickness)\nex18 = chamfer(ex18.edges().group_by()[-1], a)\nex18 = fillet(ex18.edges().filter_by(Axis.Z), b)\n\nsk18 = Plane(ex18.faces().sort_by().first) * Rectangle(2 * b, 2 * b)\nex18 -= extrude(sk18, -thickness)\n# [Ex. 18]\n# show_object(ex18)\n" + }, + { + "id": "general_examples_algebra/ex19", + "source": "docs/general_examples_algebra.py #19 (Locating a Workplane on a vertex)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 19. Locating a Workplane on a vertex\n# [Ex. 19]\nlength, thickness = 80.0, 10.0\n\nex19_sk = RegularPolygon(radius=length / 2, side_count=7)\nex19 = extrude(ex19_sk, thickness)\n\ntopf = ex19.faces().sort_by().last\n\nvtx = topf.vertices().group_by(Axis.X)[-1][0]\n\nvtx2Axis = Axis((0, 0, 0), (-1, -0.5, 0))\nvtx2 = topf.vertices().sort_by(vtx2Axis)[-1]\n\nex19_sk2 = Circle(radius=length / 8)\nex19_sk2 = Pos(vtx.X, vtx.Y) * ex19_sk2 + Pos(vtx2.X, vtx2.Y) * ex19_sk2\n\nex19 -= extrude(ex19_sk2, thickness)\n# [Ex. 19]\n# show_object(ex19)\n" + }, + { + "id": "general_examples_algebra/ex20", + "source": "docs/general_examples_algebra.py #20 (Offset Sketch Workplane)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 20. Offset Sketch Workplane\n# [Ex. 20]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nex20 = Box(length, width, thickness)\nplane = Plane(ex20.faces().sort_by(Axis.X).first).offset(2 * thickness)\n\nsk20 = plane * Circle(width / 3)\nex20 += extrude(sk20, width)\n# [Ex. 20]\n# show_object(ex20)\n" + }, + { + "id": "general_examples_algebra/ex21", + "source": "docs/general_examples_algebra.py #21 (Copying Workplanes)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 21. Copying Workplanes\n# [Ex. 21]\nwidth, length = 10.0, 60.0\n\nex21 = extrude(Circle(width / 2), length)\nplane = Plane(origin=ex21.center(), z_dir=(-1, 0, 0))\nex21 += plane * extrude(Circle(width / 2), length)\n# [Ex. 21]\n# show_object(ex21)\n" + }, + { + "id": "general_examples_algebra/ex22", + "source": "docs/general_examples_algebra.py #22 (Rotated Workplanes)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 22. Rotated Workplanes\n# [Ex. 22]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nex22 = Box(length, width, thickness)\nplane = Plane((ex22.faces().group_by(Axis.Z)[0])[0]) * Rot(0, 50, 0)\n\nholes = Sketch() + [\n plane * loc * Circle(thickness / 4)\n for loc in GridLocations(length / 4, width / 4, 2, 2)\n]\nex22 -= extrude(holes, -100, both=True)\n# [Ex. 22]\n# show_object(ex22)\n" + }, + { + "id": "general_examples_algebra/ex23", + "source": "docs/general_examples_algebra.py #23 (Revolve)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 23. Revolve\n# [Ex. 23]\npts = [\n (-25, 35),\n (-25, 0),\n (-20, 0),\n (-20, 5),\n (-15, 10),\n (-15, 35),\n]\n\nl1 = Polyline(pts)\nl2 = Line(l1 @ 1, l1 @ 0)\nsk23 = make_face([l1, l2])\n\nsk23 += Pos(0, 35) * Circle(25)\nsk23 = Plane.XZ * split(sk23, bisect_by=Plane.ZY)\n\nex23 = revolve(sk23, Axis.Z)\n# [Ex. 23]\n# show_object(ex23)\n" + }, + { + "id": "general_examples_algebra/ex24", + "source": "docs/general_examples_algebra.py #24 (Lofts)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 24. Lofts\n# [Ex. 24]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nex24 = Box(length, length, thickness)\nplane = Plane(ex24.faces().sort_by().last)\n\nfaces = Sketch() + [\n plane * Circle(length / 3),\n plane.offset(length / 2) * Rectangle(length / 6, width / 6),\n]\n\nex24 += loft(faces)\n# [Ex. 24]\n# show_object(ex24)\n" + }, + { + "id": "general_examples_algebra/ex25", + "source": "docs/general_examples_algebra.py #25 (Offset Sketch)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 25. Offset Sketch\n# [Ex. 25]\nrad, offs = 50, 10\n\nsk25_1 = RegularPolygon(radius=rad, side_count=5)\nsk25_2 = Plane.XY.offset(15) * RegularPolygon(radius=rad, side_count=5)\nsk25_2 = offset(sk25_2, offs)\nsk25_3 = Plane.XY.offset(30) * RegularPolygon(radius=rad, side_count=5)\nsk25_3 = offset(sk25_3, offs, kind=Kind.INTERSECTION)\n\nsk25 = Sketch() + [sk25_1, sk25_2, sk25_3]\nex25 = extrude(sk25, 1)\n# [Ex. 25]\n# show_object(ex25)\n" + }, + { + "id": "general_examples_algebra/ex26", + "source": "docs/general_examples_algebra.py #26 (Offset Part To Create Thin features)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 26. Offset Part To Create Thin features\n# [Ex. 26]\nlength, width, thickness, wall = 80.0, 60.0, 10.0, 2.0\n\nex26 = Box(length, width, thickness)\ntopf = ex26.faces().sort_by().last\nex26 = offset(ex26, amount=-wall, openings=topf)\n# [Ex. 26]\n# show_object(ex26)\n" + }, + { + "id": "general_examples_algebra/ex27", + "source": "docs/general_examples_algebra.py #27 (Splitting an Object)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 27. Splitting an Object\n# [Ex. 27]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nex27 = Box(length, width, thickness)\nsk27 = Plane(ex27.faces().sort_by().first) * Circle(width / 4)\nex27 -= extrude(sk27, -thickness)\nex27 = split(ex27, Plane(ex27.faces().sort_by(Axis.Y).last).offset(-width / 2))\n# [Ex. 27]\n# show_object(ex27)\n" + }, + { + "id": "general_examples_algebra/ex28", + "source": "docs/general_examples_algebra.py #28 (Locating features based on Faces)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 28. Locating features based on Faces\n# [Ex. 28]\nwidth, thickness = 80.0, 10.0\n\nsk28 = RegularPolygon(radius=width / 4, side_count=3)\ntmp28 = extrude(sk28, thickness)\nex28 = Sphere(radius=width / 2)\nfor p in [Plane(face) for face in tmp28.faces().group_by(Axis.Z)[1]]:\n ex28 -= p * Hole(thickness / 2, depth=width)\n# [Ex. 28]\n# show_object(ex28)\n" + }, + { + "id": "general_examples_algebra/ex29", + "source": "docs/general_examples_algebra.py #29 (The Classic OCC Bottle)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 29. The Classic OCC Bottle\n# [Ex. 29]\nL, w, t, b, h, n = 60.0, 18.0, 9.0, 0.9, 90.0, 8.0\n\nl1 = Line((0, 0), (0, w / 2))\nl2 = ThreePointArc(l1 @ 1, (L / 2.0, w / 2.0 + t), (L, w / 2.0))\nl3 = Line(l2 @ 1, ((l2 @ 1).X, 0, 0))\nln29 = l1 + l2 + l3\nln29 += mirror(ln29)\nsk29 = make_face(ln29)\nex29 = extrude(sk29, -(h + b))\nex29 = fillet(ex29.edges(), radius=w / 6)\n\nneck = Plane(ex29.faces().sort_by().last) * Circle(t)\nex29 += extrude(neck, n)\nnecktopf = ex29.faces().sort_by().last\nex29 = offset(ex29, -b, openings=necktopf)\n# [Ex. 29]\n# show_object(ex29)\n" + }, + { + "id": "general_examples_algebra/ex30", + "source": "docs/general_examples_algebra.py #30 (Bezier Curve)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 30. Bezier Curve\n# [Ex. 30]\npts = [\n (0, 0),\n (20, 20),\n (40, 0),\n (0, -40),\n (-60, 0),\n (0, 100),\n (100, 0),\n]\n\nwts = [\n 1.0,\n 1.0,\n 2.0,\n 3.0,\n 4.0,\n 2.0,\n 1.0,\n]\n\nex30_ln = Polyline(pts) + Bezier(pts, weights=wts)\nex30_sk = make_face(ex30_ln)\nex30 = extrude(ex30_sk, -10)\n# [Ex. 30]\n# show_object(ex30)\n" + }, + { + "id": "general_examples_algebra/ex31", + "source": "docs/general_examples_algebra.py #31 (Nesting Locations)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 31. Nesting Locations\n# [Ex. 31]\na, b, c = 80.0, 5.0, 3.0\n\nex31 = Rot(Z=30) * RegularPolygon(3 * b, 6)\nex31 += PolarLocations(a / 2, 6) * (\n RegularPolygon(b, 4) + GridLocations(3 * b, 3 * b, 2, 2) * RegularPolygon(b, 3)\n)\nex31 = extrude(ex31, 3)\n# [Ex. 31]\n# show_object(ex31)\n" + }, + { + "id": "general_examples_algebra/ex32", + "source": "docs/general_examples_algebra.py #32 (Python for-loop)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 32. Python for-loop\n# [Ex. 32]\na, b, c = 80.0, 10.0, 1.0\n\nex32_sk = RegularPolygon(2 * b, 6, rotation=30)\nex32_sk += PolarLocations(a / 2, 6) * RegularPolygon(b, 4)\nex32 = Part() + [extrude(obj, c + 3 * idx) for idx, obj in enumerate(ex32_sk.faces())]\n# [Ex. 32]\n# show_object(ex32)\n" + }, + { + "id": "general_examples_algebra/ex33", + "source": "docs/general_examples_algebra.py #33 (Python function and for-loop)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 33. Python function and for-loop\n# [Ex. 33]\na, b, c = 80.0, 5.0, 1.0\n\n\ndef square(rad, loc):\n return loc * RegularPolygon(rad, 4)\n\n\nex33 = Part() + [\n extrude(square(b + 2 * i, loc), c + 2 * i)\n for i, loc in enumerate(PolarLocations(a / 2, 6))\n]\n# [Ex. 33]\n# show_object(ex33)\n" + }, + { + "id": "general_examples_algebra/ex34", + "source": "docs/general_examples_algebra.py #34 (Embossed and Debossed Text)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 34. Embossed and Debossed Text\n# [Ex. 34]\nlength, width, thickness, fontsz, fontht = 80.0, 60.0, 10.0, 25.0, 4.0\n\nex34 = Box(length, width, thickness)\nplane = Plane(ex34.faces().sort_by().last)\nex34_sk = plane * Text(\"Hello\", font_size=fontsz, align=(Align.CENTER, Align.MIN))\nex34 += extrude(ex34_sk, amount=fontht)\nex34_sk2 = plane * Text(\"World\", font_size=fontsz, align=(Align.CENTER, Align.MAX))\nex34 -= extrude(ex34_sk2, amount=-fontht)\n# [Ex. 34]\n# show_object(ex34)\n" + }, + { + "id": "general_examples_algebra/ex35", + "source": "docs/general_examples_algebra.py #35 (Slots)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 35. Slots\n# [Ex. 35]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nex35 = Box(length, length, thickness)\nplane = Plane(ex35.faces().sort_by().last)\nex35_sk = SlotCenterToCenter(width / 2, 10)\nex35_ln = RadiusArc((-width / 2, 0), (0, width / 2), radius=width / 2)\nex35_sk += SlotArc(arc=ex35_ln.edges()[0], height=thickness)\nex35_ln2 = RadiusArc((0, -width / 2), (width / 2, 0), radius=-width / 2)\nex35_sk += SlotArc(arc=ex35_ln2.edges()[0], height=thickness)\nex35 -= extrude(plane * ex35_sk, -thickness)\n# [Ex. 35]\n# show_object(ex35)\n" + }, + { + "id": "general_examples_algebra/ex36", + "source": "docs/general_examples_algebra.py #36 (Extrude-Until)", + "kind": "docs-algebra", + "code": "from build123d import *\nfrom math import *\n\n# 36. Extrude-Until\n# [Ex. 36]\nrad, rev = 6, 50\n\nex36_sk = Pos(0, rev) * Circle(rad)\nex36 = revolve(axis=Axis.X, profiles=ex36_sk, revolution_arc=180)\nex36_sk2 = Rectangle(rad, rev)\nex36 += extrude(ex36_sk2, until=Until.NEXT, target=ex36)\n# [Ex. 36]\n# show_object(ex36)\n" + }, + { + "id": "docs/center", + "source": "docs/center.py", + "kind": "docs-script", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nsize = 50\n#\n# Symbols\n#\nbbox_symbol = Rectangle(4, 4)\ngeom_symbol = RegularPolygon(2, 3)\nmass_symbol = Circle(2)\n\n#\n# 2D Center Options\n#\ntriangle = RegularPolygon(size / 1.866, 3, rotation=90)\nsvg = ExportSVG(margin=5)\nsvg.add_layer(\"bbox\", line_type=LineType.DASHED)\nsvg.add_shape(bounding_box(triangle), \"bbox\")\nsvg.add_shape(triangle)\nsvg.add_shape(bbox_symbol.located(Location(triangle.center(CenterOf.BOUNDING_BOX))))\nsvg.add_shape(mass_symbol.located(Location(triangle.center(CenterOf.MASS))))\nsvg.write(\"assets/center.svg\")\n\n#\n# 1D Center Options\n#\nline = TangentArc((0, 0), (size, size), tangent=(1, 0))\nsvg = ExportSVG(margin=5)\nsvg.add_layer(\"bbox\", line_type=LineType.DASHED)\nsvg.add_shape(line)\nsvg.add_shape(Polyline((0, 0), (size, 0), (size, size), (0, size), (0, 0)), \"bbox\")\nsvg.add_shape(bbox_symbol.located(Location(line.center(CenterOf.BOUNDING_BOX))))\nsvg.add_shape(mass_symbol.located(Location(line.center(CenterOf.MASS))))\nsvg.add_shape(geom_symbol.located(Location(line.center(CenterOf.GEOMETRY))))\nsvg.write(\"assets/one_d_center.svg\")\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/constraint_examples", + "source": "docs/constraint_examples.py", + "kind": "docs-script", + "code": "from build123d import *\nfrom build123d.exporters import ColorIndex\n# [removed by collect.py] from ocp_vscode import show, show_all, ImageFace\n\n# 2D Axes\naxes2 = Compound.make_triad(2).edges().group_by(Axis.Z)[0]\n\n\n#\n# BlendCurve\n#\nm1 = CenterArc((-2, 0.6), 1, -10, 200).reversed()\nm2 = Spline((0.4, -0.6), (1, -1.6), (2, 0))\nconnector = BlendCurve(m1, m2, tangent_scalars=(2, 1), continuity=ContinuityLevel.C2)\ncomb = Curve(Wire([m1, connector, m2]).curvature_comb(200))\n\ns = 120 / max(*Curve(axes2 + [m1, m2]).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"m1\", line_color=(214, 40, 40), line_type=LineType.ISO_DASH_SPACE)\nsvg.add_layer(\"m2\", line_color=(252, 191, 73), line_type=LineType.ISO_DASH_SPACE)\nsvg.add_layer(\"connector\", line_color=(247, 127, 0))\nsvg.add_layer(\"comb\", line_color=(172, 172, 172))\nsvg.add_shape(axes2)\nsvg.add_shape(m1, \"m1\")\nsvg.add_shape(m2, \"m2\")\nsvg.add_shape(connector, \"connector\")\nsvg.add_shape(comb, \"comb\")\nsvg.write(\"assets/blend_curve_ex.svg\")\n\n\n#\n# Coincident\n#\nwith BuildLine() as coincident_ex:\n l1 = Line((0, 0), (1, 2))\n l2 = Line(l1 @ 1, l1 @ 1 + (1, 0))\n\ns = 50 / max(*Curve(axes2 + coincident_ex.edges()).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(l1, \"dashed\")\nsvg.add_shape(l2)\nsvg.write(\"assets/coincident_ex.svg\")\n\n#\n# Tangent\n#\nwith BuildLine() as tangent_ex:\n l1 = Line((0, 0), (1, 1))\n l2 = JernArc(start=l1 @ 1, tangent=l1 % 1, radius=1, arc_size=70)\n\ns = 50 / max(*Curve(axes2 + tangent_ex.edges()).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(l1, \"dashed\")\nsvg.add_shape(l2)\nsvg.write(\"assets/tangent_ex.svg\")\n\n#\n# Perpendicular\n#\nwith BuildLine() as perpendicular_ex:\n l1 = CenterArc((0, 0), 1.5, 0, 45)\n l2 = PolarLine(\n start=l1 @ 1, length=1, direction=l1.tangent_at(1).rotate(Axis.Z, -90)\n )\n\ns = 50 / max(*Curve(axes2 + perpendicular_ex.edges()).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(l1, \"dashed\")\nsvg.add_shape(l2)\nsvg.write(\"assets/perpendicular_ex.svg\")\n\n#\n# Intersection\n#\nwith BuildLine() as intersect_ex:\n c_l1 = EllipticalCenterArc((0, 0), 1.2, 1.8, 0, arc_size=90, mode=Mode.PRIVATE)\n l1 = IntersectingLine(\n start=(0, 0), direction=Vector(1, 0).rotate(Axis.Z, 10), other=c_l1\n )\n l2 = IntersectingLine(\n start=(0, 0), direction=Vector(1, 0).rotate(Axis.Z, 80), other=c_l1\n )\n l3 = add(c_l1.trim(l1 @ 1, l2 @ 1))\n\ns = 50 / max(*Curve(axes2 + intersect_ex.edges()).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(c_l1, \"dashed\")\nsvg.add_shape(l1)\nsvg.add_shape(l2)\nsvg.add_shape(l3)\nsvg.write(\"assets/intersect_ex.svg\")\n\n#\n# Offset\n#\ninside = FilletPolyline((1.5, 0), (1.5, 1), (-1.5, 1), (-1.5, 0), radius=0.2)\ninside.color = \"Grey\"\nperimeter = offset(inside, amount=0.2, side=Side.RIGHT)\n\ns = 100 / max(*Curve(axes2 + [inside, perimeter]).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(perimeter)\nsvg.add_shape(inside, \"dashed\")\nsvg.write(\"assets/offset_ex.svg\")\n\n#\n# Tangency Outside/Enclosing\n#\nwith BuildLine() as egg_plant:\n # Construction Geometry\n c_l1 = CenterArc((-2, 0), 0.75, 80, 240, mode=Mode.PRIVATE)\n c_l4 = CenterArc((2, 0), 1, 220, 250, mode=Mode.PRIVATE)\n\n # egg_plant perimeter\n l1 = ConstrainedArcs((c_l4, Tangency.OUTSIDE), (c_l1, Tangency.OUTSIDE), radius=6)\n l2 = ConstrainedArcs(\n (c_l4, Tangency.ENCLOSING),\n (c_l1, Tangency.ENCLOSING),\n radius=8,\n selector=lambda a: a.sort_by(Axis.Y)[-1],\n )\n l3 = add(c_l1.trim(l1 @ 1, l2 @ 1))\n l5 = add(c_l4.trim(l1 @ 0, l2 @ 0))\n\ns = 100 / max(*Curve(axes2 + egg_plant.edges()).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape([l1, l2, l3, l5])\nsvg.add_shape([c_l1, c_l4], \"dashed\")\nsvg.write(\"assets/enclosing_ex.svg\")\n\n#\n# Complex Sketch\n#\nimage = ImageFace(\n \"assets/complex_sketch.png\",\n scale=29 / 264,\n origin_pixels=(297, 390),\n location=Location((0, 0, -0.1)),\n)\naxes5 = Compound.make_triad(5).edges().group_by(Axis.Z)[0]\n\nwith BuildSketch() as sketch:\n with BuildLine() as perimeter:\n c_l1 = PolarLine((0, 32 - 14), 50, -10, mode=Mode.PRIVATE)\n a19 = ConstrainedArcs(c_l1, (-14 + 81 - 29, -14 - 19 + 57), radius=19)\n l2 = Polyline(a19 @ 1, a19 @ 1 + (29 - 5, 0), a19 @ 1 + (29, -5), (-14 + 81, 0))\n l3 = Line(l2 @ 1, (-14 + 81 - 29, (-14 - 19)))\n c_l4 = Line((-14, -14), (-14 + 81, -14), mode=Mode.PRIVATE)\n c_a29_arc_center = l3.intersect(c_l4)[0]\n c_a29 = CenterArc(c_a29_arc_center, 29, 180, 50, mode=Mode.PRIVATE)\n l5 = IntersectingLine(l3 @ 1, (-1, 0), c_a29)\n a5 = ConstrainedArcs(\n c_a29, c_l4, radius=5, selector=lambda a: a.sort_by(Axis.X)[0]\n )\n a29 = add(c_a29.trim(l5 @ 1, a5 @ 0))\n l6 = Polyline(\n a5 @ 1,\n (-14 + 7, -14),\n (-14, -14 + 7),\n (-14, -14 + 32 - 7),\n (-14 + 7, -14 + 32),\n (0, -14 + 32),\n a19 @ 0,\n )\n make_face()\n a14 = Circle(14 / 2, mode=Mode.SUBTRACT)\n\ns = 150 / max(*Curve(axes5 + perimeter.edges()).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes5)\nsvg.add_shape(perimeter.edges() + [a14.edge()])\nsvg.add_shape([c_l1, c_l4, c_a29], \"dashed\")\nsvg.write(\"assets/complex_ex.svg\")\n\n#\n# Tangent Circles\n#\na1 = CenterArc((-7, 0), 10, 0, 360)\na2 = CenterArc((7, 0), 10, 0, 360)\ntangents = ConstrainedArcs(a1, a2, radius=2).edges()\ntangent_circles = [CenterArc(e.arc_center, 2, 0, 360) for e in tangents]\n\ns = 100 / max(*Curve([a1, a2] + tangent_circles).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(tangent_circles)\nsvg.add_shape([a1, a2], \"dashed\")\nsvg.write(\"assets/tangent_circles.svg\")\n\n#\n# ConstrainedArcs - two constraints & radius\n#\ne1 = Line((0, 1), (2, 1))\ne2 = Line((1, 0), (1, 2))\ntan2_rad_edges = ConstrainedArcs(e1, e2, radius=0.75).edges()\n\ns = 50 / max(*Curve([e1, e2] + axes2 + tan2_rad_edges).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(tan2_rad_edges)\nsvg.add_shape([e1, e2], \"dashed\")\nsvg.write(\"assets/tan2_rad_ex.svg\")\n\n#\n# ConstrainedArcs - two constraints & center-on\n#\n# c1 = PolarLine((0, 0), 4, -20, length_mode=LengthMode.HORIZONTAL)\nc1 = PolarLine((0, 0), 2, 40, length_mode=LengthMode.HORIZONTAL)\nc2 = Line((1.8, 0), (1.8, 2))\nc3_center_on = Line((1, -0.5), (1, 2.5))\ntan2_on_edge = ConstrainedArcs(\n c1, c2, center_on=c3_center_on, sagitta=Sagitta.BOTH\n).edges()\n\ns = 50 / max(*Curve([c1, c2, c3_center_on] + axes2 + tan2_on_edge).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(tan2_on_edge)\nsvg.add_shape([c1, c2, c3_center_on], \"dashed\")\nsvg.write(\"assets/tan2_on_ex.svg\")\n\n#\n# ConstrainedArcs - three constraints\n#\nc5 = PolarLine((0, 0), 1.8, 60)\nc6 = PolarLine((0, 0), 1.8, 40)\nc7 = CenterArc((0, 0), 1.8, 0, 90)\ntan3 = ConstrainedArcs(c5, c6, c7).edge()\n\ns = 50 / max(*Curve([c5, c6, c7, tan3] + axes2).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(tan3)\nsvg.add_shape([c5, c6, c7], \"dashed\")\nsvg.write(\"assets/tan3_ex.svg\")\n\n#\n# ConstrainedArcs - one constraint + center\n#\npnt = CenterArc((1.5, 1.5), 0.05, 0, 360)\ncenter_pnt = CenterArc((1, 1), 0.05, 0, 360)\npnt_center = ConstrainedArcs(pnt.arc_center, center=center_pnt.arc_center).edge()\n\ns = 50 / max(*Curve([pnt, center_pnt, pnt_center] + axes2).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(axes2)\nsvg.add_shape([pnt, center_pnt, pnt_center])\nsvg.write(\"assets/pnt_center_ex.svg\")\n\n#\n# ConstrainedArcs - One constraint + radius + center_on\n#\ntan_rad_on = ConstrainedArcs(c1, radius=0.5, center_on=c3_center_on).edges()\n\ns = 50 / max(*Curve([c1, c3_center_on] + tan_rad_on + axes2).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(tan_rad_on)\nsvg.add_shape([c1, c3_center_on], \"dashed\")\nsvg.write(\"assets/tan_rad_on_ex.svg\")\n\n#\n# ConstrainedLines - two constraints\n#\na1 = CenterArc((-1, 1), 1, 0, 360)\na2 = CenterArc((1, 1), 0.5, 0, 360)\nl1 = Line((0, 0), (2, 2))\nlines_tan2_ex = ConstrainedLines(a1, a2).edges()\n\ns = 50 / max(*Curve([a1, a1] + lines_tan2_ex + axes2).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(lines_tan2_ex)\nsvg.add_shape([a1, a2], \"dashed\")\nsvg.write(\"assets/lines_tan2_ex.svg\")\n\n\npnt_line = CenterArc((1, 1), 0.05, 0, 360)\nlines_tan_pnt = ConstrainedLines(a1, pnt_line.arc_center).edges()\n\ns = 50 / max(*Curve([pnt_line, a1] + lines_tan_pnt + axes2).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(lines_tan_pnt)\nsvg.add_shape(pnt_line)\nsvg.add_shape([a1], \"dashed\")\nsvg.write(\"assets/lines_tan_pnt_ex.svg\")\n\ny_axis = Line((0, 0), (0, 2.5))\nlines_angle = ConstrainedLines(a2, Axis.Y, angle=55).edges()\n\ns = 50 / max(*Curve([y_axis, a2] + lines_angle + axes2).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(axes2)\nsvg.add_shape(lines_angle)\nsvg.add_shape([y_axis, a2], \"dashed\")\nsvg.write(\"assets/lines_angle_ex.svg\")\n\nshow_all()\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/heart_token", + "source": "docs/heart_token.py", + "kind": "docs-script", + "code": "# [Code]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n# Create the edges of one half the heart surface\nl1 = JernArc((0, 0), (1, 1.4), 40, -17)\nl2 = JernArc(l1 @ 1, l1 % 1, 4.5, 175)\nl3 = IntersectingLine(l2 @ 1, l2 % 1, other=Edge.make_line((0, 0), (0, 20)))\nl4 = ThreePointArc(l3 @ 1, (0, 0, 1.5) + (l3 @ 1 + l1 @ 0) / 2, l1 @ 0)\nheart_half = Wire([l1, l2, l3, l4])\n# [SurfaceEdges]\n\n# Create a point elevated off the center\nsurface_pnt = l2.arc_center + (0, 0, 1.5)\n# [SurfacePoint]\n\n# Create the surface from the edges and point\ntop_right_surface = Pos(Z=0.5) * -Face.make_surface(heart_half, [surface_pnt])\n# [Surface]\n\n# Use the mirror method to create the other top and bottom surfaces\ntop_left_surface = top_right_surface.mirror(Plane.YZ)\nbottom_right_surface = top_right_surface.mirror(Plane.XY)\nbottom_left_surface = -top_left_surface.mirror(Plane.XY)\n# [Surfaces]\n\n# Create the left and right sides\nleft_wire = Wire([l3, l2, l1])\nleft_side = Pos(Z=-0.5) * Shell.extrude(left_wire, (0, 0, 1))\nright_side = left_side.mirror(Plane.YZ)\n# [Sides]\n\n# Put all of the faces together into a Shell/Solid\nheart = Solid(\n Shell(\n [\n top_right_surface,\n top_left_surface,\n bottom_right_surface,\n bottom_left_surface,\n left_side,\n right_side,\n ]\n )\n)\n# [Solid]\n\n# Build a frame around the heart\nwith BuildPart() as heart_token:\n with BuildSketch() as outline:\n with BuildLine():\n add(l1)\n add(l2)\n add(l3)\n Line(l3 @ 1, l1 @ 0)\n make_face()\n mirror(about=Plane.YZ)\n center = outline.sketch\n offset(amount=2, kind=Kind.INTERSECTION)\n add(center, mode=Mode.SUBTRACT)\n extrude(amount=2, both=True)\n add(heart)\n\nheart_token.part.color = \"Red\"\n\nshow(heart_token)\n# [End]\n# export_gltf(heart_token.part, \"heart_token.glb\", binary=True)\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/line_types", + "source": "docs/line_types.py", + "kind": "docs-script", + "code": "from build123d import *\n\nexporter = ExportSVG(scale=1)\nexporter.add_layer(name=\"Text\", fill_color=(0, 0, 0))\nline_types = [l for l in LineType.__members__]\ntext_locs = Pos((100, 0, 0)) * GridLocations(0, 6, 1, len(line_types)).locations\nline_locs = Pos((105, 0, 0)) * GridLocations(0, 6, 1, len(line_types)).locations\nfor line_type, text_loc, line_loc in zip(line_types, text_locs, line_locs):\n exporter.add_layer(name=line_type, line_type=getattr(LineType, line_type))\n exporter.add_shape(\n Compound.make_text(\n \"LineType.\" + line_type,\n font_size=5,\n align=(Align.MAX, Align.CENTER),\n ).locate(text_loc),\n layer=\"Text\",\n )\n exporter.add_shape(\n Edge.make_line((0, 0), (100, 0)).locate(line_loc), layer=line_type\n )\nexporter.write(\"assets/line_types.svg\")\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/objects_1d", + "source": "docs/objects_1d.py", + "kind": "docs-script", + "code": "# [Setup]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndot = Circle(0.05)\n\n# [Ex. 1]\nwith BuildLine() as example_1:\n Line((0, 0), (2, 0))\n ThreePointArc((0, 0), (1, 1), (2, 0))\n# [Ex. 1]\ns = 100 / max(*example_1.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(example_1.line)\nsvg.write(\"assets/buildline_example_1.svg\")\n# [Ex. 2]\nwith BuildLine() as example_2:\n l1 = Line((0, 0), (2, 0))\n l2 = ThreePointArc(l1 @ 0, (1, 1), l1 @ 1)\n# [Ex. 2]\n\n# [Ex. 3]\nwith BuildLine() as example_3:\n l1 = Line((0, 0), (2, 0))\n l2 = ThreePointArc(l1 @ 0, l1 @ 0.5 + (0, 1), l1 @ 1)\n# [Ex. 3]\n\n# [Ex. 4]\nwith BuildLine() as example_4:\n l1 = Line((0, 0), (2, 0))\n l2 = ThreePointArc(l1 @ 0, l1 @ 0.5 + (0, l1.length / 2), l1 @ 1)\n# [Ex. 4]\n\n# [Ex. 5]\nwith BuildLine() as example_5:\n l1 = Line((0, 0), (5, 0))\n l2 = Line(l1 @ 1, l1 @ 1 + (0, l1.length - 1))\n l3 = JernArc(start=l2 @ 1, tangent=l2 % 1, radius=0.5, arc_size=90)\n l4 = Line(l3 @ 1, (0, l2.length + l3.radius))\n# [Ex. 5]\ns = 100 / max(*example_5.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(example_5.line)\nsvg.add_shape(dot.moved(Location(l1 @ 1)))\nsvg.add_shape(dot.moved(Location(l2 @ 1)))\nsvg.add_shape(dot.moved(Location(l3 @ 1)))\nsvg.add_shape(PolarLine(l2 @ 1, 0.5, direction=l2 % 1), \"dashed\")\nsvg.write(\"assets/buildline_example_5.svg\")\n# [Ex. 6]\nwith BuildSketch() as example_6:\n with BuildLine() as club_outline:\n l0 = Line((0, -188), (76, -188))\n b0 = Bezier(l0 @ 1, (61, -185), (33, -173), (17, -81))\n b1 = Bezier(b0 @ 1, (49, -128), (146, -145), (167, -67))\n b2 = Bezier(b1 @ 1, (187, 9), (94, 52), (32, 18))\n b3 = Bezier(b2 @ 1, (92, 57), (113, 188), (0, 188))\n mirror(about=Plane.YZ)\n make_face()\n # [Ex. 6]\ns = 100 / max(*example_6.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s, margin=5)\nsvg.add_shape(example_6.sketch)\nsvg.write(\"assets/buildline_example_6.svg\")\n\n# [Ex. 7]\nwith BuildPart() as example_7:\n with BuildLine() as example_7_path:\n l1 = RadiusArc((0, 0), (1, 1), 2)\n l2 = Spline(l1 @ 1, (2, 3), (3, 3), tangents=(l1 % 1, (0, -1)))\n l3 = Line(l2 @ 1, (3, 0))\n with BuildSketch(Plane(origin=l1 @ 0, z_dir=l1 % 0)) as example_7_section:\n Circle(0.1)\n sweep()\n# [Ex. 7]\nvisible, hidden = example_7.part.project_to_viewport((100, -50, 100))\ns = 100 / max(*Compound(children=visible + hidden).bounding_box().size)\nexporter = ExportSVG(scale=s)\nexporter.add_layer(\"Visible\")\nexporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)\nexporter.add_shape(visible, layer=\"Visible\")\nexporter.add_shape(hidden, layer=\"Hidden\")\nexporter.write(\"assets/buildline_example_7.svg\")\n# [Ex. 8]\nwith BuildLine(Plane.YZ) as example_8:\n l1 = Line((0, 0), (5, 0))\n l2 = Line(l1 @ 1, l1 @ 1 + (0, l1.length - 1))\n l3 = JernArc(start=l2 @ 1, tangent=l2 % 1, radius=0.5, arc_size=90)\n l4 = Line(l3 @ 1, (0, l2.length + l3.radius))\n# [Ex. 8]\nscene = Compound(example_8.line) + Compound.make_triad(2)\nvisible, _hidden = scene.project_to_viewport((100, -50, 100))\ns = 100 / max(*Compound(children=visible + hidden).bounding_box().size)\nexporter = ExportSVG(scale=s)\nexporter.add_layer(\"Visible\")\nexporter.add_shape(visible, layer=\"Visible\")\nexporter.write(\"assets/buildline_example_8.svg\")\n\n\npts = [(0, 0), (2 / 3, 2 / 3), (0, 4 / 3), (-4 / 3, 0), (0, -2), (4, 0), (0, 3)]\nwts = [1.0, 1.0, 2.0, 3.0, 4.0, 2.0, 1.0]\nwith BuildLine() as bezier_curve:\n Bezier(*pts, weights=wts)\n\ns = 100 / max(*bezier_curve.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(bezier_curve.line)\nfor pt in pts:\n svg.add_shape(dot.moved(Location(Vector(pt))))\nsvg.write(\"assets/bezier_curve_example.svg\")\n\nwith BuildLine() as center_arc:\n CenterArc((0, 0), 3, 0, 90)\ns = 100 / max(*center_arc.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(center_arc.line)\nsvg.add_shape(dot.moved(Location(Vector((0, 0)))))\nsvg.write(\"assets/center_arc_example.svg\")\n\nwith BuildLine() as elliptical_center_arc:\n EllipticalCenterArc((0, 0), 2, 3, 0, arc_size=90)\ns = 100 / max(*elliptical_center_arc.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(elliptical_center_arc.line)\nsvg.add_shape(dot.moved(Location(Vector((0, 0)))))\nsvg.write(\"assets/elliptical_center_arc_example.svg\")\n\n\nwith BuildLine() as helix:\n Helix(1, 3, 1)\nscene = Compound(helix.line) + Compound.make_triad(0.5)\nvisible, _hidden = scene.project_to_viewport((1, 1, 1))\ns = 100 / max(*Compound(children=visible).bounding_box().size)\nexporter = ExportSVG(scale=s)\nexporter.add_layer(\"Visible\")\nexporter.add_shape(visible, layer=\"Visible\")\nexporter.write(\"assets/helix_example.svg\")\n\nwith BuildLine() as jern_arc:\n JernArc((1, 1), (1, 0.5), 2, 100)\ns = 100 / max(*jern_arc.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(jern_arc.line)\nsvg.add_shape(dot.moved(Location(Vector((1, 1)))))\nsvg.add_shape(PolarLine((1, 1), 0.5, direction=(1, 0.5)), \"dashed\")\nsvg.write(\"assets/jern_arc_example.svg\")\n\nwith BuildLine() as line:\n Line((1, 1), (3, 3))\ns = 100 / max(*line.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(line.line)\nsvg.add_shape(dot.moved(Location(Vector((1, 1)))))\nsvg.add_shape(dot.moved(Location(Vector((3, 3)))))\nsvg.write(\"assets/line_example.svg\")\n\nwith BuildLine() as polar_line:\n PolarLine((1, 1), 2.5, 60)\ns = 100 / max(*polar_line.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(polar_line.line)\nsvg.add_shape(dot.moved(Location(Vector((1, 1)))))\nsvg.add_shape(PolarLine((1, 1), 4, angle=60), \"dashed\")\nsvg.write(\"assets/polar_line_example.svg\")\n\nwith BuildLine() as polyline:\n Polyline((1, 1), (1.5, 2.5), (3, 3))\ns = 100 / max(*polyline.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(polyline.line)\nsvg.add_shape(dot.moved(Location(Vector((1, 1)))))\nsvg.add_shape(dot.moved(Location(Vector((1.5, 2.5)))))\nsvg.add_shape(dot.moved(Location(Vector((3, 3)))))\nsvg.write(\"assets/polyline_example.svg\")\n\nwith BuildLine(Plane.YZ) as filletpolyline:\n FilletPolyline((0, 0, 0), (0, 10, 2), (0, 10, 10), (5, 20, 10), radius=2)\nscene = Compound(filletpolyline.line) + Compound.make_triad(2)\nvisible, _hidden = scene.project_to_viewport((0, 0, 1), (0, 1, 0))\ns = 100 / max(*Compound(children=visible).bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(visible)\nsvg.write(\"assets/filletpolyline_example.svg\")\n\nwith BuildLine() as radius_arc:\n RadiusArc((1, 1), (3, 3), 2)\ns = 100 / max(*radius_arc.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(radius_arc.line)\nsvg.add_shape(dot.moved(Location(Vector((1, 1)))))\nsvg.add_shape(dot.moved(Location(Vector((3, 3)))))\nsvg.write(\"assets/radius_arc_example.svg\")\n\nwith BuildLine() as sagitta_arc:\n SagittaArc((1, 1), (3, 1), 1)\ns = 100 / max(*sagitta_arc.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(sagitta_arc.line)\nsvg.add_shape(dot.moved(Location(Vector((1, 1)))))\nsvg.add_shape(dot.moved(Location(Vector((3, 1)))))\nsvg.write(\"assets/sagitta_arc_example.svg\")\n\nwith BuildLine() as spline:\n Spline((1, 1), (2, 1.5), (1, 2), (2, 2.5), (1, 3))\ns = 100 / max(*spline.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(spline.line)\nsvg.add_shape(dot.moved(Location(Vector((1, 1)))))\nsvg.add_shape(dot.moved(Location(Vector((2, 1.5)))))\nsvg.add_shape(dot.moved(Location(Vector((1, 2)))))\nsvg.add_shape(dot.moved(Location(Vector((2, 2.5)))))\nsvg.add_shape(dot.moved(Location(Vector((1, 3)))))\nsvg.write(\"assets/spline_example.svg\")\n\nwith BuildLine() as tangent_arc:\n TangentArc((1, 1), (3, 3), tangent=(1, 0))\ns = 100 / max(*tangent_arc.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(tangent_arc.line)\nsvg.add_shape(dot.moved(Location(Vector((1, 1)))))\nsvg.add_shape(dot.moved(Location(Vector((3, 3)))))\nsvg.add_shape(PolarLine((1, 1), 1, direction=(1, 0)), \"dashed\")\nsvg.write(\"assets/tangent_arc_example.svg\")\n\nwith BuildLine() as three_point_arc:\n ThreePointArc((1, 1), (1.5, 2), (3, 3))\ns = 100 / max(*three_point_arc.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(three_point_arc.line)\nsvg.add_shape(dot.moved(Location(Vector((1, 1)))))\nsvg.add_shape(dot.moved(Location(Vector((1.5, 2)))))\nsvg.add_shape(dot.moved(Location(Vector((3, 3)))))\nsvg.write(\"assets/three_point_arc_example.svg\")\n\nwith BuildLine() as intersecting_line:\n other = Line((2, 0), (2, 2), mode=Mode.PRIVATE)\n IntersectingLine((1, 0), (1, 1), other)\ns = 100 / max(*intersecting_line.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(other, \"dashed\")\nsvg.add_shape(intersecting_line.line)\nsvg.add_shape(dot.moved(Location(Vector((1, 0)))))\nsvg.write(\"assets/intersecting_line_example.svg\")\n\nwith BuildLine() as double_tangent:\n p1 = (6, 0)\n d1 = (0, 1)\n l2 = Spline((0, 10), (3, 8), (7, 7), (10, 10))\n show_object([p1, l2])\n l3 = DoubleTangentArc(p1, tangent=d1, other=l2)\ns = 100 / max(*double_tangent.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(l2, \"dashed\")\nsvg.add_shape(l3)\nsvg.add_shape(dot.scale(5).moved(Pos(p1)))\nsvg.add_shape(PolarLine(p1, 1, direction=d1), \"dashed\")\nsvg.write(\"assets/double_tangent_line_example.svg\")\n\n# show_object(example_1.line, name=\"Ex. 1\")\n# show_object(example_2.line, name=\"Ex. 2\")\n# show_object(example_3.line, name=\"Ex. 3\")\n# show_object(example_4.line, name=\"Ex. 4\")\n# show_object(example_5.line, name=\"Ex. 5\")\n# show_object(example_6.line, name=\"Ex. 6\")\n# show_object(example_7_path.line, name=\"Ex. 7 path\")\n# show_object(example_8.line, name=\"Ex. 8\")\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/objects_1d_airfoil", + "source": "docs/objects_1d_airfoil.py", + "kind": "docs-script", + "code": "from build123d import *\n\n# from ocp_vscode import show_all, set_defaults, Camera\n\n# set_defaults(reset_camera=Camera.KEEP)\n\nwith BuildLine() as airfoil:\n l1 = Airfoil(\"2213\")\ns = 100 / max(*airfoil.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(l1)\nsvg.write(\"assets/example_airfoil.svg\")\n# show_all()\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/objects_1d_blend_curve", + "source": "docs/objects_1d_blend_curve.py", + "kind": "docs-script", + "code": "from build123d import *\n\n# from ocp_vscode import show_all, set_defaults, Camera\n\n# set_defaults(reset_camera=Camera.KEEP)\n\nwith BuildLine() as blend_curve:\n l1 = CenterArc((0, 0), 5, 135, -135)\n l2 = Spline((0, -5), (-3, -8), (0, -11))\n l3 = BlendCurve(l1, l2, tangent_scalars=(2, 5))\ns = 100 / max(*blend_curve.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(l1, \"dashed\")\nsvg.add_shape(l2, \"dashed\")\nsvg.add_shape(l3)\nsvg.write(\"assets/example_blend_curve.svg\")\n# show_all()\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/objects_1d_bspline", + "source": "docs/objects_1d_bspline.py", + "kind": "docs-script", + "code": "from build123d import *\n\n# from ocp_vscode import show_all\n\ndot = Circle(0.05)\n\ncontrol_points = [(0, 0), (1, 2), (3, 2), (4, 0), (5, 1)]\nknots = [0.0, 0.0, 0.0, 0.0, 1.0, 2.0, 2.0, 2.0, 2.0]\nspline = BSpline(control_points, knots, degree=3)\n\ns = 100 / max(*spline.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(spline)\nfor p in control_points:\n svg.add_shape(Pos(*p) * dot.scale(1))\nsvg.write(\"assets/example_bspline.svg\")\n# show_all()\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/objects_1d_constrained", + "source": "docs/objects_1d_constrained.py", + "kind": "docs-script", + "code": "# [Setup]\nfrom build123d import *\n\n# from ocp_vscode import *\n\ndot = Circle(0.05)\n\nwith BuildLine() as arcs:\n c1 = CenterArc((4, 0), 2, 0, 360)\n c2 = CenterArc((0, 2), 1.5, 0, 360)\n a1 = ConstrainedArcs(c1, c2, radius=6)\n\ns = 100 / max(*arcs.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(c1, \"dashed\")\nsvg.add_shape(c2, \"dashed\")\nsvg.add_shape(a1)\nsvg.write(\"assets/constrained_arcs_example.svg\")\n\n\nwith BuildLine() as lines:\n c1 = CenterArc((4, 0), 2, 0, 360)\n c2 = CenterArc((0, 2), 1.5, 0, 360)\n l1 = ConstrainedLines(c1, c2)\n\ns = 100 / max(*lines.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(c1, \"dashed\")\nsvg.add_shape(c2, \"dashed\")\nsvg.add_shape(l1)\nsvg.write(\"assets/constrained_lines_example.svg\")\n\n# show_all()\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/objects_1d_ellipticalstartarc", + "source": "docs/objects_1d_ellipticalstartarc.py", + "kind": "docs-script", + "code": "# [Setup]\nfrom build123d import *\nfrom math import atan2, degrees\n# [removed by collect.py] from ocp_vscode import *\n\ndot = Circle(0.05)\n\ne_dir = Vector(0.2, 1)\nwith BuildLine() as arcs:\n a = EllipticalStartArc((1, 1), (0, 1), 3, 1, 160, major_axis_dir=e_dir)\n d = PolarLine(a.arc_center, 0.5, direction=e_dir)\n\n\nprint(a.arc_center)\ns = 100 / max(*arcs.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.ISO_DASH_SPACE)\nsvg.add_shape(Pos(1, 1) * dot.scale(1), \"dashed\")\nsvg.add_shape(PolarLine((1, 1), 0.5, 90), \"dashed\")\nsvg.add_shape(d, \"dashed\")\nsvg.add_shape(\n ArrowHead(0.2, rotation=degrees(atan2(e_dir.Y, e_dir.X))).moved(Pos(d @ 1)),\n \"dashed\",\n)\nsvg.add_shape(a)\nsvg.write(\"assets/elliptical_start_arc_example.svg\")\n\n\nshow_all()\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/objects_1d_parabolic_hyperbolic", + "source": "docs/objects_1d_parabolic_hyperbolic.py", + "kind": "docs-script", + "code": "# [Setup]\nfrom build123d import *\n\n# from ocp_vscode import *\n\ndot = Circle(0.05)\n\nwith BuildLine() as parabolic_center_arc:\n ParabolicCenterArc((0, 0), 0.25, -60, arc_size=120)\ns = 100 / max(*parabolic_center_arc.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(parabolic_center_arc.line)\nsvg.add_shape(dot.moved(Location(Vector((0, 0)))))\nsvg.write(\"assets/parabolic_center_arc_example.svg\")\n\nwith BuildLine() as hyperbolic_center_arc:\n HyperbolicCenterArc((0, 0), 0.5, 1, 0, arc_size=180)\ns = 100 / max(*hyperbolic_center_arc.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(hyperbolic_center_arc.line)\nsvg.add_shape(dot.moved(Location(Vector((0, 0)))))\nsvg.write(\"assets/hyperbolic_center_arc_example.svg\")\n\n# show_all()\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/objects_2d", + "source": "docs/objects_2d.py", + "kind": "docs-script", + "code": "# [Setup]\nfrom build123d import *\n\ndot = Circle(0.05)\n\n# [Setup]\nsvg_opts1 = {\"pixel_scale\": 100, \"show_axes\": False, \"show_hidden\": False}\nsvg_opts2 = {\"pixel_scale\": 300, \"show_axes\": True, \"show_hidden\": False}\nsvg_opts3 = {\"pixel_scale\": 2, \"show_axes\": False, \"show_hidden\": False}\nsvg_opts4 = {\"pixel_scale\": 5, \"show_axes\": False, \"show_hidden\": False}\n\n# [Ex. 1]\nwith BuildSketch() as example_1:\n Circle(1)\n# [Ex. 1]\ns = 100 / max(*example_1.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(example_1.sketch)\nsvg.write(\"assets/circle_example.svg\")\n\n# [Ex. 2]\nwith BuildSketch() as example_2:\n Ellipse(1.5, 1)\n# [Ex. 2]\ns = 100 / max(*example_2.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(example_2.sketch)\nsvg.write(\"assets/ellipse_example.svg\")\n\n# [Ex. 3]\nwith BuildSketch() as example_3:\n inner = PolarLocations(0.5, 5, 0).local_locations\n outer = PolarLocations(1.5, 5, 36).local_locations\n points = [p.position for pair in zip(inner, outer) for p in pair]\n Polygon(*points)\n# [Ex. 3]\ns = 100 / max(*example_3.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(example_3.sketch)\nsvg.write(\"assets/polygon_example.svg\")\n\n# [Ex. 4]\nwith BuildSketch() as example_4:\n Rectangle(2, 1)\n# [Ex. 4]\ns = 100 / max(*example_4.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(example_4.sketch)\nsvg.write(\"assets/rectangle_example.svg\")\n\n# [Ex. 5]\nwith BuildSketch() as example_5:\n RectangleRounded(2, 1, 0.25)\n# [Ex. 5]\ns = 100 / max(*example_5.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(example_5.sketch)\nsvg.write(\"assets/rectangle_rounded_example.svg\")\n\n# [Ex. 6]\nwith BuildSketch() as example_6:\n RegularPolygon(1, 6)\n# [Ex. 6]\ns = 100 / max(*example_6.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(example_6.sketch)\nsvg.write(\"assets/regular_polygon_example.svg\")\n\n# [Ex. 7]\nwith BuildSketch() as example_7:\n arc = Edge.make_circle(1, start_angle=0, end_angle=45)\n SlotArc(arc, 0.25)\n# [Ex. 7]\ns = 100 / max(*example_7.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.DASHED)\nsvg.add_shape(example_7.sketch)\nsvg.add_shape(arc, \"dashed\")\nsvg.write(\"assets/slot_arc_example.svg\")\n\n# [Ex. 8]\nwith BuildSketch() as example_8:\n c = (0, 0)\n p = (0, 1)\n SlotCenterPoint(c, p, 0.25)\n# [Ex. 8]\ns = 100 / max(*example_8.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.DASHED)\nsvg.add_shape(example_8.sketch)\nsvg.add_shape(dot.moved(Location(c)), \"dashed\")\nsvg.add_shape(dot.moved(Location(p)), \"dashed\")\nsvg.write(\"assets/slot_center_point_example.svg\")\n\n# [Ex. 9]\nwith BuildSketch() as example_9:\n SlotCenterToCenter(1, 0.25, rotation=90)\n# [Ex. 9]\ns = 100 / max(*example_9.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(example_9.sketch)\nsvg.write(\"assets/slot_center_to_center_example.svg\")\n\n# [Ex. 10]\nwith BuildSketch() as example_10:\n SlotOverall(1, 0.25)\n# [Ex. 10]\ns = 100 / max(*example_10.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(example_10.sketch)\nsvg.write(\"assets/slot_overall_example.svg\")\n\n# [Ex. 11]\nwith BuildSketch() as example_11:\n Text(\"text\", 1)\n# [Ex. 11]\ns = 100 / max(*example_11.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(example_11.sketch)\nsvg.write(\"assets/text_example.svg\")\n\n# [Ex. 12]\nwith BuildSketch() as example_12:\n t = Trapezoid(2, 1, 80)\n with Locations((-0.6, -0.3)):\n Text(\"80\u00b0\", 0.3, mode=Mode.SUBTRACT)\n# [Ex. 12]\ns = 100 / max(*example_12.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.DASHED)\nsvg.add_shape(\n Edge.make_circle(\n 0.75,\n Plane(tuple(t.vertices().group_by(Axis.Y)[0].sort_by(Axis.X)[0])),\n start_angle=0,\n end_angle=80,\n ),\n \"dashed\",\n)\nsvg.add_shape(example_12.sketch)\nsvg.write(\"assets/trapezoid_example.svg\")\n\n# [Ex. 13]\nlength, radius = 40.0, 60.0\n\nwith BuildSketch() as circle_with_hole:\n Circle(radius=radius)\n Rectangle(width=length, height=length, mode=Mode.SUBTRACT)\n# [Ex. 13]\ns = 100 / max(*circle_with_hole.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(circle_with_hole.sketch)\nsvg.write(\"assets/circle_with_hole.svg\")\n\n# [Ex. 14]\nwith BuildPart() as controller:\n # Create the side view of the controller\n with BuildSketch(Plane.YZ) as profile:\n with BuildLine():\n Polyline((0, 0), (0, 40), (20, 80), (40, 80), (40, 0), (0, 0))\n # Create a filled face from the perimeter drawing\n make_face()\n # Extrude to create the basis controller shape\n extrude(amount=30, both=True)\n # Round off all the edges\n fillet(controller.edges(), radius=3)\n # Hollow out the controller\n offset(amount=-1, mode=Mode.SUBTRACT)\n # Extract the face that will house the display\n display_face = (\n controller.faces()\n .filter_by(GeomType.PLANE)\n .filter_by_position(Axis.Z, 50, 70)[0]\n )\n # Create a workplane from the face\n display_workplane = Plane(\n origin=display_face.center(), x_dir=(1, 0, 0), z_dir=display_face.normal_at()\n )\n # Place the sketch directly on the controller\n with BuildSketch(display_workplane) as display:\n RectangleRounded(40, 30, 2)\n with GridLocations(45, 35, 2, 2):\n Circle(1)\n # Cut the display sketch through the controller\n extrude(amount=-1, mode=Mode.SUBTRACT)\n# [Ex. 14]\nvisible, hidden = controller.part.project_to_viewport((70, -50, 120))\nmax_dimension = max(*Compound(children=visible + hidden).bounding_box().size)\nexporter = ExportSVG(scale=100 / max_dimension)\nexporter.add_layer(\"Visible\")\nexporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)\nexporter.add_shape(visible, layer=\"Visible\")\nexporter.add_shape(hidden, layer=\"Hidden\")\nexporter.write(f\"assets/controller.svg\")\n\nd = Draft(line_width=0.1)\n# [Ex. 15]\nwith BuildSketch() as isosceles_triangle:\n t = Triangle(a=30, b=40, c=40)\n # [Ex. 15]\n ExtensionLine(t.edges().sort_by(Axis.Y)[0], 6, d, label=\"a\")\n ExtensionLine(t.edges().sort_by(Axis.X)[-1], 6, d, label=\"b\")\n ExtensionLine(t.edges().sort_by(SortBy.LENGTH)[-1], 6, d, label=\"c\")\na1 = CenterArc(t.vertices().group_by(Axis.Y)[0].sort_by(Axis.X)[0], 5, 0, t.B)\na2 = CenterArc(t.vertices().group_by(Axis.Y)[0].sort_by(Axis.X)[-1], 5, 180 - t.C, t.C)\na3 = CenterArc(t.vertices().sort_by(Axis.Y)[-1], 5, 270 - t.A / 2, t.A)\np1 = CenterArc(t.vertices().group_by(Axis.Y)[0].sort_by(Axis.X)[0], 8, 0, t.B)\np2 = CenterArc(t.vertices().group_by(Axis.Y)[0].sort_by(Axis.X)[-1], 8, 180 - t.C, t.C)\np3 = CenterArc(t.vertices().sort_by(Axis.Y)[-1], 8, 270 - t.A / 2, t.A)\nt1 = Text(\"B\", font_size=d.font_size).moved(Pos(p1 @ 0.5))\nt2 = Text(\"C\", font_size=d.font_size).moved(Pos(p2 @ 0.5))\nt3 = Text(\"A\", font_size=d.font_size).moved(Pos(p3 @ 0.5))\n\ns = 100 / max(*isosceles_triangle.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_layer(\"dashed\", line_type=LineType.DASHED)\nsvg.add_shape([a1, a2, a3], \"dashed\")\nsvg.add_shape(isosceles_triangle.sketch)\nsvg.add_shape([t1, t2, t3])\nsvg.write(\"assets/triangle_example.svg\")\n\n\n# [Align]\nwith BuildSketch() as align:\n with GridLocations(1, 1, 2, 2):\n Circle(0.5)\n Circle(0.49, mode=Mode.SUBTRACT)\n with GridLocations(1, 1, 1, 2):\n Circle(0.5)\n Circle(0.49, mode=Mode.SUBTRACT)\n with GridLocations(1, 1, 2, 1):\n Circle(0.5)\n Circle(0.49, mode=Mode.SUBTRACT)\n with Locations((0, 0)):\n Circle(0.5)\n Circle(0.49, mode=Mode.SUBTRACT)\n\n # Top Right: (MIN, MIN)\n with Locations((0.75, 0.75)):\n Text(\"MIN\\nMIN\", font=\"FreeSerif\", font_size=0.07)\n # Top Center: (CENTER, MIN)\n with Locations((0.0, 0.75 + 0.07 / 2)):\n Text(\"CENTER\", font=\"FreeSerif\", font_size=0.07)\n with Locations((0.0, 0.75 - 0.07 / 2)):\n Text(\"MIN\", font=\"FreeSerif\", font_size=0.07)\n # Top Left: (MAX, MIN)\n with Locations((-0.75, 0.75 + 0.07 / 2)):\n Text(\"MAX\", font=\"FreeSerif\", font_size=0.07)\n with Locations((-0.75, 0.75 - 0.07 / 2)):\n Text(\"MIN\", font=\"FreeSerif\", font_size=0.07)\n # Center Right: (MIN, CENTER)\n with Locations((0.75, 0.07 / 2)):\n Text(\"MIN\", font=\"FreeSerif\", font_size=0.07)\n with Locations((0.75, -0.07 / 2)):\n Text(\"CENTER\", font=\"FreeSerif\", font_size=0.07)\n # Center: (CENTER, CENTER)\n with Locations((0, 0)):\n Text(\"CENTER\\nCENTER\", font=\"FreeSerif\", font_size=0.07)\n # Center Left: (MAX, CENTER)\n with Locations((-0.75, 0.07 / 2)):\n Text(\"MAX\", font=\"FreeSerif\", font_size=0.07)\n with Locations((-0.75, -0.07 / 2)):\n Text(\"CENTER\", font=\"FreeSerif\", font_size=0.07)\n # Bottom Right: (MIN, MAX)\n with Locations((0.75, -0.75 + 0.07 / 2)):\n Text(\"MIN\", font=\"FreeSerif\", font_size=0.07)\n with Locations((0.75, -0.75 - 0.07 / 2)):\n Text(\"MAX\", font=\"FreeSerif\", font_size=0.07)\n # Bottom Center: (CENTER, MAX)\n with Locations((0.0, -0.75 + 0.07 / 2)):\n Text(\"CENTER\", font=\"FreeSerif\", font_size=0.07)\n with Locations((0.0, -0.75 - 0.07 / 2)):\n Text(\"MAX\", font=\"FreeSerif\", font_size=0.07)\n # Bottom Left: (MAx, MAX)\n with Locations((-0.75, -0.75)):\n Text(\"MAX\\nMAX\", font=\"FreeSerif\", font_size=0.07)\n\ns = 100 / max(*align.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(align.sketch)\nsvg.write(\"assets/align.svg\")\n\n# [DimensionLine]\nstd = Draft()\nwith BuildSketch() as d_line:\n Rectangle(100, 100)\n c = Circle(45, mode=Mode.SUBTRACT)\n DimensionLine([c.edge() @ 0, c.edge() @ 0.5], draft=std)\ns = 100 / max(*d_line.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(d_line.sketch)\nsvg.write(\"assets/d_line.svg\")\n\n# [ExtensionLine]\nwith BuildSketch() as e_line:\n with BuildLine():\n l1 = Polyline((20, 40), (-40, 40), (-40, -40), (20, -40))\n RadiusArc(l1 @ 0, l1 @ 1, 50)\n make_face()\n ExtensionLine(border=e_line.edges().sort_by(Axis.X)[0], offset=10, draft=std)\n outside_curve = e_line.edges().sort_by(Axis.X)[-1]\n ExtensionLine(border=outside_curve, offset=10, label_angle=True, draft=std)\ns = 100 / max(*e_line.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(e_line.sketch)\nsvg.write(\"assets/e_line.svg\")\n\n# [TechnicalDrawing]\nwith BuildSketch() as tech_drawing:\n with Locations((0, 20)):\n add(e_line)\n TechnicalDrawing()\ns = 100 / max(*tech_drawing.sketch.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(tech_drawing.sketch)\nsvg.write(\"assets/tech_drawing.svg\")\n\n# [ArrowHead]\narrow_head_types = [HeadType.CURVED, HeadType.STRAIGHT, HeadType.FILLETED]\narrow_heads = [ArrowHead(50, a_type) for a_type in arrow_head_types]\ns = 100 / max(*arrow_heads[0].bounding_box().size)\nsvg = ExportSVG(scale=s)\nfor i, arrow_head in enumerate(arrow_heads):\n svg.add_shape(arrow_head.moved(Location((0, -i * 40))))\n svg.add_shape(Text(arrow_head_types[i].name, 5).moved(Location((-25, -i * 40))))\nsvg.write(\"assets/arrow_head.svg\")\n\n# [Arrow]\narrow = Arrow(\n 10, shaft_path=Edge.make_circle(100, start_angle=0, end_angle=10), shaft_width=1\n)\ns = 100 / max(*arrow.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(arrow)\nsvg.write(\"assets/arrow.svg\")\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/objects_3d", + "source": "docs/objects_3d.py", + "kind": "docs-script", + "code": "# [Setup]\nfrom build123d import *\n\n# [Setup]\n\n\ndef write_svg(filename: str, view_port_origin=(-100, -50, 30)):\n \"\"\"Save an image of the BuildPart object as SVG\"\"\"\n builder: BuildPart = BuildPart._get_context()\n\n visible, hidden = builder.part.project_to_viewport(view_port_origin)\n max_dimension = max(*Compound(children=visible + hidden).bounding_box().size)\n exporter = ExportSVG(scale=100 / max_dimension)\n exporter.add_layer(\"Visible\")\n exporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)\n exporter.add_shape(visible, layer=\"Visible\")\n exporter.add_shape(hidden, layer=\"Hidden\")\n exporter.write(f\"assets/{filename}.svg\")\n\n\n# [Ex. 1]\nwith BuildPart() as example_1:\n Box(3, 2, 1)\n # [Ex. 1]\n pass # [removed by collect.py] write_svg(\"box_example\")\n\n# [Ex. 2]\nwith BuildPart() as example_2:\n Cone(2, 1, 2)\n # [Ex. 2]\n pass # [removed by collect.py] write_svg(\"cone_example\")\n\n# [Ex. 3]\nwith BuildPart() as example_3:\n Box(3, 2, 1)\n with Locations(example_3.faces().sort_by(Axis.Z)[-1]):\n CounterBoreHole(0.2, 0.4, 0.5, 0.9)\n # [Ex. 3]\n pass # [removed by collect.py] write_svg(\"counter_bore_hole_example\")\n\n\n# [Ex. 4]\nwith BuildPart() as example_4:\n Box(3, 2, 1)\n with Locations(example_3.faces().sort_by(Axis.Z)[-1]):\n CounterSinkHole(0.2, 0.4, 0.9)\n # [Ex. 4]\n pass # [removed by collect.py] write_svg(\"counter_sink_hole_example\")\n\n# [Ex. 5]\nwith BuildPart() as example_5:\n Cylinder(1, 2)\n # [Ex. 5]\n pass # [removed by collect.py] write_svg(\"cylinder_example\")\n\n# [Ex. 6]\nwith BuildPart() as example_6:\n Box(3, 2, 1)\n Hole(0.4)\n # [Ex. 6]\n pass # [removed by collect.py] write_svg(\"hole_example\")\n\n# [Ex. 7]\nwith BuildPart() as example_7:\n Sphere(1, 0)\n # [Ex. 7]\n pass # [removed by collect.py] write_svg(\"sphere_example\")\n\n# [Ex. 8]\nwith BuildPart() as example_8:\n Torus(1, 0.2)\n # [Ex. 8]\n pass # [removed by collect.py] write_svg(\"torus_example\")\n\n# [Ex. 9]\nwith BuildPart() as example_9:\n Wedge(1, 1, 1, 0, 0, 0.5, 0.5)\n # [Ex. 9]\n pass # [removed by collect.py] write_svg(\"wedge_example\")\n\n# [Ex. 10]\nwith BuildPart() as example_10:\n Box(30, 20, 20)\n Box(20, 30, 20)\n Box(20, 20, 30)\n with Locations((-10, 0, 0)):\n Box(40, 23, 23)\n ConvexPolyhedron(example_10.vertices())\n # [Ex. 10]\n pass # [removed by collect.py] write_svg(\"convex_polyhedron_example\")\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/pack_demo", + "source": "docs/pack_demo.py", + "kind": "docs-script", + "code": "# [import]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n\n# [initial space]\nb1 = Box(100, 100, 100, align=(Align.CENTER, Align.CENTER, Align.MIN))\nb2 = Box(54, 54, 54, align=(Align.CENTER, Align.CENTER, Align.MAX), mode=Mode.SUBTRACT)\nb3 = Box(34, 34, 34, align=(Align.MIN, Align.MIN, Align.CENTER), mode=Mode.SUBTRACT)\nb4 = Box(24, 24, 24, align=(Align.MAX, Align.MAX, Align.CENTER), mode=Mode.SUBTRACT)\n\n\n\n\n# [Export SVG files]\ndef write_svg(part, filename: str, view_port_origin=(-100, 100, 150)):\n \"\"\"Save an image of the BuildPart object as SVG\"\"\"\n visible, hidden = part.project_to_viewport(view_port_origin)\n max_dimension = max(*Compound(children=visible + hidden).bounding_box().size)\n exporter = ExportSVG(scale=100 / max_dimension)\n exporter.add_layer(\"Visible\", line_weight=0.2)\n exporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)\n exporter.add_shape(visible, layer=\"Visible\")\n exporter.add_shape(hidden, layer=\"Hidden\")\n exporter.write(f\"assets/{filename}.svg\")\n\n\n\n\n# [removed by collect.py] write_svg(\n# [removed by collect.py] Compound(\n# [removed by collect.py] [b1, b2, b3, b4,],\n# [removed by collect.py] \"pack_demo_initial_state\"\n# [removed by collect.py] ),\n# [removed by collect.py] \"pack_demo_initial_state.svg\",\n# [removed by collect.py] (50, 0, 100),\n# [removed by collect.py] )\n\n# [pack 2D]\n\nxy_pack = pack(\n [b1, b2, b3, b4],\n padding=5,\n align_z=False\n)\n\n# [removed by collect.py] write_svg(Compound(xy_pack), \"pack_demo_packed_xy.svg\", (50, 0, 100))\n\n\n# [Pack and align_z]\n\n\nz_pack = pack(\n [b1, b2, b3, b4],\n padding=5,\n align_z=True\n)\n\n# [removed by collect.py] write_svg(Compound(z_pack), \"pack_demo_packed_z.svg\", (50, 0, 100))\n\n\n# [bounding box]\nprint(Compound(xy_pack).bounding_box())\nprint(Compound(z_pack).bounding_box())", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/rigid_joints_pipe", + "source": "docs/rigid_joints_pipe.py", + "kind": "docs-script", + "code": "import copy\nfrom build123d import *\nfrom bd_warehouse.flange import WeldNeckFlange\nfrom bd_warehouse.pipe import PipeSection\n# [removed by collect.py] from ocp_vscode import *\n\nflange_inlet = WeldNeckFlange(nps=\"10\", flange_class=300)\nflange_outlet = copy.copy(flange_inlet)\n\nwith BuildPart() as pipe_builder:\n # Create the pipe\n with BuildLine():\n path = TangentArc((0, 0, 0), (2 * FT, 0, 1 * FT), tangent=(1, 0, 0))\n with BuildSketch(Plane(origin=path @ 0, z_dir=path % 0)):\n PipeSection(\"10\", material=\"stainless\", identifier=\"40S\")\n sweep()\n\n # Add the joints\n RigidJoint(label=\"inlet\", joint_location=-path.location_at(0))\n RigidJoint(label=\"outlet\", joint_location=path.location_at(1))\n\n# Place the flanges at the ends of the pipe\npipe_builder.part.joints[\"inlet\"].connect_to(flange_inlet.joints[\"pipe\"])\npipe_builder.part.joints[\"outlet\"].connect_to(flange_outlet.joints[\"pipe\"])\n\nshow(pipe_builder, flange_inlet, flange_outlet, render_joints=True)\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/rod_end", + "source": "docs/rod_end.py", + "kind": "docs-script", + "code": "from build123d import *\nfrom bd_warehouse.thread import IsoThread\n# [removed by collect.py] from ocp_vscode import *\n\n# Create the thread so the min radius is available below\nthread = IsoThread(major_diameter=6, pitch=1, length=20, end_finishes=(\"fade\", \"raw\"))\ninner_radius = 15.89 / 2\ninner_gap = 0.2\n\nwith BuildPart() as rod_end:\n # Create the outer shape\n with BuildSketch():\n Circle(22.25 / 2)\n with Locations((0, -12)):\n Rectangle(8, 1)\n make_hull()\n split(bisect_by=Plane.YZ)\n revolve(axis=Axis.Y)\n # Refine the shape\n with BuildSketch(Plane.YZ) as s2:\n Rectangle(25, 8, align=(Align.MIN, Align.CENTER))\n Rectangle(9, 10, align=(Align.MIN, Align.CENTER))\n chamfer(s2.vertices(), 0.5)\n revolve(axis=Axis.Z, mode=Mode.INTERSECT)\n # Add the screw shaft\n Cylinder(\n thread.min_radius,\n 30,\n rotation=(90, 0, 0),\n align=(Align.CENTER, Align.CENTER, Align.MIN),\n )\n # Cutout the ball socket\n Sphere(inner_radius, mode=Mode.SUBTRACT)\n # Add thread\n with Locations((0, -30, 0)):\n add(thread, rotation=(-90, 0, 0))\n # Create the ball joint\n BallJoint(\n \"socket\",\n joint_location=Location(),\n angular_range=((-14, 14), (-14, 14), (0, 360)),\n )\n\nwith BuildPart() as ball:\n Sphere(inner_radius - inner_gap)\n Box(50, 50, 13, mode=Mode.INTERSECT)\n Hole(4)\n ball.part.color = Color(\"aliceblue\")\n RigidJoint(\"ball\", joint_location=Location())\n\nrod_end.part.joints[\"socket\"].connect_to(ball.part.joints[\"ball\"], angles=(5, 10, 0))\n\nshow(rod_end.part, ball.part, s2)\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/selector_example", + "source": "docs/selector_example.py", + "kind": "docs-script", + "code": "# [Code]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nwith BuildPart() as example:\n Cylinder(radius=10, height=3)\n with BuildSketch(example.faces().sort_by(Axis.Z)[-1]):\n RegularPolygon(radius=7, side_count=6)\n Circle(radius=4, mode=Mode.SUBTRACT)\n extrude(amount=-2, mode=Mode.SUBTRACT)\n visible, hidden = example.part.project_to_viewport((-100, 100, 100))\n exporter = ExportSVG(scale=6)\n exporter.add_layer(\"Visible\")\n exporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)\n exporter.add_shape(visible, layer=\"Visible\")\n exporter.add_shape(hidden, layer=\"Hidden\")\n exporter.write(\"assets/selector_before.svg\")\n\n fillet(\n example.edges()\n .filter_by(GeomType.CIRCLE)\n .sort_by(SortBy.RADIUS)[-2:]\n .sort_by(Axis.Z)[-1],\n radius=1,\n )\n\nvisible, hidden = example.part.project_to_viewport((-100, 100, 100))\nexporter = ExportSVG(scale=6)\nexporter.add_layer(\"Visible\")\nexporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)\nexporter.add_shape(visible, layer=\"Visible\")\nexporter.add_shape(hidden, layer=\"Hidden\")\nexporter.write(\"assets/selector_after.svg\")\n\nshow(example)\n# [End]\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/slide_latch", + "source": "docs/slide_latch.py", + "kind": "docs-script", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nwith BuildPart() as latch:\n # Basic box shape to start with filleted corners\n Box(70, 30, 14)\n end = latch.faces().sort_by(Axis.X)[-1] # save the end with the hole\n fillet(latch.edges().filter_by(Axis.Z), 2)\n fillet(latch.edges().sort_by(Axis.Z)[-1], 1)\n # Make screw tabs\n with BuildSketch(latch.faces().sort_by(Axis.Z)[0]) as l4:\n with Locations((-30, 0), (30, 0)):\n SlotOverall(50, 10, rotation=90)\n Rectangle(50, 30)\n fillet(l4.vertices(Select.LAST), radius=2)\n extrude(amount=-2)\n with GridLocations(60, 40, 2, 2):\n Hole(2)\n # Create the hole from the end saved previously\n with BuildSketch(end) as slide_hole:\n add(end)\n offset(amount=-2)\n fillet(slide_hole.vertices(), 1)\n extrude(amount=-68, mode=Mode.SUBTRACT)\n # Slot for the handle to slide in\n with BuildSketch(latch.faces().sort_by(Axis.Z)[-1]):\n SlotOverall(32, 8)\n extrude(amount=-2, mode=Mode.SUBTRACT)\n # The slider will move align the x axis 12mm in each direction\n LinearJoint(\"latch\", axis=Axis.X, linear_range=(-12, 12))\n\nwith BuildPart() as slide:\n # The slide will be a little smaller than the hole\n with BuildSketch() as s1:\n add(slide_hole.sketch)\n offset(amount=-0.25)\n # The extrusions aren't symmetric\n extrude(amount=46)\n extrude(slide.faces().sort_by(Axis.Z)[0], amount=20)\n # Round off the ends\n fillet(slide.edges().group_by(Axis.Z)[0], 1)\n fillet(slide.edges().group_by(Axis.Z)[-1], 1)\n # Create the knob\n with BuildSketch() as s2:\n with Locations((12, 0)):\n SlotOverall(15, 4, rotation=90)\n Rectangle(12, 7, align=(Align.MIN, Align.CENTER))\n fillet(s2.vertices(Select.LAST), 1)\n split(bisect_by=Plane.XZ)\n revolve(axis=Axis.X)\n # Align the joint to Plane.ZY flipped\n RigidJoint(\"slide\", joint_location=Location(-Plane.ZY))\n\n# Position the slide in the latch: -12 >= position <= 12\nlatch.part.joints[\"latch\"].connect_to(slide.part.joints[\"slide\"], position=12)\n\n# show(latch.part, render_joints=True)\n# show(slide.part, render_joints=True)\nshow(latch.part, slide.part, render_joints=True)\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/spitfire_wing_gordon", + "source": "docs/spitfire_wing_gordon.py", + "kind": "docs-script", + "code": "import pytest\n\n# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nwing_span = 36 * FT + 10 * IN\nwing_leading = 2.5 * FT\nwing_trailing = wing_span / 4 - wing_leading\nwing_leading_fraction = wing_leading / (wing_leading + wing_trailing)\nwing_tip_section = wing_span / 2 - 1 * IN # distance from root to last section\n\n# Create leading and trailing edges\nleading_edge = EllipticalCenterArc(\n (0, 0), wing_span / 2, wing_leading, start_angle=270, arc_size=90\n)\ntrailing_edge = EllipticalCenterArc(\n (0, 0), wing_span / 2, wing_trailing, start_angle=0, arc_size=90\n)\n\n# [AirfoilSizes]\n# Calculate the airfoil sizes from the leading/trailing edges\nairfoil_sizes = []\nfor i in [0, 1]:\n tip_axis = Axis(i * (wing_tip_section, 0, 0), (0, 1, 0))\n leading_pnt = leading_edge.intersect(tip_axis)[0]\n trailing_pnt = trailing_edge.intersect(tip_axis)[0]\n airfoil_sizes.append(trailing_pnt.Y - leading_pnt.Y)\n\n# [Airfoils]\n# Create the root and tip airfoils - note that they are different NACA profiles\nairfoil_root = Plane.YZ * scale(\n Airfoil(\"2213\").move(Pos(-wing_leading_fraction, 0, 0)),\n airfoil_sizes[0],\n about=(0, 0, 0),\n)\nairfoil_tip = (\n Plane.YZ\n * Pos(Z=wing_tip_section)\n * scale(\n Airfoil(\"2205\").move(Pos(-wing_leading_fraction, 0, 0)),\n airfoil_sizes[1],\n about=(0, 0, 0),\n )\n)\n\n# [Profiles]\n# Create the Gordon surface profiles and guides\nprofiles = airfoil_root.edges() + airfoil_tip.edges()\nprofiles.append(leading_edge @ 1) # wing tip\nguides = [leading_edge, trailing_edge]\n\n# Create the wing surface as a Gordon Surface\nwing_surface = -Face.make_gordon_surface(profiles, guides)\n# Create the root of the wing\nwing_root = -Face(Wire(wing_surface.edges().filter_by(Edge.is_closed)))\n\n# [Solid]\n# Create the wing Solid\nwing = Solid(Shell([wing_surface, wing_root]))\nwing.color = 0x99A3B9 # Azure Blue\n\nshow(wing)\n# [End]\n\nassert wing.volume / 1e9 == pytest.approx(1.9879945989)\n\n# Documentation artifact generation\n# wing_control_edges = Curve(\n# [airfoil_root, airfoil_tip, Vertex(leading_edge @ 1), leading_edge, trailing_edge]\n# )\n# visible, _ = wing_control_edges.project_to_viewport((50 * FT, -50 * FT, 50 * FT))\n# max_dimension = max(*Compound(children=visible).bounding_box().size)\n# svg = ExportSVG(scale=100 / max_dimension)\n# svg.add_shape(visible)\n# svg.write(\"assets/surface_modeling/spitfire_wing_profiles_guides.svg\")\n\n# export_gltf(\n# wing,\n# \"assets/surface_modeling/spitfire_wing.glb\",\n# binary=True,\n# linear_deflection=0.1,\n# angular_deflection=1,\n# )\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/technical_drawing", + "source": "docs/technical_drawing.py", + "kind": "docs-script", + "code": "# [code]\nfrom datetime import date\n\nfrom bd_warehouse.open_builds import StepperMotor\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n\ndef project_to_2d(\n part: Part,\n viewport_origin: VectorLike,\n viewport_up: VectorLike,\n page_origin: VectorLike,\n scale_factor: float = 1.0,\n) -> tuple[ShapeList[Edge], ShapeList[Edge]]:\n \"\"\"project_to_2d\n\n Helper function to generate 2d views translated on the 2d page.\n\n Args:\n part (Part): 3d object\n viewport_origin (VectorLike): location of viewport\n viewport_up (VectorLike): direction of the viewport Y axis\n page_origin (VectorLike): center of 2d object on page\n scale_factor (float, optional): part scalar. Defaults to 1.0.\n\n Returns:\n tuple[ShapeList[Edge], ShapeList[Edge]]: visible & hidden edges\n \"\"\"\n scaled_part = part if scale_factor == 1.0 else scale(part, scale_factor)\n visible, hidden = scaled_part.project_to_viewport(\n viewport_origin, viewport_up, look_at=(0, 0, 0)\n )\n visible = [Pos(*page_origin) * e for e in visible]\n hidden = [Pos(*page_origin) * e for e in hidden]\n\n return ShapeList(visible), ShapeList(hidden)\n\n\n# The object that appearing in the drawing\nstepper: Part = StepperMotor(\"Nema23\")\n\n# Create a standard technical drawing border on A4 paper\nborder = TechnicalDrawing(\n designed_by=\"build123d\",\n design_date=date.fromisoformat(\"2025-05-23\"),\n page_size=PageSize.A4,\n title=\"Nema 23 Stepper\",\n sub_title=\"Units: mm\",\n drawing_number=\"BD-1\",\n sheet_number=1,\n drawing_scale=1,\n)\npage_size = border.bounding_box().size\n\n# Specify the drafting options for extension lines\ndrafting_options = Draft(font_size=3.5, decimal_precision=1, display_units=False)\n\n# Lists used to store the 2d visible and hidden lines\nvisible_lines, hidden_lines = [], []\n\n# Isometric Projection - A 3D view where the part is rotated to reveal three\n# dimensions equally.\niso_v, iso_h = project_to_2d(\n stepper,\n (100, 100, 100),\n (0, 0, 1),\n page_size * 0.3,\n 0.75,\n)\nvisible_lines.extend(iso_v)\nhidden_lines.extend(iso_h)\n\n# Plan View (Top) - The view from directly above the part (looking down along\n# the Z-axis).\nvis, _ = project_to_2d(\n stepper,\n (0, 0, 100),\n (0, 1, 0),\n (page_size.X * -0.3, page_size.Y * 0.25),\n)\nvisible_lines.extend(vis)\n\n# Dimension the top of the stepper\ntop_bbox = Curve(vis).bounding_box()\nperimeter = Pos(*top_bbox.center()) * Rectangle(top_bbox.size.X, top_bbox.size.Y)\nd1 = ExtensionLine(\n border=perimeter.edges().sort_by(Axis.X)[-1], offset=1 * CM, draft=drafting_options\n)\nd2 = ExtensionLine(\n border=perimeter.edges().sort_by(Axis.Y)[0], offset=1 * CM, draft=drafting_options\n)\n# Add a label\nl1 = Text(\"Plan View\", 6)\nl1.position = vis.sort_by(Axis.Y)[-1].center() + (0, 5 * MM)\n\n# Front Elevation - The primary view, typically looking along the Y-axis,\n# showing the height.\nvis, _ = project_to_2d(\n stepper,\n (0, -100, 0),\n (0, 0, 1),\n (page_size.X * -0.3, page_size.Y * -0.125),\n)\nvisible_lines.extend(vis)\nd3 = ExtensionLine(\n border=vis.sort_by(Axis.Y)[-1], offset=-5 * MM, draft=drafting_options\n)\nl2 = Text(\"Front Elevation\", 6)\nl2.position = vis.group_by(Axis.Y)[0].sort_by(Edge.length)[-1].center() + (0, -5 * MM)\n\n# Side Elevation - Often refers to the Right Side View, looking along the X-axis.\nvis, _ = project_to_2d(\n stepper,\n (100, 0, 0),\n (0, 0, 1),\n (0, page_size.Y * 0.15),\n)\nvisible_lines.extend(vis)\nside_bbox = Curve(vis).bounding_box()\nshaft_top_corner = vis.edges().sort_by(Axis.Y)[-1].vertices().sort_by(Axis.X)[-1]\nbody_bottom_corner = (side_bbox.max.X, side_bbox.min.Y)\nd4 = ExtensionLine(\n border=(shaft_top_corner, body_bottom_corner),\n offset=-(side_bbox.max.X - shaft_top_corner.X) - 1 * CM, # offset to outside view.\n measurement_direction=(0, 1, 0),\n draft=drafting_options,\n)\nl3 = Text(\"Side Elevation\", 6)\nl3.position = vis.group_by(Axis.Y)[0].sort_by(Edge.length)[-1].center() + (0, -5 * MM)\n\n\n# Initialize the SVG exporter\nexporter = ExportSVG(unit=Unit.MM)\n# Define visible and hidden line layers\nexporter.add_layer(\"Visible\")\nexporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)\n# Add the objects to the appropriate layer\nexporter.add_shape(visible_lines, layer=\"Visible\")\nexporter.add_shape(hidden_lines, layer=\"Hidden\")\nexporter.add_shape(border, layer=\"Visible\")\nexporter.add_shape([d1, d2, d3, d4], layer=\"Visible\")\nexporter.add_shape([l1, l2, l3], layer=\"Visible\")\n# Write the file\nexporter.write(f\"assets/stepper_drawing.svg\")\n\nshow(border, visible_lines, d1, d2, d3, d4, l1, l2, l3)\n# [end]\n", + "data_dir": "docs", + "assets": [] + }, + { + "id": "docs/tutorial_joints", + "source": "docs/tutorial_joints.py", + "kind": "docs-script", + "code": "# [import]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n\n# [Hinge Class]\nclass Hinge(Compound):\n \"\"\"Hinge\n\n Half a simple hinge with several joints. The joints are:\n - \"leaf\": RigidJoint where hinge attaches to object\n - \"hinge_axis\": RigidJoint (inner) or RevoluteJoint (outer)\n - \"hole0\", \"hole1\", \"hole2\": CylindricalJoints for attachment screws\n\n Args:\n width (float): width of one leaf\n length (float): hinge length\n barrel_diameter (float): size of hinge pin barrel\n thickness (float): hinge leaf thickness\n pin_diameter (float): hinge pin diameter\n inner (bool, optional): inner or outer half of hinge . Defaults to True.\n \"\"\"\n\n def __init__(\n self,\n width: float,\n length: float,\n barrel_diameter: float,\n thickness: float,\n pin_diameter: float,\n inner: bool = True,\n ):\n # The profile of the hinge used to create the tabs\n with BuildPart() as hinge_profile:\n with BuildSketch():\n for i, loc in enumerate(\n GridLocations(0, length / 5, 1, 5, align=(Align.MIN, Align.MIN))\n ):\n if i % 2 == inner:\n with Locations(loc):\n Rectangle(width, length / 5, align=(Align.MIN, Align.MIN))\n Rectangle(\n width - barrel_diameter,\n length,\n align=(Align.MIN, Align.MIN),\n )\n extrude(amount=-barrel_diameter)\n\n # The hinge pin\n with BuildPart() as pin:\n Cylinder(\n radius=pin_diameter / 2,\n height=length,\n align=(Align.CENTER, Align.CENTER, Align.MIN),\n )\n with BuildPart(pin.part.faces().sort_by(Axis.Z)[-1]) as pin_head:\n Cylinder(\n radius=barrel_diameter / 2,\n height=pin_diameter,\n align=(Align.CENTER, Align.CENTER, Align.MIN),\n )\n fillet(\n pin.edges(Select.LAST).filter_by(GeomType.CIRCLE),\n radius=pin_diameter / 3,\n )\n\n # Either the external and internal leaf with joints\n with BuildPart() as leaf_builder:\n with BuildSketch():\n with BuildLine():\n l1 = Line((0, 0), (width - barrel_diameter / 2, 0))\n l2 = RadiusArc(\n l1 @ 1,\n l1 @ 1 + Vector(0, barrel_diameter),\n -barrel_diameter / 2,\n )\n l3 = RadiusArc(\n l2 @ 1,\n (\n width - barrel_diameter,\n barrel_diameter / 2,\n ),\n -barrel_diameter / 2,\n )\n l4 = Line(l3 @ 1, (width - barrel_diameter, thickness))\n l5 = Line(l4 @ 1, (0, thickness))\n Line(l5 @ 1, l1 @ 0)\n make_face()\n with Locations(\n (width - barrel_diameter / 2, barrel_diameter / 2)\n ) as pin_center:\n Circle(pin_diameter / 2 + 0.1 * MM, mode=Mode.SUBTRACT)\n extrude(amount=length)\n add(hinge_profile.part, rotation=(90, 0, 0), mode=Mode.INTERSECT)\n\n # Create holes for fasteners\n with Locations(leaf_builder.part.faces().filter_by(Axis.Y)[-1]):\n with GridLocations(0, length / 3, 1, 3):\n holes = CounterSinkHole(3 * MM, 5 * MM)\n # Add the hinge pin to the external leaf\n if not inner:\n with Locations(pin_center.locations[0]):\n add(pin.part)\n\n # [Create the Joints]\n #\n # Leaf attachment\n RigidJoint(\n label=\"leaf\",\n joint_location=Location(\n (width - barrel_diameter, 0, length / 2), (90, 0, 0)\n ),\n )\n # [Hinge Axis] (fixed with inner)\n if inner:\n RigidJoint(\n \"hinge_axis\",\n joint_location=Location(\n (width - barrel_diameter / 2, barrel_diameter / 2, 0)\n ),\n )\n else:\n RevoluteJoint(\n \"hinge_axis\",\n axis=Axis(\n (width - barrel_diameter / 2, barrel_diameter / 2, 0), (0, 0, 1)\n ),\n angular_range=(90, 270),\n )\n # [Fastener holes]\n hole_locations = [hole.location for hole in holes]\n for hole, hole_location in enumerate(hole_locations):\n CylindricalJoint(\n label=\"hole\" + str(hole),\n axis=Axis(hole_location),\n linear_range=(-2 * CM, 2 * CM),\n angular_range=(0, 360),\n )\n # [End Fastener holes]\n super().__init__(leaf_builder.part.wrapped, joints=leaf_builder.part.joints)\n # [Hinge Class]\n\n\n# [Create instances of the two leaves of the hinge]\nhinge_inner = Hinge(\n width=5 * CM,\n length=12 * CM,\n barrel_diameter=1 * CM,\n thickness=2 * MM,\n pin_diameter=4 * MM,\n)\nhinge_outer = Hinge(\n width=5 * CM,\n length=12 * CM,\n barrel_diameter=1 * CM,\n thickness=2 * MM,\n pin_diameter=4 * MM,\n inner=False,\n)\n\n# [Create the box with a RigidJoint to mount the hinge]\nwith BuildPart() as box_builder:\n box = Box(30 * CM, 30 * CM, 10 * CM)\n offset(amount=-1 * CM, openings=box_builder.faces().sort_by(Axis.Z)[-1])\n # Create a notch for the hinge\n with Locations((-15 * CM, 0, 5 * CM)):\n Box(2 * CM, 12 * CM, 4 * MM, mode=Mode.SUBTRACT)\n bbox = box.bounding_box()\n with Locations(\n Plane(origin=(bbox.min.X, 0, bbox.max.Z - 30 * MM), z_dir=(-1, 0, 0))\n ):\n with GridLocations(0, 40 * MM, 1, 3):\n Hole(3 * MM, 1 * CM)\n RigidJoint(\n \"hinge_attachment\",\n joint_location=Location((-15 * CM, 0, 4 * CM), (180, 90, 0)),\n )\n# [Demonstrate that objects with Joints can be moved and the joints follow]\nbox = box_builder.part.moved(Location((0, 0, 5 * CM)))\n\n# [The lid with a RigidJoint for the hinge]\nwith BuildPart() as lid_builder:\n Box(30 * CM, 30 * CM, 1 * CM, align=(Align.MIN, Align.CENTER, Align.MIN))\n with Locations((2 * CM, 0, 0)):\n with GridLocations(0, 40 * MM, 1, 3):\n Hole(3 * MM, 1 * CM)\n RigidJoint(\n \"hinge_attachment\",\n joint_location=Location((0, 0, 0), (0, 0, 180)),\n )\nlid = lid_builder.part\n\n# [A screw to attach the hinge to the box]\nm6_screw = import_step(\"M6-1x12-countersunk-screw.step\")\nm6_joint = RigidJoint(\"head\", m6_screw, Location((0, 0, 0), (0, 0, 0)))\n# [End of screw creation]\n\n\n# [Export SVG files]\ndef write_svg(part, filename: str, view_port_origin=(-100, 100, 150)):\n \"\"\"Save an image of the BuildPart object as SVG\"\"\"\n visible, hidden = part.project_to_viewport(view_port_origin)\n max_dimension = max(*Compound(children=visible + hidden).bounding_box().size)\n exporter = ExportSVG(scale=100 / max_dimension)\n exporter.add_layer(\"Visible\")\n exporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)\n exporter.add_shape(visible, layer=\"Visible\")\n exporter.add_shape(hidden, layer=\"Hidden\")\n exporter.write(f\"assets/{filename}.svg\")\n\n\n#\n# SVG Export options\n# [removed by collect.py] write_svg(\n# [removed by collect.py] Compound([box, box.joints[\"hinge_attachment\"].symbol]),\n# [removed by collect.py] \"tutorial_joint_box\",\n# [removed by collect.py] )\n# [removed by collect.py] write_svg(\n# [removed by collect.py] Compound(\n# [removed by collect.py] [\n# [removed by collect.py] hinge_inner,\n# [removed by collect.py] hinge_inner.joints[\"leaf\"].symbol,\n# [removed by collect.py] hinge_inner.joints[\"hinge_axis\"].symbol,\n# [removed by collect.py] hinge_inner.joints[\"hole0\"].symbol,\n# [removed by collect.py] hinge_inner.joints[\"hole1\"].symbol,\n# [removed by collect.py] hinge_inner.joints[\"hole2\"].symbol,\n# [removed by collect.py] ]\n# [removed by collect.py] ),\n# [removed by collect.py] \"tutorial_joint_inner_leaf\",\n# [removed by collect.py] (100, 100, -50),\n# [removed by collect.py] )\n# [removed by collect.py] write_svg(\n# [removed by collect.py] Compound(\n# [removed by collect.py] [\n# [removed by collect.py] hinge_outer,\n# [removed by collect.py] hinge_outer.joints[\"leaf\"].symbol,\n# [removed by collect.py] hinge_outer.joints[\"hinge_axis\"].symbol,\n# [removed by collect.py] hinge_outer.joints[\"hole0\"].symbol,\n# [removed by collect.py] hinge_outer.joints[\"hole1\"].symbol,\n# [removed by collect.py] hinge_outer.joints[\"hole2\"].symbol,\n# [removed by collect.py] ]\n# [removed by collect.py] ),\n# [removed by collect.py] \"tutorial_joint_outer_leaf\",\n# [removed by collect.py] (100, 100, -50),\n# [removed by collect.py] )\n# [removed by collect.py] write_svg(\n# [removed by collect.py] Compound([box, hinge_outer]),\n# [removed by collect.py] \"tutorial_joint_box_outer\",\n# [removed by collect.py] (-100, -100, 50),\n# [removed by collect.py] )\n# [removed by collect.py] write_svg(\n# [removed by collect.py] Compound([lid, lid.joints[\"hinge_attachment\"].symbol]),\n# [removed by collect.py] \"tutorial_joint_lid\",\n# [removed by collect.py] (-100, 100, 150),\n# [removed by collect.py] )\n# [removed by collect.py] write_svg(\n# [removed by collect.py] Compound([m6_screw, m6_joint.symbol]),\n# [removed by collect.py] \"tutorial_joint_m6_screw\",\n# [removed by collect.py] (-100, 100, 150),\n# [removed by collect.py] )\n\n# [Connect Box to Outer Hinge]\nbox.joints[\"hinge_attachment\"].connect_to(hinge_outer.joints[\"leaf\"])\n# [Connect Box to Outer Hinge]\n# [removed by collect.py] write_svg(\n# [removed by collect.py] Compound([box, hinge_outer]),\n# [removed by collect.py] \"tutorial_joint_box_outer\",\n# [removed by collect.py] (-100, -100, 50),\n# [removed by collect.py] )\n# [Connect Hinge Leaves]\nhinge_outer.joints[\"hinge_axis\"].connect_to(hinge_inner.joints[\"hinge_axis\"], angle=120)\n# [Connect Hinge Leaves]\n# [removed by collect.py] write_svg(\n# [removed by collect.py] Compound([box, hinge_outer, hinge_inner]),\n# [removed by collect.py] \"tutorial_joint_box_outer_inner\",\n# [removed by collect.py] (-100, -100, 50),\n# [removed by collect.py] )\n# [Connect Hinge to Lid]\nhinge_inner.joints[\"leaf\"].connect_to(lid.joints[\"hinge_attachment\"])\n# [Connect Hinge to Lid]\n# [removed by collect.py] write_svg(\n# [removed by collect.py] Compound([box, hinge_outer, hinge_inner, lid]),\n# [removed by collect.py] \"tutorial_joint_box_outer_inner_lid\",\n# [removed by collect.py] (-100, -100, 50),\n# [removed by collect.py] )\n# [Connect Screw to Hole]\nhinge_outer.joints[\"hole2\"].connect_to(m6_joint, position=5 * MM, angle=30)\n# [Connect Screw to Hole]\n\n# [Add labels]\nbox.label = \"box\"\nlid.label = \"lid\"\nhinge_outer.label = \"outer hinge\"\nhinge_inner.label = \"inner hinge\"\nm6_screw.label = \"M6 screw\"\n\n# [Create assembly]\nbox_assembly = Compound(label=\"assembly\", children=[box, lid, hinge_inner, hinge_outer])\n# [Display assembly]\nprint(box_assembly.show_topology())\n\n# [Add to the assembly by assigning the parent attribute of an object]\nm6_screw.parent = box_assembly\nprint(box_assembly.show_topology())\n\n# [Check that the components in the assembly don't intersect]\nchild_intersect, children, volume = box_assembly.do_children_intersect(\n include_parent=False\n)\nprint(f\"do children intersect: {child_intersect}\")\nif child_intersect:\n print(f\"{children} by {volume:0.3f} mm^3\")\n\n# [Export Final SVG file]\n# [removed by collect.py] write_svg(box_assembly, \"tutorial_joint\", (-100, -100, 50))\n\n\nshow_object(box, name=\"box\", options={\"alpha\": 0.8})\n# show_object(box.joints[\"hinge_attachment\"].symbol, name=\"box attachment point\")\nshow_object(hinge_outer, name=\"hinge_outer\")\n# show_object(hinge_outer.joints[\"leaf\"].symbol, name=\"hinge_outer leaf joint\")\n# show_object(hinge_outer.joints[\"hinge_axis\"].symbol, name=\"hinge_outer hinge axis\")\nshow_object(lid, name=\"lid\")\n# show_object(lid.joints[\"hinge_attachment\"].symbol, name=\"lid attachment point\")\nshow_object(hinge_inner, name=\"hinge_inner\")\n# show_object(hinge_inner.joints[\"leaf\"].symbol, name=\"hinge_inner leaf joint\")\n# show_object(hinge_inner.joints[\"hinge_axis\"].symbol, name=\"hinge_inner hinge axis\")\nfor hole in [0, 1, 2]:\n show_object(\n hinge_inner.joints[\"hole\" + str(hole)].symbol,\n name=\"hinge_inner hole \" + str(hole),\n )\n show_object(\n hinge_outer.joints[\"hole\" + str(hole)].symbol,\n name=\"hinge_outer hole \" + str(hole),\n )\nshow_object(m6_screw, name=\"m6 screw\")\nshow_object(m6_joint.symbol, name=\"m6 screw symbol\")\nshow_object(box_assembly, name=\"box assembly\")\n", + "data_dir": "docs", + "assets": [ + "M6-1x12-countersunk-screw.step" + ] + }, + { + "id": "docs-objects/text", + "source": "docs/objects/examples/text.py", + "kind": "docs-script", + "code": "from build123d import Text, Pos, Compound, TextAlign, Align, Location, RadiusArc\nfrom tcv_screenshots import save_model\n\n\ntext = \"The quick brown fox\"\nsave_model(Text(text, 10), \"text\", {\"reset_camera\": \"top\"})\npath = RadiusArc((-50, 0), (50, 0), 100)\nsave_model([path, Text(text, 10, path=path, position_on_path=.5, text_align=(TextAlign.CENTER, TextAlign.BOTTOM))], \"path\", {\"reset_camera\": \"top\"})\nsave_model([Pos(Y=10) * Text(text, 10, \"singleline\"), Text(text, 10, \"singleline\", single_line_width=1)], \"outline\", {\"reset_camera\": \"top\"})\nsave_model(Compound.make_text(text, 10, \"singleline\"), \"singleline\", {\"reset_camera\": \"top\"})\n\ntext = \"The quick brown\\nfox jumped over\\nthe lazy dog.\"\nsave_model([Location(), Text(text, 2, text_align=(TextAlign.LEFT, TextAlign.TOPFIRSTLINE))], \"text_align\", {\"reset_camera\": \"top\"})\nsave_model([Location(), Text(text, 2, align=(Align.MIN, Align.MIN))], \"align\", {\"reset_camera\": \"top\"})\n\nt = Text(\"The\", 10, \"Source Sans 3 Black\")\nsave_model([(Pos(Y=10) * t).wires(), t], \"missing_glyph\", {\"reset_camera\": \"top\"})\n", + "data_dir": "docs/objects/examples", + "assets": [] + }, + { + "id": "docs-selectors/filter_all_edges_circle", + "source": "docs/topology_selection/examples/filter_all_edges_circle.py", + "kind": "docs-script", + "code": "import os\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nwith BuildPart() as part:\n with BuildSketch() as s:\n Rectangle(115, 50)\n with Locations((5 / 2, 0)):\n SlotOverall(90, 12, mode=Mode.SUBTRACT)\n extrude(amount=15)\n\n with BuildSketch(Plane.XZ.offset(50 / 2)) as s3:\n with Locations((-115 / 2 + 26, 15)):\n SlotOverall(42 + 2 * 26 + 12, 2 * 26, rotation=90)\n zz = extrude(amount=-12)\n split(bisect_by=Plane.XY)\n edgs = part.part.edges().filter_by(Axis.Y).group_by(Axis.X)[-2]\n fillet(edgs, 9)\n\n with Locations(zz.faces().sort_by(Axis.Y)[0]):\n with Locations((42 / 2 + 6, 0)):\n CounterBoreHole(24 / 2, 34 / 2, 4)\n mirror(about=Plane.XZ)\n\n with BuildSketch() as s4:\n RectangleRounded(115, 50, 6)\n extrude(amount=80, mode=Mode.INTERSECT)\n # fillet does not work right, mode intersect is safer\n\n with BuildSketch(Plane.YZ) as s4:\n with BuildLine() as bl:\n l1 = Line((0, 0), (18 / 2, 0))\n l2 = PolarLine(l1 @ 1, 8, 60, length_mode=LengthMode.VERTICAL)\n l3 = Line(l2 @ 1, (0, 8))\n mirror(about=Plane.YZ)\n make_face()\n extrude(amount=115 / 2, both=True, mode=Mode.SUBTRACT)\n\n faces = part.faces().filter_by(\n lambda f: all(e.geom_type == GeomType.CIRCLE for e in f.edges())\n )\n for i, f in enumerate(faces):\n RigidJoint(f\"bearing_bore_{i}\", joint_location=f.center_location)\n\nshow(part, [f.translate(f.normal_at() * 0.01) for f in faces], render_joints=True)\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"filter_all_edges_circle.png\"))\n", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/filter_axisplane", + "source": "docs/topology_selection/examples/filter_axisplane.py", + "kind": "docs-script", + "code": "import os\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\naxis = Axis.Z\nplane = Plane.XY\nwith BuildPart() as part:\n with BuildSketch(Plane.XY.shift_origin((1, 1))) as plane_rep:\n Rectangle(2, 2)\n with Locations((-.9, -.9)):\n Text(\"Plane.XY\", .2, align=(Align.MIN, Align.MIN), mode=Mode.SUBTRACT)\n plane_rep = plane_rep.sketch\n plane_rep.color = Color(0, .55, .55, .1)\n\n with Locations((-1, -1, 0)):\n b = Box(1, 1, 1)\n f = b.faces()\n res = f.filter_by(axis)\n axis_rep = [Axis(f.center(), f.normal_at()) for f in res]\n show_object([b, res, axis_rep])\n\n with Locations((1, 1, 0)):\n b = Box(1, 1, 1)\n f = b.faces()\n res = f.filter_by(plane)\n show_object([b, res, plane_rep])\n\n pass # [removed by collect.py] save_screenshot(os.path.join(filedir, \"filter_axisplane.png\"))\n pass # [removed by collect.py] reset_show()\n\n with Locations((-1, -1, 0)):\n b = Box(1, 1, 1)\n f = b.faces()\n res = f.filter_by(lambda f: abs(f.normal_at().dot(axis.direction)) < 1e-6)\n show_object([b, res, axis_rep])\n\n with Locations((1, 1, 0)):\n b = Box(1, 1, 1)\n f = b.faces()\n res = f.filter_by(lambda f: abs(f.normal_at().dot(plane.z_dir)) < 1e-6)\n show_object([b, res, plane_rep])\n\n pass # [removed by collect.py] save_screenshot(os.path.join(filedir, \"filter_dot_axisplane.png\"))", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/filter_geomtype", + "source": "docs/topology_selection/examples/filter_geomtype.py", + "kind": "docs-script", + "code": "import os\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nwith BuildPart() as part:\n Box(5, 5, 1)\n Cylinder(2, 5)\n edges = part.edges().filter_by(lambda a: a.length == 1)\n fillet(edges, 1)\n\npart.edges().filter_by(GeomType.LINE)\n\npart.faces().filter_by(GeomType.CYLINDER)\n\nshow(part, part.edges().filter_by(GeomType.LINE))\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"filter_geomtype_line.png\"))\n\nshow(part, part.faces().filter_by(GeomType.CYLINDER))\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"filter_geomtype_cylinder.png\"))", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/filter_inner_wire_count", + "source": "docs/topology_selection/examples/filter_inner_wire_count.py", + "kind": "docs-script", + "code": "from copy import copy\nimport os\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nbracket = import_step(os.path.join(working_path, \"nema-17-bracket.step\"))\nfaces = bracket.faces()\n\nmotor_mounts = faces.filter_by(GeomType.CYLINDER).filter_by(lambda f: f.radius == 3.3/2)\nfor i, f in enumerate(motor_mounts):\n location = f.axis_of_rotation.location\n RigidJoint(f\"motor_m3_{i}\", bracket, joint_location=location)\n\nmotor_face = faces.filter_by(lambda f: len(f.inner_wires()) == 5).sort_by(Axis.X)[-1]\nmotor_bore = motor_face.inner_wires().edges().filter_by(lambda e: e.radius == 16).edge()\nlocation = Location(motor_bore.arc_center, motor_bore.normal() * 90, Intrinsic.YXZ)\nRigidJoint(f\"motor\", bracket, joint_location=location)\n\nbefore_linear = copy(bracket)\n\nmount_face = faces.filter_by(lambda f: len(f.inner_wires()) == 6).sort_by(Axis.Z)[-1]\nmount_slots = mount_face.inner_wires().edges().filter_by(GeomType.CIRCLE)\njoint_edges = [\n Line(mount_slots[i].arc_center, mount_slots[i + 1].arc_center)\n for i in range(0, len(mount_slots), 2)\n]\nfor i, e in enumerate(joint_edges):\n LinearJoint(f\"mount_m4_{i}\", bracket, axis=Axis(e), linear_range=(0, e.length / 2))\n\nshow(before_linear, render_joints=True)\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"filter_inner_wire_count.png\"))\n\nshow(bracket, render_joints=True)\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"filter_inner_wire_count_linear.png\"))", + "data_dir": "docs/topology_selection/examples", + "assets": [ + "nema-17-bracket.step" + ] + }, + { + "id": "docs-selectors/filter_nested", + "source": "docs/topology_selection/examples/filter_nested.py", + "kind": "docs-script", + "code": "from copy import copy\nimport os\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nwith BuildPart() as part:\n Cylinder(15, 2, align=(Align.CENTER, Align.CENTER, Align.MIN))\n with BuildSketch():\n RectangleRounded(10, 10, 2.5)\n extrude(amount=15)\n\n with BuildSketch():\n Circle(2.5)\n Rectangle(4, 5, mode=Mode.INTERSECT)\n extrude(amount=15, mode=Mode.SUBTRACT)\n\n with GridLocations(20, 0, 2, 1):\n Hole(3.5 / 2)\n\n before = copy(part)\n\n faces = part.faces().filter_by(\n lambda f: len(f.inner_wires().edges().filter_by(GeomType.LINE)) == 2\n )\n wires = faces.wires().filter_by(\n lambda w: any(e.geom_type == GeomType.LINE for e in w.edges())\n )\n chamfer(wires.edges(), 0.5)\n\nlocation = Location((-25, -25))\nb = before.part.moved(location)\nf = [f.moved(location) for f in faces]\n\nshow(b, f, part)\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"filter_nested.png\"))\n", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/filter_shape_properties", + "source": "docs/topology_selection/examples/filter_shape_properties.py", + "kind": "docs-script", + "code": "import os\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nwith BuildPart() as open_box_builder:\n Box(20, 20, 5)\n offset(amount=-2, openings=open_box_builder.faces().sort_by(Axis.Z)[-1])\n inside_edges = open_box_builder.edges().filter_by(Edge.is_interior)\n fillet(inside_edges, 1.5)\n outside_edges = open_box_builder.edges().filter_by(Edge.is_interior, reverse=True)\n fillet(outside_edges, 0.5)\n\nopen_box = open_box_builder.part\nopen_box.color = Color(0xEDAE49)\noutside_fillets = Compound(open_box.faces().filter_by(Face.is_circular_convex))\noutside_fillets.color = Color(0xD1495B)\ninside_fillets = Compound(open_box.faces().filter_by(Face.is_circular_concave))\ninside_fillets.color = Color(0x00798C)\n\nshow(open_box, inside_fillets, outside_fillets)\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"filter_shape_properties.png\"))", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/group_axis", + "source": "docs/topology_selection/examples/group_axis.py", + "kind": "docs-script", + "code": "import os\nfrom copy import copy\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nwith BuildPart() as fins:\n with GridLocations(4, 6, 4, 4):\n Box(2, 3, 10, align=(Align.CENTER, Align.CENTER, Align.MIN))\n\nwith BuildPart() as part:\n Box(34, 48, 5, align=(Align.CENTER, Align.CENTER, Align.MAX))\n with GridLocations(20, 27, 2, 2):\n add(fins)\n\n without = copy(part)\n\n target = part.edges().group_by(Axis.Z)[-1].group_by(Edge.length)[-1]\n fillet(target, .75)\n\nshow(without)\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"group_axis_without.png\"))\n\nshow(part)\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"group_axis_with.png\"))", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/group_hole_area", + "source": "docs/topology_selection/examples/group_hole_area.py", + "kind": "docs-script", + "code": "from copy import copy\nimport os\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nwith BuildPart() as part:\n Cylinder(10, 30, rotation=(90, 0, 0))\n Cylinder(8, 40, rotation=(90, 0, 0), align=(Align.CENTER, Align.CENTER, Align.MAX))\n Cylinder(8, 23, rotation=(90, 0, 0), align=(Align.CENTER, Align.CENTER, Align.MIN))\n Cylinder(5, 40, rotation=(90, 0, 0), align=(Align.CENTER, Align.CENTER, Align.MIN))\n with BuildSketch(Plane.XY.offset(8)) as s:\n SlotCenterPoint((0, 38), (0, 48), 5)\n extrude(amount=2.5, both=True, mode=Mode.SUBTRACT)\n\n before = copy(part)\n\n faces = part.faces().group_by(\n lambda f: Face(f.inner_wires()[0]).area if f.inner_wires() else 0\n )\n chamfer([f.outer_wire().edges() for f in faces[-1]], 0.5)\n\nshow(\n before,\n [f.translate(f.normal_at() * 0.01) for group in faces for f in group],\n part.part.translate((40, 40)),\n)\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"group_hole_area.png\"))\n", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/group_properties_with_keys", + "source": "docs/topology_selection/examples/group_properties_with_keys.py", + "kind": "docs-script", + "code": "import os\nfrom copy import copy\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nwith BuildPart() as part:\n with BuildSketch(Plane.XZ) as sketch:\n with BuildLine():\n CenterArc((-6, 12), 10, 0, 360)\n Line((-16, 0), (16, 0))\n make_hull()\n Rectangle(50, 5, align=(Align.CENTER, Align.MAX))\n\n extrude(amount=12)\n\n Box(38, 6, 22, align=(Align.CENTER, Align.MAX, Align.MIN), mode=Mode.SUBTRACT)\n\n circle = part.edges().filter_by(GeomType.CIRCLE).sort_by(Axis.Y)[0]\n with Locations(Plane(circle.arc_center, z_dir=circle.normal())):\n CounterBoreHole(13 / 2, 16 / 2, 4)\n\n mirror(about=Plane.XZ)\n\n before_fillet = copy(part)\n\n length_groups = part.edges().group_by(Edge.length)\n fillet(length_groups.group(6) + length_groups.group(5), 4)\n\n after_fillet = copy(part)\n\n with BuildSketch() as pins:\n with Locations((-21, 0)):\n Circle(3 / 2)\n with Locations((21, 0)):\n SlotCenterToCenter(1, 3)\n extrude(amount=-12, mode=Mode.SUBTRACT)\n\n with GridLocations(42, 16, 2, 2):\n CounterBoreHole(3.5 / 2, 3.5, 0)\n\n after_holes = copy(part)\n\n radius_groups = part.edges().filter_by(GeomType.CIRCLE).group_by(Edge.radius)\n bearing_edges = radius_groups.group(8).group_by(SortBy.DISTANCE)[-1]\n pin_edges = radius_groups.group(1.5).filter_by_position(Axis.Z, -5, -5)\n chamfer([pin_edges, bearing_edges], .5)\n\nlocation = Location((-20, -20))\nitems = [before_fillet.part] + length_groups.group(6) + length_groups.group(5)\nbefore = Compound(items).move(location)\nshow(before, after_fillet.part.move(Location((20, 20))))\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"group_length_key.png\"))\n\nlocation = Location((-20, -20), (180, 0, 0))\nafter = Compound([after_holes.part] + pin_edges + bearing_edges).move(location)\nshow(after, part.part.move(Location((20, 20), (180, 0, 0))))\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"group_radius_key.png\"))", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/selectors_operators", + "source": "docs/topology_selection/examples/selectors_operators.py", + "kind": "docs-script", + "code": "from copy import copy\nimport os\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nselectors = [solids, vertices, edges, faces]\nline = Line((-9, -9), (9, 9))\nfor i, selector in enumerate(selectors):\n u = i / (len(selectors) - 1)\n with BuildPart() as part:\n with Locations(line @ u):\n Box(5, 5, 1)\n Cylinder(2, 5)\n show_object([part, selector()])\n\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"selectors_select_all.png\"))\n# [removed by collect.py] reset_show()\n\nfor i, selector in enumerate(selectors[1:4]):\n u = i / (len(selectors) - 1)\n with BuildPart() as part:\n with Locations(line @ u):\n Box(5, 5, 1)\n Cylinder(2, 5)\n show_object([part, selector(Select.LAST)])\n\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"selectors_select_last.png\"))\n# [removed by collect.py] reset_show()\n\nwith BuildPart() as part:\n with Locations(line @ 1/3):\n Box(5, 5, 1)\n Cylinder(2, 5)\n edges = part.edges(Select.NEW)\n part_copy = copy(part)\n\n with Locations(line @ 2/3):\n b = Box(5, 5, 1)\n c = Cylinder(2, 5)\n c.color = Color(\"DarkTurquoise\")\n\n show(part_copy, edges, b, c, alphas=[.5, 1, .5, 1])\n\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"selectors_select_new.png\"))\n# [removed by collect.py] reset_show()\n\nwith BuildPart() as part:\n with Locations(line @ 1/3):\n Box(5, 5, 1, align=(Align.CENTER, Align.CENTER, Align.MAX))\n Cylinder(2, 2, align=(Align.CENTER, Align.CENTER, Align.MIN))\n edges = part.edges(Select.NEW)\n part_copy = copy(part)\n\n with Locations(line @ 2/3):\n b = Box(5, 5, 1, align=(Align.CENTER, Align.CENTER, Align.MAX), mode=Mode.PRIVATE)\n c = Cylinder(2, 2, align=(Align.CENTER, Align.CENTER, Align.MIN), mode=Mode.PRIVATE)\n c.color = Color(\"DarkTurquoise\")\n show(part_copy, edges, b, c, alphas=[.5, 1, .5, 1])\n\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"selectors_select_new_none.png\"))\n# [removed by collect.py] reset_show()\n\nwith BuildPart() as part:\n with Locations(line @ 1/3):\n Box(5, 5, 1)\n Cylinder(2, 5)\n edges = part.edges().filter_by(lambda a: a.length == 1)\n fillet(edges, 1)\n show_object([part, part.edges(Select.NEW)])\n\nwith BuildPart() as part:\n with Locations(line @ 2/3):\n Box(5, 5, 1)\n Cylinder(2, 5)\n edges = part.edges().filter_by(lambda a: a.length == 1)\n fillet(edges, 1)\n show_object([part, part.edges(Select.LAST)])\n\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"selectors_select_new_fillet.png\"))\n\nshow(part, part.vertices().sort_by(Axis.X)[-4:])\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"operators_sort_x.png\"))\n\nshow(part, part.faces().group_by(SortBy.AREA)[0].edges())\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"operators_group_area.png\"))\n\nfaces = part.faces().filter_by(lambda f: f.normal_at() == Vector(0, 0, 1))\nshow(part, [f.translate(f.normal_at() * 0.01) for f in faces])\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"operators_filter_z_normal.png\"))\n\nbox = Box(5, 5, 1)\ncircle = Cylinder(2, 5)\npart = box + circle\nedges = new_edges(box, circle, combined=part)\nshow(part, edges)\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"selectors_new_edges.png\"))", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/sort_along_wire", + "source": "docs/topology_selection/examples/sort_along_wire.py", + "kind": "docs-script", + "code": "import os\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nwith BuildSketch() as along_wire:\n Rectangle(48, 16, align=Align.MIN)\n Rectangle(16, 48, align=Align.MIN)\n Rectangle(32, 32, align=Align.MIN)\n\n for i, v in enumerate(along_wire.vertices()):\n fillet(v, i + 1)\n\nshow(along_wire)\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"sort_not_along_wire.png\"))\n\n\nwith BuildSketch() as along_wire:\n Rectangle(48, 16, align=Align.MIN)\n Rectangle(16, 48, align=Align.MIN)\n Rectangle(32, 32, align=Align.MIN)\n\n sorted_verts = along_wire.vertices().sort_by(along_wire.wire())\n for i, v in enumerate(sorted_verts):\n fillet(v, i + 1)\n\nshow(along_wire)\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"sort_along_wire.png\"))", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/sort_axis", + "source": "docs/topology_selection/examples/sort_axis.py", + "kind": "docs-script", + "code": "from copy import copy\nimport os\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nwith BuildPart() as part:\n with BuildSketch(Plane.YZ) as profile:\n with BuildLine():\n l1 = FilletPolyline((16, 0), (32, 0), (32, 25), radius=12)\n l2 = FilletPolyline((16, 4), (28, 4), (28, 15), radius=8)\n Line(l1 @ 0, l2 @ 0)\n Polyline(l1 @ 1, l1 @ 1 - Vector(2, 0), l2 @ 1 + Vector(2, 0), l2 @ 1)\n make_face()\n extrude(amount=34)\n\n before = copy(part).part\n\n face = part.faces().sort_by(Axis.X)[-1]\n edge = face.edges().sort_by(Axis.Y)[0]\n revolve(face, -Axis(edge), 90)\n\nf = face.translate(face.normal_at() * 0.01)\nshow(before, f, edge, part.part.translate((25, 33)))\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"sort_axis.png\"))\n", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/sort_distance_from", + "source": "docs/topology_selection/examples/sort_distance_from.py", + "kind": "docs-script", + "code": "import os\nfrom itertools import product\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nboxes = ShapeList(\n Box(1, 1, 1).scale(0.75 if (i, j) == (1, 2) else 0.25).translate((i, j, 0))\n for i, j in product(range(-3, 4), repeat=2)\n)\n\nboxes = boxes.sort_by_distance(Vertex())\nshow(*boxes, colors=ColorMap.listed(len(boxes)))\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"sort_distance_from_origin.png\"))\n\nboxes = boxes.sort_by_distance(boxes.sort_by(Solid.volume).last)\nshow(*boxes, colors=ColorMap.listed(len(boxes)))\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"sort_distance_from_largest.png\"))", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "docs-selectors/sort_sortby", + "source": "docs/topology_selection/examples/sort_sortby.py", + "kind": "docs-script", + "code": "import os\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nwith BuildPart() as part:\n Box(5, 5, 1)\n Cylinder(2, 5)\n edges = part.edges().filter_by(lambda a: a.length == 1)\n fillet(edges, 1)\n\nbox = Box(5, 5, 5).move(Location((-6, -6)))\nsphere = Sphere(5 / 2).move(Location((6, 6)))\nsolids = ShapeList([part.part, box, sphere])\n\npart.wires().sort_by(SortBy.LENGTH)[:4]\n\npart.wires().sort_by(Wire.length)[:4]\npart.wires().group_by(SortBy.LENGTH)[0]\n\npart.vertices().sort_by(SortBy.DISTANCE)[-2:]\n\npart.vertices().sort_by_distance(Vertex())[-2:]\npart.vertices().group_by(Vertex().distance)[-1]\n\n\nshow(part, part.wires().sort_by(SortBy.LENGTH)[:4])\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"sort_sortby_length.png\"))\n\n# show(part, part.faces().sort_by(SortBy.AREA)[-2:])\n# save_screenshot(os.path.join(filedir, \"sort_sortby_area.png\"))\n\n# solid = solids.sort_by(SortBy.VOLUME)[-1]\n# solid.color = \"violet\"\n# show([part, box, sphere], solid)\n# save_screenshot(os.path.join(filedir, \"sort_sortby_volume.png\"))\n\n# show(part, part.edges().filter_by(GeomType.CIRCLE).sort_by(SortBy.RADIUS)[-4:])\n# save_screenshot(os.path.join(filedir, \"sort_sortby_radius.png\"))\n\nshow(part, part.vertices().sort_by(SortBy.DISTANCE)[-2:])\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"sort_sortby_distance.png\"))", + "data_dir": "docs/topology_selection/examples", + "assets": [] + }, + { + "id": "ttt/ttt-23-02-02-sm_hanger", + "source": "docs/assets/ttt/ttt-23-02-02-sm_hanger.py", + "kind": "ttt", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nsheet_thickness = 4 * MM\n\n# Create the main body from a side profile\nwith BuildPart() as side:\n with BuildLine(Plane.XZ) as side_line:\n l1 = Line((0, 65), (170 / 2, 65))\n l2 = PolarLine(\n l1 @ 1,\n length=65,\n direction=(0.5, -0.866025403784),\n length_mode=LengthMode.VERTICAL,\n )\n l3 = Line(l2 @ 1, (170 / 2, 0))\n fillet(side_line.vertices(), 7)\n make_brake_formed(\n thickness=sheet_thickness,\n station_widths=[40, 40, 40, 112.52 / 2, 112.52 / 2, 112.52 / 2],\n side=Side.RIGHT,\n )\n # Ensure the part is always on the +ve side of Plane.YZ\n if side.vertices().sort_by(Axis.X)[0].X < -sheet_thickness:\n mirror(about=Plane.YZ, mode=Mode.REPLACE)\n fe = side.edges().filter_by(Axis.Z).group_by(Axis.Z)[0].sort_by(Axis.Y)[-1]\n fillet(fe, radius=7)\n\n# Create the \"wings\" at the top\nwith BuildPart() as wing:\n with BuildLine(Plane.YZ) as wing_line:\n l1 = Line((0, 65), (80 / 2 + 1.526 * sheet_thickness, 65))\n PolarLine(l1 @ 1, 20.371288916, direction=(0.258819045103, -0.965925826289))\n fillet(wing_line.vertices(), 7)\n make_brake_formed(\n thickness=sheet_thickness,\n station_widths=110 / 2,\n side=Side.RIGHT,\n )\n # Ensure the part is always on the +ve side of Plane.YZ\n if wing.vertices().sort_by(Axis.X)[0].X < -sheet_thickness:\n mirror(about=Plane.YZ, mode=Mode.REPLACE)\n bottom_edge = wing.edges().group_by(Axis.X)[-1].sort_by(Axis.Z)[0]\n fillet(bottom_edge, radius=7)\n\n# Create the tab at the top in Algebra mode\ntab_line = Plane.XZ * Polyline(\n (20, 65 - sheet_thickness), (56 / 2, 65 - sheet_thickness), (56 / 2, 88)\n)\ntab_line = fillet(tab_line.vertices(), 7)\ntab = make_brake_formed(sheet_thickness, 8, tab_line, Side.RIGHT)\n# Ensure the tab is always on the +ve side of Plane.XZ\nif tab.vertices().sort_by(Axis.Y)[0].Y < -sheet_thickness:\n tab = mirror(tab, about=Plane.XZ)\ntab = fillet(tab.edges().filter_by(Axis.X).group_by(Axis.Z)[-1].sort_by(Axis.Y)[-1], 5)\ntab -= Pos((0, 0, 80)) * Rot(0, 90, 0) * Hole(5, 100)\n\n# Combine the parts together\nwith BuildPart() as sm_hanger:\n add([side.part, wing.part])\n mirror(about=Plane.XZ)\n with BuildSketch(Plane.XY.offset(65)) as h1:\n with Locations((20, 0)):\n Rectangle(30, 30, align=(Align.MIN, Align.CENTER))\n fillet(h1.vertices().group_by(Axis.X)[-1], 7)\n SlotCenterPoint((154, 0), (154 / 2, 0), 20)\n extrude(amount=-40, mode=Mode.SUBTRACT)\n with BuildSketch() as h2:\n SlotCenterPoint((206, 0), (206 / 2, 0), 20)\n extrude(amount=40, mode=Mode.SUBTRACT)\n add(tab)\n mirror(about=Plane.YZ)\n mirror(about=Plane.XZ)\n\ngot_mass = sm_hanger.part.volume * 7800 * 1e-6\nwant_mass = 1028\ntolerance = 10\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.1f} g\")\nassert delta < tolerance, f\"{got_mass=}, {want_mass=}, {delta=}, {tolerance=}\"\n\n# assert abs(got_mass - 1028) < 10, f\"{got_mass=}, want=1028, tolerance=10\"\n\nshow(sm_hanger)\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-23-t-24-curved_support", + "source": "docs/assets/ttt/ttt-23-t-24-curved_support.py", + "kind": "ttt", + "code": "from math import sin, cos, tan, radians\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\nimport sympy\n\n# This problem uses the sympy symbolic math solver\n\n# Define the symbols for the unknowns\n# - the center of the radius 30 arc (x30, y30)\n# - the center of the radius 66 arc (x66, y66)\n# - end of the 8\u00b0 line (l8x, l8y)\n# - the point with the radius 30 and 66 arc meet i30_66\n# - the start of the horizontal line lh\ny30, x66, xl8, yl8 = sympy.symbols(\"y30 x66 xl8 yl8\")\nx30 = 77 - 55 / 2\ny66 = 66 + 32\n\n# There are 4 unknowns so we need 4 equations\nequations = [\n (x66 - x30) ** 2 + (y66 - y30) ** 2 - (66 + 30) ** 2, # distance between centers\n xl8 - (x30 + 30 * sin(radians(8))), # 8 degree slope\n yl8 - (y30 + 30 * cos(radians(8))), # 8 degree slope\n (yl8 - 50) / (55 / 2 - xl8) - tan(radians(8)), # 8 degree slope\n]\n# There are two solutions but we want the 2nd one\nsolution = {k: float(v) for k,v in sympy.solve(equations, dict=True)[1].items()}\n\n# Create the critical points\nc30 = Vector(x30, solution[y30])\nc66 = Vector(solution[x66], y66)\nl8 = Vector(solution[xl8], solution[yl8])\ni30_66 = Line(c30, c66) @ (30 / (30 + 66))\nlh = Vector(c66.X, 32)\n\nwith BuildLine() as profile:\n l1 = Line((55 / 2, 50), l8)\n l2 = RadiusArc(l1 @ 1, i30_66, 30)\n l3 = RadiusArc(l2 @ 1, lh, -66)\n l4 = Polyline(l3 @ 1, (125, 32), (125, 0), (0, 0), (0, (l1 @ 0).Y), l1 @ 0)\n\nwith BuildPart() as curved_support:\n with BuildSketch() as base_plan:\n c_8_degrees = Circle(55 / 2)\n with Locations((0, 125)):\n Circle(30 / 2)\n base_hull = make_hull(mode=Mode.PRIVATE)\n extrude(amount=32)\n extrude(c_8_degrees, amount=60)\n extrude(base_hull, amount=11)\n with BuildSketch(Plane.YZ) as bridge:\n make_face(profile.edges())\n extrude(amount=11 / 2, both=True)\n Hole(35 / 2)\n with Locations((0, 125)):\n Hole(20 / 2)\n\ngot_mass = curved_support.part.volume * 7800e-6\nwant_mass = 1294\ndelta = abs(got_mass - want_mass)\ntolerance = 3\nprint(f\"Mass: {got_mass:0.1f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n\nshow(curved_support)\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-24-SPO-06-Buffer_Stand", + "source": "docs/assets/ttt/ttt-24-SPO-06-Buffer_Stand.py", + "kind": "ttt", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nwith BuildPart() as p:\n with BuildSketch() as xy:\n with BuildLine():\n l1 = ThreePointArc((5 / 2, -1.25), (5.5 / 2, 0), (5 / 2, 1.25))\n Polyline(l1 @ 0, (0, -1.25), (0, 1.25), l1 @ 1)\n make_face()\n extrude(amount=4)\n\n with BuildSketch(Plane.YZ) as yz:\n Trapezoid(2.5, 4, 90 - 6, align=(Align.CENTER, Align.MIN))\n full_round(yz.edges().sort_by(SortBy.LENGTH)[0])\n circle_edge = yz.edges().filter_by(GeomType.CIRCLE)[0]\n arc_center = circle_edge.arc_center\n arc_radius = circle_edge.radius\n extrude(amount=10, mode=Mode.INTERSECT)\n\n # To avoid OCCT problems, don't attempt to extend the top arc, remove instead\n with BuildPart(mode=Mode.SUBTRACT) as internals:\n y = p.edges().filter_by(Axis.X).sort_by(Axis.Z)[-1].center().Z\n\n with BuildSketch(Plane.YZ.offset(4.25 / 2)) as yz:\n Trapezoid(2.5, y, 90 - 6, align=(Align.CENTER, Align.MIN))\n with Locations(arc_center):\n Circle(arc_radius, mode=Mode.SUBTRACT)\n extrude(amount=-(4.25 - 3.5) / 2)\n\n with BuildSketch(Plane.YZ.offset(3.5 / 2)) as yz:\n Trapezoid(2.5, 4, 90 - 6, align=(Align.CENTER, Align.MIN))\n extrude(amount=-3.5 / 2)\n\n with BuildSketch(Plane.XZ.offset(-2)) as xz:\n with Locations((0, 4)):\n RectangleRounded(4.25, 7.5, 0.5)\n extrude(amount=4, mode=Mode.INTERSECT)\n\n with Locations(p.faces(Select.LAST).filter_by(GeomType.PLANE).sort_by(Axis.Z)[-1]):\n CounterBoreHole(0.625 / 2, 1.25 / 2, 0.5)\n\n with BuildSketch(Plane.YZ) as rib:\n with Locations((0, 0.25)):\n Trapezoid(0.5, 1, 90 - 8, align=(Align.CENTER, Align.MIN))\n full_round(rib.edges().sort_by(SortBy.LENGTH)[0])\n extrude(amount=4.25 / 2)\n\n mirror(about=Plane.YZ)\n\npart = scale(p.part, IN)\n\n\ngot_mass = part.volume * 7800e-6 / LB\nwant_mass = 3.923\ntolerance = 0.02\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.1f} lbs\")\nassert delta < tolerance, f\"{got_mass=}, {want_mass=}, {delta=}, {tolerance=}\"\n\nshow(p)\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-ppp0101", + "source": "docs/assets/ttt/ttt-ppp0101.py", + "kind": "ttt", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\nwith BuildPart() as p:\n with BuildSketch() as s:\n Rectangle(115, 50)\n with Locations((5 / 2, 0)):\n SlotOverall(90, 12, mode=Mode.SUBTRACT)\n extrude(amount=15)\n\n with BuildSketch(Plane.XZ.offset(50 / 2)) as s3:\n with Locations((-115 / 2 + 26, 15)):\n SlotOverall(42 + 2 * 26 + 12, 2 * 26, rotation=90)\n zz = extrude(amount=-12)\n split(bisect_by=Plane.XY)\n edgs = p.part.edges().filter_by(Axis.Y).group_by(Axis.X)[-2]\n fillet(edgs, 9)\n\n with Locations(zz.faces().sort_by(Axis.Y)[0]):\n with Locations((42 / 2 + 6, 0)):\n CounterBoreHole(24 / 2, 34 / 2, 4)\n mirror(about=Plane.XZ)\n\n with BuildSketch() as s4:\n RectangleRounded(115, 50, 6)\n extrude(amount=80, mode=Mode.INTERSECT)\n # fillet does not work right, mode intersect is safer\n\n with BuildSketch(Plane.YZ) as s4:\n with BuildLine() as bl:\n l1 = Line((0, 0), (18 / 2, 0))\n l2 = PolarLine(l1 @ 1, 8, 60, length_mode=LengthMode.VERTICAL)\n l3 = Line(l2 @ 1, (0, 8))\n mirror(about=Plane.YZ)\n make_face()\n extrude(amount=115/2, both=True, mode=Mode.SUBTRACT)\n\nshow_object(p)\n\n\ngot_mass = p.part.volume*densa\nwant_mass = 797.15\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-ppp0102", + "source": "docs/assets/ttt/ttt-ppp0102.py", + "kind": "ttt", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\n\n# TTT Party Pack 01: PPP0102, mass(abs) = 43.09g\nwith BuildPart() as p:\n with BuildSketch(Plane.XZ) as sk1:\n Rectangle(49, 48 - 8, align=(Align.CENTER, Align.MIN))\n Rectangle(9, 48, align=(Align.CENTER, Align.MIN))\n with Locations((9 / 2, 40)):\n Ellipse(20, 8)\n split(bisect_by=Plane.YZ)\n revolve(axis=Axis.Z)\n\n with BuildSketch(Plane.YZ.offset(-15)) as xc1:\n with Locations((0, 40 / 2 - 17)):\n Ellipse(10 / 2, 4 / 2)\n with BuildLine(Plane.XZ) as l1:\n CenterArc((-15, 40 / 2), 17, 90, 180)\n sweep(path=l1)\n\n fillet(p.edges().filter_by(GeomType.CIRCLE, reverse=True).group_by(Axis.X)[0], 1)\n\n with BuildLine(mode=Mode.PRIVATE) as lc1:\n PolarLine(\n (42 / 2, 0), 37, 94, length_mode=LengthMode.VERTICAL\n ) # construction line\n\n pts = [\n (0, 0),\n (42 / 2, 0),\n ((lc1.line @ 1).X, (lc1.line @ 1).Y),\n (0, (lc1.line @ 1).Y),\n ]\n with BuildSketch(Plane.XZ) as sk2:\n Polygon(*pts, align=None)\n fillet(sk2.vertices().group_by(Axis.X)[1], 3)\n revolve(axis=Axis.Z, mode=Mode.SUBTRACT)\n\nshow(p)\n\n\ngot_mass = p.part.volume*densc\nwant_mass = 43.09\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-ppp0103", + "source": "docs/assets/ttt/ttt-ppp0103.py", + "kind": "ttt", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\n\nwith BuildPart() as ppp0103:\n with BuildSketch() as sk1:\n RectangleRounded(34 * 2, 95, 18)\n with Locations((0, -2)):\n RectangleRounded((34 - 16) * 2, 95 - 18 - 14, 7, mode=Mode.SUBTRACT)\n with Locations((-34 / 2, 0)):\n Rectangle(34, 95, 0, mode=Mode.SUBTRACT)\n extrude(amount=16)\n with BuildSketch(Plane.XZ.offset(-95 / 2)) as cyl1:\n with Locations((0, 16 / 2)):\n Circle(16 / 2)\n extrude(amount=18)\n with BuildSketch(Plane.XZ.offset(95 / 2 - 14)) as cyl2:\n with Locations((0, 16 / 2)):\n Circle(16 / 2)\n extrude(amount=23)\n with Locations(Plane.XZ.offset(95 / 2 + 9)):\n with Locations((0, 16 / 2)):\n CounterSinkHole(5.5 / 2, 11.2 / 2, None, 90)\n\nshow(ppp0103)\n\ngot_mass = ppp0103.part.volume*densb\nwant_mass = 96.13\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-ppp0104", + "source": "docs/assets/ttt/ttt-ppp0104.py", + "kind": "ttt", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\nd1, d2, d3 = 38, 26, 16\nh1, h2, h3, h4 = 20, 8, 7, 23\nw1, w2, w3 = 80, 10, 5\nf1, f2, f3 = 4, 10, 5\nsloth1, sloth2 = 18, 12\nslotw1, slotw2 = 17, 14\n\nwith BuildPart() as p:\n with BuildSketch() as s:\n Circle(d1 / 2)\n extrude(amount=h1)\n with BuildSketch(Plane.XY.offset(h1)) as s2:\n Circle(d2 / 2)\n extrude(amount=h2)\n with BuildSketch(Plane.YZ) as s3:\n Rectangle(d1 + 15, h3, align=(Align.CENTER, Align.MIN))\n extrude(amount=w1 - d1 / 2)\n # fillet workaround \\/\n ped = p.part.edges().group_by(Axis.Z)[2].filter_by(GeomType.CIRCLE)\n fillet(ped, f1)\n with BuildSketch(Plane.YZ) as s3a:\n Rectangle(d1 + 15, 15, align=(Align.CENTER, Align.MIN))\n Rectangle(d1, 15, mode=Mode.SUBTRACT, align=(Align.CENTER, Align.MIN))\n extrude(amount=w1 - d1 / 2, mode=Mode.SUBTRACT)\n # end fillet workaround /\\\n with BuildSketch() as s4:\n Circle(d3 / 2)\n extrude(amount=h1 + h2, mode=Mode.SUBTRACT)\n with BuildSketch() as s5:\n with Locations((w1 - d1 / 2 - w2 / 2, 0)):\n Rectangle(w2, d1)\n extrude(amount=-h4)\n fillet(p.part.edges().group_by(Axis.X)[-1].sort_by(Axis.Z)[-1], f2)\n fillet(p.part.edges().group_by(Axis.X)[-4].sort_by(Axis.Z)[-2], f3)\n pln = Plane.YZ.offset(w1 - d1 / 2)\n with BuildSketch(pln) as s6:\n with Locations((0, -h4)):\n SlotOverall(slotw1 * 2, sloth1, 90)\n extrude(amount=-w3, mode=Mode.SUBTRACT)\n with BuildSketch(pln) as s6b:\n with Locations((0, -h4)):\n SlotOverall(slotw2 * 2, sloth2, 90)\n extrude(amount=-w2, mode=Mode.SUBTRACT)\n\nshow(p)\n\n\ngot_mass = p.part.volume*densa\nwant_mass = 310\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-ppp0105", + "source": "docs/assets/ttt/ttt-ppp0105.py", + "kind": "ttt", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\nwith BuildPart() as p:\n with BuildSketch() as s:\n SlotOverall(45, 38)\n offset(amount=3)\n with BuildSketch(Plane.XY.offset(133 - 30)) as s2:\n SlotOverall(60, 4)\n offset(amount=3)\n loft()\n\n with BuildSketch() as s3:\n SlotOverall(45, 38)\n with BuildSketch(Plane.XY.offset(133 - 30)) as s4:\n SlotOverall(60, 4)\n loft(mode=Mode.SUBTRACT)\n\n extrude(p.part.faces().sort_by(Axis.Z)[0], amount=30)\n\nshow(p)\n\n\ngot_mass = p.part.volume*densc\nwant_mass = 57.08\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-ppp0106", + "source": "docs/assets/ttt/ttt-ppp0106.py", + "kind": "ttt", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\nr1, r2, r3, r4, r5 = 30 / 2, 13 / 2, 12 / 2, 10, 6 # radii used\nx1 = 44 # lengths used\ny1, y2, y3, y4, y_tot = 36, 36 - 22 / 2, 22 / 2, 42, 69 # widths used\n\nwith BuildSketch(Location((0, -r1, y3))) as sk_body:\n with BuildLine() as l:\n c1 = Line((r1, 0), (r1, y_tot), mode=Mode.PRIVATE) # construction line\n m1 = Line((0, y_tot), (x1 / 2, y_tot))\n m2 = JernArc(m1 @ 1, m1 % 1, r4, -90 - 45)\n m3 = IntersectingLine(m2 @ 1, m2 % 1, c1)\n m4 = Line(m3 @ 1, (r1, r1))\n m5 = JernArc(m4 @ 1, m4 % 1, r1, -90)\n mirror(about=Plane.YZ)\n make_face()\n fillet(sk_body.vertices().group_by(Axis.Y)[1], 12)\n with Locations((x1 / 2, y_tot - 10), (-x1 / 2, y_tot - 10)):\n Circle(r2, mode=Mode.SUBTRACT)\n # Keyway\n with Locations((0, r1)):\n Circle(r3, mode=Mode.SUBTRACT)\n Rectangle(4, 3 + 6, align=(Align.CENTER, Align.MIN), mode=Mode.SUBTRACT)\n\nwith BuildPart() as p:\n Box(200, 200, 22) # Oversized plate\n # Cylinder underneath\n Cylinder(r1, y2, align=(Align.CENTER, Align.CENTER, Align.MAX))\n fillet(p.edges(Select.NEW), r5) # Weld together\n extrude(sk_body.sketch, amount=-y1, mode=Mode.INTERSECT) # Cut to shape\n # Remove slot\n with Locations((0, y_tot - r1 - y4, 0)):\n Box(\n y_tot,\n y_tot,\n 10,\n align=(Align.CENTER, Align.MIN, Align.CENTER),\n mode=Mode.SUBTRACT,\n )\n\nshow(p)\n\n\ngot_mass = p.part.volume*densa\nwant_mass = 328.02\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-ppp0107", + "source": "docs/assets/ttt/ttt-ppp0107.py", + "kind": "ttt", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\nwith BuildPart() as p:\n with BuildSketch() as s:\n Circle(130 / 2)\n extrude(amount=8)\n with BuildSketch(Plane.XY.offset(8)) as s2:\n Circle(84 / 2)\n extrude(amount=25 - 8)\n with BuildSketch(Plane.XY.offset(25)) as s3:\n Circle(35 / 2)\n extrude(amount=52 - 25)\n with BuildSketch() as s4:\n Circle(73 / 2)\n extrude(amount=18, mode=Mode.SUBTRACT)\n pln2 = p.part.faces().sort_by(Axis.Z)[5]\n with BuildSketch(Plane.XY.offset(52)) as s5:\n Circle(20 / 2)\n extrude(amount=-52, mode=Mode.SUBTRACT)\n fillet(\n p.part.edges()\n .filter_by(GeomType.CIRCLE)\n .sort_by(Axis.Z)[2:-2]\n .sort_by(SortBy.RADIUS)[1:],\n 3,\n )\n pln = Plane(pln2)\n pln.origin = pln.origin + Vector(20 / 2, 0, 0)\n pln = pln.rotated((0, 45, 0))\n pln = pln.offset(-25 + 3 + 0.10)\n with BuildSketch(pln) as s6:\n Rectangle((73 - 35) / 2 * 1.414 + 5, 3)\n zz = extrude(amount=15, taper=-20 / 2, mode=Mode.PRIVATE)\n zz2 = split(zz, bisect_by=Plane.XY.offset(25), mode=Mode.PRIVATE)\n zz3 = split(zz2, bisect_by=Plane.YZ.offset(35 / 2 - 1), mode=Mode.PRIVATE)\n with PolarLocations(0, 3):\n add(zz3)\n with Locations(Plane.XY.offset(8)):\n with PolarLocations(107.95 / 2, 6):\n CounterBoreHole(6 / 2, 13 / 2, 4)\n\nshow(p)\n\n\ngot_mass = p.part.volume*densb\nwant_mass = 372.99\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-ppp0108", + "source": "docs/assets/ttt/ttt-ppp0108.py", + "kind": "ttt", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\nwith BuildPart() as p:\n with BuildSketch() as s1:\n Rectangle(188 / 2 - 33, 162, align=(Align.MIN, Align.CENTER))\n with Locations((188 / 2 - 33, 0)):\n SlotOverall(190, 33 * 2, rotation=90)\n mirror(about=Plane.YZ)\n with GridLocations(188 - 2 * 33, 190 - 2 * 33, 2, 2):\n Circle(29 / 2, mode=Mode.SUBTRACT)\n Circle(84 / 2, mode=Mode.SUBTRACT)\n extrude(amount=16)\n\n with BuildPart() as p2:\n with BuildSketch(Plane.XZ) as s2:\n with BuildLine() as l1:\n l1 = Polyline(\n (222 / 2 + 14 - 40 - 40, 0),\n (222 / 2 + 14 - 40, -35 + 16),\n (222 / 2 + 14, -35 + 16),\n (222 / 2 + 14, -35 + 16 + 30),\n (222 / 2 + 14 - 40 - 40, -35 + 16 + 30),\n close=True,\n )\n make_face()\n with Locations((222 / 2, -35 + 16 + 14)):\n Circle(11 / 2, mode=Mode.SUBTRACT)\n extrude(amount=20 / 2, both=True)\n with BuildSketch() as s3:\n with Locations(l1 @ 0):\n Rectangle(40 + 40, 8, align=(Align.MIN, Align.CENTER))\n with Locations((40, 0)):\n Rectangle(40, 20, align=(Align.MIN, Align.CENTER))\n extrude(amount=30, both=True, mode=Mode.INTERSECT)\n mirror(about=Plane.YZ)\n\nshow(p)\n\n\ngot_mass = p.part.volume*densa\nwant_mass = 3387.06\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-ppp0109", + "source": "docs/assets/ttt/ttt-ppp0109.py", + "kind": "ttt", + "code": "from math import sqrt\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\nwith BuildPart() as ppp109:\n with BuildSketch() as one:\n Rectangle(69, 75, align=(Align.MAX, Align.CENTER))\n fillet(one.vertices().group_by(Axis.X)[0], 17)\n extrude(amount=13)\n centers = [\n arc.arc_center\n for arc in ppp109.edges().filter_by(GeomType.CIRCLE).group_by(Axis.Z)[-1]\n ]\n with Locations(*centers):\n CounterBoreHole(radius=8 / 2, counter_bore_radius=15 / 2, counter_bore_depth=4)\n\n with BuildSketch(Plane.YZ) as two:\n with Locations((0, 45)):\n Circle(15)\n with BuildLine() as bl:\n c = Line((75 / 2, 0), (75 / 2, 60), mode=Mode.PRIVATE)\n u = two.edge().find_tangent(75 / 2 + 90)[0] # where is the slope 75/2?\n l1 = IntersectingLine(\n two.edge().position_at(u), -two.edge().tangent_at(u), other=c\n )\n Line(l1 @ 0, (0, 45))\n Polyline((0, 0), c @ 0, l1 @ 1)\n mirror(about=Plane.YZ)\n make_face()\n with Locations((0, 45)):\n Circle(12 / 2, mode=Mode.SUBTRACT)\n extrude(amount=-13)\n\n with BuildSketch(Plane((0, 0, 0), x_dir=(1, 0, 0), z_dir=(1, 0, 1))) as three:\n Rectangle(45 * 2 / sqrt(2) - 37.5, 75, align=(Align.MIN, Align.CENTER))\n with Locations(three.edges().sort_by(Axis.X)[-1].center()):\n Circle(37.5)\n Circle(33 / 2, mode=Mode.SUBTRACT)\n split(bisect_by=Plane.YZ)\n extrude(amount=6)\n f = ppp109.faces().filter_by(Axis((0, 0, 0), (-1, 0, 1)))[0]\n extrude(f, until=Until.NEXT)\n fillet(ppp109.edges().filter_by(Axis.Y).sort_by(Axis.Z)[2], 16)\n # extrude(f, amount=10)\n # fillet(ppp109.edges(Select.NEW), 16)\n\n\nshow(ppp109)\n\ngot_mass = ppp109.part.volume * densb\nwant_mass = 307.23\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f\"{got_mass=}, {want_mass=}, {delta=}, {tolerance=}\"\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "ttt/ttt-ppp0110", + "source": "docs/assets/ttt/ttt-ppp0110.py", + "kind": "ttt", + "code": "from math import sqrt, asin, pi\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\n# The smaller cross-section is defined as having R40, height 46,\n# and base width 84, so clearly it's not entirely a half-circle or\n# similar; the base's extreme points need to connect via tangents\n# to the R40 arc centered 6mm above the baseline.\n#\n# Compute the angle of the tangent line (working with the\n# left/negativeX side, given symmetry) by observing the tangent\n# point (T), the circle's center (O), and the baseline's edge (P)\n# form a right triangle, so:\n\nOT=40\nOP=sqrt((-84/2)**2+(-6)**2)\nTP=sqrt(OP**2-40**2)\nOPT_degrees = asin(OT/OP) * 180/pi\n# Correct for the fact that OP isn't horizontal.\nOP_to_X_axis_degrees = asin(6/OP) * 180/pi\nleft_tangent_degrees = OPT_degrees + OP_to_X_axis_degrees\nleft_tangent_length = TP\nwith BuildPart() as outer:\n with BuildSketch(Plane.XZ) as sk:\n with BuildLine():\n l1 = PolarLine(start=(-84/2, 0), length=left_tangent_length, angle=left_tangent_degrees)\n l2 = TangentArc(l1@1, (0, 46), tangent=l1%1)\n l3 = offset(amount=-8, side=Side.RIGHT, closed=False, mode=Mode.ADD)\n l4 = Line(l1@0, l3@1)\n l5 = Line(l3@0, l2@1)\n make_face()\n\n with BuildLine():\n l6 = Line(l2 @ 1, (0, 46 - 16))\n l7 = IntersectingLine(start=l6 @ 1, direction=(-1, 0), other=l3)\n l8 = TangentArc(l7 @ 1, l2 @ 1, tangent=(-1, 0), tangent_from_first=False)\n\n make_face()\n \n revolve(axis=Axis.Z)\nsk = sk.sketch & Plane.XZ*Rectangle(1000, 1000, align=[Align.CENTER, Align.MIN])\npositive_Z = Box(100, 100, 100, align=[Align.CENTER, Align.MIN, Align.MIN])\np = outer.part & positive_Z\ncross_section = sk + mirror(sk, about=Plane.YZ)\np += extrude(cross_section, amount=50)\np += mirror(p, about=Plane.XZ.offset(50))\np += fillet(p.edges().filter_by(GeomType.LINE).filter_by(Axis.Y).group_by(Axis.Z)[-1], radius=8)\nppp0110 = p\n\ngot_mass = ppp0110.volume*densc\nwant_mass = 211.30\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.1f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n\nshow(ppp0110)\n", + "data_dir": "docs/assets/ttt", + "assets": [] + }, + { + "id": "docs-rst/OpenSCAD/b01", + "source": "docs/OpenSCAD.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\n# Builder mode\nwith BuildPart() as angle_iron:\n with BuildSketch() as profile:\n Rectangle(3 * CM, 4 * MM, align=Align.MIN)\n Rectangle(4 * MM, 3 * CM, align=Align.MIN)\n extrude(amount=10 * CM)\n fillet(angle_iron.edges().filter_by(lambda e: e.is_interior), 5 * MM)\n" + }, + { + "id": "docs-rst/OpenSCAD/b02", + "source": "docs/OpenSCAD.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\n# Algebra mode\nprofile = Rectangle(3 * CM, 4 * MM, align=Align.MIN)\nprofile += Rectangle(4 * MM, 3 * CM, align=Align.MIN)\nangle_iron = extrude(profile, 10 * CM)\nangle_iron = fillet(angle_iron.edges().filter_by(lambda e: e.is_interior), 5 * MM)\n" + }, + { + "id": "docs-rst/OpenSCAD/all", + "source": "docs/OpenSCAD.rst (all 2 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\nfrom math import *\n\n# Builder mode\nwith BuildPart() as angle_iron:\n with BuildSketch() as profile:\n Rectangle(3 * CM, 4 * MM, align=Align.MIN)\n Rectangle(4 * MM, 3 * CM, align=Align.MIN)\n extrude(amount=10 * CM)\n fillet(angle_iron.edges().filter_by(lambda e: e.is_interior), 5 * MM)\n\n# Algebra mode\nprofile = Rectangle(3 * CM, 4 * MM, align=Align.MIN)\nprofile += Rectangle(4 * MM, 3 * CM, align=Align.MIN)\nangle_iron = extrude(profile, 10 * CM)\nangle_iron = fillet(angle_iron.edges().filter_by(lambda e: e.is_interior), 5 * MM)\n" + }, + { + "id": "docs-rst/algebra_performance/b01", + "source": "docs/algebra_performance.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\ndiam = 80\nholes = Sketch()\nr = Rectangle(2, 2)\nfor loc in GridLocations(4, 4, 20, 20):\n if loc.position.X**2 + loc.position.Y**2 < (diam / 2 - 1.8) ** 2:\n holes += loc * r\n\nc = Circle(diam / 2) - holes\n" + }, + { + "id": "docs-rst/algebra_performance/b03", + "source": "docs/algebra_performance.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\npolygons = Sketch() + [\n loc * RegularPolygon(radius=5, side_count=5)\n for loc in GridLocations(40, 30, 2, 2)\n]\n" + }, + { + "id": "docs-rst/algebra_performance/all", + "source": "docs/algebra_performance.rst (all 3 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\nfrom math import *\n\ndiam = 80\nholes = Sketch()\nr = Rectangle(2, 2)\nfor loc in GridLocations(4, 4, 20, 20):\n if loc.position.X**2 + loc.position.Y**2 < (diam / 2 - 1.8) ** 2:\n holes += loc * r\n\nc = Circle(diam / 2) - holes\n\nr = Rectangle(2, 2)\nholes = [\n loc * r\n for loc in GridLocations(4, 4, 20, 20).locations\n if loc.position.X**2 + loc.position.Y**2 < (diam / 2 - 1.8) ** 2\n]\n\nc = Circle(diam / 2) - holes\n\npolygons = Sketch() + [\n loc * RegularPolygon(radius=5, side_count=5)\n for loc in GridLocations(40, 30, 2, 2)\n]\n" + }, + { + "id": "docs-rst/build_sketch/b03", + "source": "docs/build_sketch.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith GridLocations(20, 20, 2, 2):\n with BuildSketch() as repeated:\n Rectangle(8, 4)\n\nshow_object(repeated.sketch_local, name=\"one local rectangle\")\nshow_object(repeated.sketch, name=\"four placed rectangles\")\n" + }, + { + "id": "docs-rst/import_export/b01", + "source": "docs/import_export.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as box_builder:\n Box(1, 1, 1)\nexport_step(box_builder.part, \"box.step\")\n" + }, + { + "id": "docs-rst/key_concepts_algebra/b01", + "source": "docs/key_concepts_algebra.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nb = Box(1, 2, 3)\nc = Cylinder(0.2, 5)\n" + }, + { + "id": "docs-rst/key_concepts_algebra/b02", + "source": "docs/key_concepts_algebra.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nr = Box(1, 2, 3) + Cylinder(0.2, 5)\n" + }, + { + "id": "docs-rst/key_concepts_algebra/b03", + "source": "docs/key_concepts_algebra.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nr = Box(1, 2, 3) - Cylinder(0.2, 5)\n" + }, + { + "id": "docs-rst/key_concepts_algebra/b04", + "source": "docs/key_concepts_algebra.rst code-block #4", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nr = Box(1, 2, 3) & Cylinder(0.2, 5)\n" + }, + { + "id": "docs-rst/key_concepts_algebra/b13", + "source": "docs/key_concepts_algebra.rst code-block #13", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nb = Plane.XZ * Rot(X=30) * Box(1, 2, 3) + Plane.YZ * Pos(X=-1) * Cylinder(0.2, 5)\n" + }, + { + "id": "docs-rst/key_concepts_builder/b01", + "source": "docs/key_concepts_builder.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\n\n# Using BuildPart to create a 3D model\nwith BuildPart() as example_part:\n with BuildSketch() as base_sketch:\n Rectangle(20, 20)\n extrude(amount=10) # Create a base block\n with BuildSketch(Plane(example_part.faces().sort_by(Axis.Z).last)) as cut_sketch:\n Circle(5)\n extrude(amount=-5, mode=Mode.SUBTRACT) # Subtract a cylinder\n\n# Access the final part\nresult_part = example_part.part\n" + }, + { + "id": "docs-rst/key_concepts_builder/b02", + "source": "docs/key_concepts_builder.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as invalid:\n Cylinder(1, 2).moved(Location((1, 2, 3)))\n" + }, + { + "id": "docs-rst/key_concepts_builder/b03", + "source": "docs/key_concepts_builder.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as valid:\n with Locations((1, 2, 3)):\n Cylinder(1, 2)\n" + }, + { + "id": "docs-rst/key_concepts_builder/b09", + "source": "docs/key_concepts_builder.rst code-block #9", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as part_builder:\n Box(10,10,10)\n with BuildSketch() as sketch_builder:\n Circle(2)\n" + }, + { + "id": "docs-rst/key_concepts_builder/b10", + "source": "docs/key_concepts_builder.rst code-block #10", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildSketch(Plane.XZ) as profile:\n Circle(5)\n\nshow(profile.sketch_local) # circle on local Plane.XY\nshow(profile.sketch) # circle placed on Plane.XZ\n" + }, + { + "id": "docs-rst/key_concepts_builder/b11", + "source": "docs/key_concepts_builder.rst code-block #11", + "kind": "docs-rst", + "code": "import build123d as bd\n\nwith bd.BuildPart() as bp:\n bd.Box(3, 3, 3)\n with bd.BuildSketch(*bp.faces()):\n bd.Rectangle(1, 2, rotation=45)\n bd.extrude(amount=0.1)\n" + }, + { + "id": "docs-rst/key_concepts_builder/b13", + "source": "docs/key_concepts_builder.rst code-block #13", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as model:\n with Locations((-20, 0), (20, 0)):\n with BuildSketch() as holes:\n Circle(3)\n extrude(amount=5)\n" + }, + { + "id": "docs-rst/key_concepts_builder/b14", + "source": "docs/key_concepts_builder.rst code-block #14", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith Locations((-10, 0), (10, 0)):\n with BuildPart() as placed_parts:\n Box(5, 5, 5)\n" + }, + { + "id": "docs-rst/key_concepts_builder/b17", + "source": "docs/key_concepts_builder.rst code-block #17", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as pipes:\n Box(10, 10, 10, rotation=(10, 20, 30))\n ...\n fillet(pipes.edges(Select.LAST), radius=0.2)\n" + }, + { + "id": "docs-rst/key_concepts_builder/b19", + "source": "docs/key_concepts_builder.rst code-block #19", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as pipes:\n Box(10, 10, 10, rotation=(10, 20, 30))\n" + }, + { + "id": "docs-rst/key_concepts_builder/b20", + "source": "docs/key_concepts_builder.rst code-block #20", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as pipes:\n with Locations((-10, -10, -10), (10, 10, 10)):\n Box(10, 10, 10, rotation=(10, 20, 30))\n" + }, + { + "id": "docs-rst/key_concepts_builder/b21", + "source": "docs/key_concepts_builder.rst code-block #21", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nheight, width, thickness, f_rad = 60, 80, 20, 10\n\nwith BuildPart() as pillow_block:\n with BuildSketch() as plan:\n Rectangle(width, height)\n fillet(plan.vertices(), radius=f_rad)\n extrude(amount=thickness)\n" + }, + { + "id": "docs-rst/location_arithmetic/all", + "source": "docs/location_arithmetic.rst (all 8 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\nfrom math import *\n\ndef location_symbol(location: Location, scale: float = 1) -> Compound:\n return Compound.make_triad(axes_scale=scale).locate(location)\n\ndef plane_symbol(plane: Plane, scale: float = 1) -> Compound:\n triad = Compound.make_triad(axes_scale=scale)\n circle = Circle(scale * .8).edge()\n return (triad + circle).locate(plane.location)\n\nloc = Location((0.1, 0.2, 0.3), (10, 20, 30))\n\nface = loc * Rectangle(1, 2)\n\nshow_object(face, name=\"face\")\nshow_object(location_symbol(loc), name=\"location\")\n\nplane = Plane.XZ\n\nface = plane * Rectangle(1, 2)\n\nshow_object(face, name=\"face\")\nshow_object(plane_symbol(plane), name=\"plane\")\n\nloc = Location((0.1, 0.2, 0.3), (10, 20, 30))\n\nface = loc * Rectangle(1,2)\n\nbox = Plane(loc) * Pos(0.2, 0.4, 0.1) * Box(0.2, 0.2, 0.2)\n# box = Plane(face.location) * Pos(0.2, 0.4, 0.1) * Box(0.2, 0.2, 0.2)\n# box = loc * Pos(0.2, 0.4, 0.1) * Box(0.2, 0.2, 0.2)\n\nshow_object(face, name=\"face\")\nshow_object(location_symbol(loc), name=\"location\")\nshow_object(box, name=\"box\")\n\nloc = Location((0.1, 0.2, 0.3), (10, 20, 30))\n\nface = loc * Rectangle(1,2)\n\nbox = Plane(loc) * Rot(Z=80) * Box(0.2, 0.2, 0.2)\n\nshow_object(face, name=\"face\")\nshow_object(location_symbol(loc), name=\"location\")\nshow_object(box, name=\"box\")\n\nloc = Location((0.1, 0.2, 0.3), (10, 20, 30))\n\nface = loc * Rectangle(1,2)\n\nbox = loc * Rot(20, 40, 80) * Box(0.2, 0.2, 0.2)\n\nshow_object(face, name=\"face\")\nshow_object(location_symbol(loc), name=\"location\")\nshow_object(box, name=\"box\")\n\nloc = Location((0.1, 0.2, 0.3), (10, 20, 30))\n\nface = loc * Rectangle(1,2)\n\nbox = loc * Rot(20, 40, 80) * Pos(0.2, 0.4, 0.1) * Box(0.2, 0.2, 0.2)\n\nshow_object(face, name=\"face\")\nshow_object(location_symbol(loc), name=\"location\")\nshow_object(box, name=\"box\")\nshow_object(location_symbol(loc * Rot(20, 40, 80), 0.5), options={\"color\":(0, 255, 255)}, name=\"local_location\")\n\nloc = Location((0.1, 0.2, 0.3), (10, 20, 30))\n\nface = loc * Rectangle(1,2)\n\nbox = loc * Pos(0.2, 0.4, 0.1) * Rot(20, 40, 80) * Box(0.2, 0.2, 0.2)\n\nshow_object(face, name=\"face\")\nshow_object(location_symbol(loc), name=\"location\")\nshow_object(box, name=\"box\")\nshow_object(location_symbol(loc * Pos(0.2, 0.4, 0.1), 0.5), options={\"color\":(0, 255, 255)}, name=\"local_location\")\n" + }, + { + "id": "docs-rst/selectors/b02", + "source": "docs/selectors.rst code-block #2", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nobj = Box(1, 1, 1) - Cylinder(0.2, 1)\nfaces_with_holes = obj.faces().filter_by(lambda f: f.inner_wires())\n" + }, + { + "id": "docs-rst/selectors/all", + "source": "docs/selectors.rst (all 2 code-blocks)", + "kind": "docs-rst-page", + "code": "from build123d import *\nfrom math import *\n\nwith BuildSketch() as din:\n ...\n outside_vertices = filter(\n lambda v: (v.Y == 0.0 or v.Y == height)\n and -overall_width / 2 < v.X < overall_width / 2,\n din.vertices(),\n )\n\nobj = Box(1, 1, 1) - Cylinder(0.2, 1)\nfaces_with_holes = obj.faces().filter_by(lambda f: f.inner_wires())\n" + }, + { + "id": "docs-rst/tips/b01", + "source": "docs/tips.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\n\nsvg_opts = {\"pixel_scale\": 5, \"show_axes\": False, \"show_hidden\": True}\n\nlength, width, thickness = 80.0, 60.0, 10.0\nhole_dia = 6.0\n\nwith BuildPart() as plate:\n Box(length, width, thickness)\n with GridLocations(length - 20, width - 20, 2, 2):\n Hole(radius=hole_dia / 2)\n top_face: Face = plate.faces().sort_by(Axis.Z)[-1]\n hole_edges = top_face.edges().filter_by(GeomType.CIRCLE)\n chamfer(hole_edges, length=1)\n" + }, + { + "id": "docs-rst/tips/b04", + "source": "docs/tips.rst code-block #4", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildSketch(Plane.XZ) as vertical_sketch:\n Rectangle(1, 1)\n with Locations(vertices().group_by(Axis.X)[-1].sort_by(Axis.Z)[-1]):\n Circle(0.2)\n" + }, + { + "id": "docs-rst/tips/b05", + "source": "docs/tips.rst code-block #5", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildSketch(Plane.YZ.rotated((123, 45, 6))) as custom_plane:\n Rectangle(1, 1, align=Align.MIN)\n with Locations(vertices().group_by(Axis.X)[-1].sort_by(Axis.Y)[-1]):\n Circle(0.2)\n" + }, + { + "id": "docs-rst/topology_selection/b01", + "source": "docs/topology_selection.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\n# In context\nwith BuildSketch() as context:\n Rectangle(1, 1)\n context.edges()\n\n # Build context implicitly has access to the selector\n edges()\n\n# Taking the sketch out of context\ncontext.sketch.edges()\n\n# Create sketch out of context\nRectangle(1, 1).edges()\n" + }, + { + "id": "docs-rst/topology_selection/b03", + "source": "docs/topology_selection.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as part:\n Box(5, 5, 1)\n Cylinder(1, 5)\n\n part.vertices()\n part.edges()\n part.faces()\n\n # Is the same as\n part.vertices(Select.ALL)\n part.edges(Select.ALL)\n part.faces(Select.ALL)\n" + }, + { + "id": "docs-rst/topology_selection/b04", + "source": "docs/topology_selection.rst code-block #4", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as part:\n Box(5, 5, 1)\n Cylinder(1, 5)\n\n part.vertices(Select.LAST)\n part.edges(Select.LAST)\n part.faces(Select.LAST)\n" + }, + { + "id": "docs-rst/topology_selection/b05", + "source": "docs/topology_selection.rst code-block #5", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as part:\n Box(5, 5, 1)\n Cylinder(1, 5)\n\n part.edges(Select.NEW)\n" + }, + { + "id": "docs-rst/topology_selection/b06", + "source": "docs/topology_selection.rst code-block #6", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as part:\n Box(5, 5, 1, align=(Align.CENTER, Align.CENTER, Align.MAX))\n Cylinder(2, 2, align=(Align.CENTER, Align.CENTER, Align.MIN))\n\n part.edges(Select.NEW)\n" + }, + { + "id": "docs-rst/topology_selection/b07", + "source": "docs/topology_selection.rst code-block #7", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildPart() as part:\n Box(5, 5, 1)\n Cylinder(1, 5)\n edges = part.edges().filter_by(lambda a: a.length == 1)\n fillet(edges, 1)\n\n part.edges(Select.NEW)\n" + }, + { + "id": "docs-rst/topology_selection/b08", + "source": "docs/topology_selection.rst code-block #8", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nbox = Box(5, 5, 1)\ncircle = Cylinder(2, 5)\npart = box + circle\nedges = new_edges(box, circle, combined=part)\n" + }, + { + "id": "docs-rst/topology_selection/b09", + "source": "docs/topology_selection.rst code-block #9", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nbox = Box(5, 5, 1)\ncircle = Cylinder(2, 5)\npart_before = box + circle\nedges = part_before.edges().filter_by(lambda a: a.length == 1)\npart = fillet(edges, 1)\nedges = new_edges(part_before, combined=part)\n" + }, + { + "id": "docs-rst/topology_selection/b12", + "source": "docs/topology_selection.rst code-block #12", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nbox = Box(1, 1, 1)\nfaces = box.faces()\ntop_face = faces.sort_by(Axis.Z)[-1]\n\nface_rings = faces.group_by(topo_distance_to(top_face))\n\ntop = face_rings[0]\nsides = face_rings[1]\nbottom = face_rings[2]\n" + }, + { + "id": "docs-rst/tutorial_constraints/b03", + "source": "docs/tutorial_constraints.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nisosceles = Triangle(a=30, b=30, C=60)\nisosceles.c\nisosceles.A\nisosceles.B\nisosceles.vertex_A\n" + }, + { + "id": "docs-rst/tutorial_constraints/b05", + "source": "docs/tutorial_constraints.rst code-block #5", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nm1 = CenterArc((-2, 0.6), 1, -10, 200).reversed()\nm2 = Spline((0.4, -0.6), (1, -1.6), (2, 0))\nconnector = BlendCurve(m1, m2, tangent_scalars=(2, 1), continuity=ContinuityLevel.C2)\ncomb = Curve(Wire([m1, connector, m2]).curvature_comb(200))\n" + }, + { + "id": "docs-rst/tutorial_constraints/b06", + "source": "docs/tutorial_constraints.rst code-block #6", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildLine() as coincident_ex:\n l1 = Line((0, 0), (1, 2))\n l2 = Line(l1 @ 1, l1 @ 1 + (1, 0))\n" + }, + { + "id": "docs-rst/tutorial_constraints/b07", + "source": "docs/tutorial_constraints.rst code-block #7", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildLine() as tangent_ex:\n l1 = Line((0, 0), (1, 1))\n l2 = JernArc(start=l1 @ 1, tangent=l1 % 1, radius=1, arc_size=70)\n" + }, + { + "id": "docs-rst/tutorial_constraints/b08", + "source": "docs/tutorial_constraints.rst code-block #8", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildLine() as perpendicular_ex:\n l1 = CenterArc((0, 0), 1.5, 0, 45)\n l2 = PolarLine(\n start=l1 @ 1, length=1, direction=l1.tangent_at(1).rotate(Axis.Z, -90)\n )\n" + }, + { + "id": "docs-rst/tutorial_constraints/b09", + "source": "docs/tutorial_constraints.rst code-block #9", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildLine() as intersect_ex:\n c1 = EllipticalCenterArc((0, 0), 1.2, 1.8, 0, arc_size=120, mode=Mode.PRIVATE)\n l1 = PolarLine(start=(-0.2, 0.1), length=c1, angle=10)\n l2 = PolarLine(start=(-0.2, 0.1), length=c1, angle=70)\n l3 = add(c1.trim(l1 @ 1, l2 @ 1))\n" + }, + { + "id": "docs-rst/tutorial_constraints/b10", + "source": "docs/tutorial_constraints.rst code-block #10", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\np1 = ParabolicCenterArc((0, 0), 0.5, 0, arc_size=Line((0, 1), (5, 1)))\nh1 = HyperbolicCenterArc((0, 0), 2, 1, 0, arc_size=Axis((0, 1), (1, 0)))\n" + }, + { + "id": "docs-rst/tutorial_constraints/b11", + "source": "docs/tutorial_constraints.rst code-block #11", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\ninside = FilletPolyline((1.5, 0), (1.5, 1), (-1.5, 1), (-1.5, 0), radius=0.2)\nperimeter = offset(inside, amount=0.2, side=Side.RIGHT)\n" + }, + { + "id": "docs-rst/tutorial_constraints/b13", + "source": "docs/tutorial_constraints.rst code-block #13", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nwith BuildLine() as egg_plant:\n # Construction Geometry\n c1 = CenterArc((-2, 0), 0.75, 80, 240, mode=Mode.PRIVATE)\n c2 = CenterArc((2, 0), 1, 220, 250, mode=Mode.PRIVATE)\n\n # egg_plant perimeter\n l1 = ConstrainedArcs((c2, Tangency.OUTSIDE), (c1, Tangency.OUTSIDE), radius=6)\n l2 = ConstrainedArcs(\n (c2, Tangency.ENCLOSING),\n (c1, Tangency.ENCLOSING),\n radius=8,\n selector=lambda a: a.sort_by(Axis.Y)[-1],\n )\n l3 = add(c1.trim(l1 @ 1, l2 @ 1))\n l4 = add(c2.trim(l1 @ 0, l2 @ 0))\n" + }, + { + "id": "docs-rst/tutorial_design/b07", + "source": "docs/tutorial_design.rst code-block #7", + "kind": "docs-rst", + "code": "from build123d import *\n# [removed by collect.py] from ocp_vscode import show_all\n\nthickness = 3 * MM\nwidth = 25 * MM\nlength = 50 * MM\nheight = 25 * MM\nhole_diameter = 5 * MM\nbend_radius = 5 * MM\nfillet_radius = 2 * MM\n\nwith BuildPart() as bracket:\n with BuildSketch() as sketch:\n with BuildLine() as profile:\n FilletPolyline(\n (0, 0), (length / 2, 0), (length / 2, height), radius=bend_radius\n )\n offset(amount=thickness, side=Side.LEFT)\n make_face()\n mirror(about=Plane.YZ)\n extrude(amount=width / 2)\n mirror(about=Plane.XY)\n corners = bracket.edges().filter_by(Axis.X).group_by(Axis.Y)[-1]\n fillet(corners, fillet_radius)\n with Locations(bracket.faces().sort_by(Axis.X)[-1]):\n Hole(hole_diameter / 2)\n with BuildSketch(bracket.faces().sort_by(Axis.Y)[0]):\n SlotOverall(20 * MM, hole_diameter)\n extrude(amount=-thickness, mode=Mode.SUBTRACT)\n\nshow_all()\n" + }, + { + "id": "docs-rst/tutorial_stl_reconstruction/b03", + "source": "docs/tutorial_stl_reconstruction.rst code-block #3", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nfillet_box = fillet(Box(1, 1, 1).edges(), 0.1)\n" + }, + { + "id": "docs-rst/tutorial_stl_reconstruction/b04", + "source": "docs/tutorial_stl_reconstruction.rst code-block #4", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\nr00 = Plane.XY.offset(-0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)\nc01 = Plane.XY.offset(-0.4) * Pos(0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc02 = Plane.XY.offset(-0.4) * Pos(-0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc03 = Plane.XY.offset(-0.4) * Pos(-0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc04 = Plane.XY.offset(-0.4) * Pos(0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nr05 = Plane.XY.offset(0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)\nr06 = Plane.YZ.offset(-0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)\nc07 = Plane.YZ.offset(-0.4) * Pos(-0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc08 = Plane.YZ.offset(-0.4) * Pos(-0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc09 = Plane.YZ.offset(-0.4) * Pos(0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc10 = Plane.YZ.offset(-0.4) * Pos(0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nr11 = Plane.YZ.offset(0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)\nr12 = Plane.ZX.offset(-0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)\nc13 = Plane.ZX.offset(-0.4) * Pos(-0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc14 = Plane.ZX.offset(-0.4) * Pos(-0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc15 = Plane.ZX.offset(-0.4) * Pos(0.4, -0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nc16 = Plane.ZX.offset(-0.4) * Pos(0.4, 0.4) * Face.extrude(Circle(0.0999996).edge(), (0, 0, 0.8))\nr17 = Plane.ZX.offset(0.5) * Pos(-0.4, -0.4) * Rectangle(0.8, 0.8, align=Align.MIN)\ns18 = Pos((0.399999, -0.399999, 0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\ns19 = Pos((-0.399999, 0.399999, -0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\ns20 = Pos((-0.399999, -0.399999, -0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\ns21 = Pos((0.399999, 0.399999, -0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\ns22 = Pos((-0.399999, 0.400026, 0.399999)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\ns23 = Pos((-0.399999, -0.399999, 0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\ns24 = Pos((0.399999, 0.400026, 0.399999)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\ns25 = Pos((0.399999, -0.399999, -0.400026)) * Sphere(0.099983).faces().filter_by(GeomType.SPHERE)[0]\n" + }, + { + "id": "docs-rst/objects-text/b10", + "source": "docs/objects/text.rst code-block #10", + "kind": "docs-rst", + "code": "from build123d import *\nfrom math import *\n\ntext = \"The quick brown fox\"\npath = RadiusArc((-50, 0), (50, 0), 100)\nText(\n text,\n 10,\n path=path,\n position_on_path=.5,\n text_align=(TextAlign.CENTER, TextAlign.BOTTOM)\n)\n" + }, + { + "id": "docs-rst/topology_selection-filter_examples/b01", + "source": "docs/topology_selection/filter_examples.rst code-block #1", + "kind": "docs-rst", + "code": "from build123d import *\n\nwith BuildPart() as part:\n Box(1, 1, 1)\n" + } +] \ No newline at end of file diff --git a/test/b123d-validation/probe.mjs b/test/b123d-validation/probe.mjs new file mode 100644 index 00000000..61629834 --- /dev/null +++ b/test/b123d-validation/probe.mjs @@ -0,0 +1,88 @@ +// probe.mjs - debug helper: run ONE Python script (file path or --id from +// manifest.json) through the built app and print the raw measurement JSON +// plus errors/logs. Usage: +// CS_TEST_HEADFUL=1 DISPLAY=:99 node probe.mjs /tmp/snippet.py +// CS_TEST_HEADFUL=1 DISPLAY=:99 node probe.mjs --id examples/extrude_algebra +// Env: CS_TEST_PORT (default 8517; the server must already be running or +// dist is served on the port by run-lite.mjs conventions). +import { chromium } from 'playwright'; +import { readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { spawn } from 'node:child_process'; +import http from 'node:http'; +import { readAssets } from './run-lite.mjs'; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const ROOT = join(HERE, '..', '..'); +const PORT = parseInt(process.env.CS_TEST_PORT || '8517', 10); +// CS_PY_RUNTIME=pyodide runs the script on the experimental CPython runtime +// (needs the vendored Pyodide core — see PyodideRuntime.js). +const PY_RUNTIME_QUERY = process.env.CS_PY_RUNTIME === 'pyodide' ? '?pyruntime=pyodide' : ''; + +const args = process.argv.slice(2); +let code; +let assets = null; +if (args[0] === '--id') { + const manifest = JSON.parse(readFileSync(join(HERE, 'manifest.json'), 'utf8')); + const entry = manifest.find((e) => e.id === args[1]); + code = entry.code; + assets = readAssets(entry); +} else { + code = readFileSync(args[0], 'utf8'); +} +code += '\n\nimport build123d as _b123d_lite_mod\n' + + 'print("B123D_MEASURE " + _b123d_lite_mod._measure_globals_json(globals()))\n'; + +async function ensureServer() { + const alive = await new Promise((res) => { + const req = http.get({ host: 'localhost', port: PORT, path: '/' }, (r) => { r.resume(); res(true); }); + req.on('error', () => res(false)); + req.setTimeout(2000, () => { req.destroy(); res(false); }); + }); + if (alive) return null; + const proc = spawn('npx', ['http-server', './packages/cascade-studio/dist', + '-p', String(PORT), '-c-1', '--silent'], { cwd: ROOT, stdio: 'ignore' }); + await new Promise((r) => setTimeout(r, 2500)); + return proc; +} + +const serverProc = await ensureServer(); +const browser = await chromium.launch({ + headless: !process.env.CS_TEST_HEADFUL, + args: ['--use-gl=angle', '--use-angle=swiftshader'], +}); +const page = await browser.newPage(); +page.on('pageerror', () => {}); +await page.goto(`http://localhost:${PORT}/${PY_RUNTIME_QUERY}`, { timeout: 60000 }); +await page.waitForFunction(() => window.CascadeAPI && window.CascadeAPI.isReady(), undefined, { timeout: 90000 }); +await page.waitForFunction(() => !window.CascadeAPI.isWorking(), undefined, { timeout: 90000 }); +await page.evaluate(() => window.CascadeAPI.setMode('python')); +if (assets) { + console.log('assets loaded:', await page.evaluate( + (a) => window.CascadeAPI.loadExternalFiles(a), assets)); +} +await page.evaluate(async (c) => { return await window.CascadeAPI.runCode(c); }, code); +try { + await page.waitForFunction( + () => window.CascadeAPI.getConsoleLog().some((l) => l.startsWith('B123D_MEASURE ')) || + window.CascadeAPI.getErrors().some((e) => e.includes('Python ')), + undefined, { timeout: 90000 }); +} catch (e) { /* fall through and dump whatever we have */ } +const logs = await page.evaluate(() => window.CascadeAPI.getConsoleLog()); +const errors = await page.evaluate(() => window.CascadeAPI.getErrors()); +const line = logs.find((l) => l.startsWith('B123D_MEASURE ')); +if (line) { + let payload = line.slice('B123D_MEASURE '.length); + let measure; + try { measure = JSON.parse(payload); } catch (e) { measure = JSON.parse(JSON.parse('"' + payload + '"')); } + console.log(JSON.stringify(measure, null, 1)); +} else { + console.log('NO MEASUREMENT'); +} +console.log('--- errors ---'); +for (const e of errors) console.log(e.replace(/\\n/g, '\n')); +console.log('--- last logs ---'); +for (const l of logs.slice(-12)) if (!l.startsWith('B123D_MEASURE')) console.log(l); +await browser.close(); +if (serverProc) serverProc.kill(); diff --git a/test/b123d-validation/reference-all.json b/test/b123d-validation/reference-all.json new file mode 100644 index 00000000..d503eb96 --- /dev/null +++ b/test/b123d-validation/reference-all.json @@ -0,0 +1,22754 @@ +{ + "docs-objects/text": { + "error": "ModuleNotFoundError: No module named 'tcv_screenshots'", + "status": "error" + }, + "docs-rst/OpenSCAD/all": { + "shapes": { + "angle_iron": { + "area": 12244.128255227575, + "bbox": [ + 0.0, + 0.0, + 0.0, + 30.0, + 30.0, + 100.0 + ], + "edges": 21, + "faces": 9, + "volume": 22936.50459150638 + }, + "profile": { + "area": 224.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 30.0, + 30.0, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/OpenSCAD/b01": { + "shapes": { + "angle_iron": { + "area": 12244.128255227575, + "bbox": [ + 0.0, + 0.0, + 0.0, + 30.0, + 30.0, + 100.0 + ], + "edges": 21, + "faces": 9, + "volume": 22936.50459150638 + }, + "profile": { + "area": 224.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 30.0, + 30.0, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/OpenSCAD/b02": { + "shapes": { + "angle_iron": { + "area": 12244.128255227575, + "bbox": [ + 0.0, + 0.0, + 0.0, + 30.0, + 30.0, + 100.0 + ], + "edges": 21, + "faces": 9, + "volume": 22936.50459150638 + }, + "profile": { + "area": 224.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 30.0, + 30.0, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/advantages/all": { + "error": "SyntaxError: invalid syntax. Perhaps you forgot a comma?", + "status": "error" + }, + "docs-rst/advantages/b01": { + "error": "SyntaxError: invalid syntax. Perhaps you forgot a comma?", + "status": "error" + }, + "docs-rst/advantages/b02": { + "error": "NameError: name 'width' is not defined", + "status": "error" + }, + "docs-rst/advantages/b03": { + "error": "NameError: name 'width' is not defined", + "status": "error" + }, + "docs-rst/advantages/b04": { + "error": "ValueError: Polyline requires two or more pts", + "status": "error" + }, + "docs-rst/advantages/b05": { + "error": "NameError: name 'rail' is not defined", + "status": "error" + }, + "docs-rst/algebra_performance/all": { + "shapes": { + "c": { + "area": 3890.5482457436683, + "bbox": [ + -40.0, + -40.0, + 0.0, + 40.0, + 40.0, + 0.0 + ], + "edges": 1137, + "faces": 1, + "volume": 0.0 + }, + "holes[0]": { + "area": 4.0, + "bbox": [ + -39.0, + -3.0, + 0.0, + -37.0, + -1.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[10]": { + "area": 4.0, + "bbox": [ + -31.0, + -23.0, + 0.0, + -29.0, + -21.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[11]": { + "area": 4.0, + "bbox": [ + -31.0, + -19.0, + 0.0, + -29.0, + -17.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[12]": { + "area": 4.0, + "bbox": [ + -31.0, + -15.0, + 0.0, + -29.0, + -13.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[13]": { + "area": 4.0, + "bbox": [ + -31.0, + -11.0, + 0.0, + -29.0, + -9.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[14]": { + "area": 4.0, + "bbox": [ + -31.0, + -7.0, + 0.0, + -29.0, + -5.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[15]": { + "area": 4.0, + "bbox": [ + -31.0, + -3.0, + 0.0, + -29.0, + -1.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[16]": { + "area": 4.0, + "bbox": [ + -31.0, + 1.0, + 0.0, + -29.0, + 3.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[17]": { + "area": 4.0, + "bbox": [ + -31.0, + 5.0, + 0.0, + -29.0, + 7.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[18]": { + "area": 4.0, + "bbox": [ + -31.0, + 9.0, + 0.0, + -29.0, + 11.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[19]": { + "area": 4.0, + "bbox": [ + -31.0, + 13.0, + 0.0, + -29.0, + 15.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[1]": { + "area": 4.0, + "bbox": [ + -39.0, + 1.0, + 0.0, + -37.0, + 3.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[20]": { + "area": 4.0, + "bbox": [ + -31.0, + 17.0, + 0.0, + -29.0, + 19.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[21]": { + "area": 4.0, + "bbox": [ + -31.0, + 21.0, + 0.0, + -29.0, + 23.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[22]": { + "area": 4.0, + "bbox": [ + -27.0, + -27.0, + 0.0, + -25.0, + -25.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[23]": { + "area": 4.0, + "bbox": [ + -27.0, + -23.0, + 0.0, + -25.0, + -21.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[24]": { + "area": 4.0, + "bbox": [ + -27.0, + -19.0, + 0.0, + -25.0, + -17.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[25]": { + "area": 4.0, + "bbox": [ + -27.0, + -15.0, + 0.0, + -25.0, + -13.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[26]": { + "area": 4.0, + "bbox": [ + -27.0, + -11.0, + 0.0, + -25.0, + -9.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[27]": { + "area": 4.0, + "bbox": [ + -27.0, + -7.0, + 0.0, + -25.0, + -5.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[28]": { + "area": 4.0, + "bbox": [ + -27.0, + -3.0, + 0.0, + -25.0, + -1.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[29]": { + "area": 4.0, + "bbox": [ + -27.0, + 1.0, + 0.0, + -25.0, + 3.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[2]": { + "area": 4.0, + "bbox": [ + -35.0, + -15.0, + 0.0, + -33.0, + -13.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[30]": { + "area": 4.0, + "bbox": [ + -27.0, + 5.0, + 0.0, + -25.0, + 7.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[31]": { + "area": 4.0, + "bbox": [ + -27.0, + 9.0, + 0.0, + -25.0, + 11.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[32]": { + "area": 4.0, + "bbox": [ + -27.0, + 13.0, + 0.0, + -25.0, + 15.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[33]": { + "area": 4.0, + "bbox": [ + -27.0, + 17.0, + 0.0, + -25.0, + 19.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[34]": { + "area": 4.0, + "bbox": [ + -27.0, + 21.0, + 0.0, + -25.0, + 23.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[35]": { + "area": 4.0, + "bbox": [ + -27.0, + 25.0, + 0.0, + -25.0, + 27.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[36]": { + "area": 4.0, + "bbox": [ + -23.0, + -31.0, + 0.0, + -21.0, + -29.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[37]": { + "area": 4.0, + "bbox": [ + -23.0, + -27.0, + 0.0, + -21.0, + -25.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[38]": { + "area": 4.0, + "bbox": [ + -23.0, + -23.0, + 0.0, + -21.0, + -21.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[39]": { + "area": 4.0, + "bbox": [ + -23.0, + -19.0, + 0.0, + -21.0, + -17.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[3]": { + "area": 4.0, + "bbox": [ + -35.0, + -11.0, + 0.0, + -33.0, + -9.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[40]": { + "area": 4.0, + "bbox": [ + -23.0, + -15.0, + 0.0, + -21.0, + -13.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[41]": { + "area": 4.0, + "bbox": [ + -23.0, + -11.0, + 0.0, + -21.0, + -9.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[42]": { + "area": 4.0, + "bbox": [ + -23.0, + -7.0, + 0.0, + -21.0, + -5.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[43]": { + "area": 4.0, + "bbox": [ + -23.0, + -3.0, + 0.0, + -21.0, + -1.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[44]": { + "area": 4.0, + "bbox": [ + -23.0, + 1.0, + 0.0, + -21.0, + 3.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[45]": { + "area": 4.0, + "bbox": [ + -23.0, + 5.0, + 0.0, + -21.0, + 7.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[46]": { + "area": 4.0, + "bbox": [ + -23.0, + 9.0, + 0.0, + -21.0, + 11.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[47]": { + "area": 4.0, + "bbox": [ + -23.0, + 13.0, + 0.0, + -21.0, + 15.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[48]": { + "area": 4.0, + "bbox": [ + -23.0, + 17.0, + 0.0, + -21.0, + 19.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[49]": { + "area": 4.0, + "bbox": [ + -23.0, + 21.0, + 0.0, + -21.0, + 23.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[4]": { + "area": 4.0, + "bbox": [ + -35.0, + -7.0, + 0.0, + -33.0, + -5.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[50]": { + "area": 4.0, + "bbox": [ + -23.0, + 25.0, + 0.0, + -21.0, + 27.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[51]": { + "area": 4.0, + "bbox": [ + -23.0, + 29.0, + 0.0, + -21.0, + 31.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[52]": { + "area": 4.0, + "bbox": [ + -19.0, + -31.0, + 0.0, + -17.0, + -29.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[53]": { + "area": 4.0, + "bbox": [ + -19.0, + -27.0, + 0.0, + -17.0, + -25.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[54]": { + "area": 4.0, + "bbox": [ + -19.0, + -23.0, + 0.0, + -17.0, + -21.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[55]": { + "area": 4.0, + "bbox": [ + -19.0, + -19.0, + 0.0, + -17.0, + -17.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[56]": { + "area": 4.0, + "bbox": [ + -19.0, + -15.0, + 0.0, + -17.0, + -13.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[57]": { + "area": 4.0, + "bbox": [ + -19.0, + -11.0, + 0.0, + -17.0, + -9.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[58]": { + "area": 4.0, + "bbox": [ + -19.0, + -7.0, + 0.0, + -17.0, + -5.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[59]": { + "area": 4.0, + "bbox": [ + -19.0, + -3.0, + 0.0, + -17.0, + -1.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[5]": { + "area": 4.0, + "bbox": [ + -35.0, + -3.0, + 0.0, + -33.0, + -1.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[60]": { + "area": 4.0, + "bbox": [ + -19.0, + 1.0, + 0.0, + -17.0, + 3.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[61]": { + "area": 4.0, + "bbox": [ + -19.0, + 5.0, + 0.0, + -17.0, + 7.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[62]": { + "area": 4.0, + "bbox": [ + -19.0, + 9.0, + 0.0, + -17.0, + 11.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[63]": { + "area": 4.0, + "bbox": [ + -19.0, + 13.0, + 0.0, + -17.0, + 15.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[6]": { + "area": 4.0, + "bbox": [ + -35.0, + 1.0, + 0.0, + -33.0, + 3.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[7]": { + "area": 4.0, + "bbox": [ + -35.0, + 5.0, + 0.0, + -33.0, + 7.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[8]": { + "area": 4.0, + "bbox": [ + -35.0, + 9.0, + 0.0, + -33.0, + 11.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[9]": { + "area": 4.0, + "bbox": [ + -35.0, + 13.0, + 0.0, + -33.0, + 15.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "polygons": { + "area": 237.76412907378844, + "bbox": [ + -24.045084971874736, + -19.755282581475768, + 0.0, + 25.0, + 19.755282581475768, + 0.0 + ], + "edges": 20, + "faces": 4, + "volume": 0.0 + }, + "r": { + "area": 4.0, + "bbox": [ + -1.0, + -1.0, + 0.0, + 1.0, + 1.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/algebra_performance/b01": { + "shapes": { + "c": { + "area": 3890.5482457436683, + "bbox": [ + -40.0, + -40.0, + 0.0, + 40.0, + 40.0, + 0.0 + ], + "edges": 1137, + "faces": 1, + "volume": 0.0 + }, + "holes": { + "area": 1136.0, + "bbox": [ + -39.0, + -39.0, + 0.0, + 39.0, + 39.0, + 0.0 + ], + "edges": 1136, + "faces": 284, + "volume": 0.0 + }, + "r": { + "area": 4.0, + "bbox": [ + -1.0, + -1.0, + 0.0, + 1.0, + 1.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/algebra_performance/b02": { + "error": "NameError: name 'diam' is not defined", + "status": "error" + }, + "docs-rst/algebra_performance/b03": { + "shapes": { + "polygons": { + "area": 237.76412907378844, + "bbox": [ + -24.045084971874736, + -19.755282581475768, + 0.0, + 25.0, + 19.755282581475768, + 0.0 + ], + "edges": 20, + "faces": 4, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/build_line/b01": { + "error": "AttributeError: 'BuildLine' has no attribute 'line_local'. Did you intend '.line.line_local'?", + "status": "error" + }, + "docs-rst/build_sketch/all": { + "error": "NameError: name 'display' is not defined", + "status": "error" + }, + "docs-rst/build_sketch/b01": { + "error": "NameError: name 'display' is not defined", + "status": "error" + }, + "docs-rst/build_sketch/b02": { + "error": "NameError: name 'display' is not defined", + "status": "error" + }, + "docs-rst/build_sketch/b03": { + "shapes": { + "repeated": { + "area": 32.0, + "bbox": [ + -4.0, + -2.0, + 0.0, + 4.0, + 2.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/build_sketch/b04": { + "error": "NameError: name 'plane' is not defined. Did you mean: 'Plane'?", + "status": "error" + }, + "docs-rst/debugging_logging/all": { + "error": "NameError: name 'logging' is not defined. Did you forget to import 'logging'?", + "status": "error" + }, + "docs-rst/debugging_logging/b01": { + "error": "NameError: name 'logging' is not defined. Did you forget to import 'logging'?", + "status": "error" + }, + "docs-rst/debugging_logging/b02": { + "error": "NameError: name 'logging' is not defined. Did you forget to import 'logging'?", + "status": "error" + }, + "docs-rst/debugging_logging/b03": { + "error": "NameError: name 'logger' is not defined", + "status": "error" + }, + "docs-rst/debugging_logging/b04": { + "status": "no-shapes" + }, + "docs-rst/import_export/all": { + "error": "NameError: name 'export_obj' is not defined. Did you mean: 'export_stl'?", + "status": "error" + }, + "docs-rst/import_export/b01": { + "shapes": { + "box_builder": { + "area": 6.0, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 0.9999999999999998 + } + }, + "status": "ok" + }, + "docs-rst/import_export/b02": { + "error": "NameError: name 'export_obj' is not defined. Did you mean: 'export_stl'?", + "status": "error" + }, + "docs-rst/import_export/b03": { + "error": "NameError: name 'part' is not defined. Did you mean: 'Part'?", + "status": "error" + }, + "docs-rst/import_export/b04": { + "error": "NameError: name 'uuid' is not defined. Did you forget to import 'uuid'?", + "status": "error" + }, + "docs-rst/import_export/b05": { + "error": "lib3mf.Lib3MF.ELib3MFException: Lib3MFException 5: The specified file could not be opened", + "status": "error" + }, + "docs-rst/joints/all": { + "error": "NameError: name 'pipe' is not defined", + "status": "error" + }, + "docs-rst/joints/b01": { + "error": "NameError: name 'pipe' is not defined", + "status": "error" + }, + "docs-rst/joints/b02": { + "error": "NameError: name 'pipe' is not defined", + "status": "error" + }, + "docs-rst/joints/b03": { + "error": "NameError: name 'hinge_outer' is not defined", + "status": "error" + }, + "docs-rst/key_concepts_algebra/all": { + "error": "NameError: name 'plane' is not defined. Did you mean: 'Plane'?", + "status": "error" + }, + "docs-rst/key_concepts_algebra/b01": { + "shapes": { + "b": { + "area": 22.0, + "bbox": [ + -0.5, + -1.0, + -1.5, + 0.5, + 1.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 6.0 + }, + "c": { + "area": 6.53451271946677, + "bbox": [ + -0.2, + -0.2, + -2.5, + 0.2, + 0.2, + 2.5 + ], + "edges": 3, + "faces": 3, + "volume": 0.6283185307179587 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_algebra/b02": { + "shapes": { + "r": { + "area": 24.513274122871834, + "bbox": [ + -0.5, + -1.0, + -2.5, + 0.5, + 1.0, + 2.5 + ], + "edges": 18, + "faces": 10, + "volume": 6.251327412287184 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_algebra/b03": { + "shapes": { + "r": { + "area": 25.51858377202057, + "bbox": [ + -0.5, + -1.0, + -1.5, + 0.5, + 1.0, + 1.5 + ], + "edges": 15, + "faces": 7, + "volume": 5.623008881569225 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_algebra/b04": { + "shapes": { + "r": { + "area": 4.0212385965949355, + "bbox": [ + -0.2, + -0.2, + -1.5, + 0.2, + 0.2, + 1.5 + ], + "edges": 3, + "faces": 3, + "volume": 0.37699111843077515 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_algebra/b05": { + "error": "NameError: name 'plane' is not defined. Did you mean: 'Plane'?", + "status": "error" + }, + "docs-rst/key_concepts_algebra/b06": { + "error": "NameError: name 'plane' is not defined. Did you mean: 'Plane'?", + "status": "error" + }, + "docs-rst/key_concepts_algebra/b07": { + "status": "no-shapes" + }, + "docs-rst/key_concepts_algebra/b08": { + "status": "no-shapes" + }, + "docs-rst/key_concepts_algebra/b09": { + "status": "no-shapes" + }, + "docs-rst/key_concepts_algebra/b10": { + "status": "no-shapes" + }, + "docs-rst/key_concepts_algebra/b11": { + "status": "no-shapes" + }, + "docs-rst/key_concepts_algebra/b12": { + "status": "no-shapes" + }, + "docs-rst/key_concepts_algebra/b13": { + "shapes": { + "b": { + "area": 27.02654824574367, + "bbox": [ + -2.5, + -1.799038105676658, + -1.6160254037844386, + 2.5, + 1.799038105676658, + 1.6160254037844386 + ], + "edges": 18, + "faces": 10, + "volume": 6.502654824574366 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/all": { + "error": "IndentationError: expected an indented block after function definition on line 87", + "status": "error" + }, + "docs-rst/key_concepts_builder/b01": { + "shapes": { + "base_sketch": { + "area": 399.99999999999994, + "bbox": [ + -10.0, + -10.0, + 0.0, + 10.0, + 10.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "cut_sketch": { + "area": 78.53981633974482, + "bbox": [ + -5.0, + -5.0, + 0.0, + 5.0, + 5.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "example_part": { + "area": 1757.0796326794894, + "bbox": [ + -10.0, + -10.0, + 0.0, + 10.0, + 10.0, + 10.0 + ], + "edges": 15, + "faces": 8, + "volume": 3607.300918301276 + }, + "result_part": { + "area": 1757.0796326794894, + "bbox": [ + -10.0, + -10.0, + 0.0, + 10.0, + 10.0, + 10.0 + ], + "edges": 15, + "faces": 8, + "volume": 3607.300918301276 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b02": { + "shapes": { + "invalid": { + "area": 18.849555921538755, + "bbox": [ + -1.0, + -1.0, + -1.0, + 1.0, + 1.0, + 1.0 + ], + "edges": 3, + "faces": 3, + "volume": 6.283185307179585 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b03": { + "shapes": { + "valid": { + "area": 18.849555921538755, + "bbox": [ + 0.0, + 1.0, + 2.0, + 2.0, + 3.0, + 4.0 + ], + "edges": 3, + "faces": 3, + "volume": 6.283185307179585 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b04": { + "status": "no-shapes" + }, + "docs-rst/key_concepts_builder/b05": { + "error": "AttributeError: 'BuildPart' has no attribute 'part_local'. Did you intend '.part.part_local'?", + "status": "error" + }, + "docs-rst/key_concepts_builder/b06": { + "error": "ValueError: Unable to repositioned type with respect to local coordinates", + "status": "error" + }, + "docs-rst/key_concepts_builder/b07": { + "error": "AttributeError: 'BuildLine' has no attribute 'line_local'. Did you intend '.line.line_local'?", + "status": "error" + }, + "docs-rst/key_concepts_builder/b08": { + "error": "Invoked with: , , 10, 10", + "status": "error" + }, + "docs-rst/key_concepts_builder/b09": { + "shapes": { + "part_builder": { + "area": 599.9999999999999, + "bbox": [ + -5.0, + -5.0, + -5.0, + 5.0, + 5.0, + 5.0 + ], + "edges": 12, + "faces": 6, + "volume": 999.9999999999998 + }, + "sketch_builder": { + "area": 12.566370614359167, + "bbox": [ + -2.0, + -2.0, + 0.0, + 2.0, + 2.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b10": { + "shapes": { + "profile": { + "area": 78.53981633974482, + "bbox": [ + -5.0, + -5.0, + 0.0, + 5.0, + 5.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b11": { + "shapes": { + "bp": { + "area": 57.60000000000005, + "bbox": [ + -1.6, + -1.6, + -1.6, + 1.6, + 1.6, + 1.6 + ], + "edges": 84, + "faces": 36, + "volume": 28.20000000000004 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b12": { + "status": "no-shapes" + }, + "docs-rst/key_concepts_builder/b13": { + "shapes": { + "holes": { + "area": 28.274333882308138, + "bbox": [ + -3.0, + -3.0, + 0.0, + 3.0, + 3.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "model": { + "area": 150.79644737231007, + "bbox": [ + -3.0, + -3.0, + 0.0, + 3.0, + 3.0, + 5.0 + ], + "edges": 3, + "faces": 3, + "volume": 141.3716694115407 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b14": { + "shapes": { + "placed_parts": { + "area": 149.99999999999997, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 124.99999999999997 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b15": { + "status": "no-shapes" + }, + "docs-rst/key_concepts_builder/b16": { + "error": "IndentationError: expected an indented block after function definition on line 4", + "status": "error" + }, + "docs-rst/key_concepts_builder/b17": { + "shapes": { + "pipes": { + "area": 589.6538021939285, + "bbox": [ + -8.003187848326382, + -7.544897591812934, + -7.155615126552097, + 8.003187848326384, + 7.5448975918129335, + 7.155615126552096 + ], + "edges": 48, + "faces": 26, + "volume": 998.9806250585737 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b18": { + "error": "NameError: name 'Enum' is not defined", + "status": "error" + }, + "docs-rst/key_concepts_builder/b19": { + "shapes": { + "pipes": { + "area": 599.9999999999999, + "bbox": [ + -8.128320675339982, + -7.650934991471806, + -7.245432423491767, + 8.128320675339983, + 7.650934991471806, + 7.245432423491767 + ], + "edges": 12, + "faces": 6, + "volume": 999.9999999999998 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b20": { + "shapes": { + "pipes": { + "area": 1199.9999999999998, + "bbox": [ + -18.12832067533998, + -17.650934991471807, + -17.245432423491767, + 18.128320675339985, + 17.650934991471807, + 17.245432423491767 + ], + "edges": 24, + "faces": 12, + "volume": 2000.0 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b21": { + "shapes": { + "pillow_block": { + "area": 14684.955591832535, + "bbox": [ + -40.0, + -30.00000000000011, + 0.0, + 40.0, + 30.0, + 20.0 + ], + "edges": 24, + "faces": 10, + "volume": 94283.18530525154 + }, + "plan": { + "area": 4714.159265230443, + "bbox": [ + -40.0, + -30.00000000000011, + 0.0, + 40.0, + 30.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/location_arithmetic/all": { + "shapes": { + "box": { + "area": 0.24, + "bbox": [ + -0.043244666586104694, + 0.48121244275425257, + 0.31023361216202655, + 0.26129071947662474, + 0.7622259877217714, + 0.6479366741142275 + ], + "edges": 12, + "faces": 6, + "volume": 0.007999999999999998 + }, + "face": { + "area": 2.0, + "bbox": [ + -0.7767451510676409, + -0.8950920158866638, + -0.12123284194859885, + 0.976745151067641, + 1.2950920158866637, + 0.7212328419485988 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/location_arithmetic/b01": { + "status": "no-shapes" + }, + "docs-rst/location_arithmetic/b02": { + "error": "NameError: name 'location_symbol' is not defined", + "status": "error" + }, + "docs-rst/location_arithmetic/b03": { + "error": "NameError: name 'plane_symbol' is not defined", + "status": "error" + }, + "docs-rst/location_arithmetic/b04": { + "error": "NameError: name 'location_symbol' is not defined", + "status": "error" + }, + "docs-rst/location_arithmetic/b05": { + "error": "NameError: name 'location_symbol' is not defined", + "status": "error" + }, + "docs-rst/location_arithmetic/b06": { + "error": "NameError: name 'location_symbol' is not defined", + "status": "error" + }, + "docs-rst/location_arithmetic/b07": { + "error": "NameError: name 'location_symbol' is not defined", + "status": "error" + }, + "docs-rst/location_arithmetic/b08": { + "error": "NameError: name 'location_symbol' is not defined", + "status": "error" + }, + "docs-rst/moving_objects/all": { + "error": "NameError: name 'box' is not defined. Did you mean: 'Box'?", + "status": "error" + }, + "docs-rst/moving_objects/b01": { + "status": "no-shapes" + }, + "docs-rst/moving_objects/b02": { + "error": "NameError: name 'box' is not defined. Did you mean: 'Box'?", + "status": "error" + }, + "docs-rst/moving_objects/b03": { + "error": "NameError: name 'x' is not defined", + "status": "error" + }, + "docs-rst/moving_objects/b04": { + "error": "NameError: name 'shape' is not defined. Did you mean: 'scale'?", + "status": "error" + }, + "docs-rst/moving_objects/b05": { + "error": "NameError: name 'X' is not defined", + "status": "error" + }, + "docs-rst/moving_objects/b06": { + "error": "NameError: name 'shape' is not defined. Did you mean: 'scale'?", + "status": "error" + }, + "docs-rst/moving_objects/b07": { + "error": "NameError: name 'shape' is not defined. Did you mean: 'scale'?", + "status": "error" + }, + "docs-rst/moving_objects/b08": { + "error": "NameError: name 'shape' is not defined. Did you mean: 'scale'?", + "status": "error" + }, + "docs-rst/moving_objects/b09": { + "error": "NameError: name 'shape' is not defined. Did you mean: 'scale'?", + "status": "error" + }, + "docs-rst/moving_objects/b10": { + "error": "NameError: name 'shape' is not defined. Did you mean: 'scale'?", + "status": "error" + }, + "docs-rst/moving_objects/b11": { + "error": "NameError: name 'shape' is not defined. Did you mean: 'scale'?", + "status": "error" + }, + "docs-rst/moving_objects/b12": { + "error": "NameError: name 'shape' is not defined. Did you mean: 'scale'?", + "status": "error" + }, + "docs-rst/objects-text/all": { + "error": "FileNotFoundError: [Errno 2] No such file or directory: 'Roboto-VariableFont_wdth,wght.ttf'", + "status": "error" + }, + "docs-rst/objects-text/b01": { + "status": "no-shapes" + }, + "docs-rst/objects-text/b02": { + "error": "NameError: name 'text' is not defined. Did you mean: 'Text'?", + "status": "error" + }, + "docs-rst/objects-text/b03": { + "status": "no-shapes" + }, + "docs-rst/objects-text/b04": { + "error": "NameError: name 'text' is not defined. Did you mean: 'Text'?", + "status": "error" + }, + "docs-rst/objects-text/b05": { + "error": "NameError: name 'text' is not defined. Did you mean: 'Text'?", + "status": "error" + }, + "docs-rst/objects-text/b06": { + "error": "NameError: name 'text' is not defined. Did you mean: 'Text'?", + "status": "error" + }, + "docs-rst/objects-text/b07": { + "error": "FileNotFoundError: [Errno 2] No such file or directory: 'Roboto-VariableFont_wdth,wght.ttf'", + "status": "error" + }, + "docs-rst/objects-text/b08": { + "error": "NameError: name 'text' is not defined. Did you mean: 'Text'?", + "status": "error" + }, + "docs-rst/objects-text/b09": { + "status": "no-shapes" + }, + "docs-rst/objects-text/b10": { + "shapes": { + "path": { + "area": 0.0, + "bbox": [ + -50.00000000000001, + 0.0, + 0.0, + 50.000000000000014, + 13.397459621556152, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/objects-text/b11": { + "error": "NameError: name 'text' is not defined. Did you mean: 'Text'?", + "status": "error" + }, + "docs-rst/objects-text/b12": { + "error": "NameError: name 'text' is not defined. Did you mean: 'Text'?", + "status": "error" + }, + "docs-rst/objects-text/b13": { + "status": "no-shapes" + }, + "docs-rst/objects/all": { + "error": "NameError: name 'a' is not defined", + "status": "error" + }, + "docs-rst/objects/b01": { + "error": "NameError: name 'a' is not defined", + "status": "error" + }, + "docs-rst/objects/b02": { + "error": "NameError: name 'a' is not defined", + "status": "error" + }, + "docs-rst/objects/b03": { + "status": "no-shapes" + }, + "docs-rst/objects/b04": { + "status": "no-shapes" + }, + "docs-rst/operations/all": { + "error": "NameError: name 'radius' is not defined. Did you mean: 'radians'?", + "status": "error" + }, + "docs-rst/operations/b01": { + "error": "NameError: name 'radius' is not defined. Did you mean: 'radians'?", + "status": "error" + }, + "docs-rst/operations/b02": { + "error": "NameError: name 'radius' is not defined. Did you mean: 'radians'?", + "status": "error" + }, + "docs-rst/selectors/all": { + "shapes": { + "faces_with_holes[0]": { + "area": 0.8743362938564083, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + 0.5, + -0.5 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "faces_with_holes[1]": { + "area": 0.8743362938564083, + "bbox": [ + -0.5, + -0.5, + 0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "obj": { + "area": 7.005309649148733, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 15, + "faces": 7, + "volume": 0.8743362938564081 + } + }, + "status": "ok" + }, + "docs-rst/selectors/b01": { + "status": "no-shapes" + }, + "docs-rst/selectors/b02": { + "shapes": { + "faces_with_holes[0]": { + "area": 0.8743362938564083, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + 0.5, + -0.5 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "faces_with_holes[1]": { + "area": 0.8743362938564083, + "bbox": [ + -0.5, + -0.5, + 0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "obj": { + "area": 7.005309649148733, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 15, + "faces": 7, + "volume": 0.8743362938564081 + } + }, + "status": "ok" + }, + "docs-rst/tips/all": { + "error": "SyntaxError: invalid syntax", + "status": "error" + }, + "docs-rst/tips/b01": { + "shapes": { + "plate": { + "area": 12888.825470084847, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 32, + "faces": 14, + "volume": 46827.13874265981 + }, + "top_face": { + "area": 4686.902664470766, + "bbox": [ + -40.0, + -30.0, + 5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tips/b02": { + "error": "SyntaxError: invalid syntax", + "status": "error" + }, + "docs-rst/tips/b03": { + "error": "ModuleNotFoundError: No module named 'cadquery'", + "status": "error" + }, + "docs-rst/tips/b04": { + "shapes": { + "vertical_sketch": { + "area": 1.0942477796076904, + "bbox": [ + -0.5, + -0.7, + 0.0, + 0.7, + 0.5, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tips/b05": { + "shapes": { + "custom_plane": { + "area": 1.0942477796076904, + "bbox": [ + 0.0, + 0.0, + 0.0, + 1.2, + 1.2, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tips/b06": { + "error": "ValueError: Polyline requires two or more pts", + "status": "error" + }, + "docs-rst/topology_selection-filter_examples/all": { + "error": "SyntaxError: '(' was never closed", + "status": "error" + }, + "docs-rst/topology_selection-filter_examples/b01": { + "shapes": { + "part": { + "area": 6.0, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 0.9999999999999998 + } + }, + "status": "ok" + }, + "docs-rst/topology_selection-filter_examples/b02": { + "error": "NameError: name 'part' is not defined. Did you mean: 'Part'?", + "status": "error" + }, + "docs-rst/topology_selection-filter_examples/b03": { + "error": "SyntaxError: '(' was never closed", + "status": "error" + }, + "docs-rst/topology_selection-group_examples/b01": { + "error": "NameError: name 'part' is not defined. Did you mean: 'Part'?", + "status": "error" + }, + "docs-rst/topology_selection/all": { + "error": "SyntaxError: unmatched ')'", + "status": "error" + }, + "docs-rst/topology_selection/b01": { + "shapes": { + "context": { + "area": 1.0, + "bbox": [ + -0.5, + -0.5, + 0.0, + 0.5, + 0.5, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/topology_selection/b02": { + "error": "TypeError: Shape.edges() takes 1 positional argument but 2 were given", + "status": "error" + }, + "docs-rst/topology_selection/b03": { + "shapes": { + "part": { + "area": 95.13274122871834, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 18, + "faces": 10, + "volume": 37.56637061435917 + } + }, + "status": "ok" + }, + "docs-rst/topology_selection/b04": { + "shapes": { + "part": { + "area": 95.13274122871834, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 18, + "faces": 10, + "volume": 37.56637061435917 + } + }, + "status": "ok" + }, + "docs-rst/topology_selection/b05": { + "shapes": { + "part": { + "area": 95.13274122871834, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 18, + "faces": 10, + "volume": 37.56637061435917 + } + }, + "status": "ok" + }, + "docs-rst/topology_selection/b06": { + "shapes": { + "part": { + "area": 95.13274122871834, + "bbox": [ + -2.5, + -2.5, + -1.0, + 2.5, + 2.5, + 2.0 + ], + "edges": 15, + "faces": 8, + "volume": 50.13274122871835 + } + }, + "status": "ok" + }, + "docs-rst/topology_selection/b07": { + "shapes": { + "part": { + "area": 91.69911184307752, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 30, + "faces": 14, + "volume": 36.70796326794897 + } + }, + "status": "ok" + }, + "docs-rst/topology_selection/b08": { + "shapes": { + "box": { + "area": 70.0, + "bbox": [ + -2.5, + -2.5, + -0.5, + 2.5, + 2.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 24.999999999999993 + }, + "circle": { + "area": 87.96459430051421, + "bbox": [ + -2.0, + -2.0, + -2.5, + 2.0, + 2.0, + 2.5 + ], + "edges": 3, + "faces": 3, + "volume": 62.83185307179585 + }, + "part": { + "area": 120.26548245743669, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 18, + "faces": 10, + "volume": 75.26548245743669 + } + }, + "status": "ok" + }, + "docs-rst/topology_selection/b09": { + "shapes": { + "box": { + "area": 70.0, + "bbox": [ + -2.5, + -2.5, + -0.5, + 2.5, + 2.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 24.999999999999993 + }, + "circle": { + "area": 87.96459430051421, + "bbox": [ + -2.0, + -2.0, + -2.5, + 2.0, + 2.0, + 2.5 + ], + "edges": 3, + "faces": 3, + "volume": 62.83185307179585 + }, + "part": { + "area": 116.83185307179586, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 30, + "faces": 14, + "volume": 74.40707511102647 + }, + "part_before": { + "area": 120.26548245743669, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 18, + "faces": 10, + "volume": 75.26548245743669 + } + }, + "status": "ok" + }, + "docs-rst/topology_selection/b10": { + "error": "NameError: name 'part' is not defined. Did you mean: 'Part'?", + "status": "error" + }, + "docs-rst/topology_selection/b11": { + "error": "SyntaxError: unmatched ')'", + "status": "error" + }, + "docs-rst/topology_selection/b12": { + "shapes": { + "bottom[0]": { + "area": 1.0, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + 0.5, + -0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "box": { + "area": 6.0, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 0.9999999999999998 + }, + "faces[0]": { + "area": 1.0, + "bbox": [ + -0.5, + -0.5, + -0.5, + -0.5, + 0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[1]": { + "area": 1.0, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + -0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[2]": { + "area": 1.0, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + 0.5, + -0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[3]": { + "area": 1.0, + "bbox": [ + -0.5, + -0.5, + 0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[4]": { + "area": 1.0, + "bbox": [ + -0.5, + 0.5, + -0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[5]": { + "area": 1.0, + "bbox": [ + 0.5, + -0.5, + -0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "sides[0]": { + "area": 1.0, + "bbox": [ + -0.5, + -0.5, + -0.5, + -0.5, + 0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "sides[1]": { + "area": 1.0, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + -0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "sides[2]": { + "area": 1.0, + "bbox": [ + -0.5, + 0.5, + -0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "sides[3]": { + "area": 1.0, + "bbox": [ + 0.5, + -0.5, + -0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "top[0]": { + "area": 1.0, + "bbox": [ + -0.5, + -0.5, + 0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "top_face": { + "area": 1.0, + "bbox": [ + -0.5, + -0.5, + 0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/topology_selection/b13": { + "error": "NameError: name 'ColorMap' is not defined", + "status": "error" + }, + "docs-rst/topology_selection/b14": { + "error": "NameError: name 'mesh_sphere' is not defined", + "status": "error" + }, + "docs-rst/topology_selection/b15": { + "error": "NameError: name 'part' is not defined. Did you mean: 'Part'?", + "status": "error" + }, + "docs-rst/tutorial_constraints/all": { + "error": "NameError: name 'ConstrainedArcs' is not defined", + "status": "error" + }, + "docs-rst/tutorial_constraints/b01": { + "error": "TypeError: Invalid tangency: Ellipsis", + "status": "error" + }, + "docs-rst/tutorial_constraints/b02": { + "error": "TypeError: Invalid tangency: Ellipsis", + "status": "error" + }, + "docs-rst/tutorial_constraints/b03": { + "shapes": { + "isosceles": { + "area": 389.71143170299746, + "bbox": [ + -14.999999999999996, + -8.660254037844386, + 0.0, + 15.000000000000004, + 17.32050807568877, + 0.0 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_constraints/b04": { + "status": "no-shapes" + }, + "docs-rst/tutorial_constraints/b05": { + "shapes": { + "comb": { + "area": 0.0, + "bbox": [ + -3.113873265052331, + -2.0854169758374566, + 0.0, + 2.0082720352407724, + 1.7138381555328697, + 0.0 + ], + "edges": 200, + "faces": 0, + "volume": 0.0 + }, + "connector": { + "area": 0.0, + "bbox": [ + -1.0415531356024228, + -0.6000000999999998, + -1e-07, + 0.40000009999999814, + 0.42635192233306957, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "m1": { + "area": 0.0, + "bbox": [ + -3.0, + 0.42635182233306956, + 0.0, + -1.0, + 1.6, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "m2": { + "area": 0.0, + "bbox": [ + 0.3999999, + -1.6189955196822747, + -1e-07, + 2.0000001, + 1e-07, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_constraints/b06": { + "shapes": { + "coincident_ex": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 2.0, + 2.0, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 1.0, + 2.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 1.0, + 2.0, + 0.0, + 2.0, + 2.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_constraints/b07": { + "shapes": { + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 1.0, + 1.0, + 0.0, + 1.2928932188134525, + 2.1297250429272467, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "tangent_ex": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 1.2928932188134525, + 2.1297250429272467, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_constraints/b08": { + "shapes": { + "l1": { + "area": 0.0, + "bbox": [ + 1.0606601717798214, + 0.0, + 0.0, + 1.5, + 1.0606601717798212, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 1.0606601717798214, + 1.0606601717798212, + 0.0, + 1.7677669529663689, + 1.7677669529663689, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "perpendicular_ex": { + "area": 0.0, + "bbox": [ + 1.0606601717798214, + 0.0, + 0.0, + 1.7677669529663689, + 1.7677669529663689, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_constraints/b09": { + "shapes": { + "c1": { + "area": 0.0, + "bbox": [ + -0.5999999999999989, + -4.638813987248042e-16, + 0.0, + 1.2, + 1.8, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "intersect_ex": { + "area": 0.0, + "bbox": [ + -0.2, + 0.1, + 0.0, + 1.1780141450153974, + 1.7052231276140966, + 0.0 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -0.2, + 0.1, + 0.0, + 1.1780141450153974, + 0.34298107356412183, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + -0.2, + 0.1, + 0.0, + 0.3842534378072289, + 1.7052231276140966, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 0.38425343780722687, + 0.34298107356412094, + 0.0, + 1.178014145015397, + 1.7052231276140926, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_constraints/b10": { + "shapes": { + "h1": { + "area": 0.0, + "bbox": [ + 2.0, + 0.0, + 0.0, + 2.828427124746121, + 0.999999999999951, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "p1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 0.4999999999999728, + 0.9999999999999728, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_constraints/b11": { + "shapes": { + "inside": { + "area": 0.0, + "bbox": [ + -1.5, + 0.0, + 0.0, + 1.5, + 1.000000014082108, + 0.0 + ], + "edges": 5, + "faces": 0, + "volume": 0.0 + }, + "perimeter": { + "area": 0.0, + "bbox": [ + -1.7, + 0.0, + 0.0, + 1.7, + 1.2000000140821079, + 0.0 + ], + "edges": 12, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_constraints/b12": { + "error": "TypeError: Invalid tangency: Ellipsis", + "status": "error" + }, + "docs-rst/tutorial_constraints/b13": { + "shapes": { + "c1": { + "area": 0.0, + "bbox": [ + -2.75, + -0.75, + 0.0, + -1.4254666676607668, + 0.75, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "c2": { + "area": 0.0, + "bbox": [ + 1.233955556881022, + -1.0, + 0.0, + 3.0, + 1.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "egg_plant": { + "area": 0.0, + "bbox": [ + -2.75, + -1.0, + 0.0, + 3.0, + 1.1748299085411977, + 0.0 + ], + "edges": 4, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -1.825520833333334, + -0.9378287848214448, + 0.0, + 1.6529017857142851, + -0.5648014937501156, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + -2.2529633620689657, + 0.7060520784267723, + 0.0, + 2.2220982142857144, + 1.1748299085411977, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + -2.75, + -0.75, + 0.0, + -1.825520833333335, + 0.7060520784267723, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 1.652901785714285, + -1.0, + 0.0, + 3.0, + 0.9750242987798288, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_constraints/b14": { + "error": "NameError: name 'tangency_one' is not defined", + "status": "error" + }, + "docs-rst/tutorial_constraints/b15": { + "error": "NameError: name 'tangency_one' is not defined", + "status": "error" + }, + "docs-rst/tutorial_constraints/b16": { + "error": "NameError: name 'tangency_one' is not defined", + "status": "error" + }, + "docs-rst/tutorial_constraints/b17": { + "error": "NameError: name 'tangency_one' is not defined", + "status": "error" + }, + "docs-rst/tutorial_constraints/b18": { + "error": "NameError: name 'tangency_one' is not defined", + "status": "error" + }, + "docs-rst/tutorial_constraints/b19": { + "error": "NameError: name 'tangency_one' is not defined", + "status": "error" + }, + "docs-rst/tutorial_constraints/b20": { + "error": "NameError: name 'tangency_one' is not defined", + "status": "error" + }, + "docs-rst/tutorial_constraints/b21": { + "error": "NameError: name 'tangency_one' is not defined", + "status": "error" + }, + "docs-rst/tutorial_constraints/b22": { + "error": "TypeError: Invalid tangency: Ellipsis", + "status": "error" + }, + "docs-rst/tutorial_constraints/b23": { + "error": "TypeError: Invalid tangency: Ellipsis", + "status": "error" + }, + "docs-rst/tutorial_constraints/b24": { + "status": "no-shapes" + }, + "docs-rst/tutorial_constraints/b25": { + "error": "NameError: name 'ImageFace' is not defined. Did you mean: 'make_face'?", + "status": "error" + }, + "docs-rst/tutorial_design/all": { + "error": "NameError: name 'BuildSketch' is not defined", + "status": "error" + }, + "docs-rst/tutorial_design/b01": { + "error": "NameError: name 'length' is not defined", + "status": "error" + }, + "docs-rst/tutorial_design/b02": { + "error": "NameError: name 'length' is not defined", + "status": "error" + }, + "docs-rst/tutorial_design/b03": { + "error": "NameError: name 'bracket' is not defined", + "status": "error" + }, + "docs-rst/tutorial_design/b04": { + "error": "NameError: name 'bracket' is not defined", + "status": "error" + }, + "docs-rst/tutorial_design/b05": { + "error": "NameError: name 'bracket' is not defined", + "status": "error" + }, + "docs-rst/tutorial_design/b06": { + "status": "no-shapes" + }, + "docs-rst/tutorial_design/b07": { + "shapes": { + "bracket": { + "area": 5192.145949565215, + "bbox": [ + -25.0, + 0.0, + -12.5, + 25.0, + 25.000000012324477, + 12.5 + ], + "edges": 66, + "faces": 24, + "volume": 6412.652585245836 + }, + "profile": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 25.0, + 25.000000012324477, + 0.0 + ], + "edges": 8, + "faces": 0, + "volume": 0.0 + }, + "sketch": { + "area": 272.9867224066874, + "bbox": [ + -25.0, + 0.0, + 0.0, + 25.0, + 25.000000012324477, + 0.0 + ], + "edges": 12, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_materials/all": { + "error": "SyntaxError: invalid syntax", + "status": "error" + }, + "docs-rst/tutorial_materials/b01": { + "error": "NameError: name 'box' is not defined. Did you mean: 'Box'?", + "status": "error" + }, + "docs-rst/tutorial_materials/b02": { + "error": "NameError: name 'box' is not defined. Did you mean: 'Box'?", + "status": "error" + }, + "docs-rst/tutorial_materials/b03": { + "error": "NameError: name 'metals' is not defined", + "status": "error" + }, + "docs-rst/tutorial_materials/b04": { + "error": "NameError: name 'hinge_inner' is not defined", + "status": "error" + }, + "docs-rst/tutorial_materials/b05": { + "status": "no-shapes" + }, + "docs-rst/tutorial_materials/b06": { + "error": "NameError: name 'hinge_inner' is not defined", + "status": "error" + }, + "docs-rst/tutorial_materials/b07": { + "error": "NameError: name 'hinge_inner' is not defined", + "status": "error" + }, + "docs-rst/tutorial_materials/b08": { + "error": "NameError: name 'hinge_inner' is not defined", + "status": "error" + }, + "docs-rst/tutorial_materials/b09": { + "error": "SyntaxError: invalid syntax", + "status": "error" + }, + "docs-rst/tutorial_materials/b10": { + "error": "NameError: name 'hinge_outer' is not defined", + "status": "error" + }, + "docs-rst/tutorial_materials/b11": { + "error": "NameError: name 'hinge_outer' is not defined", + "status": "error" + }, + "docs-rst/tutorial_materials/b12": { + "error": "NameError: name 'hinge_outer' is not defined", + "status": "error" + }, + "docs-rst/tutorial_materials/b13": { + "error": "NameError: name 'box' is not defined. Did you mean: 'Box'?", + "status": "error" + }, + "docs-rst/tutorial_materials/b14": { + "error": "NameError: name 'box' is not defined. Did you mean: 'Box'?", + "status": "error" + }, + "docs-rst/tutorial_materials/b15": { + "error": "NameError: name 'box' is not defined. Did you mean: 'Box'?", + "status": "error" + }, + "docs-rst/tutorial_materials/b16": { + "error": "ImportError: MaterialX is required to convert materials from a source, but it is not installed. Install it with: pip install threejs-materials[materialx]", + "status": "error" + }, + "docs-rst/tutorial_materials/b17": { + "error": "NameError: name 'box' is not defined. Did you mean: 'Box'?", + "status": "error" + }, + "docs-rst/tutorial_materials/b18": { + "error": "NameError: name 'hinge_outer' is not defined", + "status": "error" + }, + "docs-rst/tutorial_stl_reconstruction/all": { + "error": "lib3mf.Lib3MF.ELib3MFException: Lib3MFException 5: The specified file could not be opened", + "status": "error" + }, + "docs-rst/tutorial_stl_reconstruction/b01": { + "error": "lib3mf.Lib3MF.ELib3MFException: Lib3MFException 5: The specified file could not be opened", + "status": "error" + }, + "docs-rst/tutorial_stl_reconstruction/b02": { + "error": "ValueError: Could not import target_part_quarter.brep", + "status": "error" + }, + "docs-rst/tutorial_stl_reconstruction/b03": { + "shapes": { + "fillet_box": { + "area": 5.473628179866694, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 48, + "faces": 26, + "volume": 0.9755870138909416 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_stl_reconstruction/b04": { + "shapes": { + "c01": { + "area": 0.5026528139550687, + "bbox": [ + 0.30000040000000006, + 0.30000040000000006, + -0.4, + 0.4999996, + 0.4999996, + 0.4 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "c02": { + "area": 0.5026528139550687, + "bbox": [ + -0.4999996, + 0.30000040000000006, + -0.4, + -0.30000040000000006, + 0.4999996, + 0.4 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "c03": { + "area": 0.5026528139550687, + "bbox": [ + -0.4999996, + -0.4999996, + -0.4, + -0.30000040000000006, + -0.30000040000000006, + 0.4 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "c04": { + "area": 0.5026528139550687, + "bbox": [ + 0.30000040000000006, + -0.4999996, + -0.4, + 0.4999996, + -0.30000040000000006, + 0.4 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "c07": { + "area": 0.5026528139550687, + "bbox": [ + -0.4, + -0.4999996, + -0.4999996, + 0.4, + -0.30000040000000006, + -0.30000040000000006 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "c08": { + "area": 0.5026528139550687, + "bbox": [ + -0.4, + -0.4999996, + 0.30000040000000006, + 0.4, + -0.30000040000000006, + 0.4999996 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "c09": { + "area": 0.5026528139550687, + "bbox": [ + -0.4, + 0.30000040000000006, + -0.4999996, + 0.4, + 0.4999996, + -0.30000040000000006 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "c10": { + "area": 0.5026528139550687, + "bbox": [ + -0.4, + 0.30000040000000006, + 0.30000040000000006, + 0.4, + 0.4999996, + 0.4999996 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "c13": { + "area": 0.5026528139550687, + "bbox": [ + 0.30000040000000006, + -0.4, + -0.4999996, + 0.4999996, + 0.4, + -0.30000040000000006 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "c14": { + "area": 0.5026528139550687, + "bbox": [ + -0.4999996, + -0.4, + -0.4999996, + -0.30000040000000006, + 0.4, + -0.30000040000000006 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "c15": { + "area": 0.5026528139550687, + "bbox": [ + -0.4999996, + -0.4, + 0.30000040000000006, + -0.30000040000000006, + 0.4, + 0.4999996 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "c16": { + "area": 0.5026528139550687, + "bbox": [ + 0.30000040000000006, + -0.4, + 0.30000040000000006, + 0.4999996, + 0.4, + 0.4999996 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "r00": { + "area": 0.6400000000000001, + "bbox": [ + -0.4, + -0.4, + -0.5, + 0.4, + 0.4, + -0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "r05": { + "area": 0.6400000000000001, + "bbox": [ + -0.4, + -0.4, + 0.5, + 0.4, + 0.4, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "r06": { + "area": 0.6400000000000001, + "bbox": [ + -0.5, + -0.4, + -0.4, + -0.5, + 0.4, + 0.4 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "r11": { + "area": 0.6400000000000001, + "bbox": [ + 0.5, + -0.4, + -0.4, + 0.5, + 0.4, + 0.4 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "r12": { + "area": 0.6400000000000001, + "bbox": [ + -0.4, + -0.5, + -0.4, + 0.4, + -0.5, + 0.4 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "r17": { + "area": 0.6400000000000001, + "bbox": [ + -0.4, + 0.5, + -0.4, + 0.4, + 0.5, + 0.4 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "s18": { + "area": 0.12562098411518396, + "bbox": [ + 0.300016, + -0.499982, + 0.300043, + 0.499982, + -0.300016, + 0.500009 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s19": { + "area": 0.12562098411518396, + "bbox": [ + -0.499982, + 0.300016, + -0.500009, + -0.300016, + 0.499982, + -0.300043 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s20": { + "area": 0.12562098411518396, + "bbox": [ + -0.499982, + -0.499982, + -0.500009, + -0.300016, + -0.300016, + -0.300043 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s21": { + "area": 0.12562098411518396, + "bbox": [ + 0.300016, + 0.300016, + -0.500009, + 0.499982, + 0.499982, + -0.300043 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s22": { + "area": 0.12562098411518396, + "bbox": [ + -0.499982, + 0.300043, + 0.300016, + -0.300016, + 0.500009, + 0.499982 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s23": { + "area": 0.12562098411518396, + "bbox": [ + -0.499982, + -0.499982, + 0.300043, + -0.300016, + -0.300016, + 0.500009 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s24": { + "area": 0.12562098411518396, + "bbox": [ + 0.300016, + 0.300043, + 0.300016, + 0.499982, + 0.500009, + 0.499982 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s25": { + "area": 0.12562098411518396, + "bbox": [ + 0.300016, + -0.499982, + -0.500009, + 0.499982, + -0.300016, + -0.300043 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_stl_reconstruction/b05": { + "status": "no-shapes" + }, + "docs-selectors/filter_all_edges_circle": { + "shapes": { + "bl": { + "area": 0.0, + "bbox": [ + -13.61880215351701, + 0.0, + 0.0, + 13.618802153517008, + 8.0, + 0.0 + ], + "edges": 6, + "faces": 0, + "volume": 0.0 + }, + "f": { + "area": 455.5309347705202, + "bbox": [ + -48.5, + -21.0, + 25.0, + -14.5, + -21.0, + 59.0 + ], + "edges": 2, + "faces": 1, + "volume": 0.0 + }, + "faces[0]": { + "area": 455.5309347705202, + "bbox": [ + -48.5, + -21.0, + 25.0, + -14.5, + -21.0, + 59.0 + ], + "edges": 2, + "faces": 1, + "volume": 0.0 + }, + "faces[1]": { + "area": 455.5309347705202, + "bbox": [ + -48.5, + 21.0, + 25.0, + -14.5, + 21.0, + 59.0 + ], + "edges": 2, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 9.0, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 9.0, + 0.0, + 0.0, + 13.618802153517008, + 8.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 0.0, + 8.0, + 0.0, + 13.618802153517008, + 8.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "part": { + "area": 30243.24648019587, + "bbox": [ + -57.5000001, + -25.00000010000011, + -1e-07, + 57.5, + 25.0000001, + 68.0000001 + ], + "edges": 84, + "faces": 32, + "volume": 102198.22251481404 + }, + "s": { + "area": 4700.902664470767, + "bbox": [ + -57.5, + -25.0, + 0.0, + 57.5, + 25.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "s3": { + "area": 4931.716633826699, + "bbox": [ + -57.50000000000001, + -38.0, + 0.0, + -5.499999999999993, + 68.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "s4": { + "area": 180.9504172281361, + "bbox": [ + -13.61880215351701, + 0.0, + 0.0, + 13.618802153517008, + 8.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "zz": { + "area": 13119.78708349343, + "bbox": [ + -57.50000000000001, + -25.0, + -38.0, + -5.499999999999993, + -13.0, + 68.0 + ], + "edges": 12, + "faces": 6, + "volume": 59180.599605920404 + } + }, + "status": "ok" + }, + "docs-selectors/filter_axisplane": { + "shapes": { + "b": { + "area": 6.0, + "bbox": [ + 0.5, + 0.5, + -0.5, + 1.5, + 1.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 0.9999999999999998 + }, + "f[0]": { + "area": 1.0, + "bbox": [ + 0.5, + 0.5, + -0.5, + 0.5, + 1.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "f[1]": { + "area": 1.0, + "bbox": [ + 0.5, + 0.5, + -0.5, + 1.5, + 0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "f[2]": { + "area": 1.0, + "bbox": [ + 0.5, + 0.5, + -0.5, + 1.5, + 1.5, + -0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "f[3]": { + "area": 1.0, + "bbox": [ + 0.5, + 0.5, + 0.5, + 1.5, + 1.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "f[4]": { + "area": 1.0, + "bbox": [ + 0.5, + 1.5, + -0.5, + 1.5, + 1.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "f[5]": { + "area": 1.0, + "bbox": [ + 1.5, + 0.5, + -0.5, + 1.5, + 1.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "part": { + "area": 12.0, + "bbox": [ + -1.5, + -1.5, + -0.5, + 1.5, + 1.5, + 0.5 + ], + "edges": 24, + "faces": 12, + "volume": 1.9999999999999996 + }, + "plane_rep": { + "area": 3.965656732033233, + "bbox": [ + -1e-07, + -1e-07, + -1e-07, + 2.0000001, + 2.0000001, + 1e-07 + ], + "edges": 71, + "faces": 4, + "volume": 0.0 + }, + "res[0]": { + "area": 1.0, + "bbox": [ + 0.5, + 0.5, + -0.5, + 0.5, + 1.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "res[1]": { + "area": 1.0, + "bbox": [ + 0.5, + 0.5, + -0.5, + 1.5, + 0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "res[2]": { + "area": 1.0, + "bbox": [ + 0.5, + 1.5, + -0.5, + 1.5, + 1.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "res[3]": { + "area": 1.0, + "bbox": [ + 1.5, + 0.5, + -0.5, + 1.5, + 1.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-selectors/filter_geomtype": { + "shapes": { + "part": { + "area": 116.83185307179586, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 30, + "faces": 14, + "volume": 74.40707511102647 + } + }, + "status": "ok" + }, + "docs-selectors/filter_inner_wire_count": { + "shapes": { + "before_linear": { + "area": 6221.9841290626955, + "bbox": [ + -4.440892098501e-16, + -20.5, + -4.440892098501e-16, + 35.0, + 20.5, + 51.0 + ], + "edges": 120, + "faces": 42, + "volume": 7061.1553017856795 + }, + "bracket": { + "area": 6221.9841290626955, + "bbox": [ + -4.440892098501e-16, + -20.5, + -4.440892098501e-16, + 35.0, + 20.5, + 51.0 + ], + "edges": 120, + "faces": 42, + "volume": 7061.1553017856795 + }, + "e": { + "area": 0.0, + "bbox": [ + 30.0, + 12.5, + 3.0, + 30.0, + 15.5, + 3.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "f": { + "area": 31.1017672705369, + "bbox": [ + 0.0, + -17.15, + 43.85, + 3.0, + -13.85, + 47.15 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "faces[0]": { + "area": 1125.54033668342, + "bbox": [ + -4.440892098501e-16, + -20.5, + 3.0, + 0.0, + 20.5, + 51.0 + ], + "edges": 11, + "faces": 1, + "volume": 0.0 + }, + "faces[10]": { + "area": 1125.54033668342, + "bbox": [ + 3.0, + -20.5, + 3.0, + 3.0, + 20.5, + 51.0 + ], + "edges": 11, + "faces": 1, + "volume": 0.0 + }, + "faces[11]": { + "area": 9.0, + "bbox": [ + 7.75, + -15.5, + 0.0, + 7.75, + -12.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[12]": { + "area": 9.0, + "bbox": [ + 7.75, + 12.5, + 0.0, + 7.75, + 15.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[13]": { + "area": 21.2057504117325, + "bbox": [ + 7.75, + -17.75, + 0.0, + 12.25, + -15.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[14]": { + "area": 21.2057504117325, + "bbox": [ + 7.75, + -12.5, + 0.0, + 12.25, + -10.25, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[15]": { + "area": 21.2057504117325, + "bbox": [ + 7.75, + 10.25, + 0.0, + 12.25, + 12.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[16]": { + "area": 21.2057504117325, + "bbox": [ + 7.75, + 15.5, + 0.0, + 12.25, + 17.75, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[17]": { + "area": 9.0, + "bbox": [ + 12.25, + -15.5, + 0.0, + 12.25, + -12.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[18]": { + "area": 9.0, + "bbox": [ + 12.25, + 12.5, + 0.0, + 12.25, + 15.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[19]": { + "area": 235.06858347057704, + "bbox": [ + 0.0, + -20.5, + 0.0, + 33.0, + -20.5, + 49.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "faces[1]": { + "area": 8.485281374238564, + "bbox": [ + 0.0, + -20.5, + 49.0, + 3.0, + -18.5, + 51.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[20]": { + "area": 235.06858347057704, + "bbox": [ + 0.0, + 20.5, + 0.0, + 33.0, + 20.5, + 49.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "faces[21]": { + "area": 21.2057504117325, + "bbox": [ + 16.25, + -17.75, + 0.0, + 18.5, + -13.25, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[22]": { + "area": 21.2057504117325, + "bbox": [ + 16.25, + 13.25, + 0.0, + 18.5, + 17.75, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[23]": { + "area": 1131.5741231472102, + "bbox": [ + 3.0, + -20.5, + -4.440892098501e-16, + 35.0, + 20.5, + 0.0 + ], + "edges": 30, + "faces": 1, + "volume": 0.0 + }, + "faces[24]": { + "area": 1131.5741231472102, + "bbox": [ + 3.0, + -20.5, + 3.0, + 35.0, + 20.5, + 3.0 + ], + "edges": 30, + "faces": 1, + "volume": 0.0 + }, + "faces[25]": { + "area": 9.0, + "bbox": [ + 18.5, + -17.75, + 0.0, + 21.5, + -17.75, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[26]": { + "area": 9.0, + "bbox": [ + 18.5, + -13.25, + 0.0, + 21.5, + -13.25, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[27]": { + "area": 9.0, + "bbox": [ + 18.5, + 13.25, + 0.0, + 21.5, + 13.25, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[28]": { + "area": 9.0, + "bbox": [ + 18.5, + 17.75, + 0.0, + 21.5, + 17.75, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[29]": { + "area": 21.2057504117325, + "bbox": [ + 21.5, + -17.75, + 0.0, + 23.75, + -13.25, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[2]": { + "area": 31.1017672705369, + "bbox": [ + 0.0, + -17.15, + 12.85, + 3.0, + -13.85, + 16.15 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "faces[30]": { + "area": 21.2057504117325, + "bbox": [ + 21.5, + 13.25, + 0.0, + 23.75, + 17.75, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[31]": { + "area": 9.0, + "bbox": [ + 27.75, + -15.5, + 0.0, + 27.75, + -12.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[32]": { + "area": 9.0, + "bbox": [ + 27.75, + 12.5, + 0.0, + 27.75, + 15.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[33]": { + "area": 21.2057504117325, + "bbox": [ + 27.75, + -17.75, + 0.0, + 32.25, + -15.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[34]": { + "area": 21.2057504117325, + "bbox": [ + 27.75, + -12.5, + 0.0, + 32.25, + -10.25, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[35]": { + "area": 21.2057504117325, + "bbox": [ + 27.75, + 10.25, + 0.0, + 32.25, + 12.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[36]": { + "area": 21.2057504117325, + "bbox": [ + 27.75, + 15.5, + 0.0, + 32.25, + 17.75, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[37]": { + "area": 9.0, + "bbox": [ + 32.25, + -15.5, + 0.0, + 32.25, + -12.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[38]": { + "area": 9.0, + "bbox": [ + 32.25, + 12.5, + 0.0, + 32.25, + 15.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[39]": { + "area": 8.48528137423857, + "bbox": [ + 33.0, + -20.5, + 0.0, + 35.0, + -18.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[3]": { + "area": 31.1017672705369, + "bbox": [ + 0.0, + -17.15, + 43.85, + 3.0, + -13.85, + 47.15 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "faces[40]": { + "area": 8.485281374238571, + "bbox": [ + 33.0, + 18.5, + 0.0, + 35.0, + 20.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[41]": { + "area": 111.0, + "bbox": [ + 35.0, + -18.5, + 0.0, + 35.0, + 18.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[4]": { + "area": 193.20794819578495, + "bbox": [ + -4.440892098501e-16, + -20.5, + -4.440892098501e-16, + 3.0, + 20.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[5]": { + "area": 301.59289474460024, + "bbox": [ + 0.0, + -16.0, + 14.0, + 3.0, + 16.0, + 46.0 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "faces[6]": { + "area": 111.0, + "bbox": [ + 0.0, + -18.5, + 51.0, + 3.0, + 18.5, + 51.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[7]": { + "area": 31.1017672705369, + "bbox": [ + 0.0, + 13.85, + 12.85, + 3.0, + 17.15, + 16.15 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "faces[8]": { + "area": 31.1017672705369, + "bbox": [ + 0.0, + 13.85, + 43.85, + 3.0, + 17.15, + 47.15 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "faces[9]": { + "area": 8.48528137423857, + "bbox": [ + 0.0, + 18.5, + 49.0, + 3.0, + 20.5, + 51.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "motor_bore": { + "area": 0.0, + "bbox": [ + 3.0, + -16.0, + 14.0, + 3.0, + 16.0, + 46.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "motor_face": { + "area": 1125.54033668342, + "bbox": [ + 3.0, + -20.5, + 3.0, + 3.0, + 20.5, + 51.0 + ], + "edges": 11, + "faces": 1, + "volume": 0.0 + }, + "motor_mounts[0]": { + "area": 31.1017672705369, + "bbox": [ + 0.0, + -17.15, + 12.85, + 3.0, + -13.85, + 16.15 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "motor_mounts[1]": { + "area": 31.1017672705369, + "bbox": [ + 0.0, + -17.15, + 43.85, + 3.0, + -13.85, + 47.15 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "motor_mounts[2]": { + "area": 31.1017672705369, + "bbox": [ + 0.0, + 13.85, + 12.85, + 3.0, + 17.15, + 16.15 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "motor_mounts[3]": { + "area": 31.1017672705369, + "bbox": [ + 0.0, + 13.85, + 43.85, + 3.0, + 17.15, + 47.15 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "mount_face": { + "area": 1131.5741231472102, + "bbox": [ + 3.0, + -20.5, + 3.0, + 35.0, + 20.5, + 3.0 + ], + "edges": 30, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-selectors/filter_nested": { + "shapes": { + "b": { + "area": 2265.8254672326348, + "bbox": [ + -40.0, + -40.0, + 0.0, + -10.0, + -10.0, + 15.0 + ], + "edges": 45, + "faces": 18, + "volume": 2341.618734706661 + }, + "before": { + "area": 2265.8254672326348, + "bbox": [ + -15.0, + -15.0, + 0.0, + 15.0, + 15.0, + 15.0 + ], + "edges": 45, + "faces": 18, + "volume": 2341.6187347066616 + }, + "f[0]": { + "area": 670.0249018294472, + "bbox": [ + -40.0, + -40.0, + 0.0, + -10.0, + -10.0, + 0.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "f[1]": { + "area": 77.0437637608331, + "bbox": [ + -30.0, + -30.000000046931095, + 15.0, + -20.0, + -19.9999999530689, + 15.0 + ], + "edges": 12, + "faces": 1, + "volume": 0.0 + }, + "faces[0]": { + "area": 670.0249018294472, + "bbox": [ + -15.0, + -15.0, + 0.0, + 15.0, + 15.0, + 0.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "faces[1]": { + "area": 77.0437637608331, + "bbox": [ + -5.0, + -5.000000046931097, + 15.0, + 5.0, + 5.000000046931097, + 15.0 + ], + "edges": 12, + "faces": 1, + "volume": 0.0 + }, + "part": { + "area": 2246.754172807212, + "bbox": [ + -15.0, + -15.0, + -1.2434497875801753e-14, + 15.0, + 15.0, + 15.000000000000016 + ], + "edges": 77, + "faces": 34, + "volume": 2333.2025193287527 + } + }, + "status": "ok" + }, + "docs-selectors/filter_shape_properties": { + "shapes": { + "inside_fillets": { + "area": 146.0840583919254, + "bbox": [ + -8.0, + -8.0, + -0.5000000000000002, + 8.0, + 8.0, + 2.0 + ], + "edges": 32, + "faces": 12, + "volume": 0.0 + }, + "open_box": { + "area": 1291.7456844528895, + "bbox": [ + -10.0, + -10.0, + -2.5, + 10.0, + 10.0, + 2.5 + ], + "edges": 100, + "faces": 51, + "volume": 1253.6141621090958 + }, + "open_box_builder": { + "area": 1291.7456844528895, + "bbox": [ + -10.0, + -10.0, + -2.5, + 10.0, + 10.0, + 2.5 + ], + "edges": 100, + "faces": 51, + "volume": 1253.6141621090958 + }, + "outside_fillets": { + "area": 184.22799667532294, + "bbox": [ + -10.0, + -10.0, + -2.5, + 10.0, + 10.0, + 2.5 + ], + "edges": 72, + "faces": 28, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-selectors/group_axis": { + "shapes": { + "fins": { + "area": 1791.9999999999998, + "bbox": [ + -7.0, + -10.5, + 0.0, + 7.0, + 10.5, + 10.0 + ], + "edges": 192, + "faces": 96, + "volume": 960.0 + }, + "part": { + "area": 10329.486677646151, + "bbox": [ + -17.0, + -24.0, + -5.0, + 17.0, + 24.0, + 10.0 + ], + "edges": 1152, + "faces": 422, + "volume": 11953.646003293885 + }, + "without": { + "area": 10484.0, + "bbox": [ + -17.0, + -24.0, + -5.0, + 17.0, + 24.0, + 10.0 + ], + "edges": 768, + "faces": 294, + "volume": 12000.0 + } + }, + "status": "ok" + }, + "docs-selectors/group_hole_area": { + "shapes": { + "before": { + "area": 4761.5770231504675, + "bbox": [ + -10.0, + -40.0, + -10.000000000000004, + 10.0, + 40.0000001, + 10.000000000000004 + ], + "edges": 24, + "faces": 13, + "volume": 17229.357855935177 + }, + "part": { + "area": 4724.120330627758, + "bbox": [ + -10.0, + -40.0, + -10.000000000000004, + 10.0, + 40.0000001, + 10.000000000000004 + ], + "edges": 28, + "faces": 15, + "volume": 17213.91154780689 + }, + "s": { + "area": 119.6349540849362, + "bbox": [ + -2.5, + 25.5, + 0.0, + 2.5, + 50.5, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-selectors/group_properties_with_keys": { + "shapes": { + "after": { + "area": 5620.026752561991, + "bbox": [ + -45.0, + -32.0000001, + -22.0, + 5.0, + -7.999999899999999, + 5.000000000000002 + ], + "edges": 111, + "faces": 41, + "volume": 9447.96726971913 + }, + "after_fillet": { + "area": 5401.890853673273, + "bbox": [ + -5.0, + 8.0, + -5.0, + 45.0, + 32.0, + 22.0 + ], + "edges": 72, + "faces": 28, + "volume": 9730.739028031032 + }, + "after_holes": { + "area": 5620.026752561991, + "bbox": [ + -25.0, + -12.0000001, + -5.0, + 25.0, + 12.0000001, + 22.0 + ], + "edges": 111, + "faces": 41, + "volume": 9447.96726971913 + }, + "before": { + "area": 5471.658311786602, + "bbox": [ + -45.0, + -32.0, + -5.0, + 5.0, + -8.0, + 22.0 + ], + "edges": 48, + "faces": 20, + "volume": 9751.638840713078 + }, + "before_fillet": { + "area": 5471.658311786602, + "bbox": [ + -25.0, + -12.0, + -5.0, + 25.0, + 12.0, + 22.0 + ], + "edges": 48, + "faces": 20, + "volume": 9751.638840713076 + }, + "circle": { + "area": 0.0, + "bbox": [ + -15.999987650650775, + -12.0, + 12.015715814709198, + 1.8995269025627435, + -12.0, + 22.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "items[0]": { + "area": 5471.658311786602, + "bbox": [ + -25.0, + -12.0, + -5.0, + 25.0, + 12.0, + 22.0 + ], + "edges": 48, + "faces": 20, + "volume": 9751.638840713076 + }, + "part": { + "area": 5585.776511397532, + "bbox": [ + -5.0, + 7.9999999, + -22.0, + 45.0, + 32.0000001, + 5.000000000000002 + ], + "edges": 125, + "faces": 48, + "volume": 9432.271118481687 + }, + "pins": { + "area": 17.13716694115407, + "bbox": [ + -22.5, + -1.5, + 0.0, + 23.0, + 1.5, + 0.0 + ], + "edges": 5, + "faces": 2, + "volume": 0.0 + }, + "sketch": { + "area": 740.921953150644, + "bbox": [ + -25.0, + -5.0, + 0.0, + 25.0, + 22.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-selectors/selectors_operators": { + "shapes": { + "b": { + "area": 70.0, + "bbox": [ + 6.5, + 6.5, + -1.0, + 11.5, + 11.5, + 0.0 + ], + "edges": 12, + "faces": 6, + "volume": 24.999999999999993 + }, + "box": { + "area": 70.0, + "bbox": [ + -2.5, + -2.5, + -0.5, + 2.5, + 2.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 24.999999999999993 + }, + "c": { + "area": 50.265482457436676, + "bbox": [ + 7.0, + 7.0, + 0.0, + 11.0, + 11.0, + 2.0 + ], + "edges": 3, + "faces": 3, + "volume": 25.132741228718338 + }, + "circle": { + "area": 87.96459430051421, + "bbox": [ + -2.0, + -2.0, + -2.5, + 2.0, + 2.0, + 2.5 + ], + "edges": 3, + "faces": 3, + "volume": 62.83185307179585 + }, + "faces[0]": { + "area": 11.575222039230619, + "bbox": [ + 6.5, + 6.5, + 0.5, + 11.5, + 11.5, + 0.5 + ], + "edges": 9, + "faces": 1, + "volume": 0.0 + }, + "faces[1]": { + "area": 12.566370614359167, + "bbox": [ + 7.0, + 7.0, + 2.5, + 11.0, + 11.0, + 2.5 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "line": { + "area": 0.0, + "bbox": [ + -9.0, + -9.0, + 0.0, + 9.0, + 9.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "part": { + "area": 120.26548245743669, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 18, + "faces": 10, + "volume": 75.26548245743669 + }, + "part_copy": { + "area": 95.13274122871834, + "bbox": [ + 0.5, + 0.5, + -1.0, + 5.5, + 5.5, + 2.0 + ], + "edges": 15, + "faces": 8, + "volume": 50.132741228718345 + } + }, + "status": "ok" + }, + "docs-selectors/sort_along_wire": { + "shapes": { + "along_wire": { + "area": 1535.7853981634066, + "bbox": [ + -9.269029987990507e-12, + 0.0, + 0.0, + 48.0, + 48.0, + 0.0 + ], + "edges": 9, + "faces": 1, + "volume": 0.0 + }, + "v": { + "area": 0.0, + "bbox": [ + 0.0, + 48.0, + 0.0, + 0.0, + 48.0, + 0.0 + ], + "edges": 0, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-selectors/sort_axis": { + "shapes": { + "before": { + "area": 2717.805208455828, + "bbox": [ + 0.0, + 16.0, + 0.0, + 34.0, + 32.0, + 25.0 + ], + "edges": 30, + "faces": 12, + "volume": 3768.282996588303 + }, + "edge": { + "area": 0.0, + "bbox": [ + 34.0, + 16.0, + 0.0, + 34.0, + 16.0, + 4.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "f": { + "area": 110.83185284083245, + "bbox": [ + 34.01, + 16.0, + 0.0, + 34.01, + 32.0, + 25.0 + ], + "edges": 10, + "faces": 1, + "volume": 0.0 + }, + "face": { + "area": 110.83185284083245, + "bbox": [ + 34.0, + 16.0, + 0.0, + 34.0, + 32.0, + 25.0 + ], + "edges": 10, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 16.0, + 0.0, + 0.0, + 32.0, + 25.0, + 0.0 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 16.0, + 3.9999999813261358, + 0.0, + 28.0, + 15.0, + 0.0 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + }, + "part": { + "area": 3958.206057686804, + "bbox": [ + 0.0, + 16.0, + 0.0, + 50.0, + 32.0, + 25.0 + ], + "edges": 42, + "faces": 17, + "volume": 5585.161443960096 + }, + "profile": { + "area": 110.83185284083245, + "bbox": [ + 16.0, + 0.0, + 0.0, + 32.0, + 25.0, + 0.0 + ], + "edges": 10, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-selectors/sort_distance_from": { + "error": "NameError: name 'ColorMap' is not defined", + "status": "error" + }, + "docs-selectors/sort_sortby": { + "shapes": { + "box": { + "area": 149.99999999999997, + "bbox": [ + -8.5, + -8.5, + -2.5, + -3.5, + -3.5, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 124.99999999999997 + }, + "part": { + "area": 116.83185307179586, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 30, + "faces": 14, + "volume": 74.40707511102647 + }, + "solids[0]": { + "area": 149.99999999999997, + "bbox": [ + -8.5, + -8.5, + -2.5, + -3.5, + -3.5, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 124.99999999999997 + }, + "solids[1]": { + "area": 116.83185307179586, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 30, + "faces": 14, + "volume": 74.40707511102647 + }, + "solids[2]": { + "area": 78.5398163397448, + "bbox": [ + 3.5, + 3.5, + -2.5, + 8.5, + 8.5, + 2.5 + ], + "edges": 1, + "faces": 1, + "volume": 65.44984694978736 + }, + "sphere": { + "area": 78.5398163397448, + "bbox": [ + 3.5, + 3.5, + -2.5, + 8.5, + 8.5, + 2.5 + ], + "edges": 1, + "faces": 1, + "volume": 65.44984694978736 + } + }, + "status": "ok" + }, + "docs/center": { + "shapes": { + "bbox_symbol": { + "area": 16.0, + "bbox": [ + -2.0, + -2.0, + 0.0, + 2.0, + 2.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "geom_symbol": { + "area": 5.196152422706633, + "bbox": [ + -1.0000000000000009, + -1.732050807568877, + 0.0, + 2.0, + 1.7320508075688776, + 0.0 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "line": { + "area": 0.0, + "bbox": [ + -7.888609052210118e-31, + 7.105427357601002e-15, + 0.0, + 50.0, + 50.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "mass_symbol": { + "area": 12.566370614359167, + "bbox": [ + -2.0, + -2.0, + 0.0, + 2.0, + 2.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "triangle": { + "area": 932.6927922672327, + "bbox": [ + -23.205396671608757, + -13.39764201500537, + 0.0, + 23.205396671608742, + 26.795284030010716, + 0.0 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/constraint_examples": { + "error": "NameError: name 'ImageFace' is not defined. Did you mean: 'make_face'?", + "status": "error" + }, + "docs/heart_token": { + "shapes": { + "bottom_left_surface": { + "area": 107.53326858513432, + "bbox": [ + -9.982677224380025, + 0.0, + -2.525926762461026, + 3.1086244689504383e-15, + 16.572502608194313, + 4.822598771995576 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "bottom_right_surface": { + "area": 107.53326858513432, + "bbox": [ + -3.1086244689504383e-15, + 0.0, + -2.525926762461026, + 9.982677224380025, + 16.572502608194313, + 4.822598771995576 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "center": { + "area": 200.2097298862263, + "bbox": [ + -9.982677224380025, + 3.552713678800501e-15, + 0.0, + 9.982677224380025, + 16.572502608194313, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "heart": { + "area": 488.6292416342477, + "bbox": [ + -9.982677224380025, + 0.0, + -4.822598771995573, + 9.982677224380025, + 16.572502608194313, + 4.822598771995573 + ], + "edges": 20, + "faces": 10, + "volume": 555.8200293752507 + }, + "heart_half": { + "area": 0.0, + "bbox": [ + -2.664535259100376e-15, + 0.0, + 0.0, + 9.982677224380025, + 16.572502608194313, + 1.5 + ], + "edges": 4, + "faces": 0, + "volume": 0.0 + }, + "heart_token": { + "area": 1159.8438469488065, + "bbox": [ + -11.982677224380026, + -3.639822484774289, + -4.822598771995573, + 11.982677224380026, + 18.572502608194313, + 4.822598771995573 + ], + "edges": 56, + "faces": 24, + "volume": 1080.970639222408 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 3.552713678800501e-15, + 0.0, + 8.219755366121646, + 8.50061189794011, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 3.0673252791798786, + 8.50061189794011, + 0.0, + 9.982677224380025, + 16.572502608194313, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + -2.6645352591003757e-15, + 13.918086097615445, + 0.0, + 3.0673252791798786, + 15.869353274314575, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + -2.664535259100376e-15, + 0.0, + 0.0, + 3.944304526105059e-31, + 13.918086097615445, + 1.5 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "left_side": { + "area": 29.24807855086281, + "bbox": [ + -2.6645352591003757e-15, + 3.552713678800501e-15, + -0.5, + 9.982677224380025, + 16.572502608194313, + 0.5 + ], + "edges": 10, + "faces": 3, + "volume": 0.0 + }, + "left_wire": { + "area": 0.0, + "bbox": [ + -2.6645352591003757e-15, + 3.552713678800501e-15, + 0.0, + 9.982677224380025, + 16.572502608194313, + 0.0 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + }, + "outline": { + "area": 131.28762859224454, + "bbox": [ + -11.982677224380026, + -3.639822484774289, + 0.0, + 11.982677224380026, + 18.572502608194313, + 0.0 + ], + "edges": 12, + "faces": 1, + "volume": 0.0 + }, + "right_side": { + "area": 29.24807855086281, + "bbox": [ + -9.982677224380025, + 3.552713678800501e-15, + -0.5, + 3.1086244689504383e-15, + 16.572502608194313, + 0.5 + ], + "edges": 10, + "faces": 3, + "volume": 0.0 + }, + "top_left_surface": { + "area": 107.5332685851343, + "bbox": [ + -9.982677224380025, + 0.0, + -4.822598771995575, + 3.1086244689504383e-15, + 16.572502608194313, + 2.5259267624610304 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "top_right_surface": { + "area": 107.5332685851343, + "bbox": [ + -2.664535259100376e-15, + 0.0, + -4.822598771995575, + 9.982677224380025, + 16.572502608194313, + 2.5259267624610304 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/line_types": { + "status": "no-shapes" + }, + "docs/objects_1d": { + "shapes": { + "b0": { + "area": 0.0, + "bbox": [ + 16.999999900000013, + -188.0000001, + -1e-07, + 76.0000001, + -80.99999990000008, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "b1": { + "area": 0.0, + "bbox": [ + 16.9999999, + -120.88173612530014, + -1e-07, + 167.00000009999997, + -66.99999990000003, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "b2": { + "area": 0.0, + "bbox": [ + 31.999999900000056, + -67.0000001, + -1e-07, + 169.764206271366, + 29.78469938145792, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "b3": { + "area": 0.0, + "bbox": [ + -9.999993605115378e-08, + 17.9999999, + -1e-07, + 80.94095757252077, + 188.00000010000002, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "bezier_curve": { + "area": 0.0, + "bbox": [ + -0.3005197222022917, + -0.7739316599914514, + -1e-07, + 1.652805652127694, + 3.0000000999999923, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "center_arc": { + "area": 0.0, + "bbox": [ + 1.8369701987210297e-16, + 0.0, + 0.0, + 3.0, + 3.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "club_outline": { + "area": 0.0, + "bbox": [ + -169.764206271366, + -188.0000001, + -1e-07, + 169.764206271366, + 188.00000010000002, + 1e-07 + ], + "edges": 10, + "faces": 0, + "volume": 0.0 + }, + "dot": { + "area": 0.007853981633974483, + "bbox": [ + -0.05, + -0.05, + 0.0, + 0.05, + 0.05, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "double_tangent": { + "area": 0.0, + "bbox": [ + -1e-07, + 0.0, + -1e-07, + 10.000000100000003, + 10.00000010000001, + 1e-07 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + }, + "elliptical_center_arc": { + "area": 0.0, + "bbox": [ + 8.229256270464882e-16, + -7.731356645413403e-16, + 0.0, + 2.0, + 3.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "example_1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 2.0, + 1.0, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "example_2": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 2.0, + 1.0, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "example_3": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 2.0, + 1.0, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "example_4": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 2.0, + 1.0, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "example_5": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 5.0, + 4.5, + 0.0 + ], + "edges": 4, + "faces": 0, + "volume": 0.0 + }, + "example_6": { + "area": 71458.36040851026, + "bbox": [ + -169.764206271366, + -188.0000001, + -1e-07, + 169.764206271366, + 188.0000001, + 1e-07 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "example_7": { + "area": 5.107700537488835, + "bbox": [ + -0.09114388277661528, + -0.04114388277661479, + -0.1000001000000001, + 3.1000001000000057, + 3.389684097194619, + 0.10000010000000023 + ], + "edges": 7, + "faces": 5, + "volume": 0.25224086415950203 + }, + "example_7_path": { + "area": 0.0, + "bbox": [ + -4.440892098500626e-16, + 0.0, + -1e-07, + 3.0000001000000003, + 3.2906428477012213, + 1e-07 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + }, + "example_7_section": { + "area": 0.031415926535897934, + "bbox": [ + -0.1, + -0.1, + 0.0, + 0.1, + 0.1, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "example_8": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 0.0, + 5.0, + 4.5 + ], + "edges": 4, + "faces": 0, + "volume": 0.0 + }, + "filletpolyline": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 10.0, + 5.0, + 20.0 + ], + "edges": 5, + "faces": 0, + "volume": 0.0 + }, + "helix": { + "area": 0.0, + "bbox": [ + -1.0000001000089513, + -1.0000001000089516, + -1.0000000005551115e-07, + 1.0000001000089511, + 1.0000001000089505, + 3.0000000999999954 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "intersecting_line": { + "area": 0.0, + "bbox": [ + 1.0, + 0.0, + 0.0, + 1.9999999999999996, + 0.9999999999999997, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "jern_arc": { + "area": 0.0, + "bbox": [ + 1.0, + 1.0, + 0.0, + 2.1055728090000843, + 3.980324517747254, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l0": { + "area": 0.0, + "bbox": [ + 0.0, + -188.0, + 0.0, + 76.0, + -188.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 0.0, + 5.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + -1e-07, + 6.967844587866777, + -1e-07, + 10.000000100000001, + 10.000000100000006, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 1.242000349090623, + 0.0, + 0.0, + 5.999999999999999, + 9.069002744142319, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 4.5, + 0.0, + 4.5, + 4.5 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "line": { + "area": 0.0, + "bbox": [ + 1.0, + 1.0, + 0.0, + 3.0, + 3.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "other": { + "area": 0.0, + "bbox": [ + 2.0, + 0.0, + 0.0, + 2.0, + 2.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "polar_line": { + "area": 0.0, + "bbox": [ + 1.0, + 1.0, + 0.0, + 2.25, + 3.1650635094610964, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "polyline": { + "area": 0.0, + "bbox": [ + 1.0, + 1.0, + 0.0, + 3.0, + 3.0, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "radius_arc": { + "area": 0.0, + "bbox": [ + 1.0, + 1.0, + 0.0, + 3.0, + 3.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "sagitta_arc": { + "area": 0.0, + "bbox": [ + 1.0, + 1.0, + 0.0, + 3.0, + 2.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "scene": { + "area": 0.0, + "bbox": [ + -0.17000000000000004, + -0.17000000000000004, + -1e-07, + 10.0, + 5.0, + 20.0 + ], + "edges": 27, + "faces": 0, + "volume": 0.0 + }, + "spline": { + "area": 0.0, + "bbox": [ + 0.9999998999999999, + 0.9999999, + -1e-07, + 2.1179608811676958, + 3.0000000999999985, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "tangent_arc": { + "area": 0.0, + "bbox": [ + 1.0, + 1.0, + 0.0, + 3.0, + 3.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "three_point_arc": { + "area": 0.0, + "bbox": [ + 1.0, + 1.0, + 0.0, + 2.999999999999999, + 3.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/objects_1d_airfoil": { + "shapes": { + "airfoil": { + "area": 0.0, + "bbox": [ + -0.0003477461814230394, + -0.0453617673637853, + -1e-07, + 1.0000839797955396, + 0.08473140414780772, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -0.0003477461814230394, + -0.0453617673637853, + -1e-07, + 1.0000839797955396, + 0.08473140414780772, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/objects_1d_blend_curve": { + "shapes": { + "blend_curve": { + "area": 0.0, + "bbox": [ + -3.5355339059327386, + -11.0000001, + -1e-07, + 5.0000001, + 5.0, + 1e-07 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -3.5355339059327386, + 1.2246467991473533e-15, + 0.0, + 5.0, + 5.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + -3.0000001000000003, + -11.0000001, + -1e-07, + 1e-07, + -4.9999999, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + -1.0000000088817842e-07, + -5.000000100000002, + -1e-07, + 5.0000001, + 1.000000012246468e-07, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/objects_1d_bspline": { + "shapes": { + "dot": { + "area": 0.007853981633974483, + "bbox": [ + -0.05, + -0.05, + 0.0, + 0.05, + 0.05, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "spline": { + "area": 0.0, + "bbox": [ + -1e-07, + -1e-07, + -1e-07, + 5.0000001, + 1.777777877777778, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/objects_1d_constrained": { + "shapes": { + "a1": { + "area": 0.0, + "bbox": [ + -1.34372973372799, + -1.9999392011839694, + 0.0, + 5.590594600591985, + 3.5751215976320596, + 0.0 + ], + "edges": 8, + "faces": 0, + "volume": 0.0 + }, + "arcs": { + "area": 0.0, + "bbox": [ + -1.5, + -2.0, + 0.0, + 6.0, + 3.5751215976320596, + 0.0 + ], + "edges": 32, + "faces": 0, + "volume": 0.0 + }, + "c1": { + "area": 0.0, + "bbox": [ + 2.0, + -2.0, + 0.0, + 6.0, + 2.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "c2": { + "area": 0.0, + "bbox": [ + -1.5, + 0.5, + 0.0, + 1.5, + 3.5, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "dot": { + "area": 0.007853981633974483, + "bbox": [ + -0.05, + -0.05, + 0.0, + 0.05, + 0.05, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -0.8166145812986692, + -1.677638883463118, + 0.0, + 4.6888194417315585, + 3.408229162597338, + 0.0 + ], + "edges": 4, + "faces": 0, + "volume": 0.0 + }, + "lines": { + "area": 0.0, + "bbox": [ + -1.5, + -2.0, + 0.0, + 6.0, + 3.5, + 0.0 + ], + "edges": 14, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/objects_1d_ellipticalstartarc": { + "shapes": { + "a": { + "area": 0.0, + "bbox": [ + -1.2181233730207475, + -0.7122930923219757, + 0.0, + 1.0000000000000002, + 2.602926396120729, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "arcs": { + "area": 0.0, + "bbox": [ + -1.2181233730207475, + -0.7122930923219757, + 0.0, + 1.0000000000000002, + 2.602926396120729, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "d": { + "area": 0.0, + "bbox": [ + -0.14354374979373108, + -0.34534558799262466, + 0.0, + -0.04548568222463907, + 0.1449447498528354, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "dot": { + "area": 0.007853981633974483, + "bbox": [ + -0.05, + -0.05, + 0.0, + 0.05, + 0.05, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/objects_1d_parabolic_hyperbolic": { + "shapes": { + "dot": { + "area": 0.007853981633974483, + "bbox": [ + -0.05, + -0.05, + 0.0, + 0.05, + 0.05, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "hyperbolic_center_arc": { + "area": 0.0, + "bbox": [ + -1.1506494511536471, + 1.0, + 0.0, + 1.1506494511536476, + 2.5091784786580567, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "parabolic_center_arc": { + "area": 0.0, + "bbox": [ + 0.0, + -1.0471975511965976, + 0.0, + 1.0966227112321507, + 1.0471975511965976, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/objects_2d": { + "shapes": { + "a1": { + "area": 0.0, + "bbox": [ + -13.125000000000002, + -12.360330811826104, + 0.0, + -10.0, + -7.725206757391314, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "a2": { + "area": 0.0, + "bbox": [ + 10.0, + -12.360330811826104, + 0.0, + 13.125, + -7.725206757391315, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "a3": { + "area": 0.0, + "bbox": [ + -1.8750000000000036, + 19.720661623652212, + 0.0, + 1.8749999999999947, + 20.085537569217422, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "align": { + "area": 0.20378540262707984, + "bbox": [ + -1.0, + -1.0, + -1e-07, + 1.0, + 1.0, + 1e-07 + ], + "edges": 1497, + "faces": 73, + "volume": 0.0 + }, + "arc": { + "area": 0.0, + "bbox": [ + 0.7071067811865476, + 0.0, + 0.0, + 1.0, + 0.7071067811865475, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "arrow": { + "area": 31.884992221173, + "bbox": [ + 96.66666666666669, + 0.0, + 0.0, + 103.33333333333333, + 17.451641855526496, + 0.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "arrow_head": { + "area": 567.3233268822228, + "bbox": [ + -47.936741552969245, + -14.117980750193544, + 0.0, + 1.0658141036401503e-14, + 14.117980750193544, + 0.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "arrow_heads[0]": { + "area": 583.6891229404811, + "bbox": [ + -50.0, + -16.666666666666657, + 0.0, + 1.0658141036401503e-14, + 16.666666666666657, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "arrow_heads[1]": { + "area": 833.3333333333335, + "bbox": [ + -50.00000000000001, + -16.666666666666668, + 0.0, + 7.105427357601002e-15, + 16.66666666666667, + 0.0 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "arrow_heads[2]": { + "area": 567.3233268822228, + "bbox": [ + -47.936741552969245, + -14.117980750193544, + 0.0, + 1.0658141036401503e-14, + 14.117980750193544, + 0.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "c": { + "area": 6361.725123519331, + "bbox": [ + -45.0, + -45.0, + 0.0, + 45.0, + 45.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "circle_with_hole": { + "area": 9709.733552923255, + "bbox": [ + -60.0, + -60.0, + 0.0, + 60.0, + 60.0, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "controller": { + "area": 33184.47090241657, + "bbox": [ + -30.000000000000004, + -4.440892098500626e-16, + -4.440892098500626e-16, + 30.000000000000004, + 40.0, + 80.0 + ], + "edges": 156, + "faces": 76, + "volume": 16509.291500220876 + }, + "d_line": { + "area": 3695.8989384292922, + "bbox": [ + -50.0, + -50.0, + -1e-07, + 50.0, + 50.0, + 1e-07 + ], + "edges": 64, + "faces": 10, + "volume": 0.0 + }, + "display": { + "area": 1209.1327411168006, + "bbox": [ + -23.5, + -18.5, + 0.0, + 23.5, + 18.5, + 0.0 + ], + "edges": 12, + "faces": 5, + "volume": 0.0 + }, + "display_face": { + "area": 2276.5888971673244, + "bbox": [ + -27.000000000000004, + 1.211145618000168, + 40.186223258500554, + 27.000000000000004, + 20.06524758424986, + 77.89442719099992 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "dot": { + "area": 0.007853981633974483, + "bbox": [ + -0.05, + -0.05, + 0.0, + 0.05, + 0.05, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "e_line": { + "area": 6055.834951872848, + "bbox": [ + -52.0, + -49.75000000000002, + -1e-07, + 51.8300049828125, + 49.75000000000001, + 1e-07 + ], + "edges": 129, + "faces": 19, + "volume": 0.0 + }, + "example_1": { + "area": 3.141592653589792, + "bbox": [ + -1.0, + -1.0, + 0.0, + 1.0, + 1.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "example_10": { + "area": 0.2365873852123405, + "bbox": [ + -0.5, + -0.125, + 0.0, + 0.5, + 0.125, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "example_11": { + "area": 0.3661889620534203, + "bbox": [ + -0.7470004255208335, + -0.27300145294596356, + -1e-07, + 0.7550017276041666, + 0.4180013512207032, + 1e-07 + ], + "edges": 46, + "faces": 4, + "volume": 0.0 + }, + "example_12": { + "area": 1.7920243386146189, + "bbox": [ + -1.0000001, + -0.5000001, + -1e-07, + 1.0000001, + 0.5000001, + 1e-07 + ], + "edges": 12, + "faces": 5, + "volume": 0.0 + }, + "example_2": { + "area": 4.712388980384689, + "bbox": [ + -1.5, + -1.0, + 0.0, + 1.5, + 1.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "example_3": { + "area": 2.2041946960967733, + "bbox": [ + -1.5, + -1.4265847744427302, + 0.0, + 1.2135254915624212, + 1.4265847744427305, + 0.0 + ], + "edges": 10, + "faces": 1, + "volume": 0.0 + }, + "example_4": { + "area": 2.0, + "bbox": [ + -1.0, + -0.5, + 0.0, + 1.0, + 0.5, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "example_5": { + "area": 1.946349383472889, + "bbox": [ + -1.0, + -0.500000004693107, + 0.0, + 1.0, + 0.500000004693107, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "example_6": { + "area": 2.598076211353316, + "bbox": [ + -1.0, + -0.8660254037844386, + 0.0, + 1.0, + 0.8660254037844387, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "example_7": { + "area": 0.2454369260617028, + "bbox": [ + 0.5821067811865476, + -0.125, + 0.0, + 1.125, + 0.8321067811865475, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "example_8": { + "area": 0.5490873852123406, + "bbox": [ + -0.125, + -1.125, + 0.0, + 0.125, + 1.125, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "example_9": { + "area": 0.2990873852123405, + "bbox": [ + -0.1250000000000001, + -0.625, + 0.0, + 0.1250000000000001, + 0.625, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "isosceles_triangle": { + "area": 581.0722922878593, + "bbox": [ + -15.05, + -20.360330811826103, + -1e-07, + 22.434948587095644, + 27.767012864196612, + 1e-07 + ], + "edges": 82, + "faces": 7, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -40.0, + -40.0, + 0.0, + 20.0, + 40.0, + 0.0 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + }, + "outside_curve": { + "area": 0.0, + "bbox": [ + 19.999999999999996, + -39.99999999999999, + 0.0, + 39.999999999999986, + 40.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "p1": { + "area": 0.0, + "bbox": [ + -12.000000000000002, + -12.360330811826104, + 0.0, + -7.0, + -4.9441323247304405, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "p2": { + "area": 0.0, + "bbox": [ + 7.0, + -12.360330811826103, + 0.0, + 12.0, + -4.944132324730441, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "p3": { + "area": 0.0, + "bbox": [ + -3.0000000000000036, + 16.720661623652212, + 0.0, + 2.999999999999994, + 17.30446313655655, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "profile": { + "area": 2800.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 40.0, + 80.0, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "t": { + "area": 556.2148865321747, + "bbox": [ + -15.0, + -12.360330811826104, + 0.0, + 15.0, + 24.720661623652212, + 0.0 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "t1": { + "area": 5.136676608677263, + "bbox": [ + -9.626752146893367, + -9.138195211139674, + -1e-07, + -6.906748691685034, + -5.493191755931341, + 1e-07 + ], + "edges": 25, + "faces": 1, + "volume": 0.0 + }, + "t2": { + "area": 3.8481447407603273, + "bbox": [ + 6.794248691685033, + -9.25320172155634, + -1e-07, + 9.939252146893367, + -5.4331982663480085, + 1e-07 + ], + "edges": 22, + "faces": 1, + "volume": 0.0 + }, + "t3": { + "area": 3.9716762777169525, + "bbox": [ + -1.5899983723958384, + 15.470661369339062, + 0.0, + 1.5899983723958284, + 19.115664624547396, + 0.0 + ], + "edges": 11, + "faces": 1, + "volume": 0.0 + }, + "tech_drawing": { + "area": 7398.438377480315, + "bbox": [ + -143.5, + -99.23001362945963, + -1e-07, + 143.5, + 101.40999308095702, + 1e-07 + ], + "edges": 1140, + "faces": 133, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/objects_3d": { + "shapes": { + "example_1": { + "area": 22.0, + "bbox": [ + -1.5, + -1.0, + -0.5, + 1.5, + 1.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 6.0 + }, + "example_10": { + "area": 5776.661211217315, + "bbox": [ + -30.0, + -15.0, + -15.0, + 15.0, + 15.0, + 15.0 + ], + "edges": 48, + "faces": 26, + "volume": 33876.666666666664 + }, + "example_2": { + "area": 36.78240746107114, + "bbox": [ + -2.0, + -2.0, + -1.0000000000000002, + 2.0, + 2.0, + 1.0000000000000002 + ], + "edges": 3, + "faces": 3, + "volume": 14.660765716752369 + }, + "example_3": { + "area": 23.759291886010285, + "bbox": [ + -1.5, + -1.0, + -0.5, + 1.5, + 1.0, + 0.5 + ], + "edges": 18, + "faces": 10, + "volume": 5.69840710525538 + }, + "example_4": { + "area": 23.03949299782655, + "bbox": [ + -1.5, + -1.0, + -0.5, + 1.5, + 1.0, + 0.5 + ], + "edges": 17, + "faces": 9, + "volume": 5.848353449142262 + }, + "example_5": { + "area": 18.849555921538755, + "bbox": [ + -1.0, + -1.0, + -1.0, + 1.0, + 1.0, + 1.0 + ], + "edges": 3, + "faces": 3, + "volume": 6.283185307179585 + }, + "example_6": { + "area": 23.5079644737231, + "bbox": [ + -1.5, + -1.0, + -0.5, + 1.5, + 1.0, + 0.5 + ], + "edges": 15, + "faces": 7, + "volume": 5.497345175425633 + }, + "example_7": { + "area": 9.42477796076938, + "bbox": [ + -1.0, + -1.0, + -0.5, + 1.0, + 1.0, + 0.5 + ], + "edges": 2, + "faces": 2, + "volume": 2.0943951023931957 + }, + "example_8": { + "area": 7.895683520871486, + "bbox": [ + -1.2000001, + -1.2000001, + -0.20000010000000001, + 1.2000001, + 1.2000001, + 0.20000010000000001 + ], + "edges": 2, + "faces": 1, + "volume": 0.7895683520871484 + }, + "example_9": { + "area": 4.427050983124842, + "bbox": [ + -0.5, + -0.5000000000000001, + -0.5, + 0.5, + 0.5000000000000001, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 0.5833333333333333 + } + }, + "status": "ok" + }, + "docs/pack_demo": { + "shapes": { + "b1": { + "area": 60000.0, + "bbox": [ + -50.0, + -50.0, + 0.0, + 50.0, + 50.0, + 100.0 + ], + "edges": 12, + "faces": 6, + "volume": 999999.9999999999 + }, + "b2": { + "area": 17496.0, + "bbox": [ + -27.0, + -27.0, + -54.0, + 27.0, + 27.0, + 0.0 + ], + "edges": 12, + "faces": 6, + "volume": 157464.0 + }, + "b3": { + "area": 6936.0, + "bbox": [ + 0.0, + 0.0, + -17.0, + 34.0, + 34.0, + 17.0 + ], + "edges": 12, + "faces": 6, + "volume": 39304.0 + }, + "b4": { + "area": 3456.0, + "bbox": [ + -24.0, + -24.0, + -12.0, + 0.0, + 0.0, + 12.0 + ], + "edges": 12, + "faces": 6, + "volume": 13824.0 + }, + "xy_pack[0]": { + "area": 3456.0, + "bbox": [ + 0.0, + 105.0, + -12.0, + 24.0, + 129.0, + 12.0 + ], + "edges": 12, + "faces": 6, + "volume": 13824.0 + }, + "xy_pack[1]": { + "area": 60000.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 100.0, + 100.0, + 100.0 + ], + "edges": 12, + "faces": 6, + "volume": 999999.9999999999 + }, + "xy_pack[2]": { + "area": 6936.0, + "bbox": [ + 105.0, + 59.0, + -17.0, + 139.0, + 93.0, + 17.0 + ], + "edges": 12, + "faces": 6, + "volume": 39304.0 + }, + "xy_pack[3]": { + "area": 17496.0, + "bbox": [ + 105.0, + 0.0, + -54.0, + 159.0, + 54.0, + 0.0 + ], + "edges": 12, + "faces": 6, + "volume": 157464.0 + }, + "z_pack[0]": { + "area": 3456.0, + "bbox": [ + 0.0, + 105.0, + 0.0, + 24.0, + 129.0, + 24.0 + ], + "edges": 12, + "faces": 6, + "volume": 13824.0 + }, + "z_pack[1]": { + "area": 60000.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 100.0, + 100.0, + 100.0 + ], + "edges": 12, + "faces": 6, + "volume": 999999.9999999999 + }, + "z_pack[2]": { + "area": 6936.0, + "bbox": [ + 105.0, + 59.0, + 0.0, + 139.0, + 93.0, + 34.0 + ], + "edges": 12, + "faces": 6, + "volume": 39304.0 + }, + "z_pack[3]": { + "area": 17496.0, + "bbox": [ + 105.0, + 0.0, + 0.0, + 159.0, + 54.0, + 54.0 + ], + "edges": 12, + "faces": 6, + "volume": 157464.0 + } + }, + "status": "ok" + }, + "docs/rigid_joints_pipe": { + "error": "ModuleNotFoundError: No module named 'bd_warehouse'", + "status": "error" + }, + "docs/rod_end": { + "error": "ModuleNotFoundError: No module named 'bd_warehouse'", + "status": "error" + }, + "docs/selector_example": { + "shapes": { + "example": { + "area": 923.6670838177648, + "bbox": [ + -10.0, + -10.0, + -1.5, + 10.0, + 10.0, + 1.5 + ], + "edges": 26, + "faces": 13, + "volume": 775.2146467682759 + } + }, + "status": "ok" + }, + "docs/slide_latch": { + "shapes": { + "end": { + "area": 419.9999999999999, + "bbox": [ + 35.0, + -15.0, + -7.0, + 35.0, + 15.0, + 7.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "l4": { + "area": 2460.5132658689467, + "bbox": [ + -35.00000000000001, + -25.0, + 0.0, + 35.00000000000001, + 25.0, + 0.0 + ], + "edges": 16, + "faces": 1, + "volume": 0.0 + }, + "latch": { + "area": 12154.798699572615, + "bbox": [ + -35.00000000000001, + -25.0, + -7.0, + 35.00000000000001, + 25.0, + 7.0 + ], + "edges": 138, + "faces": 52, + "volume": 11831.250489574682 + }, + "s1": { + "area": 241.76714429538606, + "bbox": [ + -4.750000000000072, + -12.75, + 0.0, + 4.749999938342812, + 12.75, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "s2": { + "area": 63.283185307179565, + "bbox": [ + 0.0, + -7.5, + 0.0, + 14.000000000000002, + 0.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "slide": { + "area": 5445.343408542543, + "bbox": [ + -8.0000001, + -12.750000100000001, + -4.750000100000072, + 58.00000010000001, + 12.750000100000003, + 14.000000099999998 + ], + "edges": 56, + "faces": 31, + "volume": 16765.45878762745 + }, + "slide_hole": { + "area": 259.1415925302756, + "bbox": [ + -5.000000000000072, + -13.0, + 0.0, + 5.0, + 13.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/spitfire_wing_gordon": { + "shapes": { + "airfoil_root": { + "area": 0.0, + "bbox": [ + -1e-07, + -762.9757386374004, + -127.31659188993615, + 1e-07, + 2044.9354255245626, + 237.81535145165188 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "airfoil_tip": { + "area": 0.0, + "bbox": [ + 5587.9999999, + -72.41933657704821, + -1.6342193604474813, + 5588.0000001, + 194.3153584753745, + 11.939555252102151 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "leading_edge": { + "area": 0.0, + "bbox": [ + -1.0311648513500628e-12, + -761.9999999999999, + 0.0, + 5613.4, + -1.866361721900566e-13, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "leading_pnt": { + "area": 0.0, + "bbox": [ + 5588.000000000007, + -72.40723981898407, + 0.0, + 5588.000000000007, + -72.40723981898407, + 0.0 + ], + "edges": 0, + "faces": 0, + "volume": 0.0 + }, + "trailing_edge": { + "area": 0.0, + "bbox": [ + 3.437216171166876e-13, + 0.0, + 0.0, + 5613.4, + 2044.6999999999998, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "trailing_pnt": { + "area": 0.0, + "bbox": [ + 5588.000000000006, + 194.29276018094413, + 0.0, + 5588.000000000006, + 194.29276018094413, + 0.0 + ], + "edges": 0, + "faces": 0, + "volume": 0.0 + }, + "wing": { + "area": 25739616.165786173, + "bbox": [ + -1.000070319180606e-07, + -763.1497142918938, + -127.31667772465318, + 5613.400000101416, + 2044.700000500986, + 237.81569027837008 + ], + "edges": 2, + "faces": 2, + "volume": 1987994598.9439144 + }, + "wing_root": { + "area": 699009.5088700533, + "bbox": [ + -1.0000113626453102e-07, + -763.1497142918938, + -127.31667772465318, + 1.0000034106051316e-07, + 2044.700000500986, + 237.81569027837008 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "wing_surface": { + "area": 25040606.65691612, + "bbox": [ + -1.000070319180606e-07, + -763.1497142918905, + -127.31667772462875, + 5613.400000101416, + 2044.700000500986, + 237.81569027834476 + ], + "edges": 2, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/technical_drawing": { + "error": "ModuleNotFoundError: No module named 'bd_warehouse'", + "status": "error" + }, + "docs/tutorial_joints": { + "shapes": { + "box": { + "area": 400755.84067435225, + "bbox": [ + -150.0, + -150.0, + 0.0, + 150.0, + 150.0, + 100.0 + ], + "edges": 45, + "faces": 17, + "volume": 1940751.7699835314 + }, + "box_assembly": { + "area": 620900.3194944883, + "bbox": [ + -160.0000001, + -150.0, + 0.0, + 150.0, + 150.0000000000001, + 260.4903810567665 + ], + "edges": 188, + "faces": 77, + "volume": 2868656.424929345 + }, + "box_builder": { + "area": 400755.84067435225, + "bbox": [ + -150.0, + -150.0, + -50.0, + 150.0, + 150.0, + 50.0 + ], + "edges": 45, + "faces": 17, + "volume": 1940751.7699835314 + }, + "hinge_inner": { + "area": 12442.9869076802, + "bbox": [ + -160.00000000000003, + -60.0, + 90.00000000000001, + -117.52885682970023, + 60.000000000000014, + 121.8301270189222 + ], + "edges": 53, + "faces": 21, + "volume": 12636.047054068478 + }, + "hinge_outer": { + "area": 15305.651238102755, + "bbox": [ + -160.0000001, + -64.0, + 49.99999999999999, + -150.0, + 60.0, + 100.00000010000001 + ], + "edges": 69, + "faces": 30, + "volume": 16116.837908214178 + }, + "lid": { + "area": 192395.84067435237, + "bbox": [ + -158.1698729810779, + -150.0, + 101.83012701892221, + 106.6377481542539, + 150.0000000000001, + 260.4903810567665 + ], + "edges": 21, + "faces": 9, + "volume": 899151.7699835307 + }, + "lid_builder": { + "area": 192395.84067435237, + "bbox": [ + -158.1698729810779, + -150.0, + 101.83012701892221, + 106.6377481542539, + 150.0000000000001, + 260.4903810567665 + ], + "edges": 21, + "faces": 9, + "volume": 899151.7699835307 + }, + "m6_screw": { + "area": 465.63400042563006, + "bbox": [ + -157.0000001, + -45.18180204846601, + 64.818197951534, + -144.9999999, + -34.818197951534, + 75.181802048466 + ], + "edges": 72, + "faces": 28, + "volume": 332.2762644199616 + } + }, + "status": "ok" + }, + "examples/bicycle_tire": { + "shapes": { + "build_profile": { + "area": 0.0, + "bbox": [ + -20.000180988089184, + -1e-07, + -1e-07, + 20.000180988089184, + 46.800846774556284, + 1e-07 + ], + "edges": 38, + "faces": 0, + "volume": 0.0 + }, + "half_road_surface": { + "area": 37520.822625203444, + "bbox": [ + -1e-07, + -370.0000001, + -370.0000001, + 15.130000099999998, + 370.0000001, + 370.0000001 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "l00": { + "area": 0.0, + "bbox": [ + -1e-07, + -1e-07, + -1e-07, + 15.130000099999998, + 4.5400000999999985, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l01": { + "area": 0.0, + "bbox": [ + 15.1299999, + 4.5399999, + -1e-07, + 16.5000001, + 6.2300001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l02": { + "area": 0.0, + "bbox": [ + 16.4999999, + 6.2299999, + -1e-07, + 19.940000100000002, + 20.060000099999996, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l03": { + "area": 0.0, + "bbox": [ + 19.559999899999998, + 20.059999899999998, + -1e-07, + 20.000180988089184, + 29.4500001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l04": { + "area": 0.0, + "bbox": [ + 16.9099999, + 29.449999899999998, + -1e-07, + 19.5600001, + 35.3200001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l05": { + "area": 0.0, + "bbox": [ + 14.479999900000001, + 35.3199999, + -1e-07, + 16.9100001, + 37.5800001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l06": { + "area": 0.0, + "bbox": [ + 10.759999900000002, + 37.5799999, + -1e-07, + 14.4800001, + 41.7800001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l07": { + "area": 0.0, + "bbox": [ + 10.298929809814856, + 41.7799999, + -1e-07, + 11.030000099999999, + 43.9800001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l08": { + "area": 0.0, + "bbox": [ + 11.0299999, + 43.979999899999996, + -1e-07, + 12.089147795245106, + 45.3300001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l09": { + "area": 0.0, + "bbox": [ + 11.4299999, + 45.3299999, + -1e-07, + 12.0800001, + 46.6900001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l10": { + "area": 0.0, + "bbox": [ + 9.469999900000001, + 46.0999999, + -1e-07, + 11.430000099999999, + 46.800846774556284, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l11": { + "area": 0.0, + "bbox": [ + 8.8399999, + 44.6499999, + -1e-07, + 9.4700001, + 46.1000001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l12": { + "area": 0.0, + "bbox": [ + 8.833025921944877, + 40.9999999, + -1e-07, + 9.7200001, + 44.6500001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l13": { + "area": 0.0, + "bbox": [ + 9.719999900000001, + 37.2199999, + -1e-07, + 12.780000099999997, + 41.0000001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l14": { + "area": 0.0, + "bbox": [ + 12.7799999, + 31.6199999, + -1e-07, + 17.4500001, + 37.2200001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l15": { + "area": 0.0, + "bbox": [ + 17.449999899999998, + 27.7999999, + -1e-07, + 18.4000001, + 31.620000100000002, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l16": { + "area": 0.0, + "bbox": [ + 18.3699999, + 22.6099999, + -1e-07, + 18.490535814285714, + 27.800000100000002, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l17": { + "area": 0.0, + "bbox": [ + 13.389999900000003, + 11.939999900000002, + -1e-07, + 18.370000100000002, + 22.6100001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l18": { + "area": 0.0, + "bbox": [ + 8.0899999, + 8.4099999, + -1e-07, + 13.3900001, + 11.940000099999999, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l19": { + "area": 0.0, + "bbox": [ + -1e-07, + 6.604212665957446, + -1e-07, + 8.0900001, + 8.4100001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "tire": { + "area": 523146.5151533416, + "bbox": [ + -20.000180988089184, + -370.0000001, + -370.0000001, + 20.000180988089184, + 370.0000001, + 370.0000001 + ], + "edges": 74, + "faces": 37, + "volume": 906269.1100540357 + }, + "tire_profile": { + "area": 407.8638289680655, + "bbox": [ + -20.000180988089184, + -1e-07, + -1e-07, + 20.000180988089184, + 46.800846774556284, + 1e-07 + ], + "edges": 37, + "faces": 1, + "volume": 0.0 + }, + "tread[0]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -372.98736580465527, + -11.17619194365762, + 4.8369313569596954, + -369.5975783806762, + -1.0008285568041597 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[10]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -350.1483886508986, + -137.9311863926377, + 4.8369313569596954, + -343.51621027745887, + -127.48383856366523 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[11]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -345.4498982749647, + -149.9323633049298, + 4.8369313569596954, + -338.5317213224887, + -139.52791669737874 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[12]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -340.33053041058224, + -161.7508707279667, + 4.8369313569596954, + -333.13478361177926, + -151.40200155710264 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[13]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -334.79652221891575, + -173.3723096309551, + 4.8369313569596954, + -327.33197248259665, + -163.09162639949787 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[14]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -328.85461603649816, + -184.78252108073696, + 4.8369313569596954, + -321.1303577664473, + -174.58254921734266 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[15]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -322.5120511607321, + -195.96760349226915, + 4.8369313569596954, + -314.53749517557713, + -185.86077009122434 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220365 + }, + "tread[16]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -315.77655502992167, + -206.91392956556308, + 4.8369313569596954, + -307.56141709750057, + -196.91254824628498 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[17]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -308.65633380857855, + -217.60816288845075, + 4.8369313569596954, + -300.21062280877334, + -207.72441879323853 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[18]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -301.16006238947506, + -228.03727418494847, + 4.8369313569596954, + -292.4940681199333, + -218.28320913326442 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220365 + }, + "tread[19]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -293.29687382462305, + -238.18855718942302, + 4.8369313569596954, + -284.4211544642244, + -228.57605500679043 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220365 + }, + "tread[1]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -372.7249395785359, + -24.17215005844477, + 4.8369313569596954, + -368.9855060007738, + -13.912615760007357 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[20]": { + "area": 132.6498132210872, + "bbox": [ + 0.9998724708504492, + -285.07634819805764, + -248.0496441272186, + 4.8369313569596954, + -276.00171744339934, + -238.59041616661088 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[21]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -276.5085009539798, + -257.60852078288565, + 4.8369313569596954, + -267.24601484455303, + -248.3140916562467 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220365 + }, + "tread[22]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -267.6037706944808, + -266.8535411376511, + 4.8369313569596954, + -258.1647141425896, + -257.73523467493106 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[23]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -258.37300646171326, + -275.77344155829917, + 4.8369313569596954, + -248.76887950354728, + -266.8423670111117 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[24]": { + "area": 132.6498132210872, + "bbox": [ + 0.9998724708504492, + -248.82745452000455, + -284.3573545201725, + 4.8369313569596954, + -239.06995830461628, + -275.6243930268832 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[25]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -238.97874465401617, + -292.5948218475773, + 4.8369313569596954, + -229.07976718727332, + -284.0706131763124 + ], + "edges": 12, + "faces": 6, + "volume": 88.5329187422036 + }, + "tread[26]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -228.8388759996424, + -300.4758074554599, + 4.8369313569596954, + -218.8104776605239, + -292.1707370411881 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220368 + }, + "tread[27]": { + "area": 132.64981322108713, + "bbox": [ + 0.9998724708504492, + -218.42020242491142, + -307.99070957682983, + 4.8369313569596954, + -208.27460127179353, + -299.91489586830903 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220365 + }, + "tread[28]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -207.7354174787005, + -315.13037246103414, + 4.8369313569596954, + -197.48497436353452, + -307.2936545930409 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[29]": { + "area": 132.6498132210872, + "bbox": [ + 0.9998724708504492, + -196.79753892560157, + -321.8860975286292, + 4.8369313569596954, + -186.45474243411996, + -314.2980233344882 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[2]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -372.00840542753735, + -37.13865813194182, + 4.8369313569596954, + -367.9238816195476, + -26.807452583859323 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[30]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -185.61989288578076, + -328.24965396925967, + 4.8369313569596954, + -175.19734412207904, + -320.91946834827945 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[31]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -174.21609759915296, + -334.2132887696335, + 4.8369313569596954, + -163.7264948331855, + -327.1499224236211 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[32]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -162.60004683365506, + -339.76973615937396, + 4.8369313569596954, + -152.05617003034905, + -332.9817947119501 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[33]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -150.78589295782922, + -344.9122264632418, + 4.8369313569596954, + -140.20058820666617, + -338.4079799752143 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[34]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -138.7880296983424, + -349.63449434894153, + 4.8369313569596954, + -128.17419356237667, + -343.42186724251 + ], + "edges": 12, + "faces": 6, + "volume": 88.5329187422036 + }, + "tread[35]": { + "area": 132.6498132210872, + "bbox": [ + 0.9998724708504492, + -126.6210746034472, + -353.9307864604641, + 4.8369313569596954, + -115.99163840683067, + -348.01734786453227 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[36]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -114.29985123375076, + -357.79586842766463, + 4.8369313569596954, + -103.66776530690623, + -352.18882295602253 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[37]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -101.83937110199022, + -361.2250312435363, + 4.8369313569596954, + -91.2175890036286, + -355.93121021714836 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220365 + }, + "tread[38]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -89.25481538381626, + -364.21409700141027, + 4.8369313569596954, + -78.65627811902063, + -359.2399501255028 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220365 + }, + "tread[39]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -76.56151642186938, + -366.7631195826628, + 4.8369313569596954, + -65.9991366754737, + -362.1110114911796 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[3]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -370.8386363381493, + -50.059918471398674, + 4.8369313569596954, + -366.4139986627755, + -39.669628655987545 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[40]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -63.7749390456817, + -368.87833586799604, + 4.8369313569596954, + -53.26158545015341, + -364.540896368158 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220369 + }, + "tread[41]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -50.91066173016501, + -370.5580145680017, + 4.8369313569596954, + -40.45914318715862, + -366.5266443160102 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[42]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -37.98435761563816, + -371.8009330393594, + 4.8369313569596954, + -27.607407690321637, + -368.0409708949939 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[43]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -25.01177541252026, + -372.6064254246586, + 4.8369313569596954, + -14.722036819687345, + -369.06069727222183 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[44]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -12.008720213952772, + -372.9685766197825, + 4.8369313569596954, + -1.8187294148224347, + -369.6307800391441 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[45]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 1.0008285568040776, + -372.98736580465527, + 4.8369313569596954, + 11.176191943657537, + -369.5975783806762 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[46]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 13.91261576000734, + -372.7249395785359, + 4.8369313569596954, + 24.172150058444753, + -368.9855060007738 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220365 + }, + "tread[47]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 26.807452583859362, + -372.0084054275374, + 4.8369313569596954, + 37.13865813194186, + -367.92388161954767 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220369 + }, + "tread[48]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 39.66962865598755, + -370.83863633814934, + 4.8369313569596954, + 50.05991847139869, + -366.4139986627755 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[49]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 52.48347339611443, + -369.2170574938179, + 4.8369313569596954, + 62.92018851145924, + -364.45769669026066 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[4]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -369.2170574938179, + -62.92018851145925, + 4.8369313569596954, + -364.45769669026066, + -52.48347339611445 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220368 + }, + "tread[50]": { + "area": 132.6498132210872, + "bbox": [ + 0.9998724708504492, + 65.23337510824567, + -367.1456445385799, + 4.8369313569596954, + 75.70379999405247, + -362.05735915461133 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[51]": { + "area": 132.6498132210872, + "bbox": [ + 0.9998724708504492, + 77.90380000111682, + -364.6269211700445, + 4.8369313569596954, + 88.39517805775128, + -359.2159104973711 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[52]": { + "area": 132.6498132210872, + "bbox": [ + 0.9998724708504492, + 90.47931111372523, + -361.6639560646568, + 4.8369313569596954, + 100.9788602133419, + -355.9368125860372 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[53]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 102.9445871228891, + -358.2603591389879, + 4.8369313569596954, + 113.43951518248531, + -352.22406049630825 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[54]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 115.28444100992, + -354.42027715160765, + 4.8369313569596954, + 125.76196157651856, + -348.0821776446999 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[55]": { + "area": 132.6498132210872, + "bbox": [ + 0.9998724708504492, + 127.48383856366522, + -350.14838865089865, + 4.8369313569596954, + 137.9311863926377, + -343.5162102774589 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[56]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 139.5279166973788, + -345.4498982749647, + 4.8369313569596954, + 149.93236330492985, + -338.5317213224887 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[57]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 151.4020015571027, + -340.33053041058224, + 4.8369313569596954, + 161.75087072796683, + -333.13478361177926 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[58]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 163.09162639949784, + -334.79652221891575, + 4.8369313569596954, + 173.37230963095507, + -327.3319724825966 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[59]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 174.5825492173427, + -328.8546160364981, + 4.8369313569596954, + 184.782521080737, + -321.1303577664472 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220368 + }, + "tread[5]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -367.14564453857986, + -75.70379999405249, + 4.8369313569596954, + -362.0573591546113, + -65.23337510824567 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220362 + }, + "tread[60]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 185.86077009122428, + -322.51205116073214, + 4.8369313569596954, + 195.9676034922691, + -314.53749517557713 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220368 + }, + "tread[61]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 196.9125482462849, + -315.7765550299217, + 4.8369313569596954, + 206.913929565563, + -307.5614170975006 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220365 + }, + "tread[62]": { + "area": 132.64981322108713, + "bbox": [ + 0.9998724708504492, + 207.7244187932384, + -308.6563338085786, + 4.8369313569596954, + 217.60816288845064, + -300.2106228087734 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220359 + }, + "tread[63]": { + "area": 132.64981322108721, + "bbox": [ + 0.9998724708504492, + 218.2832091332645, + -301.16006238947506, + 4.8369313569596954, + 228.03727418494856, + -292.49406811993333 + ], + "edges": 12, + "faces": 6, + "volume": 88.5329187422037 + }, + "tread[6]": { + "area": 132.6498132210872, + "bbox": [ + 0.9998724708504492, + -364.6269211700445, + -88.39517805775127, + 4.8369313569596954, + -359.2159104973711, + -77.90380000111682 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[7]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -361.6639560646568, + -100.97886021334187, + 4.8369313569596954, + -355.93681258603715, + -90.4793111137252 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[8]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -358.26035913898784, + -113.43951518248531, + 4.8369313569596954, + -352.2240604963082, + -102.9445871228891 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220365 + }, + "tread[9]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -354.42027715160765, + -125.76196157651856, + 4.8369313569596954, + -348.0821776446999, + -115.28444100992 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220365 + }, + "tread_faces[0]": { + "area": 12.228202995761057, + "bbox": [ + -12.617985782970301, + -367.99992919082837, + -15.920280238539997, + -11.250131996589724, + -367.0454599525497, + -7.007214936981318 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "tread_faces[1]": { + "area": 24.30283196548578, + "bbox": [ + -9.401602679969935, + -369.45425275353966, + -13.948074902773874, + -6.467020535967484, + -368.5052816264254, + -4.165090249157016 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "tread_faces[2]": { + "area": 28.299963439775834, + "bbox": [ + -4.5011496212511295, + -369.98805439919306, + -11.086793831802567, + -0.9998724708504492, + -369.5975783806762, + -1.0008285568041597 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "tread_faces[3]": { + "area": 28.299963439775834, + "bbox": [ + 0.9998724708504492, + -369.98805439919306, + -11.086793831802567, + 4.5011496212511295, + -369.5975783806762, + -1.0008285568041597 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "tread_faces[4]": { + "area": 24.30283196548578, + "bbox": [ + 6.467020535967484, + -369.45425275353966, + -13.948074902773874, + 9.401602679969935, + -368.5052816264254, + -4.165090249157016 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "tread_faces[5]": { + "area": 12.228202995761057, + "bbox": [ + 11.250131996589724, + -367.99992919082837, + -15.920280238539997, + 12.617985782970301, + -367.0454599525497, + -7.007214936981318 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "tread_path": { + "area": 0.0, + "bbox": [ + 0.0, + -370.0, + -370.0, + 0.0, + 370.0, + 370.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "tread_pattern": { + "area": 64.57437415779594, + "bbox": [ + 1.0, + 1.0, + 0.0, + 16.0, + 13.0, + 0.0 + ], + "edges": 12, + "faces": 3, + "volume": 0.0 + }, + "tread_prime[0]": { + "area": 86.71106461415305, + "bbox": [ + -14.109072137890605, + -370.74086851826763, + -16.03342656621189, + -11.250131996589724, + -367.0454599525497, + -7.007214936981318 + ], + "edges": 12, + "faces": 6, + "volume": 40.275611681727014 + }, + "tread_prime[1]": { + "area": 122.34106617585738, + "bbox": [ + -10.306420817495235, + -372.40635386566066, + -14.056134514839114, + -6.467020535967484, + -368.5052816264254, + -4.165090249157016 + ], + "edges": 12, + "faces": 6, + "volume": 77.81369200341186 + }, + "tread_prime[2]": { + "area": 132.64981322108713, + "bbox": [ + -4.8369313569596954, + -372.98736580465527, + -11.17619194365762, + -0.9998724708504492, + -369.5975783806762, + -1.0008285568041597 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread_prime[3]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -372.98736580465527, + -11.17619194365762, + 4.8369313569596954, + -369.5975783806762, + -1.0008285568041597 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread_prime[4]": { + "area": 122.34106617585738, + "bbox": [ + 6.467020535967484, + -372.40635386566066, + -14.056134514839114, + 10.306420817495235, + -368.5052816264254, + -4.165090249157016 + ], + "edges": 12, + "faces": 6, + "volume": 77.81369200341184 + }, + "tread_prime[5]": { + "area": 86.71106461415305, + "bbox": [ + 11.250131996589724, + -370.74086851826763, + -16.03342656621189, + 14.109072137890605, + -367.0454599525497, + -7.007214936981318 + ], + "edges": 12, + "faces": 6, + "volume": 40.27561168172702 + } + }, + "status": "ok" + }, + "examples/boxes_on_faces": { + "shapes": { + "bp": { + "area": 57.60000000000005, + "bbox": [ + -1.6, + -1.6, + -1.6, + 1.6, + 1.6, + 1.6 + ], + "edges": 84, + "faces": 36, + "volume": 28.20000000000004 + } + }, + "status": "ok" + }, + "examples/boxes_on_faces_algebra": { + "shapes": { + "b": { + "area": 57.60000000000005, + "bbox": [ + -1.6, + -1.6, + -1.6, + 1.6, + 1.6, + 1.6 + ], + "edges": 84, + "faces": 36, + "volume": 28.20000000000004 + }, + "b2": { + "area": 4.6, + "bbox": [ + -1.0606601717798214, + -1.0606601717798212, + 0.0, + 1.0606601717798214, + 1.0606601717798212, + 0.1 + ], + "edges": 12, + "faces": 6, + "volume": 0.19999999999999996 + } + }, + "status": "ok" + }, + "examples/bracelet": { + "shapes": { + "alignment_holes[0]": { + "area": 58.12339108222815, + "bbox": [ + -40.93852111761477, + 12.82997635418359, + -4.0, + -38.88852111761477, + 14.87997635418359, + 4.0 + ], + "edges": 3, + "faces": 3, + "volume": 26.405086253422212 + }, + "alignment_holes[1]": { + "area": 58.12339108222815, + "bbox": [ + -29.950442435894256, + -24.006333293569345, + -4.0, + -27.90044243589426, + -21.956333293569347, + 4.0 + ], + "edges": 3, + "faces": 3, + "volume": 26.405086253422212 + }, + "alignment_holes[2]": { + "area": 58.12339108222815, + "bbox": [ + -1.0265487338260646, + 28.974999982232767, + -4.0, + 1.0234512661739352, + 31.024999982232764, + 4.0 + ], + "edges": 3, + "faces": 3, + "volume": 26.405086253422212 + }, + "alignment_holes[3]": { + "area": 58.12339108222815, + "bbox": [ + 27.900442435894266, + -24.00633329356934, + -4.0, + 29.950442435894264, + -21.956333293569344, + 4.0 + ], + "edges": 3, + "faces": 3, + "volume": 26.405086253422212 + }, + "alignment_holes[4]": { + "area": 58.12339108222815, + "bbox": [ + 38.88650451888775, + 12.83255801862175, + -4.0, + 40.936504518887745, + 14.88255801862175, + 4.0 + ], + "edges": 3, + "faces": 3, + "volume": 26.405086253422212 + }, + "bracelet": { + "area": 10712.646008816786, + "bbox": [ + -47.50000680684389, + -28.42907241778009, + -12.500000100009856, + 47.50000680684386, + 32.500000100003525, + 12.500000100009858 + ], + "edges": 21, + "faces": 19, + "volume": 18972.11597109143 + }, + "center_arc": { + "area": 0.0, + "bbox": [ + -45.0, + -22.981333293569346, + 0.0, + 45.0, + 30.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "center_section": { + "area": 9536.429150791675, + "bbox": [ + -47.50000680684389, + -25.163156513303015, + -12.500000100009856, + 47.50000680684386, + 32.500000100003525, + 12.500000100009858 + ], + "edges": 6, + "faces": 4, + "volume": 17457.567609126447 + }, + "center_surface": { + "area": 8891.060728470897, + "bbox": [ + -45.0, + -30.0, + -25.0, + 45.0, + 22.981333293569346, + 25.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "half_x_section": { + "area": 0.0, + "bbox": [ + -30.145953752600928, + -25.163156413299944, + -12.5, + -27.704931119187588, + -20.799510173838748, + 7.654042494670958e-16 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "planar_tip_arc": { + "area": 0.0, + "bbox": [ + -28.925442435894265, + -29.083889877102706, + -12.5, + -18.016326837241287, + -22.981333293569346, + 12.5 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "profile": { + "area": 0.0, + "bbox": [ + -30.14595385260093, + -28.42907241997514, + -1.0000000229621274e-07, + -17.203436804458377, + -20.799510073838743, + 1.0000000077666542e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "tip": { + "area": 639.1494972376158, + "bbox": [ + -30.14595385260263, + -28.42907241778009, + -12.500000100009812, + -17.203437266176635, + -20.799510073835027, + 12.500000100009812 + ], + "edges": 2, + "faces": 2, + "volume": 823.2871234965413 + }, + "tip_arc": { + "area": 0.0, + "bbox": [ + -28.925442535894273, + -27.709063193574917, + -12.5000001, + -17.247248972580756, + -22.981333193569334, + 12.5000001 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "tip_side": { + "area": 98.17477296531575, + "bbox": [ + -30.14595385260263, + -25.163156513303008, + -12.500000100009812, + -27.704931019185505, + -20.799510073835027, + 12.500000100009812 + ], + "edges": 2, + "faces": 1, + "volume": 0.0 + }, + "tip_surface": { + "area": 540.9747242723, + "bbox": [ + -30.145953852600933, + -28.42907241778009, + -12.500000100009807, + -17.203437266176635, + -20.79951007383872, + 12.500000100009805 + ], + "edges": 2, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/build123d_customizable_logo": { + "shapes": { + "arrow_left": { + "area": 0.0, + "bbox": [ + 0.0, + -0.75, + 0.0, + 1.0000000000000002, + 0.75, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "build": { + "area": 29.471797050365033, + "bbox": [ + 0.3117625375520839, + -7.0904029320312505, + -1e-07, + 18.190272177656247, + -0.752001888125, + 1e-07 + ], + "edges": 138, + "faces": 19, + "volume": 0.0 + }, + "build_bb": { + "area": 36.47203622048563, + "bbox": [ + -4.8499919619791685, + -1.8800049828124998, + 0.0, + 4.849991961979168, + 1.8800049828125, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "build_text": { + "area": 12.830895495161258, + "bbox": [ + -4.849991961979168, + -1.8800049828124998, + -1e-07, + 4.849991961979167, + 1.8800049828125, + 1e-07 + ], + "edges": 36, + "faces": 6, + "volume": 0.0 + }, + "cmpd": { + "area": 259.90594956677614, + "bbox": [ + 0.0, + -7.0904029320312505, + -1e-07, + 18.502034815208336, + 7.52001973125, + 2.2560059893749997 + ], + "edges": 208, + "faces": 40, + "volume": 64.41227299746792 + }, + "cust_text": { + "area": 16.640901555203776, + "bbox": [ + -8.939254820052081, + -1.09040293203125, + -1e-07, + 8.939254820052081, + 1.0904029320312498, + 1e-07 + ], + "edges": 102, + "faces": 13, + "volume": 0.0 + }, + "extension_lines": { + "area": 0.0, + "bbox": [ + 0.0, + -4.51201177875, + 0.0, + 18.50203471520833, + -0.752001963125, + 0.0 + ], + "edges": 10, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + -4.51201177875, + 0.0, + 0.0, + -0.752001963125, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 18.50203471520833, + -4.51201177875, + 0.0, + 18.50203471520833, + -0.752001963125, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "logo_text": { + "area": 49.909254882945575, + "bbox": [ + -2.1620593270422453e-15, + -2.746801851794792e-16, + -1e-07, + 20.6500002, + 7.52001973125, + 1e-07 + ], + "edges": 33, + "faces": 4, + "volume": 0.0 + }, + "one": { + "area": 0.0, + "bbox": [ + 1.949852461287869e-16, + 0.0, + 0.0, + 2.256005889375, + 7.520019631249999, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "t1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 1.0000000000000002, + 0.75, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "three_d": { + "area": 216.46423313018914, + "bbox": [ + 8.272021594375, + -1.6365788271696354e-16, + -1e-07, + 18.502034815208336, + 7.52001973125, + 2.2560059893749997 + ], + "edges": 48, + "faces": 20, + "volume": 64.41227299746792 + }, + "two": { + "area": 13.969919386221946, + "bbox": [ + 2.632006870937499, + -1.27388090734212e-15, + -1e-07, + 7.4019940501041654, + 7.0900067104166675, + 1e-07 + ], + "edges": 10, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/build123d_customizable_logo_algebra": { + "shapes": { + "arrow_left": { + "area": 0.0, + "bbox": [ + 0.0, + -0.75, + 0.0, + 1.0000000000000002, + 0.75, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "build": { + "area": 29.471797050365033, + "bbox": [ + 0.3117625375520842, + -7.00241471078125, + -1e-07, + 18.190272177656247, + -0.752001888125, + 1e-07 + ], + "edges": 138, + "faces": 19, + "volume": 0.0 + }, + "build_text": { + "area": 12.838607571080878, + "bbox": [ + -4.849991961979168, + -1.8800049828124998, + -1e-07, + 4.849991961979167, + 1.8800049828125, + 1e-07 + ], + "edges": 67, + "faces": 6, + "volume": 0.0 + }, + "cmpd": { + "area": 260.0588594205412, + "bbox": [ + 0.0, + -7.00241471078125, + -1e-07, + 18.502034815208336, + 7.52001973125, + 2.2560059893749997 + ], + "edges": 303, + "faces": 69, + "volume": 64.39081358526559 + }, + "cust_text": { + "area": 16.64303109109882, + "bbox": [ + -8.939254820052081, + -1.09040293203125, + -1e-07, + 8.939254820052081, + 1.0904029320312498, + 1e-07 + ], + "edges": 219, + "faces": 13, + "volume": 0.0 + }, + "extension_lines": { + "area": 0.0, + "bbox": [ + 0.0, + -4.51201177875, + 0.0, + 18.50203471520833, + -0.752001963125, + 0.0 + ], + "edges": 10, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + -4.51201177875, + 0.0, + 0.0, + -0.752001963125, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 18.50203471520833, + -4.51201177875, + 0.0, + 18.50203471520833, + -0.752001963125, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "logo_text": { + "area": 49.92024866492662, + "bbox": [ + -1.0518363024170888e-15, + -2.1916903394822137e-16, + -1e-07, + 20.6500002, + 7.52001973125, + 1e-07 + ], + "edges": 71, + "faces": 4, + "volume": 0.0 + }, + "one": { + "area": 0.0, + "bbox": [ + 1.949852461287869e-16, + 0.0, + 0.0, + 2.256005889375, + 7.520019631249999, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "t1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 1.0000000000000002, + 0.75, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "three_d": { + "area": 216.59938464157614, + "bbox": [ + 8.272021594375, + -2.1916903394822137e-16, + -1e-07, + 18.502034815208336, + 7.52001973125, + 2.2560059893749997 + ], + "edges": 135, + "faces": 49, + "volume": 64.39081358526559 + }, + "two": { + "area": 13.987677728599975, + "bbox": [ + 2.632006870937499, + -1.6365788271696354e-16, + -1e-07, + 7.4019940501041654, + 7.0900067104166675, + 1e-07 + ], + "edges": 18, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/build123d_logo": { + "shapes": { + "arrow_left": { + "area": 0.0, + "bbox": [ + 0.0, + -0.75, + 0.0, + 1.0000000000000002, + 0.75, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "build": { + "area": 12.830895495161256, + "bbox": [ + 4.401025395624998, + -4.51201185375, + -1e-07, + 14.101009319583332, + -0.752001888125, + 1e-07 + ], + "edges": 36, + "faces": 6, + "volume": 0.0 + }, + "build_bb": { + "area": 36.47203622048563, + "bbox": [ + -4.8499919619791685, + -1.8800049828124998, + 0.0, + 4.849991961979168, + 1.8800049828125, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "build_text": { + "area": 12.830895495161258, + "bbox": [ + -4.849991961979168, + -1.8800049828124998, + -1e-07, + 4.849991961979167, + 1.8800049828125, + 1e-07 + ], + "edges": 36, + "faces": 6, + "volume": 0.0 + }, + "extension_lines": { + "area": 0.0, + "bbox": [ + 0.0, + -4.51201177875, + 0.0, + 18.50203471520833, + -0.752001963125, + 0.0 + ], + "edges": 10, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + -4.51201177875, + 0.0, + 0.0, + -0.752001963125, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 18.50203471520833, + -4.51201177875, + 0.0, + 18.50203471520833, + -0.752001963125, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "logo": { + "area": 243.26504801157236, + "bbox": [ + 0.0, + -4.51201185375, + -1e-07, + 18.502034815208336, + 7.52001973125, + 2.2560059893749997 + ], + "edges": 106, + "faces": 27, + "volume": 64.41227299746792 + }, + "logo_text": { + "area": 49.909254882945575, + "bbox": [ + -2.1620593270422453e-15, + -2.746801851794792e-16, + -1e-07, + 20.6500002, + 7.52001973125, + 1e-07 + ], + "edges": 33, + "faces": 4, + "volume": 0.0 + }, + "one": { + "area": 0.0, + "bbox": [ + 1.949852461287869e-16, + 0.0, + 0.0, + 2.256005889375, + 7.520019631249999, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "t1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 1.0000000000000002, + 0.75, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "three_d": { + "area": 216.46423313018914, + "bbox": [ + 8.272021594375, + -1.6365788271696354e-16, + -1e-07, + 18.502034815208336, + 7.52001973125, + 2.2560059893749997 + ], + "edges": 48, + "faces": 20, + "volume": 64.41227299746792 + }, + "two": { + "area": 13.969919386221946, + "bbox": [ + 2.632006870937499, + -1.27388090734212e-15, + -1e-07, + 7.4019940501041654, + 7.0900067104166675, + 1e-07 + ], + "edges": 10, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/build123d_logo_algebra": { + "shapes": { + "arrow_left": { + "area": 0.0, + "bbox": [ + 0.0, + -0.75, + 0.0, + 1.0000000000000002, + 0.75, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "build": { + "area": 12.838607571080878, + "bbox": [ + 4.401025395624998, + -4.51201185375, + -1e-07, + 14.101009319583332, + -0.752001888125, + 1e-07 + ], + "edges": 67, + "faces": 6, + "volume": 0.0 + }, + "build_text": { + "area": 12.838607571080878, + "bbox": [ + -4.849991961979168, + -1.8800049828124998, + -1e-07, + 4.849991961979167, + 1.8800049828125, + 1e-07 + ], + "edges": 67, + "faces": 6, + "volume": 0.0 + }, + "cmpd": { + "area": 243.42566994125704, + "bbox": [ + 0.0, + -4.51201185375, + -1e-07, + 18.502034815208336, + 7.52001973125, + 2.2560059893749997 + ], + "edges": 232, + "faces": 56, + "volume": 64.39081358526559 + }, + "extension_lines": { + "area": 0.0, + "bbox": [ + 0.0, + -4.51201177875, + 0.0, + 18.50203471520833, + -0.752001963125, + 0.0 + ], + "edges": 10, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + -4.51201177875, + 0.0, + 0.0, + -0.752001963125, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 18.50203471520833, + -4.51201177875, + 0.0, + 18.50203471520833, + -0.752001963125, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "logo_text": { + "area": 49.92024866492662, + "bbox": [ + -1.0518363024170888e-15, + -2.1916903394822137e-16, + -1e-07, + 20.6500002, + 7.52001973125, + 1e-07 + ], + "edges": 71, + "faces": 4, + "volume": 0.0 + }, + "one": { + "area": 0.0, + "bbox": [ + 1.949852461287869e-16, + 0.0, + 0.0, + 2.256005889375, + 7.520019631249999, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "t1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 1.0000000000000002, + 0.75, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "three_d": { + "area": 216.59938464157614, + "bbox": [ + 8.272021594375, + -2.1916903394822137e-16, + -1e-07, + 18.502034815208336, + 7.52001973125, + 2.2560059893749997 + ], + "edges": 135, + "faces": 49, + "volume": 64.39081358526559 + }, + "two": { + "area": 13.987677728599975, + "bbox": [ + 2.632006870937499, + -1.6365788271696354e-16, + -1e-07, + 7.4019940501041654, + 7.0900067104166675, + 1e-07 + ], + "edges": 18, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/canadian_flag": { + "shapes": { + "canadian_flag": { + "area": 5208.439087165348, + "bbox": [ + -1e-07, + -1e-07, + -4.699608638596608, + 100.0000001, + 50.00000010000018, + 6.243966828598882 + ], + "edges": 95, + "faces": 4, + "volume": 0.0 + }, + "center_field": { + "area": 1874.9565394083024, + "bbox": [ + 24.999999899999963, + -1e-07, + 0.48039636802062596, + 75.0000001, + 50.00000010000005, + 6.224217892824805 + ], + "edges": 45, + "faces": 1, + "volume": 0.0 + }, + "center_field_builder": { + "area": 1814.5998286779718, + "bbox": [ + -25.0000001, + -1e-07, + -1e-07, + 25.0000001, + 50.0000001, + 1e-07 + ], + "edges": 41, + "faces": 1, + "volume": 0.0 + }, + "center_field_planar": { + "area": 1814.5998286779718, + "bbox": [ + 24.9999999, + -1e-07, + 9.9999999, + 75.0000001, + 50.0000001, + 10.0000001 + ], + "edges": 41, + "faces": 1, + "volume": 0.0 + }, + "east_field": { + "area": 1368.6486110025678, + "bbox": [ + 74.9999999, + -1e-07, + -4.699608638596608, + 100.0000001, + 50.000000100000044, + 5.939367188682708 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "east_field_planar": { + "area": 1250.0, + "bbox": [ + 75.0, + 0.0, + 10.0, + 100.0, + 50.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0771, + 0.0, + 0.0187, + 0.2569, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 0.0325, + 0.2458, + 0.0, + 0.2115, + 0.3125, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 0.1915, + 0.3277, + 0.0, + 0.3875, + 0.5071, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 0.2621, + 0.5235, + 0.0, + 0.375, + 0.6427, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l5": { + "area": 0.0, + "bbox": [ + 0.1369, + 0.5835, + 0.0, + 0.2469, + 0.6781, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l6": { + "area": 0.0, + "bbox": [ + 0.0881, + 0.5954, + 0.0, + 0.1562, + 0.8146, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l7": { + "area": 0.0, + "bbox": [ + 0.0, + 0.7808, + 0.0, + 0.0692, + 0.9167, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "maple_leaf": { + "area": 707.6909826528384, + "bbox": [ + 30.6249999, + 3.8549999, + 1.4673530901044824, + 69.37500010000001, + 45.83499887345733, + 6.243966828598882 + ], + "edges": 42, + "faces": 1, + "volume": 0.0 + }, + "maple_leaf_planar": { + "area": 685.4001713220289, + "bbox": [ + 30.62499989999999, + 3.8549999, + 9.9999999, + 69.3750001, + 45.835000099999995, + 10.0000001 + ], + "edges": 38, + "faces": 1, + "volume": 0.0 + }, + "outline": { + "area": 0.0, + "bbox": [ + -19.375, + 3.855, + -1e-07, + 19.375, + 45.834999999999994, + 1e-07 + ], + "edges": 38, + "faces": 0, + "volume": 0.0 + }, + "the_wind": { + "area": 6889.33825264371, + "bbox": [ + -5.000000100000006, + -5.000000100000005, + -6.073785244149412, + 105.00000010000004, + 55.00000010000005, + 6.24440441934073 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "west_field": { + "area": 1257.142954101639, + "bbox": [ + -1e-07, + -1e-07, + -0.13937941960498412, + 25.0000001, + 50.00000010000018, + 2.6755890038105794 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "west_field_builder": { + "area": 1250.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 25.0, + 50.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "west_field_planar": { + "area": 1250.0, + "bbox": [ + 0.0, + 0.0, + 10.0, + 25.0, + 50.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/canadian_flag_algebra": { + "shapes": { + "canadian_flag": { + "area": 5208.439033683858, + "bbox": [ + -1e-07, + -1e-07, + -4.699608638596608, + 100.0000001, + 50.00000010000018, + 6.243966828598882 + ], + "edges": 94, + "faces": 4, + "volume": 0.0 + }, + "center_field": { + "area": 1874.9564859268125, + "bbox": [ + 24.999999899999967, + -1e-07, + 0.48039636802062596, + 75.0000001, + 50.000000100000044, + 6.224217892824805 + ], + "edges": 45, + "faces": 1, + "volume": 0.0 + }, + "center_field_planar": { + "area": 1814.5998286779723, + "bbox": [ + 24.9999999, + -1e-07, + 9.9999999, + 75.0000001, + 50.0000001, + 10.0000001 + ], + "edges": 41, + "faces": 1, + "volume": 0.0 + }, + "east_field": { + "area": 1368.6486110025678, + "bbox": [ + 74.9999999, + -1e-07, + -4.699608638596608, + 100.0000001, + 50.000000100000044, + 5.939367188682708 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "east_field_planar": { + "area": 1250.0, + "bbox": [ + 75.0, + 0.0, + 10.0, + 100.0, + 50.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "field_planar": { + "area": 1250.0, + "bbox": [ + 0.0, + 0.0, + 10.0, + 25.0, + 50.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0771, + 0.0, + 0.0187, + 0.2569, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 0.0325, + 0.2458, + 0.0, + 0.2115, + 0.3125, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 0.1915, + 0.3277, + 0.0, + 0.3875, + 0.5071, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 0.2621, + 0.5235, + 0.0, + 0.375, + 0.6427, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l5": { + "area": 0.0, + "bbox": [ + 0.1369, + 0.5835, + 0.0, + 0.2469, + 0.6781, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l6": { + "area": 0.0, + "bbox": [ + 0.0881, + 0.5954, + 0.0, + 0.1562, + 0.8146, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l7": { + "area": 0.0, + "bbox": [ + 0.0, + 0.7808, + 0.0, + 0.0692, + 0.9167, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "maple_leaf": { + "area": 707.6909826528384, + "bbox": [ + 30.6249999, + 3.8549999, + 1.4673530901044824, + 69.37500010000001, + 45.83499887345733, + 6.243966828598882 + ], + "edges": 41, + "faces": 1, + "volume": 0.0 + }, + "maple_leaf_planar": { + "area": 685.4001713220289, + "bbox": [ + 30.62499989999999, + 3.8549999, + 9.9999999, + 69.3750001, + 45.835000099999995, + 10.0000001 + ], + "edges": 37, + "faces": 1, + "volume": 0.0 + }, + "outline": { + "area": 0.0, + "bbox": [ + -0.38750000000000007, + 0.0771, + -1e-07, + 0.3875, + 0.9167, + 1e-07 + ], + "edges": 37, + "faces": 0, + "volume": 0.0 + }, + "r1": { + "area": 0.0, + "bbox": [ + 0.009399999999999974, + 0.2569, + 0.0, + 0.0325, + 0.2773, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "r2": { + "area": 0.0, + "bbox": [ + 0.1864836247564839, + 0.3125, + 0.0, + 0.1915, + 0.3277, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "r3": { + "area": 0.0, + "bbox": [ + 0.33577817867175824, + 0.5071, + 0.0, + 0.3433, + 0.5235, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "r4": { + "area": 0.0, + "bbox": [ + 0.24689999999999998, + 0.6186630156729, + 0.0, + 0.2621, + 0.6267, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "r5": { + "area": 0.0, + "bbox": [ + 0.06919999999999998, + 0.7733751402565928, + 0.0, + 0.0881, + 0.7808, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "s": { + "area": 0.0, + "bbox": [ + 0.11332820126959792, + 0.5771646513943651, + -1e-07, + 0.1369001, + 0.5954001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "the_wind": { + "area": 6889.33825264371, + "bbox": [ + -5.000000100000006, + -5.000000100000005, + -6.073785244149412, + 105.00000010000004, + 55.00000010000005, + 6.24440441934073 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "west_field": { + "area": 1257.142954101639, + "bbox": [ + -1e-07, + -1e-07, + -0.13937941960498412, + 25.0000001, + 50.00000010000018, + 2.6755890038105794 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "west_field_planar": { + "area": 1250.0, + "bbox": [ + 0.0, + 0.0, + 10.0, + 25.0, + 50.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/cast_bearing_unit": { + "shapes": { + "drafted_faces[0]": { + "area": 265.3714050163985, + "bbox": [ + -49.25, + -9.786139554237103, + 0.0, + -43.27309392953986, + 9.786139554237913, + 11.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "drafted_faces[1]": { + "area": 373.9384228347135, + "bbox": [ + -43.27309392954144, + -25.339137948504423, + 0.0, + -13.045232386840148, + -9.786139554237101, + 11.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "drafted_faces[2]": { + "area": 373.9384228346939, + "bbox": [ + -43.27309392953986, + 9.786139554237913, + 0.0, + -13.045232386840153, + 25.339137948504423, + 11.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "drafted_faces[3]": { + "area": 3282.2603201329794, + "bbox": [ + -28.5, + -28.5, + 0.0, + 28.5, + 28.5, + 26.0 + ], + "edges": 11, + "faces": 1, + "volume": 0.0 + }, + "drafted_faces[4]": { + "area": 373.93842283466034, + "bbox": [ + 13.045232386840139, + -25.339137948504426, + 0.0, + 43.27309392953713, + -9.786139554239316, + 11.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "drafted_faces[5]": { + "area": 373.9384228347802, + "bbox": [ + 13.045232386840127, + 9.786139554234339, + 0.0, + 43.27309392954682, + 25.339137948504433, + 11.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "drafted_faces[6]": { + "area": 265.3714050163659, + "bbox": [ + 43.27309392953713, + -9.786139554239316, + 0.0, + 49.25, + 9.786139554234342, + 11.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "housing": { + "area": 2551.7586328783095, + "bbox": [ + -28.5, + -28.5, + 0.0, + 28.5, + 28.5, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "oval_flanged_bearing_unit": { + "area": 14647.574729140939, + "bbox": [ + -49.177631389975325, + -28.427631389975318, + -4.440892098500626e-16, + 49.17763138997532, + 28.427631389975318, + 26.0 + ], + "edges": 85, + "faces": 37, + "volume": 46882.6848405294 + }, + "plan": { + "area": 3724.040132749337, + "bbox": [ + -49.25, + -28.5, + 0.0, + 49.25, + 28.5, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/circuit_board": { + "shapes": { + "pcb": { + "area": 5173.203492338984, + "bbox": [ + -35.0, + -15.0, + 0.0, + 35.0, + 15.000000000000018, + 3.0 + ], + "edges": 285, + "faces": 97, + "volume": 5767.5000452165295 + } + }, + "status": "ok" + }, + "examples/circuit_board_algebra": { + "shapes": { + "pcb": { + "area": 5173.203492338984, + "bbox": [ + -35.0, + -15.0, + 0.0, + 35.0, + 15.000000000000018, + 3.0 + ], + "edges": 285, + "faces": 97, + "volume": 5767.5000452165295 + } + }, + "status": "ok" + }, + "examples/clock": { + "shapes": { + "clock_face": { + "area": 283.1444639522574, + "bbox": [ + -10.0000001, + -10.0000001, + -1e-07, + 10.0000001, + 10.0000001, + 1e-07 + ], + "edges": 635, + "faces": 7, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 9.709098043852952, + 0.1276235568206083, + 0.0, + 9.749164693846568, + 0.8921407819681733, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 9.211195580065622, + 0.12107875903493608, + 0.0, + 9.249207530059564, + 0.846389972636472, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "minute_indicator": { + "area": 0.3644814849538257, + "bbox": [ + 9.219393703646357, + 0.12237215025528633, + 0.0, + 9.747346197310556, + 0.8833626200981024, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "outline": { + "area": 0.0, + "bbox": [ + 9.211195580065622, + 0.12107875903493608, + 0.0, + 9.749164693846568, + 0.8921407819681733, + 0.0 + ], + "edges": 4, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/clock_algebra": { + "shapes": { + "clock_face": { + "area": 283.14429874819405, + "bbox": [ + -10.0000001, + -10.0000001, + -1e-07, + 10.0000001, + 10.0000001, + 1e-07 + ], + "edges": 634, + "faces": 7, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 9.709098043852952, + 0.1276235568206083, + 0.0, + 9.749164693846568, + 0.8921407819681733, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 9.211195580065622, + 0.12107875903493608, + 0.0, + 9.249207530059564, + 0.846389972636472, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 9.249207530059564, + 0.12107875903493608, + 0.0, + 9.749164693846568, + 0.1276235568206083, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 9.211195580065622, + 0.846389972636472, + 0.0, + 9.709098043852952, + 0.8921407819681733, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "minute_indicator": { + "area": 0.3644814849538257, + "bbox": [ + 9.219393703646357, + 0.12237215025528633, + 0.0, + 9.747346197310556, + 0.8833626200981024, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/custom_sketch_objects": { + "shapes": { + "base_top": { + "area": 6911.982079412815, + "bbox": [ + -35.75000000000079, + -48.45, + 8.85, + 35.750000000000796, + 48.449999999999996, + 8.85 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "box": { + "area": 23664.619114821067, + "bbox": [ + -35.75000000000079, + -48.45, + 0.0, + 35.750000000000796, + 48.449999999999996, + 16.2 + ], + "edges": 72, + "faces": 28, + "volume": 21485.21909241953 + }, + "box_builder": { + "area": 23664.619114821067, + "bbox": [ + -35.75000000000079, + -48.45, + 0.0, + 35.750000000000796, + 48.449999999999996, + 16.2 + ], + "edges": 72, + "faces": 28, + "volume": 21485.21909241953 + }, + "box_plan": { + "area": 6911.982079412815, + "bbox": [ + -35.75000000000079, + -48.45, + 0.0, + 35.750000000000796, + 48.449999999999996, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "lid": { + "area": 18194.269405705156, + "bbox": [ + -35.75000010000079, + -48.450000100000004, + 8.85, + 35.7500001000008, + 48.4500001, + 17.7000001 + ], + "edges": 129, + "faces": 46, + "volume": 13597.407606122617 + }, + "lid_builder": { + "area": 18194.269405705156, + "bbox": [ + -35.75000010000079, + -48.450000100000004, + 0.0, + 35.7500001000008, + 48.4500001, + 8.850000099999999 + ], + "edges": 129, + "faces": 46, + "volume": 13597.4076061226 + }, + "pocket": { + "area": 6345.322532550661, + "bbox": [ + -34.00000000000079, + -46.7, + 0.0, + 34.000000000000796, + 46.699999999999996, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "suits": { + "area": 623.0320537573014, + "bbox": [ + -28.263412090917864, + -36.36000009540909, + -1e-07, + 27.85828006423877, + 36.360000095409085, + 1e-07 + ], + "edges": 27, + "faces": 4, + "volume": 0.0 + }, + "walls": { + "area": 6505.261764817069, + "bbox": [ + -34.50000000000079, + -47.2, + 0.0, + 34.500000000000796, + 47.199999999999996, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/custom_sketch_objects_algebra": { + "shapes": { + "base_top": { + "area": 6911.982079412815, + "bbox": [ + -35.75000000000079, + -48.45, + 8.85, + 35.750000000000796, + 48.449999999999996, + 8.85 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "box": { + "area": 23664.619114821067, + "bbox": [ + -35.75000000000079, + -48.45, + 0.0, + 35.750000000000796, + 48.449999999999996, + 16.2 + ], + "edges": 72, + "faces": 28, + "volume": 21485.21909241953 + }, + "box_plan": { + "area": 6911.982079412815, + "bbox": [ + -35.75000000000079, + -48.45, + 0.0, + 35.750000000000796, + 48.449999999999996, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "lid": { + "area": 18194.269405705156, + "bbox": [ + -35.75000010000079, + -48.450000100000004, + 8.85, + 35.7500001000008, + 48.4500001, + 17.7000001 + ], + "edges": 129, + "faces": 46, + "volume": 13597.407606122617 + }, + "lid_bottom": { + "area": 6345.322532550661, + "bbox": [ + -34.00000000000079, + -46.7, + 0.0, + 34.000000000000796, + 46.699999999999996, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "pocket": { + "area": 19077.859216208104, + "bbox": [ + -35.75000000000079, + -48.45, + 8.85, + 35.750000000000796, + 48.449999999999996, + 17.7 + ], + "edges": 48, + "faces": 19, + "volume": 14532.920788237936 + }, + "suites": { + "area": 623.0320537573014, + "bbox": [ + -28.263412091234628, + -36.36000009540909, + 17.699999899999998, + 27.85828006392201, + 36.360000095409085, + 17.7000001 + ], + "edges": 27, + "faces": 4, + "volume": 0.0 + }, + "top": { + "area": 5952.346685814369, + "bbox": [ + -32.75000000000079, + -45.45, + 1.5, + 32.750000000000796, + 45.449999999999996, + 1.5 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "walls": { + "area": 6505.261764817069, + "bbox": [ + -34.500000000317556, + -47.2, + 8.85, + 34.49999999968403, + 47.199999999999996, + 8.85 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/din_rail": { + "shapes": { + "din": { + "area": 45.768141582522894, + "bbox": [ + -17.5, + 0.0, + 0.0, + 17.5, + 7.5, + 0.0 + ], + "edges": 20, + "faces": 1, + "volume": 0.0 + }, + "rail": { + "area": 88463.30007296926, + "bbox": [ + -17.5, + -500.0, + 0.0, + 17.5, + 500.0, + 7.5 + ], + "edges": 528, + "faces": 178, + "volume": 42462.863691085535 + }, + "slots": { + "area": 3305.27751063892, + "bbox": [ + -3.100000000000001, + -482.5, + 0.0, + 3.100000000000001, + 482.5, + 0.0 + ], + "edges": 156, + "faces": 39, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/din_rail_algebra": { + "shapes": { + "din": { + "area": 45.768141582522894, + "bbox": [ + -17.5, + 0.0, + 0.0, + 17.5, + 7.5, + 0.0 + ], + "edges": 20, + "faces": 1, + "volume": 0.0 + }, + "rail": { + "area": 88463.30007296933, + "bbox": [ + -17.5, + -1000.0, + 0.0, + 17.5, + 0.0, + 7.5 + ], + "edges": 528, + "faces": 178, + "volume": 42462.86369108561 + }, + "slot_faces[0]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 17.5, + 3.100000000000001, + 7.5, + 32.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[10]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 267.5, + 3.100000000000001, + 7.5, + 282.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[11]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 292.5, + 3.100000000000001, + 7.5, + 307.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[12]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 317.5, + 3.100000000000001, + 7.5, + 332.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[13]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 342.5, + 3.100000000000001, + 7.5, + 357.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[14]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 367.5, + 3.100000000000001, + 7.5, + 382.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[15]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 392.5, + 3.100000000000001, + 7.5, + 407.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[16]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 417.5, + 3.100000000000001, + 7.5, + 432.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[17]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 442.5, + 3.100000000000001, + 7.5, + 457.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[18]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 467.5, + 3.100000000000001, + 7.5, + 482.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[19]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 492.5, + 3.100000000000001, + 7.5, + 507.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[1]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 42.5, + 3.100000000000001, + 7.5, + 57.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[20]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 517.5, + 3.100000000000001, + 7.5, + 532.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[21]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 542.5, + 3.100000000000001, + 7.5, + 557.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[22]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 567.5, + 3.100000000000001, + 7.5, + 582.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[23]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 592.5, + 3.100000000000001, + 7.5, + 607.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[24]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 617.5, + 3.100000000000001, + 7.5, + 632.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[25]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 642.5, + 3.100000000000001, + 7.5, + 657.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[26]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 667.5, + 3.100000000000001, + 7.5, + 682.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[27]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 692.5, + 3.100000000000001, + 7.5, + 707.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[28]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 717.5, + 3.100000000000001, + 7.5, + 732.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[29]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 742.5, + 3.100000000000001, + 7.5, + 757.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[2]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 67.5, + 3.100000000000001, + 7.5, + 82.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[30]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 767.5, + 3.100000000000001, + 7.5, + 782.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[31]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 792.5, + 3.100000000000001, + 7.5, + 807.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[32]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 817.5, + 3.100000000000001, + 7.5, + 832.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[33]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 842.5, + 3.100000000000001, + 7.5, + 857.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[34]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 867.5, + 3.100000000000001, + 7.5, + 882.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[35]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 892.5, + 3.100000000000001, + 7.5, + 907.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[36]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 917.5, + 3.100000000000001, + 7.5, + 932.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[37]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 942.5, + 3.100000000000001, + 7.5, + 957.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[38]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 967.5, + 3.100000000000001, + 7.5, + 982.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[3]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 92.5, + 3.100000000000001, + 7.5, + 107.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[4]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 117.5, + 3.100000000000001, + 7.5, + 132.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[5]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 142.5, + 3.100000000000001, + 7.5, + 157.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[6]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 167.5, + 3.100000000000001, + 7.5, + 182.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[7]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 192.5, + 3.100000000000001, + 7.5, + 207.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[8]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 217.5, + 3.100000000000001, + 7.5, + 232.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[9]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 242.5, + 3.100000000000001, + 7.5, + 257.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/dual_color_3mf": { + "shapes": { + "bl": { + "area": 0.0, + "bbox": [ + -0.5, + -0.28734788556634605, + 0.0, + 9.447213595499958, + 9.0, + 0.0 + ], + "edges": 6, + "faces": 0, + "volume": 0.0 + }, + "inset": { + "area": 356.17444482833366, + "bbox": [ + -9.0, + -9.0, + 0.0, + 9.0, + 9.0, + 1.0 + ], + "edges": 96, + "faces": 34, + "volume": 91.63423335418365 + }, + "inset_builder": { + "area": 356.17444482833366, + "bbox": [ + -9.0, + -9.0, + 0.0, + 9.0, + 9.0, + 1.0 + ], + "edges": 96, + "faces": 34, + "volume": 91.63423335418365 + }, + "inset_pattern": { + "area": 91.6342333541837, + "bbox": [ + -9.0, + -9.0, + 0.0, + 9.0, + 9.0, + 0.0 + ], + "edges": 32, + "faces": 1, + "volume": 0.0 + }, + "outset": { + "area": 869.6375114115988, + "bbox": [ + -10.0, + -10.0, + 0.0, + 10.0, + 10.0, + 1.0 + ], + "edges": 108, + "faces": 46, + "volume": 308.3657666458164 + }, + "outset_builder": { + "area": 869.6375114115988, + "bbox": [ + -10.0, + -10.0, + 0.0, + 10.0, + 10.0, + 1.0 + ], + "edges": 108, + "faces": 46, + "volume": 308.3657666458164 + } + }, + "status": "ok" + }, + "examples/extrude": { + "shapes": { + "both": { + "area": 447.4664797546875, + "bbox": [ + -3.5200033552083334, + -2.730013629459635, + -5.0000001, + 3.520003355208333, + 4.909993080957031, + 5.0000001 + ], + "edges": 10, + "faces": 6, + "volume": 182.54094425947818 + }, + "ex26": { + "area": 1637.0551793147051, + "bbox": [ + -1.5000001022277465, + -12.500000100000017, + -1e-07, + 1.5000001019810518, + 12.5000001, + 28.0000001 + ], + "edges": 12, + "faces": 6, + "volume": 2017.872302865583 + }, + "ex26_sk": { + "area": 28.274333882308138, + "bbox": [ + -3.0, + 22.0, + 0.0, + 3.0, + 28.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "ex26_sk2": { + "area": 75.0, + "bbox": [ + -1.5, + -12.5, + 0.0, + 1.5, + 12.5, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "ex26_target": { + "area": 1536.9893279280202, + "bbox": [ + -3.0000001, + -28.0, + 0.0, + 3.0, + 28.0, + 28.0000001 + ], + "edges": 3, + "faces": 3, + "volume": 2220.6609902451046 + }, + "ex27": { + "area": 9480.191004404856, + "bbox": [ + -3.0000001, + -28.0000001, + -60.0000001, + 3.0000001, + 28.0000001, + 28.0000001 + ], + "edges": 21, + "faces": 9, + "volume": 13889.535427359886 + }, + "extrusion27": { + "area": 8187.22362975772, + "bbox": [ + -1.5000001018576363, + -25.000000100000005, + -60.0000001, + 1.5000001015528512, + 25.0, + 22.401923788646684 + ], + "edges": 21, + "faces": 9, + "volume": 10928.653206002937 + }, + "multiple": { + "area": 932.5946069058361, + "bbox": [ + -6.0000001, + -6.0000001, + -6.0000001, + 6.0000001, + 6.0000001, + 6.0000001 + ], + "edges": 732, + "faces": 270, + "volume": 1037.8293576759434 + }, + "non_planar": { + "area": 289.43951023931925, + "bbox": [ + -5.0, + -5.000000000000003, + 0.0, + 5.0, + 5.0, + 3.3397459621556145 + ], + "edges": 12, + "faces": 6, + "volume": 199.99999999999983 + }, + "simple": { + "area": 242.00987487380706, + "bbox": [ + -3.5200033552083334, + -2.730013629459635, + -1e-07, + 3.520003355208333, + 4.909993080957031, + 5.0000001 + ], + "edges": 6, + "faces": 4, + "volume": 91.27047212973908 + } + }, + "status": "ok" + }, + "examples/extrude_algebra": { + "shapes": { + "both": { + "area": 448.8319148675164, + "bbox": [ + -3.5200033552083334, + -2.730013629459635, + -5.0000001, + 3.520003355208333, + 4.909993080957031, + 5.0000001 + ], + "edges": 80, + "faces": 34, + "volume": 182.79491940648458 + }, + "circle": { + "area": 28.274333882308138, + "bbox": [ + -3.0, + 22.0, + 0.0, + 3.0, + 28.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "circle2": { + "area": 28.274333882308138, + "bbox": [ + -3.0, + 0.0, + 22.0, + 3.0, + 0.0, + 28.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "ex26": { + "area": 1637.0551793147051, + "bbox": [ + -1.5000001022277465, + -12.500000100000017, + -1e-07, + 1.5000001019810518, + 12.5000001, + 28.0000001 + ], + "edges": 12, + "faces": 6, + "volume": 2017.872302865583 + }, + "ex26_target": { + "area": 1536.9893279280202, + "bbox": [ + -3.0000001, + -28.0, + 0.0, + 3.0, + 28.0, + 28.0000001 + ], + "edges": 3, + "faces": 3, + "volume": 2220.6609902451046 + }, + "ex27": { + "area": 2030.469547982488, + "bbox": [ + -3.0000001, + -28.0000001, + -24.2487114199643, + 3.0, + 28.0, + 28.0000001 + ], + "edges": 3, + "faces": 3, + "volume": 2960.8813203268073 + }, + "extrusion27": { + "area": 8187.22362975772, + "bbox": [ + -1.500000101857663, + -25.0000001, + -60.0000001, + 1.5000001015528286, + 25.0, + 22.401923788646684 + ], + "edges": 21, + "faces": 9, + "volume": 10928.653206002935 + }, + "faces[0]": { + "area": 1.5824519985159247, + "bbox": [ + -5.0000001, + -3.2500002525878906, + -3.5154981468749997, + -4.9999999, + -1.0509961463378905, + -1.484501853125 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[10]": { + "area": 1.5824519985159247, + "bbox": [ + -3.2500002525878906, + 4.9999999, + -3.5154981468749997, + -1.0509961463378905, + 5.0000001, + -1.484501853125 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[11]": { + "area": 1.5824519985159247, + "bbox": [ + -3.2500002525878906, + 4.9999999, + 1.484501853125, + -1.0509961463378905, + 5.0000001, + 3.5154981468749997 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[12]": { + "area": 1.5824519985159247, + "bbox": [ + 1.0509961463378905, + -5.0000001, + -3.5154981468749997, + 3.2500002525878906, + -4.9999999, + -1.484501853125 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[13]": { + "area": 1.5824519985159247, + "bbox": [ + 1.0509961463378905, + -5.0000001, + 1.484501853125, + 3.2500002525878906, + -4.9999999, + 3.5154981468749997 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[14]": { + "area": 1.5824519985159247, + "bbox": [ + 1.484501853125, + -3.9490038536621093, + -5.0000001, + 3.5154981468749997, + -1.7499997474121092, + -4.9999999 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[15]": { + "area": 1.5824519985159247, + "bbox": [ + 1.484501853125, + -3.2500002525878906, + 4.9999999, + 3.5154981468749997, + -1.0509961463378905, + 5.0000001 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[16]": { + "area": 1.5824519985159247, + "bbox": [ + 1.484501853125, + 1.0509961463378905, + -5.0000001, + 3.5154981468749997, + 3.2500002525878906, + -4.9999999 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[17]": { + "area": 1.5824519985159247, + "bbox": [ + 1.484501853125, + 1.7499997474121092, + 4.9999999, + 3.5154981468749997, + 3.9490038536621093, + 5.0000001 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[18]": { + "area": 1.5824519985159247, + "bbox": [ + 1.7499997474121092, + 4.9999999, + -3.5154981468749997, + 3.9490038536621093, + 5.0000001, + -1.484501853125 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[19]": { + "area": 1.5824519985159247, + "bbox": [ + 1.7499997474121092, + 4.9999999, + 1.484501853125, + 3.9490038536621093, + 5.0000001, + 3.5154981468749997 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[1]": { + "area": 1.5824519985159247, + "bbox": [ + -5.0000001, + -3.2500002525878906, + 1.484501853125, + -4.9999999, + -1.0509961463378905, + 3.5154981468749997 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[20]": { + "area": 1.5824519985159247, + "bbox": [ + 4.9999999, + -3.9490038536621093, + -3.5154981468749997, + 5.0000001, + -1.7499997474121092, + -1.484501853125 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[21]": { + "area": 1.5824519985159247, + "bbox": [ + 4.9999999, + -3.9490038536621093, + 1.484501853125, + 5.0000001, + -1.7499997474121092, + 3.5154981468749997 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[22]": { + "area": 1.5824519985159247, + "bbox": [ + 4.9999999, + 1.0509961463378905, + -3.5154981468749997, + 5.0000001, + 3.2500002525878906, + -1.484501853125 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[23]": { + "area": 1.5824519985159247, + "bbox": [ + 4.9999999, + 1.0509961463378905, + 1.484501853125, + 5.0000001, + 3.2500002525878906, + 3.5154981468749997 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[2]": { + "area": 1.5824519985159247, + "bbox": [ + -5.0000001, + 1.7499997474121092, + -3.5154981468749997, + -4.9999999, + 3.9490038536621093, + -1.484501853125 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[3]": { + "area": 1.5824519985159247, + "bbox": [ + -5.0000001, + 1.7499997474121092, + 1.484501853125, + -4.9999999, + 3.9490038536621093, + 3.5154981468749997 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[4]": { + "area": 1.5824519985159247, + "bbox": [ + -3.9490038536621093, + -5.0000001, + -3.5154981468749997, + -1.7499997474121092, + -4.9999999, + -1.484501853125 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[5]": { + "area": 1.5824519985159247, + "bbox": [ + -3.9490038536621093, + -5.0000001, + 1.484501853125, + -1.7499997474121092, + -4.9999999, + 3.5154981468749997 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[6]": { + "area": 1.5824519985159247, + "bbox": [ + -3.5154981468749997, + -3.9490038536621093, + -5.0000001, + -1.484501853125, + -1.7499997474121092, + -4.9999999 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[7]": { + "area": 1.5824519985159247, + "bbox": [ + -3.5154981468749997, + -3.2500002525878906, + 4.9999999, + -1.484501853125, + -1.0509961463378905, + 5.0000001 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[8]": { + "area": 1.5824519985159247, + "bbox": [ + -3.5154981468749997, + 1.0509961463378905, + -5.0000001, + -1.484501853125, + 3.2500002525878906, + -4.9999999 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[9]": { + "area": 1.5824519985159247, + "bbox": [ + -3.5154981468749997, + 1.7499997474121092, + 4.9999999, + -1.484501853125, + 3.9490038536621093, + 5.0000001 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "multiple": { + "area": 931.8885215782758, + "bbox": [ + -6.0000001, + -6.0000001, + -6.0000001, + 6.0000001, + 6.0000001, + 6.0000001 + ], + "edges": 1884, + "faces": 654, + "volume": 1037.9788479643803 + }, + "non_planar": { + "area": 289.43951023931925, + "bbox": [ + -5.0, + -5.000000000000003, + 0.0, + 5.0, + 5.0, + 3.3397459621556145 + ], + "edges": 12, + "faces": 6, + "volume": 199.99999999999983 + }, + "rect": { + "area": 150.0, + "bbox": [ + -1.5, + -25.0, + -60.0, + 1.5, + 25.0, + -60.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "simple": { + "area": 242.6954493744067, + "bbox": [ + -3.5200033552083334, + -2.730013629459635, + -1e-07, + 3.520003355208333, + 4.909993080957031, + 5.0000001 + ], + "edges": 48, + "faces": 18, + "volume": 91.39745970324233 + } + }, + "status": "ok" + }, + "examples/fast_grid_holes": { + "shapes": { + "face_perimeter": { + "area": 0.0, + "bbox": [ + -250.0, + -300.0, + 0.0, + 250.0, + 300.0, + 0.0 + ], + "edges": 4, + "faces": 0, + "volume": 0.0 + }, + "grid": { + "area": 372894.78360043187, + "bbox": [ + -250.0, + -300.0, + 0.0, + 250.0, + 300.0, + 1.0 + ], + "edges": 11262, + "faces": 3756, + "volume": 168472.3918002237 + }, + "grid_pattern": { + "area": 168472.39180021593, + "bbox": [ + -250.0, + -300.0, + 0.0, + 250.0, + 300.0, + 0.0 + ], + "edges": 3754, + "faces": 1, + "volume": 0.0 + }, + "hex_hole": { + "area": 0.0, + "bbox": [ + -9.0, + -7.794228634059947, + 0.0, + 9.0, + 7.794228634059948, + 0.0 + ], + "edges": 6, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/handle": { + "shapes": { + "handle": { + "area": 211.5164306268522, + "bbox": [ + -11.000000097363479, + -1.5090618035519154, + -1.0000000139418649e-07, + 11.000000097363479, + 1.5090618035519123, + 5.64371925956298 + ], + "edges": 27, + "faces": 11, + "volume": 94.77347434513797 + }, + "handle_center_line": { + "area": 0.0, + "bbox": [ + -10.0000001, + -1e-07, + -1e-07, + 10.0000001, + 1e-07, + 5.017937046343326 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "section": { + "area": 3.141592653589792, + "bbox": [ + 9.0, + -1.0, + 0.0, + 11.0, + 1.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "sections[0]": { + "area": 3.141592653589792, + "bbox": [ + -11.0, + -1.0, + 0.0, + -9.0, + 1.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "sections[1]": { + "area": 3.7156637146343536, + "bbox": [ + -8.804299919129857, + -1.5, + 3.3506064896102243, + -8.053484166456128, + 1.5, + 4.3499941860953 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "sections[2]": { + "area": 3.7156637146343536, + "bbox": [ + -4.354416398514175, + -1.5, + 4.35488428457654, + -4.287080091966721, + 1.5, + 5.603069320606578 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "sections[3]": { + "area": 3.7156637146343536, + "bbox": [ + 0.0, + -1.5, + 4.374999975060495, + 0.0, + 1.5, + 5.625 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "sections[4]": { + "area": 3.7156637146343536, + "bbox": [ + 4.2870797817706645, + -1.5, + 4.354884301310801, + 4.354416069187994, + 1.5, + 5.60306933837286 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "sections[5]": { + "area": 3.7156637146343536, + "bbox": [ + 8.053484010444263, + -1.5, + 3.350606606818149, + 8.80429970109714, + 1.5, + 4.349994349897985 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "sections[6]": { + "area": 3.141592653589792, + "bbox": [ + 9.0, + -1.0, + 0.0, + 11.0, + 1.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/handle_algebra": { + "shapes": { + "circle": { + "area": 3.141592653589792, + "bbox": [ + 9.0, + -1.0, + -1.1102230246251562e-16, + 11.0, + 1.0, + 1.1102230246251562e-16 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "handle": { + "area": 211.51619811553383, + "bbox": [ + -11.00000009735155, + -1.5090618035519148, + -1.0745058059692382e-07, + 11.000000097755366, + 1.5090618035519179, + 5.643719267214584 + ], + "edges": 27, + "faces": 11, + "volume": 94.7736147223482 + }, + "handle_center_line": { + "area": 0.0, + "bbox": [ + -10.0000001, + -1e-07, + -1e-07, + 10.0000001, + 1e-07, + 5.017937046343326 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "sections": { + "area": 24.861503880351354, + "bbox": [ + -11.0, + -1.5000000000000002, + -1.1102230246251562e-16, + 11.0, + 1.5, + 5.625 + ], + "edges": 42, + "faces": 7, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/heat_exchanger": { + "shapes": { + "heat_exchanger": { + "area": 1255886.680540645, + "bbox": [ + -50.0, + -50.0, + -150.0, + 50.0, + 50.0, + 150.0 + ], + "edges": 2966, + "faces": 1486, + "volume": 363795.07369811094 + }, + "plate_plan": { + "area": 5994.158783049332, + "bbox": [ + -50.0, + -50.0, + 0.0, + 50.0, + 50.0, + 0.0 + ], + "edges": 149, + "faces": 1, + "volume": 0.0 + }, + "tube_plan": { + "area": 1046.150353645398, + "bbox": [ + -41.904155872191964, + -46.25, + 0.0, + 41.904155872191964, + 46.25, + 0.0 + ], + "edges": 296, + "faces": 148, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/heat_exchanger_algebra": { + "shapes": { + "heat_exchanger": { + "area": 1255886.6805406844, + "bbox": [ + -50.0, + -50.0, + -150.0, + 50.0, + 50.0, + 150.0 + ], + "edges": 2374, + "faces": 1190, + "volume": 363795.07369811274 + }, + "plate": { + "area": 5994.158783049332, + "bbox": [ + -50.0, + -50.0, + 0.0, + 50.0, + 50.0, + 0.0 + ], + "edges": 149, + "faces": 1, + "volume": 0.0 + }, + "ring": { + "area": 7.068583470577035, + "bbox": [ + -2.5, + -2.5, + 0.0, + 2.5, + 2.5, + 0.0 + ], + "edges": 2, + "faces": 1, + "volume": 0.0 + }, + "tube_plan": { + "area": 1046.150353645398, + "bbox": [ + -41.904155872191964, + -46.25, + 0.0, + 41.904155872191964, + 46.25, + 0.0 + ], + "edges": 296, + "faces": 148, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/holes": { + "shapes": { + "flush_counter_sink": { + "area": 98.97571303509702, + "bbox": [ + 7.0, + 7.0, + -1.0, + 13.0, + 13.0, + 1.0 + ], + "edges": 8, + "faces": 5, + "volume": 49.21140235079789 + }, + "recessed_counter_bore": { + "area": 105.2433538952581, + "bbox": [ + 7.0, + -3.0, + -1.0, + 13.0, + 3.0, + 1.0 + ], + "edges": 9, + "faces": 6, + "volume": 44.37499623195583 + }, + "recessed_counter_sink": { + "area": 102.11730568868683, + "bbox": [ + -3.0, + 7.0, + -1.0, + 3.0, + 13.0, + 1.0 + ], + "edges": 11, + "faces": 6, + "volume": 45.284411533810655 + }, + "thru_hole": { + "area": 100.5309649148734, + "bbox": [ + -3.0, + -3.0, + -1.0, + 3.0, + 3.0, + 1.0 + ], + "edges": 6, + "faces": 4, + "volume": 50.26548245743668 + } + }, + "status": "ok" + }, + "examples/holes_algebra": { + "shapes": { + "flush_counter_sink": { + "area": 98.97571303509702, + "bbox": [ + -3.0, + -3.0, + -1.0, + 3.0, + 3.0, + 1.0 + ], + "edges": 8, + "faces": 5, + "volume": 49.21140235079789 + }, + "recessed_counter_bore": { + "area": 105.2433538952581, + "bbox": [ + -3.0, + -3.0, + -1.0, + 3.0, + 3.0, + 1.0 + ], + "edges": 9, + "faces": 6, + "volume": 44.37499623195583 + }, + "recessed_counter_sink": { + "area": 102.11730568868683, + "bbox": [ + -3.0, + -3.0, + -1.0, + 3.0, + 3.0, + 1.0 + ], + "edges": 11, + "faces": 6, + "volume": 45.284411533810655 + }, + "thru_hole": { + "area": 100.5309649148734, + "bbox": [ + -3.0, + -3.0, + -1.0, + 3.0, + 3.0, + 1.0 + ], + "edges": 6, + "faces": 4, + "volume": 50.26548245743668 + } + }, + "status": "ok" + }, + "examples/intersecting_chamfers": { + "shapes": { + "blocks": { + "area": 24.349245156795856, + "bbox": [ + -1.5, + -1.0, + -5.551115123125783e-17, + 1.5, + 1.0000000000000002, + 2.0 + ], + "edges": 108, + "faces": 50, + "volume": 5.887333333333333 + } + }, + "status": "ok" + }, + "examples/intersecting_chamfers_algebra": { + "shapes": { + "blocks": { + "area": 26.0, + "bbox": [ + -1.5, + -1.0, + 0.0, + 1.5, + 1.0, + 2.0 + ], + "edges": 34, + "faces": 14, + "volume": 5.999999999999999 + }, + "blocks2": { + "area": 24.349245156795867, + "bbox": [ + -1.5, + -1.0, + -4.163336342344337e-17, + 1.5, + 1.0000000000000002, + 2.0 + ], + "edges": 110, + "faces": 50, + "volume": 5.887333333333332 + } + }, + "status": "ok" + }, + "examples/intersecting_pipes": { + "shapes": { + "box": { + "area": 599.9999999999999, + "bbox": [ + -8.128320675339982, + -7.650934991471806, + -7.245432423491767, + 8.128320675339983, + 7.650934991471806, + 7.245432423491767 + ], + "edges": 12, + "faces": 6, + "volume": 999.9999999999998 + }, + "pipe": { + "area": 13.351768777756618, + "bbox": [ + -4.5, + -4.5, + 0.0, + 4.5, + 4.5, + 0.0 + ], + "edges": 2, + "faces": 1, + "volume": 0.0 + }, + "pipes": { + "area": 3671.7930464859546, + "bbox": [ + -14.743143426754909, + -14.82445978237856, + -15.525656439806344, + 14.743143426754909, + 14.824459782378561, + 15.525656439806344 + ], + "edges": 114, + "faces": 48, + "volume": 1015.9390056815633 + } + }, + "status": "ok" + }, + "examples/joints": { + "shapes": { + "ball": { + "area": 12.766865020514242, + "bbox": [ + -2.5206118420362733, + -2.885983133715599, + 6.022072943407486, + -0.5070270544691564, + -0.8725117260799224, + 8.030278894202937 + ], + "edges": 52, + "faces": 26, + "volume": 3.8561787180391613 + }, + "base": { + "area": 567.2602982837606, + "bbox": [ + -4.773502691896258, + -7.008292387857169, + -1.8867513459481287, + 9.501683115695705, + 7.220084679281462, + 12.691010916410779 + ], + "edges": 18, + "faces": 8, + "volume": 801.7636001838378 + }, + "base_corner_edge": { + "area": 0.0, + "bbox": [ + 4.333333333333333, + 4.127953670731317, + 1.4465819873852044, + 7.3172814714463925, + 7.220084679281461, + 10.506609272161466 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "fixed_arm": { + "area": 19.692137780516926, + "bbox": [ + 6.908713247241016, + 0.7795575930802792, + 3.2051724239635417, + 11.889904378203358, + 3.3551304560922928, + 5.26019296613428 + ], + "edges": 60, + "faces": 30, + "volume": 4.680524109068732 + }, + "hinge_arm": { + "area": 56.470671085422296, + "bbox": [ + 4.333333333333332, + 3.548420768098773, + 0.7195644043411336, + 9.000081744166089, + 7.845073860201328, + 10.484579709449077 + ], + "edges": 18, + "faces": 8, + "volume": 14.973733683061244 + }, + "pin_arm": { + "area": 16.37444678594553, + "bbox": [ + 8.083533844394877, + -2.8600401948303653, + 8.151149073432403, + 10.148917985297086, + -0.2946560539281542, + 10.68384114388351 + ], + "edges": 18, + "faces": 8, + "volume": 3.803650459150637 + }, + "screw_arm": { + "area": 31.654516471323102, + "bbox": [ + 3.6800351009479453, + -14.685218175112015, + 0.3892244301109322, + 7.040023983660519, + -5.395568716160048, + 3.965281816045624 + ], + "edges": 52, + "faces": 26, + "volume": 7.633934357700818 + }, + "slider_arm": { + "area": 26.54833227070927, + "bbox": [ + -3.783362018523447, + -3.7261097605677627, + 11.63780065200003, + 0.5748424454063146, + -1.189272501683919, + 14.573355385119417 + ], + "edges": 60, + "faces": 30, + "volume": 7.385999419283467 + }, + "swing_arm_hinge_edge": { + "area": 0.0, + "bbox": [ + 0.8254493507178241, + -0.5, + 0.0, + 1.0, + -0.3254493507178241, + 10.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/joints_algebra": { + "shapes": { + "ball": { + "area": 12.766865020514242, + "bbox": [ + -2.5206118420362733, + -2.885983133715599, + 6.022072943407486, + -0.5070270544691564, + -0.8725117260799224, + 8.030278894202937 + ], + "edges": 52, + "faces": 26, + "volume": 3.8561787180391613 + }, + "base": { + "area": 567.2602982837606, + "bbox": [ + -4.773502691896258, + -7.008292387857169, + -1.8867513459481287, + 9.501683115695705, + 7.220084679281462, + 12.691010916410779 + ], + "edges": 18, + "faces": 8, + "volume": 801.7636001838378 + }, + "base_corner_edge": { + "area": 0.0, + "bbox": [ + 4.333333333333333, + 4.127953670731317, + 1.4465819873852044, + 7.3172814714463925, + 7.220084679281461, + 10.506609272161466 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "fixed_arm": { + "area": 19.692137780516926, + "bbox": [ + 6.908713247241016, + 0.7795575930802792, + 3.2051724239635417, + 11.889904378203358, + 3.3551304560922928, + 5.26019296613428 + ], + "edges": 60, + "faces": 30, + "volume": 4.680524109068732 + }, + "hinge_arm": { + "area": 56.470671085422296, + "bbox": [ + 4.333333333333332, + 3.548420768098773, + 0.7195644043411336, + 9.000081744166089, + 7.845073860201328, + 10.484579709449077 + ], + "edges": 18, + "faces": 8, + "volume": 14.973733683061244 + }, + "pin_arm": { + "area": 16.37444678594553, + "bbox": [ + 8.083533844394877, + -2.8600401948303653, + 8.151149073432403, + 10.148917985297086, + -0.2946560539281542, + 10.68384114388351 + ], + "edges": 18, + "faces": 8, + "volume": 3.803650459150637 + }, + "screw_arm": { + "area": 31.654516471323102, + "bbox": [ + 3.6800351009479453, + -14.685218175112015, + 0.3892244301109322, + 7.040023983660519, + -5.395568716160048, + 3.965281816045624 + ], + "edges": 52, + "faces": 26, + "volume": 7.633934357700818 + }, + "slider_arm": { + "area": 26.54833227070927, + "bbox": [ + -3.783362018523447, + -3.7261097605677627, + 11.63780065200003, + 0.5748424454063146, + -1.189272501683919, + 14.573355385119417 + ], + "edges": 60, + "faces": 30, + "volume": 7.385999419283467 + }, + "swing_arm_hinge_edge": { + "area": 0.0, + "bbox": [ + 0.8254493507178241, + -0.5, + 0.0, + 1.0, + -0.3254493507178241, + 10.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/key_cap": { + "shapes": { + "cruciform": { + "area": 15.533194442772814, + "bbox": [ + -2.75, + -2.75, + 0.0, + 2.75, + 2.75, + 0.0 + ], + "edges": 13, + "faces": 1, + "volume": 0.0 + }, + "key_cap": { + "area": 1497.8970768745387, + "bbox": [ + -9.000000100000001, + -9.000000100000001, + -1.0000000005551115e-07, + 9.000000100000001, + 9.000000100000001, + 8.441500309937386 + ], + "edges": 171, + "faces": 69, + "volume": 644.8900474026628 + }, + "key_cap_section": { + "area": 49.96808215448871, + "bbox": [ + -7.928203330275511, + -7.928203330275511, + 3.9999999, + 7.928203330275511, + 7.928203330275511, + 4.0000001 + ], + "edges": 9, + "faces": 1, + "volume": 0.0 + }, + "plan": { + "area": 324.0, + "bbox": [ + -9.0, + -9.0, + 0.0, + 9.0, + 9.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "rib_bottom": { + "area": 32.58314567374609, + "bbox": [ + -7.158633027064528, + -7.158633027064528, + 4.0, + 7.158633027064528, + 7.158633027064528, + 4.0 + ], + "edges": 16, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/key_cap_algebra": { + "shapes": { + "key_cap": { + "area": 1497.8832746392773, + "bbox": [ + -9.000000100000001, + -9.000000100000001, + -1.0000000005551115e-07, + 9.000000100000001, + 9.000000100000001, + 8.441500309937386 + ], + "edges": 171, + "faces": 70, + "volume": 645.0537078866295 + }, + "key_cap_section": { + "area": 49.96808215448871, + "bbox": [ + -7.928203330275511, + -7.928203330275511, + 3.9999999, + 7.928203330275511, + 7.928203330275511, + 4.0000001 + ], + "edges": 9, + "faces": 1, + "volume": 0.0 + }, + "plan": { + "area": 324.0, + "bbox": [ + -9.0, + -9.0, + 0.0, + 9.0, + 9.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "ribs": { + "area": 32.65960421113574, + "bbox": [ + -7.158633027064528, + -7.158633027064528, + 0.0, + 7.158633027064528, + 7.158633027064528, + 0.0 + ], + "edges": 16, + "faces": 1, + "volume": 0.0 + }, + "socket": { + "area": 15.533194442772814, + "bbox": [ + -2.75, + -2.75, + 0.0, + 2.75, + 2.75, + 0.0 + ], + "edges": 13, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/lego": { + "shapes": { + "lego": { + "area": 5656.720199717906, + "bbox": [ + -24.0, + -8.0, + 0.0, + 24.0, + 8.0, + 11.4 + ], + "edges": 282, + "faces": 119, + "volume": 3212.1873377813517 + }, + "perimeter": { + "area": 768.0, + "bbox": [ + -24.0, + -8.0, + 0.0, + 24.0, + 8.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "plan": { + "area": 226.1574935943251, + "bbox": [ + -24.0, + -8.0, + 0.0, + 24.0, + 8.0, + 0.0 + ], + "edges": 82, + "faces": 6, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/lego_algebra": { + "shapes": { + "lego": { + "area": 5656.720199717906, + "bbox": [ + -24.0, + -8.0, + 0.0, + 24.0, + 8.0, + 11.4 + ], + "edges": 282, + "faces": 119, + "volume": 3212.187337781355 + }, + "plan": { + "area": 226.1574935943251, + "bbox": [ + -24.0, + -8.0, + 0.0, + 24.0, + 8.0, + 0.0 + ], + "edges": 82, + "faces": 6, + "volume": 0.0 + }, + "ring": { + "area": 15.087498718864992, + "bbox": [ + -3.25, + -3.25, + 0.0, + 3.25, + 3.25, + 0.0 + ], + "edges": 2, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/loft": { + "shapes": { + "art": { + "area": 5285.0901899643095, + "bbox": [ + -15.500000098012299, + -15.500000077682515, + -1e-07, + 15.500000100000108, + 15.500000077682527, + 30.0000001 + ], + "edges": 6, + "faces": 4, + "volume": 1306.3405290344635 + }, + "slice": { + "area": 78.53981633974483, + "bbox": [ + -5.000000000000001, + -5.000000000000001, + 0.0, + 5.000000000000001, + 5.000000000000001, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "top_bottom[0]": { + "area": 78.53981634270136, + "bbox": [ + -5.000000099337427, + -5.000000092560834, + -1e-07, + 5.0000001000001095, + 5.00000009256084, + 1e-07 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "top_bottom[1]": { + "area": 78.53981634270122, + "bbox": [ + -5.000000099337423, + -5.000000092560845, + 29.9999999, + 5.000000100000043, + 5.000000092560842, + 30.0000001 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/loft_algebra": { + "shapes": { + "art": { + "area": 5285.0901899643095, + "bbox": [ + -15.500000098012299, + -15.500000077682515, + -1e-07, + 15.500000100000108, + 15.500000077682527, + 30.0000001 + ], + "edges": 6, + "faces": 4, + "volume": 1306.3405290344635 + }, + "top_bottom[0]": { + "area": 78.53981634270136, + "bbox": [ + -5.000000099337427, + -5.000000092560834, + -1e-07, + 5.0000001000001095, + 5.00000009256084, + 1e-07 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "top_bottom[1]": { + "area": 78.53981634270122, + "bbox": [ + -5.000000099337423, + -5.000000092560845, + 29.9999999, + 5.000000100000043, + 5.000000092560842, + 30.0000001 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/maker_coin": { + "shapes": { + "detents": { + "area": 1231.504320207199, + "bbox": [ + -34.5, + -34.5, + 0.0, + 34.5, + 34.5, + 0.0 + ], + "edges": 8, + "faces": 8, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 20.0, + 6.0, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 15.0, + 0.0, + 0.0, + 25.0, + 10.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 0.0, + 6.0, + 0.0, + 18.07692307692308, + 9.615384615384613, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "label": { + "area": 79.96928325034885, + "bbox": [ + -10.1699952171875, + -4.095020394189453, + -1e-07, + 10.1550049828125, + 7.3649895714355464, + 1e-07 + ], + "edges": 11, + "faces": 2, + "volume": 0.0 + }, + "maker_coin": { + "area": 4760.8136277540025, + "bbox": [ + -23.895454660994158, + -23.895454649195383, + -1.0000001082467451e-07, + 23.895454645821104, + 23.895454656202602, + 10.000004345711716 + ], + "edges": 160, + "faces": 68, + "volume": 13160.217918773385 + }, + "outline": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 25.0, + 10.0, + 0.0 + ], + "edges": 5, + "faces": 0, + "volume": 0.0 + }, + "profile": { + "area": 188.15800545773288, + "bbox": [ + 0.0, + 0.0, + 0.0, + 25.0, + 10.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/mixed_algebra_context": { + "shapes": { + "b": { + "area": 23.492215605537947, + "bbox": [ + -0.75, + -1.0, + -1.5, + 0.75, + 1.0, + 1.5 + ], + "edges": 24, + "faces": 12, + "volume": 6.967963150034936 + }, + "bl": { + "area": 0.0, + "bbox": [ + -1.0, + 0.0, + 0.0, + 2.0, + 4.0, + 0.0 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + }, + "bp": { + "area": 30.026728325004715, + "bbox": [ + -0.75, + -1.0, + -1.5, + 0.75, + 1.0, + 1.5 + ], + "edges": 27, + "faces": 13, + "volume": 5.459998676311835 + }, + "bs": { + "area": 1.8845304354395986, + "bbox": [ + -0.75, + -1.0, + 0.0, + 0.7500000000000002, + 1.0, + 0.0 + ], + "edges": 9, + "faces": 1, + "volume": 0.0 + }, + "c": { + "area": 30.424857133514315, + "bbox": [ + -0.7500001, + -1.0, + -1.5000001, + 0.7500001, + 1.0, + 1.5000001 + ], + "edges": 36, + "faces": 15, + "volume": 5.370479810783857 + }, + "d": { + "area": 1.8216985823678027, + "bbox": [ + -0.75, + -1.0, + 0.0, + 0.7500000000000002, + 1.0, + 0.0 + ], + "edges": 11, + "faces": 1, + "volume": 0.0 + }, + "e": { + "area": 0.0, + "bbox": [ + -1.5, + 0.0, + 0.0, + 2.0, + 4.0, + 0.0 + ], + "edges": 4, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -1.0, + 0.0, + 0.0, + 2.0, + 4.0, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "r": { + "area": 2.387185260013966, + "bbox": [ + -0.75, + -1.0, + 0.0, + 0.7500000000000002, + 1.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/multiple_workplanes": { + "shapes": { + "obj": { + "area": 60.95221315766136, + "bbox": [ + -2.5, + -2.5, + -0.5, + 2.5, + 2.5, + 0.5 + ], + "edges": 17, + "faces": 8, + "volume": 15.083039190168236 + } + }, + "status": "ok" + }, + "examples/multiple_workplanes_algebra": { + "shapes": { + "obj": { + "area": 60.95221315766136, + "bbox": [ + -2.5, + -2.5, + -0.5, + 2.5, + 2.5, + 0.5 + ], + "edges": 17, + "faces": 8, + "volume": 15.083039190168236 + } + }, + "status": "ok" + }, + "examples/packed_boxes": { + "shapes": { + "packed[0]": { + "area": 378.0, + "bbox": [ + 0.0, + 41.0, + -1.5, + 6.0, + 60.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 342.0 + }, + "packed[10]": { + "area": 753.9999999999999, + "bbox": [ + 22.0, + 0.0, + -0.5, + 42.0, + 17.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 340.00000000000006 + }, + "packed[11]": { + "area": 766.0, + "bbox": [ + 30.0, + 41.0, + -2.0, + 47.0, + 56.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 1020.0 + }, + "packed[12]": { + "area": 249.99999999999997, + "bbox": [ + 45.0, + 0.0, + -2.5, + 46.0, + 20.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 99.99999999999997 + }, + "packed[13]": { + "area": 291.99999999999994, + "bbox": [ + 40.0, + 63.0, + -2.5, + 54.0, + 67.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 280.0 + }, + "packed[14]": { + "area": 386.0, + "bbox": [ + 49.0, + 22.0, + -2.0, + 56.0, + 37.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 420.0 + }, + "packed[15]": { + "area": 136.0, + "bbox": [ + 50.0, + 41.0, + -1.5, + 58.0, + 45.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 96.0 + }, + "packed[16]": { + "area": 72.0, + "bbox": [ + 50.0, + 48.0, + -1.0, + 58.0, + 50.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 31.999999999999993 + }, + "packed[17]": { + "area": 598.0, + "bbox": [ + 49.0, + 0.0, + -1.5, + 60.0, + 19.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 627.0 + }, + "packed[18]": { + "area": 220.0, + "bbox": [ + 57.0, + 63.0, + -1.5, + 71.0, + 67.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 168.0 + }, + "packed[19]": { + "area": 42.0, + "bbox": [ + 63.0, + 35.0, + -1.0, + 66.0, + 38.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 18.0 + }, + "packed[1]": { + "area": 471.9999999999999, + "bbox": [ + 0.0, + 79.0, + -1.0, + 14.0, + 92.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 364.0 + }, + "packed[20]": { + "area": 148.0, + "bbox": [ + 63.0, + 21.0, + -2.0, + 68.0, + 27.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 120.0 + }, + "packed[21]": { + "area": 312.0, + "bbox": [ + 63.0, + 0.0, + -1.0, + 69.0, + 18.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 216.0 + }, + "packed[22]": { + "area": 88.0, + "bbox": [ + 63.0, + 30.0, + -2.0, + 69.0, + 32.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 48.0 + }, + "packed[23]": { + "area": 222.0, + "bbox": [ + 72.0, + 20.0, + -1.5, + 75.0, + 37.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 153.0 + }, + "packed[24]": { + "area": 352.0, + "bbox": [ + 72.0, + 40.0, + -1.0, + 80.0, + 56.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 255.99999999999994 + }, + "packed[25]": { + "area": 38.0, + "bbox": [ + 72.0, + 59.0, + -0.5, + 81.0, + 60.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 9.0 + }, + "packed[26]": { + "area": 106.0, + "bbox": [ + 78.0, + 20.0, + -1.0, + 79.0, + 37.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 34.0 + }, + "packed[27]": { + "area": 830.0, + "bbox": [ + 72.0, + 0.0, + -2.5, + 87.0, + 17.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 1275.0 + }, + "packed[28]": { + "area": 202.0, + "bbox": [ + 82.0, + 20.0, + -1.0, + 87.0, + 33.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 129.99999999999997 + }, + "packed[29]": { + "area": 93.99999999999999, + "bbox": [ + 83.0, + 40.0, + -2.5, + 86.0, + 44.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 60.0 + }, + "packed[2]": { + "area": 502.0, + "bbox": [ + 0.0, + 63.0, + -0.5, + 17.0, + 76.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 221.0 + }, + "packed[30]": { + "area": 38.0, + "bbox": [ + 83.0, + 47.0, + -0.5, + 86.0, + 51.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 12.0 + }, + "packed[31]": { + "area": 75.99999999999999, + "bbox": [ + 83.0, + 54.0, + -2.5, + 87.0, + 56.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 39.99999999999999 + }, + "packed[32]": { + "area": 148.0, + "bbox": [ + 90.0, + 17.0, + -0.5, + 94.0, + 31.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 56.0 + }, + "packed[33]": { + "area": 232.0, + "bbox": [ + 90.0, + 61.0, + -1.0, + 96.0, + 74.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 156.0 + }, + "packed[34]": { + "area": 418.0, + "bbox": [ + 90.0, + 0.0, + -0.5, + 103.0, + 14.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 182.00000000000003 + }, + "packed[35]": { + "area": 362.0, + "bbox": [ + 90.0, + 34.0, + -0.5, + 103.0, + 46.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 156.0 + }, + "packed[36]": { + "area": 453.99999999999994, + "bbox": [ + 90.0, + 49.0, + -2.5, + 103.0, + 58.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 585.0 + }, + "packed[37]": { + "area": 220.0, + "bbox": [ + 97.0, + 17.0, + -2.0, + 100.0, + 31.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 168.0 + }, + "packed[38]": { + "area": 58.0, + "bbox": [ + 99.0, + 61.0, + -0.5, + 101.0, + 70.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 17.999999999999996 + }, + "packed[39]": { + "area": 28.0, + "bbox": [ + 99.0, + 73.0, + -2.0, + 101.0, + 74.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 7.999999999999998 + }, + "packed[3]": { + "area": 1072.0, + "bbox": [ + 0.0, + 0.0, + -2.0, + 19.0, + 20.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 1519.9999999999998 + }, + "packed[40]": { + "area": 160.0, + "bbox": [ + 106.0, + 30.0, + -2.0, + 108.0, + 42.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 95.99999999999999 + }, + "packed[41]": { + "area": 278.0, + "bbox": [ + 106.0, + 59.0, + -1.0, + 115.0, + 70.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 198.0 + }, + "packed[42]": { + "area": 78.0, + "bbox": [ + 106.0, + 86.0, + -0.5, + 115.0, + 89.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 27.0 + }, + "packed[43]": { + "area": 279.99999999999994, + "bbox": [ + 106.0, + 73.0, + -2.5, + 116.0, + 79.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 299.99999999999994 + }, + "packed[44]": { + "area": 64.0, + "bbox": [ + 106.0, + 82.0, + -1.0, + 116.0, + 83.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 19.999999999999996 + }, + "packed[45]": { + "area": 402.0, + "bbox": [ + 106.0, + 15.0, + -1.5, + 117.0, + 27.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 396.0 + }, + "packed[46]": { + "area": 286.0, + "bbox": [ + 106.0, + 45.0, + -0.5, + 117.0, + 56.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 121.0 + }, + "packed[47]": { + "area": 384.0, + "bbox": [ + 106.0, + 0.0, + -1.0, + 118.0, + 12.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 288.0 + }, + "packed[48]": { + "area": 126.0, + "bbox": [ + 111.0, + 30.0, + -1.5, + 114.0, + 39.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 81.0 + }, + "packed[49]": { + "area": 14.0, + "bbox": [ + 117.0, + 30.0, + -0.5, + 118.0, + 33.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 2.9999999999999996 + }, + "packed[4]": { + "area": 910.0, + "bbox": [ + 0.0, + 23.0, + -2.5, + 19.0, + 38.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 1425.0 + }, + "packed[5]": { + "area": 822.0, + "bbox": [ + 9.0, + 41.0, + -1.5, + 27.0, + 58.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 918.0 + }, + "packed[6]": { + "area": 267.99999999999994, + "bbox": [ + 17.0, + 79.0, + -0.5, + 31.0, + 87.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 112.0 + }, + "packed[7]": { + "area": 433.99999999999994, + "bbox": [ + 20.0, + 63.0, + -2.5, + 37.0, + 69.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 510.0 + }, + "packed[8]": { + "area": 522.0, + "bbox": [ + 22.0, + 28.0, + -2.5, + 39.0, + 36.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 680.0 + }, + "packed[9]": { + "area": 112.0, + "bbox": [ + 22.0, + 23.0, + -0.5, + 40.0, + 25.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 36.0 + }, + "test_boxes[0]": { + "area": 64.0, + "bbox": [ + -5.0, + -0.5, + -1.0, + 5.0, + 0.5, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 19.999999999999996 + }, + "test_boxes[10]": { + "area": 93.99999999999999, + "bbox": [ + -1.5, + -2.0, + -2.5, + 1.5, + 2.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 60.0 + }, + "test_boxes[11]": { + "area": 42.0, + "bbox": [ + -1.5, + -1.5, + -1.0, + 1.5, + 1.5, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 18.0 + }, + "test_boxes[12]": { + "area": 598.0, + "bbox": [ + -5.5, + -9.5, + -1.5, + 5.5, + 9.5, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 627.0 + }, + "test_boxes[13]": { + "area": 830.0, + "bbox": [ + -7.5, + -8.5, + -2.5, + 7.5, + 8.5, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 1275.0 + }, + "test_boxes[14]": { + "area": 402.0, + "bbox": [ + -5.5, + -6.0, + -1.5, + 5.5, + 6.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 396.0 + }, + "test_boxes[15]": { + "area": 136.0, + "bbox": [ + -4.0, + -2.0, + -1.5, + 4.0, + 2.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 96.0 + }, + "test_boxes[16]": { + "area": 78.0, + "bbox": [ + -4.5, + -1.5, + -0.5, + 4.5, + 1.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 27.0 + }, + "test_boxes[17]": { + "area": 386.0, + "bbox": [ + -3.5, + -7.5, + -2.0, + 3.5, + 7.5, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 420.0 + }, + "test_boxes[18]": { + "area": 28.0, + "bbox": [ + -1.0, + -0.5, + -2.0, + 1.0, + 0.5, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 7.999999999999998 + }, + "test_boxes[19]": { + "area": 148.0, + "bbox": [ + -2.0, + -7.0, + -0.5, + 2.0, + 7.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 56.0 + }, + "test_boxes[1]": { + "area": 14.0, + "bbox": [ + -0.5, + -1.5, + -0.5, + 0.5, + 1.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 2.9999999999999996 + }, + "test_boxes[20]": { + "area": 232.0, + "bbox": [ + -3.0, + -6.5, + -1.0, + 3.0, + 6.5, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 156.0 + }, + "test_boxes[21]": { + "area": 249.99999999999997, + "bbox": [ + -0.5, + -10.0, + -2.5, + 0.5, + 10.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 99.99999999999997 + }, + "test_boxes[22]": { + "area": 291.99999999999994, + "bbox": [ + -7.0, + -2.0, + -2.5, + 7.0, + 2.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 280.0 + }, + "test_boxes[23]": { + "area": 522.0, + "bbox": [ + -8.5, + -4.0, + -2.5, + 8.5, + 4.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 680.0 + }, + "test_boxes[24]": { + "area": 753.9999999999999, + "bbox": [ + -10.0, + -8.5, + -0.5, + 10.0, + 8.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 340.00000000000006 + }, + "test_boxes[25]": { + "area": 453.99999999999994, + "bbox": [ + -6.5, + -4.5, + -2.5, + 6.5, + 4.5, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 585.0 + }, + "test_boxes[26]": { + "area": 160.0, + "bbox": [ + -1.0, + -6.0, + -2.0, + 1.0, + 6.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 95.99999999999999 + }, + "test_boxes[27]": { + "area": 75.99999999999999, + "bbox": [ + -2.0, + -1.0, + -2.5, + 2.0, + 1.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 39.99999999999999 + }, + "test_boxes[28]": { + "area": 72.0, + "bbox": [ + -4.0, + -1.0, + -1.0, + 4.0, + 1.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 31.999999999999993 + }, + "test_boxes[29]": { + "area": 312.0, + "bbox": [ + -3.0, + -9.0, + -1.0, + 3.0, + 9.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 216.0 + }, + "test_boxes[2]": { + "area": 38.0, + "bbox": [ + -4.5, + -0.5, + -0.5, + 4.5, + 0.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 9.0 + }, + "test_boxes[30]": { + "area": 384.0, + "bbox": [ + -6.0, + -6.0, + -1.0, + 6.0, + 6.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 288.0 + }, + "test_boxes[31]": { + "area": 222.0, + "bbox": [ + -1.5, + -8.5, + -1.5, + 1.5, + 8.5, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 153.0 + }, + "test_boxes[32]": { + "area": 822.0, + "bbox": [ + -9.0, + -8.5, + -1.5, + 9.0, + 8.5, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 918.0 + }, + "test_boxes[33]": { + "area": 1072.0, + "bbox": [ + -9.5, + -10.0, + -2.0, + 9.5, + 10.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 1519.9999999999998 + }, + "test_boxes[34]": { + "area": 220.0, + "bbox": [ + -1.5, + -7.0, + -2.0, + 1.5, + 7.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 168.0 + }, + "test_boxes[35]": { + "area": 279.99999999999994, + "bbox": [ + -5.0, + -3.0, + -2.5, + 5.0, + 3.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 299.99999999999994 + }, + "test_boxes[36]": { + "area": 418.0, + "bbox": [ + -6.5, + -7.0, + -0.5, + 6.5, + 7.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 182.00000000000003 + }, + "test_boxes[37]": { + "area": 202.0, + "bbox": [ + -2.5, + -6.5, + -1.0, + 2.5, + 6.5, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 129.99999999999997 + }, + "test_boxes[38]": { + "area": 220.0, + "bbox": [ + -7.0, + -2.0, + -1.5, + 7.0, + 2.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 168.0 + }, + "test_boxes[39]": { + "area": 278.0, + "bbox": [ + -4.5, + -5.5, + -1.0, + 4.5, + 5.5, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 198.0 + }, + "test_boxes[3]": { + "area": 352.0, + "bbox": [ + -4.0, + -8.0, + -1.0, + 4.0, + 8.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 255.99999999999994 + }, + "test_boxes[40]": { + "area": 286.0, + "bbox": [ + -5.5, + -5.5, + -0.5, + 5.5, + 5.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 121.0 + }, + "test_boxes[41]": { + "area": 910.0, + "bbox": [ + -9.5, + -7.5, + -2.5, + 9.5, + 7.5, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 1425.0 + }, + "test_boxes[42]": { + "area": 148.0, + "bbox": [ + -2.5, + -3.0, + -2.0, + 2.5, + 3.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 120.0 + }, + "test_boxes[43]": { + "area": 378.0, + "bbox": [ + -3.0, + -9.5, + -1.5, + 3.0, + 9.5, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 342.0 + }, + "test_boxes[44]": { + "area": 126.0, + "bbox": [ + -1.5, + -4.5, + -1.5, + 1.5, + 4.5, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 81.0 + }, + "test_boxes[45]": { + "area": 502.0, + "bbox": [ + -8.5, + -6.5, + -0.5, + 8.5, + 6.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 221.0 + }, + "test_boxes[46]": { + "area": 58.0, + "bbox": [ + -1.0, + -4.5, + -0.5, + 1.0, + 4.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 17.999999999999996 + }, + "test_boxes[47]": { + "area": 38.0, + "bbox": [ + -1.5, + -2.0, + -0.5, + 1.5, + 2.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 12.0 + }, + "test_boxes[48]": { + "area": 471.9999999999999, + "bbox": [ + -7.0, + -6.5, + -1.0, + 7.0, + 6.5, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 364.0 + }, + "test_boxes[49]": { + "area": 267.99999999999994, + "bbox": [ + -7.0, + -4.0, + -0.5, + 7.0, + 4.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 112.0 + }, + "test_boxes[4]": { + "area": 106.0, + "bbox": [ + -0.5, + -8.5, + -1.0, + 0.5, + 8.5, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 34.0 + }, + "test_boxes[5]": { + "area": 362.0, + "bbox": [ + -6.5, + -6.0, + -0.5, + 6.5, + 6.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 156.0 + }, + "test_boxes[6]": { + "area": 88.0, + "bbox": [ + -3.0, + -1.0, + -2.0, + 3.0, + 1.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 48.0 + }, + "test_boxes[7]": { + "area": 766.0, + "bbox": [ + -8.5, + -7.5, + -2.0, + 8.5, + 7.5, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 1020.0 + }, + "test_boxes[8]": { + "area": 433.99999999999994, + "bbox": [ + -8.5, + -3.0, + -2.5, + 8.5, + 3.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 510.0 + }, + "test_boxes[9]": { + "area": 112.0, + "bbox": [ + -9.0, + -1.0, + -0.5, + 9.0, + 1.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 36.0 + } + }, + "status": "ok" + }, + "examples/pegboard_j_hook": { + "shapes": { + "l1": { + "area": 0.0, + "bbox": [ + -10.0, + 0.0, + 0.0, + 21.299999999999997, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 21.299999999999997, + 0.0, + 0.0, + 24.682893434829268, + 2.3687274840275916, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 24.682893434829268, + 2.3687274840275916, + 0.0, + 26.735014294783284, + 8.00688320874304, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 26.735014294783284, + 8.006883208743043, + 0.0, + 30.117907729612554, + 10.375610692770634, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l5": { + "area": 0.0, + "bbox": [ + 30.117907729612554, + 10.375610692770634, + 0.0, + 36.117907729612554, + 10.375610692770634, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l6": { + "area": 0.0, + "bbox": [ + -21.5, + -22.82528915964039, + 0.0, + -10.0, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l7": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 0.0, + 10.644499999999999, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "mainp": { + "area": 1809.6005320461445, + "bbox": [ + -24.144500100000002, + -27.166095039150477, + -2.544500099999999, + 36.117907729612554, + 13.020110692770633, + 2.5445001 + ], + "edges": 60, + "faces": 22, + "volume": 2340.1516603407586 + }, + "sprof": { + "area": 0.0, + "bbox": [ + -21.5, + -24.5617709363097, + 0.0, + 36.117907729612554, + 10.375610692770634, + 0.0 + ], + "edges": 7, + "faces": 0, + "volume": 0.0 + }, + "stub": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 0.0, + 10.644499999999999, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/pegboard_j_hook_algebra": { + "shapes": { + "l1": { + "area": 0.0, + "bbox": [ + -10.0, + 0.0, + 0.0, + 21.299999999999997, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 21.299999999999997, + 0.0, + 0.0, + 24.682893434829268, + 2.3687274840275916, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 24.682893434829268, + 2.3687274840275916, + 0.0, + 26.735014294783284, + 8.00688320874304, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 26.735014294783284, + 8.006883208743043, + 0.0, + 30.117907729612554, + 10.375610692770634, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l5": { + "area": 0.0, + "bbox": [ + 30.117907729612554, + 10.375610692770634, + 0.0, + 36.117907729612554, + 10.375610692770634, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l6": { + "area": 0.0, + "bbox": [ + -21.5, + -22.82528915964039, + 0.0, + -10.0, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l7": { + "area": 0.0, + "bbox": [ + -11.996954043169703, + -24.5617709363097, + 0.0, + -2.1488765130476235, + -22.82528915964039, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "mainp": { + "area": 1809.6005711185123, + "bbox": [ + -24.144500100000002, + -27.166095039150477, + -2.544500099999999, + 36.117907729612554, + 13.020110692770633, + 2.5445001000000005 + ], + "edges": 80, + "faces": 31, + "volume": 2340.1516963224026 + }, + "sprof": { + "area": 0.0, + "bbox": [ + -21.5, + -24.5617709363097, + 0.0, + 36.117907729612554, + 10.375610692770634, + 0.0 + ], + "edges": 7, + "faces": 0, + "volume": 0.0 + }, + "stub": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 0.0, + 10.644499999999999, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "wire": { + "area": 0.0, + "bbox": [ + -21.5, + -24.5617709363097, + 0.0, + 36.117907729612554, + 10.375610692770634, + 0.0 + ], + "edges": 7, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/pillow_block": { + "shapes": { + "pillow_block": { + "area": 13163.451210852161, + "bbox": [ + -40.0, + -30.000000000000682, + 0.0, + 40.0, + 30.0, + 10.0 + ], + "edges": 54, + "faces": 25, + "volume": 44436.460392133944 + }, + "plan": { + "area": 4778.539815981608, + "bbox": [ + -40.0, + -30.000000000000682, + 0.0, + 40.0, + 30.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/pillow_block_algebra": { + "shapes": { + "pillow_block": { + "area": 13163.451210852161, + "bbox": [ + -40.0, + -30.000000000000682, + 0.0, + 40.0, + 30.0, + 10.0 + ], + "edges": 54, + "faces": 25, + "volume": 44436.460392133944 + }, + "plan": { + "area": 4778.539815981608, + "bbox": [ + -40.0, + -30.000000000000682, + 0.0, + 40.0, + 30.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/platonic_solids": { + "shapes": { + "solids[0]": { + "area": 2.628655560595669, + "bbox": [ + -1.2279009295211316, + -1.0705332903609601, + -0.46708617948135794, + -0.39013305922876385, + -0.10503721422398593, + 0.46708617948135794 + ], + "edges": 30, + "faces": 12, + "volume": 0.3481454828903281 + }, + "solids[1]": { + "area": 1.732050807568878, + "bbox": [ + -1.2135254915624216, + 0.18327675510499938, + -0.5, + -0.4045084971874737, + 0.9922937494799471, + 0.5 + ], + "edges": 12, + "faces": 8, + "volume": 0.16666666666666674 + }, + "solids[2]": { + "area": 2.3936353458184843, + "bbox": [ + -0.17672142687075315, + -1.3555650134826274, + -0.42532540417602, + 0.7947554156206473, + -0.5465480191076799, + 0.42532540417602 + ], + "edges": 30, + "faces": 20, + "volume": 0.3170188387650511 + }, + "solids[3]": { + "area": 2.000000000000001, + "bbox": [ + -0.0547348959171024, + 0.5873046260031037, + -0.2886751345948129, + 0.6727688846669972, + 1.3148084065872034, + 0.2886751345948129 + ], + "edges": 12, + "faces": 6, + "volume": 0.19245008972987532 + }, + "solids[4]": { + "area": 1.154700538379252, + "bbox": [ + 0.711324865405187, + -0.2886751345948129, + -0.2886751345948129, + 1.288675134594813, + 0.2886751345948129, + 0.2886751345948129 + ], + "edges": 6, + "faces": 4, + "volume": 0.06415002990995845 + } + }, + "status": "ok" + }, + "examples/playing_cards": { + "shapes": { + "ace_spades": { + "area": 5368.318052955945, + "bbox": [ + -1.4959255122671514e-15, + 1.1686097468332243e-15, + -1e-07, + 63.500000200001175, + 88.9000002, + 1e-07 + ], + "edges": 51, + "faces": 3, + "volume": 0.0 + }, + "box": { + "area": 40327.220820985116, + "bbox": [ + -35.75000010168624, + -48.45000009999999, + 0.0, + 35.750000098315496, + 48.450000100000004, + 16.7000001 + ], + "edges": 225, + "faces": 74, + "volume": 41557.90012086231 + }, + "box_builder": { + "area": 22446.156889983973, + "bbox": [ + -35.75000000031827, + -48.449999999999996, + 0.0, + 35.75, + 48.449999999999996, + 14.7 + ], + "edges": 88, + "faces": 28, + "volume": 25320.596199425287 + }, + "hand": { + "area": 26823.958131195715, + "bbox": [ + -29.213006747145688, + -20.525695104648154, + -4.0000001, + 88.88348834942424, + 104.06436928045393, + 1e-07 + ], + "edges": 238, + "faces": 11, + "volume": 0.0 + }, + "inset_walls": { + "area": 542.3200948094333, + "bbox": [ + -33.500000000001734, + -46.199999999999996, + 0.0, + 33.5, + 46.199999999999996, + 0.0 + ], + "edges": 16, + "faces": 1, + "volume": 0.0 + }, + "jack_diamonds": { + "area": 5434.9791023194175, + "bbox": [ + -1.4959255122671514e-15, + 1.1686097468332243e-15, + -1e-07, + 63.500000200001175, + 88.9000002, + 1e-07 + ], + "edges": 36, + "faces": 1, + "volume": 0.0 + }, + "king_hearts": { + "area": 5320.512076373615, + "bbox": [ + -1.4959255122671514e-15, + 1.1686097468332243e-15, + -1e-07, + 63.500000200001175, + 88.9000002, + 1e-07 + ], + "edges": 56, + "faces": 1, + "volume": 0.0 + }, + "lid_builder": { + "area": 17881.063931001125, + "bbox": [ + -35.75000010168624, + -48.45000009999999, + 0.0, + 35.750000098315496, + 48.450000100000004, + 8.350000099999999 + ], + "edges": 137, + "faces": 46, + "volume": 16237.303921437033 + }, + "outset_walls": { + "area": 567.060136956453, + "bbox": [ + -35.750000000001734, + -48.449999999999996, + 0.0, + 35.75, + 48.449999999999996, + 0.0 + ], + "edges": 16, + "faces": 1, + "volume": 0.0 + }, + "plan": { + "area": 6912.966386503612, + "bbox": [ + -35.750000000001734, + -48.449999999999996, + 0.0, + 35.75, + 48.449999999999996, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "queen_clubs": { + "area": 5341.759604328184, + "bbox": [ + -1.4959255122671514e-15, + 1.1686097468332243e-15, + -1e-07, + 63.500000200001175, + 88.9000002, + 1e-07 + ], + "edges": 48, + "faces": 3, + "volume": 0.0 + }, + "ten_spades": { + "area": 5358.389295218552, + "bbox": [ + -1.4959255122671514e-15, + 1.1686097468332243e-15, + -1e-07, + 63.500000200001175, + 88.9000002, + 1e-07 + ], + "edges": 47, + "faces": 3, + "volume": 0.0 + }, + "top": { + "area": 6912.966386503612, + "bbox": [ + -35.750000000001734, + -48.449999999999996, + 0.0, + 35.75, + 48.449999999999996, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "walls": { + "area": 1267.8631220181535, + "bbox": [ + -35.750000000001734, + -48.449999999999996, + 0.0, + 35.75, + 48.449999999999996, + 0.0 + ], + "edges": 16, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/projection": { + "shapes": { + "arch_path": { + "area": 0.0, + "bbox": [ + -48.98979494059401, + -49.48716602599162, + -7.142857244362895, + 48.98979495566356, + 49.48716603053936, + 10.0000001 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "arch_path_start": { + "area": 0.0, + "bbox": [ + 48.98979485566356, + -1.4210854715202004e-14, + 10.0, + 48.98979485566356, + -1.4210854715202004e-14, + 10.0 + ], + "edges": 0, + "faces": 0, + "volume": 0.0 + }, + "flat_planar_text_faces[0]": { + "area": 115.9265244102478, + "bbox": [ + -21.6900390625, + -8.326674378754569e-16, + -7.500001525878907, + -7.020019531249998, + 1.5953924853841055e-15, + 14.370018005371094 + ], + "edges": 10, + "faces": 1, + "volume": 0.0 + }, + "flat_planar_text_faces[1]": { + "area": 55.112876367568965, + "bbox": [ + -4.320019531250001, + -8.32667437875457e-16, + -7.500001525878908, + -1.8000000000000007, + 1.5953924853841055e-15, + 14.370018005371094 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "flat_planar_text_faces[2]": { + "area": 118.62244042992596, + "bbox": [ + 0.8999999, + -1.0000000090927716e-07, + -8.190040688378906, + 15.6900391625, + 1.0000000096256535e-07, + 8.670018105371094 + ], + "edges": 39, + "faces": 1, + "volume": 0.0 + }, + "flat_planar_text_faces[3]": { + "area": 65.68666660070419, + "bbox": [ + 16.530078025, + -1.0000000090927716e-07, + -8.190040688378906, + 23.730078225000003, + 1.0000000139222383e-07, + 12.540037636621093 + ], + "edges": 18, + "faces": 1, + "volume": 0.0 + }, + "flat_projected_text_faces": { + "area": 375.8696518533476, + "bbox": [ + -121.6900390625, + -149.96848944965197, + -8.19004063770358, + -76.269921775, + -142.6971285684709, + 14.3700180053711 + ], + "edges": 71, + "faces": 4, + "volume": 0.0 + }, + "flat_projection_beams": { + "area": 24970.198655057124, + "bbox": [ + -121.6900390625, + -180.0000001, + -8.190040688378906, + -76.269921775, + -99.9999999, + 14.370018005371094 + ], + "edges": 213, + "faces": 79, + "volume": 28427.880624675752 + }, + "projected_text": { + "area": 921.5007770178046, + "bbox": [ + -49.64700683297132, + -49.904988182728125, + -14.080162949122684, + 49.893182324546885, + 49.8903435467756, + 16.801341061604283 + ], + "edges": 602, + "faces": 41, + "volume": 0.0 + }, + "projection_beams[0]": { + "area": 13600.0, + "bbox": [ + -10.0000001, + -80.0000001, + -10.0000001, + 10.0000001, + 80.0000001, + 10.0000001 + ], + "edges": 12, + "faces": 6, + "volume": 64000.00000000001 + }, + "sphere": { + "area": 31415.926535897932, + "bbox": [ + -50.0, + -50.0, + -50.0, + 50.0, + 50.0, + 50.0 + ], + "edges": 1, + "faces": 1, + "volume": 523598.7755982988 + }, + "square": { + "area": 399.99999999999994, + "bbox": [ + -10.0, + -80.0, + -10.0, + 10.0, + -80.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "square_projected[0]": { + "area": 405.4884004082704, + "bbox": [ + -10.000000000000032, + -50.0, + -10.0, + 10.0, + -47.95831523312719, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "square_projected[1]": { + "area": 405.48840040823103, + "bbox": [ + -10.0, + 47.95831523312719, + -10.000000000000032, + 10.0, + 50.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "square_solids": { + "area": 2016.839572088195, + "bbox": [ + -10.400000100000035, + -52.0, + -10.400000100000032, + 10.400000100000003, + 52.0, + 10.400000100000195 + ], + "edges": 24, + "faces": 12, + "volume": 1687.6968274500937 + }, + "text": { + "area": 919.0971088992558, + "bbox": [ + 2.270406085358445e-14, + -7.1250001, + -1e-07, + 294.2999511718751, + 7.1250001, + 1e-07 + ], + "edges": 599, + "faces": 40, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/projection_algebra": { + "shapes": { + "arch_path": { + "area": 0.0, + "bbox": [ + -48.98979494059401, + -49.48716602599162, + -7.142857244362895, + 48.98979495566356, + 49.48716603053936, + 10.0000001 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "arch_path_start": { + "area": 0.0, + "bbox": [ + 48.98979485566356, + -1.4210854715202004e-14, + 10.0, + 48.98979485566356, + -1.4210854715202004e-14, + 10.0 + ], + "edges": 0, + "faces": 0, + "volume": 0.0 + }, + "cyl": { + "area": 90477.86842338605, + "bbox": [ + 0.0, + -80.0, + -80.0, + 100.0, + 80.0, + 80.0 + ], + "edges": 3, + "faces": 3, + "volume": 2010619.2982974676 + }, + "face": { + "area": 399.99999999999994, + "bbox": [ + -10.0, + -80.0, + -10.0, + 10.0, + -80.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "flat_planar_text": { + "area": 355.3485078084469, + "bbox": [ + -21.6900390625, + -1.0000000181855432e-07, + -8.190040688378906, + 23.730078225000003, + 1.0000000278444767e-07, + 14.370018005371094 + ], + "edges": 71, + "faces": 4, + "volume": 0.0 + }, + "flat_projected_text_faces": { + "area": 375.87739211226085, + "bbox": [ + -21.690039062500013, + -49.96848944965196, + -8.190040688378906, + 23.730078225000003, + -42.6971285684709, + 14.370018005371108 + ], + "edges": 40, + "faces": 4, + "volume": 0.0 + }, + "flat_projection_beams": { + "area": 24970.198655057124, + "bbox": [ + -21.6900390625, + -80.0000001, + -8.190040688378906, + 23.730078225000003, + 1.0000000278444767e-07, + 14.370018005371094 + ], + "edges": 213, + "faces": 79, + "volume": 28427.880624675752 + }, + "obj": { + "area": 23379.16859557134, + "bbox": [ + -48.98979494059401, + -50.0, + -7.142857244362895, + 48.98979495566356, + 50.0, + 50.0 + ], + "edges": 2, + "faces": 2, + "volume": 215833.80603659086 + }, + "projected_text": { + "area": 921.5007770178046, + "bbox": [ + -49.64700683297132, + -49.904988182728125, + -14.080162949122684, + 49.893182324546885, + 49.8903435467756, + 16.801341061604283 + ], + "edges": 602, + "faces": 41, + "volume": 0.0 + }, + "projection_beams": { + "area": 13600.0, + "bbox": [ + -10.0000001, + -80.0000001, + -10.0000001, + 10.0000001, + 80.0000001, + 10.0000001 + ], + "edges": 12, + "faces": 6, + "volume": 64000.00000000001 + }, + "sphere": { + "area": 31415.926535897932, + "bbox": [ + -50.0, + -50.0, + -50.0, + 50.0, + 50.0, + 50.0 + ], + "edges": 1, + "faces": 1, + "volume": 523598.7755982988 + }, + "square": { + "area": 399.99999999999994, + "bbox": [ + -10.0, + -80.0, + -10.0, + 10.0, + -80.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "square_projected[0]": { + "area": 405.4884004082704, + "bbox": [ + -10.000000000000032, + -50.0, + -10.0, + 10.0, + -47.95831523312719, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "square_projected[1]": { + "area": 405.48840040823103, + "bbox": [ + -10.0, + 47.95831523312719, + -10.000000000000032, + 10.0, + 50.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "square_solids": { + "area": 2016.839572088195, + "bbox": [ + -10.400000100000035, + -52.0, + -10.400000100000032, + 10.400000100000003, + 52.0, + 10.400000100000195 + ], + "edges": 24, + "faces": 12, + "volume": 1687.6968274500937 + }, + "text": { + "area": 919.0971088992558, + "bbox": [ + 2.270406085358445e-14, + -7.1250001, + -1e-07, + 294.2999511718751, + 7.1250001, + 1e-07 + ], + "edges": 599, + "faces": 40, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/python_logo": { + "status": "no-shapes" + }, + "examples/roller_coaster": { + "shapes": { + "corner": { + "area": 0.0, + "bbox": [ + 100.0, + 0.0, + 0.0, + 130.0, + 60.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "powerup": { + "area": 0.0, + "bbox": [ + -1e-07, + -1e-07, + -1e-07, + 100.0000001, + 1e-07, + 50.00000010000001 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "roller_coaster": { + "area": 0.0, + "bbox": [ + -109.5901135328278, + -1e-07, + -1.000013592516275e-07, + 130.0, + 60.0000001, + 50.0000001 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "screw": { + "area": 0.0, + "bbox": [ + -75.00000009999991, + 24.999999900014025, + -1e-07, + 75.0000001, + 55.00000009998598, + 30.000000100000474 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/roller_coaster_algebra": { + "shapes": { + "corner": { + "area": 0.0, + "bbox": [ + 100.0, + 0.0, + 0.0, + 130.0, + 60.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "powerup": { + "area": 0.0, + "bbox": [ + -1e-07, + -1e-07, + -1e-07, + 100.0000001, + 1e-07, + 50.00000010000001 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "roller_coaster": { + "area": 0.0, + "bbox": [ + -109.5901135328278, + -1e-07, + -1.000013592516275e-07, + 130.0, + 60.0000001, + 50.0000001 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "screw": { + "area": 0.0, + "bbox": [ + -75.00000009999991, + 24.999999900014025, + -1e-07, + 75.0000001, + 55.00000009998598, + 30.000000100000474 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/shamrock": { + "shapes": { + "shamrock_example": { + "area": 55.95029074173247, + "bbox": [ + -4.250063872754515, + -5.000000098279332, + -1e-07, + 4.250063872754515, + 5.000000098279333, + 1e-07 + ], + "edges": 11, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/stud_wall": { + "shapes": { + "x_wall": { + "area": 8330382.167734902, + "bbox": [ + -3.730349362740526e-14, + -44.45, + -8.828493491819245e-13, + 3962.3999999999996, + 44.45, + 2438.4000000000005 + ], + "edges": 312, + "faces": 130, + "volume": 113679138.17586128 + }, + "y_wall": { + "area": 5994756.697212661, + "bbox": [ + -1.3233858453531795e-14, + 44.449999999999804, + -6.092903959142859e-13, + 88.9000000000003, + 2787.6499999999996, + 2438.4000000000005 + ], + "edges": 240, + "faces": 100, + "volume": 81746795.99163055 + } + }, + "status": "ok" + }, + "examples/tea_cup": { + "error": "AssertionError", + "status": "error" + }, + "examples/tea_cup_algebra": { + "shapes": { + "bowl_section": { + "area": 5888.2371230476865, + "bbox": [ + -1e-07, + -1e-07, + -1.0000000177635683e-07, + 69.0000001, + 1e-07, + 105.0000001 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "handle_cross_section": { + "area": 22.14506729993644, + "bbox": [ + 54.75340194148883, + -4.0, + 34.062957428489106, + 57.09600837026606, + 4.0, + 35.937042571510894 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "path_spline": { + "area": 0.0, + "bbox": [ + 55.924705055877446, + -1e-07, + 34.9999999, + 100.11387722320589, + 1e-07, + 100.90946051908081 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "s": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + -1e-07, + 69.00000009999994, + 105.0000001, + 1e-07 + ], + "edges": 5, + "faces": 0, + "volume": 0.0 + }, + "tea_cup": { + "area": 87984.57116055215, + "bbox": [ + -67.77624350150904, + -67.77624539686997, + 0.0, + 101.6138781423565, + 67.77624539687, + 105.00000010000007 + ], + "edges": 68, + "faces": 28, + "volume": 130326.75447606308 + } + }, + "status": "ok" + }, + "examples/toy_truck": { + "shapes": { + "body": { + "area": 2686.7660397470236, + "bbox": [ + -11.000000099999996, + -17.5, + -1.0000001000000005, + 11.000000100000001, + 20.300000100000005, + 10.000000100000005 + ], + "edges": 353, + "faces": 145, + "volume": 6160.103112027914 + }, + "body_skt": { + "area": 699.1415926487991, + "bbox": [ + -10.000000000000696, + -17.5, + 0.0, + 10.0, + 17.5, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "bumper": { + "area": 169.15956892216423, + "bbox": [ + -11.000000099999996, + 17.764101515137757, + -1.0000001000000005, + 11.000000100000001, + 20.300000100000005, + 1.0000001000000005 + ], + "edges": 52, + "faces": 24, + "volume": 85.20641354750668 + }, + "bumper_plan": { + "area": 43.22033318946846, + "bbox": [ + -11.000000099999996, + 17.764101515137757, + -1.0000000002775557e-07, + 11.000000100000001, + 20.300000100000005, + 9.999999997224442e-08 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "cab": { + "area": 797.8127178461689, + "bbox": [ + -8.0000001, + -15.500000099999829, + 9.9999999, + 8.0000001, + 1.3771958444962784, + 16.922554570078084 + ], + "edges": 112, + "faces": 43, + "volume": 473.9017698761655 + }, + "cab_plan": { + "area": 127.57079631255239, + "bbox": [ + 0.0, + -8.000000000000172, + 0.0, + 8.0, + 8.000000000000172, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "fender": { + "area": 103.4354385592997, + "bbox": [ + 0.0, + 0.0, + 0.0, + 18.0, + 6.000000057766622, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "front_window": { + "area": 166.7171458523731, + "bbox": [ + -7.6, + -5.500000000000098, + 0.0, + 7.6, + 5.500000000000098, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "grill": { + "area": 109.50221197868926, + "bbox": [ + -8.0, + 0.0, + 0.0, + 8.0, + 8.5, + 0.0 + ], + "edges": 24, + "faces": 1, + "volume": 0.0 + }, + "grill_perimeter": { + "area": 0.0, + "bbox": [ + -8.0, + 18.500000000000004, + 0.0, + 8.0, + 18.500000000000004, + 8.5 + ], + "edges": 6, + "faces": 0, + "volume": 0.0 + }, + "rear_window": { + "area": 31.517146300668532, + "bbox": [ + -4.0, + -2.000000031294789, + 0.0, + 4.0, + 2.000000031294789, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "side_window": { + "area": 88.10218842939275, + "bbox": [ + -6.000000000000001, + 0.0, + 0.0, + 12.90412319597138, + 5.500000000000048, + 0.0 + ], + "edges": 12, + "faces": 2, + "volume": 0.0 + }, + "wheel_well": { + "area": 43.33269367002239, + "bbox": [ + -2.220446049250313e-16, + -8.881784197001252e-16, + 0.0, + 12.0, + 4.000000009404593, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/twist_extrude": { + "shapes": { + "hex_sketch": { + "area": 2.598076211353316, + "bbox": [ + -1.0, + -0.8660254037844386, + 0.0, + 1.0, + 0.8660254037844387, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "twist_extrude": { + "area": 35.274985576271064, + "bbox": [ + -1.0000086048838088, + -0.9999950866681123, + -1e-07, + 1.000008604883809, + 0.9999950866681125, + 5.0000001 + ], + "edges": 18, + "faces": 8, + "volume": 12.990434585813642 + } + }, + "status": "ok" + }, + "examples/vase": { + "shapes": { + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 12.0, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 11.925073826078204, + -4.440892098500626e-16, + 0.0, + 14.999999999999993, + 20.000000000000007, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 14.999999899999994, + 19.999999900000006, + -1e-07, + 22.098432090885872, + 50.000000099999994, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 19.330127018922195, + 50.0, + 0.0, + 20.0, + 55.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l5": { + "area": 0.0, + "bbox": [ + 19.9999999, + 54.9999999, + -1e-07, + 22.63266188224813, + 60.00000010000001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "outline": { + "area": 0.0, + "bbox": [ + 0.0, + -4.440892098500626e-16, + -1e-07, + 22.63266188224813, + 61.0, + 1e-07 + ], + "edges": 8, + "faces": 0, + "volume": 0.0 + }, + "profile": { + "area": 1077.9809577204992, + "bbox": [ + -1e-07, + -1.000000004440892e-07, + -1e-07, + 22.63266188224813, + 61.0000001, + 1e-07 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "vase": { + "area": 15208.407680709272, + "bbox": [ + -22.63266188224813, + -1.0000000077715611e-07, + -22.63266188224813, + 22.63266188224813, + 61.0000001, + 22.63266188224813 + ], + "edges": 34, + "faces": 19, + "volume": 7560.707918295803 + } + }, + "status": "ok" + }, + "examples/vase_algebra": { + "shapes": { + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 12.0, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 11.925073826078204, + -4.440892098500626e-16, + 0.0, + 14.999999999999993, + 20.000000000000007, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 14.999999899999994, + 19.999999900000006, + -1e-07, + 22.098432090885872, + 50.000000099999994, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 19.330127018922195, + 50.0, + 0.0, + 20.0, + 55.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l5": { + "area": 0.0, + "bbox": [ + 19.9999999, + 54.9999999, + -1e-07, + 22.63266188224813, + 60.00000010000001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "outline": { + "area": 0.0, + "bbox": [ + 0.0, + -4.440892098500626e-16, + -1e-07, + 22.63266188224813, + 61.0, + 1e-07 + ], + "edges": 8, + "faces": 0, + "volume": 0.0 + }, + "profile": { + "area": 1077.9809577204992, + "bbox": [ + -1e-07, + -1.000000004440892e-07, + -1e-07, + 22.63266188224813, + 61.0000001, + 1e-07 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "vase": { + "area": 15208.407680709272, + "bbox": [ + -22.63266188224813, + -1.0000000077715611e-07, + -22.63266188224813, + 22.63266188224813, + 61.0, + 22.63266188224813 + ], + "edges": 34, + "faces": 19, + "volume": 7560.707918295803 + } + }, + "status": "ok" + }, + "general_examples/ex01": { + "shapes": { + "ex1": { + "area": 12399.999999999996, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 12, + "faces": 6, + "volume": 48000.0 + } + }, + "status": "ok" + }, + "general_examples/ex02": { + "shapes": { + "ex2": { + "area": 12330.884961621023, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 15, + "faces": 7, + "volume": 44198.67288915635 + } + }, + "status": "ok" + }, + "general_examples/ex03": { + "shapes": { + "ex3": { + "area": 30559.289474462013, + "bbox": [ + -60.0, + -60.0, + 0.0, + 60.0, + 60.0, + 20.0 + ], + "edges": 15, + "faces": 7, + "volume": 202194.6710584651 + }, + "ex3_sk": { + "area": 10109.733552923255, + "bbox": [ + -60.0, + -60.0, + 0.0, + 60.0, + 60.0, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex08": { + "shapes": { + "ex8": { + "area": 11916.0, + "bbox": [ + 0.0, + -10.0, + -10.0, + 100.0, + 10.0, + 10.0 + ], + "edges": 36, + "faces": 14, + "volume": 5800.0 + }, + "ex8_ln": { + "area": 0.0, + "bbox": [ + -10.0, + -10.0, + 0.0, + 10.0, + 10.0, + 0.0 + ], + "edges": 14, + "faces": 0, + "volume": 0.0 + }, + "ex8_sk": { + "area": 57.99999999999997, + "bbox": [ + -10.0, + -10.0, + 0.0, + 10.0, + 10.0, + 0.0 + ], + "edges": 12, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex09": { + "shapes": { + "ex9": { + "area": 11629.753883596593, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.000000000000014 + ], + "edges": 36, + "faces": 14, + "volume": 45706.90228944049 + } + }, + "status": "ok" + }, + "general_examples/ex10": { + "shapes": { + "ex10": { + "area": 11849.637421324715, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 17, + "faces": 8, + "volume": 40848.10405858136 + } + }, + "status": "ok" + }, + "general_examples/ex11": { + "shapes": { + "ex11": { + "area": 11779.433551358667, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.000000000000014 + ], + "edges": 101, + "faces": 36, + "volume": 36177.36505728397 + }, + "ex11_sk": { + "area": 237.76412907378844, + "bbox": [ + -24.045084971874736, + -19.755282581475768, + 0.0, + 25.0, + 19.755282581475768, + 0.0 + ], + "edges": 20, + "faces": 4, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex12": { + "shapes": { + "ex12": { + "area": 4698.413109265178, + "bbox": [ + -1.0000004796163466e-07, + -1e-07, + -1e-07, + 60.0000001, + 35.08289914429867, + 10.0000001 + ], + "edges": 12, + "faces": 6, + "volume": 14627.106295339287 + }, + "ex12_ln": { + "area": 0.0, + "bbox": [ + -1.0000004796163466e-07, + 0.0, + -1e-07, + 60.0, + 35.08289914429867, + 1e-07 + ], + "edges": 4, + "faces": 0, + "volume": 0.0 + }, + "ex12_sk": { + "area": 1462.767056160497, + "bbox": [ + -1.0000004796163466e-07, + -1e-07, + -1e-07, + 60.0000001, + 35.08289914429867, + 1e-07 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -1.0000004796163466e-07, + 18.603717035290245, + -1e-07, + 55.0000001, + 35.08289914429867, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 55.0, + 0.0, + 0.0, + 60.0, + 30.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 60.0, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 0.0, + 20.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex13": { + "shapes": { + "ex13": { + "area": 20311.6827882315, + "bbox": [ + -50.0, + -50.0, + -5.0, + 50.0, + 50.0, + 5.000000000000002 + ], + "edges": 47, + "faces": 23, + "volume": 70872.25969468078 + } + }, + "status": "ok" + }, + "general_examples/ex14": { + "shapes": { + "ex14": { + "area": 19742.386437027966, + "bbox": [ + -160.00000000000023, + -49.99999999999995, + -10.0, + 10.0, + 50.0, + 10.0 + ], + "edges": 24, + "faces": 10, + "volume": 91398.2236861551 + }, + "ex14_ln": { + "area": 0.0, + "bbox": [ + -160.0, + -39.99999999999995, + 0.0, + 0.0, + 40.0, + 0.0 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + }, + "ex14_sk": { + "area": 399.99999999999994, + "bbox": [ + -10.0, + -10.0, + 0.0, + 10.0, + 10.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -80.0, + 0.0, + 0.0, + 0.0, + 40.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + -120.00000000000001, + -39.99999999999995, + 0.0, + -80.0, + 2.2662155590591917e-14, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + -160.0, + -39.99999999999995, + 0.0, + -120.00000000000001, + 4.973799150320701e-14, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex15": { + "shapes": { + "ex15": { + "area": 16800.0, + "bbox": [ + -80.0, + 0.0, + 0.0, + 80.0, + 40.0, + 20.0 + ], + "edges": 24, + "faces": 10, + "volume": 79999.99999999999 + }, + "ex15_ln": { + "area": 0.0, + "bbox": [ + -80.0, + 0.0, + 0.0, + 80.0, + 40.0, + 0.0 + ], + "edges": 10, + "faces": 0, + "volume": 0.0 + }, + "ex15_sk": { + "area": 3999.999999999999, + "bbox": [ + -80.0, + 0.0, + 0.0, + 80.0, + 40.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 80.0, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 80.0, + 0.0, + 0.0, + 80.0, + 40.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 60.0, + 40.0, + 0.0, + 80.0, + 40.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 60.0, + 20.0, + 0.0, + 60.0, + 40.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l5": { + "area": 0.0, + "bbox": [ + 0.0, + 20.0, + 0.0, + 60.0, + 20.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex16": { + "shapes": { + "ex16": { + "area": 167358.46173281362, + "bbox": [ + -160.0, + -80.0, + -150.0, + 160.0, + 0.0, + 150.0000000000002 + ], + "edges": 195, + "faces": 75, + "volume": 1297854.8727899147 + }, + "ex16_single": { + "area": 33471.69234656273, + "bbox": [ + -40.0, + -80.0, + -30.00000000000021, + 40.0, + 0.0, + 30.0 + ], + "edges": 39, + "faces": 15, + "volume": 259570.97455798293 + }, + "ex16_sk": { + "area": 3244.6371820133463, + "bbox": [ + -40.0, + -30.00000000000021, + 0.0, + 40.0, + 30.0, + 0.0 + ], + "edges": 13, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex17": { + "shapes": { + "ex17": { + "area": 14202.247068664105, + "bbox": [ + -45.00000000000017, + -74.69694854648331, + 0.0, + 30.0, + 28.53169548885461, + 20.0 + ], + "edges": 24, + "faces": 10, + "volume": 85595.0864665638 + }, + "ex17_sk": { + "area": 2139.8771616640956, + "bbox": [ + -24.270509831248425, + -28.53169548885461, + 0.0, + 30.0, + 28.531695488854606, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex18": { + "shapes": { + "ex18": { + "area": 11829.753883596593, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.000000000000014 + ], + "edges": 48, + "faces": 18, + "volume": 44706.902289440506 + } + }, + "status": "ok" + }, + "general_examples/ex19": { + "shapes": { + "ex19": { + "area": 10786.261542700255, + "bbox": [ + -36.03875471609677, + -38.99711648727295, + 0.0, + 35.66116260882442, + 38.99711648727295, + 10.0 + ], + "edges": 27, + "faces": 11, + "volume": 41538.56826564553 + }, + "ex19_sk": { + "area": 4378.256301820968, + "bbox": [ + -36.03875471609677, + -38.99711648727295, + 0.0, + 40.0, + 38.99711648727295, + 0.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "ex19_sk2": { + "area": 628.3185307179585, + "bbox": [ + -46.03875471609677, + -27.35534956470232, + 0.0, + 50.0, + 10.0, + 0.0 + ], + "edges": 2, + "faces": 2, + "volume": 0.0 + }, + "topf": { + "area": 4378.256301820968, + "bbox": [ + -36.03875471609677, + -38.99711648727295, + 10.0, + 40.0, + 38.99711648727295, + 10.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "vtx": { + "area": 0.0, + "bbox": [ + 40.0, + 0.0, + 10.0, + 40.0, + 0.0, + 10.0 + ], + "edges": 0, + "faces": 0, + "volume": 0.0 + }, + "vtx2": { + "area": 0.0, + "bbox": [ + -36.03875471609677, + -17.35534956470232, + 10.0, + -36.03875471609677, + -17.35534956470232, + 10.0 + ], + "edges": 0, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex20": { + "shapes": { + "ex20": { + "area": 22453.096491487337, + "bbox": [ + -120.0, + -30.0, + -20.0, + 40.0, + 30.0, + 20.0 + ], + "edges": 15, + "faces": 9, + "volume": 123398.22368615503 + } + }, + "status": "ok" + }, + "general_examples/ex21": { + "shapes": { + "ex21": { + "area": 3805.530633309707, + "bbox": [ + -60.0, + -5.0, + 0.0, + 5.0, + 5.0, + 60.0 + ], + "edges": 8, + "faces": 5, + "volume": 9091.444627420231 + }, + "ex21_sk": { + "area": 78.53981633974482, + "bbox": [ + -5.0, + -5.0, + 0.0, + 5.0, + 5.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex22": { + "shapes": { + "ex22": { + "area": 13133.117581821516, + "bbox": [ + -40.0, + -30.0, + -5.000000000000007, + 40.0, + 30.0, + 5.0 + ], + "edges": 24, + "faces": 10, + "volume": 46778.13736363063 + }, + "ex22_sk": { + "area": 78.53981633974482, + "bbox": [ + -12.5, + -10.0, + 0.0, + 12.5, + 10.0, + 0.0 + ], + "edges": 4, + "faces": 4, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex23": { + "shapes": { + "ex23": { + "area": 12794.096414158657, + "bbox": [ + -25.0, + -25.0, + 0.0, + 25.0, + 25.0, + 60.0 + ], + "edges": 12, + "faces": 7, + "volume": 88619.09277001212 + }, + "ex23_ln": { + "area": 0.0, + "bbox": [ + -25.0, + 0.0, + 0.0, + -15.0, + 35.0, + 0.0 + ], + "edges": 6, + "faces": 0, + "volume": 0.0 + }, + "ex23_sk": { + "area": 1154.4679486213063, + "bbox": [ + -25.0, + 0.0, + 0.0, + 1.7763568394002505e-15, + 60.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -25.0, + 0.0, + 0.0, + -15.0, + 35.0, + 0.0 + ], + "edges": 5, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + -25.0, + 35.0, + 0.0, + -15.0, + 35.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex24": { + "shapes": { + "ex24": { + "area": 18101.68145896556, + "bbox": [ + -40.0000001, + -40.0000001, + -5.0, + 40.0000001, + 40.0000001, + 40.0000001 + ], + "edges": 27, + "faces": 12, + "volume": 89024.35585088223 + }, + "ex24_sk": { + "area": 2234.0214425527415, + "bbox": [ + -26.666666666666668, + -26.666666666666668, + 0.0, + 26.666666666666668, + 26.666666666666668, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "ex24_sk2": { + "area": 133.33333333333331, + "bbox": [ + -6.666666666666667, + -5.0, + 0.0, + 6.666666666666667, + 5.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex25": { + "shapes": { + "ex25": { + "area": 49792.349449952104, + "bbox": [ + -50.45084971874738, + -59.30853086060715, + 0.0, + 62.3606797749979, + 59.308530860607135, + 31.0 + ], + "edges": 60, + "faces": 26, + "volume": 24387.59273282052 + }, + "ex25_sk1": { + "area": 5944.103226844711, + "bbox": [ + -40.45084971874738, + -47.55282581475768, + 0.0, + 50.0, + 47.552825814757675, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "ex25_sk2": { + "area": 9197.188753666056, + "bbox": [ + -50.45084971874738, + -57.55282581475768, + 0.0, + 60.0, + 57.552825814757675, + 0.0 + ], + "edges": 10, + "faces": 1, + "volume": 0.0 + }, + "ex25_sk3": { + "area": 9246.300752309755, + "bbox": [ + -50.45084971874738, + -59.30853086060715, + 0.0, + 62.3606797749979, + 59.308530860607135, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex26": { + "shapes": { + "ex26": { + "area": 14511.999999999996, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 24, + "faces": 11, + "volume": 13952.0 + }, + "topf": { + "area": 4799.999999999999, + "bbox": [ + -40.0, + -30.0, + 5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex27": { + "shapes": { + "ex27": { + "area": 6464.380550980764, + "bbox": [ + -40.0, + -8.942397556322032e-15, + -5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 18, + "faces": 8, + "volume": 20465.708264711477 + }, + "ex27_sk": { + "area": 706.8583470577034, + "bbox": [ + -15.0, + -15.0, + 0.0, + 15.0, + 15.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex28": { + "shapes": { + "ex28": { + "area": 26010.66992507376, + "bbox": [ + -40.00000009999812, + -40.0, + -40.0, + 40.0000001, + 40.0, + 40.0 + ], + "edges": 27, + "faces": 7, + "volume": 251188.19571970133 + }, + "ex28_ex": { + "area": 2078.4609690826524, + "bbox": [ + -10.000000000000009, + -17.320508075688767, + 0.0, + 20.0, + 17.320508075688775, + 10.0 + ], + "edges": 9, + "faces": 5, + "volume": 5196.152422706632 + }, + "ex28_sk": { + "area": 519.6152422706632, + "bbox": [ + -10.000000000000009, + -17.320508075688767, + 0.0, + 20.0, + 17.320508075688775, + 0.0 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "face": { + "area": 346.4101615137754, + "bbox": [ + -10.000000000000009, + -17.320508075688767, + 0.0, + 20.0, + 0.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "midfaces[0]": { + "area": 346.41016151377534, + "bbox": [ + -10.000000000000009, + -17.320508075688767, + 0.0, + -9.999999999999996, + 17.320508075688775, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "midfaces[1]": { + "area": 346.4101615137754, + "bbox": [ + -10.000000000000009, + -17.320508075688767, + 0.0, + 20.0, + 0.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "midfaces[2]": { + "area": 346.41016151377534, + "bbox": [ + -9.999999999999996, + 0.0, + 0.0, + 20.0, + 17.320508075688775, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex29": { + "shapes": { + "ex29": { + "area": 35153.637381728615, + "bbox": [ + -4.440892098500626e-16, + -18.0, + -0.9000000000000004, + 60.0, + 18.0, + 96.0 + ], + "edges": 104, + "faces": 56, + "volume": 15796.616314840636 + }, + "ex29_ow_ln": { + "area": 0.0, + "bbox": [ + 0.0, + -18.0, + 0.0, + 60.0, + 18.0, + 0.0 + ], + "edges": 6, + "faces": 0, + "volume": 0.0 + }, + "ex29_ow_sk": { + "area": 1812.7981751915386, + "bbox": [ + 0.0, + -18.0, + 0.0, + 60.0, + 18.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 0.0, + 9.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 0.0, + 9.0, + 0.0, + 60.0, + 18.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 60.0, + 0.0, + 0.0, + 60.0, + 9.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "necktopf": { + "area": 254.46900494077323, + "bbox": [ + 21.000000000000004, + -9.000000000000002, + 96.0, + 39.0, + 8.999999999999998, + 96.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex30": { + "shapes": { + "ex30": { + "area": 19585.35478229689, + "bbox": [ + -60.0000001, + -40.00000010000001, + -1e-07, + 100.00000010000053, + 100.0000001, + 10.0000001 + ], + "edges": 21, + "faces": 9, + "volume": 64463.381199800904 + }, + "ex30_ln": { + "area": 0.0, + "bbox": [ + -60.0, + -40.0, + -1e-07, + 100.00000010000053, + 100.0, + 1e-07 + ], + "edges": 7, + "faces": 0, + "volume": 0.0 + }, + "ex30_sk": { + "area": 6446.340125697502, + "bbox": [ + -60.0000001, + -40.0000001, + -1e-07, + 100.00000010000053, + 100.0000001, + 1e-07 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "l0": { + "area": 0.0, + "bbox": [ + -60.0, + -40.0, + 0.0, + 100.0, + 100.0, + 0.0 + ], + "edges": 6, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -22.86940140297584, + -9.564478162344862, + -1e-07, + 100.00000010000053, + 41.15608373612789, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex31": { + "shapes": { + "ex31": { + "area": 5977.711776549692, + "bbox": [ + -52.5, + -49.21633369868303, + 0.0, + 52.5, + 49.21633369868303, + 3.0 + ], + "edges": 306, + "faces": 164, + "volume": 4991.9700328814715 + }, + "ex31_sk": { + "area": 1663.990010960491, + "bbox": [ + -52.5, + -49.21633369868303, + 0.0, + 52.5, + 49.21633369868303, + 0.0 + ], + "edges": 102, + "faces": 31, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex32": { + "shapes": { + "ex32": { + "area": 8501.690401232398, + "bbox": [ + -50.0, + -43.30127018922193, + 0.0, + 50.0, + 43.30127018922194, + 19.0 + ], + "edges": 90, + "faces": 44, + "volume": 14839.230484541325 + }, + "ex32_sk": { + "area": 2239.2304845413264, + "bbox": [ + -50.0, + -43.30127018922193, + 0.0, + 50.0, + 43.30127018922194, + 0.0 + ], + "edges": 30, + "faces": 7, + "volume": 0.0 + }, + "obj": { + "area": 200.0, + "bbox": [ + 11.339745962155614, + -43.30127018922193, + 0.0, + 28.66025403784439, + -25.980762113533157, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex33": { + "shapes": { + "ex33": { + "area": 5112.447327281725, + "bbox": [ + -51.0, + -47.63139720814412, + 0.0, + 45.0, + 42.4352447854375, + 11.0 + ], + "edges": 72, + "faces": 36, + "volume": 10840.0 + }, + "ex33_sk": { + "area": 1340.0, + "bbox": [ + -51.0, + -47.63139720814412, + 0.0, + 45.0, + 42.4352447854375, + 0.0 + ], + "edges": 24, + "faces": 6, + "volume": 0.0 + }, + "obj": { + "area": 450.0, + "bbox": [ + 7.0096189432334235, + -47.63139720814412, + 0.0, + 32.99038105676659, + -21.650635094610962, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex34": { + "shapes": { + "ex34": { + "area": 15251.740859121084, + "bbox": [ + -40.0000001, + -30.0000001, + -5.0, + 40.0000001, + 30.0000001, + 9.0000001 + ], + "edges": 198, + "faces": 82, + "volume": 47753.51022950544 + }, + "ex34_sk": { + "area": 335.2048288043953, + "bbox": [ + -25.912516326041665, + 5.838672220806777e-17, + -1e-07, + 25.91251632604167, + 18.800048928124998, + 1e-07 + ], + "edges": 28, + "faces": 5, + "volume": 0.0 + }, + "ex34_sk2": { + "area": 396.6430686960962, + "bbox": [ + -30.762491911979165, + -18.800049028125002, + -1e-07, + 30.76249191197917, + -1.1686097468332243e-15, + 1e-07 + ], + "edges": 34, + "faces": 5, + "volume": 0.0 + }, + "topf": { + "area": 4799.999999999999, + "bbox": [ + -40.0, + -30.0, + 5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex35": { + "shapes": { + "ex35": { + "area": 16471.23889803847, + "bbox": [ + -40.0, + -40.0, + -5.0, + 40.0, + 40.0, + 5.0 + ], + "edges": 48, + "faces": 18, + "volume": 49219.02754903829 + }, + "ex35_ln": { + "area": 0.0, + "bbox": [ + -29.999999999999993, + 7.888609052210118e-31, + 0.0, + 8.27565060379343e-16, + 29.999999999999993, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "ex35_ln2": { + "area": 0.0, + "bbox": [ + 7.888609052210118e-31, + -29.999999999999993, + 0.0, + 29.999999999999993, + 8.27565060379343e-16, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "ex35_sk": { + "area": 1478.0972450961729, + "bbox": [ + -34.999999999999986, + -34.999999999999986, + 0.0, + 34.999999999999986, + 34.999999999999986, + 0.0 + ], + "edges": 12, + "faces": 3, + "volume": 0.0 + }, + "topf": { + "area": 6399.999999999999, + "bbox": [ + -40.0, + -40.0, + 5.0, + 40.0, + 40.0, + 5.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex36": { + "shapes": { + "ex36": { + "area": 10787.997618106452, + "bbox": [ + -6.0000001, + -56.0000001, + -1e-07, + 6.0000001, + 56.0000001, + 56.0000001 + ], + "edges": 15, + "faces": 8, + "volume": 30298.935241110394 + }, + "ex36_sk": { + "area": 113.09733552923255, + "bbox": [ + -6.0, + 44.0, + 0.0, + 6.0, + 56.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "ex36_sk2": { + "area": 300.0, + "bbox": [ + -3.0, + -25.0, + 0.0, + 3.0, + 25.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex37": { + "shapes": { + "ex37": { + "area": 21.914146723649658, + "bbox": [ + -1.5, + -1.0, + -1.000000002220446e-07, + 1.0000001, + 3.0000001, + 1.0000001 + ], + "edges": 17, + "faces": 7, + "volume": 5.534291735082541 + }, + "ex37_sk": { + "area": 2.0, + "bbox": [ + -0.5, + 0.0, + 0.0, + 0.5, + 2.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex01": { + "shapes": { + "ex1": { + "area": 12399.999999999996, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 12, + "faces": 6, + "volume": 48000.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex02": { + "shapes": { + "ex2": { + "area": 12330.884961621023, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 15, + "faces": 7, + "volume": 44198.67288915635 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex03": { + "shapes": { + "ex3": { + "area": 30559.289474462013, + "bbox": [ + -60.0, + -60.0, + 0.0, + 60.0, + 60.0, + 20.0 + ], + "edges": 15, + "faces": 7, + "volume": 202194.6710584651 + }, + "sk3": { + "area": 10109.733552923255, + "bbox": [ + -60.0, + -60.0, + 0.0, + 60.0, + 60.0, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex08": { + "shapes": { + "ex8": { + "area": 11916.0, + "bbox": [ + 0.0, + -10.0, + -10.0, + 100.0, + 10.0, + 10.0 + ], + "edges": 36, + "faces": 14, + "volume": 5799.999999999997 + }, + "ln": { + "area": 0.0, + "bbox": [ + -10.0, + -10.0, + 0.0, + 10.0, + 10.0, + 0.0 + ], + "edges": 12, + "faces": 0, + "volume": 0.0 + }, + "sk8": { + "area": 57.99999999999994, + "bbox": [ + 0.0, + -10.0, + -10.0, + 0.0, + 10.0, + 10.0 + ], + "edges": 12, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex09": { + "shapes": { + "ex9": { + "area": 11629.753883596593, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.000000000000014 + ], + "edges": 36, + "faces": 14, + "volume": 45706.90228944049 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex10": { + "error": "NameError: name 'length' is not defined", + "status": "error" + }, + "general_examples_algebra/ex11": { + "shapes": { + "ex11": { + "area": 11779.433551358667, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.000000000000014 + ], + "edges": 101, + "faces": 36, + "volume": 36177.36505728397 + }, + "polygons": { + "area": 237.76412907378844, + "bbox": [ + -24.045084971874736, + -19.755282581475768, + 5.0, + 25.0, + 19.755282581475765, + 5.0 + ], + "edges": 20, + "faces": 4, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex12": { + "shapes": { + "ex12": { + "area": 4698.413109265178, + "bbox": [ + -1.0000004796163466e-07, + -1e-07, + -1e-07, + 60.0000001, + 35.08289914429867, + 10.0000001 + ], + "edges": 12, + "faces": 6, + "volume": 14627.106295339287 + }, + "l1": { + "area": 0.0, + "bbox": [ + -1.0000004796163466e-07, + 18.603717035290245, + -1e-07, + 55.0000001, + 35.08289914429867, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 55.0, + 0.0, + 0.0, + 60.0, + 30.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 60.0, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 0.0, + 20.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "sk12": { + "area": 1462.767056160497, + "bbox": [ + -1.0000004796163466e-07, + -1e-07, + -1e-07, + 60.0000001, + 35.08289914429867, + 1e-07 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex13": { + "shapes": { + "ex13": { + "area": 20311.6827882315, + "bbox": [ + -50.0, + -50.0, + -5.0, + 50.0, + 50.0, + 5.000000000000002 + ], + "edges": 47, + "faces": 23, + "volume": 70872.25969468078 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex14": { + "shapes": { + "ex14": { + "area": 19742.386437027966, + "bbox": [ + -160.00000000000023, + -49.99999999999995, + -10.0, + 10.0, + 50.0, + 10.0 + ], + "edges": 24, + "faces": 10, + "volume": 91398.2236861551 + }, + "ex14_ln": { + "area": 0.0, + "bbox": [ + -160.0, + -39.99999999999995, + 0.0, + 0.0, + 40.0, + 0.0 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -80.0, + 0.0, + 0.0, + 0.0, + 40.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + -120.00000000000001, + -39.99999999999995, + 0.0, + -80.0, + 2.2662155590591917e-14, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + -160.0, + -39.99999999999995, + 0.0, + -120.00000000000001, + 4.973799150320701e-14, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "sk14": { + "area": 399.99999999999994, + "bbox": [ + -10.0, + 0.0, + -10.0, + 10.0, + 0.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex15": { + "shapes": { + "ex15": { + "area": 16799.999999999996, + "bbox": [ + -80.0, + 0.0, + 0.0, + 80.0, + 40.0, + 20.0 + ], + "edges": 24, + "faces": 10, + "volume": 79999.99999999999 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 80.0, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 80.0, + 0.0, + 0.0, + 80.0, + 40.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 60.0, + 40.0, + 0.0, + 80.0, + 40.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 60.0, + 20.0, + 0.0, + 60.0, + 40.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l5": { + "area": 0.0, + "bbox": [ + 0.0, + 20.0, + 0.0, + 60.0, + 20.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "ln": { + "area": 0.0, + "bbox": [ + -80.0, + 0.0, + 0.0, + 80.0, + 40.0, + 0.0 + ], + "edges": 8, + "faces": 0, + "volume": 0.0 + }, + "sk15": { + "area": 3999.999999999999, + "bbox": [ + -80.0, + 0.0, + 0.0, + 80.0, + 40.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex16": { + "shapes": { + "circles[0]": { + "area": 139.62634015954634, + "bbox": [ + -26.666666666666668, + -6.666666666666667, + 0.0, + -13.333333333333332, + 6.666666666666667, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "circles[1]": { + "area": 139.62634015954634, + "bbox": [ + -6.666666666666667, + -6.666666666666667, + 0.0, + 6.666666666666667, + 6.666666666666667, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "circles[2]": { + "area": 139.62634015954634, + "bbox": [ + 13.333333333333332, + -6.666666666666667, + 0.0, + 26.666666666666668, + 6.666666666666667, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "ex16": { + "area": 167358.46173281362, + "bbox": [ + -160.0, + -80.0, + -150.0, + 160.0, + 0.0, + 150.0000000000002 + ], + "edges": 195, + "faces": 75, + "volume": 1297854.8727899147 + }, + "ex16_single": { + "area": 33471.69234656273, + "bbox": [ + -40.0, + -80.0, + -30.00000000000021, + 40.0, + 0.0, + 30.0 + ], + "edges": 39, + "faces": 15, + "volume": 259570.97455798293 + }, + "objs[0]": { + "area": 33471.69234656273, + "bbox": [ + -160.0, + -80.0, + -30.00000000000021, + -80.0, + 0.0, + 30.0 + ], + "edges": 39, + "faces": 15, + "volume": 259570.97455798293 + }, + "objs[1]": { + "area": 33471.69234656273, + "bbox": [ + -40.0, + -80.0, + -150.0, + 40.0, + 0.0, + -89.99999999999979 + ], + "edges": 39, + "faces": 15, + "volume": 259570.97455798293 + }, + "objs[2]": { + "area": 33471.69234656273, + "bbox": [ + -40.0, + -80.0, + 90.0, + 40.0, + 0.0, + 150.0000000000002 + ], + "edges": 39, + "faces": 15, + "volume": 259570.97455798293 + }, + "objs[3]": { + "area": 33471.69234656273, + "bbox": [ + 80.0, + -80.0, + -30.00000000000021, + 160.0, + 0.0, + 30.0 + ], + "edges": 39, + "faces": 15, + "volume": 259570.974557983 + }, + "sk16": { + "area": 3244.6371820133463, + "bbox": [ + -40.0, + -30.00000000000021, + 0.0, + 40.0, + 30.0, + 0.0 + ], + "edges": 13, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex17": { + "shapes": { + "ex17": { + "area": 14202.247068664106, + "bbox": [ + -45.00000000000017, + -74.69694854648331, + 0.0, + 30.0, + 28.531695488854606, + 20.0 + ], + "edges": 24, + "faces": 10, + "volume": 85595.08646656378 + }, + "sk17": { + "area": 2139.8771616640956, + "bbox": [ + -24.270509831248425, + -28.53169548885461, + 0.0, + 30.0, + 28.531695488854606, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex18": { + "shapes": { + "ex18": { + "area": 11829.753883596593, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.000000000000014 + ], + "edges": 48, + "faces": 18, + "volume": 44706.902289440506 + }, + "sk18": { + "area": 99.99999999999999, + "bbox": [ + -5.0, + -5.0, + -5.0, + 5.0, + 5.0, + -5.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex19": { + "shapes": { + "ex19": { + "area": 10786.261542700253, + "bbox": [ + -36.03875471609677, + -38.99711648727295, + 0.0, + 35.66116260882442, + 38.99711648727295, + 10.0 + ], + "edges": 27, + "faces": 11, + "volume": 41538.56826564553 + }, + "ex19_sk": { + "area": 4378.256301820968, + "bbox": [ + -36.03875471609677, + -38.99711648727295, + 0.0, + 40.0, + 38.99711648727295, + 0.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "ex19_sk2": { + "area": 628.3185307179585, + "bbox": [ + -46.03875471609677, + -27.35534956470232, + 0.0, + 50.0, + 10.0, + 0.0 + ], + "edges": 2, + "faces": 2, + "volume": 0.0 + }, + "topf": { + "area": 4378.256301820968, + "bbox": [ + -36.03875471609677, + -38.99711648727295, + 10.0, + 40.0, + 38.99711648727295, + 10.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "vtx": { + "area": 0.0, + "bbox": [ + 40.0, + 0.0, + 10.0, + 40.0, + 0.0, + 10.0 + ], + "edges": 0, + "faces": 0, + "volume": 0.0 + }, + "vtx2": { + "area": 0.0, + "bbox": [ + -36.03875471609677, + -17.35534956470232, + 10.0, + -36.03875471609677, + -17.35534956470232, + 10.0 + ], + "edges": 0, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex20": { + "shapes": { + "ex20": { + "area": 22453.096491487337, + "bbox": [ + -120.0, + -30.0, + -20.0, + 40.0, + 30.0, + 20.0 + ], + "edges": 15, + "faces": 9, + "volume": 123398.22368615503 + }, + "sk20": { + "area": 1256.637061435917, + "bbox": [ + -60.0, + -20.0, + -20.0, + -60.0, + 20.0, + 20.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex21": { + "shapes": { + "ex21": { + "area": 3805.5306333206363, + "bbox": [ + -60.0, + -5.0, + 0.0, + 5.0, + 5.0, + 60.0 + ], + "edges": 8, + "faces": 5, + "volume": 9091.44462742916 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex22": { + "shapes": { + "ex22": { + "area": 13133.117581821689, + "bbox": [ + -40.0, + -30.0, + -5.000000000000014, + 40.0, + 30.0, + 5.0 + ], + "edges": 24, + "faces": 10, + "volume": 46778.13736363046 + }, + "holes": { + "area": 78.5398163397448, + "bbox": [ + -8.03484512108174, + -10.000000000000007, + -14.575555538987226, + 8.03484512108174, + 9.999999999999993, + 4.575555538987225 + ], + "edges": 4, + "faces": 4, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex23": { + "shapes": { + "ex23": { + "area": 12794.096414158565, + "bbox": [ + -25.0, + -25.0, + 0.0, + 25.0, + 25.0, + 60.0 + ], + "edges": 12, + "faces": 7, + "volume": 88619.09277001217 + }, + "l1": { + "area": 0.0, + "bbox": [ + -25.0, + 0.0, + 0.0, + -15.0, + 35.0, + 0.0 + ], + "edges": 5, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + -25.0, + 35.0, + 0.0, + -15.0, + 35.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "sk23": { + "area": 1154.467948621305, + "bbox": [ + -25.0, + 0.0, + 0.0, + 1.7763568394002505e-15, + 0.0, + 60.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex24": { + "shapes": { + "ex24": { + "area": 18620.854281369207, + "bbox": [ + -40.0, + -40.0, + -5.0, + 40.0, + 40.0, + 45.0000001 + ], + "edges": 27, + "faces": 12, + "volume": 102969.87958520795 + }, + "faces": { + "area": 2367.354775886075, + "bbox": [ + -26.666666666666668, + -26.666666666666668, + 5.0, + 26.666666666666668, + 26.666666666666668, + 45.0 + ], + "edges": 5, + "faces": 2, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex25": { + "shapes": { + "ex25": { + "area": 49792.34944995208, + "bbox": [ + -50.45084971874738, + -59.30853086060715, + 0.0, + 62.3606797749979, + 59.308530860607135, + 31.000000000000007 + ], + "edges": 60, + "faces": 26, + "volume": 24387.59273282052 + }, + "sk25": { + "area": 24387.59273282052, + "bbox": [ + -50.45084971874738, + -59.30853086060715, + 0.0, + 62.3606797749979, + 59.308530860607135, + 30.000000000000007 + ], + "edges": 20, + "faces": 3, + "volume": 0.0 + }, + "sk25_1": { + "area": 5944.103226844711, + "bbox": [ + -40.45084971874738, + -47.55282581475768, + 0.0, + 50.0, + 47.552825814757675, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "sk25_2": { + "area": 9197.188753666056, + "bbox": [ + -50.45084971874738, + -57.55282581475768, + 15.000000000000004, + 60.0, + 57.552825814757675, + 15.000000000000004 + ], + "edges": 10, + "faces": 1, + "volume": 0.0 + }, + "sk25_3": { + "area": 9246.300752309755, + "bbox": [ + -50.45084971874738, + -59.30853086060715, + 30.000000000000007, + 62.3606797749979, + 59.308530860607135, + 30.000000000000007 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex26": { + "shapes": { + "ex26": { + "area": 14511.999999999996, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 24, + "faces": 11, + "volume": 13952.0 + }, + "topf": { + "area": 4799.999999999999, + "bbox": [ + -40.0, + -30.0, + 5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex27": { + "shapes": { + "ex27": { + "area": 6464.380550980764, + "bbox": [ + -40.0, + -8.942397556322032e-15, + -5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 18, + "faces": 8, + "volume": 20465.708264711477 + }, + "sk27": { + "area": 706.8583470577034, + "bbox": [ + -15.0, + -15.000000000000007, + -5.0, + 15.0, + 14.999999999999993, + -5.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex28": { + "shapes": { + "ex28": { + "area": 26010.669925071885, + "bbox": [ + -40.00000009999814, + -40.0, + -40.0, + 40.0000001, + 40.0, + 40.0 + ], + "edges": 27, + "faces": 7, + "volume": 251188.1957196978 + }, + "sk28": { + "area": 519.6152422706632, + "bbox": [ + -10.000000000000009, + -17.320508075688767, + 0.0, + 20.0, + 17.320508075688775, + 0.0 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "tmp28": { + "area": 2078.4609690826524, + "bbox": [ + -10.000000000000009, + -17.320508075688767, + 0.0, + 20.0, + 17.320508075688775, + 10.0 + ], + "edges": 9, + "faces": 5, + "volume": 5196.152422706632 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex29": { + "shapes": { + "ex29": { + "area": 35368.52231923416, + "bbox": [ + -4.440892098500626e-16, + -18.0, + -90.9, + 60.0, + 18.0, + 8.0 + ], + "edges": 104, + "faces": 56, + "volume": 15893.314536718119 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 0.0, + 9.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 0.0, + 9.0, + 0.0, + 60.0, + 18.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 60.0, + 0.0, + 0.0, + 60.0, + 9.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "ln29": { + "area": 0.0, + "bbox": [ + 0.0, + -18.0, + 0.0, + 60.0, + 18.0, + 0.0 + ], + "edges": 4, + "faces": 0, + "volume": 0.0 + }, + "neck": { + "area": 254.46900494077323, + "bbox": [ + 21.000000000000004, + -8.999999999999998, + 0.0, + 39.0, + 9.000000000000002, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "necktopf": { + "area": 254.46900494077323, + "bbox": [ + 21.000000000000004, + -8.999999999999998, + 8.0, + 39.0, + 9.000000000000002, + 8.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "sk29": { + "area": 1812.7981751915386, + "bbox": [ + 0.0, + -18.0, + 0.0, + 60.0, + 18.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex30": { + "shapes": { + "ex30": { + "area": 19585.35478229689, + "bbox": [ + -60.0000001, + -40.0000001, + -10.0000001, + 100.00000010000053, + 100.0000001, + 1e-07 + ], + "edges": 21, + "faces": 9, + "volume": 64463.381199800904 + }, + "ex30_ln": { + "area": 0.0, + "bbox": [ + -60.0, + -40.0, + -1e-07, + 100.00000010000053, + 100.0, + 1e-07 + ], + "edges": 7, + "faces": 0, + "volume": 0.0 + }, + "ex30_sk": { + "area": 6446.340125697501, + "bbox": [ + -60.0000001, + -40.0000001, + -1e-07, + 100.00000010000053, + 100.0000001, + 1e-07 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex31": { + "shapes": { + "ex31": { + "area": 5977.711776549694, + "bbox": [ + -52.5, + -49.21633369868303, + 0.0, + 52.5, + 49.21633369868303, + 3.0 + ], + "edges": 306, + "faces": 164, + "volume": 4991.970032881472 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex32": { + "shapes": { + "ex32": { + "area": 8501.690401232394, + "bbox": [ + -50.0, + -43.30127018922193, + 0.0, + 50.0, + 43.30127018922194, + 19.0 + ], + "edges": 90, + "faces": 44, + "volume": 14839.230484541325 + }, + "ex32_sk": { + "area": 2239.2304845413264, + "bbox": [ + -50.0, + -43.30127018922193, + 0.0, + 50.0, + 43.30127018922194, + 0.0 + ], + "edges": 30, + "faces": 7, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex33": { + "shapes": { + "ex33": { + "area": 5112.447327281724, + "bbox": [ + -51.0, + -47.63139720814412, + 0.0, + 45.0, + 42.4352447854375, + 11.0 + ], + "edges": 72, + "faces": 36, + "volume": 10840.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex34": { + "shapes": { + "ex34": { + "area": 15255.980977205254, + "bbox": [ + -40.0000001, + -30.0000001, + -5.0, + 40.0000001, + 30.0000001, + 9.0000001 + ], + "edges": 363, + "faces": 137, + "volume": 47754.582611832375 + }, + "ex34_sk": { + "area": 335.25362582753087, + "bbox": [ + -25.912516326041665, + -7.047040635392934e-15, + 4.9999999, + 25.91251632604167, + 18.80004892812499, + 5.0000001 + ], + "edges": 53, + "faces": 5, + "volume": 0.0 + }, + "ex34_sk2": { + "area": 396.6079728694424, + "bbox": [ + -30.762491911979165, + -18.800049028125006, + 4.9999999, + 30.76249191197917, + -8.274037104434226e-15, + 5.0000001 + ], + "edges": 64, + "faces": 5, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex35": { + "shapes": { + "ex35": { + "area": 16471.23889803847, + "bbox": [ + -40.0, + -40.0, + -5.0, + 40.0, + 40.0, + 5.0 + ], + "edges": 48, + "faces": 18, + "volume": 49219.02754903829 + }, + "ex35_ln": { + "area": 0.0, + "bbox": [ + -29.999999999999993, + 7.888609052210118e-31, + 0.0, + 8.27565060379343e-16, + 29.999999999999993, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "ex35_ln2": { + "area": 0.0, + "bbox": [ + 7.888609052210118e-31, + -29.999999999999993, + 0.0, + 29.999999999999993, + 8.27565060379343e-16, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "ex35_sk": { + "area": 1478.0972450961729, + "bbox": [ + -34.999999999999986, + -34.999999999999986, + 0.0, + 34.999999999999986, + 34.999999999999986, + 0.0 + ], + "edges": 12, + "faces": 3, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex36": { + "shapes": { + "ex36": { + "area": 10787.997618106452, + "bbox": [ + -6.0000001, + -56.0000001, + -1e-07, + 6.0000001, + 56.0000001, + 56.0000001 + ], + "edges": 15, + "faces": 8, + "volume": 30298.935241110394 + }, + "ex36_sk": { + "area": 113.09733552923255, + "bbox": [ + -6.0, + 44.0, + 0.0, + 6.0, + 56.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "ex36_sk2": { + "area": 300.0, + "bbox": [ + -3.0, + -25.0, + 0.0, + 3.0, + 25.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "ttt/ttt-23-02-02-sm_hanger": { + "shapes": { + "bottom_edge": { + "area": 0.0, + "bbox": [ + 55.0, + 47.512774239600375, + 44.28756974082995, + 55.0, + 51.37647754475615, + 45.32284592124189 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "fe": { + "area": 0.0, + "bbox": [ + 84.99999999999999, + 56.26, + 0.0, + 84.99999999999999, + 56.26, + 4.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "h1": { + "area": 4273.128283793375, + "bbox": [ + 20.0, + -15.0, + 0.0, + 241.0, + 15.0, + 0.0 + ], + "edges": 10, + "faces": 2, + "volume": 0.0 + }, + "h2": { + "area": 4434.1592653589805, + "bbox": [ + 93.0, + -10.0, + 0.0, + 319.0, + 10.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 65.0, + 0.0, + 46.104, + 65.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 85.0, + 0.0, + 0.0, + 122.52776749734468, + 0.0, + 65.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 85.0, + 0.0, + 0.0, + 122.52776749734468, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "side": { + "area": 18409.469311640918, + "bbox": [ + -1.0000000036739404e-07, + -1.0000002842170943e-07, + -1e-07, + 117.40341194435948, + 56.2600001, + 65.00000010000015 + ], + "edges": 41, + "faces": 17, + "volume": 33159.911280247536 + }, + "side_line": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 117.40341184435948, + 0.0, + 65.0000000000003 + ], + "edges": 5, + "faces": 0, + "volume": 0.0 + }, + "sm_hanger": { + "area": 73437.84063364491, + "bbox": [ + -117.40341194435948, + -56.2600001, + -1e-07, + 117.40341194435948, + 56.2600001, + 88.0 + ], + "edges": 347, + "faces": 121, + "volume": 131756.34943954204 + }, + "tab": { + "area": 681.4734305929795, + "bbox": [ + 20.0, + -1.2246467991473533e-15, + 61.0, + 27.999999999395058, + 8.0, + 88.0 + ], + "edges": 33, + "faces": 13, + "volume": 744.7875958452756 + }, + "tab_line": { + "area": 0.0, + "bbox": [ + 20.0, + 0.0, + 61.0, + 28.0, + 0.0, + 88.0 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + }, + "wing": { + "area": 7755.719418127756, + "bbox": [ + -1.0000002131628207e-07, + -1e-07, + 44.287569640829936, + 55.0000001, + 51.37647764475615, + 65.00000014936666 + ], + "edges": 27, + "faces": 11, + "volume": 13659.030992631924 + }, + "wing_line": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 45.32284592124189, + 0.0, + 51.37647754475614, + 65.00000009873332 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "ttt/ttt-23-t-24-curved_support": { + "shapes": { + "base_hull": { + "area": 6880.426598184431, + "bbox": [ + -27.5, + -27.5, + 0.0, + 27.5, + 140.0, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "base_plan": { + "area": 3082.6877913349845, + "bbox": [ + -27.5, + -27.5, + 0.0, + 27.5, + 140.0, + 0.0 + ], + "edges": 2, + "faces": 2, + "volume": 0.0 + }, + "bridge": { + "area": 5179.877817107864, + "bbox": [ + 0.0, + 0.0, + 0.0, + 125.0, + 50.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "c_8_degrees": { + "area": 2375.829444277281, + "bbox": [ + -27.5, + -27.5, + 0.0, + 27.5, + 27.5, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "curved_support": { + "area": 38781.015174540895, + "bbox": [ + -27.5, + -27.5, + 0.0, + 27.5, + 140.0, + 60.0 + ], + "edges": 47, + "faces": 18, + "volume": 165914.0718803271 + }, + "l1": { + "area": 0.0, + "bbox": [ + 27.5, + 46.321316523235964, + 0.0, + 53.675193028802, + 50.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 53.675193028802, + 42.046626191929846, + 0.0, + 65.41051915338532, + 46.321316523235964, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 65.41051915338532, + 32.0, + 0.0, + 100.41366129083305, + 42.046626191929846, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 125.0, + 50.0, + 0.0 + ], + "edges": 5, + "faces": 0, + "volume": 0.0 + }, + "profile": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 125.0, + 50.0, + 0.0 + ], + "edges": 8, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "ttt/ttt-24-SPO-06-Buffer_Stand": { + "shapes": { + "circle_edge": { + "area": 0.0, + "bbox": [ + -0.9159111568790768, + 3.178643013542057, + 0.0, + 0.9159111568790766, + 4.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "internals": { + "area": 39.42266724894786, + "bbox": [ + 0.0, + -1.223723941183581, + 0.2499999999999991, + 2.125, + 1.223723941183581, + 4.0 + ], + "edges": 21, + "faces": 9, + "volume": 15.12561291741207 + }, + "l1": { + "area": 0.0, + "bbox": [ + 2.5, + -1.2500000000000002, + 0.0, + 2.75, + 1.2500000000000009, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "p": { + "area": 80.19898864180658, + "bbox": [ + -2.7500001172116475, + -1.2500001000000005, + -1.0000000532907051e-07, + 2.7500001172116475, + 1.250000100000001, + 4.0000001 + ], + "edges": 72, + "faces": 28, + "volume": 13.921380784973927 + }, + "part": { + "area": 51741.17951214792, + "bbox": [ + -69.85000053717586, + -31.750000100000012, + -1.0000011368683772e-07, + 69.85000053717586, + 31.75000010000002, + 101.60000009999999 + ], + "edges": 72, + "faces": 28, + "volume": 228130.55789173767 + }, + "rib": { + "area": 0.3545854840344225, + "bbox": [ + -0.25, + 0.25, + 0.0, + 0.25, + 1.25, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "xy": { + "area": 6.669981201828734, + "bbox": [ + 0.0, + -1.2500000000000002, + 0.0, + 2.75, + 1.2500000000000009, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "xz": { + "area": 31.660398142333932, + "bbox": [ + -2.1250000000000107, + 0.25, + 0.0, + 2.1250000000000107, + 7.75, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "yz": { + "area": 8.318332235749178, + "bbox": [ + -1.25, + 0.0, + 0.0, + 1.25, + 4.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "ttt/ttt-ppp0101": { + "shapes": { + "bl": { + "area": 0.0, + "bbox": [ + -13.61880215351701, + 0.0, + 0.0, + 13.618802153517008, + 8.0, + 0.0 + ], + "edges": 6, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 9.0, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 9.0, + 0.0, + 0.0, + 13.618802153517008, + 8.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 0.0, + 8.0, + 0.0, + 13.618802153517008, + 8.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "p": { + "area": 30243.24648019587, + "bbox": [ + -57.5000001, + -25.00000010000011, + -1e-07, + 57.5, + 25.0000001, + 68.0000001 + ], + "edges": 84, + "faces": 32, + "volume": 102198.22251481404 + }, + "s": { + "area": 4700.902664470767, + "bbox": [ + -57.5, + -25.0, + 0.0, + 57.5, + 25.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "s3": { + "area": 4931.716633826699, + "bbox": [ + -57.50000000000001, + -38.0, + 0.0, + -5.499999999999993, + 68.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "s4": { + "area": 180.9504172281361, + "bbox": [ + -13.61880215351701, + 0.0, + 0.0, + 13.618802153517008, + 8.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "zz": { + "area": 13119.78708349343, + "bbox": [ + -57.50000000000001, + -25.0, + -38.0, + -5.499999999999993, + -13.0, + 68.0 + ], + "edges": 12, + "faces": 6, + "volume": 59180.599605920404 + } + }, + "status": "ok" + }, + "ttt/ttt-ppp0102": { + "shapes": { + "l1": { + "area": 0.0, + "bbox": [ + -32.0, + 0.0, + 3.0, + -14.999999999999998, + 0.0, + 37.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "lc1": { + "area": 0.0, + "bbox": [ + 18.41270795809011, + 0.0, + 0.0, + 21.0, + 37.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "p": { + "area": 15159.916261715422, + "bbox": [ + -34.0000001, + -24.5000001, + -1e-07, + 24.5000001, + 24.5000001, + 48.0000001 + ], + "edges": 17, + "faces": 10, + "volume": 42248.61825268254 + }, + "sk1": { + "area": 1141.6637061435918, + "bbox": [ + 0.0, + 0.0, + 0.0, + 24.5, + 48.0, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "sk2": { + "area": 727.4968856546463, + "bbox": [ + 0.0, + 0.0, + 0.0, + 21.0, + 37.00000000000001, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "xc1": { + "area": 31.41592653589793, + "bbox": [ + -5.0, + 1.0, + 0.0, + 5.0, + 5.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "ttt/ttt-ppp0103": { + "shapes": { + "cyl1": { + "area": 201.06192982974667, + "bbox": [ + -8.0, + 0.0, + 0.0, + 8.0, + 16.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "cyl2": { + "area": 201.06192982974667, + "bbox": [ + -8.0, + 0.0, + 0.0, + 8.0, + 16.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "ppp0103": { + "area": 10058.45491355111, + "bbox": [ + -8.0, + -56.5, + 0.0, + 34.000000000000014, + 47.5, + 16.0 + ], + "edges": 47, + "faces": 18, + "volume": 35605.546935185695 + }, + "sk1": { + "area": 1977.9689891987991, + "bbox": [ + 0.0, + -47.5, + 0.0, + 34.000000000000014, + 47.5, + 0.0 + ], + "edges": 12, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "ttt/ttt-ppp0104": { + "shapes": { + "p": { + "area": 12314.23805633903, + "bbox": [ + -19.0, + -19.0000001, + -23.0000001, + 61.0000001, + 19.0000001, + 28.0 + ], + "edges": 62, + "faces": 23, + "volume": 39743.211180667735 + }, + "s": { + "area": 1134.1149479459152, + "bbox": [ + -19.0, + -19.0, + 0.0, + 19.0, + 19.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s2": { + "area": 530.929158456675, + "bbox": [ + -13.0, + -13.0, + 0.0, + 13.0, + 13.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s3": { + "area": 371.0, + "bbox": [ + -26.5, + 0.0, + 0.0, + 26.5, + 7.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "s3a": { + "area": 225.0, + "bbox": [ + -26.5, + 0.0, + 0.0, + 26.5, + 15.0, + 0.0 + ], + "edges": 8, + "faces": 2, + "volume": 0.0 + }, + "s4": { + "area": 201.06192982974667, + "bbox": [ + -8.0, + -8.0, + 0.0, + 8.0, + 8.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s5": { + "area": 379.99999999999994, + "bbox": [ + 51.0, + -19.0, + 0.0, + 61.0, + 19.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "s6": { + "area": 542.4690049407732, + "bbox": [ + -9.000000000000002, + -40.0, + 0.0, + 9.000000000000002, + -6.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "s6b": { + "area": 305.0973355292325, + "bbox": [ + -6.000000000000002, + -37.0, + 0.0, + 6.000000000000002, + -9.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "ttt/ttt-ppp0105": { + "shapes": { + "p": { + "area": 38127.23996439501, + "bbox": [ + -33.0000001, + -22.000000100000012, + -30.0, + 33.0000001, + 22.00000010000003, + 103.0000001 + ], + "edges": 40, + "faces": 18, + "volume": 55617.528016135795 + }, + "s": { + "area": 1828.5308443374602, + "bbox": [ + -25.5, + -22.0, + 0.0, + 25.5, + 22.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "s2": { + "area": 638.5398163397448, + "bbox": [ + -33.0, + -5.0, + 0.0, + 33.0, + 5.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "s3": { + "area": 1400.1149479459154, + "bbox": [ + -22.5, + -19.0, + 0.0, + 22.5, + 19.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "s4": { + "area": 236.56637061435916, + "bbox": [ + -30.0, + -2.0, + 0.0, + 30.0, + 2.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "ttt/ttt-ppp0106": { + "shapes": { + "c1": { + "area": 0.0, + "bbox": [ + 15.0, + 0.0, + 0.0, + 15.0, + 69.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l": { + "area": 0.0, + "bbox": [ + -32.0, + -5.329070518200751e-15, + 0.0, + 32.0, + 69.0, + 0.0 + ], + "edges": 10, + "faces": 0, + "volume": 0.0 + }, + "m1": { + "area": 0.0, + "bbox": [ + 0.0, + 69.0, + 0.0, + 22.0, + 69.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "m2": { + "area": 0.0, + "bbox": [ + 22.0, + 51.928932188134524, + 0.0, + 32.0, + 69.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "m3": { + "area": 0.0, + "bbox": [ + 14.999999999999998, + 37.85786437626904, + 0.0, + 29.071067811865476, + 51.928932188134524, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "m4": { + "area": 0.0, + "bbox": [ + 14.999999999999998, + 15.0, + 0.0, + 15.0, + 37.85786437626904, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "m5": { + "area": 0.0, + "bbox": [ + 4.970762342300593e-15, + -5.329070518200751e-15, + 0.0, + 14.999999999999998, + 15.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "p": { + "area": 15572.097246681229, + "bbox": [ + -32.0, + -15.000000000000005, + -25.0, + 32.0, + 54.0, + 11.0000001 + ], + "edges": 89, + "faces": 32, + "volume": 42053.82765929348 + }, + "sk_body": { + "area": 2311.2206145214886, + "bbox": [ + -32.0, + -3.552713678800501e-15, + 0.0, + 32.0, + 69.0, + 0.0 + ], + "edges": 16, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "ttt/ttt-ppp0107": { + "shapes": { + "p": { + "area": 42918.896751501714, + "bbox": [ + -65.0, + -65.0, + 0.0, + 65.0, + 65.0, + 52.0000001 + ], + "edges": 95, + "faces": 44, + "volume": 138137.45529650067 + }, + "pln2": { + "area": 4185.386812745002, + "bbox": [ + -36.5, + -36.5, + 18.0, + 36.5, + 36.5, + 18.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s": { + "area": 13273.228961416879, + "bbox": [ + -65.0, + -65.0, + 0.0, + 65.0, + 65.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s2": { + "area": 5541.769440932394, + "bbox": [ + -42.0, + -42.0, + 0.0, + 42.0, + 42.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s3": { + "area": 962.1127501618739, + "bbox": [ + -17.5, + -17.5, + 0.0, + 17.5, + 17.5, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s4": { + "area": 4185.386812745002, + "bbox": [ + -36.5, + -36.5, + 0.0, + 36.5, + 36.5, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s5": { + "area": 314.15926535897927, + "bbox": [ + -10.0, + -10.0, + 0.0, + 10.0, + 10.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s6": { + "area": 95.598, + "bbox": [ + -15.933, + -1.5, + 0.0, + 15.933, + 1.5, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "zz": { + "area": 1626.8709042392347, + "bbox": [ + 1.7424742890653342, + -4.144904810626978, + 9.742474289065331, + 36.75197095263065, + 4.144904810626971, + 44.75197095263065 + ], + "edges": 12, + "faces": 6, + "volume": 2957.1391331767377 + }, + "zz2": { + "area": 1081.4162159863508, + "bbox": [ + 1.7424742890653342, + -4.14490481062698, + 24.9999999, + 33.97127711597077, + 4.144904810626972, + 44.75197095263065 + ], + "edges": 12, + "faces": 6, + "volume": 1760.459473189193 + }, + "zz3": { + "area": 565.0236841850011, + "bbox": [ + 16.499999899999995, + -3.678353926686466, + 24.999999899999995, + 33.97127711597077, + 3.6783539266864587, + 42.47127711597077 + ], + "edges": 9, + "faces": 5, + "volume": 679.5125304414706 + } + }, + "status": "ok" + }, + "ttt/ttt-ppp0108": { + "shapes": { + "l1": { + "area": 0.0, + "bbox": [ + 45.0, + -19.0, + 0.0, + 125.0, + 11.0, + 0.0 + ], + "edges": 5, + "faces": 0, + "volume": 0.0 + }, + "p": { + "area": 80718.19778560844, + "bbox": [ + -125.0, + -95.0, + -19.000000000000004, + 125.0, + 95.0, + 16.0 + ], + "edges": 101, + "faces": 37, + "volume": 434238.2673538104 + }, + "p2": { + "area": 15726.698930910648, + "bbox": [ + -125.0, + -10.0, + -19.000000000000004, + 125.0, + 10.0, + 11.0 + ], + "edges": 56, + "faces": 24, + "volume": 57318.67288915634 + }, + "s1": { + "area": 24244.974654040903, + "bbox": [ + -94.00000000000001, + -95.0, + 0.0, + 94.00000000000001, + 95.0, + 0.0 + ], + "edges": 13, + "faces": 1, + "volume": 0.0 + }, + "s2": { + "area": 1924.9668222289083, + "bbox": [ + 45.0, + -19.0, + 0.0, + 125.0, + 11.0, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "s3": { + "area": 1119.9999999999998, + "bbox": [ + 45.0, + -10.0, + 0.0, + 125.0, + 10.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "ttt/ttt-ppp0109": { + "shapes": { + "bl": { + "area": 0.0, + "bbox": [ + -37.5, + 0.0, + 0.0, + 37.5, + 54.131421435130896, + 0.0 + ], + "edges": 8, + "faces": 0, + "volume": 0.0 + }, + "c": { + "area": 0.0, + "bbox": [ + 37.5, + 0.0, + 0.0, + 37.5, + 60.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "f": { + "area": 450.00000000000006, + "bbox": [ + 0.0, + -37.5, + 0.0, + 4.242640687119286, + 37.5, + 4.242640687119286 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 11.90030010436846, + 20.76924299402401, + 0.0, + 37.49999999999999, + 54.131421435130896, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "one": { + "area": 5050.960138443726, + "bbox": [ + -69.00000000000003, + -37.5, + 0.0, + 0.0, + 37.5, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "ppp109": { + "area": 27962.4888083657, + "bbox": [ + -69.00000000000003, + -37.5, + -45.0, + 49.242640687119284, + 37.5, + 60.0 + ], + "edges": 60, + "faces": 24, + "volume": 113789.2638826812 + }, + "three": { + "area": 3314.104507624698, + "bbox": [ + 0.0, + -37.5, + 0.0, + 63.63961030678927, + 37.5, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "two": { + "area": 3190.197878583049, + "bbox": [ + -37.5, + 0.0, + 0.0, + 37.5, + 60.0, + 0.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "ttt/ttt-ppp0110": { + "shapes": { + "cross_section": { + "area": 1236.2988836335278, + "bbox": [ + -42.0, + 0.0, + -2.220446049250313e-16, + 42.0, + 0.0, + 45.99999999999999 + ], + "edges": 10, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -42.0, + 0.0, + 0.0, + -39.21895141649746, + 13.865993248815562, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + -39.21895141649746, + 13.865993248815563, + 0.0, + 8.881784197001252e-16, + 45.99999999999999, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + -34.15620971670051, + -1.5731986497631094, + 0.0, + -2.5757174171303632e-14, + 37.99999999999999, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + -42.0, + -1.5731986497631094, + 0.0, + -34.15620971670051, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l5": { + "area": 0.0, + "bbox": [ + -3.375077994860476e-14, + 37.99999999999999, + 0.0, + 8.881784197001252e-16, + 45.99999999999999, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l6": { + "area": 0.0, + "bbox": [ + 0.0, + 30.0, + 0.0, + 8.881784197001252e-16, + 45.99999999999999, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l7": { + "area": 0.0, + "bbox": [ + -21.166010488516733, + 30.0, + 0.0, + 0.0, + 30.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l8": { + "area": 0.0, + "bbox": [ + -21.166010488516726, + 30.0, + 0.0, + -4.440892098500626e-16, + 45.99999999999999, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "outer": { + "area": 21477.794503244477, + "bbox": [ + -42.0, + -42.0, + -1.5731986497631094, + 42.0, + 42.0, + 45.99999999999999 + ], + "edges": 10, + "faces": 6, + "volume": 84526.45673285302 + }, + "p": { + "area": 47394.61329450654, + "bbox": [ + -42.0, + -142.0, + -1.1102230246251565e-15, + 42.0, + 42.0, + 45.99999999999999 + ], + "edges": 44, + "faces": 21, + "volume": 207159.36406587544 + }, + "positive_Z": { + "area": 60000.0, + "bbox": [ + -50.0, + 0.0, + 0.0, + 50.0, + 100.0, + 100.0 + ], + "edges": 12, + "faces": 6, + "volume": 999999.9999999999 + }, + "ppp0110": { + "area": 47394.61329450654, + "bbox": [ + -42.0, + -142.0, + -1.1102230246251565e-15, + 42.0, + 42.0, + 45.99999999999999 + ], + "edges": 44, + "faces": 21, + "volume": 207159.36406587544 + }, + "sk": { + "area": 618.1494418167646, + "bbox": [ + -42.0, + 0.0, + -2.220446049250313e-16, + 8.881784197001252e-16, + 0.0, + 45.99999999999999 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + } +} \ No newline at end of file diff --git a/test/b123d-validation/reference.json b/test/b123d-validation/reference.json new file mode 100644 index 00000000..1605e685 --- /dev/null +++ b/test/b123d-validation/reference.json @@ -0,0 +1,22178 @@ +{ + "docs-objects/text": { + "error": "ModuleNotFoundError: No module named 'tcv_screenshots'", + "status": "error" + }, + "docs-rst/OpenSCAD/all": { + "shapes": { + "angle_iron": { + "area": 12244.128255227575, + "bbox": [ + 0.0, + 0.0, + 0.0, + 30.0, + 30.0, + 100.0 + ], + "edges": 21, + "faces": 9, + "volume": 22936.50459150638 + }, + "profile": { + "area": 224.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 30.0, + 30.0, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/OpenSCAD/b01": { + "shapes": { + "angle_iron": { + "area": 12244.128255227575, + "bbox": [ + 0.0, + 0.0, + 0.0, + 30.0, + 30.0, + 100.0 + ], + "edges": 21, + "faces": 9, + "volume": 22936.50459150638 + }, + "profile": { + "area": 224.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 30.0, + 30.0, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/OpenSCAD/b02": { + "shapes": { + "angle_iron": { + "area": 12244.128255227575, + "bbox": [ + 0.0, + 0.0, + 0.0, + 30.0, + 30.0, + 100.0 + ], + "edges": 21, + "faces": 9, + "volume": 22936.50459150638 + }, + "profile": { + "area": 224.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 30.0, + 30.0, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/algebra_performance/all": { + "shapes": { + "c": { + "area": 3890.5482457436683, + "bbox": [ + -40.0, + -40.0, + 0.0, + 40.0, + 40.0, + 0.0 + ], + "edges": 1137, + "faces": 1, + "volume": 0.0 + }, + "holes[0]": { + "area": 4.0, + "bbox": [ + -39.0, + -3.0, + 0.0, + -37.0, + -1.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[10]": { + "area": 4.0, + "bbox": [ + -31.0, + -23.0, + 0.0, + -29.0, + -21.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[11]": { + "area": 4.0, + "bbox": [ + -31.0, + -19.0, + 0.0, + -29.0, + -17.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[12]": { + "area": 4.0, + "bbox": [ + -31.0, + -15.0, + 0.0, + -29.0, + -13.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[13]": { + "area": 4.0, + "bbox": [ + -31.0, + -11.0, + 0.0, + -29.0, + -9.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[14]": { + "area": 4.0, + "bbox": [ + -31.0, + -7.0, + 0.0, + -29.0, + -5.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[15]": { + "area": 4.0, + "bbox": [ + -31.0, + -3.0, + 0.0, + -29.0, + -1.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[16]": { + "area": 4.0, + "bbox": [ + -31.0, + 1.0, + 0.0, + -29.0, + 3.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[17]": { + "area": 4.0, + "bbox": [ + -31.0, + 5.0, + 0.0, + -29.0, + 7.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[18]": { + "area": 4.0, + "bbox": [ + -31.0, + 9.0, + 0.0, + -29.0, + 11.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[19]": { + "area": 4.0, + "bbox": [ + -31.0, + 13.0, + 0.0, + -29.0, + 15.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[1]": { + "area": 4.0, + "bbox": [ + -39.0, + 1.0, + 0.0, + -37.0, + 3.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[20]": { + "area": 4.0, + "bbox": [ + -31.0, + 17.0, + 0.0, + -29.0, + 19.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[21]": { + "area": 4.0, + "bbox": [ + -31.0, + 21.0, + 0.0, + -29.0, + 23.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[22]": { + "area": 4.0, + "bbox": [ + -27.0, + -27.0, + 0.0, + -25.0, + -25.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[23]": { + "area": 4.0, + "bbox": [ + -27.0, + -23.0, + 0.0, + -25.0, + -21.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[24]": { + "area": 4.0, + "bbox": [ + -27.0, + -19.0, + 0.0, + -25.0, + -17.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[25]": { + "area": 4.0, + "bbox": [ + -27.0, + -15.0, + 0.0, + -25.0, + -13.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[26]": { + "area": 4.0, + "bbox": [ + -27.0, + -11.0, + 0.0, + -25.0, + -9.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[27]": { + "area": 4.0, + "bbox": [ + -27.0, + -7.0, + 0.0, + -25.0, + -5.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[28]": { + "area": 4.0, + "bbox": [ + -27.0, + -3.0, + 0.0, + -25.0, + -1.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[29]": { + "area": 4.0, + "bbox": [ + -27.0, + 1.0, + 0.0, + -25.0, + 3.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[2]": { + "area": 4.0, + "bbox": [ + -35.0, + -15.0, + 0.0, + -33.0, + -13.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[30]": { + "area": 4.0, + "bbox": [ + -27.0, + 5.0, + 0.0, + -25.0, + 7.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[31]": { + "area": 4.0, + "bbox": [ + -27.0, + 9.0, + 0.0, + -25.0, + 11.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[32]": { + "area": 4.0, + "bbox": [ + -27.0, + 13.0, + 0.0, + -25.0, + 15.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[33]": { + "area": 4.0, + "bbox": [ + -27.0, + 17.0, + 0.0, + -25.0, + 19.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[34]": { + "area": 4.0, + "bbox": [ + -27.0, + 21.0, + 0.0, + -25.0, + 23.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[35]": { + "area": 4.0, + "bbox": [ + -27.0, + 25.0, + 0.0, + -25.0, + 27.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[36]": { + "area": 4.0, + "bbox": [ + -23.0, + -31.0, + 0.0, + -21.0, + -29.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[37]": { + "area": 4.0, + "bbox": [ + -23.0, + -27.0, + 0.0, + -21.0, + -25.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[38]": { + "area": 4.0, + "bbox": [ + -23.0, + -23.0, + 0.0, + -21.0, + -21.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[39]": { + "area": 4.0, + "bbox": [ + -23.0, + -19.0, + 0.0, + -21.0, + -17.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[3]": { + "area": 4.0, + "bbox": [ + -35.0, + -11.0, + 0.0, + -33.0, + -9.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[40]": { + "area": 4.0, + "bbox": [ + -23.0, + -15.0, + 0.0, + -21.0, + -13.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[41]": { + "area": 4.0, + "bbox": [ + -23.0, + -11.0, + 0.0, + -21.0, + -9.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[42]": { + "area": 4.0, + "bbox": [ + -23.0, + -7.0, + 0.0, + -21.0, + -5.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[43]": { + "area": 4.0, + "bbox": [ + -23.0, + -3.0, + 0.0, + -21.0, + -1.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[44]": { + "area": 4.0, + "bbox": [ + -23.0, + 1.0, + 0.0, + -21.0, + 3.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[45]": { + "area": 4.0, + "bbox": [ + -23.0, + 5.0, + 0.0, + -21.0, + 7.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[46]": { + "area": 4.0, + "bbox": [ + -23.0, + 9.0, + 0.0, + -21.0, + 11.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[47]": { + "area": 4.0, + "bbox": [ + -23.0, + 13.0, + 0.0, + -21.0, + 15.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[48]": { + "area": 4.0, + "bbox": [ + -23.0, + 17.0, + 0.0, + -21.0, + 19.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[49]": { + "area": 4.0, + "bbox": [ + -23.0, + 21.0, + 0.0, + -21.0, + 23.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[4]": { + "area": 4.0, + "bbox": [ + -35.0, + -7.0, + 0.0, + -33.0, + -5.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[50]": { + "area": 4.0, + "bbox": [ + -23.0, + 25.0, + 0.0, + -21.0, + 27.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[51]": { + "area": 4.0, + "bbox": [ + -23.0, + 29.0, + 0.0, + -21.0, + 31.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[52]": { + "area": 4.0, + "bbox": [ + -19.0, + -31.0, + 0.0, + -17.0, + -29.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[53]": { + "area": 4.0, + "bbox": [ + -19.0, + -27.0, + 0.0, + -17.0, + -25.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[54]": { + "area": 4.0, + "bbox": [ + -19.0, + -23.0, + 0.0, + -17.0, + -21.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[55]": { + "area": 4.0, + "bbox": [ + -19.0, + -19.0, + 0.0, + -17.0, + -17.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[56]": { + "area": 4.0, + "bbox": [ + -19.0, + -15.0, + 0.0, + -17.0, + -13.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[57]": { + "area": 4.0, + "bbox": [ + -19.0, + -11.0, + 0.0, + -17.0, + -9.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[58]": { + "area": 4.0, + "bbox": [ + -19.0, + -7.0, + 0.0, + -17.0, + -5.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[59]": { + "area": 4.0, + "bbox": [ + -19.0, + -3.0, + 0.0, + -17.0, + -1.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[5]": { + "area": 4.0, + "bbox": [ + -35.0, + -3.0, + 0.0, + -33.0, + -1.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[60]": { + "area": 4.0, + "bbox": [ + -19.0, + 1.0, + 0.0, + -17.0, + 3.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[61]": { + "area": 4.0, + "bbox": [ + -19.0, + 5.0, + 0.0, + -17.0, + 7.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[62]": { + "area": 4.0, + "bbox": [ + -19.0, + 9.0, + 0.0, + -17.0, + 11.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[63]": { + "area": 4.0, + "bbox": [ + -19.0, + 13.0, + 0.0, + -17.0, + 15.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[6]": { + "area": 4.0, + "bbox": [ + -35.0, + 1.0, + 0.0, + -33.0, + 3.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[7]": { + "area": 4.0, + "bbox": [ + -35.0, + 5.0, + 0.0, + -33.0, + 7.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[8]": { + "area": 4.0, + "bbox": [ + -35.0, + 9.0, + 0.0, + -33.0, + 11.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "holes[9]": { + "area": 4.0, + "bbox": [ + -35.0, + 13.0, + 0.0, + -33.0, + 15.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "polygons": { + "area": 237.76412907378844, + "bbox": [ + -24.045084971874736, + -19.755282581475768, + 0.0, + 25.0, + 19.755282581475768, + 0.0 + ], + "edges": 20, + "faces": 4, + "volume": 0.0 + }, + "r": { + "area": 4.0, + "bbox": [ + -1.0, + -1.0, + 0.0, + 1.0, + 1.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/algebra_performance/b01": { + "shapes": { + "c": { + "area": 3890.5482457436683, + "bbox": [ + -40.0, + -40.0, + 0.0, + 40.0, + 40.0, + 0.0 + ], + "edges": 1137, + "faces": 1, + "volume": 0.0 + }, + "holes": { + "area": 1136.0, + "bbox": [ + -39.0, + -39.0, + 0.0, + 39.0, + 39.0, + 0.0 + ], + "edges": 1136, + "faces": 284, + "volume": 0.0 + }, + "r": { + "area": 4.0, + "bbox": [ + -1.0, + -1.0, + 0.0, + 1.0, + 1.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/algebra_performance/b03": { + "shapes": { + "polygons": { + "area": 237.76412907378844, + "bbox": [ + -24.045084971874736, + -19.755282581475768, + 0.0, + 25.0, + 19.755282581475768, + 0.0 + ], + "edges": 20, + "faces": 4, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/build_sketch/b03": { + "shapes": { + "repeated": { + "area": 32.0, + "bbox": [ + -4.0, + -2.0, + 0.0, + 4.0, + 2.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/import_export/b01": { + "shapes": { + "box_builder": { + "area": 6.0, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 0.9999999999999998 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_algebra/b01": { + "shapes": { + "b": { + "area": 22.0, + "bbox": [ + -0.5, + -1.0, + -1.5, + 0.5, + 1.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 6.0 + }, + "c": { + "area": 6.53451271946677, + "bbox": [ + -0.2, + -0.2, + -2.5, + 0.2, + 0.2, + 2.5 + ], + "edges": 3, + "faces": 3, + "volume": 0.6283185307179587 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_algebra/b02": { + "shapes": { + "r": { + "area": 24.513274122871834, + "bbox": [ + -0.5, + -1.0, + -2.5, + 0.5, + 1.0, + 2.5 + ], + "edges": 18, + "faces": 10, + "volume": 6.251327412287184 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_algebra/b03": { + "shapes": { + "r": { + "area": 25.51858377202057, + "bbox": [ + -0.5, + -1.0, + -1.5, + 0.5, + 1.0, + 1.5 + ], + "edges": 15, + "faces": 7, + "volume": 5.623008881569225 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_algebra/b04": { + "shapes": { + "r": { + "area": 4.0212385965949355, + "bbox": [ + -0.2, + -0.2, + -1.5, + 0.2, + 0.2, + 1.5 + ], + "edges": 3, + "faces": 3, + "volume": 0.37699111843077515 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_algebra/b13": { + "shapes": { + "b": { + "area": 27.02654824574367, + "bbox": [ + -2.5, + -1.799038105676658, + -1.6160254037844386, + 2.5, + 1.799038105676658, + 1.6160254037844386 + ], + "edges": 18, + "faces": 10, + "volume": 6.502654824574366 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b01": { + "shapes": { + "base_sketch": { + "area": 399.99999999999994, + "bbox": [ + -10.0, + -10.0, + 0.0, + 10.0, + 10.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "cut_sketch": { + "area": 78.53981633974482, + "bbox": [ + -5.0, + -5.0, + 0.0, + 5.0, + 5.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "example_part": { + "area": 1757.0796326794894, + "bbox": [ + -10.0, + -10.0, + 0.0, + 10.0, + 10.0, + 10.0 + ], + "edges": 15, + "faces": 8, + "volume": 3607.300918301276 + }, + "result_part": { + "area": 1757.0796326794894, + "bbox": [ + -10.0, + -10.0, + 0.0, + 10.0, + 10.0, + 10.0 + ], + "edges": 15, + "faces": 8, + "volume": 3607.300918301276 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b02": { + "shapes": { + "invalid": { + "area": 18.849555921538755, + "bbox": [ + -1.0, + -1.0, + -1.0, + 1.0, + 1.0, + 1.0 + ], + "edges": 3, + "faces": 3, + "volume": 6.283185307179585 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b03": { + "shapes": { + "valid": { + "area": 18.849555921538755, + "bbox": [ + 0.0, + 1.0, + 2.0, + 2.0, + 3.0, + 4.0 + ], + "edges": 3, + "faces": 3, + "volume": 6.283185307179585 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b09": { + "shapes": { + "part_builder": { + "area": 599.9999999999999, + "bbox": [ + -5.0, + -5.0, + -5.0, + 5.0, + 5.0, + 5.0 + ], + "edges": 12, + "faces": 6, + "volume": 999.9999999999998 + }, + "sketch_builder": { + "area": 12.566370614359167, + "bbox": [ + -2.0, + -2.0, + 0.0, + 2.0, + 2.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b10": { + "shapes": { + "profile": { + "area": 78.53981633974482, + "bbox": [ + -5.0, + -5.0, + 0.0, + 5.0, + 5.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b11": { + "shapes": { + "bp": { + "area": 57.60000000000005, + "bbox": [ + -1.6, + -1.6, + -1.6, + 1.6, + 1.6, + 1.6 + ], + "edges": 84, + "faces": 36, + "volume": 28.20000000000004 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b13": { + "shapes": { + "holes": { + "area": 28.274333882308138, + "bbox": [ + -3.0, + -3.0, + 0.0, + 3.0, + 3.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "model": { + "area": 150.79644737231007, + "bbox": [ + -3.0, + -3.0, + 0.0, + 3.0, + 3.0, + 5.0 + ], + "edges": 3, + "faces": 3, + "volume": 141.3716694115407 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b14": { + "shapes": { + "placed_parts": { + "area": 149.99999999999997, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 124.99999999999997 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b17": { + "shapes": { + "pipes": { + "area": 589.6538021939285, + "bbox": [ + -8.003187848326382, + -7.544897591812934, + -7.155615126552097, + 8.003187848326384, + 7.5448975918129335, + 7.155615126552096 + ], + "edges": 48, + "faces": 26, + "volume": 998.9806250585737 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b19": { + "shapes": { + "pipes": { + "area": 599.9999999999999, + "bbox": [ + -8.128320675339982, + -7.650934991471806, + -7.245432423491767, + 8.128320675339983, + 7.650934991471806, + 7.245432423491767 + ], + "edges": 12, + "faces": 6, + "volume": 999.9999999999998 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b20": { + "shapes": { + "pipes": { + "area": 1199.9999999999998, + "bbox": [ + -18.12832067533998, + -17.650934991471807, + -17.245432423491767, + 18.128320675339985, + 17.650934991471807, + 17.245432423491767 + ], + "edges": 24, + "faces": 12, + "volume": 2000.0 + } + }, + "status": "ok" + }, + "docs-rst/key_concepts_builder/b21": { + "shapes": { + "pillow_block": { + "area": 14684.955591832535, + "bbox": [ + -40.0, + -30.00000000000011, + 0.0, + 40.0, + 30.0, + 20.0 + ], + "edges": 24, + "faces": 10, + "volume": 94283.18530525154 + }, + "plan": { + "area": 4714.159265230443, + "bbox": [ + -40.0, + -30.00000000000011, + 0.0, + 40.0, + 30.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/location_arithmetic/all": { + "shapes": { + "box": { + "area": 0.24, + "bbox": [ + -0.043244666586104694, + 0.48121244275425257, + 0.31023361216202655, + 0.26129071947662474, + 0.7622259877217714, + 0.6479366741142275 + ], + "edges": 12, + "faces": 6, + "volume": 0.007999999999999998 + }, + "face": { + "area": 2.0, + "bbox": [ + -0.7767451510676409, + -0.8950920158866638, + -0.12123284194859885, + 0.976745151067641, + 1.2950920158866637, + 0.7212328419485988 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/objects-text/b10": { + "shapes": { + "path": { + "area": 0.0, + "bbox": [ + -50.00000000000001, + 0.0, + 0.0, + 50.000000000000014, + 13.397459621556152, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/selectors/all": { + "shapes": { + "faces_with_holes[0]": { + "area": 0.8743362938564083, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + 0.5, + -0.5 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "faces_with_holes[1]": { + "area": 0.8743362938564083, + "bbox": [ + -0.5, + -0.5, + 0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "obj": { + "area": 7.005309649148733, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 15, + "faces": 7, + "volume": 0.8743362938564081 + } + }, + "status": "ok" + }, + "docs-rst/selectors/b02": { + "shapes": { + "faces_with_holes[0]": { + "area": 0.8743362938564083, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + 0.5, + -0.5 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "faces_with_holes[1]": { + "area": 0.8743362938564083, + "bbox": [ + -0.5, + -0.5, + 0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "obj": { + "area": 7.005309649148733, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 15, + "faces": 7, + "volume": 0.8743362938564081 + } + }, + "status": "ok" + }, + "docs-rst/tips/b01": { + "shapes": { + "plate": { + "area": 12888.825470084847, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 32, + "faces": 14, + "volume": 46827.13874265981 + }, + "top_face": { + "area": 4686.902664470766, + "bbox": [ + -40.0, + -30.0, + 5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tips/b04": { + "shapes": { + "vertical_sketch": { + "area": 1.0942477796076904, + "bbox": [ + -0.5, + -0.7, + 0.0, + 0.7, + 0.5, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tips/b05": { + "shapes": { + "custom_plane": { + "area": 1.0942477796076904, + "bbox": [ + 0.0, + 0.0, + 0.0, + 1.2, + 1.2, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/topology_selection-filter_examples/b01": { + "shapes": { + "part": { + "area": 6.0, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 0.9999999999999998 + } + }, + "status": "ok" + }, + "docs-rst/topology_selection/b01": { + "shapes": { + "context": { + "area": 1.0, + "bbox": [ + -0.5, + -0.5, + 0.0, + 0.5, + 0.5, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/topology_selection/b03": { + "shapes": { + "part": { + "area": 95.13274122871834, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 18, + "faces": 10, + "volume": 37.56637061435917 + } + }, + "status": "ok" + }, + "docs-rst/topology_selection/b04": { + "shapes": { + "part": { + "area": 95.13274122871834, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 18, + "faces": 10, + "volume": 37.56637061435917 + } + }, + "status": "ok" + }, + "docs-rst/topology_selection/b05": { + "shapes": { + "part": { + "area": 95.13274122871834, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 18, + "faces": 10, + "volume": 37.56637061435917 + } + }, + "status": "ok" + }, + "docs-rst/topology_selection/b06": { + "shapes": { + "part": { + "area": 95.13274122871834, + "bbox": [ + -2.5, + -2.5, + -1.0, + 2.5, + 2.5, + 2.0 + ], + "edges": 15, + "faces": 8, + "volume": 50.13274122871835 + } + }, + "status": "ok" + }, + "docs-rst/topology_selection/b07": { + "shapes": { + "part": { + "area": 91.69911184307752, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 30, + "faces": 14, + "volume": 36.70796326794897 + } + }, + "status": "ok" + }, + "docs-rst/topology_selection/b08": { + "shapes": { + "box": { + "area": 70.0, + "bbox": [ + -2.5, + -2.5, + -0.5, + 2.5, + 2.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 24.999999999999993 + }, + "circle": { + "area": 87.96459430051421, + "bbox": [ + -2.0, + -2.0, + -2.5, + 2.0, + 2.0, + 2.5 + ], + "edges": 3, + "faces": 3, + "volume": 62.83185307179585 + }, + "part": { + "area": 120.26548245743669, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 18, + "faces": 10, + "volume": 75.26548245743669 + } + }, + "status": "ok" + }, + "docs-rst/topology_selection/b09": { + "shapes": { + "box": { + "area": 70.0, + "bbox": [ + -2.5, + -2.5, + -0.5, + 2.5, + 2.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 24.999999999999993 + }, + "circle": { + "area": 87.96459430051421, + "bbox": [ + -2.0, + -2.0, + -2.5, + 2.0, + 2.0, + 2.5 + ], + "edges": 3, + "faces": 3, + "volume": 62.83185307179585 + }, + "part": { + "area": 116.83185307179586, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 30, + "faces": 14, + "volume": 74.40707511102647 + }, + "part_before": { + "area": 120.26548245743669, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 18, + "faces": 10, + "volume": 75.26548245743669 + } + }, + "status": "ok" + }, + "docs-rst/topology_selection/b12": { + "shapes": { + "bottom[0]": { + "area": 1.0, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + 0.5, + -0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "box": { + "area": 6.0, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 0.9999999999999998 + }, + "faces[0]": { + "area": 1.0, + "bbox": [ + -0.5, + -0.5, + -0.5, + -0.5, + 0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[1]": { + "area": 1.0, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + -0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[2]": { + "area": 1.0, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + 0.5, + -0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[3]": { + "area": 1.0, + "bbox": [ + -0.5, + -0.5, + 0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[4]": { + "area": 1.0, + "bbox": [ + -0.5, + 0.5, + -0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[5]": { + "area": 1.0, + "bbox": [ + 0.5, + -0.5, + -0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "sides[0]": { + "area": 1.0, + "bbox": [ + -0.5, + -0.5, + -0.5, + -0.5, + 0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "sides[1]": { + "area": 1.0, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + -0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "sides[2]": { + "area": 1.0, + "bbox": [ + -0.5, + 0.5, + -0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "sides[3]": { + "area": 1.0, + "bbox": [ + 0.5, + -0.5, + -0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "top[0]": { + "area": 1.0, + "bbox": [ + -0.5, + -0.5, + 0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "top_face": { + "area": 1.0, + "bbox": [ + -0.5, + -0.5, + 0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_constraints/b03": { + "shapes": { + "isosceles": { + "area": 389.71143170299746, + "bbox": [ + -14.999999999999996, + -8.660254037844386, + 0.0, + 15.000000000000004, + 17.32050807568877, + 0.0 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_constraints/b05": { + "shapes": { + "comb": { + "area": 0.0, + "bbox": [ + -3.113873265052331, + -2.0854169758374566, + 0.0, + 2.0082720352407724, + 1.7138381555328697, + 0.0 + ], + "edges": 200, + "faces": 0, + "volume": 0.0 + }, + "connector": { + "area": 0.0, + "bbox": [ + -1.0415531356024228, + -0.6000000999999998, + -1e-07, + 0.40000009999999814, + 0.42635192233306957, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "m1": { + "area": 0.0, + "bbox": [ + -3.0, + 0.42635182233306956, + 0.0, + -1.0, + 1.6, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "m2": { + "area": 0.0, + "bbox": [ + 0.3999999, + -1.6189955196822747, + -1e-07, + 2.0000001, + 1e-07, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_constraints/b06": { + "shapes": { + "coincident_ex": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 2.0, + 2.0, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 1.0, + 2.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 1.0, + 2.0, + 0.0, + 2.0, + 2.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_constraints/b07": { + "shapes": { + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 1.0, + 1.0, + 0.0, + 1.2928932188134525, + 2.1297250429272467, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "tangent_ex": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 1.2928932188134525, + 2.1297250429272467, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_constraints/b08": { + "shapes": { + "l1": { + "area": 0.0, + "bbox": [ + 1.0606601717798214, + 0.0, + 0.0, + 1.5, + 1.0606601717798212, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 1.0606601717798214, + 1.0606601717798212, + 0.0, + 1.7677669529663689, + 1.7677669529663689, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "perpendicular_ex": { + "area": 0.0, + "bbox": [ + 1.0606601717798214, + 0.0, + 0.0, + 1.7677669529663689, + 1.7677669529663689, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_constraints/b09": { + "shapes": { + "c1": { + "area": 0.0, + "bbox": [ + -0.5999999999999989, + -4.638813987248042e-16, + 0.0, + 1.2, + 1.8, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "intersect_ex": { + "area": 0.0, + "bbox": [ + -0.2, + 0.1, + 0.0, + 1.1780141450153974, + 1.7052231276140966, + 0.0 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -0.2, + 0.1, + 0.0, + 1.1780141450153974, + 0.34298107356412183, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + -0.2, + 0.1, + 0.0, + 0.3842534378072289, + 1.7052231276140966, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 0.38425343780722687, + 0.34298107356412094, + 0.0, + 1.178014145015397, + 1.7052231276140926, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_constraints/b10": { + "shapes": { + "h1": { + "area": 0.0, + "bbox": [ + 2.0, + 0.0, + 0.0, + 2.828427124746121, + 0.999999999999951, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "p1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 0.4999999999999728, + 0.9999999999999728, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_constraints/b11": { + "shapes": { + "inside": { + "area": 0.0, + "bbox": [ + -1.5, + 0.0, + 0.0, + 1.5, + 1.000000014082108, + 0.0 + ], + "edges": 5, + "faces": 0, + "volume": 0.0 + }, + "perimeter": { + "area": 0.0, + "bbox": [ + -1.7, + 0.0, + 0.0, + 1.7, + 1.2000000140821079, + 0.0 + ], + "edges": 12, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_constraints/b13": { + "shapes": { + "c1": { + "area": 0.0, + "bbox": [ + -2.75, + -0.75, + 0.0, + -1.4254666676607668, + 0.75, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "c2": { + "area": 0.0, + "bbox": [ + 1.233955556881022, + -1.0, + 0.0, + 3.0, + 1.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "egg_plant": { + "area": 0.0, + "bbox": [ + -2.75, + -1.0, + 0.0, + 3.0, + 1.1748299085411977, + 0.0 + ], + "edges": 4, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -1.825520833333334, + -0.9378287848214448, + 0.0, + 1.6529017857142851, + -0.5648014937501156, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + -2.2529633620689657, + 0.7060520784267723, + 0.0, + 2.2220982142857144, + 1.1748299085411977, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + -2.75, + -0.75, + 0.0, + -1.825520833333335, + 0.7060520784267723, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 1.652901785714285, + -1.0, + 0.0, + 3.0, + 0.9750242987798288, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_design/b07": { + "shapes": { + "bracket": { + "area": 5192.145949565215, + "bbox": [ + -25.0, + 0.0, + -12.5, + 25.0, + 25.000000012324477, + 12.5 + ], + "edges": 66, + "faces": 24, + "volume": 6412.652585245836 + }, + "profile": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 25.0, + 25.000000012324477, + 0.0 + ], + "edges": 8, + "faces": 0, + "volume": 0.0 + }, + "sketch": { + "area": 272.9867224066874, + "bbox": [ + -25.0, + 0.0, + 0.0, + 25.0, + 25.000000012324477, + 0.0 + ], + "edges": 12, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_stl_reconstruction/b03": { + "shapes": { + "fillet_box": { + "area": 5.473628179866694, + "bbox": [ + -0.5, + -0.5, + -0.5, + 0.5, + 0.5, + 0.5 + ], + "edges": 48, + "faces": 26, + "volume": 0.9755870138909416 + } + }, + "status": "ok" + }, + "docs-rst/tutorial_stl_reconstruction/b04": { + "shapes": { + "c01": { + "area": 0.5026528139550687, + "bbox": [ + 0.30000040000000006, + 0.30000040000000006, + -0.4, + 0.4999996, + 0.4999996, + 0.4 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "c02": { + "area": 0.5026528139550687, + "bbox": [ + -0.4999996, + 0.30000040000000006, + -0.4, + -0.30000040000000006, + 0.4999996, + 0.4 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "c03": { + "area": 0.5026528139550687, + "bbox": [ + -0.4999996, + -0.4999996, + -0.4, + -0.30000040000000006, + -0.30000040000000006, + 0.4 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "c04": { + "area": 0.5026528139550687, + "bbox": [ + 0.30000040000000006, + -0.4999996, + -0.4, + 0.4999996, + -0.30000040000000006, + 0.4 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "c07": { + "area": 0.5026528139550687, + "bbox": [ + -0.4, + -0.4999996, + -0.4999996, + 0.4, + -0.30000040000000006, + -0.30000040000000006 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "c08": { + "area": 0.5026528139550687, + "bbox": [ + -0.4, + -0.4999996, + 0.30000040000000006, + 0.4, + -0.30000040000000006, + 0.4999996 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "c09": { + "area": 0.5026528139550687, + "bbox": [ + -0.4, + 0.30000040000000006, + -0.4999996, + 0.4, + 0.4999996, + -0.30000040000000006 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "c10": { + "area": 0.5026528139550687, + "bbox": [ + -0.4, + 0.30000040000000006, + 0.30000040000000006, + 0.4, + 0.4999996, + 0.4999996 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "c13": { + "area": 0.5026528139550687, + "bbox": [ + 0.30000040000000006, + -0.4, + -0.4999996, + 0.4999996, + 0.4, + -0.30000040000000006 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "c14": { + "area": 0.5026528139550687, + "bbox": [ + -0.4999996, + -0.4, + -0.4999996, + -0.30000040000000006, + 0.4, + -0.30000040000000006 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "c15": { + "area": 0.5026528139550687, + "bbox": [ + -0.4999996, + -0.4, + 0.30000040000000006, + -0.30000040000000006, + 0.4, + 0.4999996 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "c16": { + "area": 0.5026528139550687, + "bbox": [ + 0.30000040000000006, + -0.4, + 0.30000040000000006, + 0.4999996, + 0.4, + 0.4999996 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "r00": { + "area": 0.6400000000000001, + "bbox": [ + -0.4, + -0.4, + -0.5, + 0.4, + 0.4, + -0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "r05": { + "area": 0.6400000000000001, + "bbox": [ + -0.4, + -0.4, + 0.5, + 0.4, + 0.4, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "r06": { + "area": 0.6400000000000001, + "bbox": [ + -0.5, + -0.4, + -0.4, + -0.5, + 0.4, + 0.4 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "r11": { + "area": 0.6400000000000001, + "bbox": [ + 0.5, + -0.4, + -0.4, + 0.5, + 0.4, + 0.4 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "r12": { + "area": 0.6400000000000001, + "bbox": [ + -0.4, + -0.5, + -0.4, + 0.4, + -0.5, + 0.4 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "r17": { + "area": 0.6400000000000001, + "bbox": [ + -0.4, + 0.5, + -0.4, + 0.4, + 0.5, + 0.4 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "s18": { + "area": 0.12562098411518396, + "bbox": [ + 0.300016, + -0.499982, + 0.300043, + 0.499982, + -0.300016, + 0.500009 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s19": { + "area": 0.12562098411518396, + "bbox": [ + -0.499982, + 0.300016, + -0.500009, + -0.300016, + 0.499982, + -0.300043 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s20": { + "area": 0.12562098411518396, + "bbox": [ + -0.499982, + -0.499982, + -0.500009, + -0.300016, + -0.300016, + -0.300043 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s21": { + "area": 0.12562098411518396, + "bbox": [ + 0.300016, + 0.300016, + -0.500009, + 0.499982, + 0.499982, + -0.300043 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s22": { + "area": 0.12562098411518396, + "bbox": [ + -0.499982, + 0.300043, + 0.300016, + -0.300016, + 0.500009, + 0.499982 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s23": { + "area": 0.12562098411518396, + "bbox": [ + -0.499982, + -0.499982, + 0.300043, + -0.300016, + -0.300016, + 0.500009 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s24": { + "area": 0.12562098411518396, + "bbox": [ + 0.300016, + 0.300043, + 0.300016, + 0.499982, + 0.500009, + 0.499982 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s25": { + "area": 0.12562098411518396, + "bbox": [ + 0.300016, + -0.499982, + -0.500009, + 0.499982, + -0.300016, + -0.300043 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-selectors/filter_all_edges_circle": { + "shapes": { + "bl": { + "area": 0.0, + "bbox": [ + -13.61880215351701, + 0.0, + 0.0, + 13.618802153517008, + 8.0, + 0.0 + ], + "edges": 6, + "faces": 0, + "volume": 0.0 + }, + "f": { + "area": 455.5309347705202, + "bbox": [ + -48.5, + -21.0, + 25.0, + -14.5, + -21.0, + 59.0 + ], + "edges": 2, + "faces": 1, + "volume": 0.0 + }, + "faces[0]": { + "area": 455.5309347705202, + "bbox": [ + -48.5, + -21.0, + 25.0, + -14.5, + -21.0, + 59.0 + ], + "edges": 2, + "faces": 1, + "volume": 0.0 + }, + "faces[1]": { + "area": 455.5309347705202, + "bbox": [ + -48.5, + 21.0, + 25.0, + -14.5, + 21.0, + 59.0 + ], + "edges": 2, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 9.0, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 9.0, + 0.0, + 0.0, + 13.618802153517008, + 8.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 0.0, + 8.0, + 0.0, + 13.618802153517008, + 8.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "part": { + "area": 30243.24648019587, + "bbox": [ + -57.5000001, + -25.00000010000011, + -1e-07, + 57.5, + 25.0000001, + 68.0000001 + ], + "edges": 84, + "faces": 32, + "volume": 102198.22251481404 + }, + "s": { + "area": 4700.902664470767, + "bbox": [ + -57.5, + -25.0, + 0.0, + 57.5, + 25.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "s3": { + "area": 4931.716633826699, + "bbox": [ + -57.50000000000001, + -38.0, + 0.0, + -5.499999999999993, + 68.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "s4": { + "area": 180.9504172281361, + "bbox": [ + -13.61880215351701, + 0.0, + 0.0, + 13.618802153517008, + 8.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "zz": { + "area": 13119.78708349343, + "bbox": [ + -57.50000000000001, + -25.0, + -38.0, + -5.499999999999993, + -13.0, + 68.0 + ], + "edges": 12, + "faces": 6, + "volume": 59180.599605920404 + } + }, + "status": "ok" + }, + "docs-selectors/filter_axisplane": { + "shapes": { + "b": { + "area": 6.0, + "bbox": [ + 0.5, + 0.5, + -0.5, + 1.5, + 1.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 0.9999999999999998 + }, + "f[0]": { + "area": 1.0, + "bbox": [ + 0.5, + 0.5, + -0.5, + 0.5, + 1.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "f[1]": { + "area": 1.0, + "bbox": [ + 0.5, + 0.5, + -0.5, + 1.5, + 0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "f[2]": { + "area": 1.0, + "bbox": [ + 0.5, + 0.5, + -0.5, + 1.5, + 1.5, + -0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "f[3]": { + "area": 1.0, + "bbox": [ + 0.5, + 0.5, + 0.5, + 1.5, + 1.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "f[4]": { + "area": 1.0, + "bbox": [ + 0.5, + 1.5, + -0.5, + 1.5, + 1.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "f[5]": { + "area": 1.0, + "bbox": [ + 1.5, + 0.5, + -0.5, + 1.5, + 1.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "part": { + "area": 12.0, + "bbox": [ + -1.5, + -1.5, + -0.5, + 1.5, + 1.5, + 0.5 + ], + "edges": 24, + "faces": 12, + "volume": 1.9999999999999996 + }, + "plane_rep": { + "area": 3.965656732033233, + "bbox": [ + -1e-07, + -1e-07, + -1e-07, + 2.0000001, + 2.0000001, + 1e-07 + ], + "edges": 71, + "faces": 4, + "volume": 0.0 + }, + "res[0]": { + "area": 1.0, + "bbox": [ + 0.5, + 0.5, + -0.5, + 0.5, + 1.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "res[1]": { + "area": 1.0, + "bbox": [ + 0.5, + 0.5, + -0.5, + 1.5, + 0.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "res[2]": { + "area": 1.0, + "bbox": [ + 0.5, + 1.5, + -0.5, + 1.5, + 1.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "res[3]": { + "area": 1.0, + "bbox": [ + 1.5, + 0.5, + -0.5, + 1.5, + 1.5, + 0.5 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-selectors/filter_geomtype": { + "shapes": { + "part": { + "area": 116.83185307179586, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 30, + "faces": 14, + "volume": 74.40707511102647 + } + }, + "status": "ok" + }, + "docs-selectors/filter_inner_wire_count": { + "shapes": { + "before_linear": { + "area": 6221.9841290626955, + "bbox": [ + -4.440892098501e-16, + -20.5, + -4.440892098501e-16, + 35.0, + 20.5, + 51.0 + ], + "edges": 120, + "faces": 42, + "volume": 7061.1553017856795 + }, + "bracket": { + "area": 6221.9841290626955, + "bbox": [ + -4.440892098501e-16, + -20.5, + -4.440892098501e-16, + 35.0, + 20.5, + 51.0 + ], + "edges": 120, + "faces": 42, + "volume": 7061.1553017856795 + }, + "e": { + "area": 0.0, + "bbox": [ + 30.0, + 12.5, + 3.0, + 30.0, + 15.5, + 3.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "f": { + "area": 31.1017672705369, + "bbox": [ + 0.0, + -17.15, + 43.85, + 3.0, + -13.85, + 47.15 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "faces[0]": { + "area": 1125.54033668342, + "bbox": [ + -4.440892098501e-16, + -20.5, + 3.0, + 0.0, + 20.5, + 51.0 + ], + "edges": 11, + "faces": 1, + "volume": 0.0 + }, + "faces[10]": { + "area": 1125.54033668342, + "bbox": [ + 3.0, + -20.5, + 3.0, + 3.0, + 20.5, + 51.0 + ], + "edges": 11, + "faces": 1, + "volume": 0.0 + }, + "faces[11]": { + "area": 9.0, + "bbox": [ + 7.75, + -15.5, + 0.0, + 7.75, + -12.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[12]": { + "area": 9.0, + "bbox": [ + 7.75, + 12.5, + 0.0, + 7.75, + 15.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[13]": { + "area": 21.2057504117325, + "bbox": [ + 7.75, + -17.75, + 0.0, + 12.25, + -15.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[14]": { + "area": 21.2057504117325, + "bbox": [ + 7.75, + -12.5, + 0.0, + 12.25, + -10.25, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[15]": { + "area": 21.2057504117325, + "bbox": [ + 7.75, + 10.25, + 0.0, + 12.25, + 12.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[16]": { + "area": 21.2057504117325, + "bbox": [ + 7.75, + 15.5, + 0.0, + 12.25, + 17.75, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[17]": { + "area": 9.0, + "bbox": [ + 12.25, + -15.5, + 0.0, + 12.25, + -12.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[18]": { + "area": 9.0, + "bbox": [ + 12.25, + 12.5, + 0.0, + 12.25, + 15.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[19]": { + "area": 235.06858347057704, + "bbox": [ + 0.0, + -20.5, + 0.0, + 33.0, + -20.5, + 49.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "faces[1]": { + "area": 8.485281374238564, + "bbox": [ + 0.0, + -20.5, + 49.0, + 3.0, + -18.5, + 51.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[20]": { + "area": 235.06858347057704, + "bbox": [ + 0.0, + 20.5, + 0.0, + 33.0, + 20.5, + 49.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "faces[21]": { + "area": 21.2057504117325, + "bbox": [ + 16.25, + -17.75, + 0.0, + 18.5, + -13.25, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[22]": { + "area": 21.2057504117325, + "bbox": [ + 16.25, + 13.25, + 0.0, + 18.5, + 17.75, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[23]": { + "area": 1131.5741231472102, + "bbox": [ + 3.0, + -20.5, + -4.440892098501e-16, + 35.0, + 20.5, + 0.0 + ], + "edges": 30, + "faces": 1, + "volume": 0.0 + }, + "faces[24]": { + "area": 1131.5741231472102, + "bbox": [ + 3.0, + -20.5, + 3.0, + 35.0, + 20.5, + 3.0 + ], + "edges": 30, + "faces": 1, + "volume": 0.0 + }, + "faces[25]": { + "area": 9.0, + "bbox": [ + 18.5, + -17.75, + 0.0, + 21.5, + -17.75, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[26]": { + "area": 9.0, + "bbox": [ + 18.5, + -13.25, + 0.0, + 21.5, + -13.25, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[27]": { + "area": 9.0, + "bbox": [ + 18.5, + 13.25, + 0.0, + 21.5, + 13.25, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[28]": { + "area": 9.0, + "bbox": [ + 18.5, + 17.75, + 0.0, + 21.5, + 17.75, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[29]": { + "area": 21.2057504117325, + "bbox": [ + 21.5, + -17.75, + 0.0, + 23.75, + -13.25, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[2]": { + "area": 31.1017672705369, + "bbox": [ + 0.0, + -17.15, + 12.85, + 3.0, + -13.85, + 16.15 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "faces[30]": { + "area": 21.2057504117325, + "bbox": [ + 21.5, + 13.25, + 0.0, + 23.75, + 17.75, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[31]": { + "area": 9.0, + "bbox": [ + 27.75, + -15.5, + 0.0, + 27.75, + -12.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[32]": { + "area": 9.0, + "bbox": [ + 27.75, + 12.5, + 0.0, + 27.75, + 15.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[33]": { + "area": 21.2057504117325, + "bbox": [ + 27.75, + -17.75, + 0.0, + 32.25, + -15.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[34]": { + "area": 21.2057504117325, + "bbox": [ + 27.75, + -12.5, + 0.0, + 32.25, + -10.25, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[35]": { + "area": 21.2057504117325, + "bbox": [ + 27.75, + 10.25, + 0.0, + 32.25, + 12.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[36]": { + "area": 21.2057504117325, + "bbox": [ + 27.75, + 15.5, + 0.0, + 32.25, + 17.75, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[37]": { + "area": 9.0, + "bbox": [ + 32.25, + -15.5, + 0.0, + 32.25, + -12.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[38]": { + "area": 9.0, + "bbox": [ + 32.25, + 12.5, + 0.0, + 32.25, + 15.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[39]": { + "area": 8.48528137423857, + "bbox": [ + 33.0, + -20.5, + 0.0, + 35.0, + -18.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[3]": { + "area": 31.1017672705369, + "bbox": [ + 0.0, + -17.15, + 43.85, + 3.0, + -13.85, + 47.15 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "faces[40]": { + "area": 8.485281374238571, + "bbox": [ + 33.0, + 18.5, + 0.0, + 35.0, + 20.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[41]": { + "area": 111.0, + "bbox": [ + 35.0, + -18.5, + 0.0, + 35.0, + 18.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[4]": { + "area": 193.20794819578495, + "bbox": [ + -4.440892098501e-16, + -20.5, + -4.440892098501e-16, + 3.0, + 20.5, + 3.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[5]": { + "area": 301.59289474460024, + "bbox": [ + 0.0, + -16.0, + 14.0, + 3.0, + 16.0, + 46.0 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "faces[6]": { + "area": 111.0, + "bbox": [ + 0.0, + -18.5, + 51.0, + 3.0, + 18.5, + 51.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "faces[7]": { + "area": 31.1017672705369, + "bbox": [ + 0.0, + 13.85, + 12.85, + 3.0, + 17.15, + 16.15 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "faces[8]": { + "area": 31.1017672705369, + "bbox": [ + 0.0, + 13.85, + 43.85, + 3.0, + 17.15, + 47.15 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "faces[9]": { + "area": 8.48528137423857, + "bbox": [ + 0.0, + 18.5, + 49.0, + 3.0, + 20.5, + 51.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "motor_bore": { + "area": 0.0, + "bbox": [ + 3.0, + -16.0, + 14.0, + 3.0, + 16.0, + 46.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "motor_face": { + "area": 1125.54033668342, + "bbox": [ + 3.0, + -20.5, + 3.0, + 3.0, + 20.5, + 51.0 + ], + "edges": 11, + "faces": 1, + "volume": 0.0 + }, + "motor_mounts[0]": { + "area": 31.1017672705369, + "bbox": [ + 0.0, + -17.15, + 12.85, + 3.0, + -13.85, + 16.15 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "motor_mounts[1]": { + "area": 31.1017672705369, + "bbox": [ + 0.0, + -17.15, + 43.85, + 3.0, + -13.85, + 47.15 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "motor_mounts[2]": { + "area": 31.1017672705369, + "bbox": [ + 0.0, + 13.85, + 12.85, + 3.0, + 17.15, + 16.15 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "motor_mounts[3]": { + "area": 31.1017672705369, + "bbox": [ + 0.0, + 13.85, + 43.85, + 3.0, + 17.15, + 47.15 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "mount_face": { + "area": 1131.5741231472102, + "bbox": [ + 3.0, + -20.5, + 3.0, + 35.0, + 20.5, + 3.0 + ], + "edges": 30, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-selectors/filter_nested": { + "shapes": { + "b": { + "area": 2265.8254672326348, + "bbox": [ + -40.0, + -40.0, + 0.0, + -10.0, + -10.0, + 15.0 + ], + "edges": 45, + "faces": 18, + "volume": 2341.618734706661 + }, + "before": { + "area": 2265.8254672326348, + "bbox": [ + -15.0, + -15.0, + 0.0, + 15.0, + 15.0, + 15.0 + ], + "edges": 45, + "faces": 18, + "volume": 2341.6187347066616 + }, + "f[0]": { + "area": 670.0249018294472, + "bbox": [ + -40.0, + -40.0, + 0.0, + -10.0, + -10.0, + 0.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "f[1]": { + "area": 77.0437637608331, + "bbox": [ + -30.0, + -30.000000046931095, + 15.0, + -20.0, + -19.9999999530689, + 15.0 + ], + "edges": 12, + "faces": 1, + "volume": 0.0 + }, + "faces[0]": { + "area": 670.0249018294472, + "bbox": [ + -15.0, + -15.0, + 0.0, + 15.0, + 15.0, + 0.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "faces[1]": { + "area": 77.0437637608331, + "bbox": [ + -5.0, + -5.000000046931097, + 15.0, + 5.0, + 5.000000046931097, + 15.0 + ], + "edges": 12, + "faces": 1, + "volume": 0.0 + }, + "part": { + "area": 2246.754172807212, + "bbox": [ + -15.0, + -15.0, + -1.2434497875801753e-14, + 15.0, + 15.0, + 15.000000000000016 + ], + "edges": 77, + "faces": 34, + "volume": 2333.2025193287527 + } + }, + "status": "ok" + }, + "docs-selectors/filter_shape_properties": { + "shapes": { + "inside_fillets": { + "area": 146.0840583919254, + "bbox": [ + -8.0, + -8.0, + -0.5000000000000002, + 8.0, + 8.0, + 2.0 + ], + "edges": 32, + "faces": 12, + "volume": 0.0 + }, + "open_box": { + "area": 1291.7456844528895, + "bbox": [ + -10.0, + -10.0, + -2.5, + 10.0, + 10.0, + 2.5 + ], + "edges": 100, + "faces": 51, + "volume": 1253.6141621090958 + }, + "open_box_builder": { + "area": 1291.7456844528895, + "bbox": [ + -10.0, + -10.0, + -2.5, + 10.0, + 10.0, + 2.5 + ], + "edges": 100, + "faces": 51, + "volume": 1253.6141621090958 + }, + "outside_fillets": { + "area": 184.22799667532294, + "bbox": [ + -10.0, + -10.0, + -2.5, + 10.0, + 10.0, + 2.5 + ], + "edges": 72, + "faces": 28, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-selectors/group_axis": { + "shapes": { + "fins": { + "area": 1791.9999999999998, + "bbox": [ + -7.0, + -10.5, + 0.0, + 7.0, + 10.5, + 10.0 + ], + "edges": 192, + "faces": 96, + "volume": 960.0 + }, + "part": { + "area": 10329.486677646151, + "bbox": [ + -17.0, + -24.0, + -5.0, + 17.0, + 24.0, + 10.0 + ], + "edges": 1152, + "faces": 422, + "volume": 11953.646003293885 + }, + "without": { + "area": 10484.0, + "bbox": [ + -17.0, + -24.0, + -5.0, + 17.0, + 24.0, + 10.0 + ], + "edges": 768, + "faces": 294, + "volume": 12000.0 + } + }, + "status": "ok" + }, + "docs-selectors/group_hole_area": { + "shapes": { + "before": { + "area": 4761.5770231504675, + "bbox": [ + -10.0, + -40.0, + -10.000000000000004, + 10.0, + 40.0000001, + 10.000000000000004 + ], + "edges": 24, + "faces": 13, + "volume": 17229.357855935177 + }, + "part": { + "area": 4724.120330627758, + "bbox": [ + -10.0, + -40.0, + -10.000000000000004, + 10.0, + 40.0000001, + 10.000000000000004 + ], + "edges": 28, + "faces": 15, + "volume": 17213.91154780689 + }, + "s": { + "area": 119.6349540849362, + "bbox": [ + -2.5, + 25.5, + 0.0, + 2.5, + 50.5, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-selectors/group_properties_with_keys": { + "shapes": { + "after": { + "area": 5620.026752561991, + "bbox": [ + -45.0, + -32.0000001, + -22.0, + 5.0, + -7.999999899999999, + 5.000000000000002 + ], + "edges": 111, + "faces": 41, + "volume": 9447.96726971913 + }, + "after_fillet": { + "area": 5401.890853673273, + "bbox": [ + -5.0, + 8.0, + -5.0, + 45.0, + 32.0, + 22.0 + ], + "edges": 72, + "faces": 28, + "volume": 9730.739028031032 + }, + "after_holes": { + "area": 5620.026752561991, + "bbox": [ + -25.0, + -12.0000001, + -5.0, + 25.0, + 12.0000001, + 22.0 + ], + "edges": 111, + "faces": 41, + "volume": 9447.96726971913 + }, + "before": { + "area": 5471.658311786602, + "bbox": [ + -45.0, + -32.0, + -5.0, + 5.0, + -8.0, + 22.0 + ], + "edges": 48, + "faces": 20, + "volume": 9751.638840713078 + }, + "before_fillet": { + "area": 5471.658311786602, + "bbox": [ + -25.0, + -12.0, + -5.0, + 25.0, + 12.0, + 22.0 + ], + "edges": 48, + "faces": 20, + "volume": 9751.638840713076 + }, + "circle": { + "area": 0.0, + "bbox": [ + -15.999987650650775, + -12.0, + 12.015715814709198, + 1.8995269025627435, + -12.0, + 22.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "items[0]": { + "area": 5471.658311786602, + "bbox": [ + -25.0, + -12.0, + -5.0, + 25.0, + 12.0, + 22.0 + ], + "edges": 48, + "faces": 20, + "volume": 9751.638840713076 + }, + "part": { + "area": 5585.776511397532, + "bbox": [ + -5.0, + 7.9999999, + -22.0, + 45.0, + 32.0000001, + 5.000000000000002 + ], + "edges": 125, + "faces": 48, + "volume": 9432.271118481687 + }, + "pins": { + "area": 17.13716694115407, + "bbox": [ + -22.5, + -1.5, + 0.0, + 23.0, + 1.5, + 0.0 + ], + "edges": 5, + "faces": 2, + "volume": 0.0 + }, + "sketch": { + "area": 740.921953150644, + "bbox": [ + -25.0, + -5.0, + 0.0, + 25.0, + 22.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-selectors/selectors_operators": { + "shapes": { + "b": { + "area": 70.0, + "bbox": [ + 6.5, + 6.5, + -1.0, + 11.5, + 11.5, + 0.0 + ], + "edges": 12, + "faces": 6, + "volume": 24.999999999999993 + }, + "box": { + "area": 70.0, + "bbox": [ + -2.5, + -2.5, + -0.5, + 2.5, + 2.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 24.999999999999993 + }, + "c": { + "area": 50.265482457436676, + "bbox": [ + 7.0, + 7.0, + 0.0, + 11.0, + 11.0, + 2.0 + ], + "edges": 3, + "faces": 3, + "volume": 25.132741228718338 + }, + "circle": { + "area": 87.96459430051421, + "bbox": [ + -2.0, + -2.0, + -2.5, + 2.0, + 2.0, + 2.5 + ], + "edges": 3, + "faces": 3, + "volume": 62.83185307179585 + }, + "faces[0]": { + "area": 11.575222039230619, + "bbox": [ + 6.5, + 6.5, + 0.5, + 11.5, + 11.5, + 0.5 + ], + "edges": 9, + "faces": 1, + "volume": 0.0 + }, + "faces[1]": { + "area": 12.566370614359167, + "bbox": [ + 7.0, + 7.0, + 2.5, + 11.0, + 11.0, + 2.5 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "line": { + "area": 0.0, + "bbox": [ + -9.0, + -9.0, + 0.0, + 9.0, + 9.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "part": { + "area": 120.26548245743669, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 18, + "faces": 10, + "volume": 75.26548245743669 + }, + "part_copy": { + "area": 95.13274122871834, + "bbox": [ + 0.5, + 0.5, + -1.0, + 5.5, + 5.5, + 2.0 + ], + "edges": 15, + "faces": 8, + "volume": 50.132741228718345 + } + }, + "status": "ok" + }, + "docs-selectors/sort_along_wire": { + "shapes": { + "along_wire": { + "area": 1535.7853981634066, + "bbox": [ + -9.269029987990507e-12, + 0.0, + 0.0, + 48.0, + 48.0, + 0.0 + ], + "edges": 9, + "faces": 1, + "volume": 0.0 + }, + "v": { + "area": 0.0, + "bbox": [ + 0.0, + 48.0, + 0.0, + 0.0, + 48.0, + 0.0 + ], + "edges": 0, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-selectors/sort_axis": { + "shapes": { + "before": { + "area": 2717.805208455828, + "bbox": [ + 0.0, + 16.0, + 0.0, + 34.0, + 32.0, + 25.0 + ], + "edges": 30, + "faces": 12, + "volume": 3768.282996588303 + }, + "edge": { + "area": 0.0, + "bbox": [ + 34.0, + 16.0, + 0.0, + 34.0, + 16.0, + 4.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "f": { + "area": 110.83185284083245, + "bbox": [ + 34.01, + 16.0, + 0.0, + 34.01, + 32.0, + 25.0 + ], + "edges": 10, + "faces": 1, + "volume": 0.0 + }, + "face": { + "area": 110.83185284083245, + "bbox": [ + 34.0, + 16.0, + 0.0, + 34.0, + 32.0, + 25.0 + ], + "edges": 10, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 16.0, + 0.0, + 0.0, + 32.0, + 25.0, + 0.0 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 16.0, + 3.9999999813261358, + 0.0, + 28.0, + 15.0, + 0.0 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + }, + "part": { + "area": 3958.206057686804, + "bbox": [ + 0.0, + 16.0, + 0.0, + 50.0, + 32.0, + 25.0 + ], + "edges": 42, + "faces": 17, + "volume": 5585.161443960096 + }, + "profile": { + "area": 110.83185284083245, + "bbox": [ + 16.0, + 0.0, + 0.0, + 32.0, + 25.0, + 0.0 + ], + "edges": 10, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs-selectors/sort_distance_from": { + "error": "NameError: name 'ColorMap' is not defined", + "status": "error" + }, + "docs-selectors/sort_sortby": { + "shapes": { + "box": { + "area": 149.99999999999997, + "bbox": [ + -8.5, + -8.5, + -2.5, + -3.5, + -3.5, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 124.99999999999997 + }, + "part": { + "area": 116.83185307179586, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 30, + "faces": 14, + "volume": 74.40707511102647 + }, + "solids[0]": { + "area": 149.99999999999997, + "bbox": [ + -8.5, + -8.5, + -2.5, + -3.5, + -3.5, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 124.99999999999997 + }, + "solids[1]": { + "area": 116.83185307179586, + "bbox": [ + -2.5, + -2.5, + -2.5, + 2.5, + 2.5, + 2.5 + ], + "edges": 30, + "faces": 14, + "volume": 74.40707511102647 + }, + "solids[2]": { + "area": 78.5398163397448, + "bbox": [ + 3.5, + 3.5, + -2.5, + 8.5, + 8.5, + 2.5 + ], + "edges": 1, + "faces": 1, + "volume": 65.44984694978736 + }, + "sphere": { + "area": 78.5398163397448, + "bbox": [ + 3.5, + 3.5, + -2.5, + 8.5, + 8.5, + 2.5 + ], + "edges": 1, + "faces": 1, + "volume": 65.44984694978736 + } + }, + "status": "ok" + }, + "docs/center": { + "shapes": { + "bbox_symbol": { + "area": 16.0, + "bbox": [ + -2.0, + -2.0, + 0.0, + 2.0, + 2.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "geom_symbol": { + "area": 5.196152422706633, + "bbox": [ + -1.0000000000000009, + -1.732050807568877, + 0.0, + 2.0, + 1.7320508075688776, + 0.0 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "line": { + "area": 0.0, + "bbox": [ + -7.888609052210118e-31, + 7.105427357601002e-15, + 0.0, + 50.0, + 50.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "mass_symbol": { + "area": 12.566370614359167, + "bbox": [ + -2.0, + -2.0, + 0.0, + 2.0, + 2.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "triangle": { + "area": 932.6927922672327, + "bbox": [ + -23.205396671608757, + -13.39764201500537, + 0.0, + 23.205396671608742, + 26.795284030010716, + 0.0 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/constraint_examples": { + "error": "NameError: name 'ImageFace' is not defined. Did you mean: 'make_face'?", + "status": "error" + }, + "docs/heart_token": { + "shapes": { + "bottom_left_surface": { + "area": 107.53326858513432, + "bbox": [ + -9.982677224380025, + 0.0, + -2.525926762461026, + 3.1086244689504383e-15, + 16.572502608194313, + 4.822598771995576 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "bottom_right_surface": { + "area": 107.53326858513432, + "bbox": [ + -3.1086244689504383e-15, + 0.0, + -2.525926762461026, + 9.982677224380025, + 16.572502608194313, + 4.822598771995576 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "center": { + "area": 200.2097298862263, + "bbox": [ + -9.982677224380025, + 3.552713678800501e-15, + 0.0, + 9.982677224380025, + 16.572502608194313, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "heart": { + "area": 488.6292416342477, + "bbox": [ + -9.982677224380025, + 0.0, + -4.822598771995573, + 9.982677224380025, + 16.572502608194313, + 4.822598771995573 + ], + "edges": 20, + "faces": 10, + "volume": 555.8200293752507 + }, + "heart_half": { + "area": 0.0, + "bbox": [ + -2.664535259100376e-15, + 0.0, + 0.0, + 9.982677224380025, + 16.572502608194313, + 1.5 + ], + "edges": 4, + "faces": 0, + "volume": 0.0 + }, + "heart_token": { + "area": 1159.8438469488065, + "bbox": [ + -11.982677224380026, + -3.639822484774289, + -4.822598771995573, + 11.982677224380026, + 18.572502608194313, + 4.822598771995573 + ], + "edges": 56, + "faces": 24, + "volume": 1080.970639222408 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 3.552713678800501e-15, + 0.0, + 8.219755366121646, + 8.50061189794011, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 3.0673252791798786, + 8.50061189794011, + 0.0, + 9.982677224380025, + 16.572502608194313, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + -2.6645352591003757e-15, + 13.918086097615445, + 0.0, + 3.0673252791798786, + 15.869353274314575, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + -2.664535259100376e-15, + 0.0, + 0.0, + 3.944304526105059e-31, + 13.918086097615445, + 1.5 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "left_side": { + "area": 29.24807855086281, + "bbox": [ + -2.6645352591003757e-15, + 3.552713678800501e-15, + -0.5, + 9.982677224380025, + 16.572502608194313, + 0.5 + ], + "edges": 10, + "faces": 3, + "volume": 0.0 + }, + "left_wire": { + "area": 0.0, + "bbox": [ + -2.6645352591003757e-15, + 3.552713678800501e-15, + 0.0, + 9.982677224380025, + 16.572502608194313, + 0.0 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + }, + "outline": { + "area": 131.28762859224454, + "bbox": [ + -11.982677224380026, + -3.639822484774289, + 0.0, + 11.982677224380026, + 18.572502608194313, + 0.0 + ], + "edges": 12, + "faces": 1, + "volume": 0.0 + }, + "right_side": { + "area": 29.24807855086281, + "bbox": [ + -9.982677224380025, + 3.552713678800501e-15, + -0.5, + 3.1086244689504383e-15, + 16.572502608194313, + 0.5 + ], + "edges": 10, + "faces": 3, + "volume": 0.0 + }, + "top_left_surface": { + "area": 107.5332685851343, + "bbox": [ + -9.982677224380025, + 0.0, + -4.822598771995575, + 3.1086244689504383e-15, + 16.572502608194313, + 2.5259267624610304 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "top_right_surface": { + "area": 107.5332685851343, + "bbox": [ + -2.664535259100376e-15, + 0.0, + -4.822598771995575, + 9.982677224380025, + 16.572502608194313, + 2.5259267624610304 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/line_types": { + "status": "no-shapes" + }, + "docs/objects_1d": { + "shapes": { + "b0": { + "area": 0.0, + "bbox": [ + 16.999999900000013, + -188.0000001, + -1e-07, + 76.0000001, + -80.99999990000008, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "b1": { + "area": 0.0, + "bbox": [ + 16.9999999, + -120.88173612530014, + -1e-07, + 167.00000009999997, + -66.99999990000003, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "b2": { + "area": 0.0, + "bbox": [ + 31.999999900000056, + -67.0000001, + -1e-07, + 169.764206271366, + 29.78469938145792, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "b3": { + "area": 0.0, + "bbox": [ + -9.999993605115378e-08, + 17.9999999, + -1e-07, + 80.94095757252077, + 188.00000010000002, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "bezier_curve": { + "area": 0.0, + "bbox": [ + -0.3005197222022917, + -0.7739316599914514, + -1e-07, + 1.652805652127694, + 3.0000000999999923, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "center_arc": { + "area": 0.0, + "bbox": [ + 1.8369701987210297e-16, + 0.0, + 0.0, + 3.0, + 3.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "club_outline": { + "area": 0.0, + "bbox": [ + -169.764206271366, + -188.0000001, + -1e-07, + 169.764206271366, + 188.00000010000002, + 1e-07 + ], + "edges": 10, + "faces": 0, + "volume": 0.0 + }, + "dot": { + "area": 0.007853981633974483, + "bbox": [ + -0.05, + -0.05, + 0.0, + 0.05, + 0.05, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "double_tangent": { + "area": 0.0, + "bbox": [ + -1e-07, + 0.0, + -1e-07, + 10.000000100000003, + 10.00000010000001, + 1e-07 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + }, + "elliptical_center_arc": { + "area": 0.0, + "bbox": [ + 8.229256270464882e-16, + -7.731356645413403e-16, + 0.0, + 2.0, + 3.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "example_1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 2.0, + 1.0, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "example_2": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 2.0, + 1.0, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "example_3": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 2.0, + 1.0, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "example_4": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 2.0, + 1.0, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "example_5": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 5.0, + 4.5, + 0.0 + ], + "edges": 4, + "faces": 0, + "volume": 0.0 + }, + "example_6": { + "area": 71458.36040851026, + "bbox": [ + -169.764206271366, + -188.0000001, + -1e-07, + 169.764206271366, + 188.0000001, + 1e-07 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "example_7": { + "area": 5.107700537488835, + "bbox": [ + -0.09114388277661528, + -0.04114388277661479, + -0.1000001000000001, + 3.1000001000000057, + 3.389684097194619, + 0.10000010000000023 + ], + "edges": 7, + "faces": 5, + "volume": 0.25224086415950203 + }, + "example_7_path": { + "area": 0.0, + "bbox": [ + -4.440892098500626e-16, + 0.0, + -1e-07, + 3.0000001000000003, + 3.2906428477012213, + 1e-07 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + }, + "example_7_section": { + "area": 0.031415926535897934, + "bbox": [ + -0.1, + -0.1, + 0.0, + 0.1, + 0.1, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "example_8": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 0.0, + 5.0, + 4.5 + ], + "edges": 4, + "faces": 0, + "volume": 0.0 + }, + "filletpolyline": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 10.0, + 5.0, + 20.0 + ], + "edges": 5, + "faces": 0, + "volume": 0.0 + }, + "helix": { + "area": 0.0, + "bbox": [ + -1.0000001000089513, + -1.0000001000089516, + -1.0000000005551115e-07, + 1.0000001000089511, + 1.0000001000089505, + 3.0000000999999954 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "intersecting_line": { + "area": 0.0, + "bbox": [ + 1.0, + 0.0, + 0.0, + 1.9999999999999996, + 0.9999999999999997, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "jern_arc": { + "area": 0.0, + "bbox": [ + 1.0, + 1.0, + 0.0, + 2.1055728090000843, + 3.980324517747254, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l0": { + "area": 0.0, + "bbox": [ + 0.0, + -188.0, + 0.0, + 76.0, + -188.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 0.0, + 5.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + -1e-07, + 6.967844587866777, + -1e-07, + 10.000000100000001, + 10.000000100000006, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 1.242000349090623, + 0.0, + 0.0, + 5.999999999999999, + 9.069002744142319, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 4.5, + 0.0, + 4.5, + 4.5 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "line": { + "area": 0.0, + "bbox": [ + 1.0, + 1.0, + 0.0, + 3.0, + 3.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "other": { + "area": 0.0, + "bbox": [ + 2.0, + 0.0, + 0.0, + 2.0, + 2.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "polar_line": { + "area": 0.0, + "bbox": [ + 1.0, + 1.0, + 0.0, + 2.25, + 3.1650635094610964, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "polyline": { + "area": 0.0, + "bbox": [ + 1.0, + 1.0, + 0.0, + 3.0, + 3.0, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "radius_arc": { + "area": 0.0, + "bbox": [ + 1.0, + 1.0, + 0.0, + 3.0, + 3.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "sagitta_arc": { + "area": 0.0, + "bbox": [ + 1.0, + 1.0, + 0.0, + 3.0, + 2.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "scene": { + "area": 0.0, + "bbox": [ + -0.17000000000000004, + -0.17000000000000004, + -1e-07, + 10.0, + 5.0, + 20.0 + ], + "edges": 27, + "faces": 0, + "volume": 0.0 + }, + "spline": { + "area": 0.0, + "bbox": [ + 0.9999998999999999, + 0.9999999, + -1e-07, + 2.1179608811676958, + 3.0000000999999985, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "tangent_arc": { + "area": 0.0, + "bbox": [ + 1.0, + 1.0, + 0.0, + 3.0, + 3.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "three_point_arc": { + "area": 0.0, + "bbox": [ + 1.0, + 1.0, + 0.0, + 2.999999999999999, + 3.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/objects_1d_airfoil": { + "shapes": { + "airfoil": { + "area": 0.0, + "bbox": [ + -0.0003477461814230394, + -0.0453617673637853, + -1e-07, + 1.0000839797955396, + 0.08473140414780772, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -0.0003477461814230394, + -0.0453617673637853, + -1e-07, + 1.0000839797955396, + 0.08473140414780772, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/objects_1d_blend_curve": { + "shapes": { + "blend_curve": { + "area": 0.0, + "bbox": [ + -3.5355339059327386, + -11.0000001, + -1e-07, + 5.0000001, + 5.0, + 1e-07 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -3.5355339059327386, + 1.2246467991473533e-15, + 0.0, + 5.0, + 5.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + -3.0000001000000003, + -11.0000001, + -1e-07, + 1e-07, + -4.9999999, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + -1.0000000088817842e-07, + -5.000000100000002, + -1e-07, + 5.0000001, + 1.000000012246468e-07, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/objects_1d_bspline": { + "shapes": { + "dot": { + "area": 0.007853981633974483, + "bbox": [ + -0.05, + -0.05, + 0.0, + 0.05, + 0.05, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "spline": { + "area": 0.0, + "bbox": [ + -1e-07, + -1e-07, + -1e-07, + 5.0000001, + 1.777777877777778, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/objects_1d_constrained": { + "shapes": { + "a1": { + "area": 0.0, + "bbox": [ + -1.34372973372799, + -1.9999392011839694, + 0.0, + 5.590594600591985, + 3.5751215976320596, + 0.0 + ], + "edges": 8, + "faces": 0, + "volume": 0.0 + }, + "arcs": { + "area": 0.0, + "bbox": [ + -1.5, + -2.0, + 0.0, + 6.0, + 3.5751215976320596, + 0.0 + ], + "edges": 32, + "faces": 0, + "volume": 0.0 + }, + "c1": { + "area": 0.0, + "bbox": [ + 2.0, + -2.0, + 0.0, + 6.0, + 2.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "c2": { + "area": 0.0, + "bbox": [ + -1.5, + 0.5, + 0.0, + 1.5, + 3.5, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "dot": { + "area": 0.007853981633974483, + "bbox": [ + -0.05, + -0.05, + 0.0, + 0.05, + 0.05, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -0.8166145812986692, + -1.677638883463118, + 0.0, + 4.6888194417315585, + 3.408229162597338, + 0.0 + ], + "edges": 4, + "faces": 0, + "volume": 0.0 + }, + "lines": { + "area": 0.0, + "bbox": [ + -1.5, + -2.0, + 0.0, + 6.0, + 3.5, + 0.0 + ], + "edges": 14, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/objects_1d_ellipticalstartarc": { + "shapes": { + "a": { + "area": 0.0, + "bbox": [ + -1.2181233730207475, + -0.7122930923219757, + 0.0, + 1.0000000000000002, + 2.602926396120729, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "arcs": { + "area": 0.0, + "bbox": [ + -1.2181233730207475, + -0.7122930923219757, + 0.0, + 1.0000000000000002, + 2.602926396120729, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "d": { + "area": 0.0, + "bbox": [ + -0.14354374979373108, + -0.34534558799262466, + 0.0, + -0.04548568222463907, + 0.1449447498528354, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "dot": { + "area": 0.007853981633974483, + "bbox": [ + -0.05, + -0.05, + 0.0, + 0.05, + 0.05, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/objects_1d_parabolic_hyperbolic": { + "shapes": { + "dot": { + "area": 0.007853981633974483, + "bbox": [ + -0.05, + -0.05, + 0.0, + 0.05, + 0.05, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "hyperbolic_center_arc": { + "area": 0.0, + "bbox": [ + -1.1506494511536471, + 1.0, + 0.0, + 1.1506494511536476, + 2.5091784786580567, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "parabolic_center_arc": { + "area": 0.0, + "bbox": [ + 0.0, + -1.0471975511965976, + 0.0, + 1.0966227112321507, + 1.0471975511965976, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/objects_2d": { + "shapes": { + "a1": { + "area": 0.0, + "bbox": [ + -13.125000000000002, + -12.360330811826104, + 0.0, + -10.0, + -7.725206757391314, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "a2": { + "area": 0.0, + "bbox": [ + 10.0, + -12.360330811826104, + 0.0, + 13.125, + -7.725206757391315, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "a3": { + "area": 0.0, + "bbox": [ + -1.8750000000000036, + 19.720661623652212, + 0.0, + 1.8749999999999947, + 20.085537569217422, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "align": { + "area": 0.20378540262707984, + "bbox": [ + -1.0, + -1.0, + -1e-07, + 1.0, + 1.0, + 1e-07 + ], + "edges": 1497, + "faces": 73, + "volume": 0.0 + }, + "arc": { + "area": 0.0, + "bbox": [ + 0.7071067811865476, + 0.0, + 0.0, + 1.0, + 0.7071067811865475, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "arrow": { + "area": 31.884992221173, + "bbox": [ + 96.66666666666669, + 0.0, + 0.0, + 103.33333333333333, + 17.451641855526496, + 0.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "arrow_head": { + "area": 567.3233268822228, + "bbox": [ + -47.936741552969245, + -14.117980750193544, + 0.0, + 1.0658141036401503e-14, + 14.117980750193544, + 0.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "arrow_heads[0]": { + "area": 583.6891229404811, + "bbox": [ + -50.0, + -16.666666666666657, + 0.0, + 1.0658141036401503e-14, + 16.666666666666657, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "arrow_heads[1]": { + "area": 833.3333333333335, + "bbox": [ + -50.00000000000001, + -16.666666666666668, + 0.0, + 7.105427357601002e-15, + 16.66666666666667, + 0.0 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "arrow_heads[2]": { + "area": 567.3233268822228, + "bbox": [ + -47.936741552969245, + -14.117980750193544, + 0.0, + 1.0658141036401503e-14, + 14.117980750193544, + 0.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "c": { + "area": 6361.725123519331, + "bbox": [ + -45.0, + -45.0, + 0.0, + 45.0, + 45.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "circle_with_hole": { + "area": 9709.733552923255, + "bbox": [ + -60.0, + -60.0, + 0.0, + 60.0, + 60.0, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "controller": { + "area": 33184.47090241657, + "bbox": [ + -30.000000000000004, + -4.440892098500626e-16, + -4.440892098500626e-16, + 30.000000000000004, + 40.0, + 80.0 + ], + "edges": 156, + "faces": 76, + "volume": 16509.291500220876 + }, + "d_line": { + "area": 3695.8989384292922, + "bbox": [ + -50.0, + -50.0, + -1e-07, + 50.0, + 50.0, + 1e-07 + ], + "edges": 64, + "faces": 10, + "volume": 0.0 + }, + "display": { + "area": 1209.1327411168006, + "bbox": [ + -23.5, + -18.5, + 0.0, + 23.5, + 18.5, + 0.0 + ], + "edges": 12, + "faces": 5, + "volume": 0.0 + }, + "display_face": { + "area": 2276.5888971673244, + "bbox": [ + -27.000000000000004, + 1.211145618000168, + 40.186223258500554, + 27.000000000000004, + 20.06524758424986, + 77.89442719099992 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "dot": { + "area": 0.007853981633974483, + "bbox": [ + -0.05, + -0.05, + 0.0, + 0.05, + 0.05, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "e_line": { + "area": 6055.834951872848, + "bbox": [ + -52.0, + -49.75000000000002, + -1e-07, + 51.8300049828125, + 49.75000000000001, + 1e-07 + ], + "edges": 129, + "faces": 19, + "volume": 0.0 + }, + "example_1": { + "area": 3.141592653589792, + "bbox": [ + -1.0, + -1.0, + 0.0, + 1.0, + 1.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "example_10": { + "area": 0.2365873852123405, + "bbox": [ + -0.5, + -0.125, + 0.0, + 0.5, + 0.125, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "example_11": { + "area": 0.3661889620534203, + "bbox": [ + -0.7470004255208335, + -0.27300145294596356, + -1e-07, + 0.7550017276041666, + 0.4180013512207032, + 1e-07 + ], + "edges": 46, + "faces": 4, + "volume": 0.0 + }, + "example_12": { + "area": 1.7920243386146189, + "bbox": [ + -1.0000001, + -0.5000001, + -1e-07, + 1.0000001, + 0.5000001, + 1e-07 + ], + "edges": 12, + "faces": 5, + "volume": 0.0 + }, + "example_2": { + "area": 4.712388980384689, + "bbox": [ + -1.5, + -1.0, + 0.0, + 1.5, + 1.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "example_3": { + "area": 2.2041946960967733, + "bbox": [ + -1.5, + -1.4265847744427302, + 0.0, + 1.2135254915624212, + 1.4265847744427305, + 0.0 + ], + "edges": 10, + "faces": 1, + "volume": 0.0 + }, + "example_4": { + "area": 2.0, + "bbox": [ + -1.0, + -0.5, + 0.0, + 1.0, + 0.5, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "example_5": { + "area": 1.946349383472889, + "bbox": [ + -1.0, + -0.500000004693107, + 0.0, + 1.0, + 0.500000004693107, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "example_6": { + "area": 2.598076211353316, + "bbox": [ + -1.0, + -0.8660254037844386, + 0.0, + 1.0, + 0.8660254037844387, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "example_7": { + "area": 0.2454369260617028, + "bbox": [ + 0.5821067811865476, + -0.125, + 0.0, + 1.125, + 0.8321067811865475, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "example_8": { + "area": 0.5490873852123406, + "bbox": [ + -0.125, + -1.125, + 0.0, + 0.125, + 1.125, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "example_9": { + "area": 0.2990873852123405, + "bbox": [ + -0.1250000000000001, + -0.625, + 0.0, + 0.1250000000000001, + 0.625, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "isosceles_triangle": { + "area": 581.0722922878593, + "bbox": [ + -15.05, + -20.360330811826103, + -1e-07, + 22.434948587095644, + 27.767012864196612, + 1e-07 + ], + "edges": 82, + "faces": 7, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -40.0, + -40.0, + 0.0, + 20.0, + 40.0, + 0.0 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + }, + "outside_curve": { + "area": 0.0, + "bbox": [ + 19.999999999999996, + -39.99999999999999, + 0.0, + 39.999999999999986, + 40.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "p1": { + "area": 0.0, + "bbox": [ + -12.000000000000002, + -12.360330811826104, + 0.0, + -7.0, + -4.9441323247304405, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "p2": { + "area": 0.0, + "bbox": [ + 7.0, + -12.360330811826103, + 0.0, + 12.0, + -4.944132324730441, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "p3": { + "area": 0.0, + "bbox": [ + -3.0000000000000036, + 16.720661623652212, + 0.0, + 2.999999999999994, + 17.30446313655655, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "profile": { + "area": 2800.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 40.0, + 80.0, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "t": { + "area": 556.2148865321747, + "bbox": [ + -15.0, + -12.360330811826104, + 0.0, + 15.0, + 24.720661623652212, + 0.0 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "t1": { + "area": 5.136676608677263, + "bbox": [ + -9.626752146893367, + -9.138195211139674, + -1e-07, + -6.906748691685034, + -5.493191755931341, + 1e-07 + ], + "edges": 25, + "faces": 1, + "volume": 0.0 + }, + "t2": { + "area": 3.8481447407603273, + "bbox": [ + 6.794248691685033, + -9.25320172155634, + -1e-07, + 9.939252146893367, + -5.4331982663480085, + 1e-07 + ], + "edges": 22, + "faces": 1, + "volume": 0.0 + }, + "t3": { + "area": 3.9716762777169525, + "bbox": [ + -1.5899983723958384, + 15.470661369339062, + 0.0, + 1.5899983723958284, + 19.115664624547396, + 0.0 + ], + "edges": 11, + "faces": 1, + "volume": 0.0 + }, + "tech_drawing": { + "area": 7398.438377480315, + "bbox": [ + -143.5, + -99.23001362945963, + -1e-07, + 143.5, + 101.40999308095702, + 1e-07 + ], + "edges": 1140, + "faces": 133, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/objects_3d": { + "shapes": { + "example_1": { + "area": 22.0, + "bbox": [ + -1.5, + -1.0, + -0.5, + 1.5, + 1.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 6.0 + }, + "example_10": { + "area": 5776.661211217315, + "bbox": [ + -30.0, + -15.0, + -15.0, + 15.0, + 15.0, + 15.0 + ], + "edges": 48, + "faces": 26, + "volume": 33876.666666666664 + }, + "example_2": { + "area": 36.78240746107114, + "bbox": [ + -2.0, + -2.0, + -1.0000000000000002, + 2.0, + 2.0, + 1.0000000000000002 + ], + "edges": 3, + "faces": 3, + "volume": 14.660765716752369 + }, + "example_3": { + "area": 23.759291886010285, + "bbox": [ + -1.5, + -1.0, + -0.5, + 1.5, + 1.0, + 0.5 + ], + "edges": 18, + "faces": 10, + "volume": 5.69840710525538 + }, + "example_4": { + "area": 23.03949299782655, + "bbox": [ + -1.5, + -1.0, + -0.5, + 1.5, + 1.0, + 0.5 + ], + "edges": 17, + "faces": 9, + "volume": 5.848353449142262 + }, + "example_5": { + "area": 18.849555921538755, + "bbox": [ + -1.0, + -1.0, + -1.0, + 1.0, + 1.0, + 1.0 + ], + "edges": 3, + "faces": 3, + "volume": 6.283185307179585 + }, + "example_6": { + "area": 23.5079644737231, + "bbox": [ + -1.5, + -1.0, + -0.5, + 1.5, + 1.0, + 0.5 + ], + "edges": 15, + "faces": 7, + "volume": 5.497345175425633 + }, + "example_7": { + "area": 9.42477796076938, + "bbox": [ + -1.0, + -1.0, + -0.5, + 1.0, + 1.0, + 0.5 + ], + "edges": 2, + "faces": 2, + "volume": 2.0943951023931957 + }, + "example_8": { + "area": 7.895683520871486, + "bbox": [ + -1.2000001, + -1.2000001, + -0.20000010000000001, + 1.2000001, + 1.2000001, + 0.20000010000000001 + ], + "edges": 2, + "faces": 1, + "volume": 0.7895683520871484 + }, + "example_9": { + "area": 4.427050983124842, + "bbox": [ + -0.5, + -0.5000000000000001, + -0.5, + 0.5, + 0.5000000000000001, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 0.5833333333333333 + } + }, + "status": "ok" + }, + "docs/pack_demo": { + "shapes": { + "b1": { + "area": 60000.0, + "bbox": [ + -50.0, + -50.0, + 0.0, + 50.0, + 50.0, + 100.0 + ], + "edges": 12, + "faces": 6, + "volume": 999999.9999999999 + }, + "b2": { + "area": 17496.0, + "bbox": [ + -27.0, + -27.0, + -54.0, + 27.0, + 27.0, + 0.0 + ], + "edges": 12, + "faces": 6, + "volume": 157464.0 + }, + "b3": { + "area": 6936.0, + "bbox": [ + 0.0, + 0.0, + -17.0, + 34.0, + 34.0, + 17.0 + ], + "edges": 12, + "faces": 6, + "volume": 39304.0 + }, + "b4": { + "area": 3456.0, + "bbox": [ + -24.0, + -24.0, + -12.0, + 0.0, + 0.0, + 12.0 + ], + "edges": 12, + "faces": 6, + "volume": 13824.0 + }, + "xy_pack[0]": { + "area": 3456.0, + "bbox": [ + 0.0, + 105.0, + -12.0, + 24.0, + 129.0, + 12.0 + ], + "edges": 12, + "faces": 6, + "volume": 13824.0 + }, + "xy_pack[1]": { + "area": 60000.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 100.0, + 100.0, + 100.0 + ], + "edges": 12, + "faces": 6, + "volume": 999999.9999999999 + }, + "xy_pack[2]": { + "area": 6936.0, + "bbox": [ + 105.0, + 59.0, + -17.0, + 139.0, + 93.0, + 17.0 + ], + "edges": 12, + "faces": 6, + "volume": 39304.0 + }, + "xy_pack[3]": { + "area": 17496.0, + "bbox": [ + 105.0, + 0.0, + -54.0, + 159.0, + 54.0, + 0.0 + ], + "edges": 12, + "faces": 6, + "volume": 157464.0 + }, + "z_pack[0]": { + "area": 3456.0, + "bbox": [ + 0.0, + 105.0, + 0.0, + 24.0, + 129.0, + 24.0 + ], + "edges": 12, + "faces": 6, + "volume": 13824.0 + }, + "z_pack[1]": { + "area": 60000.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 100.0, + 100.0, + 100.0 + ], + "edges": 12, + "faces": 6, + "volume": 999999.9999999999 + }, + "z_pack[2]": { + "area": 6936.0, + "bbox": [ + 105.0, + 59.0, + 0.0, + 139.0, + 93.0, + 34.0 + ], + "edges": 12, + "faces": 6, + "volume": 39304.0 + }, + "z_pack[3]": { + "area": 17496.0, + "bbox": [ + 105.0, + 0.0, + 0.0, + 159.0, + 54.0, + 54.0 + ], + "edges": 12, + "faces": 6, + "volume": 157464.0 + } + }, + "status": "ok" + }, + "docs/rigid_joints_pipe": { + "error": "ModuleNotFoundError: No module named 'bd_warehouse'", + "status": "error" + }, + "docs/rod_end": { + "error": "ModuleNotFoundError: No module named 'bd_warehouse'", + "status": "error" + }, + "docs/selector_example": { + "shapes": { + "example": { + "area": 923.6670838177648, + "bbox": [ + -10.0, + -10.0, + -1.5, + 10.0, + 10.0, + 1.5 + ], + "edges": 26, + "faces": 13, + "volume": 775.2146467682759 + } + }, + "status": "ok" + }, + "docs/slide_latch": { + "shapes": { + "end": { + "area": 419.9999999999999, + "bbox": [ + 35.0, + -15.0, + -7.0, + 35.0, + 15.0, + 7.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "l4": { + "area": 2460.5132658689467, + "bbox": [ + -35.00000000000001, + -25.0, + 0.0, + 35.00000000000001, + 25.0, + 0.0 + ], + "edges": 16, + "faces": 1, + "volume": 0.0 + }, + "latch": { + "area": 12154.798699572615, + "bbox": [ + -35.00000000000001, + -25.0, + -7.0, + 35.00000000000001, + 25.0, + 7.0 + ], + "edges": 138, + "faces": 52, + "volume": 11831.250489574682 + }, + "s1": { + "area": 241.76714429538606, + "bbox": [ + -4.750000000000072, + -12.75, + 0.0, + 4.749999938342812, + 12.75, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "s2": { + "area": 63.283185307179565, + "bbox": [ + 0.0, + -7.5, + 0.0, + 14.000000000000002, + 0.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "slide": { + "area": 5445.343408542543, + "bbox": [ + -8.0000001, + -12.750000100000001, + -4.750000100000072, + 58.00000010000001, + 12.750000100000003, + 14.000000099999998 + ], + "edges": 56, + "faces": 31, + "volume": 16765.45878762745 + }, + "slide_hole": { + "area": 259.1415925302756, + "bbox": [ + -5.000000000000072, + -13.0, + 0.0, + 5.0, + 13.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/spitfire_wing_gordon": { + "shapes": { + "airfoil_root": { + "area": 0.0, + "bbox": [ + -1e-07, + -762.9757386374004, + -127.31659188993615, + 1e-07, + 2044.9354255245626, + 237.81535145165188 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "airfoil_tip": { + "area": 0.0, + "bbox": [ + 5587.9999999, + -72.41933657704821, + -1.6342193604474813, + 5588.0000001, + 194.3153584753745, + 11.939555252102151 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "leading_edge": { + "area": 0.0, + "bbox": [ + -1.0311648513500628e-12, + -761.9999999999999, + 0.0, + 5613.4, + -1.866361721900566e-13, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "leading_pnt": { + "area": 0.0, + "bbox": [ + 5588.000000000007, + -72.40723981898407, + 0.0, + 5588.000000000007, + -72.40723981898407, + 0.0 + ], + "edges": 0, + "faces": 0, + "volume": 0.0 + }, + "trailing_edge": { + "area": 0.0, + "bbox": [ + 3.437216171166876e-13, + 0.0, + 0.0, + 5613.4, + 2044.6999999999998, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "trailing_pnt": { + "area": 0.0, + "bbox": [ + 5588.000000000006, + 194.29276018094413, + 0.0, + 5588.000000000006, + 194.29276018094413, + 0.0 + ], + "edges": 0, + "faces": 0, + "volume": 0.0 + }, + "wing": { + "area": 25739616.165786173, + "bbox": [ + -1.000070319180606e-07, + -763.1497142918938, + -127.31667772465318, + 5613.400000101416, + 2044.700000500986, + 237.81569027837008 + ], + "edges": 2, + "faces": 2, + "volume": 1987994598.9439144 + }, + "wing_root": { + "area": 699009.5088700533, + "bbox": [ + -1.0000113626453102e-07, + -763.1497142918938, + -127.31667772465318, + 1.0000034106051316e-07, + 2044.700000500986, + 237.81569027837008 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "wing_surface": { + "area": 25040606.65691612, + "bbox": [ + -1.000070319180606e-07, + -763.1497142918905, + -127.31667772462875, + 5613.400000101416, + 2044.700000500986, + 237.81569027834476 + ], + "edges": 2, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "docs/technical_drawing": { + "error": "ModuleNotFoundError: No module named 'bd_warehouse'", + "status": "error" + }, + "docs/tutorial_joints": { + "shapes": { + "box": { + "area": 400755.84067435225, + "bbox": [ + -150.0, + -150.0, + 0.0, + 150.0, + 150.0, + 100.0 + ], + "edges": 45, + "faces": 17, + "volume": 1940751.7699835314 + }, + "box_assembly": { + "area": 620900.3194944883, + "bbox": [ + -160.0000001, + -150.0, + 0.0, + 150.0, + 150.0000000000001, + 260.4903810567665 + ], + "edges": 188, + "faces": 77, + "volume": 2868656.424929345 + }, + "box_builder": { + "area": 400755.84067435225, + "bbox": [ + -150.0, + -150.0, + -50.0, + 150.0, + 150.0, + 50.0 + ], + "edges": 45, + "faces": 17, + "volume": 1940751.7699835314 + }, + "hinge_inner": { + "area": 12442.9869076802, + "bbox": [ + -160.00000000000003, + -60.0, + 90.00000000000001, + -117.52885682970023, + 60.000000000000014, + 121.8301270189222 + ], + "edges": 53, + "faces": 21, + "volume": 12636.047054068478 + }, + "hinge_outer": { + "area": 15305.651238102755, + "bbox": [ + -160.0000001, + -64.0, + 49.99999999999999, + -150.0, + 60.0, + 100.00000010000001 + ], + "edges": 69, + "faces": 30, + "volume": 16116.837908214178 + }, + "lid": { + "area": 192395.84067435237, + "bbox": [ + -158.1698729810779, + -150.0, + 101.83012701892221, + 106.6377481542539, + 150.0000000000001, + 260.4903810567665 + ], + "edges": 21, + "faces": 9, + "volume": 899151.7699835307 + }, + "lid_builder": { + "area": 192395.84067435237, + "bbox": [ + -158.1698729810779, + -150.0, + 101.83012701892221, + 106.6377481542539, + 150.0000000000001, + 260.4903810567665 + ], + "edges": 21, + "faces": 9, + "volume": 899151.7699835307 + }, + "m6_screw": { + "area": 465.63400042563006, + "bbox": [ + -157.0000001, + -45.18180204846601, + 64.818197951534, + -144.9999999, + -34.818197951534, + 75.181802048466 + ], + "edges": 72, + "faces": 28, + "volume": 332.2762644199616 + } + }, + "status": "ok" + }, + "examples/bicycle_tire": { + "shapes": { + "build_profile": { + "area": 0.0, + "bbox": [ + -20.000180988089184, + -1e-07, + -1e-07, + 20.000180988089184, + 46.800846774556284, + 1e-07 + ], + "edges": 38, + "faces": 0, + "volume": 0.0 + }, + "half_road_surface": { + "area": 37520.822625203444, + "bbox": [ + -1e-07, + -370.0000001, + -370.0000001, + 15.130000099999998, + 370.0000001, + 370.0000001 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "l00": { + "area": 0.0, + "bbox": [ + -1e-07, + -1e-07, + -1e-07, + 15.130000099999998, + 4.5400000999999985, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l01": { + "area": 0.0, + "bbox": [ + 15.1299999, + 4.5399999, + -1e-07, + 16.5000001, + 6.2300001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l02": { + "area": 0.0, + "bbox": [ + 16.4999999, + 6.2299999, + -1e-07, + 19.940000100000002, + 20.060000099999996, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l03": { + "area": 0.0, + "bbox": [ + 19.559999899999998, + 20.059999899999998, + -1e-07, + 20.000180988089184, + 29.4500001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l04": { + "area": 0.0, + "bbox": [ + 16.9099999, + 29.449999899999998, + -1e-07, + 19.5600001, + 35.3200001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l05": { + "area": 0.0, + "bbox": [ + 14.479999900000001, + 35.3199999, + -1e-07, + 16.9100001, + 37.5800001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l06": { + "area": 0.0, + "bbox": [ + 10.759999900000002, + 37.5799999, + -1e-07, + 14.4800001, + 41.7800001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l07": { + "area": 0.0, + "bbox": [ + 10.298929809814856, + 41.7799999, + -1e-07, + 11.030000099999999, + 43.9800001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l08": { + "area": 0.0, + "bbox": [ + 11.0299999, + 43.979999899999996, + -1e-07, + 12.089147795245106, + 45.3300001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l09": { + "area": 0.0, + "bbox": [ + 11.4299999, + 45.3299999, + -1e-07, + 12.0800001, + 46.6900001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l10": { + "area": 0.0, + "bbox": [ + 9.469999900000001, + 46.0999999, + -1e-07, + 11.430000099999999, + 46.800846774556284, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l11": { + "area": 0.0, + "bbox": [ + 8.8399999, + 44.6499999, + -1e-07, + 9.4700001, + 46.1000001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l12": { + "area": 0.0, + "bbox": [ + 8.833025921944877, + 40.9999999, + -1e-07, + 9.7200001, + 44.6500001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l13": { + "area": 0.0, + "bbox": [ + 9.719999900000001, + 37.2199999, + -1e-07, + 12.780000099999997, + 41.0000001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l14": { + "area": 0.0, + "bbox": [ + 12.7799999, + 31.6199999, + -1e-07, + 17.4500001, + 37.2200001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l15": { + "area": 0.0, + "bbox": [ + 17.449999899999998, + 27.7999999, + -1e-07, + 18.4000001, + 31.620000100000002, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l16": { + "area": 0.0, + "bbox": [ + 18.3699999, + 22.6099999, + -1e-07, + 18.490535814285714, + 27.800000100000002, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l17": { + "area": 0.0, + "bbox": [ + 13.389999900000003, + 11.939999900000002, + -1e-07, + 18.370000100000002, + 22.6100001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l18": { + "area": 0.0, + "bbox": [ + 8.0899999, + 8.4099999, + -1e-07, + 13.3900001, + 11.940000099999999, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l19": { + "area": 0.0, + "bbox": [ + -1e-07, + 6.604212665957446, + -1e-07, + 8.0900001, + 8.4100001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "tire": { + "area": 523146.5151533416, + "bbox": [ + -20.000180988089184, + -370.0000001, + -370.0000001, + 20.000180988089184, + 370.0000001, + 370.0000001 + ], + "edges": 74, + "faces": 37, + "volume": 906269.1100540357 + }, + "tire_profile": { + "area": 407.8638289680655, + "bbox": [ + -20.000180988089184, + -1e-07, + -1e-07, + 20.000180988089184, + 46.800846774556284, + 1e-07 + ], + "edges": 37, + "faces": 1, + "volume": 0.0 + }, + "tread[0]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -372.98736580465527, + -11.17619194365762, + 4.8369313569596954, + -369.5975783806762, + -1.0008285568041597 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[10]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -350.1483886508986, + -137.9311863926377, + 4.8369313569596954, + -343.51621027745887, + -127.48383856366523 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[11]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -345.4498982749647, + -149.9323633049298, + 4.8369313569596954, + -338.5317213224887, + -139.52791669737874 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[12]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -340.33053041058224, + -161.7508707279667, + 4.8369313569596954, + -333.13478361177926, + -151.40200155710264 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[13]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -334.79652221891575, + -173.3723096309551, + 4.8369313569596954, + -327.33197248259665, + -163.09162639949787 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[14]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -328.85461603649816, + -184.78252108073696, + 4.8369313569596954, + -321.1303577664473, + -174.58254921734266 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[15]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -322.5120511607321, + -195.96760349226915, + 4.8369313569596954, + -314.53749517557713, + -185.86077009122434 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220365 + }, + "tread[16]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -315.77655502992167, + -206.91392956556308, + 4.8369313569596954, + -307.56141709750057, + -196.91254824628498 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[17]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -308.65633380857855, + -217.60816288845075, + 4.8369313569596954, + -300.21062280877334, + -207.72441879323853 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[18]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -301.16006238947506, + -228.03727418494847, + 4.8369313569596954, + -292.4940681199333, + -218.28320913326442 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220365 + }, + "tread[19]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -293.29687382462305, + -238.18855718942302, + 4.8369313569596954, + -284.4211544642244, + -228.57605500679043 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220365 + }, + "tread[1]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -372.7249395785359, + -24.17215005844477, + 4.8369313569596954, + -368.9855060007738, + -13.912615760007357 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[20]": { + "area": 132.6498132210872, + "bbox": [ + 0.9998724708504492, + -285.07634819805764, + -248.0496441272186, + 4.8369313569596954, + -276.00171744339934, + -238.59041616661088 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[21]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -276.5085009539798, + -257.60852078288565, + 4.8369313569596954, + -267.24601484455303, + -248.3140916562467 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220365 + }, + "tread[22]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -267.6037706944808, + -266.8535411376511, + 4.8369313569596954, + -258.1647141425896, + -257.73523467493106 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[23]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -258.37300646171326, + -275.77344155829917, + 4.8369313569596954, + -248.76887950354728, + -266.8423670111117 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[24]": { + "area": 132.6498132210872, + "bbox": [ + 0.9998724708504492, + -248.82745452000455, + -284.3573545201725, + 4.8369313569596954, + -239.06995830461628, + -275.6243930268832 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[25]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -238.97874465401617, + -292.5948218475773, + 4.8369313569596954, + -229.07976718727332, + -284.0706131763124 + ], + "edges": 12, + "faces": 6, + "volume": 88.5329187422036 + }, + "tread[26]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -228.8388759996424, + -300.4758074554599, + 4.8369313569596954, + -218.8104776605239, + -292.1707370411881 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220368 + }, + "tread[27]": { + "area": 132.64981322108713, + "bbox": [ + 0.9998724708504492, + -218.42020242491142, + -307.99070957682983, + 4.8369313569596954, + -208.27460127179353, + -299.91489586830903 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220365 + }, + "tread[28]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -207.7354174787005, + -315.13037246103414, + 4.8369313569596954, + -197.48497436353452, + -307.2936545930409 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[29]": { + "area": 132.6498132210872, + "bbox": [ + 0.9998724708504492, + -196.79753892560157, + -321.8860975286292, + 4.8369313569596954, + -186.45474243411996, + -314.2980233344882 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[2]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -372.00840542753735, + -37.13865813194182, + 4.8369313569596954, + -367.9238816195476, + -26.807452583859323 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[30]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -185.61989288578076, + -328.24965396925967, + 4.8369313569596954, + -175.19734412207904, + -320.91946834827945 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[31]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -174.21609759915296, + -334.2132887696335, + 4.8369313569596954, + -163.7264948331855, + -327.1499224236211 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[32]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -162.60004683365506, + -339.76973615937396, + 4.8369313569596954, + -152.05617003034905, + -332.9817947119501 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[33]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -150.78589295782922, + -344.9122264632418, + 4.8369313569596954, + -140.20058820666617, + -338.4079799752143 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[34]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -138.7880296983424, + -349.63449434894153, + 4.8369313569596954, + -128.17419356237667, + -343.42186724251 + ], + "edges": 12, + "faces": 6, + "volume": 88.5329187422036 + }, + "tread[35]": { + "area": 132.6498132210872, + "bbox": [ + 0.9998724708504492, + -126.6210746034472, + -353.9307864604641, + 4.8369313569596954, + -115.99163840683067, + -348.01734786453227 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[36]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -114.29985123375076, + -357.79586842766463, + 4.8369313569596954, + -103.66776530690623, + -352.18882295602253 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[37]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -101.83937110199022, + -361.2250312435363, + 4.8369313569596954, + -91.2175890036286, + -355.93121021714836 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220365 + }, + "tread[38]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -89.25481538381626, + -364.21409700141027, + 4.8369313569596954, + -78.65627811902063, + -359.2399501255028 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220365 + }, + "tread[39]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -76.56151642186938, + -366.7631195826628, + 4.8369313569596954, + -65.9991366754737, + -362.1110114911796 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[3]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -370.8386363381493, + -50.059918471398674, + 4.8369313569596954, + -366.4139986627755, + -39.669628655987545 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[40]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -63.7749390456817, + -368.87833586799604, + 4.8369313569596954, + -53.26158545015341, + -364.540896368158 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220369 + }, + "tread[41]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -50.91066173016501, + -370.5580145680017, + 4.8369313569596954, + -40.45914318715862, + -366.5266443160102 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[42]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -37.98435761563816, + -371.8009330393594, + 4.8369313569596954, + -27.607407690321637, + -368.0409708949939 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[43]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -25.01177541252026, + -372.6064254246586, + 4.8369313569596954, + -14.722036819687345, + -369.06069727222183 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[44]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -12.008720213952772, + -372.9685766197825, + 4.8369313569596954, + -1.8187294148224347, + -369.6307800391441 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[45]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 1.0008285568040776, + -372.98736580465527, + 4.8369313569596954, + 11.176191943657537, + -369.5975783806762 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[46]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 13.91261576000734, + -372.7249395785359, + 4.8369313569596954, + 24.172150058444753, + -368.9855060007738 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220365 + }, + "tread[47]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 26.807452583859362, + -372.0084054275374, + 4.8369313569596954, + 37.13865813194186, + -367.92388161954767 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220369 + }, + "tread[48]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 39.66962865598755, + -370.83863633814934, + 4.8369313569596954, + 50.05991847139869, + -366.4139986627755 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[49]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 52.48347339611443, + -369.2170574938179, + 4.8369313569596954, + 62.92018851145924, + -364.45769669026066 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[4]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -369.2170574938179, + -62.92018851145925, + 4.8369313569596954, + -364.45769669026066, + -52.48347339611445 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220368 + }, + "tread[50]": { + "area": 132.6498132210872, + "bbox": [ + 0.9998724708504492, + 65.23337510824567, + -367.1456445385799, + 4.8369313569596954, + 75.70379999405247, + -362.05735915461133 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[51]": { + "area": 132.6498132210872, + "bbox": [ + 0.9998724708504492, + 77.90380000111682, + -364.6269211700445, + 4.8369313569596954, + 88.39517805775128, + -359.2159104973711 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[52]": { + "area": 132.6498132210872, + "bbox": [ + 0.9998724708504492, + 90.47931111372523, + -361.6639560646568, + 4.8369313569596954, + 100.9788602133419, + -355.9368125860372 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[53]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 102.9445871228891, + -358.2603591389879, + 4.8369313569596954, + 113.43951518248531, + -352.22406049630825 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[54]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 115.28444100992, + -354.42027715160765, + 4.8369313569596954, + 125.76196157651856, + -348.0821776446999 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[55]": { + "area": 132.6498132210872, + "bbox": [ + 0.9998724708504492, + 127.48383856366522, + -350.14838865089865, + 4.8369313569596954, + 137.9311863926377, + -343.5162102774589 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[56]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 139.5279166973788, + -345.4498982749647, + 4.8369313569596954, + 149.93236330492985, + -338.5317213224887 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[57]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 151.4020015571027, + -340.33053041058224, + 4.8369313569596954, + 161.75087072796683, + -333.13478361177926 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[58]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 163.09162639949784, + -334.79652221891575, + 4.8369313569596954, + 173.37230963095507, + -327.3319724825966 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread[59]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 174.5825492173427, + -328.8546160364981, + 4.8369313569596954, + 184.782521080737, + -321.1303577664472 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220368 + }, + "tread[5]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -367.14564453857986, + -75.70379999405249, + 4.8369313569596954, + -362.0573591546113, + -65.23337510824567 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220362 + }, + "tread[60]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 185.86077009122428, + -322.51205116073214, + 4.8369313569596954, + 195.9676034922691, + -314.53749517557713 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220368 + }, + "tread[61]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + 196.9125482462849, + -315.7765550299217, + 4.8369313569596954, + 206.913929565563, + -307.5614170975006 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220365 + }, + "tread[62]": { + "area": 132.64981322108713, + "bbox": [ + 0.9998724708504492, + 207.7244187932384, + -308.6563338085786, + 4.8369313569596954, + 217.60816288845064, + -300.2106228087734 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220359 + }, + "tread[63]": { + "area": 132.64981322108721, + "bbox": [ + 0.9998724708504492, + 218.2832091332645, + -301.16006238947506, + 4.8369313569596954, + 228.03727418494856, + -292.49406811993333 + ], + "edges": 12, + "faces": 6, + "volume": 88.5329187422037 + }, + "tread[6]": { + "area": 132.6498132210872, + "bbox": [ + 0.9998724708504492, + -364.6269211700445, + -88.39517805775127, + 4.8369313569596954, + -359.2159104973711, + -77.90380000111682 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[7]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -361.6639560646568, + -100.97886021334187, + 4.8369313569596954, + -355.93681258603715, + -90.4793111137252 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread[8]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -358.26035913898784, + -113.43951518248531, + 4.8369313569596954, + -352.2240604963082, + -102.9445871228891 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220365 + }, + "tread[9]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -354.42027715160765, + -125.76196157651856, + 4.8369313569596954, + -348.0821776446999, + -115.28444100992 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220365 + }, + "tread_faces[0]": { + "area": 12.228202995761057, + "bbox": [ + -12.617985782970301, + -367.99992919082837, + -15.920280238539997, + -11.250131996589724, + -367.0454599525497, + -7.007214936981318 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "tread_faces[1]": { + "area": 24.30283196548578, + "bbox": [ + -9.401602679969935, + -369.45425275353966, + -13.948074902773874, + -6.467020535967484, + -368.5052816264254, + -4.165090249157016 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "tread_faces[2]": { + "area": 28.299963439775834, + "bbox": [ + -4.5011496212511295, + -369.98805439919306, + -11.086793831802567, + -0.9998724708504492, + -369.5975783806762, + -1.0008285568041597 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "tread_faces[3]": { + "area": 28.299963439775834, + "bbox": [ + 0.9998724708504492, + -369.98805439919306, + -11.086793831802567, + 4.5011496212511295, + -369.5975783806762, + -1.0008285568041597 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "tread_faces[4]": { + "area": 24.30283196548578, + "bbox": [ + 6.467020535967484, + -369.45425275353966, + -13.948074902773874, + 9.401602679969935, + -368.5052816264254, + -4.165090249157016 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "tread_faces[5]": { + "area": 12.228202995761057, + "bbox": [ + 11.250131996589724, + -367.99992919082837, + -15.920280238539997, + 12.617985782970301, + -367.0454599525497, + -7.007214936981318 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "tread_path": { + "area": 0.0, + "bbox": [ + 0.0, + -370.0, + -370.0, + 0.0, + 370.0, + 370.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "tread_pattern": { + "area": 64.57437415779594, + "bbox": [ + 1.0, + 1.0, + 0.0, + 16.0, + 13.0, + 0.0 + ], + "edges": 12, + "faces": 3, + "volume": 0.0 + }, + "tread_prime[0]": { + "area": 86.71106461415305, + "bbox": [ + -14.109072137890605, + -370.74086851826763, + -16.03342656621189, + -11.250131996589724, + -367.0454599525497, + -7.007214936981318 + ], + "edges": 12, + "faces": 6, + "volume": 40.275611681727014 + }, + "tread_prime[1]": { + "area": 122.34106617585738, + "bbox": [ + -10.306420817495235, + -372.40635386566066, + -14.056134514839114, + -6.467020535967484, + -368.5052816264254, + -4.165090249157016 + ], + "edges": 12, + "faces": 6, + "volume": 77.81369200341186 + }, + "tread_prime[2]": { + "area": 132.64981322108713, + "bbox": [ + -4.8369313569596954, + -372.98736580465527, + -11.17619194365762, + -0.9998724708504492, + -369.5975783806762, + -1.0008285568041597 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220366 + }, + "tread_prime[3]": { + "area": 132.64981322108716, + "bbox": [ + 0.9998724708504492, + -372.98736580465527, + -11.17619194365762, + 4.8369313569596954, + -369.5975783806762, + -1.0008285568041597 + ], + "edges": 12, + "faces": 6, + "volume": 88.53291874220363 + }, + "tread_prime[4]": { + "area": 122.34106617585738, + "bbox": [ + 6.467020535967484, + -372.40635386566066, + -14.056134514839114, + 10.306420817495235, + -368.5052816264254, + -4.165090249157016 + ], + "edges": 12, + "faces": 6, + "volume": 77.81369200341184 + }, + "tread_prime[5]": { + "area": 86.71106461415305, + "bbox": [ + 11.250131996589724, + -370.74086851826763, + -16.03342656621189, + 14.109072137890605, + -367.0454599525497, + -7.007214936981318 + ], + "edges": 12, + "faces": 6, + "volume": 40.27561168172702 + } + }, + "status": "ok" + }, + "examples/boxes_on_faces": { + "shapes": { + "bp": { + "area": 57.60000000000005, + "bbox": [ + -1.6, + -1.6, + -1.6, + 1.6, + 1.6, + 1.6 + ], + "edges": 84, + "faces": 36, + "volume": 28.20000000000004 + } + }, + "status": "ok" + }, + "examples/boxes_on_faces_algebra": { + "shapes": { + "b": { + "area": 57.60000000000005, + "bbox": [ + -1.6, + -1.6, + -1.6, + 1.6, + 1.6, + 1.6 + ], + "edges": 84, + "faces": 36, + "volume": 28.20000000000004 + }, + "b2": { + "area": 4.6, + "bbox": [ + -1.0606601717798214, + -1.0606601717798212, + 0.0, + 1.0606601717798214, + 1.0606601717798212, + 0.1 + ], + "edges": 12, + "faces": 6, + "volume": 0.19999999999999996 + } + }, + "status": "ok" + }, + "examples/bracelet": { + "shapes": { + "alignment_holes[0]": { + "area": 58.12339108222815, + "bbox": [ + -40.93852111761477, + 12.82997635418359, + -4.0, + -38.88852111761477, + 14.87997635418359, + 4.0 + ], + "edges": 3, + "faces": 3, + "volume": 26.405086253422212 + }, + "alignment_holes[1]": { + "area": 58.12339108222815, + "bbox": [ + -29.950442435894256, + -24.006333293569345, + -4.0, + -27.90044243589426, + -21.956333293569347, + 4.0 + ], + "edges": 3, + "faces": 3, + "volume": 26.405086253422212 + }, + "alignment_holes[2]": { + "area": 58.12339108222815, + "bbox": [ + -1.0265487338260646, + 28.974999982232767, + -4.0, + 1.0234512661739352, + 31.024999982232764, + 4.0 + ], + "edges": 3, + "faces": 3, + "volume": 26.405086253422212 + }, + "alignment_holes[3]": { + "area": 58.12339108222815, + "bbox": [ + 27.900442435894266, + -24.00633329356934, + -4.0, + 29.950442435894264, + -21.956333293569344, + 4.0 + ], + "edges": 3, + "faces": 3, + "volume": 26.405086253422212 + }, + "alignment_holes[4]": { + "area": 58.12339108222815, + "bbox": [ + 38.88650451888775, + 12.83255801862175, + -4.0, + 40.936504518887745, + 14.88255801862175, + 4.0 + ], + "edges": 3, + "faces": 3, + "volume": 26.405086253422212 + }, + "bracelet": { + "area": 10712.646008816786, + "bbox": [ + -47.50000680684389, + -28.42907241778009, + -12.500000100009856, + 47.50000680684386, + 32.500000100003525, + 12.500000100009858 + ], + "edges": 21, + "faces": 19, + "volume": 18972.11597109143 + }, + "center_arc": { + "area": 0.0, + "bbox": [ + -45.0, + -22.981333293569346, + 0.0, + 45.0, + 30.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "center_section": { + "area": 9536.429150791675, + "bbox": [ + -47.50000680684389, + -25.163156513303015, + -12.500000100009856, + 47.50000680684386, + 32.500000100003525, + 12.500000100009858 + ], + "edges": 6, + "faces": 4, + "volume": 17457.567609126447 + }, + "center_surface": { + "area": 8891.060728470897, + "bbox": [ + -45.0, + -30.0, + -25.0, + 45.0, + 22.981333293569346, + 25.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "half_x_section": { + "area": 0.0, + "bbox": [ + -30.145953752600928, + -25.163156413299944, + -12.5, + -27.704931119187588, + -20.799510173838748, + 7.654042494670958e-16 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "planar_tip_arc": { + "area": 0.0, + "bbox": [ + -28.925442435894265, + -29.083889877102706, + -12.5, + -18.016326837241287, + -22.981333293569346, + 12.5 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "profile": { + "area": 0.0, + "bbox": [ + -30.14595385260093, + -28.42907241997514, + -1.0000000229621274e-07, + -17.203436804458377, + -20.799510073838743, + 1.0000000077666542e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "tip": { + "area": 639.1494972376158, + "bbox": [ + -30.14595385260263, + -28.42907241778009, + -12.500000100009812, + -17.203437266176635, + -20.799510073835027, + 12.500000100009812 + ], + "edges": 2, + "faces": 2, + "volume": 823.2871234965413 + }, + "tip_arc": { + "area": 0.0, + "bbox": [ + -28.925442535894273, + -27.709063193574917, + -12.5000001, + -17.247248972580756, + -22.981333193569334, + 12.5000001 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "tip_side": { + "area": 98.17477296531575, + "bbox": [ + -30.14595385260263, + -25.163156513303008, + -12.500000100009812, + -27.704931019185505, + -20.799510073835027, + 12.500000100009812 + ], + "edges": 2, + "faces": 1, + "volume": 0.0 + }, + "tip_surface": { + "area": 540.9747242723, + "bbox": [ + -30.145953852600933, + -28.42907241778009, + -12.500000100009807, + -17.203437266176635, + -20.79951007383872, + 12.500000100009805 + ], + "edges": 2, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/build123d_customizable_logo": { + "shapes": { + "arrow_left": { + "area": 0.0, + "bbox": [ + 0.0, + -0.75, + 0.0, + 1.0000000000000002, + 0.75, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "build": { + "area": 29.471797050365033, + "bbox": [ + 0.3117625375520839, + -7.0904029320312505, + -1e-07, + 18.190272177656247, + -0.752001888125, + 1e-07 + ], + "edges": 138, + "faces": 19, + "volume": 0.0 + }, + "build_bb": { + "area": 36.47203622048563, + "bbox": [ + -4.8499919619791685, + -1.8800049828124998, + 0.0, + 4.849991961979168, + 1.8800049828125, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "build_text": { + "area": 12.830895495161258, + "bbox": [ + -4.849991961979168, + -1.8800049828124998, + -1e-07, + 4.849991961979167, + 1.8800049828125, + 1e-07 + ], + "edges": 36, + "faces": 6, + "volume": 0.0 + }, + "cmpd": { + "area": 259.90594956677614, + "bbox": [ + 0.0, + -7.0904029320312505, + -1e-07, + 18.502034815208336, + 7.52001973125, + 2.2560059893749997 + ], + "edges": 208, + "faces": 40, + "volume": 64.41227299746792 + }, + "cust_text": { + "area": 16.640901555203776, + "bbox": [ + -8.939254820052081, + -1.09040293203125, + -1e-07, + 8.939254820052081, + 1.0904029320312498, + 1e-07 + ], + "edges": 102, + "faces": 13, + "volume": 0.0 + }, + "extension_lines": { + "area": 0.0, + "bbox": [ + 0.0, + -4.51201177875, + 0.0, + 18.50203471520833, + -0.752001963125, + 0.0 + ], + "edges": 10, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + -4.51201177875, + 0.0, + 0.0, + -0.752001963125, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 18.50203471520833, + -4.51201177875, + 0.0, + 18.50203471520833, + -0.752001963125, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "logo_text": { + "area": 49.909254882945575, + "bbox": [ + -2.1620593270422453e-15, + -2.746801851794792e-16, + -1e-07, + 20.6500002, + 7.52001973125, + 1e-07 + ], + "edges": 33, + "faces": 4, + "volume": 0.0 + }, + "one": { + "area": 0.0, + "bbox": [ + 1.949852461287869e-16, + 0.0, + 0.0, + 2.256005889375, + 7.520019631249999, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "t1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 1.0000000000000002, + 0.75, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "three_d": { + "area": 216.46423313018914, + "bbox": [ + 8.272021594375, + -1.6365788271696354e-16, + -1e-07, + 18.502034815208336, + 7.52001973125, + 2.2560059893749997 + ], + "edges": 48, + "faces": 20, + "volume": 64.41227299746792 + }, + "two": { + "area": 13.969919386221946, + "bbox": [ + 2.632006870937499, + -1.27388090734212e-15, + -1e-07, + 7.4019940501041654, + 7.0900067104166675, + 1e-07 + ], + "edges": 10, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/build123d_customizable_logo_algebra": { + "shapes": { + "arrow_left": { + "area": 0.0, + "bbox": [ + 0.0, + -0.75, + 0.0, + 1.0000000000000002, + 0.75, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "build": { + "area": 29.471797050365033, + "bbox": [ + 0.3117625375520842, + -7.00241471078125, + -1e-07, + 18.190272177656247, + -0.752001888125, + 1e-07 + ], + "edges": 138, + "faces": 19, + "volume": 0.0 + }, + "build_text": { + "area": 12.838607571080878, + "bbox": [ + -4.849991961979168, + -1.8800049828124998, + -1e-07, + 4.849991961979167, + 1.8800049828125, + 1e-07 + ], + "edges": 67, + "faces": 6, + "volume": 0.0 + }, + "cmpd": { + "area": 260.0588594205412, + "bbox": [ + 0.0, + -7.00241471078125, + -1e-07, + 18.502034815208336, + 7.52001973125, + 2.2560059893749997 + ], + "edges": 303, + "faces": 69, + "volume": 64.39081358526559 + }, + "cust_text": { + "area": 16.64303109109882, + "bbox": [ + -8.939254820052081, + -1.09040293203125, + -1e-07, + 8.939254820052081, + 1.0904029320312498, + 1e-07 + ], + "edges": 219, + "faces": 13, + "volume": 0.0 + }, + "extension_lines": { + "area": 0.0, + "bbox": [ + 0.0, + -4.51201177875, + 0.0, + 18.50203471520833, + -0.752001963125, + 0.0 + ], + "edges": 10, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + -4.51201177875, + 0.0, + 0.0, + -0.752001963125, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 18.50203471520833, + -4.51201177875, + 0.0, + 18.50203471520833, + -0.752001963125, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "logo_text": { + "area": 49.92024866492662, + "bbox": [ + -1.0518363024170888e-15, + -2.1916903394822137e-16, + -1e-07, + 20.6500002, + 7.52001973125, + 1e-07 + ], + "edges": 71, + "faces": 4, + "volume": 0.0 + }, + "one": { + "area": 0.0, + "bbox": [ + 1.949852461287869e-16, + 0.0, + 0.0, + 2.256005889375, + 7.520019631249999, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "t1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 1.0000000000000002, + 0.75, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "three_d": { + "area": 216.59938464157614, + "bbox": [ + 8.272021594375, + -2.1916903394822137e-16, + -1e-07, + 18.502034815208336, + 7.52001973125, + 2.2560059893749997 + ], + "edges": 135, + "faces": 49, + "volume": 64.39081358526559 + }, + "two": { + "area": 13.987677728599975, + "bbox": [ + 2.632006870937499, + -1.6365788271696354e-16, + -1e-07, + 7.4019940501041654, + 7.0900067104166675, + 1e-07 + ], + "edges": 18, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/build123d_logo": { + "shapes": { + "arrow_left": { + "area": 0.0, + "bbox": [ + 0.0, + -0.75, + 0.0, + 1.0000000000000002, + 0.75, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "build": { + "area": 12.830895495161256, + "bbox": [ + 4.401025395624998, + -4.51201185375, + -1e-07, + 14.101009319583332, + -0.752001888125, + 1e-07 + ], + "edges": 36, + "faces": 6, + "volume": 0.0 + }, + "build_bb": { + "area": 36.47203622048563, + "bbox": [ + -4.8499919619791685, + -1.8800049828124998, + 0.0, + 4.849991961979168, + 1.8800049828125, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "build_text": { + "area": 12.830895495161258, + "bbox": [ + -4.849991961979168, + -1.8800049828124998, + -1e-07, + 4.849991961979167, + 1.8800049828125, + 1e-07 + ], + "edges": 36, + "faces": 6, + "volume": 0.0 + }, + "extension_lines": { + "area": 0.0, + "bbox": [ + 0.0, + -4.51201177875, + 0.0, + 18.50203471520833, + -0.752001963125, + 0.0 + ], + "edges": 10, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + -4.51201177875, + 0.0, + 0.0, + -0.752001963125, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 18.50203471520833, + -4.51201177875, + 0.0, + 18.50203471520833, + -0.752001963125, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "logo": { + "area": 243.26504801157236, + "bbox": [ + 0.0, + -4.51201185375, + -1e-07, + 18.502034815208336, + 7.52001973125, + 2.2560059893749997 + ], + "edges": 106, + "faces": 27, + "volume": 64.41227299746792 + }, + "logo_text": { + "area": 49.909254882945575, + "bbox": [ + -2.1620593270422453e-15, + -2.746801851794792e-16, + -1e-07, + 20.6500002, + 7.52001973125, + 1e-07 + ], + "edges": 33, + "faces": 4, + "volume": 0.0 + }, + "one": { + "area": 0.0, + "bbox": [ + 1.949852461287869e-16, + 0.0, + 0.0, + 2.256005889375, + 7.520019631249999, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "t1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 1.0000000000000002, + 0.75, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "three_d": { + "area": 216.46423313018914, + "bbox": [ + 8.272021594375, + -1.6365788271696354e-16, + -1e-07, + 18.502034815208336, + 7.52001973125, + 2.2560059893749997 + ], + "edges": 48, + "faces": 20, + "volume": 64.41227299746792 + }, + "two": { + "area": 13.969919386221946, + "bbox": [ + 2.632006870937499, + -1.27388090734212e-15, + -1e-07, + 7.4019940501041654, + 7.0900067104166675, + 1e-07 + ], + "edges": 10, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/build123d_logo_algebra": { + "shapes": { + "arrow_left": { + "area": 0.0, + "bbox": [ + 0.0, + -0.75, + 0.0, + 1.0000000000000002, + 0.75, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "build": { + "area": 12.838607571080878, + "bbox": [ + 4.401025395624998, + -4.51201185375, + -1e-07, + 14.101009319583332, + -0.752001888125, + 1e-07 + ], + "edges": 67, + "faces": 6, + "volume": 0.0 + }, + "build_text": { + "area": 12.838607571080878, + "bbox": [ + -4.849991961979168, + -1.8800049828124998, + -1e-07, + 4.849991961979167, + 1.8800049828125, + 1e-07 + ], + "edges": 67, + "faces": 6, + "volume": 0.0 + }, + "cmpd": { + "area": 243.42566994125704, + "bbox": [ + 0.0, + -4.51201185375, + -1e-07, + 18.502034815208336, + 7.52001973125, + 2.2560059893749997 + ], + "edges": 232, + "faces": 56, + "volume": 64.39081358526559 + }, + "extension_lines": { + "area": 0.0, + "bbox": [ + 0.0, + -4.51201177875, + 0.0, + 18.50203471520833, + -0.752001963125, + 0.0 + ], + "edges": 10, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + -4.51201177875, + 0.0, + 0.0, + -0.752001963125, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 18.50203471520833, + -4.51201177875, + 0.0, + 18.50203471520833, + -0.752001963125, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "logo_text": { + "area": 49.92024866492662, + "bbox": [ + -1.0518363024170888e-15, + -2.1916903394822137e-16, + -1e-07, + 20.6500002, + 7.52001973125, + 1e-07 + ], + "edges": 71, + "faces": 4, + "volume": 0.0 + }, + "one": { + "area": 0.0, + "bbox": [ + 1.949852461287869e-16, + 0.0, + 0.0, + 2.256005889375, + 7.520019631249999, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "t1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 1.0000000000000002, + 0.75, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "three_d": { + "area": 216.59938464157614, + "bbox": [ + 8.272021594375, + -2.1916903394822137e-16, + -1e-07, + 18.502034815208336, + 7.52001973125, + 2.2560059893749997 + ], + "edges": 135, + "faces": 49, + "volume": 64.39081358526559 + }, + "two": { + "area": 13.987677728599975, + "bbox": [ + 2.632006870937499, + -1.6365788271696354e-16, + -1e-07, + 7.4019940501041654, + 7.0900067104166675, + 1e-07 + ], + "edges": 18, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/canadian_flag": { + "shapes": { + "canadian_flag": { + "area": 5208.439087165348, + "bbox": [ + -1e-07, + -1e-07, + -4.699608638596608, + 100.0000001, + 50.00000010000018, + 6.243966828598882 + ], + "edges": 95, + "faces": 4, + "volume": 0.0 + }, + "center_field": { + "area": 1874.9565394083024, + "bbox": [ + 24.999999899999963, + -1e-07, + 0.48039636802062596, + 75.0000001, + 50.00000010000005, + 6.224217892824805 + ], + "edges": 45, + "faces": 1, + "volume": 0.0 + }, + "center_field_builder": { + "area": 1814.5998286779718, + "bbox": [ + -25.0000001, + -1e-07, + -1e-07, + 25.0000001, + 50.0000001, + 1e-07 + ], + "edges": 41, + "faces": 1, + "volume": 0.0 + }, + "center_field_planar": { + "area": 1814.5998286779718, + "bbox": [ + 24.9999999, + -1e-07, + 9.9999999, + 75.0000001, + 50.0000001, + 10.0000001 + ], + "edges": 41, + "faces": 1, + "volume": 0.0 + }, + "east_field": { + "area": 1368.6486110025678, + "bbox": [ + 74.9999999, + -1e-07, + -4.699608638596608, + 100.0000001, + 50.000000100000044, + 5.939367188682708 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "east_field_planar": { + "area": 1250.0, + "bbox": [ + 75.0, + 0.0, + 10.0, + 100.0, + 50.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0771, + 0.0, + 0.0187, + 0.2569, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 0.0325, + 0.2458, + 0.0, + 0.2115, + 0.3125, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 0.1915, + 0.3277, + 0.0, + 0.3875, + 0.5071, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 0.2621, + 0.5235, + 0.0, + 0.375, + 0.6427, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l5": { + "area": 0.0, + "bbox": [ + 0.1369, + 0.5835, + 0.0, + 0.2469, + 0.6781, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l6": { + "area": 0.0, + "bbox": [ + 0.0881, + 0.5954, + 0.0, + 0.1562, + 0.8146, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l7": { + "area": 0.0, + "bbox": [ + 0.0, + 0.7808, + 0.0, + 0.0692, + 0.9167, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "maple_leaf": { + "area": 707.6909826528384, + "bbox": [ + 30.6249999, + 3.8549999, + 1.4673530901044824, + 69.37500010000001, + 45.83499887345733, + 6.243966828598882 + ], + "edges": 42, + "faces": 1, + "volume": 0.0 + }, + "maple_leaf_planar": { + "area": 685.4001713220289, + "bbox": [ + 30.62499989999999, + 3.8549999, + 9.9999999, + 69.3750001, + 45.835000099999995, + 10.0000001 + ], + "edges": 38, + "faces": 1, + "volume": 0.0 + }, + "outline": { + "area": 0.0, + "bbox": [ + -19.375, + 3.855, + -1e-07, + 19.375, + 45.834999999999994, + 1e-07 + ], + "edges": 38, + "faces": 0, + "volume": 0.0 + }, + "the_wind": { + "area": 6889.33825264371, + "bbox": [ + -5.000000100000006, + -5.000000100000005, + -6.073785244149412, + 105.00000010000004, + 55.00000010000005, + 6.24440441934073 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "west_field": { + "area": 1257.142954101639, + "bbox": [ + -1e-07, + -1e-07, + -0.13937941960498412, + 25.0000001, + 50.00000010000018, + 2.6755890038105794 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "west_field_builder": { + "area": 1250.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 25.0, + 50.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "west_field_planar": { + "area": 1250.0, + "bbox": [ + 0.0, + 0.0, + 10.0, + 25.0, + 50.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/canadian_flag_algebra": { + "shapes": { + "canadian_flag": { + "area": 5208.439033683858, + "bbox": [ + -1e-07, + -1e-07, + -4.699608638596608, + 100.0000001, + 50.00000010000018, + 6.243966828598882 + ], + "edges": 94, + "faces": 4, + "volume": 0.0 + }, + "center_field": { + "area": 1874.9564859268125, + "bbox": [ + 24.999999899999967, + -1e-07, + 0.48039636802062596, + 75.0000001, + 50.000000100000044, + 6.224217892824805 + ], + "edges": 45, + "faces": 1, + "volume": 0.0 + }, + "center_field_planar": { + "area": 1814.5998286779723, + "bbox": [ + 24.9999999, + -1e-07, + 9.9999999, + 75.0000001, + 50.0000001, + 10.0000001 + ], + "edges": 41, + "faces": 1, + "volume": 0.0 + }, + "east_field": { + "area": 1368.6486110025678, + "bbox": [ + 74.9999999, + -1e-07, + -4.699608638596608, + 100.0000001, + 50.000000100000044, + 5.939367188682708 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "east_field_planar": { + "area": 1250.0, + "bbox": [ + 75.0, + 0.0, + 10.0, + 100.0, + 50.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "field_planar": { + "area": 1250.0, + "bbox": [ + 0.0, + 0.0, + 10.0, + 25.0, + 50.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0771, + 0.0, + 0.0187, + 0.2569, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 0.0325, + 0.2458, + 0.0, + 0.2115, + 0.3125, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 0.1915, + 0.3277, + 0.0, + 0.3875, + 0.5071, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 0.2621, + 0.5235, + 0.0, + 0.375, + 0.6427, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l5": { + "area": 0.0, + "bbox": [ + 0.1369, + 0.5835, + 0.0, + 0.2469, + 0.6781, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l6": { + "area": 0.0, + "bbox": [ + 0.0881, + 0.5954, + 0.0, + 0.1562, + 0.8146, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l7": { + "area": 0.0, + "bbox": [ + 0.0, + 0.7808, + 0.0, + 0.0692, + 0.9167, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "maple_leaf": { + "area": 707.6909826528384, + "bbox": [ + 30.6249999, + 3.8549999, + 1.4673530901044824, + 69.37500010000001, + 45.83499887345733, + 6.243966828598882 + ], + "edges": 41, + "faces": 1, + "volume": 0.0 + }, + "maple_leaf_planar": { + "area": 685.4001713220289, + "bbox": [ + 30.62499989999999, + 3.8549999, + 9.9999999, + 69.3750001, + 45.835000099999995, + 10.0000001 + ], + "edges": 37, + "faces": 1, + "volume": 0.0 + }, + "outline": { + "area": 0.0, + "bbox": [ + -0.38750000000000007, + 0.0771, + -1e-07, + 0.3875, + 0.9167, + 1e-07 + ], + "edges": 37, + "faces": 0, + "volume": 0.0 + }, + "r1": { + "area": 0.0, + "bbox": [ + 0.009399999999999974, + 0.2569, + 0.0, + 0.0325, + 0.2773, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "r2": { + "area": 0.0, + "bbox": [ + 0.1864836247564839, + 0.3125, + 0.0, + 0.1915, + 0.3277, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "r3": { + "area": 0.0, + "bbox": [ + 0.33577817867175824, + 0.5071, + 0.0, + 0.3433, + 0.5235, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "r4": { + "area": 0.0, + "bbox": [ + 0.24689999999999998, + 0.6186630156729, + 0.0, + 0.2621, + 0.6267, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "r5": { + "area": 0.0, + "bbox": [ + 0.06919999999999998, + 0.7733751402565928, + 0.0, + 0.0881, + 0.7808, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "s": { + "area": 0.0, + "bbox": [ + 0.11332820126959792, + 0.5771646513943651, + -1e-07, + 0.1369001, + 0.5954001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "the_wind": { + "area": 6889.33825264371, + "bbox": [ + -5.000000100000006, + -5.000000100000005, + -6.073785244149412, + 105.00000010000004, + 55.00000010000005, + 6.24440441934073 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "west_field": { + "area": 1257.142954101639, + "bbox": [ + -1e-07, + -1e-07, + -0.13937941960498412, + 25.0000001, + 50.00000010000018, + 2.6755890038105794 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "west_field_planar": { + "area": 1250.0, + "bbox": [ + 0.0, + 0.0, + 10.0, + 25.0, + 50.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/cast_bearing_unit": { + "shapes": { + "drafted_faces[0]": { + "area": 265.3714050163985, + "bbox": [ + -49.25, + -9.786139554237103, + 0.0, + -43.27309392953986, + 9.786139554237913, + 11.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "drafted_faces[1]": { + "area": 373.9384228347135, + "bbox": [ + -43.27309392954144, + -25.339137948504423, + 0.0, + -13.045232386840148, + -9.786139554237101, + 11.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "drafted_faces[2]": { + "area": 373.9384228346939, + "bbox": [ + -43.27309392953986, + 9.786139554237913, + 0.0, + -13.045232386840153, + 25.339137948504423, + 11.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "drafted_faces[3]": { + "area": 3282.2603201329794, + "bbox": [ + -28.5, + -28.5, + 0.0, + 28.5, + 28.5, + 26.0 + ], + "edges": 11, + "faces": 1, + "volume": 0.0 + }, + "drafted_faces[4]": { + "area": 373.93842283466034, + "bbox": [ + 13.045232386840139, + -25.339137948504426, + 0.0, + 43.27309392953713, + -9.786139554239316, + 11.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "drafted_faces[5]": { + "area": 373.9384228347802, + "bbox": [ + 13.045232386840127, + 9.786139554234339, + 0.0, + 43.27309392954682, + 25.339137948504433, + 11.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "drafted_faces[6]": { + "area": 265.3714050163659, + "bbox": [ + 43.27309392953713, + -9.786139554239316, + 0.0, + 49.25, + 9.786139554234342, + 11.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "housing": { + "area": 2551.7586328783095, + "bbox": [ + -28.5, + -28.5, + 0.0, + 28.5, + 28.5, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "oval_flanged_bearing_unit": { + "area": 14647.574729140939, + "bbox": [ + -49.177631389975325, + -28.427631389975318, + -4.440892098500626e-16, + 49.17763138997532, + 28.427631389975318, + 26.0 + ], + "edges": 85, + "faces": 37, + "volume": 46882.6848405294 + }, + "plan": { + "area": 3724.040132749337, + "bbox": [ + -49.25, + -28.5, + 0.0, + 49.25, + 28.5, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/circuit_board": { + "shapes": { + "pcb": { + "area": 5173.203492338984, + "bbox": [ + -35.0, + -15.0, + 0.0, + 35.0, + 15.000000000000018, + 3.0 + ], + "edges": 285, + "faces": 97, + "volume": 5767.5000452165295 + } + }, + "status": "ok" + }, + "examples/circuit_board_algebra": { + "shapes": { + "pcb": { + "area": 5173.203492338984, + "bbox": [ + -35.0, + -15.0, + 0.0, + 35.0, + 15.000000000000018, + 3.0 + ], + "edges": 285, + "faces": 97, + "volume": 5767.5000452165295 + } + }, + "status": "ok" + }, + "examples/clock": { + "shapes": { + "clock_face": { + "area": 283.1444639522574, + "bbox": [ + -10.0000001, + -10.0000001, + -1e-07, + 10.0000001, + 10.0000001, + 1e-07 + ], + "edges": 635, + "faces": 7, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 9.709098043852952, + 0.1276235568206083, + 0.0, + 9.749164693846568, + 0.8921407819681733, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 9.211195580065622, + 0.12107875903493608, + 0.0, + 9.249207530059564, + 0.846389972636472, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "minute_indicator": { + "area": 0.3644814849538257, + "bbox": [ + 9.219393703646357, + 0.12237215025528633, + 0.0, + 9.747346197310556, + 0.8833626200981024, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "outline": { + "area": 0.0, + "bbox": [ + 9.211195580065622, + 0.12107875903493608, + 0.0, + 9.749164693846568, + 0.8921407819681733, + 0.0 + ], + "edges": 4, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/clock_algebra": { + "shapes": { + "clock_face": { + "area": 283.14429874819405, + "bbox": [ + -10.0000001, + -10.0000001, + -1e-07, + 10.0000001, + 10.0000001, + 1e-07 + ], + "edges": 634, + "faces": 7, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 9.709098043852952, + 0.1276235568206083, + 0.0, + 9.749164693846568, + 0.8921407819681733, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 9.211195580065622, + 0.12107875903493608, + 0.0, + 9.249207530059564, + 0.846389972636472, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 9.249207530059564, + 0.12107875903493608, + 0.0, + 9.749164693846568, + 0.1276235568206083, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 9.211195580065622, + 0.846389972636472, + 0.0, + 9.709098043852952, + 0.8921407819681733, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "minute_indicator": { + "area": 0.3644814849538257, + "bbox": [ + 9.219393703646357, + 0.12237215025528633, + 0.0, + 9.747346197310556, + 0.8833626200981024, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/custom_sketch_objects": { + "shapes": { + "base_top": { + "area": 6911.982079412815, + "bbox": [ + -35.75000000000079, + -48.45, + 8.85, + 35.750000000000796, + 48.449999999999996, + 8.85 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "box": { + "area": 23664.619114821067, + "bbox": [ + -35.75000000000079, + -48.45, + 0.0, + 35.750000000000796, + 48.449999999999996, + 16.2 + ], + "edges": 72, + "faces": 28, + "volume": 21485.21909241953 + }, + "box_builder": { + "area": 23664.619114821067, + "bbox": [ + -35.75000000000079, + -48.45, + 0.0, + 35.750000000000796, + 48.449999999999996, + 16.2 + ], + "edges": 72, + "faces": 28, + "volume": 21485.21909241953 + }, + "box_plan": { + "area": 6911.982079412815, + "bbox": [ + -35.75000000000079, + -48.45, + 0.0, + 35.750000000000796, + 48.449999999999996, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "lid": { + "area": 18194.269405705156, + "bbox": [ + -35.75000010000079, + -48.450000100000004, + 8.85, + 35.7500001000008, + 48.4500001, + 17.7000001 + ], + "edges": 129, + "faces": 46, + "volume": 13597.407606122617 + }, + "lid_builder": { + "area": 18194.269405705156, + "bbox": [ + -35.75000010000079, + -48.450000100000004, + 0.0, + 35.7500001000008, + 48.4500001, + 8.850000099999999 + ], + "edges": 129, + "faces": 46, + "volume": 13597.4076061226 + }, + "pocket": { + "area": 6345.322532550661, + "bbox": [ + -34.00000000000079, + -46.7, + 0.0, + 34.000000000000796, + 46.699999999999996, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "suits": { + "area": 623.0320537573014, + "bbox": [ + -28.263412090917864, + -36.36000009540909, + -1e-07, + 27.85828006423877, + 36.360000095409085, + 1e-07 + ], + "edges": 27, + "faces": 4, + "volume": 0.0 + }, + "walls": { + "area": 6505.261764817069, + "bbox": [ + -34.50000000000079, + -47.2, + 0.0, + 34.500000000000796, + 47.199999999999996, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/custom_sketch_objects_algebra": { + "shapes": { + "base_top": { + "area": 6911.982079412815, + "bbox": [ + -35.75000000000079, + -48.45, + 8.85, + 35.750000000000796, + 48.449999999999996, + 8.85 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "box": { + "area": 23664.619114821067, + "bbox": [ + -35.75000000000079, + -48.45, + 0.0, + 35.750000000000796, + 48.449999999999996, + 16.2 + ], + "edges": 72, + "faces": 28, + "volume": 21485.21909241953 + }, + "box_plan": { + "area": 6911.982079412815, + "bbox": [ + -35.75000000000079, + -48.45, + 0.0, + 35.750000000000796, + 48.449999999999996, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "lid": { + "area": 18194.269405705156, + "bbox": [ + -35.75000010000079, + -48.450000100000004, + 8.85, + 35.7500001000008, + 48.4500001, + 17.7000001 + ], + "edges": 129, + "faces": 46, + "volume": 13597.407606122617 + }, + "lid_bottom": { + "area": 6345.322532550661, + "bbox": [ + -34.00000000000079, + -46.7, + 0.0, + 34.000000000000796, + 46.699999999999996, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "pocket": { + "area": 19077.859216208104, + "bbox": [ + -35.75000000000079, + -48.45, + 8.85, + 35.750000000000796, + 48.449999999999996, + 17.7 + ], + "edges": 48, + "faces": 19, + "volume": 14532.920788237936 + }, + "suites": { + "area": 623.0320537573014, + "bbox": [ + -28.263412091234628, + -36.36000009540909, + 17.699999899999998, + 27.85828006392201, + 36.360000095409085, + 17.7000001 + ], + "edges": 27, + "faces": 4, + "volume": 0.0 + }, + "top": { + "area": 5952.346685814369, + "bbox": [ + -32.75000000000079, + -45.45, + 1.5, + 32.750000000000796, + 45.449999999999996, + 1.5 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "walls": { + "area": 6505.261764817069, + "bbox": [ + -34.500000000317556, + -47.2, + 8.85, + 34.49999999968403, + 47.199999999999996, + 8.85 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/din_rail": { + "shapes": { + "din": { + "area": 45.768141582522894, + "bbox": [ + -17.5, + 0.0, + 0.0, + 17.5, + 7.5, + 0.0 + ], + "edges": 20, + "faces": 1, + "volume": 0.0 + }, + "rail": { + "area": 88463.30007296926, + "bbox": [ + -17.5, + -500.0, + 0.0, + 17.5, + 500.0, + 7.5 + ], + "edges": 528, + "faces": 178, + "volume": 42462.863691085535 + }, + "slots": { + "area": 3305.27751063892, + "bbox": [ + -3.100000000000001, + -482.5, + 0.0, + 3.100000000000001, + 482.5, + 0.0 + ], + "edges": 156, + "faces": 39, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/din_rail_algebra": { + "shapes": { + "din": { + "area": 45.768141582522894, + "bbox": [ + -17.5, + 0.0, + 0.0, + 17.5, + 7.5, + 0.0 + ], + "edges": 20, + "faces": 1, + "volume": 0.0 + }, + "rail": { + "area": 88463.30007296933, + "bbox": [ + -17.5, + -1000.0, + 0.0, + 17.5, + 0.0, + 7.5 + ], + "edges": 528, + "faces": 178, + "volume": 42462.86369108561 + }, + "slot_faces[0]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 17.5, + 3.100000000000001, + 7.5, + 32.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[10]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 267.5, + 3.100000000000001, + 7.5, + 282.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[11]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 292.5, + 3.100000000000001, + 7.5, + 307.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[12]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 317.5, + 3.100000000000001, + 7.5, + 332.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[13]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 342.5, + 3.100000000000001, + 7.5, + 357.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[14]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 367.5, + 3.100000000000001, + 7.5, + 382.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[15]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 392.5, + 3.100000000000001, + 7.5, + 407.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[16]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 417.5, + 3.100000000000001, + 7.5, + 432.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[17]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 442.5, + 3.100000000000001, + 7.5, + 457.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[18]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 467.5, + 3.100000000000001, + 7.5, + 482.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[19]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 492.5, + 3.100000000000001, + 7.5, + 507.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[1]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 42.5, + 3.100000000000001, + 7.5, + 57.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[20]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 517.5, + 3.100000000000001, + 7.5, + 532.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[21]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 542.5, + 3.100000000000001, + 7.5, + 557.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[22]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 567.5, + 3.100000000000001, + 7.5, + 582.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[23]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 592.5, + 3.100000000000001, + 7.5, + 607.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[24]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 617.5, + 3.100000000000001, + 7.5, + 632.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[25]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 642.5, + 3.100000000000001, + 7.5, + 657.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[26]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 667.5, + 3.100000000000001, + 7.5, + 682.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[27]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 692.5, + 3.100000000000001, + 7.5, + 707.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[28]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 717.5, + 3.100000000000001, + 7.5, + 732.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[29]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 742.5, + 3.100000000000001, + 7.5, + 757.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[2]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 67.5, + 3.100000000000001, + 7.5, + 82.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[30]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 767.5, + 3.100000000000001, + 7.5, + 782.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[31]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 792.5, + 3.100000000000001, + 7.5, + 807.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[32]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 817.5, + 3.100000000000001, + 7.5, + 832.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[33]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 842.5, + 3.100000000000001, + 7.5, + 857.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[34]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 867.5, + 3.100000000000001, + 7.5, + 882.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[35]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 892.5, + 3.100000000000001, + 7.5, + 907.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[36]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 917.5, + 3.100000000000001, + 7.5, + 932.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[37]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 942.5, + 3.100000000000001, + 7.5, + 957.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[38]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 967.5, + 3.100000000000001, + 7.5, + 982.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[3]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 92.5, + 3.100000000000001, + 7.5, + 107.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[4]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 117.5, + 3.100000000000001, + 7.5, + 132.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[5]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 142.5, + 3.100000000000001, + 7.5, + 157.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[6]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 167.5, + 3.100000000000001, + 7.5, + 182.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[7]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 192.5, + 3.100000000000001, + 7.5, + 207.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[8]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 217.5, + 3.100000000000001, + 7.5, + 232.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "slot_faces[9]": { + "area": 84.75070540099793, + "bbox": [ + -3.1000000000000014, + 7.5, + 242.5, + 3.100000000000001, + 7.5, + 257.5 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/dual_color_3mf": { + "shapes": { + "bl": { + "area": 0.0, + "bbox": [ + -0.5, + -0.28734788556634605, + 0.0, + 9.447213595499958, + 9.0, + 0.0 + ], + "edges": 6, + "faces": 0, + "volume": 0.0 + }, + "inset": { + "area": 356.17444482833366, + "bbox": [ + -9.0, + -9.0, + 0.0, + 9.0, + 9.0, + 1.0 + ], + "edges": 96, + "faces": 34, + "volume": 91.63423335418365 + }, + "inset_builder": { + "area": 356.17444482833366, + "bbox": [ + -9.0, + -9.0, + 0.0, + 9.0, + 9.0, + 1.0 + ], + "edges": 96, + "faces": 34, + "volume": 91.63423335418365 + }, + "inset_pattern": { + "area": 91.6342333541837, + "bbox": [ + -9.0, + -9.0, + 0.0, + 9.0, + 9.0, + 0.0 + ], + "edges": 32, + "faces": 1, + "volume": 0.0 + }, + "outset": { + "area": 869.6375114115988, + "bbox": [ + -10.0, + -10.0, + 0.0, + 10.0, + 10.0, + 1.0 + ], + "edges": 108, + "faces": 46, + "volume": 308.3657666458164 + }, + "outset_builder": { + "area": 869.6375114115988, + "bbox": [ + -10.0, + -10.0, + 0.0, + 10.0, + 10.0, + 1.0 + ], + "edges": 108, + "faces": 46, + "volume": 308.3657666458164 + } + }, + "status": "ok" + }, + "examples/extrude": { + "shapes": { + "both": { + "area": 447.4664797546875, + "bbox": [ + -3.5200033552083334, + -2.730013629459635, + -5.0000001, + 3.520003355208333, + 4.909993080957031, + 5.0000001 + ], + "edges": 10, + "faces": 6, + "volume": 182.54094425947818 + }, + "ex26": { + "area": 1637.0551793147051, + "bbox": [ + -1.5000001022277465, + -12.500000100000017, + -1e-07, + 1.5000001019810518, + 12.5000001, + 28.0000001 + ], + "edges": 12, + "faces": 6, + "volume": 2017.872302865583 + }, + "ex26_sk": { + "area": 28.274333882308138, + "bbox": [ + -3.0, + 22.0, + 0.0, + 3.0, + 28.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "ex26_sk2": { + "area": 75.0, + "bbox": [ + -1.5, + -12.5, + 0.0, + 1.5, + 12.5, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "ex26_target": { + "area": 1536.9893279280202, + "bbox": [ + -3.0000001, + -28.0, + 0.0, + 3.0, + 28.0, + 28.0000001 + ], + "edges": 3, + "faces": 3, + "volume": 2220.6609902451046 + }, + "ex27": { + "area": 9480.191004404856, + "bbox": [ + -3.0000001, + -28.0000001, + -60.0000001, + 3.0000001, + 28.0000001, + 28.0000001 + ], + "edges": 21, + "faces": 9, + "volume": 13889.535427359886 + }, + "extrusion27": { + "area": 8187.22362975772, + "bbox": [ + -1.5000001018576363, + -25.000000100000005, + -60.0000001, + 1.5000001015528512, + 25.0, + 22.401923788646684 + ], + "edges": 21, + "faces": 9, + "volume": 10928.653206002937 + }, + "multiple": { + "area": 932.5946069058361, + "bbox": [ + -6.0000001, + -6.0000001, + -6.0000001, + 6.0000001, + 6.0000001, + 6.0000001 + ], + "edges": 732, + "faces": 270, + "volume": 1037.8293576759434 + }, + "non_planar": { + "area": 289.43951023931925, + "bbox": [ + -5.0, + -5.000000000000003, + 0.0, + 5.0, + 5.0, + 3.3397459621556145 + ], + "edges": 12, + "faces": 6, + "volume": 199.99999999999983 + }, + "simple": { + "area": 242.00987487380706, + "bbox": [ + -3.5200033552083334, + -2.730013629459635, + -1e-07, + 3.520003355208333, + 4.909993080957031, + 5.0000001 + ], + "edges": 6, + "faces": 4, + "volume": 91.27047212973908 + } + }, + "status": "ok" + }, + "examples/extrude_algebra": { + "shapes": { + "both": { + "area": 448.8319148675164, + "bbox": [ + -3.5200033552083334, + -2.730013629459635, + -5.0000001, + 3.520003355208333, + 4.909993080957031, + 5.0000001 + ], + "edges": 80, + "faces": 34, + "volume": 182.79491940648458 + }, + "circle": { + "area": 28.274333882308138, + "bbox": [ + -3.0, + 22.0, + 0.0, + 3.0, + 28.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "circle2": { + "area": 28.274333882308138, + "bbox": [ + -3.0, + 0.0, + 22.0, + 3.0, + 0.0, + 28.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "ex26": { + "area": 1637.0551793147051, + "bbox": [ + -1.5000001022277465, + -12.500000100000017, + -1e-07, + 1.5000001019810518, + 12.5000001, + 28.0000001 + ], + "edges": 12, + "faces": 6, + "volume": 2017.872302865583 + }, + "ex26_target": { + "area": 1536.9893279280202, + "bbox": [ + -3.0000001, + -28.0, + 0.0, + 3.0, + 28.0, + 28.0000001 + ], + "edges": 3, + "faces": 3, + "volume": 2220.6609902451046 + }, + "ex27": { + "area": 2030.469547982488, + "bbox": [ + -3.0000001, + -28.0000001, + -24.2487114199643, + 3.0, + 28.0, + 28.0000001 + ], + "edges": 3, + "faces": 3, + "volume": 2960.8813203268073 + }, + "extrusion27": { + "area": 8187.22362975772, + "bbox": [ + -1.500000101857663, + -25.0000001, + -60.0000001, + 1.5000001015528286, + 25.0, + 22.401923788646684 + ], + "edges": 21, + "faces": 9, + "volume": 10928.653206002935 + }, + "faces[0]": { + "area": 1.5824519985159247, + "bbox": [ + -5.0000001, + -3.2500002525878906, + -3.5154981468749997, + -4.9999999, + -1.0509961463378905, + -1.484501853125 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[10]": { + "area": 1.5824519985159247, + "bbox": [ + -3.2500002525878906, + 4.9999999, + -3.5154981468749997, + -1.0509961463378905, + 5.0000001, + -1.484501853125 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[11]": { + "area": 1.5824519985159247, + "bbox": [ + -3.2500002525878906, + 4.9999999, + 1.484501853125, + -1.0509961463378905, + 5.0000001, + 3.5154981468749997 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[12]": { + "area": 1.5824519985159247, + "bbox": [ + 1.0509961463378905, + -5.0000001, + -3.5154981468749997, + 3.2500002525878906, + -4.9999999, + -1.484501853125 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[13]": { + "area": 1.5824519985159247, + "bbox": [ + 1.0509961463378905, + -5.0000001, + 1.484501853125, + 3.2500002525878906, + -4.9999999, + 3.5154981468749997 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[14]": { + "area": 1.5824519985159247, + "bbox": [ + 1.484501853125, + -3.9490038536621093, + -5.0000001, + 3.5154981468749997, + -1.7499997474121092, + -4.9999999 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[15]": { + "area": 1.5824519985159247, + "bbox": [ + 1.484501853125, + -3.2500002525878906, + 4.9999999, + 3.5154981468749997, + -1.0509961463378905, + 5.0000001 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[16]": { + "area": 1.5824519985159247, + "bbox": [ + 1.484501853125, + 1.0509961463378905, + -5.0000001, + 3.5154981468749997, + 3.2500002525878906, + -4.9999999 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[17]": { + "area": 1.5824519985159247, + "bbox": [ + 1.484501853125, + 1.7499997474121092, + 4.9999999, + 3.5154981468749997, + 3.9490038536621093, + 5.0000001 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[18]": { + "area": 1.5824519985159247, + "bbox": [ + 1.7499997474121092, + 4.9999999, + -3.5154981468749997, + 3.9490038536621093, + 5.0000001, + -1.484501853125 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[19]": { + "area": 1.5824519985159247, + "bbox": [ + 1.7499997474121092, + 4.9999999, + 1.484501853125, + 3.9490038536621093, + 5.0000001, + 3.5154981468749997 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[1]": { + "area": 1.5824519985159247, + "bbox": [ + -5.0000001, + -3.2500002525878906, + 1.484501853125, + -4.9999999, + -1.0509961463378905, + 3.5154981468749997 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[20]": { + "area": 1.5824519985159247, + "bbox": [ + 4.9999999, + -3.9490038536621093, + -3.5154981468749997, + 5.0000001, + -1.7499997474121092, + -1.484501853125 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[21]": { + "area": 1.5824519985159247, + "bbox": [ + 4.9999999, + -3.9490038536621093, + 1.484501853125, + 5.0000001, + -1.7499997474121092, + 3.5154981468749997 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[22]": { + "area": 1.5824519985159247, + "bbox": [ + 4.9999999, + 1.0509961463378905, + -3.5154981468749997, + 5.0000001, + 3.2500002525878906, + -1.484501853125 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[23]": { + "area": 1.5824519985159247, + "bbox": [ + 4.9999999, + 1.0509961463378905, + 1.484501853125, + 5.0000001, + 3.2500002525878906, + 3.5154981468749997 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[2]": { + "area": 1.5824519985159247, + "bbox": [ + -5.0000001, + 1.7499997474121092, + -3.5154981468749997, + -4.9999999, + 3.9490038536621093, + -1.484501853125 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[3]": { + "area": 1.5824519985159247, + "bbox": [ + -5.0000001, + 1.7499997474121092, + 1.484501853125, + -4.9999999, + 3.9490038536621093, + 3.5154981468749997 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[4]": { + "area": 1.5824519985159247, + "bbox": [ + -3.9490038536621093, + -5.0000001, + -3.5154981468749997, + -1.7499997474121092, + -4.9999999, + -1.484501853125 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[5]": { + "area": 1.5824519985159247, + "bbox": [ + -3.9490038536621093, + -5.0000001, + 1.484501853125, + -1.7499997474121092, + -4.9999999, + 3.5154981468749997 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[6]": { + "area": 1.5824519985159247, + "bbox": [ + -3.5154981468749997, + -3.9490038536621093, + -5.0000001, + -1.484501853125, + -1.7499997474121092, + -4.9999999 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[7]": { + "area": 1.5824519985159247, + "bbox": [ + -3.5154981468749997, + -3.2500002525878906, + 4.9999999, + -1.484501853125, + -1.0509961463378905, + 5.0000001 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[8]": { + "area": 1.5824519985159247, + "bbox": [ + -3.5154981468749997, + 1.0509961463378905, + -5.0000001, + -1.484501853125, + 3.2500002525878906, + -4.9999999 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "faces[9]": { + "area": 1.5824519985159247, + "bbox": [ + -3.5154981468749997, + 1.7499997474121092, + 4.9999999, + -1.484501853125, + 3.9490038536621093, + 5.0000001 + ], + "edges": 26, + "faces": 1, + "volume": 0.0 + }, + "multiple": { + "area": 931.8885215782758, + "bbox": [ + -6.0000001, + -6.0000001, + -6.0000001, + 6.0000001, + 6.0000001, + 6.0000001 + ], + "edges": 1884, + "faces": 654, + "volume": 1037.9788479643803 + }, + "non_planar": { + "area": 289.43951023931925, + "bbox": [ + -5.0, + -5.000000000000003, + 0.0, + 5.0, + 5.0, + 3.3397459621556145 + ], + "edges": 12, + "faces": 6, + "volume": 199.99999999999983 + }, + "rect": { + "area": 150.0, + "bbox": [ + -1.5, + -25.0, + -60.0, + 1.5, + 25.0, + -60.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "simple": { + "area": 242.6954493744067, + "bbox": [ + -3.5200033552083334, + -2.730013629459635, + -1e-07, + 3.520003355208333, + 4.909993080957031, + 5.0000001 + ], + "edges": 48, + "faces": 18, + "volume": 91.39745970324233 + } + }, + "status": "ok" + }, + "examples/fast_grid_holes": { + "shapes": { + "face_perimeter": { + "area": 0.0, + "bbox": [ + -250.0, + -300.0, + 0.0, + 250.0, + 300.0, + 0.0 + ], + "edges": 4, + "faces": 0, + "volume": 0.0 + }, + "grid": { + "area": 372894.78360043187, + "bbox": [ + -250.0, + -300.0, + 0.0, + 250.0, + 300.0, + 1.0 + ], + "edges": 11262, + "faces": 3756, + "volume": 168472.3918002237 + }, + "grid_pattern": { + "area": 168472.39180021593, + "bbox": [ + -250.0, + -300.0, + 0.0, + 250.0, + 300.0, + 0.0 + ], + "edges": 3754, + "faces": 1, + "volume": 0.0 + }, + "hex_hole": { + "area": 0.0, + "bbox": [ + -9.0, + -7.794228634059947, + 0.0, + 9.0, + 7.794228634059948, + 0.0 + ], + "edges": 6, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/handle": { + "shapes": { + "handle": { + "area": 211.5164306268522, + "bbox": [ + -11.000000097363479, + -1.5090618035519154, + -1.0000000139418649e-07, + 11.000000097363479, + 1.5090618035519123, + 5.64371925956298 + ], + "edges": 27, + "faces": 11, + "volume": 94.77347434513797 + }, + "handle_center_line": { + "area": 0.0, + "bbox": [ + -10.0000001, + -1e-07, + -1e-07, + 10.0000001, + 1e-07, + 5.017937046343326 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "section": { + "area": 3.141592653589792, + "bbox": [ + 9.0, + -1.0, + 0.0, + 11.0, + 1.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "sections[0]": { + "area": 3.141592653589792, + "bbox": [ + -11.0, + -1.0, + 0.0, + -9.0, + 1.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "sections[1]": { + "area": 3.7156637146343536, + "bbox": [ + -8.804299919129857, + -1.5, + 3.3506064896102243, + -8.053484166456128, + 1.5, + 4.3499941860953 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "sections[2]": { + "area": 3.7156637146343536, + "bbox": [ + -4.354416398514175, + -1.5, + 4.35488428457654, + -4.287080091966721, + 1.5, + 5.603069320606578 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "sections[3]": { + "area": 3.7156637146343536, + "bbox": [ + 0.0, + -1.5, + 4.374999975060495, + 0.0, + 1.5, + 5.625 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "sections[4]": { + "area": 3.7156637146343536, + "bbox": [ + 4.2870797817706645, + -1.5, + 4.354884301310801, + 4.354416069187994, + 1.5, + 5.60306933837286 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "sections[5]": { + "area": 3.7156637146343536, + "bbox": [ + 8.053484010444263, + -1.5, + 3.350606606818149, + 8.80429970109714, + 1.5, + 4.349994349897985 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "sections[6]": { + "area": 3.141592653589792, + "bbox": [ + 9.0, + -1.0, + 0.0, + 11.0, + 1.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/handle_algebra": { + "shapes": { + "circle": { + "area": 3.141592653589792, + "bbox": [ + 9.0, + -1.0, + -1.1102230246251562e-16, + 11.0, + 1.0, + 1.1102230246251562e-16 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "handle": { + "area": 211.51619811553383, + "bbox": [ + -11.00000009735155, + -1.5090618035519148, + -1.0745058059692382e-07, + 11.000000097755366, + 1.5090618035519179, + 5.643719267214584 + ], + "edges": 27, + "faces": 11, + "volume": 94.7736147223482 + }, + "handle_center_line": { + "area": 0.0, + "bbox": [ + -10.0000001, + -1e-07, + -1e-07, + 10.0000001, + 1e-07, + 5.017937046343326 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "sections": { + "area": 24.861503880351354, + "bbox": [ + -11.0, + -1.5000000000000002, + -1.1102230246251562e-16, + 11.0, + 1.5, + 5.625 + ], + "edges": 42, + "faces": 7, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/heat_exchanger": { + "shapes": { + "heat_exchanger": { + "area": 1255886.680540645, + "bbox": [ + -50.0, + -50.0, + -150.0, + 50.0, + 50.0, + 150.0 + ], + "edges": 2966, + "faces": 1486, + "volume": 363795.07369811094 + }, + "plate_plan": { + "area": 5994.158783049332, + "bbox": [ + -50.0, + -50.0, + 0.0, + 50.0, + 50.0, + 0.0 + ], + "edges": 149, + "faces": 1, + "volume": 0.0 + }, + "tube_plan": { + "area": 1046.150353645398, + "bbox": [ + -41.904155872191964, + -46.25, + 0.0, + 41.904155872191964, + 46.25, + 0.0 + ], + "edges": 296, + "faces": 148, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/heat_exchanger_algebra": { + "shapes": { + "heat_exchanger": { + "area": 1255886.6805406844, + "bbox": [ + -50.0, + -50.0, + -150.0, + 50.0, + 50.0, + 150.0 + ], + "edges": 2374, + "faces": 1190, + "volume": 363795.07369811274 + }, + "plate": { + "area": 5994.158783049332, + "bbox": [ + -50.0, + -50.0, + 0.0, + 50.0, + 50.0, + 0.0 + ], + "edges": 149, + "faces": 1, + "volume": 0.0 + }, + "ring": { + "area": 7.068583470577035, + "bbox": [ + -2.5, + -2.5, + 0.0, + 2.5, + 2.5, + 0.0 + ], + "edges": 2, + "faces": 1, + "volume": 0.0 + }, + "tube_plan": { + "area": 1046.150353645398, + "bbox": [ + -41.904155872191964, + -46.25, + 0.0, + 41.904155872191964, + 46.25, + 0.0 + ], + "edges": 296, + "faces": 148, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/holes": { + "shapes": { + "flush_counter_sink": { + "area": 98.97571303509702, + "bbox": [ + 7.0, + 7.0, + -1.0, + 13.0, + 13.0, + 1.0 + ], + "edges": 8, + "faces": 5, + "volume": 49.21140235079789 + }, + "recessed_counter_bore": { + "area": 105.2433538952581, + "bbox": [ + 7.0, + -3.0, + -1.0, + 13.0, + 3.0, + 1.0 + ], + "edges": 9, + "faces": 6, + "volume": 44.37499623195583 + }, + "recessed_counter_sink": { + "area": 102.11730568868683, + "bbox": [ + -3.0, + 7.0, + -1.0, + 3.0, + 13.0, + 1.0 + ], + "edges": 11, + "faces": 6, + "volume": 45.284411533810655 + }, + "thru_hole": { + "area": 100.5309649148734, + "bbox": [ + -3.0, + -3.0, + -1.0, + 3.0, + 3.0, + 1.0 + ], + "edges": 6, + "faces": 4, + "volume": 50.26548245743668 + } + }, + "status": "ok" + }, + "examples/holes_algebra": { + "shapes": { + "flush_counter_sink": { + "area": 98.97571303509702, + "bbox": [ + -3.0, + -3.0, + -1.0, + 3.0, + 3.0, + 1.0 + ], + "edges": 8, + "faces": 5, + "volume": 49.21140235079789 + }, + "recessed_counter_bore": { + "area": 105.2433538952581, + "bbox": [ + -3.0, + -3.0, + -1.0, + 3.0, + 3.0, + 1.0 + ], + "edges": 9, + "faces": 6, + "volume": 44.37499623195583 + }, + "recessed_counter_sink": { + "area": 102.11730568868683, + "bbox": [ + -3.0, + -3.0, + -1.0, + 3.0, + 3.0, + 1.0 + ], + "edges": 11, + "faces": 6, + "volume": 45.284411533810655 + }, + "thru_hole": { + "area": 100.5309649148734, + "bbox": [ + -3.0, + -3.0, + -1.0, + 3.0, + 3.0, + 1.0 + ], + "edges": 6, + "faces": 4, + "volume": 50.26548245743668 + } + }, + "status": "ok" + }, + "examples/intersecting_chamfers": { + "shapes": { + "blocks": { + "area": 24.349245156795856, + "bbox": [ + -1.5, + -1.0, + -5.551115123125783e-17, + 1.5, + 1.0000000000000002, + 2.0 + ], + "edges": 108, + "faces": 50, + "volume": 5.887333333333333 + } + }, + "status": "ok" + }, + "examples/intersecting_chamfers_algebra": { + "shapes": { + "blocks": { + "area": 26.0, + "bbox": [ + -1.5, + -1.0, + 0.0, + 1.5, + 1.0, + 2.0 + ], + "edges": 34, + "faces": 14, + "volume": 5.999999999999999 + }, + "blocks2": { + "area": 24.349245156795867, + "bbox": [ + -1.5, + -1.0, + -4.163336342344337e-17, + 1.5, + 1.0000000000000002, + 2.0 + ], + "edges": 110, + "faces": 50, + "volume": 5.887333333333332 + } + }, + "status": "ok" + }, + "examples/intersecting_pipes": { + "shapes": { + "box": { + "area": 599.9999999999999, + "bbox": [ + -8.128320675339982, + -7.650934991471806, + -7.245432423491767, + 8.128320675339983, + 7.650934991471806, + 7.245432423491767 + ], + "edges": 12, + "faces": 6, + "volume": 999.9999999999998 + }, + "pipe": { + "area": 13.351768777756618, + "bbox": [ + -4.5, + -4.5, + 0.0, + 4.5, + 4.5, + 0.0 + ], + "edges": 2, + "faces": 1, + "volume": 0.0 + }, + "pipes": { + "area": 3671.7930464859546, + "bbox": [ + -14.743143426754909, + -14.82445978237856, + -15.525656439806344, + 14.743143426754909, + 14.824459782378561, + 15.525656439806344 + ], + "edges": 114, + "faces": 48, + "volume": 1015.9390056815633 + } + }, + "status": "ok" + }, + "examples/joints": { + "shapes": { + "ball": { + "area": 12.766865020514242, + "bbox": [ + -2.5206118420362733, + -2.885983133715599, + 6.022072943407486, + -0.5070270544691564, + -0.8725117260799224, + 8.030278894202937 + ], + "edges": 52, + "faces": 26, + "volume": 3.8561787180391613 + }, + "base": { + "area": 567.2602982837606, + "bbox": [ + -4.773502691896258, + -7.008292387857169, + -1.8867513459481287, + 9.501683115695705, + 7.220084679281462, + 12.691010916410779 + ], + "edges": 18, + "faces": 8, + "volume": 801.7636001838378 + }, + "base_corner_edge": { + "area": 0.0, + "bbox": [ + 4.333333333333333, + 4.127953670731317, + 1.4465819873852044, + 7.3172814714463925, + 7.220084679281461, + 10.506609272161466 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "fixed_arm": { + "area": 19.692137780516926, + "bbox": [ + 6.908713247241016, + 0.7795575930802792, + 3.2051724239635417, + 11.889904378203358, + 3.3551304560922928, + 5.26019296613428 + ], + "edges": 60, + "faces": 30, + "volume": 4.680524109068732 + }, + "hinge_arm": { + "area": 56.470671085422296, + "bbox": [ + 4.333333333333332, + 3.548420768098773, + 0.7195644043411336, + 9.000081744166089, + 7.845073860201328, + 10.484579709449077 + ], + "edges": 18, + "faces": 8, + "volume": 14.973733683061244 + }, + "pin_arm": { + "area": 16.37444678594553, + "bbox": [ + 8.083533844394877, + -2.8600401948303653, + 8.151149073432403, + 10.148917985297086, + -0.2946560539281542, + 10.68384114388351 + ], + "edges": 18, + "faces": 8, + "volume": 3.803650459150637 + }, + "screw_arm": { + "area": 31.654516471323102, + "bbox": [ + 3.6800351009479453, + -14.685218175112015, + 0.3892244301109322, + 7.040023983660519, + -5.395568716160048, + 3.965281816045624 + ], + "edges": 52, + "faces": 26, + "volume": 7.633934357700818 + }, + "slider_arm": { + "area": 26.54833227070927, + "bbox": [ + -3.783362018523447, + -3.7261097605677627, + 11.63780065200003, + 0.5748424454063146, + -1.189272501683919, + 14.573355385119417 + ], + "edges": 60, + "faces": 30, + "volume": 7.385999419283467 + }, + "swing_arm_hinge_edge": { + "area": 0.0, + "bbox": [ + 0.8254493507178241, + -0.5, + 0.0, + 1.0, + -0.3254493507178241, + 10.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/joints_algebra": { + "shapes": { + "ball": { + "area": 12.766865020514242, + "bbox": [ + -2.5206118420362733, + -2.885983133715599, + 6.022072943407486, + -0.5070270544691564, + -0.8725117260799224, + 8.030278894202937 + ], + "edges": 52, + "faces": 26, + "volume": 3.8561787180391613 + }, + "base": { + "area": 567.2602982837606, + "bbox": [ + -4.773502691896258, + -7.008292387857169, + -1.8867513459481287, + 9.501683115695705, + 7.220084679281462, + 12.691010916410779 + ], + "edges": 18, + "faces": 8, + "volume": 801.7636001838378 + }, + "base_corner_edge": { + "area": 0.0, + "bbox": [ + 4.333333333333333, + 4.127953670731317, + 1.4465819873852044, + 7.3172814714463925, + 7.220084679281461, + 10.506609272161466 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "fixed_arm": { + "area": 19.692137780516926, + "bbox": [ + 6.908713247241016, + 0.7795575930802792, + 3.2051724239635417, + 11.889904378203358, + 3.3551304560922928, + 5.26019296613428 + ], + "edges": 60, + "faces": 30, + "volume": 4.680524109068732 + }, + "hinge_arm": { + "area": 56.470671085422296, + "bbox": [ + 4.333333333333332, + 3.548420768098773, + 0.7195644043411336, + 9.000081744166089, + 7.845073860201328, + 10.484579709449077 + ], + "edges": 18, + "faces": 8, + "volume": 14.973733683061244 + }, + "pin_arm": { + "area": 16.37444678594553, + "bbox": [ + 8.083533844394877, + -2.8600401948303653, + 8.151149073432403, + 10.148917985297086, + -0.2946560539281542, + 10.68384114388351 + ], + "edges": 18, + "faces": 8, + "volume": 3.803650459150637 + }, + "screw_arm": { + "area": 31.654516471323102, + "bbox": [ + 3.6800351009479453, + -14.685218175112015, + 0.3892244301109322, + 7.040023983660519, + -5.395568716160048, + 3.965281816045624 + ], + "edges": 52, + "faces": 26, + "volume": 7.633934357700818 + }, + "slider_arm": { + "area": 26.54833227070927, + "bbox": [ + -3.783362018523447, + -3.7261097605677627, + 11.63780065200003, + 0.5748424454063146, + -1.189272501683919, + 14.573355385119417 + ], + "edges": 60, + "faces": 30, + "volume": 7.385999419283467 + }, + "swing_arm_hinge_edge": { + "area": 0.0, + "bbox": [ + 0.8254493507178241, + -0.5, + 0.0, + 1.0, + -0.3254493507178241, + 10.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/key_cap": { + "shapes": { + "cruciform": { + "area": 15.533194442772814, + "bbox": [ + -2.75, + -2.75, + 0.0, + 2.75, + 2.75, + 0.0 + ], + "edges": 13, + "faces": 1, + "volume": 0.0 + }, + "key_cap": { + "area": 1497.8970768745387, + "bbox": [ + -9.000000100000001, + -9.000000100000001, + -1.0000000005551115e-07, + 9.000000100000001, + 9.000000100000001, + 8.441500309937386 + ], + "edges": 171, + "faces": 69, + "volume": 644.8900474026628 + }, + "key_cap_section": { + "area": 49.96808215448871, + "bbox": [ + -7.928203330275511, + -7.928203330275511, + 3.9999999, + 7.928203330275511, + 7.928203330275511, + 4.0000001 + ], + "edges": 9, + "faces": 1, + "volume": 0.0 + }, + "plan": { + "area": 324.0, + "bbox": [ + -9.0, + -9.0, + 0.0, + 9.0, + 9.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "rib_bottom": { + "area": 32.58314567374609, + "bbox": [ + -7.158633027064528, + -7.158633027064528, + 4.0, + 7.158633027064528, + 7.158633027064528, + 4.0 + ], + "edges": 16, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/key_cap_algebra": { + "shapes": { + "key_cap": { + "area": 1497.8832746392773, + "bbox": [ + -9.000000100000001, + -9.000000100000001, + -1.0000000005551115e-07, + 9.000000100000001, + 9.000000100000001, + 8.441500309937386 + ], + "edges": 171, + "faces": 70, + "volume": 645.0537078866295 + }, + "key_cap_section": { + "area": 49.96808215448871, + "bbox": [ + -7.928203330275511, + -7.928203330275511, + 3.9999999, + 7.928203330275511, + 7.928203330275511, + 4.0000001 + ], + "edges": 9, + "faces": 1, + "volume": 0.0 + }, + "plan": { + "area": 324.0, + "bbox": [ + -9.0, + -9.0, + 0.0, + 9.0, + 9.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "ribs": { + "area": 32.65960421113574, + "bbox": [ + -7.158633027064528, + -7.158633027064528, + 0.0, + 7.158633027064528, + 7.158633027064528, + 0.0 + ], + "edges": 16, + "faces": 1, + "volume": 0.0 + }, + "socket": { + "area": 15.533194442772814, + "bbox": [ + -2.75, + -2.75, + 0.0, + 2.75, + 2.75, + 0.0 + ], + "edges": 13, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/lego": { + "shapes": { + "lego": { + "area": 5656.720199717906, + "bbox": [ + -24.0, + -8.0, + 0.0, + 24.0, + 8.0, + 11.4 + ], + "edges": 282, + "faces": 119, + "volume": 3212.1873377813517 + }, + "perimeter": { + "area": 768.0, + "bbox": [ + -24.0, + -8.0, + 0.0, + 24.0, + 8.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "plan": { + "area": 226.1574935943251, + "bbox": [ + -24.0, + -8.0, + 0.0, + 24.0, + 8.0, + 0.0 + ], + "edges": 82, + "faces": 6, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/lego_algebra": { + "shapes": { + "lego": { + "area": 5656.720199717906, + "bbox": [ + -24.0, + -8.0, + 0.0, + 24.0, + 8.0, + 11.4 + ], + "edges": 282, + "faces": 119, + "volume": 3212.187337781355 + }, + "plan": { + "area": 226.1574935943251, + "bbox": [ + -24.0, + -8.0, + 0.0, + 24.0, + 8.0, + 0.0 + ], + "edges": 82, + "faces": 6, + "volume": 0.0 + }, + "ring": { + "area": 15.087498718864992, + "bbox": [ + -3.25, + -3.25, + 0.0, + 3.25, + 3.25, + 0.0 + ], + "edges": 2, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/loft": { + "shapes": { + "art": { + "area": 5285.0901899643095, + "bbox": [ + -15.500000098012299, + -15.500000077682515, + -1e-07, + 15.500000100000108, + 15.500000077682527, + 30.0000001 + ], + "edges": 6, + "faces": 4, + "volume": 1306.3405290344635 + }, + "slice": { + "area": 78.53981633974483, + "bbox": [ + -5.000000000000001, + -5.000000000000001, + 0.0, + 5.000000000000001, + 5.000000000000001, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "top_bottom[0]": { + "area": 78.53981634270136, + "bbox": [ + -5.000000099337427, + -5.000000092560834, + -1e-07, + 5.0000001000001095, + 5.00000009256084, + 1e-07 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "top_bottom[1]": { + "area": 78.53981634270122, + "bbox": [ + -5.000000099337423, + -5.000000092560845, + 29.9999999, + 5.000000100000043, + 5.000000092560842, + 30.0000001 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/loft_algebra": { + "shapes": { + "art": { + "area": 5285.0901899643095, + "bbox": [ + -15.500000098012299, + -15.500000077682515, + -1e-07, + 15.500000100000108, + 15.500000077682527, + 30.0000001 + ], + "edges": 6, + "faces": 4, + "volume": 1306.3405290344635 + }, + "top_bottom[0]": { + "area": 78.53981634270136, + "bbox": [ + -5.000000099337427, + -5.000000092560834, + -1e-07, + 5.0000001000001095, + 5.00000009256084, + 1e-07 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "top_bottom[1]": { + "area": 78.53981634270122, + "bbox": [ + -5.000000099337423, + -5.000000092560845, + 29.9999999, + 5.000000100000043, + 5.000000092560842, + 30.0000001 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/maker_coin": { + "shapes": { + "detents": { + "area": 1231.504320207199, + "bbox": [ + -34.5, + -34.5, + 0.0, + 34.5, + 34.5, + 0.0 + ], + "edges": 8, + "faces": 8, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 20.0, + 6.0, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 15.0, + 0.0, + 0.0, + 25.0, + 10.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 0.0, + 6.0, + 0.0, + 18.07692307692308, + 9.615384615384613, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "label": { + "area": 79.96928325034885, + "bbox": [ + -10.1699952171875, + -4.095020394189453, + -1e-07, + 10.1550049828125, + 7.3649895714355464, + 1e-07 + ], + "edges": 11, + "faces": 2, + "volume": 0.0 + }, + "maker_coin": { + "area": 4760.8136277540025, + "bbox": [ + -23.895454660994158, + -23.895454649195383, + -1.0000001082467451e-07, + 23.895454645821104, + 23.895454656202602, + 10.000004345711716 + ], + "edges": 160, + "faces": 68, + "volume": 13160.217918773385 + }, + "outline": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 25.0, + 10.0, + 0.0 + ], + "edges": 5, + "faces": 0, + "volume": 0.0 + }, + "profile": { + "area": 188.15800545773288, + "bbox": [ + 0.0, + 0.0, + 0.0, + 25.0, + 10.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/mixed_algebra_context": { + "shapes": { + "b": { + "area": 23.492215605537947, + "bbox": [ + -0.75, + -1.0, + -1.5, + 0.75, + 1.0, + 1.5 + ], + "edges": 24, + "faces": 12, + "volume": 6.967963150034936 + }, + "bl": { + "area": 0.0, + "bbox": [ + -1.0, + 0.0, + 0.0, + 2.0, + 4.0, + 0.0 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + }, + "bp": { + "area": 30.026728325004715, + "bbox": [ + -0.75, + -1.0, + -1.5, + 0.75, + 1.0, + 1.5 + ], + "edges": 27, + "faces": 13, + "volume": 5.459998676311835 + }, + "bs": { + "area": 1.8845304354395986, + "bbox": [ + -0.75, + -1.0, + 0.0, + 0.7500000000000002, + 1.0, + 0.0 + ], + "edges": 9, + "faces": 1, + "volume": 0.0 + }, + "c": { + "area": 30.424857133514315, + "bbox": [ + -0.7500001, + -1.0, + -1.5000001, + 0.7500001, + 1.0, + 1.5000001 + ], + "edges": 36, + "faces": 15, + "volume": 5.370479810783857 + }, + "d": { + "area": 1.8216985823678027, + "bbox": [ + -0.75, + -1.0, + 0.0, + 0.7500000000000002, + 1.0, + 0.0 + ], + "edges": 11, + "faces": 1, + "volume": 0.0 + }, + "e": { + "area": 0.0, + "bbox": [ + -1.5, + 0.0, + 0.0, + 2.0, + 4.0, + 0.0 + ], + "edges": 4, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -1.0, + 0.0, + 0.0, + 2.0, + 4.0, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "r": { + "area": 2.387185260013966, + "bbox": [ + -0.75, + -1.0, + 0.0, + 0.7500000000000002, + 1.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/multiple_workplanes": { + "shapes": { + "obj": { + "area": 60.95221315766136, + "bbox": [ + -2.5, + -2.5, + -0.5, + 2.5, + 2.5, + 0.5 + ], + "edges": 17, + "faces": 8, + "volume": 15.083039190168236 + } + }, + "status": "ok" + }, + "examples/multiple_workplanes_algebra": { + "shapes": { + "obj": { + "area": 60.95221315766136, + "bbox": [ + -2.5, + -2.5, + -0.5, + 2.5, + 2.5, + 0.5 + ], + "edges": 17, + "faces": 8, + "volume": 15.083039190168236 + } + }, + "status": "ok" + }, + "examples/packed_boxes": { + "shapes": { + "packed[0]": { + "area": 378.0, + "bbox": [ + 0.0, + 41.0, + -1.5, + 6.0, + 60.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 342.0 + }, + "packed[10]": { + "area": 753.9999999999999, + "bbox": [ + 22.0, + 0.0, + -0.5, + 42.0, + 17.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 340.00000000000006 + }, + "packed[11]": { + "area": 766.0, + "bbox": [ + 30.0, + 41.0, + -2.0, + 47.0, + 56.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 1020.0 + }, + "packed[12]": { + "area": 249.99999999999997, + "bbox": [ + 45.0, + 0.0, + -2.5, + 46.0, + 20.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 99.99999999999997 + }, + "packed[13]": { + "area": 291.99999999999994, + "bbox": [ + 40.0, + 63.0, + -2.5, + 54.0, + 67.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 280.0 + }, + "packed[14]": { + "area": 386.0, + "bbox": [ + 49.0, + 22.0, + -2.0, + 56.0, + 37.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 420.0 + }, + "packed[15]": { + "area": 136.0, + "bbox": [ + 50.0, + 41.0, + -1.5, + 58.0, + 45.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 96.0 + }, + "packed[16]": { + "area": 72.0, + "bbox": [ + 50.0, + 48.0, + -1.0, + 58.0, + 50.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 31.999999999999993 + }, + "packed[17]": { + "area": 598.0, + "bbox": [ + 49.0, + 0.0, + -1.5, + 60.0, + 19.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 627.0 + }, + "packed[18]": { + "area": 220.0, + "bbox": [ + 57.0, + 63.0, + -1.5, + 71.0, + 67.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 168.0 + }, + "packed[19]": { + "area": 42.0, + "bbox": [ + 63.0, + 35.0, + -1.0, + 66.0, + 38.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 18.0 + }, + "packed[1]": { + "area": 471.9999999999999, + "bbox": [ + 0.0, + 79.0, + -1.0, + 14.0, + 92.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 364.0 + }, + "packed[20]": { + "area": 148.0, + "bbox": [ + 63.0, + 21.0, + -2.0, + 68.0, + 27.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 120.0 + }, + "packed[21]": { + "area": 312.0, + "bbox": [ + 63.0, + 0.0, + -1.0, + 69.0, + 18.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 216.0 + }, + "packed[22]": { + "area": 88.0, + "bbox": [ + 63.0, + 30.0, + -2.0, + 69.0, + 32.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 48.0 + }, + "packed[23]": { + "area": 222.0, + "bbox": [ + 72.0, + 20.0, + -1.5, + 75.0, + 37.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 153.0 + }, + "packed[24]": { + "area": 352.0, + "bbox": [ + 72.0, + 40.0, + -1.0, + 80.0, + 56.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 255.99999999999994 + }, + "packed[25]": { + "area": 38.0, + "bbox": [ + 72.0, + 59.0, + -0.5, + 81.0, + 60.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 9.0 + }, + "packed[26]": { + "area": 106.0, + "bbox": [ + 78.0, + 20.0, + -1.0, + 79.0, + 37.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 34.0 + }, + "packed[27]": { + "area": 830.0, + "bbox": [ + 72.0, + 0.0, + -2.5, + 87.0, + 17.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 1275.0 + }, + "packed[28]": { + "area": 202.0, + "bbox": [ + 82.0, + 20.0, + -1.0, + 87.0, + 33.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 129.99999999999997 + }, + "packed[29]": { + "area": 93.99999999999999, + "bbox": [ + 83.0, + 40.0, + -2.5, + 86.0, + 44.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 60.0 + }, + "packed[2]": { + "area": 502.0, + "bbox": [ + 0.0, + 63.0, + -0.5, + 17.0, + 76.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 221.0 + }, + "packed[30]": { + "area": 38.0, + "bbox": [ + 83.0, + 47.0, + -0.5, + 86.0, + 51.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 12.0 + }, + "packed[31]": { + "area": 75.99999999999999, + "bbox": [ + 83.0, + 54.0, + -2.5, + 87.0, + 56.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 39.99999999999999 + }, + "packed[32]": { + "area": 148.0, + "bbox": [ + 90.0, + 17.0, + -0.5, + 94.0, + 31.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 56.0 + }, + "packed[33]": { + "area": 232.0, + "bbox": [ + 90.0, + 61.0, + -1.0, + 96.0, + 74.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 156.0 + }, + "packed[34]": { + "area": 418.0, + "bbox": [ + 90.0, + 0.0, + -0.5, + 103.0, + 14.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 182.00000000000003 + }, + "packed[35]": { + "area": 362.0, + "bbox": [ + 90.0, + 34.0, + -0.5, + 103.0, + 46.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 156.0 + }, + "packed[36]": { + "area": 453.99999999999994, + "bbox": [ + 90.0, + 49.0, + -2.5, + 103.0, + 58.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 585.0 + }, + "packed[37]": { + "area": 220.0, + "bbox": [ + 97.0, + 17.0, + -2.0, + 100.0, + 31.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 168.0 + }, + "packed[38]": { + "area": 58.0, + "bbox": [ + 99.0, + 61.0, + -0.5, + 101.0, + 70.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 17.999999999999996 + }, + "packed[39]": { + "area": 28.0, + "bbox": [ + 99.0, + 73.0, + -2.0, + 101.0, + 74.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 7.999999999999998 + }, + "packed[3]": { + "area": 1072.0, + "bbox": [ + 0.0, + 0.0, + -2.0, + 19.0, + 20.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 1519.9999999999998 + }, + "packed[40]": { + "area": 160.0, + "bbox": [ + 106.0, + 30.0, + -2.0, + 108.0, + 42.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 95.99999999999999 + }, + "packed[41]": { + "area": 278.0, + "bbox": [ + 106.0, + 59.0, + -1.0, + 115.0, + 70.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 198.0 + }, + "packed[42]": { + "area": 78.0, + "bbox": [ + 106.0, + 86.0, + -0.5, + 115.0, + 89.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 27.0 + }, + "packed[43]": { + "area": 279.99999999999994, + "bbox": [ + 106.0, + 73.0, + -2.5, + 116.0, + 79.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 299.99999999999994 + }, + "packed[44]": { + "area": 64.0, + "bbox": [ + 106.0, + 82.0, + -1.0, + 116.0, + 83.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 19.999999999999996 + }, + "packed[45]": { + "area": 402.0, + "bbox": [ + 106.0, + 15.0, + -1.5, + 117.0, + 27.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 396.0 + }, + "packed[46]": { + "area": 286.0, + "bbox": [ + 106.0, + 45.0, + -0.5, + 117.0, + 56.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 121.0 + }, + "packed[47]": { + "area": 384.0, + "bbox": [ + 106.0, + 0.0, + -1.0, + 118.0, + 12.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 288.0 + }, + "packed[48]": { + "area": 126.0, + "bbox": [ + 111.0, + 30.0, + -1.5, + 114.0, + 39.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 81.0 + }, + "packed[49]": { + "area": 14.0, + "bbox": [ + 117.0, + 30.0, + -0.5, + 118.0, + 33.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 2.9999999999999996 + }, + "packed[4]": { + "area": 910.0, + "bbox": [ + 0.0, + 23.0, + -2.5, + 19.0, + 38.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 1425.0 + }, + "packed[5]": { + "area": 822.0, + "bbox": [ + 9.0, + 41.0, + -1.5, + 27.0, + 58.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 918.0 + }, + "packed[6]": { + "area": 267.99999999999994, + "bbox": [ + 17.0, + 79.0, + -0.5, + 31.0, + 87.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 112.0 + }, + "packed[7]": { + "area": 433.99999999999994, + "bbox": [ + 20.0, + 63.0, + -2.5, + 37.0, + 69.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 510.0 + }, + "packed[8]": { + "area": 522.0, + "bbox": [ + 22.0, + 28.0, + -2.5, + 39.0, + 36.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 680.0 + }, + "packed[9]": { + "area": 112.0, + "bbox": [ + 22.0, + 23.0, + -0.5, + 40.0, + 25.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 36.0 + }, + "test_boxes[0]": { + "area": 64.0, + "bbox": [ + -5.0, + -0.5, + -1.0, + 5.0, + 0.5, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 19.999999999999996 + }, + "test_boxes[10]": { + "area": 93.99999999999999, + "bbox": [ + -1.5, + -2.0, + -2.5, + 1.5, + 2.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 60.0 + }, + "test_boxes[11]": { + "area": 42.0, + "bbox": [ + -1.5, + -1.5, + -1.0, + 1.5, + 1.5, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 18.0 + }, + "test_boxes[12]": { + "area": 598.0, + "bbox": [ + -5.5, + -9.5, + -1.5, + 5.5, + 9.5, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 627.0 + }, + "test_boxes[13]": { + "area": 830.0, + "bbox": [ + -7.5, + -8.5, + -2.5, + 7.5, + 8.5, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 1275.0 + }, + "test_boxes[14]": { + "area": 402.0, + "bbox": [ + -5.5, + -6.0, + -1.5, + 5.5, + 6.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 396.0 + }, + "test_boxes[15]": { + "area": 136.0, + "bbox": [ + -4.0, + -2.0, + -1.5, + 4.0, + 2.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 96.0 + }, + "test_boxes[16]": { + "area": 78.0, + "bbox": [ + -4.5, + -1.5, + -0.5, + 4.5, + 1.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 27.0 + }, + "test_boxes[17]": { + "area": 386.0, + "bbox": [ + -3.5, + -7.5, + -2.0, + 3.5, + 7.5, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 420.0 + }, + "test_boxes[18]": { + "area": 28.0, + "bbox": [ + -1.0, + -0.5, + -2.0, + 1.0, + 0.5, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 7.999999999999998 + }, + "test_boxes[19]": { + "area": 148.0, + "bbox": [ + -2.0, + -7.0, + -0.5, + 2.0, + 7.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 56.0 + }, + "test_boxes[1]": { + "area": 14.0, + "bbox": [ + -0.5, + -1.5, + -0.5, + 0.5, + 1.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 2.9999999999999996 + }, + "test_boxes[20]": { + "area": 232.0, + "bbox": [ + -3.0, + -6.5, + -1.0, + 3.0, + 6.5, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 156.0 + }, + "test_boxes[21]": { + "area": 249.99999999999997, + "bbox": [ + -0.5, + -10.0, + -2.5, + 0.5, + 10.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 99.99999999999997 + }, + "test_boxes[22]": { + "area": 291.99999999999994, + "bbox": [ + -7.0, + -2.0, + -2.5, + 7.0, + 2.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 280.0 + }, + "test_boxes[23]": { + "area": 522.0, + "bbox": [ + -8.5, + -4.0, + -2.5, + 8.5, + 4.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 680.0 + }, + "test_boxes[24]": { + "area": 753.9999999999999, + "bbox": [ + -10.0, + -8.5, + -0.5, + 10.0, + 8.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 340.00000000000006 + }, + "test_boxes[25]": { + "area": 453.99999999999994, + "bbox": [ + -6.5, + -4.5, + -2.5, + 6.5, + 4.5, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 585.0 + }, + "test_boxes[26]": { + "area": 160.0, + "bbox": [ + -1.0, + -6.0, + -2.0, + 1.0, + 6.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 95.99999999999999 + }, + "test_boxes[27]": { + "area": 75.99999999999999, + "bbox": [ + -2.0, + -1.0, + -2.5, + 2.0, + 1.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 39.99999999999999 + }, + "test_boxes[28]": { + "area": 72.0, + "bbox": [ + -4.0, + -1.0, + -1.0, + 4.0, + 1.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 31.999999999999993 + }, + "test_boxes[29]": { + "area": 312.0, + "bbox": [ + -3.0, + -9.0, + -1.0, + 3.0, + 9.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 216.0 + }, + "test_boxes[2]": { + "area": 38.0, + "bbox": [ + -4.5, + -0.5, + -0.5, + 4.5, + 0.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 9.0 + }, + "test_boxes[30]": { + "area": 384.0, + "bbox": [ + -6.0, + -6.0, + -1.0, + 6.0, + 6.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 288.0 + }, + "test_boxes[31]": { + "area": 222.0, + "bbox": [ + -1.5, + -8.5, + -1.5, + 1.5, + 8.5, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 153.0 + }, + "test_boxes[32]": { + "area": 822.0, + "bbox": [ + -9.0, + -8.5, + -1.5, + 9.0, + 8.5, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 918.0 + }, + "test_boxes[33]": { + "area": 1072.0, + "bbox": [ + -9.5, + -10.0, + -2.0, + 9.5, + 10.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 1519.9999999999998 + }, + "test_boxes[34]": { + "area": 220.0, + "bbox": [ + -1.5, + -7.0, + -2.0, + 1.5, + 7.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 168.0 + }, + "test_boxes[35]": { + "area": 279.99999999999994, + "bbox": [ + -5.0, + -3.0, + -2.5, + 5.0, + 3.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 299.99999999999994 + }, + "test_boxes[36]": { + "area": 418.0, + "bbox": [ + -6.5, + -7.0, + -0.5, + 6.5, + 7.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 182.00000000000003 + }, + "test_boxes[37]": { + "area": 202.0, + "bbox": [ + -2.5, + -6.5, + -1.0, + 2.5, + 6.5, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 129.99999999999997 + }, + "test_boxes[38]": { + "area": 220.0, + "bbox": [ + -7.0, + -2.0, + -1.5, + 7.0, + 2.0, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 168.0 + }, + "test_boxes[39]": { + "area": 278.0, + "bbox": [ + -4.5, + -5.5, + -1.0, + 4.5, + 5.5, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 198.0 + }, + "test_boxes[3]": { + "area": 352.0, + "bbox": [ + -4.0, + -8.0, + -1.0, + 4.0, + 8.0, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 255.99999999999994 + }, + "test_boxes[40]": { + "area": 286.0, + "bbox": [ + -5.5, + -5.5, + -0.5, + 5.5, + 5.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 121.0 + }, + "test_boxes[41]": { + "area": 910.0, + "bbox": [ + -9.5, + -7.5, + -2.5, + 9.5, + 7.5, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 1425.0 + }, + "test_boxes[42]": { + "area": 148.0, + "bbox": [ + -2.5, + -3.0, + -2.0, + 2.5, + 3.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 120.0 + }, + "test_boxes[43]": { + "area": 378.0, + "bbox": [ + -3.0, + -9.5, + -1.5, + 3.0, + 9.5, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 342.0 + }, + "test_boxes[44]": { + "area": 126.0, + "bbox": [ + -1.5, + -4.5, + -1.5, + 1.5, + 4.5, + 1.5 + ], + "edges": 12, + "faces": 6, + "volume": 81.0 + }, + "test_boxes[45]": { + "area": 502.0, + "bbox": [ + -8.5, + -6.5, + -0.5, + 8.5, + 6.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 221.0 + }, + "test_boxes[46]": { + "area": 58.0, + "bbox": [ + -1.0, + -4.5, + -0.5, + 1.0, + 4.5, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 17.999999999999996 + }, + "test_boxes[47]": { + "area": 38.0, + "bbox": [ + -1.5, + -2.0, + -0.5, + 1.5, + 2.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 12.0 + }, + "test_boxes[48]": { + "area": 471.9999999999999, + "bbox": [ + -7.0, + -6.5, + -1.0, + 7.0, + 6.5, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 364.0 + }, + "test_boxes[49]": { + "area": 267.99999999999994, + "bbox": [ + -7.0, + -4.0, + -0.5, + 7.0, + 4.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 112.0 + }, + "test_boxes[4]": { + "area": 106.0, + "bbox": [ + -0.5, + -8.5, + -1.0, + 0.5, + 8.5, + 1.0 + ], + "edges": 12, + "faces": 6, + "volume": 34.0 + }, + "test_boxes[5]": { + "area": 362.0, + "bbox": [ + -6.5, + -6.0, + -0.5, + 6.5, + 6.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 156.0 + }, + "test_boxes[6]": { + "area": 88.0, + "bbox": [ + -3.0, + -1.0, + -2.0, + 3.0, + 1.0, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 48.0 + }, + "test_boxes[7]": { + "area": 766.0, + "bbox": [ + -8.5, + -7.5, + -2.0, + 8.5, + 7.5, + 2.0 + ], + "edges": 12, + "faces": 6, + "volume": 1020.0 + }, + "test_boxes[8]": { + "area": 433.99999999999994, + "bbox": [ + -8.5, + -3.0, + -2.5, + 8.5, + 3.0, + 2.5 + ], + "edges": 12, + "faces": 6, + "volume": 510.0 + }, + "test_boxes[9]": { + "area": 112.0, + "bbox": [ + -9.0, + -1.0, + -0.5, + 9.0, + 1.0, + 0.5 + ], + "edges": 12, + "faces": 6, + "volume": 36.0 + } + }, + "status": "ok" + }, + "examples/pegboard_j_hook": { + "shapes": { + "l1": { + "area": 0.0, + "bbox": [ + -10.0, + 0.0, + 0.0, + 21.299999999999997, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 21.299999999999997, + 0.0, + 0.0, + 24.682893434829268, + 2.3687274840275916, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 24.682893434829268, + 2.3687274840275916, + 0.0, + 26.735014294783284, + 8.00688320874304, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 26.735014294783284, + 8.006883208743043, + 0.0, + 30.117907729612554, + 10.375610692770634, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l5": { + "area": 0.0, + "bbox": [ + 30.117907729612554, + 10.375610692770634, + 0.0, + 36.117907729612554, + 10.375610692770634, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l6": { + "area": 0.0, + "bbox": [ + -21.5, + -22.82528915964039, + 0.0, + -10.0, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l7": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 0.0, + 10.644499999999999, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "mainp": { + "area": 1809.6005320461445, + "bbox": [ + -24.144500100000002, + -27.166095039150477, + -2.544500099999999, + 36.117907729612554, + 13.020110692770633, + 2.5445001 + ], + "edges": 60, + "faces": 22, + "volume": 2340.1516603407586 + }, + "sprof": { + "area": 0.0, + "bbox": [ + -21.5, + -24.5617709363097, + 0.0, + 36.117907729612554, + 10.375610692770634, + 0.0 + ], + "edges": 7, + "faces": 0, + "volume": 0.0 + }, + "stub": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 0.0, + 10.644499999999999, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/pegboard_j_hook_algebra": { + "shapes": { + "l1": { + "area": 0.0, + "bbox": [ + -10.0, + 0.0, + 0.0, + 21.299999999999997, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 21.299999999999997, + 0.0, + 0.0, + 24.682893434829268, + 2.3687274840275916, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 24.682893434829268, + 2.3687274840275916, + 0.0, + 26.735014294783284, + 8.00688320874304, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 26.735014294783284, + 8.006883208743043, + 0.0, + 30.117907729612554, + 10.375610692770634, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l5": { + "area": 0.0, + "bbox": [ + 30.117907729612554, + 10.375610692770634, + 0.0, + 36.117907729612554, + 10.375610692770634, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l6": { + "area": 0.0, + "bbox": [ + -21.5, + -22.82528915964039, + 0.0, + -10.0, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l7": { + "area": 0.0, + "bbox": [ + -11.996954043169703, + -24.5617709363097, + 0.0, + -2.1488765130476235, + -22.82528915964039, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "mainp": { + "area": 1809.6005711185123, + "bbox": [ + -24.144500100000002, + -27.166095039150477, + -2.544500099999999, + 36.117907729612554, + 13.020110692770633, + 2.5445001000000005 + ], + "edges": 80, + "faces": 31, + "volume": 2340.1516963224026 + }, + "sprof": { + "area": 0.0, + "bbox": [ + -21.5, + -24.5617709363097, + 0.0, + 36.117907729612554, + 10.375610692770634, + 0.0 + ], + "edges": 7, + "faces": 0, + "volume": 0.0 + }, + "stub": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 0.0, + 10.644499999999999, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "wire": { + "area": 0.0, + "bbox": [ + -21.5, + -24.5617709363097, + 0.0, + 36.117907729612554, + 10.375610692770634, + 0.0 + ], + "edges": 7, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/pillow_block": { + "shapes": { + "pillow_block": { + "area": 13163.451210852161, + "bbox": [ + -40.0, + -30.000000000000682, + 0.0, + 40.0, + 30.0, + 10.0 + ], + "edges": 54, + "faces": 25, + "volume": 44436.460392133944 + }, + "plan": { + "area": 4778.539815981608, + "bbox": [ + -40.0, + -30.000000000000682, + 0.0, + 40.0, + 30.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/pillow_block_algebra": { + "shapes": { + "pillow_block": { + "area": 13163.451210852161, + "bbox": [ + -40.0, + -30.000000000000682, + 0.0, + 40.0, + 30.0, + 10.0 + ], + "edges": 54, + "faces": 25, + "volume": 44436.460392133944 + }, + "plan": { + "area": 4778.539815981608, + "bbox": [ + -40.0, + -30.000000000000682, + 0.0, + 40.0, + 30.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/platonic_solids": { + "shapes": { + "solids[0]": { + "area": 2.628655560595669, + "bbox": [ + -1.2279009295211316, + -1.0705332903609601, + -0.46708617948135794, + -0.39013305922876385, + -0.10503721422398593, + 0.46708617948135794 + ], + "edges": 30, + "faces": 12, + "volume": 0.3481454828903281 + }, + "solids[1]": { + "area": 1.732050807568878, + "bbox": [ + -1.2135254915624216, + 0.18327675510499938, + -0.5, + -0.4045084971874737, + 0.9922937494799471, + 0.5 + ], + "edges": 12, + "faces": 8, + "volume": 0.16666666666666674 + }, + "solids[2]": { + "area": 2.3936353458184843, + "bbox": [ + -0.17672142687075315, + -1.3555650134826274, + -0.42532540417602, + 0.7947554156206473, + -0.5465480191076799, + 0.42532540417602 + ], + "edges": 30, + "faces": 20, + "volume": 0.3170188387650511 + }, + "solids[3]": { + "area": 2.000000000000001, + "bbox": [ + -0.0547348959171024, + 0.5873046260031037, + -0.2886751345948129, + 0.6727688846669972, + 1.3148084065872034, + 0.2886751345948129 + ], + "edges": 12, + "faces": 6, + "volume": 0.19245008972987532 + }, + "solids[4]": { + "area": 1.154700538379252, + "bbox": [ + 0.711324865405187, + -0.2886751345948129, + -0.2886751345948129, + 1.288675134594813, + 0.2886751345948129, + 0.2886751345948129 + ], + "edges": 6, + "faces": 4, + "volume": 0.06415002990995845 + } + }, + "status": "ok" + }, + "examples/playing_cards": { + "shapes": { + "ace_spades": { + "area": 5368.318052955945, + "bbox": [ + -1.4959255122671514e-15, + 1.1686097468332243e-15, + -1e-07, + 63.500000200001175, + 88.9000002, + 1e-07 + ], + "edges": 51, + "faces": 3, + "volume": 0.0 + }, + "box": { + "area": 40327.220820985116, + "bbox": [ + -35.75000010168624, + -48.45000009999999, + 0.0, + 35.750000098315496, + 48.450000100000004, + 16.7000001 + ], + "edges": 225, + "faces": 74, + "volume": 41557.90012086231 + }, + "box_builder": { + "area": 22446.156889983973, + "bbox": [ + -35.75000000031827, + -48.449999999999996, + 0.0, + 35.75, + 48.449999999999996, + 14.7 + ], + "edges": 88, + "faces": 28, + "volume": 25320.596199425287 + }, + "hand": { + "area": 26823.958131195715, + "bbox": [ + -29.213006747145688, + -20.525695104648154, + -4.0000001, + 88.88348834942424, + 104.06436928045393, + 1e-07 + ], + "edges": 238, + "faces": 11, + "volume": 0.0 + }, + "inset_walls": { + "area": 542.3200948094333, + "bbox": [ + -33.500000000001734, + -46.199999999999996, + 0.0, + 33.5, + 46.199999999999996, + 0.0 + ], + "edges": 16, + "faces": 1, + "volume": 0.0 + }, + "jack_diamonds": { + "area": 5434.9791023194175, + "bbox": [ + -1.4959255122671514e-15, + 1.1686097468332243e-15, + -1e-07, + 63.500000200001175, + 88.9000002, + 1e-07 + ], + "edges": 36, + "faces": 1, + "volume": 0.0 + }, + "king_hearts": { + "area": 5320.512076373615, + "bbox": [ + -1.4959255122671514e-15, + 1.1686097468332243e-15, + -1e-07, + 63.500000200001175, + 88.9000002, + 1e-07 + ], + "edges": 56, + "faces": 1, + "volume": 0.0 + }, + "lid_builder": { + "area": 17881.063931001125, + "bbox": [ + -35.75000010168624, + -48.45000009999999, + 0.0, + 35.750000098315496, + 48.450000100000004, + 8.350000099999999 + ], + "edges": 137, + "faces": 46, + "volume": 16237.303921437033 + }, + "outset_walls": { + "area": 567.060136956453, + "bbox": [ + -35.750000000001734, + -48.449999999999996, + 0.0, + 35.75, + 48.449999999999996, + 0.0 + ], + "edges": 16, + "faces": 1, + "volume": 0.0 + }, + "plan": { + "area": 6912.966386503612, + "bbox": [ + -35.750000000001734, + -48.449999999999996, + 0.0, + 35.75, + 48.449999999999996, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "queen_clubs": { + "area": 5341.759604328184, + "bbox": [ + -1.4959255122671514e-15, + 1.1686097468332243e-15, + -1e-07, + 63.500000200001175, + 88.9000002, + 1e-07 + ], + "edges": 48, + "faces": 3, + "volume": 0.0 + }, + "ten_spades": { + "area": 5358.389295218552, + "bbox": [ + -1.4959255122671514e-15, + 1.1686097468332243e-15, + -1e-07, + 63.500000200001175, + 88.9000002, + 1e-07 + ], + "edges": 47, + "faces": 3, + "volume": 0.0 + }, + "top": { + "area": 6912.966386503612, + "bbox": [ + -35.750000000001734, + -48.449999999999996, + 0.0, + 35.75, + 48.449999999999996, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "walls": { + "area": 1267.8631220181535, + "bbox": [ + -35.750000000001734, + -48.449999999999996, + 0.0, + 35.75, + 48.449999999999996, + 0.0 + ], + "edges": 16, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/projection": { + "shapes": { + "arch_path": { + "area": 0.0, + "bbox": [ + -48.98979494059401, + -49.48716602599162, + -7.142857244362895, + 48.98979495566356, + 49.48716603053936, + 10.0000001 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "arch_path_start": { + "area": 0.0, + "bbox": [ + 48.98979485566356, + -1.4210854715202004e-14, + 10.0, + 48.98979485566356, + -1.4210854715202004e-14, + 10.0 + ], + "edges": 0, + "faces": 0, + "volume": 0.0 + }, + "flat_planar_text_faces[0]": { + "area": 115.9265244102478, + "bbox": [ + -21.6900390625, + -8.326674378754569e-16, + -7.500001525878907, + -7.020019531249998, + 1.5953924853841055e-15, + 14.370018005371094 + ], + "edges": 10, + "faces": 1, + "volume": 0.0 + }, + "flat_planar_text_faces[1]": { + "area": 55.112876367568965, + "bbox": [ + -4.320019531250001, + -8.32667437875457e-16, + -7.500001525878908, + -1.8000000000000007, + 1.5953924853841055e-15, + 14.370018005371094 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "flat_planar_text_faces[2]": { + "area": 118.62244042992596, + "bbox": [ + 0.8999999, + -1.0000000090927716e-07, + -8.190040688378906, + 15.6900391625, + 1.0000000096256535e-07, + 8.670018105371094 + ], + "edges": 39, + "faces": 1, + "volume": 0.0 + }, + "flat_planar_text_faces[3]": { + "area": 65.68666660070419, + "bbox": [ + 16.530078025, + -1.0000000090927716e-07, + -8.190040688378906, + 23.730078225000003, + 1.0000000139222383e-07, + 12.540037636621093 + ], + "edges": 18, + "faces": 1, + "volume": 0.0 + }, + "flat_projected_text_faces": { + "area": 375.8696518533476, + "bbox": [ + -121.6900390625, + -149.96848944965197, + -8.19004063770358, + -76.269921775, + -142.6971285684709, + 14.3700180053711 + ], + "edges": 71, + "faces": 4, + "volume": 0.0 + }, + "flat_projection_beams": { + "area": 24970.198655057124, + "bbox": [ + -121.6900390625, + -180.0000001, + -8.190040688378906, + -76.269921775, + -99.9999999, + 14.370018005371094 + ], + "edges": 213, + "faces": 79, + "volume": 28427.880624675752 + }, + "projected_text": { + "area": 921.5007770178046, + "bbox": [ + -49.64700683297132, + -49.904988182728125, + -14.080162949122684, + 49.893182324546885, + 49.8903435467756, + 16.801341061604283 + ], + "edges": 602, + "faces": 41, + "volume": 0.0 + }, + "projection_beams[0]": { + "area": 13600.0, + "bbox": [ + -10.0000001, + -80.0000001, + -10.0000001, + 10.0000001, + 80.0000001, + 10.0000001 + ], + "edges": 12, + "faces": 6, + "volume": 64000.00000000001 + }, + "sphere": { + "area": 31415.926535897932, + "bbox": [ + -50.0, + -50.0, + -50.0, + 50.0, + 50.0, + 50.0 + ], + "edges": 1, + "faces": 1, + "volume": 523598.7755982988 + }, + "square": { + "area": 399.99999999999994, + "bbox": [ + -10.0, + -80.0, + -10.0, + 10.0, + -80.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "square_projected[0]": { + "area": 405.4884004082704, + "bbox": [ + -10.000000000000032, + -50.0, + -10.0, + 10.0, + -47.95831523312719, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "square_projected[1]": { + "area": 405.48840040823103, + "bbox": [ + -10.0, + 47.95831523312719, + -10.000000000000032, + 10.0, + 50.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "square_solids": { + "area": 2016.839572088195, + "bbox": [ + -10.400000100000035, + -52.0, + -10.400000100000032, + 10.400000100000003, + 52.0, + 10.400000100000195 + ], + "edges": 24, + "faces": 12, + "volume": 1687.6968274500937 + }, + "text": { + "area": 919.0971088992558, + "bbox": [ + 2.270406085358445e-14, + -7.1250001, + -1e-07, + 294.2999511718751, + 7.1250001, + 1e-07 + ], + "edges": 599, + "faces": 40, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/projection_algebra": { + "shapes": { + "arch_path": { + "area": 0.0, + "bbox": [ + -48.98979494059401, + -49.48716602599162, + -7.142857244362895, + 48.98979495566356, + 49.48716603053936, + 10.0000001 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "arch_path_start": { + "area": 0.0, + "bbox": [ + 48.98979485566356, + -1.4210854715202004e-14, + 10.0, + 48.98979485566356, + -1.4210854715202004e-14, + 10.0 + ], + "edges": 0, + "faces": 0, + "volume": 0.0 + }, + "cyl": { + "area": 90477.86842338605, + "bbox": [ + 0.0, + -80.0, + -80.0, + 100.0, + 80.0, + 80.0 + ], + "edges": 3, + "faces": 3, + "volume": 2010619.2982974676 + }, + "face": { + "area": 399.99999999999994, + "bbox": [ + -10.0, + -80.0, + -10.0, + 10.0, + -80.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "flat_planar_text": { + "area": 355.3485078084469, + "bbox": [ + -21.6900390625, + -1.0000000181855432e-07, + -8.190040688378906, + 23.730078225000003, + 1.0000000278444767e-07, + 14.370018005371094 + ], + "edges": 71, + "faces": 4, + "volume": 0.0 + }, + "flat_projected_text_faces": { + "area": 375.87739211226085, + "bbox": [ + -21.690039062500013, + -49.96848944965196, + -8.190040688378906, + 23.730078225000003, + -42.6971285684709, + 14.370018005371108 + ], + "edges": 40, + "faces": 4, + "volume": 0.0 + }, + "flat_projection_beams": { + "area": 24970.198655057124, + "bbox": [ + -21.6900390625, + -80.0000001, + -8.190040688378906, + 23.730078225000003, + 1.0000000278444767e-07, + 14.370018005371094 + ], + "edges": 213, + "faces": 79, + "volume": 28427.880624675752 + }, + "obj": { + "area": 23379.16859557134, + "bbox": [ + -48.98979494059401, + -50.0, + -7.142857244362895, + 48.98979495566356, + 50.0, + 50.0 + ], + "edges": 2, + "faces": 2, + "volume": 215833.80603659086 + }, + "projected_text": { + "area": 921.5007770178046, + "bbox": [ + -49.64700683297132, + -49.904988182728125, + -14.080162949122684, + 49.893182324546885, + 49.8903435467756, + 16.801341061604283 + ], + "edges": 602, + "faces": 41, + "volume": 0.0 + }, + "projection_beams": { + "area": 13600.0, + "bbox": [ + -10.0000001, + -80.0000001, + -10.0000001, + 10.0000001, + 80.0000001, + 10.0000001 + ], + "edges": 12, + "faces": 6, + "volume": 64000.00000000001 + }, + "sphere": { + "area": 31415.926535897932, + "bbox": [ + -50.0, + -50.0, + -50.0, + 50.0, + 50.0, + 50.0 + ], + "edges": 1, + "faces": 1, + "volume": 523598.7755982988 + }, + "square": { + "area": 399.99999999999994, + "bbox": [ + -10.0, + -80.0, + -10.0, + 10.0, + -80.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "square_projected[0]": { + "area": 405.4884004082704, + "bbox": [ + -10.000000000000032, + -50.0, + -10.0, + 10.0, + -47.95831523312719, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "square_projected[1]": { + "area": 405.48840040823103, + "bbox": [ + -10.0, + 47.95831523312719, + -10.000000000000032, + 10.0, + 50.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "square_solids": { + "area": 2016.839572088195, + "bbox": [ + -10.400000100000035, + -52.0, + -10.400000100000032, + 10.400000100000003, + 52.0, + 10.400000100000195 + ], + "edges": 24, + "faces": 12, + "volume": 1687.6968274500937 + }, + "text": { + "area": 919.0971088992558, + "bbox": [ + 2.270406085358445e-14, + -7.1250001, + -1e-07, + 294.2999511718751, + 7.1250001, + 1e-07 + ], + "edges": 599, + "faces": 40, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/python_logo": { + "status": "no-shapes" + }, + "examples/roller_coaster": { + "shapes": { + "corner": { + "area": 0.0, + "bbox": [ + 100.0, + 0.0, + 0.0, + 130.0, + 60.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "powerup": { + "area": 0.0, + "bbox": [ + -1e-07, + -1e-07, + -1e-07, + 100.0000001, + 1e-07, + 50.00000010000001 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "roller_coaster": { + "area": 0.0, + "bbox": [ + -109.5901135328278, + -1e-07, + -1.000013592516275e-07, + 130.0, + 60.0000001, + 50.0000001 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "screw": { + "area": 0.0, + "bbox": [ + -75.00000009999991, + 24.999999900014025, + -1e-07, + 75.0000001, + 55.00000009998598, + 30.000000100000474 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/roller_coaster_algebra": { + "shapes": { + "corner": { + "area": 0.0, + "bbox": [ + 100.0, + 0.0, + 0.0, + 130.0, + 60.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "powerup": { + "area": 0.0, + "bbox": [ + -1e-07, + -1e-07, + -1e-07, + 100.0000001, + 1e-07, + 50.00000010000001 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "roller_coaster": { + "area": 0.0, + "bbox": [ + -109.5901135328278, + -1e-07, + -1.000013592516275e-07, + 130.0, + 60.0000001, + 50.0000001 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "screw": { + "area": 0.0, + "bbox": [ + -75.00000009999991, + 24.999999900014025, + -1e-07, + 75.0000001, + 55.00000009998598, + 30.000000100000474 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/shamrock": { + "shapes": { + "shamrock_example": { + "area": 55.95029074173247, + "bbox": [ + -4.250063872754515, + -5.000000098279332, + -1e-07, + 4.250063872754515, + 5.000000098279333, + 1e-07 + ], + "edges": 11, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/stud_wall": { + "shapes": { + "x_wall": { + "area": 8330382.167734902, + "bbox": [ + -3.730349362740526e-14, + -44.45, + -8.828493491819245e-13, + 3962.3999999999996, + 44.45, + 2438.4000000000005 + ], + "edges": 312, + "faces": 130, + "volume": 113679138.17586128 + }, + "y_wall": { + "area": 5994756.697212661, + "bbox": [ + -1.3233858453531795e-14, + 44.449999999999804, + -6.092903959142859e-13, + 88.9000000000003, + 2787.6499999999996, + 2438.4000000000005 + ], + "edges": 240, + "faces": 100, + "volume": 81746795.99163055 + } + }, + "status": "ok" + }, + "examples/tea_cup": { + "error": "AssertionError", + "status": "error" + }, + "examples/tea_cup_algebra": { + "shapes": { + "bowl_section": { + "area": 5888.2371230476865, + "bbox": [ + -1e-07, + -1e-07, + -1.0000000177635683e-07, + 69.0000001, + 1e-07, + 105.0000001 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "handle_cross_section": { + "area": 22.14506729993644, + "bbox": [ + 54.75340194148883, + -4.0, + 34.062957428489106, + 57.09600837026606, + 4.0, + 35.937042571510894 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "path_spline": { + "area": 0.0, + "bbox": [ + 55.924705055877446, + -1e-07, + 34.9999999, + 100.11387722320589, + 1e-07, + 100.90946051908081 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "s": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + -1e-07, + 69.00000009999994, + 105.0000001, + 1e-07 + ], + "edges": 5, + "faces": 0, + "volume": 0.0 + }, + "tea_cup": { + "area": 87984.57116055215, + "bbox": [ + -67.77624350150904, + -67.77624539686997, + 0.0, + 101.6138781423565, + 67.77624539687, + 105.00000010000007 + ], + "edges": 68, + "faces": 28, + "volume": 130326.75447606308 + } + }, + "status": "ok" + }, + "examples/toy_truck": { + "shapes": { + "body": { + "area": 2686.7660397470236, + "bbox": [ + -11.000000099999996, + -17.5, + -1.0000001000000005, + 11.000000100000001, + 20.300000100000005, + 10.000000100000005 + ], + "edges": 353, + "faces": 145, + "volume": 6160.103112027914 + }, + "body_skt": { + "area": 699.1415926487991, + "bbox": [ + -10.000000000000696, + -17.5, + 0.0, + 10.0, + 17.5, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "bumper": { + "area": 169.15956892216423, + "bbox": [ + -11.000000099999996, + 17.764101515137757, + -1.0000001000000005, + 11.000000100000001, + 20.300000100000005, + 1.0000001000000005 + ], + "edges": 52, + "faces": 24, + "volume": 85.20641354750668 + }, + "bumper_plan": { + "area": 43.22033318946846, + "bbox": [ + -11.000000099999996, + 17.764101515137757, + -1.0000000002775557e-07, + 11.000000100000001, + 20.300000100000005, + 9.999999997224442e-08 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "cab": { + "area": 797.8127178461689, + "bbox": [ + -8.0000001, + -15.500000099999829, + 9.9999999, + 8.0000001, + 1.3771958444962784, + 16.922554570078084 + ], + "edges": 112, + "faces": 43, + "volume": 473.9017698761655 + }, + "cab_plan": { + "area": 127.57079631255239, + "bbox": [ + 0.0, + -8.000000000000172, + 0.0, + 8.0, + 8.000000000000172, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "fender": { + "area": 103.4354385592997, + "bbox": [ + 0.0, + 0.0, + 0.0, + 18.0, + 6.000000057766622, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "front_window": { + "area": 166.7171458523731, + "bbox": [ + -7.6, + -5.500000000000098, + 0.0, + 7.6, + 5.500000000000098, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "grill": { + "area": 109.50221197868926, + "bbox": [ + -8.0, + 0.0, + 0.0, + 8.0, + 8.5, + 0.0 + ], + "edges": 24, + "faces": 1, + "volume": 0.0 + }, + "grill_perimeter": { + "area": 0.0, + "bbox": [ + -8.0, + 18.500000000000004, + 0.0, + 8.0, + 18.500000000000004, + 8.5 + ], + "edges": 6, + "faces": 0, + "volume": 0.0 + }, + "rear_window": { + "area": 31.517146300668532, + "bbox": [ + -4.0, + -2.000000031294789, + 0.0, + 4.0, + 2.000000031294789, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "side_window": { + "area": 88.10218842939275, + "bbox": [ + -6.000000000000001, + 0.0, + 0.0, + 12.90412319597138, + 5.500000000000048, + 0.0 + ], + "edges": 12, + "faces": 2, + "volume": 0.0 + }, + "wheel_well": { + "area": 43.33269367002239, + "bbox": [ + -2.220446049250313e-16, + -8.881784197001252e-16, + 0.0, + 12.0, + 4.000000009404593, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "examples/twist_extrude": { + "shapes": { + "hex_sketch": { + "area": 2.598076211353316, + "bbox": [ + -1.0, + -0.8660254037844386, + 0.0, + 1.0, + 0.8660254037844387, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "twist_extrude": { + "area": 35.274985576271064, + "bbox": [ + -1.0000086048838088, + -0.9999950866681123, + -1e-07, + 1.000008604883809, + 0.9999950866681125, + 5.0000001 + ], + "edges": 18, + "faces": 8, + "volume": 12.990434585813642 + } + }, + "status": "ok" + }, + "examples/vase": { + "shapes": { + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 12.0, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 11.925073826078204, + -4.440892098500626e-16, + 0.0, + 14.999999999999993, + 20.000000000000007, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 14.999999899999994, + 19.999999900000006, + -1e-07, + 22.098432090885872, + 50.000000099999994, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 19.330127018922195, + 50.0, + 0.0, + 20.0, + 55.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l5": { + "area": 0.0, + "bbox": [ + 19.9999999, + 54.9999999, + -1e-07, + 22.63266188224813, + 60.00000010000001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "outline": { + "area": 0.0, + "bbox": [ + 0.0, + -4.440892098500626e-16, + -1e-07, + 22.63266188224813, + 61.0, + 1e-07 + ], + "edges": 8, + "faces": 0, + "volume": 0.0 + }, + "profile": { + "area": 1077.9809577204992, + "bbox": [ + -1e-07, + -1.000000004440892e-07, + -1e-07, + 22.63266188224813, + 61.0000001, + 1e-07 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "vase": { + "area": 15208.407680709272, + "bbox": [ + -22.63266188224813, + -1.0000000077715611e-07, + -22.63266188224813, + 22.63266188224813, + 61.0000001, + 22.63266188224813 + ], + "edges": 34, + "faces": 19, + "volume": 7560.707918295803 + } + }, + "status": "ok" + }, + "examples/vase_algebra": { + "shapes": { + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 12.0, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 11.925073826078204, + -4.440892098500626e-16, + 0.0, + 14.999999999999993, + 20.000000000000007, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 14.999999899999994, + 19.999999900000006, + -1e-07, + 22.098432090885872, + 50.000000099999994, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 19.330127018922195, + 50.0, + 0.0, + 20.0, + 55.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l5": { + "area": 0.0, + "bbox": [ + 19.9999999, + 54.9999999, + -1e-07, + 22.63266188224813, + 60.00000010000001, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "outline": { + "area": 0.0, + "bbox": [ + 0.0, + -4.440892098500626e-16, + -1e-07, + 22.63266188224813, + 61.0, + 1e-07 + ], + "edges": 8, + "faces": 0, + "volume": 0.0 + }, + "profile": { + "area": 1077.9809577204992, + "bbox": [ + -1e-07, + -1.000000004440892e-07, + -1e-07, + 22.63266188224813, + 61.0000001, + 1e-07 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "vase": { + "area": 15208.407680709272, + "bbox": [ + -22.63266188224813, + -1.0000000077715611e-07, + -22.63266188224813, + 22.63266188224813, + 61.0, + 22.63266188224813 + ], + "edges": 34, + "faces": 19, + "volume": 7560.707918295803 + } + }, + "status": "ok" + }, + "general_examples/ex01": { + "shapes": { + "ex1": { + "area": 12399.999999999996, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 12, + "faces": 6, + "volume": 48000.0 + } + }, + "status": "ok" + }, + "general_examples/ex02": { + "shapes": { + "ex2": { + "area": 12330.884961621023, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 15, + "faces": 7, + "volume": 44198.67288915635 + } + }, + "status": "ok" + }, + "general_examples/ex03": { + "shapes": { + "ex3": { + "area": 30559.289474462013, + "bbox": [ + -60.0, + -60.0, + 0.0, + 60.0, + 60.0, + 20.0 + ], + "edges": 15, + "faces": 7, + "volume": 202194.6710584651 + }, + "ex3_sk": { + "area": 10109.733552923255, + "bbox": [ + -60.0, + -60.0, + 0.0, + 60.0, + 60.0, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex08": { + "shapes": { + "ex8": { + "area": 11916.0, + "bbox": [ + 0.0, + -10.0, + -10.0, + 100.0, + 10.0, + 10.0 + ], + "edges": 36, + "faces": 14, + "volume": 5800.0 + }, + "ex8_ln": { + "area": 0.0, + "bbox": [ + -10.0, + -10.0, + 0.0, + 10.0, + 10.0, + 0.0 + ], + "edges": 14, + "faces": 0, + "volume": 0.0 + }, + "ex8_sk": { + "area": 57.99999999999997, + "bbox": [ + -10.0, + -10.0, + 0.0, + 10.0, + 10.0, + 0.0 + ], + "edges": 12, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex09": { + "shapes": { + "ex9": { + "area": 11629.753883596593, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.000000000000014 + ], + "edges": 36, + "faces": 14, + "volume": 45706.90228944049 + } + }, + "status": "ok" + }, + "general_examples/ex10": { + "shapes": { + "ex10": { + "area": 11849.637421324715, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 17, + "faces": 8, + "volume": 40848.10405858136 + } + }, + "status": "ok" + }, + "general_examples/ex11": { + "shapes": { + "ex11": { + "area": 11779.433551358667, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.000000000000014 + ], + "edges": 101, + "faces": 36, + "volume": 36177.36505728397 + }, + "ex11_sk": { + "area": 237.76412907378844, + "bbox": [ + -24.045084971874736, + -19.755282581475768, + 0.0, + 25.0, + 19.755282581475768, + 0.0 + ], + "edges": 20, + "faces": 4, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex12": { + "shapes": { + "ex12": { + "area": 4698.413109265178, + "bbox": [ + -1.0000004796163466e-07, + -1e-07, + -1e-07, + 60.0000001, + 35.08289914429867, + 10.0000001 + ], + "edges": 12, + "faces": 6, + "volume": 14627.106295339287 + }, + "ex12_ln": { + "area": 0.0, + "bbox": [ + -1.0000004796163466e-07, + 0.0, + -1e-07, + 60.0, + 35.08289914429867, + 1e-07 + ], + "edges": 4, + "faces": 0, + "volume": 0.0 + }, + "ex12_sk": { + "area": 1462.767056160497, + "bbox": [ + -1.0000004796163466e-07, + -1e-07, + -1e-07, + 60.0000001, + 35.08289914429867, + 1e-07 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -1.0000004796163466e-07, + 18.603717035290245, + -1e-07, + 55.0000001, + 35.08289914429867, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 55.0, + 0.0, + 0.0, + 60.0, + 30.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 60.0, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 0.0, + 20.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex13": { + "shapes": { + "ex13": { + "area": 20311.6827882315, + "bbox": [ + -50.0, + -50.0, + -5.0, + 50.0, + 50.0, + 5.000000000000002 + ], + "edges": 47, + "faces": 23, + "volume": 70872.25969468078 + } + }, + "status": "ok" + }, + "general_examples/ex14": { + "shapes": { + "ex14": { + "area": 19742.386437027966, + "bbox": [ + -160.00000000000023, + -49.99999999999995, + -10.0, + 10.0, + 50.0, + 10.0 + ], + "edges": 24, + "faces": 10, + "volume": 91398.2236861551 + }, + "ex14_ln": { + "area": 0.0, + "bbox": [ + -160.0, + -39.99999999999995, + 0.0, + 0.0, + 40.0, + 0.0 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + }, + "ex14_sk": { + "area": 399.99999999999994, + "bbox": [ + -10.0, + -10.0, + 0.0, + 10.0, + 10.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -80.0, + 0.0, + 0.0, + 0.0, + 40.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + -120.00000000000001, + -39.99999999999995, + 0.0, + -80.0, + 2.2662155590591917e-14, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + -160.0, + -39.99999999999995, + 0.0, + -120.00000000000001, + 4.973799150320701e-14, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex15": { + "shapes": { + "ex15": { + "area": 16800.0, + "bbox": [ + -80.0, + 0.0, + 0.0, + 80.0, + 40.0, + 20.0 + ], + "edges": 24, + "faces": 10, + "volume": 79999.99999999999 + }, + "ex15_ln": { + "area": 0.0, + "bbox": [ + -80.0, + 0.0, + 0.0, + 80.0, + 40.0, + 0.0 + ], + "edges": 10, + "faces": 0, + "volume": 0.0 + }, + "ex15_sk": { + "area": 3999.999999999999, + "bbox": [ + -80.0, + 0.0, + 0.0, + 80.0, + 40.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 80.0, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 80.0, + 0.0, + 0.0, + 80.0, + 40.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 60.0, + 40.0, + 0.0, + 80.0, + 40.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 60.0, + 20.0, + 0.0, + 60.0, + 40.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l5": { + "area": 0.0, + "bbox": [ + 0.0, + 20.0, + 0.0, + 60.0, + 20.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex16": { + "shapes": { + "ex16": { + "area": 167358.46173281362, + "bbox": [ + -160.0, + -80.0, + -150.0, + 160.0, + 0.0, + 150.0000000000002 + ], + "edges": 195, + "faces": 75, + "volume": 1297854.8727899147 + }, + "ex16_single": { + "area": 33471.69234656273, + "bbox": [ + -40.0, + -80.0, + -30.00000000000021, + 40.0, + 0.0, + 30.0 + ], + "edges": 39, + "faces": 15, + "volume": 259570.97455798293 + }, + "ex16_sk": { + "area": 3244.6371820133463, + "bbox": [ + -40.0, + -30.00000000000021, + 0.0, + 40.0, + 30.0, + 0.0 + ], + "edges": 13, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex17": { + "shapes": { + "ex17": { + "area": 14202.247068664105, + "bbox": [ + -45.00000000000017, + -74.69694854648331, + 0.0, + 30.0, + 28.53169548885461, + 20.0 + ], + "edges": 24, + "faces": 10, + "volume": 85595.0864665638 + }, + "ex17_sk": { + "area": 2139.8771616640956, + "bbox": [ + -24.270509831248425, + -28.53169548885461, + 0.0, + 30.0, + 28.531695488854606, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex18": { + "shapes": { + "ex18": { + "area": 11829.753883596593, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.000000000000014 + ], + "edges": 48, + "faces": 18, + "volume": 44706.902289440506 + } + }, + "status": "ok" + }, + "general_examples/ex19": { + "shapes": { + "ex19": { + "area": 10786.261542700255, + "bbox": [ + -36.03875471609677, + -38.99711648727295, + 0.0, + 35.66116260882442, + 38.99711648727295, + 10.0 + ], + "edges": 27, + "faces": 11, + "volume": 41538.56826564553 + }, + "ex19_sk": { + "area": 4378.256301820968, + "bbox": [ + -36.03875471609677, + -38.99711648727295, + 0.0, + 40.0, + 38.99711648727295, + 0.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "ex19_sk2": { + "area": 628.3185307179585, + "bbox": [ + -46.03875471609677, + -27.35534956470232, + 0.0, + 50.0, + 10.0, + 0.0 + ], + "edges": 2, + "faces": 2, + "volume": 0.0 + }, + "topf": { + "area": 4378.256301820968, + "bbox": [ + -36.03875471609677, + -38.99711648727295, + 10.0, + 40.0, + 38.99711648727295, + 10.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "vtx": { + "area": 0.0, + "bbox": [ + 40.0, + 0.0, + 10.0, + 40.0, + 0.0, + 10.0 + ], + "edges": 0, + "faces": 0, + "volume": 0.0 + }, + "vtx2": { + "area": 0.0, + "bbox": [ + -36.03875471609677, + -17.35534956470232, + 10.0, + -36.03875471609677, + -17.35534956470232, + 10.0 + ], + "edges": 0, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex20": { + "shapes": { + "ex20": { + "area": 22453.096491487337, + "bbox": [ + -120.0, + -30.0, + -20.0, + 40.0, + 30.0, + 20.0 + ], + "edges": 15, + "faces": 9, + "volume": 123398.22368615503 + } + }, + "status": "ok" + }, + "general_examples/ex21": { + "shapes": { + "ex21": { + "area": 3805.530633309707, + "bbox": [ + -60.0, + -5.0, + 0.0, + 5.0, + 5.0, + 60.0 + ], + "edges": 8, + "faces": 5, + "volume": 9091.444627420231 + }, + "ex21_sk": { + "area": 78.53981633974482, + "bbox": [ + -5.0, + -5.0, + 0.0, + 5.0, + 5.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex22": { + "shapes": { + "ex22": { + "area": 13133.117581821516, + "bbox": [ + -40.0, + -30.0, + -5.000000000000007, + 40.0, + 30.0, + 5.0 + ], + "edges": 24, + "faces": 10, + "volume": 46778.13736363063 + }, + "ex22_sk": { + "area": 78.53981633974482, + "bbox": [ + -12.5, + -10.0, + 0.0, + 12.5, + 10.0, + 0.0 + ], + "edges": 4, + "faces": 4, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex23": { + "shapes": { + "ex23": { + "area": 12794.096414158657, + "bbox": [ + -25.0, + -25.0, + 0.0, + 25.0, + 25.0, + 60.0 + ], + "edges": 12, + "faces": 7, + "volume": 88619.09277001212 + }, + "ex23_ln": { + "area": 0.0, + "bbox": [ + -25.0, + 0.0, + 0.0, + -15.0, + 35.0, + 0.0 + ], + "edges": 6, + "faces": 0, + "volume": 0.0 + }, + "ex23_sk": { + "area": 1154.4679486213063, + "bbox": [ + -25.0, + 0.0, + 0.0, + 1.7763568394002505e-15, + 60.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -25.0, + 0.0, + 0.0, + -15.0, + 35.0, + 0.0 + ], + "edges": 5, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + -25.0, + 35.0, + 0.0, + -15.0, + 35.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex24": { + "shapes": { + "ex24": { + "area": 18101.68145896556, + "bbox": [ + -40.0000001, + -40.0000001, + -5.0, + 40.0000001, + 40.0000001, + 40.0000001 + ], + "edges": 27, + "faces": 12, + "volume": 89024.35585088223 + }, + "ex24_sk": { + "area": 2234.0214425527415, + "bbox": [ + -26.666666666666668, + -26.666666666666668, + 0.0, + 26.666666666666668, + 26.666666666666668, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "ex24_sk2": { + "area": 133.33333333333331, + "bbox": [ + -6.666666666666667, + -5.0, + 0.0, + 6.666666666666667, + 5.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex25": { + "shapes": { + "ex25": { + "area": 49792.349449952104, + "bbox": [ + -50.45084971874738, + -59.30853086060715, + 0.0, + 62.3606797749979, + 59.308530860607135, + 31.0 + ], + "edges": 60, + "faces": 26, + "volume": 24387.59273282052 + }, + "ex25_sk1": { + "area": 5944.103226844711, + "bbox": [ + -40.45084971874738, + -47.55282581475768, + 0.0, + 50.0, + 47.552825814757675, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "ex25_sk2": { + "area": 9197.188753666056, + "bbox": [ + -50.45084971874738, + -57.55282581475768, + 0.0, + 60.0, + 57.552825814757675, + 0.0 + ], + "edges": 10, + "faces": 1, + "volume": 0.0 + }, + "ex25_sk3": { + "area": 9246.300752309755, + "bbox": [ + -50.45084971874738, + -59.30853086060715, + 0.0, + 62.3606797749979, + 59.308530860607135, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex26": { + "shapes": { + "ex26": { + "area": 14511.999999999996, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 24, + "faces": 11, + "volume": 13952.0 + }, + "topf": { + "area": 4799.999999999999, + "bbox": [ + -40.0, + -30.0, + 5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex27": { + "shapes": { + "ex27": { + "area": 6464.380550980764, + "bbox": [ + -40.0, + -8.942397556322032e-15, + -5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 18, + "faces": 8, + "volume": 20465.708264711477 + }, + "ex27_sk": { + "area": 706.8583470577034, + "bbox": [ + -15.0, + -15.0, + 0.0, + 15.0, + 15.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex28": { + "shapes": { + "ex28": { + "area": 26010.66992507376, + "bbox": [ + -40.00000009999812, + -40.0, + -40.0, + 40.0000001, + 40.0, + 40.0 + ], + "edges": 27, + "faces": 7, + "volume": 251188.19571970133 + }, + "ex28_ex": { + "area": 2078.4609690826524, + "bbox": [ + -10.000000000000009, + -17.320508075688767, + 0.0, + 20.0, + 17.320508075688775, + 10.0 + ], + "edges": 9, + "faces": 5, + "volume": 5196.152422706632 + }, + "ex28_sk": { + "area": 519.6152422706632, + "bbox": [ + -10.000000000000009, + -17.320508075688767, + 0.0, + 20.0, + 17.320508075688775, + 0.0 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "face": { + "area": 346.4101615137754, + "bbox": [ + -10.000000000000009, + -17.320508075688767, + 0.0, + 20.0, + 0.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "midfaces[0]": { + "area": 346.41016151377534, + "bbox": [ + -10.000000000000009, + -17.320508075688767, + 0.0, + -9.999999999999996, + 17.320508075688775, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "midfaces[1]": { + "area": 346.4101615137754, + "bbox": [ + -10.000000000000009, + -17.320508075688767, + 0.0, + 20.0, + 0.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "midfaces[2]": { + "area": 346.41016151377534, + "bbox": [ + -9.999999999999996, + 0.0, + 0.0, + 20.0, + 17.320508075688775, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex29": { + "shapes": { + "ex29": { + "area": 35153.637381728615, + "bbox": [ + -4.440892098500626e-16, + -18.0, + -0.9000000000000004, + 60.0, + 18.0, + 96.0 + ], + "edges": 104, + "faces": 56, + "volume": 15796.616314840636 + }, + "ex29_ow_ln": { + "area": 0.0, + "bbox": [ + 0.0, + -18.0, + 0.0, + 60.0, + 18.0, + 0.0 + ], + "edges": 6, + "faces": 0, + "volume": 0.0 + }, + "ex29_ow_sk": { + "area": 1812.7981751915386, + "bbox": [ + 0.0, + -18.0, + 0.0, + 60.0, + 18.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 0.0, + 9.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 0.0, + 9.0, + 0.0, + 60.0, + 18.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 60.0, + 0.0, + 0.0, + 60.0, + 9.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "necktopf": { + "area": 254.46900494077323, + "bbox": [ + 21.000000000000004, + -9.000000000000002, + 96.0, + 39.0, + 8.999999999999998, + 96.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex30": { + "shapes": { + "ex30": { + "area": 19585.35478229689, + "bbox": [ + -60.0000001, + -40.00000010000001, + -1e-07, + 100.00000010000053, + 100.0000001, + 10.0000001 + ], + "edges": 21, + "faces": 9, + "volume": 64463.381199800904 + }, + "ex30_ln": { + "area": 0.0, + "bbox": [ + -60.0, + -40.0, + -1e-07, + 100.00000010000053, + 100.0, + 1e-07 + ], + "edges": 7, + "faces": 0, + "volume": 0.0 + }, + "ex30_sk": { + "area": 6446.340125697502, + "bbox": [ + -60.0000001, + -40.0000001, + -1e-07, + 100.00000010000053, + 100.0000001, + 1e-07 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "l0": { + "area": 0.0, + "bbox": [ + -60.0, + -40.0, + 0.0, + 100.0, + 100.0, + 0.0 + ], + "edges": 6, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -22.86940140297584, + -9.564478162344862, + -1e-07, + 100.00000010000053, + 41.15608373612789, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex31": { + "shapes": { + "ex31": { + "area": 5977.711776549692, + "bbox": [ + -52.5, + -49.21633369868303, + 0.0, + 52.5, + 49.21633369868303, + 3.0 + ], + "edges": 306, + "faces": 164, + "volume": 4991.9700328814715 + }, + "ex31_sk": { + "area": 1663.990010960491, + "bbox": [ + -52.5, + -49.21633369868303, + 0.0, + 52.5, + 49.21633369868303, + 0.0 + ], + "edges": 102, + "faces": 31, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex32": { + "shapes": { + "ex32": { + "area": 8501.690401232398, + "bbox": [ + -50.0, + -43.30127018922193, + 0.0, + 50.0, + 43.30127018922194, + 19.0 + ], + "edges": 90, + "faces": 44, + "volume": 14839.230484541325 + }, + "ex32_sk": { + "area": 2239.2304845413264, + "bbox": [ + -50.0, + -43.30127018922193, + 0.0, + 50.0, + 43.30127018922194, + 0.0 + ], + "edges": 30, + "faces": 7, + "volume": 0.0 + }, + "obj": { + "area": 200.0, + "bbox": [ + 11.339745962155614, + -43.30127018922193, + 0.0, + 28.66025403784439, + -25.980762113533157, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex33": { + "shapes": { + "ex33": { + "area": 5112.447327281725, + "bbox": [ + -51.0, + -47.63139720814412, + 0.0, + 45.0, + 42.4352447854375, + 11.0 + ], + "edges": 72, + "faces": 36, + "volume": 10840.0 + }, + "ex33_sk": { + "area": 1340.0, + "bbox": [ + -51.0, + -47.63139720814412, + 0.0, + 45.0, + 42.4352447854375, + 0.0 + ], + "edges": 24, + "faces": 6, + "volume": 0.0 + }, + "obj": { + "area": 450.0, + "bbox": [ + 7.0096189432334235, + -47.63139720814412, + 0.0, + 32.99038105676659, + -21.650635094610962, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex34": { + "shapes": { + "ex34": { + "area": 15251.740859121084, + "bbox": [ + -40.0000001, + -30.0000001, + -5.0, + 40.0000001, + 30.0000001, + 9.0000001 + ], + "edges": 198, + "faces": 82, + "volume": 47753.51022950544 + }, + "ex34_sk": { + "area": 335.2048288043953, + "bbox": [ + -25.912516326041665, + 5.838672220806777e-17, + -1e-07, + 25.91251632604167, + 18.800048928124998, + 1e-07 + ], + "edges": 28, + "faces": 5, + "volume": 0.0 + }, + "ex34_sk2": { + "area": 396.6430686960962, + "bbox": [ + -30.762491911979165, + -18.800049028125002, + -1e-07, + 30.76249191197917, + -1.1686097468332243e-15, + 1e-07 + ], + "edges": 34, + "faces": 5, + "volume": 0.0 + }, + "topf": { + "area": 4799.999999999999, + "bbox": [ + -40.0, + -30.0, + 5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex35": { + "shapes": { + "ex35": { + "area": 16471.23889803847, + "bbox": [ + -40.0, + -40.0, + -5.0, + 40.0, + 40.0, + 5.0 + ], + "edges": 48, + "faces": 18, + "volume": 49219.02754903829 + }, + "ex35_ln": { + "area": 0.0, + "bbox": [ + -29.999999999999993, + 7.888609052210118e-31, + 0.0, + 8.27565060379343e-16, + 29.999999999999993, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "ex35_ln2": { + "area": 0.0, + "bbox": [ + 7.888609052210118e-31, + -29.999999999999993, + 0.0, + 29.999999999999993, + 8.27565060379343e-16, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "ex35_sk": { + "area": 1478.0972450961729, + "bbox": [ + -34.999999999999986, + -34.999999999999986, + 0.0, + 34.999999999999986, + 34.999999999999986, + 0.0 + ], + "edges": 12, + "faces": 3, + "volume": 0.0 + }, + "topf": { + "area": 6399.999999999999, + "bbox": [ + -40.0, + -40.0, + 5.0, + 40.0, + 40.0, + 5.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex36": { + "shapes": { + "ex36": { + "area": 10787.997618106452, + "bbox": [ + -6.0000001, + -56.0000001, + -1e-07, + 6.0000001, + 56.0000001, + 56.0000001 + ], + "edges": 15, + "faces": 8, + "volume": 30298.935241110394 + }, + "ex36_sk": { + "area": 113.09733552923255, + "bbox": [ + -6.0, + 44.0, + 0.0, + 6.0, + 56.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "ex36_sk2": { + "area": 300.0, + "bbox": [ + -3.0, + -25.0, + 0.0, + 3.0, + 25.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples/ex37": { + "shapes": { + "ex37": { + "area": 21.914146723649658, + "bbox": [ + -1.5, + -1.0, + -1.000000002220446e-07, + 1.0000001, + 3.0000001, + 1.0000001 + ], + "edges": 17, + "faces": 7, + "volume": 5.534291735082541 + }, + "ex37_sk": { + "area": 2.0, + "bbox": [ + -0.5, + 0.0, + 0.0, + 0.5, + 2.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex01": { + "shapes": { + "ex1": { + "area": 12399.999999999996, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 12, + "faces": 6, + "volume": 48000.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex02": { + "shapes": { + "ex2": { + "area": 12330.884961621023, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 15, + "faces": 7, + "volume": 44198.67288915635 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex03": { + "shapes": { + "ex3": { + "area": 30559.289474462013, + "bbox": [ + -60.0, + -60.0, + 0.0, + 60.0, + 60.0, + 20.0 + ], + "edges": 15, + "faces": 7, + "volume": 202194.6710584651 + }, + "sk3": { + "area": 10109.733552923255, + "bbox": [ + -60.0, + -60.0, + 0.0, + 60.0, + 60.0, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex08": { + "shapes": { + "ex8": { + "area": 11916.0, + "bbox": [ + 0.0, + -10.0, + -10.0, + 100.0, + 10.0, + 10.0 + ], + "edges": 36, + "faces": 14, + "volume": 5799.999999999997 + }, + "ln": { + "area": 0.0, + "bbox": [ + -10.0, + -10.0, + 0.0, + 10.0, + 10.0, + 0.0 + ], + "edges": 12, + "faces": 0, + "volume": 0.0 + }, + "sk8": { + "area": 57.99999999999994, + "bbox": [ + 0.0, + -10.0, + -10.0, + 0.0, + 10.0, + 10.0 + ], + "edges": 12, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex09": { + "shapes": { + "ex9": { + "area": 11629.753883596593, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.000000000000014 + ], + "edges": 36, + "faces": 14, + "volume": 45706.90228944049 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex10": { + "error": "NameError: name 'length' is not defined", + "status": "error" + }, + "general_examples_algebra/ex11": { + "shapes": { + "ex11": { + "area": 11779.433551358667, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.000000000000014 + ], + "edges": 101, + "faces": 36, + "volume": 36177.36505728397 + }, + "polygons": { + "area": 237.76412907378844, + "bbox": [ + -24.045084971874736, + -19.755282581475768, + 5.0, + 25.0, + 19.755282581475765, + 5.0 + ], + "edges": 20, + "faces": 4, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex12": { + "shapes": { + "ex12": { + "area": 4698.413109265178, + "bbox": [ + -1.0000004796163466e-07, + -1e-07, + -1e-07, + 60.0000001, + 35.08289914429867, + 10.0000001 + ], + "edges": 12, + "faces": 6, + "volume": 14627.106295339287 + }, + "l1": { + "area": 0.0, + "bbox": [ + -1.0000004796163466e-07, + 18.603717035290245, + -1e-07, + 55.0000001, + 35.08289914429867, + 1e-07 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 55.0, + 0.0, + 0.0, + 60.0, + 30.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 60.0, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 0.0, + 20.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "sk12": { + "area": 1462.767056160497, + "bbox": [ + -1.0000004796163466e-07, + -1e-07, + -1e-07, + 60.0000001, + 35.08289914429867, + 1e-07 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex13": { + "shapes": { + "ex13": { + "area": 20311.6827882315, + "bbox": [ + -50.0, + -50.0, + -5.0, + 50.0, + 50.0, + 5.000000000000002 + ], + "edges": 47, + "faces": 23, + "volume": 70872.25969468078 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex14": { + "shapes": { + "ex14": { + "area": 19742.386437027966, + "bbox": [ + -160.00000000000023, + -49.99999999999995, + -10.0, + 10.0, + 50.0, + 10.0 + ], + "edges": 24, + "faces": 10, + "volume": 91398.2236861551 + }, + "ex14_ln": { + "area": 0.0, + "bbox": [ + -160.0, + -39.99999999999995, + 0.0, + 0.0, + 40.0, + 0.0 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -80.0, + 0.0, + 0.0, + 0.0, + 40.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + -120.00000000000001, + -39.99999999999995, + 0.0, + -80.0, + 2.2662155590591917e-14, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + -160.0, + -39.99999999999995, + 0.0, + -120.00000000000001, + 4.973799150320701e-14, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "sk14": { + "area": 399.99999999999994, + "bbox": [ + -10.0, + 0.0, + -10.0, + 10.0, + 0.0, + 10.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex15": { + "shapes": { + "ex15": { + "area": 16799.999999999996, + "bbox": [ + -80.0, + 0.0, + 0.0, + 80.0, + 40.0, + 20.0 + ], + "edges": 24, + "faces": 10, + "volume": 79999.99999999999 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 80.0, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 80.0, + 0.0, + 0.0, + 80.0, + 40.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 60.0, + 40.0, + 0.0, + 80.0, + 40.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 60.0, + 20.0, + 0.0, + 60.0, + 40.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l5": { + "area": 0.0, + "bbox": [ + 0.0, + 20.0, + 0.0, + 60.0, + 20.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "ln": { + "area": 0.0, + "bbox": [ + -80.0, + 0.0, + 0.0, + 80.0, + 40.0, + 0.0 + ], + "edges": 8, + "faces": 0, + "volume": 0.0 + }, + "sk15": { + "area": 3999.999999999999, + "bbox": [ + -80.0, + 0.0, + 0.0, + 80.0, + 40.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex16": { + "shapes": { + "circles[0]": { + "area": 139.62634015954634, + "bbox": [ + -26.666666666666668, + -6.666666666666667, + 0.0, + -13.333333333333332, + 6.666666666666667, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "circles[1]": { + "area": 139.62634015954634, + "bbox": [ + -6.666666666666667, + -6.666666666666667, + 0.0, + 6.666666666666667, + 6.666666666666667, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "circles[2]": { + "area": 139.62634015954634, + "bbox": [ + 13.333333333333332, + -6.666666666666667, + 0.0, + 26.666666666666668, + 6.666666666666667, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "ex16": { + "area": 167358.46173281362, + "bbox": [ + -160.0, + -80.0, + -150.0, + 160.0, + 0.0, + 150.0000000000002 + ], + "edges": 195, + "faces": 75, + "volume": 1297854.8727899147 + }, + "ex16_single": { + "area": 33471.69234656273, + "bbox": [ + -40.0, + -80.0, + -30.00000000000021, + 40.0, + 0.0, + 30.0 + ], + "edges": 39, + "faces": 15, + "volume": 259570.97455798293 + }, + "objs[0]": { + "area": 33471.69234656273, + "bbox": [ + -160.0, + -80.0, + -30.00000000000021, + -80.0, + 0.0, + 30.0 + ], + "edges": 39, + "faces": 15, + "volume": 259570.97455798293 + }, + "objs[1]": { + "area": 33471.69234656273, + "bbox": [ + -40.0, + -80.0, + -150.0, + 40.0, + 0.0, + -89.99999999999979 + ], + "edges": 39, + "faces": 15, + "volume": 259570.97455798293 + }, + "objs[2]": { + "area": 33471.69234656273, + "bbox": [ + -40.0, + -80.0, + 90.0, + 40.0, + 0.0, + 150.0000000000002 + ], + "edges": 39, + "faces": 15, + "volume": 259570.97455798293 + }, + "objs[3]": { + "area": 33471.69234656273, + "bbox": [ + 80.0, + -80.0, + -30.00000000000021, + 160.0, + 0.0, + 30.0 + ], + "edges": 39, + "faces": 15, + "volume": 259570.974557983 + }, + "sk16": { + "area": 3244.6371820133463, + "bbox": [ + -40.0, + -30.00000000000021, + 0.0, + 40.0, + 30.0, + 0.0 + ], + "edges": 13, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex17": { + "shapes": { + "ex17": { + "area": 14202.247068664106, + "bbox": [ + -45.00000000000017, + -74.69694854648331, + 0.0, + 30.0, + 28.531695488854606, + 20.0 + ], + "edges": 24, + "faces": 10, + "volume": 85595.08646656378 + }, + "sk17": { + "area": 2139.8771616640956, + "bbox": [ + -24.270509831248425, + -28.53169548885461, + 0.0, + 30.0, + 28.531695488854606, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex18": { + "shapes": { + "ex18": { + "area": 11829.753883596593, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.000000000000014 + ], + "edges": 48, + "faces": 18, + "volume": 44706.902289440506 + }, + "sk18": { + "area": 99.99999999999999, + "bbox": [ + -5.0, + -5.0, + -5.0, + 5.0, + 5.0, + -5.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex19": { + "shapes": { + "ex19": { + "area": 10786.261542700253, + "bbox": [ + -36.03875471609677, + -38.99711648727295, + 0.0, + 35.66116260882442, + 38.99711648727295, + 10.0 + ], + "edges": 27, + "faces": 11, + "volume": 41538.56826564553 + }, + "ex19_sk": { + "area": 4378.256301820968, + "bbox": [ + -36.03875471609677, + -38.99711648727295, + 0.0, + 40.0, + 38.99711648727295, + 0.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "ex19_sk2": { + "area": 628.3185307179585, + "bbox": [ + -46.03875471609677, + -27.35534956470232, + 0.0, + 50.0, + 10.0, + 0.0 + ], + "edges": 2, + "faces": 2, + "volume": 0.0 + }, + "topf": { + "area": 4378.256301820968, + "bbox": [ + -36.03875471609677, + -38.99711648727295, + 10.0, + 40.0, + 38.99711648727295, + 10.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + }, + "vtx": { + "area": 0.0, + "bbox": [ + 40.0, + 0.0, + 10.0, + 40.0, + 0.0, + 10.0 + ], + "edges": 0, + "faces": 0, + "volume": 0.0 + }, + "vtx2": { + "area": 0.0, + "bbox": [ + -36.03875471609677, + -17.35534956470232, + 10.0, + -36.03875471609677, + -17.35534956470232, + 10.0 + ], + "edges": 0, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex20": { + "shapes": { + "ex20": { + "area": 22453.096491487337, + "bbox": [ + -120.0, + -30.0, + -20.0, + 40.0, + 30.0, + 20.0 + ], + "edges": 15, + "faces": 9, + "volume": 123398.22368615503 + }, + "sk20": { + "area": 1256.637061435917, + "bbox": [ + -60.0, + -20.0, + -20.0, + -60.0, + 20.0, + 20.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex21": { + "shapes": { + "ex21": { + "area": 3805.5306333206363, + "bbox": [ + -60.0, + -5.0, + 0.0, + 5.0, + 5.0, + 60.0 + ], + "edges": 8, + "faces": 5, + "volume": 9091.44462742916 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex22": { + "shapes": { + "ex22": { + "area": 13133.117581821689, + "bbox": [ + -40.0, + -30.0, + -5.000000000000014, + 40.0, + 30.0, + 5.0 + ], + "edges": 24, + "faces": 10, + "volume": 46778.13736363046 + }, + "holes": { + "area": 78.5398163397448, + "bbox": [ + -8.03484512108174, + -10.000000000000007, + -14.575555538987226, + 8.03484512108174, + 9.999999999999993, + 4.575555538987225 + ], + "edges": 4, + "faces": 4, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex23": { + "shapes": { + "ex23": { + "area": 12794.096414158565, + "bbox": [ + -25.0, + -25.0, + 0.0, + 25.0, + 25.0, + 60.0 + ], + "edges": 12, + "faces": 7, + "volume": 88619.09277001217 + }, + "l1": { + "area": 0.0, + "bbox": [ + -25.0, + 0.0, + 0.0, + -15.0, + 35.0, + 0.0 + ], + "edges": 5, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + -25.0, + 35.0, + 0.0, + -15.0, + 35.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "sk23": { + "area": 1154.467948621305, + "bbox": [ + -25.0, + 0.0, + 0.0, + 1.7763568394002505e-15, + 0.0, + 60.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex24": { + "shapes": { + "ex24": { + "area": 18620.854281369207, + "bbox": [ + -40.0, + -40.0, + -5.0, + 40.0, + 40.0, + 45.0000001 + ], + "edges": 27, + "faces": 12, + "volume": 102969.87958520795 + }, + "faces": { + "area": 2367.354775886075, + "bbox": [ + -26.666666666666668, + -26.666666666666668, + 5.0, + 26.666666666666668, + 26.666666666666668, + 45.0 + ], + "edges": 5, + "faces": 2, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex25": { + "shapes": { + "ex25": { + "area": 49792.34944995208, + "bbox": [ + -50.45084971874738, + -59.30853086060715, + 0.0, + 62.3606797749979, + 59.308530860607135, + 31.000000000000007 + ], + "edges": 60, + "faces": 26, + "volume": 24387.59273282052 + }, + "sk25": { + "area": 24387.59273282052, + "bbox": [ + -50.45084971874738, + -59.30853086060715, + 0.0, + 62.3606797749979, + 59.308530860607135, + 30.000000000000007 + ], + "edges": 20, + "faces": 3, + "volume": 0.0 + }, + "sk25_1": { + "area": 5944.103226844711, + "bbox": [ + -40.45084971874738, + -47.55282581475768, + 0.0, + 50.0, + 47.552825814757675, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "sk25_2": { + "area": 9197.188753666056, + "bbox": [ + -50.45084971874738, + -57.55282581475768, + 15.000000000000004, + 60.0, + 57.552825814757675, + 15.000000000000004 + ], + "edges": 10, + "faces": 1, + "volume": 0.0 + }, + "sk25_3": { + "area": 9246.300752309755, + "bbox": [ + -50.45084971874738, + -59.30853086060715, + 30.000000000000007, + 62.3606797749979, + 59.308530860607135, + 30.000000000000007 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex26": { + "shapes": { + "ex26": { + "area": 14511.999999999996, + "bbox": [ + -40.0, + -30.0, + -5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 24, + "faces": 11, + "volume": 13952.0 + }, + "topf": { + "area": 4799.999999999999, + "bbox": [ + -40.0, + -30.0, + 5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex27": { + "shapes": { + "ex27": { + "area": 6464.380550980764, + "bbox": [ + -40.0, + -8.942397556322032e-15, + -5.0, + 40.0, + 30.0, + 5.0 + ], + "edges": 18, + "faces": 8, + "volume": 20465.708264711477 + }, + "sk27": { + "area": 706.8583470577034, + "bbox": [ + -15.0, + -15.000000000000007, + -5.0, + 15.0, + 14.999999999999993, + -5.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex28": { + "shapes": { + "ex28": { + "area": 26010.669925071885, + "bbox": [ + -40.00000009999814, + -40.0, + -40.0, + 40.0000001, + 40.0, + 40.0 + ], + "edges": 27, + "faces": 7, + "volume": 251188.1957196978 + }, + "sk28": { + "area": 519.6152422706632, + "bbox": [ + -10.000000000000009, + -17.320508075688767, + 0.0, + 20.0, + 17.320508075688775, + 0.0 + ], + "edges": 3, + "faces": 1, + "volume": 0.0 + }, + "tmp28": { + "area": 2078.4609690826524, + "bbox": [ + -10.000000000000009, + -17.320508075688767, + 0.0, + 20.0, + 17.320508075688775, + 10.0 + ], + "edges": 9, + "faces": 5, + "volume": 5196.152422706632 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex29": { + "shapes": { + "ex29": { + "area": 35368.52231923416, + "bbox": [ + -4.440892098500626e-16, + -18.0, + -90.9, + 60.0, + 18.0, + 8.0 + ], + "edges": 104, + "faces": 56, + "volume": 15893.314536718119 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 0.0, + 9.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 0.0, + 9.0, + 0.0, + 60.0, + 18.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 60.0, + 0.0, + 0.0, + 60.0, + 9.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "ln29": { + "area": 0.0, + "bbox": [ + 0.0, + -18.0, + 0.0, + 60.0, + 18.0, + 0.0 + ], + "edges": 4, + "faces": 0, + "volume": 0.0 + }, + "neck": { + "area": 254.46900494077323, + "bbox": [ + 21.000000000000004, + -8.999999999999998, + 0.0, + 39.0, + 9.000000000000002, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "necktopf": { + "area": 254.46900494077323, + "bbox": [ + 21.000000000000004, + -8.999999999999998, + 8.0, + 39.0, + 9.000000000000002, + 8.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "sk29": { + "area": 1812.7981751915386, + "bbox": [ + 0.0, + -18.0, + 0.0, + 60.0, + 18.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex30": { + "shapes": { + "ex30": { + "area": 19585.35478229689, + "bbox": [ + -60.0000001, + -40.0000001, + -10.0000001, + 100.00000010000053, + 100.0000001, + 1e-07 + ], + "edges": 21, + "faces": 9, + "volume": 64463.381199800904 + }, + "ex30_ln": { + "area": 0.0, + "bbox": [ + -60.0, + -40.0, + -1e-07, + 100.00000010000053, + 100.0, + 1e-07 + ], + "edges": 7, + "faces": 0, + "volume": 0.0 + }, + "ex30_sk": { + "area": 6446.340125697501, + "bbox": [ + -60.0000001, + -40.0000001, + -1e-07, + 100.00000010000053, + 100.0000001, + 1e-07 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex31": { + "shapes": { + "ex31": { + "area": 5977.711776549694, + "bbox": [ + -52.5, + -49.21633369868303, + 0.0, + 52.5, + 49.21633369868303, + 3.0 + ], + "edges": 306, + "faces": 164, + "volume": 4991.970032881472 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex32": { + "shapes": { + "ex32": { + "area": 8501.690401232394, + "bbox": [ + -50.0, + -43.30127018922193, + 0.0, + 50.0, + 43.30127018922194, + 19.0 + ], + "edges": 90, + "faces": 44, + "volume": 14839.230484541325 + }, + "ex32_sk": { + "area": 2239.2304845413264, + "bbox": [ + -50.0, + -43.30127018922193, + 0.0, + 50.0, + 43.30127018922194, + 0.0 + ], + "edges": 30, + "faces": 7, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex33": { + "shapes": { + "ex33": { + "area": 5112.447327281724, + "bbox": [ + -51.0, + -47.63139720814412, + 0.0, + 45.0, + 42.4352447854375, + 11.0 + ], + "edges": 72, + "faces": 36, + "volume": 10840.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex34": { + "shapes": { + "ex34": { + "area": 15255.980977205254, + "bbox": [ + -40.0000001, + -30.0000001, + -5.0, + 40.0000001, + 30.0000001, + 9.0000001 + ], + "edges": 363, + "faces": 137, + "volume": 47754.582611832375 + }, + "ex34_sk": { + "area": 335.25362582753087, + "bbox": [ + -25.912516326041665, + -7.047040635392934e-15, + 4.9999999, + 25.91251632604167, + 18.80004892812499, + 5.0000001 + ], + "edges": 53, + "faces": 5, + "volume": 0.0 + }, + "ex34_sk2": { + "area": 396.6079728694424, + "bbox": [ + -30.762491911979165, + -18.800049028125006, + 4.9999999, + 30.76249191197917, + -8.274037104434226e-15, + 5.0000001 + ], + "edges": 64, + "faces": 5, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex35": { + "shapes": { + "ex35": { + "area": 16471.23889803847, + "bbox": [ + -40.0, + -40.0, + -5.0, + 40.0, + 40.0, + 5.0 + ], + "edges": 48, + "faces": 18, + "volume": 49219.02754903829 + }, + "ex35_ln": { + "area": 0.0, + "bbox": [ + -29.999999999999993, + 7.888609052210118e-31, + 0.0, + 8.27565060379343e-16, + 29.999999999999993, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "ex35_ln2": { + "area": 0.0, + "bbox": [ + 7.888609052210118e-31, + -29.999999999999993, + 0.0, + 29.999999999999993, + 8.27565060379343e-16, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "ex35_sk": { + "area": 1478.0972450961729, + "bbox": [ + -34.999999999999986, + -34.999999999999986, + 0.0, + 34.999999999999986, + 34.999999999999986, + 0.0 + ], + "edges": 12, + "faces": 3, + "volume": 0.0 + } + }, + "status": "ok" + }, + "general_examples_algebra/ex36": { + "shapes": { + "ex36": { + "area": 10787.997618106452, + "bbox": [ + -6.0000001, + -56.0000001, + -1e-07, + 6.0000001, + 56.0000001, + 56.0000001 + ], + "edges": 15, + "faces": 8, + "volume": 30298.935241110394 + }, + "ex36_sk": { + "area": 113.09733552923255, + "bbox": [ + -6.0, + 44.0, + 0.0, + 6.0, + 56.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "ex36_sk2": { + "area": 300.0, + "bbox": [ + -3.0, + -25.0, + 0.0, + 3.0, + 25.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "ttt/ttt-23-02-02-sm_hanger": { + "shapes": { + "bottom_edge": { + "area": 0.0, + "bbox": [ + 55.0, + 47.512774239600375, + 44.28756974082995, + 55.0, + 51.37647754475615, + 45.32284592124189 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "fe": { + "area": 0.0, + "bbox": [ + 84.99999999999999, + 56.26, + 0.0, + 84.99999999999999, + 56.26, + 4.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "h1": { + "area": 4273.128283793375, + "bbox": [ + 20.0, + -15.0, + 0.0, + 241.0, + 15.0, + 0.0 + ], + "edges": 10, + "faces": 2, + "volume": 0.0 + }, + "h2": { + "area": 4434.1592653589805, + "bbox": [ + 93.0, + -10.0, + 0.0, + 319.0, + 10.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 65.0, + 0.0, + 46.104, + 65.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 85.0, + 0.0, + 0.0, + 122.52776749734468, + 0.0, + 65.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 85.0, + 0.0, + 0.0, + 122.52776749734468, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "side": { + "area": 18409.469311640918, + "bbox": [ + -1.0000000036739404e-07, + -1.0000002842170943e-07, + -1e-07, + 117.40341194435948, + 56.2600001, + 65.00000010000015 + ], + "edges": 41, + "faces": 17, + "volume": 33159.911280247536 + }, + "side_line": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 117.40341184435948, + 0.0, + 65.0000000000003 + ], + "edges": 5, + "faces": 0, + "volume": 0.0 + }, + "sm_hanger": { + "area": 73437.84063364491, + "bbox": [ + -117.40341194435948, + -56.2600001, + -1e-07, + 117.40341194435948, + 56.2600001, + 88.0 + ], + "edges": 347, + "faces": 121, + "volume": 131756.34943954204 + }, + "tab": { + "area": 681.4734305929795, + "bbox": [ + 20.0, + -1.2246467991473533e-15, + 61.0, + 27.999999999395058, + 8.0, + 88.0 + ], + "edges": 33, + "faces": 13, + "volume": 744.7875958452756 + }, + "tab_line": { + "area": 0.0, + "bbox": [ + 20.0, + 0.0, + 61.0, + 28.0, + 0.0, + 88.0 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + }, + "wing": { + "area": 7755.719418127756, + "bbox": [ + -1.0000002131628207e-07, + -1e-07, + 44.287569640829936, + 55.0000001, + 51.37647764475615, + 65.00000014936666 + ], + "edges": 27, + "faces": 11, + "volume": 13659.030992631924 + }, + "wing_line": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 45.32284592124189, + 0.0, + 51.37647754475614, + 65.00000009873332 + ], + "edges": 3, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "ttt/ttt-23-t-24-curved_support": { + "shapes": { + "base_hull": { + "area": 6880.426598184431, + "bbox": [ + -27.5, + -27.5, + 0.0, + 27.5, + 140.0, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "base_plan": { + "area": 3082.6877913349845, + "bbox": [ + -27.5, + -27.5, + 0.0, + 27.5, + 140.0, + 0.0 + ], + "edges": 2, + "faces": 2, + "volume": 0.0 + }, + "bridge": { + "area": 5179.877817107864, + "bbox": [ + 0.0, + 0.0, + 0.0, + 125.0, + 50.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "c_8_degrees": { + "area": 2375.829444277281, + "bbox": [ + -27.5, + -27.5, + 0.0, + 27.5, + 27.5, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "curved_support": { + "area": 38781.015174540895, + "bbox": [ + -27.5, + -27.5, + 0.0, + 27.5, + 140.0, + 60.0 + ], + "edges": 47, + "faces": 18, + "volume": 165914.0718803271 + }, + "l1": { + "area": 0.0, + "bbox": [ + 27.5, + 46.321316523235964, + 0.0, + 53.675193028802, + 50.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 53.675193028802, + 42.046626191929846, + 0.0, + 65.41051915338532, + 46.321316523235964, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 65.41051915338532, + 32.0, + 0.0, + 100.41366129083305, + 42.046626191929846, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 125.0, + 50.0, + 0.0 + ], + "edges": 5, + "faces": 0, + "volume": 0.0 + }, + "profile": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 125.0, + 50.0, + 0.0 + ], + "edges": 8, + "faces": 0, + "volume": 0.0 + } + }, + "status": "ok" + }, + "ttt/ttt-24-SPO-06-Buffer_Stand": { + "shapes": { + "circle_edge": { + "area": 0.0, + "bbox": [ + -0.9159111568790768, + 3.178643013542057, + 0.0, + 0.9159111568790766, + 4.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "internals": { + "area": 39.42266724894786, + "bbox": [ + 0.0, + -1.223723941183581, + 0.2499999999999991, + 2.125, + 1.223723941183581, + 4.0 + ], + "edges": 21, + "faces": 9, + "volume": 15.12561291741207 + }, + "l1": { + "area": 0.0, + "bbox": [ + 2.5, + -1.2500000000000002, + 0.0, + 2.75, + 1.2500000000000009, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "p": { + "area": 80.19898864180658, + "bbox": [ + -2.7500001172116475, + -1.2500001000000005, + -1.0000000532907051e-07, + 2.7500001172116475, + 1.250000100000001, + 4.0000001 + ], + "edges": 72, + "faces": 28, + "volume": 13.921380784973927 + }, + "part": { + "area": 51741.17951214792, + "bbox": [ + -69.85000053717586, + -31.750000100000012, + -1.0000011368683772e-07, + 69.85000053717586, + 31.75000010000002, + 101.60000009999999 + ], + "edges": 72, + "faces": 28, + "volume": 228130.55789173767 + }, + "rib": { + "area": 0.3545854840344225, + "bbox": [ + -0.25, + 0.25, + 0.0, + 0.25, + 1.25, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "xy": { + "area": 6.669981201828734, + "bbox": [ + 0.0, + -1.2500000000000002, + 0.0, + 2.75, + 1.2500000000000009, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "xz": { + "area": 31.660398142333932, + "bbox": [ + -2.1250000000000107, + 0.25, + 0.0, + 2.1250000000000107, + 7.75, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "yz": { + "area": 8.318332235749178, + "bbox": [ + -1.25, + 0.0, + 0.0, + 1.25, + 4.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "ttt/ttt-ppp0101": { + "shapes": { + "bl": { + "area": 0.0, + "bbox": [ + -13.61880215351701, + 0.0, + 0.0, + 13.618802153517008, + 8.0, + 0.0 + ], + "edges": 6, + "faces": 0, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 0.0, + 0.0, + 0.0, + 9.0, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + 9.0, + 0.0, + 0.0, + 13.618802153517008, + 8.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + 0.0, + 8.0, + 0.0, + 13.618802153517008, + 8.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "p": { + "area": 30243.24648019587, + "bbox": [ + -57.5000001, + -25.00000010000011, + -1e-07, + 57.5, + 25.0000001, + 68.0000001 + ], + "edges": 84, + "faces": 32, + "volume": 102198.22251481404 + }, + "s": { + "area": 4700.902664470767, + "bbox": [ + -57.5, + -25.0, + 0.0, + 57.5, + 25.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + }, + "s3": { + "area": 4931.716633826699, + "bbox": [ + -57.50000000000001, + -38.0, + 0.0, + -5.499999999999993, + 68.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "s4": { + "area": 180.9504172281361, + "bbox": [ + -13.61880215351701, + 0.0, + 0.0, + 13.618802153517008, + 8.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "zz": { + "area": 13119.78708349343, + "bbox": [ + -57.50000000000001, + -25.0, + -38.0, + -5.499999999999993, + -13.0, + 68.0 + ], + "edges": 12, + "faces": 6, + "volume": 59180.599605920404 + } + }, + "status": "ok" + }, + "ttt/ttt-ppp0102": { + "shapes": { + "l1": { + "area": 0.0, + "bbox": [ + -32.0, + 0.0, + 3.0, + -14.999999999999998, + 0.0, + 37.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "lc1": { + "area": 0.0, + "bbox": [ + 18.41270795809011, + 0.0, + 0.0, + 21.0, + 37.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "p": { + "area": 15159.916261715422, + "bbox": [ + -34.0000001, + -24.5000001, + -1e-07, + 24.5000001, + 24.5000001, + 48.0000001 + ], + "edges": 17, + "faces": 10, + "volume": 42248.61825268254 + }, + "sk1": { + "area": 1141.6637061435918, + "bbox": [ + 0.0, + 0.0, + 0.0, + 24.5, + 48.0, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "sk2": { + "area": 727.4968856546463, + "bbox": [ + 0.0, + 0.0, + 0.0, + 21.0, + 37.00000000000001, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "xc1": { + "area": 31.41592653589793, + "bbox": [ + -5.0, + 1.0, + 0.0, + 5.0, + 5.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "ttt/ttt-ppp0103": { + "shapes": { + "cyl1": { + "area": 201.06192982974667, + "bbox": [ + -8.0, + 0.0, + 0.0, + 8.0, + 16.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "cyl2": { + "area": 201.06192982974667, + "bbox": [ + -8.0, + 0.0, + 0.0, + 8.0, + 16.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "ppp0103": { + "area": 10058.45491355111, + "bbox": [ + -8.0, + -56.5, + 0.0, + 34.000000000000014, + 47.5, + 16.0 + ], + "edges": 47, + "faces": 18, + "volume": 35605.546935185695 + }, + "sk1": { + "area": 1977.9689891987991, + "bbox": [ + 0.0, + -47.5, + 0.0, + 34.000000000000014, + 47.5, + 0.0 + ], + "edges": 12, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "ttt/ttt-ppp0104": { + "shapes": { + "p": { + "area": 12314.23805633903, + "bbox": [ + -19.0, + -19.0000001, + -23.0000001, + 61.0000001, + 19.0000001, + 28.0 + ], + "edges": 62, + "faces": 23, + "volume": 39743.211180667735 + }, + "s": { + "area": 1134.1149479459152, + "bbox": [ + -19.0, + -19.0, + 0.0, + 19.0, + 19.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s2": { + "area": 530.929158456675, + "bbox": [ + -13.0, + -13.0, + 0.0, + 13.0, + 13.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s3": { + "area": 371.0, + "bbox": [ + -26.5, + 0.0, + 0.0, + 26.5, + 7.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "s3a": { + "area": 225.0, + "bbox": [ + -26.5, + 0.0, + 0.0, + 26.5, + 15.0, + 0.0 + ], + "edges": 8, + "faces": 2, + "volume": 0.0 + }, + "s4": { + "area": 201.06192982974667, + "bbox": [ + -8.0, + -8.0, + 0.0, + 8.0, + 8.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s5": { + "area": 379.99999999999994, + "bbox": [ + 51.0, + -19.0, + 0.0, + 61.0, + 19.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "s6": { + "area": 542.4690049407732, + "bbox": [ + -9.000000000000002, + -40.0, + 0.0, + 9.000000000000002, + -6.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "s6b": { + "area": 305.0973355292325, + "bbox": [ + -6.000000000000002, + -37.0, + 0.0, + 6.000000000000002, + -9.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "ttt/ttt-ppp0105": { + "shapes": { + "p": { + "area": 38127.23996439501, + "bbox": [ + -33.0000001, + -22.000000100000012, + -30.0, + 33.0000001, + 22.00000010000003, + 103.0000001 + ], + "edges": 40, + "faces": 18, + "volume": 55617.528016135795 + }, + "s": { + "area": 1828.5308443374602, + "bbox": [ + -25.5, + -22.0, + 0.0, + 25.5, + 22.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "s2": { + "area": 638.5398163397448, + "bbox": [ + -33.0, + -5.0, + 0.0, + 33.0, + 5.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "s3": { + "area": 1400.1149479459154, + "bbox": [ + -22.5, + -19.0, + 0.0, + 22.5, + 19.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "s4": { + "area": 236.56637061435916, + "bbox": [ + -30.0, + -2.0, + 0.0, + 30.0, + 2.0, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "ttt/ttt-ppp0106": { + "shapes": { + "c1": { + "area": 0.0, + "bbox": [ + 15.0, + 0.0, + 0.0, + 15.0, + 69.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l": { + "area": 0.0, + "bbox": [ + -32.0, + -5.329070518200751e-15, + 0.0, + 32.0, + 69.0, + 0.0 + ], + "edges": 10, + "faces": 0, + "volume": 0.0 + }, + "m1": { + "area": 0.0, + "bbox": [ + 0.0, + 69.0, + 0.0, + 22.0, + 69.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "m2": { + "area": 0.0, + "bbox": [ + 22.0, + 51.928932188134524, + 0.0, + 32.0, + 69.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "m3": { + "area": 0.0, + "bbox": [ + 14.999999999999998, + 37.85786437626904, + 0.0, + 29.071067811865476, + 51.928932188134524, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "m4": { + "area": 0.0, + "bbox": [ + 14.999999999999998, + 15.0, + 0.0, + 15.0, + 37.85786437626904, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "m5": { + "area": 0.0, + "bbox": [ + 4.970762342300593e-15, + -5.329070518200751e-15, + 0.0, + 14.999999999999998, + 15.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "p": { + "area": 15572.097246681229, + "bbox": [ + -32.0, + -15.000000000000005, + -25.0, + 32.0, + 54.0, + 11.0000001 + ], + "edges": 89, + "faces": 32, + "volume": 42053.82765929348 + }, + "sk_body": { + "area": 2311.2206145214886, + "bbox": [ + -32.0, + -3.552713678800501e-15, + 0.0, + 32.0, + 69.0, + 0.0 + ], + "edges": 16, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "ttt/ttt-ppp0107": { + "shapes": { + "p": { + "area": 42918.896751501714, + "bbox": [ + -65.0, + -65.0, + 0.0, + 65.0, + 65.0, + 52.0000001 + ], + "edges": 95, + "faces": 44, + "volume": 138137.45529650067 + }, + "pln2": { + "area": 4185.386812745002, + "bbox": [ + -36.5, + -36.5, + 18.0, + 36.5, + 36.5, + 18.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s": { + "area": 13273.228961416879, + "bbox": [ + -65.0, + -65.0, + 0.0, + 65.0, + 65.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s2": { + "area": 5541.769440932394, + "bbox": [ + -42.0, + -42.0, + 0.0, + 42.0, + 42.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s3": { + "area": 962.1127501618739, + "bbox": [ + -17.5, + -17.5, + 0.0, + 17.5, + 17.5, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s4": { + "area": 4185.386812745002, + "bbox": [ + -36.5, + -36.5, + 0.0, + 36.5, + 36.5, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s5": { + "area": 314.15926535897927, + "bbox": [ + -10.0, + -10.0, + 0.0, + 10.0, + 10.0, + 0.0 + ], + "edges": 1, + "faces": 1, + "volume": 0.0 + }, + "s6": { + "area": 95.598, + "bbox": [ + -15.933, + -1.5, + 0.0, + 15.933, + 1.5, + 0.0 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "zz": { + "area": 1626.8709042392347, + "bbox": [ + 1.7424742890653342, + -4.144904810626978, + 9.742474289065331, + 36.75197095263065, + 4.144904810626971, + 44.75197095263065 + ], + "edges": 12, + "faces": 6, + "volume": 2957.1391331767377 + }, + "zz2": { + "area": 1081.4162159863508, + "bbox": [ + 1.7424742890653342, + -4.14490481062698, + 24.9999999, + 33.97127711597077, + 4.144904810626972, + 44.75197095263065 + ], + "edges": 12, + "faces": 6, + "volume": 1760.459473189193 + }, + "zz3": { + "area": 565.0236841850011, + "bbox": [ + 16.499999899999995, + -3.678353926686466, + 24.999999899999995, + 33.97127711597077, + 3.6783539266864587, + 42.47127711597077 + ], + "edges": 9, + "faces": 5, + "volume": 679.5125304414706 + } + }, + "status": "ok" + }, + "ttt/ttt-ppp0108": { + "shapes": { + "l1": { + "area": 0.0, + "bbox": [ + 45.0, + -19.0, + 0.0, + 125.0, + 11.0, + 0.0 + ], + "edges": 5, + "faces": 0, + "volume": 0.0 + }, + "p": { + "area": 80718.19778560844, + "bbox": [ + -125.0, + -95.0, + -19.000000000000004, + 125.0, + 95.0, + 16.0 + ], + "edges": 101, + "faces": 37, + "volume": 434238.2673538104 + }, + "p2": { + "area": 15726.698930910648, + "bbox": [ + -125.0, + -10.0, + -19.000000000000004, + 125.0, + 10.0, + 11.0 + ], + "edges": 56, + "faces": 24, + "volume": 57318.67288915634 + }, + "s1": { + "area": 24244.974654040903, + "bbox": [ + -94.00000000000001, + -95.0, + 0.0, + 94.00000000000001, + 95.0, + 0.0 + ], + "edges": 13, + "faces": 1, + "volume": 0.0 + }, + "s2": { + "area": 1924.9668222289083, + "bbox": [ + 45.0, + -19.0, + 0.0, + 125.0, + 11.0, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "s3": { + "area": 1119.9999999999998, + "bbox": [ + 45.0, + -10.0, + 0.0, + 125.0, + 10.0, + 0.0 + ], + "edges": 8, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "ttt/ttt-ppp0109": { + "shapes": { + "bl": { + "area": 0.0, + "bbox": [ + -37.5, + 0.0, + 0.0, + 37.5, + 54.131421435130896, + 0.0 + ], + "edges": 8, + "faces": 0, + "volume": 0.0 + }, + "c": { + "area": 0.0, + "bbox": [ + 37.5, + 0.0, + 0.0, + 37.5, + 60.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "f": { + "area": 450.00000000000006, + "bbox": [ + 0.0, + -37.5, + 0.0, + 4.242640687119286, + 37.5, + 4.242640687119286 + ], + "edges": 4, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + 11.90030010436846, + 20.76924299402401, + 0.0, + 37.49999999999999, + 54.131421435130896, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "one": { + "area": 5050.960138443726, + "bbox": [ + -69.00000000000003, + -37.5, + 0.0, + 0.0, + 37.5, + 0.0 + ], + "edges": 6, + "faces": 1, + "volume": 0.0 + }, + "ppp109": { + "area": 27962.4888083657, + "bbox": [ + -69.00000000000003, + -37.5, + -45.0, + 49.242640687119284, + 37.5, + 60.0 + ], + "edges": 60, + "faces": 24, + "volume": 113789.2638826812 + }, + "three": { + "area": 3314.104507624698, + "bbox": [ + 0.0, + -37.5, + 0.0, + 63.63961030678927, + 37.5, + 0.0 + ], + "edges": 5, + "faces": 1, + "volume": 0.0 + }, + "two": { + "area": 3190.197878583049, + "bbox": [ + -37.5, + 0.0, + 0.0, + 37.5, + 60.0, + 0.0 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + }, + "ttt/ttt-ppp0110": { + "shapes": { + "cross_section": { + "area": 1236.2988836335278, + "bbox": [ + -42.0, + 0.0, + -2.220446049250313e-16, + 42.0, + 0.0, + 45.99999999999999 + ], + "edges": 10, + "faces": 1, + "volume": 0.0 + }, + "l1": { + "area": 0.0, + "bbox": [ + -42.0, + 0.0, + 0.0, + -39.21895141649746, + 13.865993248815562, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l2": { + "area": 0.0, + "bbox": [ + -39.21895141649746, + 13.865993248815563, + 0.0, + 8.881784197001252e-16, + 45.99999999999999, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l3": { + "area": 0.0, + "bbox": [ + -34.15620971670051, + -1.5731986497631094, + 0.0, + -2.5757174171303632e-14, + 37.99999999999999, + 0.0 + ], + "edges": 2, + "faces": 0, + "volume": 0.0 + }, + "l4": { + "area": 0.0, + "bbox": [ + -42.0, + -1.5731986497631094, + 0.0, + -34.15620971670051, + 0.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l5": { + "area": 0.0, + "bbox": [ + -3.375077994860476e-14, + 37.99999999999999, + 0.0, + 8.881784197001252e-16, + 45.99999999999999, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l6": { + "area": 0.0, + "bbox": [ + 0.0, + 30.0, + 0.0, + 8.881784197001252e-16, + 45.99999999999999, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l7": { + "area": 0.0, + "bbox": [ + -21.166010488516733, + 30.0, + 0.0, + 0.0, + 30.0, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "l8": { + "area": 0.0, + "bbox": [ + -21.166010488516726, + 30.0, + 0.0, + -4.440892098500626e-16, + 45.99999999999999, + 0.0 + ], + "edges": 1, + "faces": 0, + "volume": 0.0 + }, + "outer": { + "area": 21477.794503244477, + "bbox": [ + -42.0, + -42.0, + -1.5731986497631094, + 42.0, + 42.0, + 45.99999999999999 + ], + "edges": 10, + "faces": 6, + "volume": 84526.45673285302 + }, + "p": { + "area": 47394.61329450654, + "bbox": [ + -42.0, + -142.0, + -1.1102230246251565e-15, + 42.0, + 42.0, + 45.99999999999999 + ], + "edges": 44, + "faces": 21, + "volume": 207159.36406587544 + }, + "positive_Z": { + "area": 60000.0, + "bbox": [ + -50.0, + 0.0, + 0.0, + 50.0, + 100.0, + 100.0 + ], + "edges": 12, + "faces": 6, + "volume": 999999.9999999999 + }, + "ppp0110": { + "area": 47394.61329450654, + "bbox": [ + -42.0, + -142.0, + -1.1102230246251565e-15, + 42.0, + 42.0, + 45.99999999999999 + ], + "edges": 44, + "faces": 21, + "volume": 207159.36406587544 + }, + "sk": { + "area": 618.1494418167646, + "bbox": [ + -42.0, + 0.0, + -2.220446049250313e-16, + 8.881784197001252e-16, + 0.0, + 45.99999999999999 + ], + "edges": 7, + "faces": 1, + "volume": 0.0 + } + }, + "status": "ok" + } +} \ No newline at end of file diff --git a/test/b123d-validation/reference.py b/test/b123d-validation/reference.py new file mode 100644 index 00000000..3ec89235 --- /dev/null +++ b/test/b123d-validation/reference.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 +"""reference.py - run every manifest script under REAL build123d and record +ground-truth measurements. + +For each script (child subprocess, 60s timeout, scratch cwd): + * exec() the code with show/show_object/show_all stubbed out, + * find every module-level shape result: + - build123d Shape instances (Part/Sketch/Curve/Compound/Face/Wire/...) + - Builder instances (BuildPart/BuildSketch/BuildLine) -> their result + - lists/tuples whose elements are all Shapes (first 32, as name[i]) + * measure each: volume, bounding box (6 floats), surface area, + face count, edge count - keyed by the VARIABLE NAME so the lite run can + compare per-name instead of guessing. + +Scripts that fail natively are recorded with status "error" (their message is +kept: they are excluded from lite scoring, not silently dropped). + +Usage: + python3 reference.py [--manifest manifest.json] [--out reference.json] + [--jobs 4] [--only substring] +""" + +import argparse +import json +import os +import subprocess +import sys +import tempfile +from concurrent.futures import ThreadPoolExecutor + +HERE = os.path.dirname(os.path.abspath(__file__)) +VENV_PY = os.environ.get( + "B123D_REF_PY", + os.path.expanduser("~/Desktop/ocjs-deps/b123d-ref-venv/bin/python")) +TIMEOUT = int(os.environ.get("B123D_REF_TIMEOUT", "60")) + +# The child harness: executed by the reference venv's python. Reads the +# script source from argv[1], prints one JSON object on the last stdout line. +CHILD = r''' +import json, sys, math + +def _stub(*a, **k): pass + +def measure_shape(obj): + bb = obj.bounding_box() + return { + "volume": float(abs(obj.volume)), + "bbox": [float(bb.min.X), float(bb.min.Y), float(bb.min.Z), + float(bb.max.X), float(bb.max.Y), float(bb.max.Z)], + "area": float(obj.area), + "faces": len(obj.faces()), + "edges": len(obj.edges()), + } + +def main(): + src = open(sys.argv[1]).read() + from build123d import Shape + from build123d.build_common import Builder + + ns = { + "__name__": "main", # match the lite worker's module name + # docs selector examples resolve their STEP assets relative to + # os.path.dirname(os.path.abspath(__file__)) - the scratch cwd, where + # reference.py has symlinked them (the lite worker defines the same + # name in the user module, see PythonRuntime.js) + "__file__": sys.argv[1], + "show": _stub, "show_object": _stub, "show_all": _stub, + "set_port": _stub, "set_defaults": _stub, "set_colormap": _stub, + } + exec(compile(src, "script.py", "exec"), ns) + + shapes = {} + def add(name, obj): + try: + shapes[name] = measure_shape(obj) + except Exception as e: + shapes[name] = {"measure_error": "%s: %s" % (type(e).__name__, e)} + + for name, obj in list(ns.items()): + if name.startswith("_") or name in ("show", "show_object", "show_all", + "set_port", "set_defaults", + "set_colormap"): + continue + if isinstance(obj, Builder): + try: + result = obj._obj + except Exception: + result = None + if result is not None: + add(name, result) + elif isinstance(obj, Shape): + add(name, obj) + elif isinstance(obj, (list, tuple)) and obj and \ + all(isinstance(x, Shape) for x in obj): + # skip face-less elements (edge/vertex selector lists) and sort + # the rest by bbox center — element order frequently differs + # between build123d and lite (same rule as the lite measurer) + solids = [x for x in obj if len(x.faces()) > 0] + def bbkey(x): + bb = x.bounding_box() + return (round((bb.min.X + bb.max.X) / 2, 3), + round((bb.min.Y + bb.max.Y) / 2, 3), + round((bb.min.Z + bb.max.Z) / 2, 3)) + for i, x in enumerate(sorted(solids[:64], key=bbkey)): + add("%s[%d]" % (name, i), x) + + print("B123D_REF_JSON " + json.dumps({"shapes": shapes})) + +main() +''' + + +B123D_SRC = os.environ.get("B123D_SRC", "/tmp/b123d") + + +def _prepare_scratch(entry, sdir): + """Give the script the filesystem neighbourhood it expects. + + docs scripts write SVGs into `assets/` and open STEP assets next to + themselves (`os.path.dirname(__file__)`), so create the output directories + and symlink every non-.py sibling from the script's own source directory. + """ + for sub in ("assets", os.path.join("assets", "topology_selection"), + os.path.join("assets", "ttt")): + os.makedirs(os.path.join(sdir, sub), exist_ok=True) + reldir = entry.get("data_dir") + if not reldir: + return + absdir = os.path.join(B123D_SRC, reldir) + if not os.path.isdir(absdir): + return + for fname in os.listdir(absdir): + if fname.endswith(".py"): + continue + src = os.path.join(absdir, fname) + dst = os.path.join(sdir, fname) + if os.path.isfile(src) and not os.path.exists(dst): + try: + os.symlink(src, dst) + except OSError: + pass + + +def run_one(entry, scratch): + sid = entry["id"] + sdir = os.path.join(scratch, sid.replace("/", "_")) + os.makedirs(sdir, exist_ok=True) + _prepare_scratch(entry, sdir) + spath = os.path.join(sdir, "script.py") + with open(spath, "w") as f: + f.write(entry["code"]) + hpath = os.path.join(sdir, "harness.py") + with open(hpath, "w") as f: + f.write(CHILD) + try: + proc = subprocess.run( + [VENV_PY, hpath, spath], capture_output=True, text=True, + timeout=TIMEOUT, cwd=sdir) + except subprocess.TimeoutExpired: + return sid, {"status": "timeout"} + if proc.returncode != 0: + # last non-empty stderr line is the interesting one + lines = [l for l in proc.stderr.strip().split("\n") if l.strip()] + return sid, {"status": "error", + "error": lines[-1] if lines else "unknown"} + for line in proc.stdout.strip().split("\n")[::-1]: + if line.startswith("B123D_REF_JSON "): + data = json.loads(line[len("B123D_REF_JSON "):]) + shapes = data["shapes"] + if not shapes: + return sid, {"status": "no-shapes"} + return sid, {"status": "ok", "shapes": shapes} + return sid, {"status": "error", "error": "harness produced no JSON"} + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--manifest", default=os.path.join(HERE, "manifest.json")) + ap.add_argument("--out", default=os.path.join(HERE, "reference.json")) + ap.add_argument("--jobs", type=int, default=4) + ap.add_argument("--only", default=None, + help="only run scripts whose id contains this substring") + args = ap.parse_args() + + manifest = json.load(open(args.manifest)) + if args.only: + manifest = [e for e in manifest if args.only in e["id"]] + + results = {} + if os.path.exists(args.out) and not args.only: + pass # full runs regenerate from scratch + elif os.path.exists(args.out): + results = json.load(open(args.out)) + + with tempfile.TemporaryDirectory(prefix="b123d-ref-") as scratch: + with ThreadPoolExecutor(max_workers=args.jobs) as pool: + for sid, res in pool.map(lambda e: run_one(e, scratch), manifest): + results[sid] = res + print("%-45s %s" % (sid, res["status"])) + + with open(args.out, "w") as f: + json.dump(results, f, indent=1, sort_keys=True) + ok = sum(1 for r in results.values() if r["status"] == "ok") + print("\n%d/%d scripts measured ok -> %s" % (ok, len(results), args.out)) + + +if __name__ == "__main__": + main() diff --git a/test/b123d-validation/report.md b/test/b123d-validation/report.md new file mode 100644 index 00000000..63f69f0b --- /dev/null +++ b/test/b123d-validation/report.md @@ -0,0 +1,357 @@ +# build123d-lite validation report + +Generated 2026-08-15T13:48:59.315Z - 232 scripts (222 scored, 10 excluded because real build123d fails natively). + +| Status | Count | +|---|---| +| PASS | 205 | +| MISMATCH | 10 | +| ERROR | 5 | +| TIMEOUT | 2 | +| SKIP | 10 | + +## Feature-gap frequency (ERROR bucket) + +| Gap | Scripts | +|---|---| +| `NotImplemented: build123d-lite Mesher writes STL only (no lib3mf in the WASM build); got dual_color.3mf` | 1 | +| `other: Line 9634: Uncaught Error: Python JavascriptError: INTERNAL OPENCASCADE ERROR in FilletEdges: the OC` | 1 | +| `NameError: Draft` | 1 | +| `ImportError: undefined` | 1 | +| `RuntimeError: KNOWN OCCT 8.0.1 wasm kernel fault: fuse dropped an operand (result volume 0 < l` | 1 | + +## Mismatches (runs, but geometry differs) + +- **examples/joints** + - 'pin_arm' bbox[0] 5.3933 vs 8.0835 (d=2.6903) + - 'screw_arm' bbox[0] 1.0688 vs 3.6800 (d=2.6113) + - 'slider_arm' bbox[0] 5.3255 vs -3.7834 (d=9.1089) +- **examples/joints_algebra** + - 'pin_arm' bbox[0] 5.3933 vs 8.0835 (d=2.6903) + - 'screw_arm' bbox[0] 1.0688 vs 3.6800 (d=2.6113) + - 'slider_arm' bbox[0] 5.3255 vs -3.7834 (d=9.1089) +- **examples/projection** + - 'projected_text' bbox[0] -49.7713 vs -49.6470 (d=0.1243) +- **examples/projection_algebra** + - 'projected_text' bbox[0] -49.7713 vs -49.6470 (d=0.1243) +- **docs/objects_1d** + - 'l1' bbox[3] 5.0000 vs 0.0000 (d=5.0000) + - 'l3' bbox[0] 6.0000 vs 1.2420 (d=4.7580) + - 'l4' bbox[1] 4.5000 vs 0.0000 (d=4.5000) + - 'scene' bbox[0] -0.1000 vs -0.1700 (d=0.0700) +- **docs-selectors/filter_all_edges_circle** + - 'f' bbox[1] 21.0000 vs -21.0000 (d=42.0000) +- **docs/tutorial_joints** + - 'm6_screw' bbox[0] -155.1818 vs -157.0000 (d=1.8182) +- **docs-selectors/sort_axis** + - 'part' volume 4765.102 vs 5585.161 (-14.68%) + - 'part' bbox[3] 34.0000 vs 50.0000 (d=16.0000) +- **ttt/ttt-23-02-02-sm_hanger** + - 'l1' bbox[1] 65.0000 vs 0.0000 (d=65.0000) + - 'l2' bbox[4] 65.0000 vs 0.0000 (d=65.0000) +- **docs-rst/tips/b04** + - 'vertical_sketch' bbox[1] -0.5000 vs -0.7000 (d=0.2000) + +## Passing scripts + +- examples/boxes_on_faces (1 shapes) +- examples/boxes_on_faces_algebra (2 shapes) +- examples/build123d_customizable_logo_algebra (13 shapes) +- examples/build123d_logo (13 shapes) +- examples/build123d_customizable_logo (14 shapes) +- examples/build123d_logo_algebra (12 shapes) +- examples/canadian_flag_algebra (25 shapes) +- examples/canadian_flag (20 shapes) +- examples/circuit_board (1 shapes) +- examples/circuit_board_algebra (1 shapes) +- examples/cast_bearing_unit (10 shapes) +- examples/clock_algebra (6 shapes) +- examples/custom_sketch_objects (9 shapes) +- examples/custom_sketch_objects_algebra (9 shapes) +- examples/din_rail (3 shapes) +- examples/din_rail_algebra (41 shapes) +- examples/bicycle_tire (102 shapes) +- examples/extrude_algebra (35 shapes) +- examples/extrude (10 shapes) +- examples/handle (10 shapes) +- examples/fast_grid_holes (4 shapes) +- examples/handle_algebra (4 shapes) +- examples/bracelet (16 shapes) +- examples/holes (4 shapes) +- examples/holes_algebra (4 shapes) +- examples/intersecting_chamfers (1 shapes) +- examples/intersecting_chamfers_algebra (2 shapes) +- examples/intersecting_pipes (3 shapes) +- examples/clock (5 shapes) +- examples/key_cap (5 shapes) +- examples/key_cap_algebra (5 shapes) +- examples/lego (3 shapes) +- examples/lego_algebra (3 shapes) +- examples/loft (4 shapes) +- examples/loft_algebra (3 shapes) +- examples/mixed_algebra_context (9 shapes) +- examples/multiple_workplanes (1 shapes) +- examples/multiple_workplanes_algebra (1 shapes) +- examples/packed_boxes (100 shapes) +- examples/pegboard_j_hook (10 shapes) +- examples/pegboard_j_hook_algebra (11 shapes) +- examples/pillow_block (2 shapes) +- examples/heat_exchanger_algebra (4 shapes) +- examples/pillow_block_algebra (2 shapes) +- examples/platonic_solids (5 shapes) +- examples/maker_coin (8 shapes) +- examples/playing_cards (14 shapes) +- examples/roller_coaster (4 shapes) +- examples/roller_coaster_algebra (4 shapes) +- examples/shamrock (1 shapes) +- examples/stud_wall (2 shapes) +- examples/tea_cup_algebra (5 shapes) +- examples/twist_extrude (2 shapes) +- examples/vase (8 shapes) +- examples/vase_algebra (8 shapes) +- general_examples/ex01 (1 shapes) +- general_examples/ex02 (1 shapes) +- general_examples/ex03 (2 shapes) +- general_examples/ex08 (3 shapes) +- general_examples/ex09 (1 shapes) +- general_examples/ex10 (1 shapes) +- general_examples/ex11 (2 shapes) +- general_examples/ex12 (7 shapes) +- general_examples/ex13 (1 shapes) +- general_examples/ex14 (6 shapes) +- general_examples/ex15 (8 shapes) +- general_examples/ex16 (3 shapes) +- general_examples/ex17 (2 shapes) +- general_examples/ex18 (1 shapes) +- general_examples/ex19 (6 shapes) +- general_examples/ex20 (1 shapes) +- general_examples/ex21 (2 shapes) +- general_examples/ex22 (2 shapes) +- general_examples/ex23 (5 shapes) +- general_examples/ex24 (3 shapes) +- general_examples/ex25 (4 shapes) +- general_examples/ex26 (2 shapes) +- general_examples/ex27 (2 shapes) +- general_examples/ex28 (7 shapes) +- general_examples/ex29 (7 shapes) +- general_examples/ex30 (5 shapes) +- general_examples/ex31 (2 shapes) +- general_examples/ex32 (3 shapes) +- general_examples/ex33 (3 shapes) +- general_examples/ex35 (5 shapes) +- general_examples/ex36 (3 shapes) +- general_examples/ex37 (2 shapes) +- general_examples_algebra/ex01 (1 shapes) +- general_examples_algebra/ex02 (1 shapes) +- general_examples/ex34 (4 shapes) +- general_examples_algebra/ex03 (2 shapes) +- general_examples_algebra/ex08 (3 shapes) +- general_examples_algebra/ex09 (1 shapes) +- general_examples_algebra/ex12 (6 shapes) +- general_examples_algebra/ex14 (6 shapes) +- general_examples_algebra/ex15 (8 shapes) +- general_examples_algebra/ex11 (2 shapes) +- general_examples_algebra/ex13 (1 shapes) +- general_examples_algebra/ex17 (2 shapes) +- general_examples_algebra/ex18 (2 shapes) +- general_examples_algebra/ex19 (6 shapes) +- general_examples_algebra/ex20 (2 shapes) +- general_examples_algebra/ex16 (10 shapes) +- general_examples_algebra/ex21 (1 shapes) +- general_examples_algebra/ex23 (4 shapes) +- general_examples_algebra/ex22 (2 shapes) +- general_examples_algebra/ex26 (2 shapes) +- general_examples_algebra/ex24 (2 shapes) +- general_examples_algebra/ex25 (5 shapes) +- general_examples_algebra/ex27 (2 shapes) +- general_examples_algebra/ex30 (3 shapes) +- general_examples_algebra/ex29 (8 shapes) +- general_examples_algebra/ex28 (3 shapes) +- general_examples_algebra/ex32 (2 shapes) +- general_examples_algebra/ex31 (1 shapes) +- general_examples_algebra/ex33 (1 shapes) +- general_examples_algebra/ex35 (4 shapes) +- docs/center (5 shapes) +- general_examples_algebra/ex36 (3 shapes) +- docs/objects_1d_airfoil (2 shapes) +- docs/objects_1d_blend_curve (4 shapes) +- docs/objects_1d_bspline (2 shapes) +- docs/objects_1d_constrained (7 shapes) +- docs/objects_1d_ellipticalstartarc (4 shapes) +- general_examples_algebra/ex34 (3 shapes) +- docs/objects_1d_parabolic_hyperbolic (3 shapes) +- docs/objects_3d (10 shapes) +- docs/pack_demo (12 shapes) +- docs/selector_example (1 shapes) +- docs/heart_token (16 shapes) +- docs-selectors/filter_axisplane (13 shapes) +- docs/slide_latch (7 shapes) +- docs-selectors/filter_geomtype (1 shapes) +- docs-selectors/filter_nested (7 shapes) +- docs-selectors/filter_shape_properties (4 shapes) +- docs-selectors/filter_inner_wire_count (53 shapes) +- docs-selectors/group_hole_area (3 shapes) +- docs-selectors/group_properties_with_keys (10 shapes) +- docs-selectors/sort_along_wire (2 shapes) +- docs-selectors/selectors_operators (9 shapes) +- docs-selectors/sort_sortby (6 shapes) +- docs-selectors/group_axis (3 shapes) +- ttt/ttt-ppp0102 (6 shapes) +- ttt/ttt-ppp0101 (9 shapes) +- ttt/ttt-ppp0103 (4 shapes) +- ttt/ttt-ppp0104 (9 shapes) +- ttt/ttt-ppp0105 (5 shapes) +- ttt/ttt-ppp0106 (9 shapes) +- ttt/ttt-ppp0108 (6 shapes) +- ttt/ttt-ppp0109 (8 shapes) +- docs-rst/OpenSCAD/b01 (2 shapes) +- docs-rst/OpenSCAD/b02 (2 shapes) +- ttt/ttt-24-SPO-06-Buffer_Stand (9 shapes) +- docs-rst/OpenSCAD/all (2 shapes) +- docs-rst/algebra_performance/b03 (1 shapes) +- ttt/ttt-ppp0107 (11 shapes) +- docs-rst/build_sketch/b03 (1 shapes) +- docs-rst/import_export/b01 (1 shapes) +- docs-rst/key_concepts_algebra/b01 (2 shapes) +- docs-rst/key_concepts_algebra/b02 (1 shapes) +- docs-rst/key_concepts_algebra/b03 (1 shapes) +- docs-rst/key_concepts_algebra/b04 (1 shapes) +- docs-rst/key_concepts_algebra/b13 (1 shapes) +- docs-rst/key_concepts_builder/b01 (4 shapes) +- docs-rst/key_concepts_builder/b02 (1 shapes) +- docs-rst/key_concepts_builder/b03 (1 shapes) +- docs-rst/key_concepts_builder/b09 (2 shapes) +- docs-rst/key_concepts_builder/b10 (1 shapes) +- docs-rst/key_concepts_builder/b11 (1 shapes) +- docs-rst/key_concepts_builder/b13 (2 shapes) +- docs-rst/key_concepts_builder/b14 (1 shapes) +- docs-rst/key_concepts_builder/b17 (1 shapes) +- docs-rst/key_concepts_builder/b19 (1 shapes) +- docs-rst/key_concepts_builder/b20 (1 shapes) +- docs-rst/key_concepts_builder/b21 (2 shapes) +- docs-rst/location_arithmetic/all (2 shapes) +- docs-rst/selectors/b02 (3 shapes) +- docs-rst/selectors/all (3 shapes) +- docs-rst/tips/b01 (2 shapes) +- docs-rst/tips/b05 (1 shapes) +- docs-rst/topology_selection/b01 (1 shapes) +- docs-rst/topology_selection/b03 (1 shapes) +- docs-rst/topology_selection/b04 (1 shapes) +- docs-rst/topology_selection/b05 (1 shapes) +- docs-rst/topology_selection/b06 (1 shapes) +- docs-rst/topology_selection/b07 (1 shapes) +- docs-rst/topology_selection/b08 (3 shapes) +- docs-rst/topology_selection/b09 (4 shapes) +- docs-rst/topology_selection/b12 (14 shapes) +- docs-rst/tutorial_constraints/b03 (1 shapes) +- docs-rst/tutorial_constraints/b05 (4 shapes) +- docs-rst/tutorial_constraints/b06 (3 shapes) +- docs-rst/tutorial_constraints/b07 (3 shapes) +- docs-rst/tutorial_constraints/b08 (3 shapes) +- docs-rst/tutorial_constraints/b09 (5 shapes) +- docs-rst/tutorial_constraints/b10 (2 shapes) +- docs-rst/tutorial_constraints/b11 (2 shapes) +- docs-rst/tutorial_constraints/b13 (7 shapes) +- docs-rst/tutorial_design/b07 (3 shapes) +- docs-rst/tutorial_stl_reconstruction/b03 (1 shapes) +- docs-rst/tutorial_stl_reconstruction/b04 (26 shapes) +- docs-rst/objects-text/b10 (1 shapes) +- docs-rst/topology_selection-filter_examples/b01 (1 shapes) +- docs-rst/algebra_performance/b01 (3 shapes) +- docs-rst/algebra_performance/all (67 shapes) + +## Errors by script + +- examples/dual_color_3mf: `NotImplemented: build123d-lite Mesher writes STL only (no lib3mf in the WASM build); got dual_color.3mf` +- examples/toy_truck: `other: Line 9634: Uncaught Error: Python JavascriptError: INTERNAL OPENCASCADE ERROR in FilletEdges: the OC` +- docs/objects_2d: `NameError: Draft` +- ttt/ttt-23-t-24-curved_support: `ImportError: undefined` +- ttt/ttt-ppp0110: `RuntimeError: KNOWN OCCT 8.0.1 wasm kernel fault: fuse dropped an operand (result volume 0 < l` + +## Timeouts + +- examples/heat_exchanger +- docs/spitfire_wing_gordon + +## Excluded (reference failed natively) + +- examples/python_logo: reference no-shapes +- examples/tea_cup: reference error +- general_examples_algebra/ex10: reference error +- docs/constraint_examples: reference error +- docs/line_types: reference no-shapes +- docs/rigid_joints_pipe: reference error +- docs/rod_end: reference error +- docs/technical_drawing: reference error +- docs-objects/text: reference error +- docs-selectors/sort_distance_from: reference error + +# Defaults audit (every remaining non-PASS, root-caused) + +Hand-maintained (`defaults-audit.md`, appended to this report by run-lite.mjs). +For each remaining non-PASS script: the root cause, the upstream (build123d +0.11.1 / OCP 7.x) defaults compared against build123d-lite's (OCCT 8.0.1 +wasm), and an honest verdict. Closed-this-round rows are kept where the +investigation itself is the record. + +| Script(s) | Root cause | Upstream defaults vs lite | Verdict | +|---|---|---|---| +| examples/joints, examples/joints_algebra (MISMATCH) | Slider/pin positions are measured along `Axis(edge)` of slot edges selected after booleans; the parts land at the other end of the (geometrically correct) slot. | Both sides map `position_at(u)` orientation-aware (`u -> 1-u` when the edge is not FORWARD): upstream `Mixin1D._occt_param_at`, lite `Edge.position_at`. Verified the underlying curves agree; only the sub-edge TopAbs orientation flag differs (OCP 7.x vs 8.0.1 wasm construction history). | Same defaults, kernel construction-history difference — COMPROMISE(edge-orientation). Canonicalising `Edge.make_mid_way`'s references (the upstream canonical free-edge rule, default-on) shrank pin_arm 8.16 -> 2.69 mm and slider_arm 11.80 -> 9.11 mm; the remainder needs the example itself to select its two TIED top edges with `sort_by(Axis.Z, tie_break=True)`, which is opt-in upstream too. | +| examples/projection, examples/projection_algebra (MISMATCH, `projected_text` only, d=0.12) | The text wraps the *opposite way* around the sphere: the arch path (closed sphere-cylinder intersection edge) is TopAbs_REVERSED in OCP 7.x but FORWARD in 8.0.1 wasm over the SAME geometric parametrization (verified: raw curve at 25% is +Y on both; upstream's flag flips traversal to -Y first, lite's does not). | Everything else now byte-matches: `make_text` align default fixed to `None`, `position_at` switched to arc-length fraction via GCPnts_AbscissaPoint, and per-glyph text faces split disjoint outer contours (i/j dots) into separate faces (40 faces == upstream). | Same defaults, kernel edge-orientation history on a closed intersection curve — COMPROMISE(edge-orientation). Not honestly closable. | +| docs-selectors/sort_axis (MISMATCH, -14.7% volume) | `revolve(face, -Axis(edge), 90)` sweeps the OTHER WAY: the slot edge selected off the extruded solid has raw parametrization `(34,16,4) -> (34,16,0)` in this kernel and `(34,16,0) -> (34,16,4)` in OCP 7.x, so `Axis(edge)` points -Z instead of +Z. The profile face, its edge, its length and midpoint all agree exactly. | `Axis(edge)` is RAW-curve on both sides by design (`canonical=True` is opt-in, exactly as in the upstream patch), so neither side canonicalizes here. | Same defaults, kernel construction-history difference — COMPROMISE(edge-orientation), the same species as joints/projection. Opting the example into `Axis(edge, canonical=True)` would close it, and the canonical rule does give upstream's direction (open edge from the lexicographically smaller end). | +| docs-selectors/filter_all_edges_circle (MISMATCH, `f` only) | The script keeps the loop variable of `for i, f in enumerate(faces)`, i.e. THE LAST of a mirror-symmetric pair of bearing-bore faces at y = ±21. Every other measured shape (including all 53 sorted `faces[i]`) matches. | Not a defaults difference: `part.faces()` is the kernel's face traversal order, and the mirrored pair comes out in the opposite order here. | COMPROMISE(traversal-order). | +| docs-rst/tips/b04 (MISMATCH, bbox 0.2 mm) | `vertices().group_by(Axis.X)[-1].sort_by(Axis.Z)[-1]` inside `BuildSketch(Plane.XZ)`: the sketch's LOCAL vertices all have z = 0, so `sort_by(Axis.Z)` is a COMPLETE TIE between (0.5, ±0.5) and the stable sort hands back whichever the kernel enumerated last. Verified natively: upstream's rectangle enumerates [(0.5, 0.5), (0.5, -0.5)] and picks (0.5, -0.5). | Same default (a plain stable sort; `tie_break=True` is opt-in upstream too, and is exactly the fix the canonical-edges work proposes for this). | COMPROMISE(traversal-order): lite builds its rectangle from a different first corner, so the tie resolves the other way. | +| docs/objects_1d (MISMATCH, `scene` bbox 0.07 mm) | `scene = Compound(...) + Compound.make_triad(2)`. | The triad's axes and spline arrow heads are built exactly; upstream also draws 'X'/'Y'/'Z' with the **`singleline` STROKE font**, which this build does not ship (only the outline font FreeSans). | COMPROMISE(triad-labels). The triad is a viewer symbol; the deviation is confined to scripts that measure it. (`l1`/`l3`/`l4` also differ because the file reuses those names across nine examples and the harness compares the last binding.) | +| examples/cast_bearing_unit (**was ERROR, now PASS**) | The previous verdict — "genuine kernel fault in the 8.0.1 wasm fillet" — was WRONG. `FilletEdges` was aborting the wasm heap because lite's `make_hull` handed it a POLYLINE boundary (hundreds of micro-edges) instead of the trimmed arcs upstream produces. | `make_hull` is now a statement-for-statement port of `Wire.make_convex_hull` (sample -> 2-D hull -> connecting lines + trimmed source edges), so the fillet sees the same topology upstream's does. | Closed: lite bug (a simplified hull), not a kernel fault. The same fix closed docs-rst/tips/b01 (ChamferEdges "internal OCCT error"). | +| examples/toy_truck (ERROR) | `FilletEdges` raises "INTERNAL OPENCASCADE ERROR" (caught, no heap corruption) on the truck's body fillet. Unlike cast_bearing_unit this input has no hull in it. | Upstream `Solid.fillet` = `BRepFilletAPI_MakeFillet(shape)` (default ChFi3d_Rational) + `Add(radius, edge)`; lite is identical (explicit ChFi3d_Rational, same Add). No tolerance/continuity knobs differ. | Same defaults, kernel behaviour on this input. | +| examples/dual_color_3mf (ERROR, geometry closed) | All six measured shapes match the reference exactly; the script fails on its last statement, `Mesher.write("dual_color.3mf")`. | Upstream's `Wire.offset_2d` open-mode branch is ported exactly. | COMPROMISE(mesher): there is no lib3mf in this wasm build, so only STL export exists. | +| ttt/ttt-ppp0110 (ERROR) | The KNOWN 8.0.1 coplanar-BSpline fuse fault, in the one shape where the General-Fuse rebuild cannot recover the dropped operand (result volume 0). | Upstream fuse defaults reproduce the drop on this kernel; see the ex34 row below. | Genuine kernel fault, detected and raised (COMPROMISE(kernel-guard) cannot recover this one). | +| general_examples/ex34, general_examples_algebra/ex34 (PASS since the kernel-guard round) | `BRepAlgoAPI_Fuse` silently DROPS an operand when coplanar faces meet along BSpline edges (glyph solids fused onto a box face); result was the bare box. | Upstream fuse defaults — no fuzzy value, glue off, NonDestructive unset — reproduce the drop identically on this kernel; it is the fuse *result-assembly* phase that is broken, the General-Fuse *split* phase is correct on the same inputs. Lite's `Union` detects the drop and rebuilds from the `BOPAlgo_Builder` partition; see COMPROMISE(kernel-guard). | Genuine kernel fault (8.0.1 wasm), worked around via the exact GF partition. | +| examples/bracelet, examples/bicycle_tire, examples/build123d_logo*, examples/maker_coin (PASS) | Freeform-surface, wrap, Text-normal and `new_edges` rounds — see the git history of this file for the full write-ups; kept here only as the record that they are closed. | — | Closed in earlier rounds. | +| SKIP x10 (python_logo, tea_cup builder, general_examples_algebra/ex10, docs/line_types, docs/constraint_examples, docs/rigid_joints_pipe, docs/rod_end, docs/technical_drawing, docs-objects/text, docs-selectors/sort_distance_from) | Real build123d 0.11.1 fails natively on these: no module-level shapes, or an import/API that 0.11.1 does not have (`bd_warehouse` x3, `ImageFace`, `tcv_screenshots`, `ColorMap`). | n/a | Excluded from scoring by the harness. | + +### Selectors, 1-D solvers and GUI-doc round (this round) + +177 PASS -> **204 PASS**, 34 ERROR -> 9, 11 MISMATCH -> 8, and every script +that passed before still passes. Buckets worked in order: +topology-selection properties, 1-D constrained objects, the +"deliberate but tractable" items, then a triage pass over the MISMATCHes. + +| Script(s) | Root cause | Upstream defaults vs lite | Verdict | +|---|---|---|---| +| docs-selectors/filter_nested, /filter_shape_properties, /filter_all_edges_circle, /group_axis, /group_hole_area, /sort_along_wire, /sort_sortby, /group_properties_with_keys (ERROR x8 -> 7 PASS + 1 traversal-order MISMATCH) | The selector surface the topology-selection docs exercise: `ShapeList.wires`, `Face.is_circular_convex/_concave`, `Face.center_location`/`position_at`, `Mixin1D.normal`, `Edge`/`Wire.param_at_point`, `sort_by()`, `Shape.distance`/`distance_to`/`closest_points`, `GroupBy.group(key)`, iterating a Builder in `add()`, and fillet/chamfer over edges pooled from SEVERAL intermediate shapes. | All ported from 0.11.1. Three defaults had to change to match: `sort_by_distance` sorts by the MINIMAL distance (`distance_to`, BRepExtrema_DistShapeShape) rather than centre distance, `filter_by_position` returns its survivors SORTED along the axis, and `group_by` passes non-numeric keys through unrounded. fillet/chamfer now take their target from the ACTIVE BUILDER like upstream (`target = context._obj`) and map each edge onto it geometrically. | Closed. `Face._curvature_sign` is a substitution, not a behaviour compromise: gp_Cylinder/gp_Sphere/gp_Torus are unbound here, so the reference distance comes from the second fundamental form (`S_dd . N < 0` is exactly `normal . (P - reference) > 0` for these three quadrics) — COMPROMISE(curvature-sign). | +| docs/objects_1d_airfoil, _blend_curve, _bspline, _ellipticalstartarc, _parabolic_hyperbolic, docs-rst/tutorial_constraints/b03, /b05, /b09, /b10 (ERROR x9 -> PASS) | The 1-D CONSTRAINED/analytic objects: `BSpline`, `ParabolicCenterArc`/`HyperbolicCenterArc` (incl. the LIMIT form of `arc_size`), `EllipticalStartArc`, `BlendCurve`, `Airfoil`, `Triangle`, plus `derivative_at`, `curvature_comb`, `Edge.trim` by point, `trim_to_other` and `ArrowHead`. | Each is now the upstream construction on bound OCCT classes: `Geom_BSplineCurve` from poles/knots/multiplicities, `gp_Parab`/`gp_Hypr` trimmed by `GC_MakeArcOf*` (including make_hyperbola's major>=minor swap with the matching angle-range shift), the ellipse frame from the start tangent, and the cubic/quintic Bezier control points from `derivative_at(1)`/`derivative_at(2)`. `Triangle` carries a port of the `trianglesolver` package's law-of-sines/cosines solver. Airfoil's point dedup has to round to GEOM_KEY_DIGITS the way `Vector.__hash__` does — without it the two trailing-edge points differ by 1.8e-17 and OCCT's `BSplCLib::Interpolate` fails on the periodic spline. | Closed. | +| docs/objects_1d_constrained, docs-rst/tutorial_constraints/b13 (ERROR x2 -> PASS) | `ConstrainedArcs` / `ConstrainedLines`: circles and lines constrained by tangency to other geometry, with GccEnt qualifiers, `Sagitta` arc selection and a user `selector`. | Upstream is a thin wrapper over OCCT's 2-D geometric constraint solvers (`Geom2dGcc_Circ2d2TanRad`, `_Circ2d2TanOn`, `_Circ2d3Tan`, `_Circ2dTanCen`, `_Circ2dTanOnRad`, `_Lin2d2Tan`, `_Lin2dTanObl`) plus `Geom2dGcc_QualifiedCurve`. **None of that family exists in this wasm build** — the .d.ts declares them, but the module exposes no such property at runtime, and neither does `GccEnt`. The two cases the docs exercise (circle/point targets) are therefore solved in CLOSED FORM here, with upstream's semantics kept intact: the centre loci are circles of radius `R ± r` per qualifier (OUTSIDE = external contact, ENCLOSING = the solution contains the target, ENCLOSED = the reverse), a solution is rejected when its contact point falls outside the target's TRIMMED range (upstream's `_param_in_trim`), and both arcs between the contact parameters are built so `Sagitta.SHORT/LONG/BOTH` picks the same one. | Closed for the documented cases, 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. The `center=`/`center_on=`/three-tangency/oriented-line overloads still raise, with the reason: their solution SETS feed a user selector, so guessing an enumeration would be guessing the answer. | +| docs/objects_3d (`Wedge`), docs-rst/topology_selection/b12 (`topo_distance_to`), docs-rst/objects-text/b10 (text along a path) (ERROR x3 -> PASS) | Individually missing objects/operations. objects_3d also needed `ConvexPolyhedron`. | `Wedge` is `BRepPrimAPI_MakeWedge`'s min/max form (bound as `_3`), `ConvexPolyhedron` sews the quickhull3d facets, `topo_distance_to` is a 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, and `Text(path=)` places each glyph exactly like `Compound.make_text`'s `position_glyph`. | Closed. | +| docs/spitfire_wing_gordon (was `ImportError: pytest`, now TIMEOUT) | The script asserts `wing.volume / 1e9 == pytest.approx(1.9879945989)`. | Added a REAL `pytest.approx` (documented defaults rel 1e-6 / abs 1e-12, sequences and dicts); every other pytest attribute raises. The script now runs: `Vector(())` is the origin (`0 * (x, y, z)` is how the docs write a conditional offset) and `intersect(Axis)` on a 1-D shape returns the ShapeList of Vertex upstream returns. | Open, measured: the wing's Gordon surface takes **386 s** in this wasm build (the harness budget is 60 s) and then comes back null. The blocker is the cost/robustness of COMPROMISE(gordon-surface-realization) at wing scale, not the missing shim. | +| docs/objects_2d (ERROR, `Draft`) | `Draft` here is **not** the draft-angle operation (lite has had `draft()`/BRepOffsetAPI_DraftAngle for rounds) — it is `drafting.Draft`, the dimension-styling dataclass, and the script goes on to use `ExtensionLine`, `DimensionLine` and `TechnicalDrawing`. | n/a — the blocker is the whole `drafting` module (dimension lines with arrows, extension lines, label text and the drawing frame), 42 measured shapes deep. `ArrowHead`/`HeadType` are now implemented; the rest is not. | Deliberate gap, with the misidentification corrected: no kernel binding is missing here. | +| ttt/ttt-24-SPO-06-Buffer_Stand (ERROR, `full_round`) | `full_round` replaces an edge with the arc of the largest empty circle that fits in the face. | Upstream generates the CANDIDATE centres with `scipy.spatial.Voronoi` (2-D) over 100 samples per edge and then averages the best three candidates — so the result depends on the exact candidate set. Lite's scipy shim raises for 2-D `Voronoi`/`ConvexHull` (qhull is not available; the 3-D hull is served by the bundled quickhull3d). | Deliberate gap, with the reason: it needs a 2-D Voronoi diagram. The honest route is a Delaunay triangulation (circumcentres ARE the Voronoi vertices), which would reproduce the same candidate SET; it is the next numerical method worth adding, not a defaults difference. | +| ttt/ttt-23-02-02-sm_hanger (ERROR, was "no edges given" and un-triaged) | Two real gaps, in order: (1) `fillet(side_line.vertices(), 7)` is the **1-D** corner fillet of an open line (upstream's `Wire.fillet_2d` -> ChFi2d/Geom2dGcc), and (2) the script's shape comes from `make_brake_formed`, sheet-metal brake forming, which lite does not implement at all. | The misleading "no edges given" was itself a lite bug: `Builder.vertices()` read `self._obj`, which for a BuildLine only exists after `__exit__`, so a mid-context `side_line.vertices()` came back empty. The selectors now read the line built so far, and the fillet raises a message naming `Wire.fillet_2d`. | Triaged: two missing features (1-D wire fillet, brake forming), not a selector-result difference. | +| ttt/ttt-23-t-24-curved_support (ERROR, `sympy`) | The part's dimensions are derived with sympy's symbolic solver. | n/a | Deliberate gap: shimming a symbolic algebra system is out of scope. | +| docs/slide_latch (was MISMATCH, now PASS) | The open question — "does 0.11.1 localize `add()` inside a face-workplane BuildSketch?" — is answered: **yes, conditionally.** `BuildSketch._add_to_context` expresses a face that is NOT coplanar with Plane.XY in its own plane's frame and drops it onto z = 0 (keeping the in-plane x/y offset), and then orients EVERY incoming face +Z. | Lite now performs the same two steps in `_combine`. | Closed: lite bug (missing sketch-face alignment). | +| docs/heart_token (was MISMATCH, bbox 2.0 mm, now PASS) | Two lite bugs in one script: `offset(amount=2, kind=Kind.INTERSECTION)` on a SKETCH ran a 3-D `MakeOffsetShape` (thickening the sketch by ±2 in z) instead of upstream's 2-D wire offset, and `mirror(about=Plane.YZ)` inside a BuildSketch left TWO half faces because a mirrored face has a -Z normal and coplanar faces with opposite normals are not the same domain, so they never fused. | `offset()` now offsets the outer wire by +amount and each inner wire by -amount and rebuilds the planar face (upstream's face branch), and the sketch-face alignment above supplies the +Z orientation that lets the halves fuse (1 face, area 200.20972988622623 == upstream). | Closed: two lite bugs. | +| docs-selectors/group_properties_with_keys (was ERROR then MISMATCH, now PASS) | After `Mixin1D.normal` and `GroupBy.group(key)` landed, two deeper differences remained: (1) `copy.copy()` returned the SAME builder, so `before_fillet`/`after_fillet`/`after_holes` all reported the FINAL geometry, and (2) lite built a full `CenterArc` as TWO half arcs, which changed `group_by(Edge.length)` keys and the per-edge sampling of `make_hull` (hull area 490.92205 vs upstream 490.921953, and 11 selected edges instead of 12). | `copy.copy` now shallow-copies the builder like upstream's (later operations rebind `_obj`, so the copy IS the snapshot), and a full circle is ONE closed edge. The hull is now bit-identical (490.921953150644) and the length groups and 12 selected edges match exactly; before_fillet 9751.639 / after_fillet 9730.739 == upstream. | Closed: two lite bugs. | +| docs-selectors/selectors_operators (was MISMATCH, bbox 6.0 mm, now PASS) | `line @ 2/3` parses as `(line @ 2) / 3` — Python's `@` has the same precedence as `/` — so the docs place objects at twice the line's end point divided by three. Lite CLAMPED `position_at` to [0, 1] and returned the end point. | Upstream extrapolates (`param_at`: "positions outside [0, 1] are not validated and yield OCCT-dependent results"); lite now does too. | Closed: lite bug. | +| ttt/ttt-ppp0107 (was MISMATCH, -1.0% / -0.9%, now PASS) | The audit's guess ("two `extrude(until=)` intermediates") was WRONG: `zz`/`zz2` are a TAPERED extrude, `extrude(amount=15, taper=-10)`. Lite always used `LocOpe_DPrism`. | `Solid.extrude_taper` uses TWO algorithms: DPrism only for a POSITIVE taper along the profile normal with no holes, otherwise a LOFT between the profile wires and their 2-D offsets (`-length * tan(taper)`, Kind.INTERSECTION, inner wires flipped). A bare `taper=-10` rectangle now measures 2957.1391331767363 — bit-identical to the reference. | Closed: lite bug (one algorithm instead of two). | +| every raw kernel error, everywhere (infrastructure, earlier round) | Emscripten throws OCCT's C++ exceptions as bare pointer NUMBERS. | The fork binds `OCJS::getStandard_FailureData` for exactly this, but it is UNCALLABLE here ("unbound types: St9exception"). | COMPROMISE(failure-decode): CascadeWorker keeps the wasm `Memory` via Emscripten's `instantiateWasm` hook and StandardUtils reads `Standard_Failure`'s message out of it directly. | + +### OCCT binding round: Geom2dGcc, quadrics, STEP assets, Voronoi, brake forming (this round) + +204 PASS -> **207 PASS**, 9 ERROR -> 6, and the two remaining +`import_step`/`sm_hanger` scripts went ERROR -> MISMATCH. Four of the nine +errors were blocked on the WASM build rather than on lite, so this round +started in the fork: `builds/cascadestudio.yml`, +`src/filter/filterMethodOrProperties.py` and a new hand-registered `OCJS_Out` +helper class (see the fork's CHANGELOG). + +| Script(s) | Root cause | Upstream defaults vs lite | Verdict | +|---|---|---|---| +| docs/objects_1d_constrained, docs-rst/tutorial_constraints/b13 (PASS -> PASS, now on the REAL solvers) | Last round's verdict — "none of the `Geom2dGcc` family exists in this wasm build" — was right about the symptom and wrong about the cause. The classes were in the yml; every binding file in the `Geom2dGcc`/`GccAna` packages failed to COMPILE on one method, `WhichQualifier(Standard_Integer, GccEnt_Position&, GccEnt_Position&)`, whose non-const enum out-params Embind cannot bind (`bind.h:531`). One bad method takes the whole file down, and the build tolerated the failure silently. | The fork now filters any method with a non-const `GccEnt_Position&` parameter (the BSplCLib enum-out-param precedent), so `Geom2dGcc_Circ2d2TanRad`, `_Circ2d2TanOn`, `_Circ2d3Tan`, `_Circ2dTanCen`, `_Circ2dTanOnRad`, `_Lin2d2Tan` and `_Lin2dTanObl` are real here. `ConstrainedArcs`/`ConstrainedLines` are now a statement-for-statement port of build123d's `topology/constrained_lines.py` (kernel side in `StandardLibrary.js`: `ConstrainedArcs2D` / `ConstrainedLines2D`), including `_param_in_trim`, `_enclosed_circ_param_offset` and the Sagitta arc pair. The Tangency parameters come back through `OCJS_Out._Tangency()`, because `Standard_Real&` out-params are passed BY VALUE through Embind. | Closed, and the closed-form stand-in is retired. **All five arc overloads and all three line overloads** were verified against the reference venv on the doc examples (`radius=`, `center_on=`, three-tangency, `center=`, `radius=`+`center_on=`, two-tangent lines, tangent+point, oriented line): worst bbox delta **1.8e-15 mm** over 8 result sets, with identical edge counts. | +| docs-selectors/filter_nested & friends — COMPROMISE(curvature-sign) | `Face.is_circular_convex/_concave` needed the surface's own reference geometry, and `gp_Cylinder`/`gp_Sphere`/`gp_Torus` were unbound, so the sign came from the second fundamental form instead. | The three quadrics are bound now, so `_faceCurvatureSign` reads upstream's own reference (cylinder axis, sphere centre, torus core circle) and dots it against the oriented normal. The second-fundamental-form path is kept as the fallback for kernels without them. | COMPROMISE(curvature-sign) **retired**. | +| Face.normal_at / location_at — COMPROMISE(point-projection) | `GeomAPI_ProjectPointOnSurf` was registered but not constructible: every constructor takes an `Extrema_ExtAlgo`, and the enum was unbound. Lite ran a 24x24 UV grid search refined by Newton. | `Extrema_ExtAlgo`/`Extrema_ExtFlag` are bound, and `LowerDistanceParameters(u&, v&)` is read back through `OCJS_Out`. The grid+Newton search is kept only as a fallback for the cases OCCT reports no solution for. | COMPROMISE(point-projection) **retired**. | +| docs/tutorial_joints, docs-selectors/filter_inner_wire_count (ERROR x2, `import_step`) | Both import a STEP asset from a path next to `__file__`. The CAD worker has no filesystem. | The asset is now delivered ahead of the run instead of being read: `collect.py` records the CAD files a script names, `run-lite.mjs` reads them out of the clone, and `CascadeAPI.loadExternalFiles()` hands them to the worker's existing STEP-import path (MEMFS + `STEPControl_Reader`) and **awaits the import** before evaluating. `import_step` resolves the requested path by base name. | filter_inner_wire_count **PASS** (53 shapes; also needed `Face.radius`, `Face.axis_of_rotation`, `ShapeList.edge()/face()/wire()/vertex()/solid()`, and `Location(position, angles, Intrinsic/Extrinsic order)`). tutorial_joints **MISMATCH on `m6_screw` alone** — the other 7 shapes match to 1e-9; the screw is placed by `CylindricalJoint.relative_to(..., position=5, angle=30)` off `hole2`, and lite's hole-location enumeration puts it on a different hole frame. Joints now survive `Shape.moved` and `Compound(joints=)`, and `Joint.symbol`, `Shape.show_topology` and `Compound.do_children_intersect` are implemented. | +| ttt/ttt-24-SPO-06-Buffer_Stand (ERROR, `full_round`) -> **PASS** | `full_round` picks the largest empty circle from the VORONOI VERTICES of 101 samples per edge over the target edge and its two neighbours, averages the best three, and rebuilds the face. | The scipy shim now has a real 2-D `Voronoi`: a Bowyer-Watson Delaunay whose circumcentres, deduplicated the way qhull's `Qbb Qc` merges cocircular ones, ARE the finite Voronoi vertices. Verified against scipy 1.18 on full_round's own inputs — the vertex SETS are identical (220 and 210 vertices, max deviation 2e-13) and the resulting circle centres agree to 1e-14. `full_round` itself is a statement-for-statement port, including the strict `<` best-three loop. Only `.vertices` is offered; the ridge/region attributes raise. | Closed. The script's own mass assert (3.923 lb ± 0.02) passes. | +| ttt/ttt-23-02-02-sm_hanger (ERROR) -> MISMATCH | Two missing features: the 1-D corner fillet of an OPEN line (`Wire.fillet_2d`) and `make_brake_formed`. | Both ported. `Wire.fillet_2d` maps the wire into its own plane (upstream's `common_plane` + `to_local_coords`), fillets one corner at a time on **`ChFi2d_FilletAlgo`** — upstream's primary solver, now bound in the fork — and splices the arc between the two trimmed edges in connection order, with the Geom2dGcc tangent-arc solver as upstream's fallback. `make_brake_formed` is the upstream algorithm: `offset_2d(thickness, side)` for the section, a station edge per line vertex (the offset vertex exactly `thickness` away), `Face.extrude` by each width along the section plane's normal, and `sweep_multi` between consecutive stations, fused. | The part is now exact where it counts: the filleted `side_line` is 187.2428359925111 mm (bit-identical), the brake-formed side solid is **33201.973161 mm³ / 16 faces** against upstream's 33201.97324 / 16, and the script's own mass assert (1028 g ± 10) passes. The remaining MISMATCH is `l1`/`l2` only — a BuildLine on a non-XY workplane leaves its module-level line variables in LOCAL coordinates in lite, and the harness compares the last binding of a reused name. | +| offset_2d Side.LEFT/RIGHT on a wire that is not parallel to Plane.XY (lite bug found by sm_hanger) | Upstream picks the side with `tangent.get_signed_angle(centre - start)`, a signed angle taken about the FIXED `-Z` reference. For a wire in Plane.XZ the cross product has no `-Z` component, so OCCT's `gp_Vec::AngleWithRef` falls back to the UNSIGNED angle (antiparallel is +180). Python's `atan2` returns **-180** for a negative zero, which flipped every LEFT/RIGHT pick on such wires. | `Vector.get_signed_angle` now returns the unsigned angle when the reference component is negligible, exactly as `AngleWithRef` does. | Closed: lite bug. The tab section then offsets to upstream's side (65.70796326552919 mm, bit-identical). | +| PipeShellSweep profile wires (lite bug found by make_brake_formed) | `PipeShellSweep` rebuilt each profile wire by adding its edges ONE AT A TIME from a `TopExp_Explorer`, which is storage order — `BRepBuilderAPI_MakeWire` silently drops any edge that does not touch the wire built so far. A brake-formed section came out as a 3-face open shell instead of a 6-face solid. | The edges are added as a `TopTools_ListOfShape` so the builder can connect them in any order (the same call `WireFromEdgesFixed` already used). | Closed: lite bug. | +| docs/objects_2d (ERROR, `Draft`) | Unchanged: the `drafting` module. | Scoped-out this round after measuring it: the port is ~450 code lines and its accuracy rides entirely on `Compound.make_text` glyph metrics, since `label_length = Text(...).bounding_box().size.X` feeds every arrow position and the 3-candidate label-placement score in `DimensionLine`. No OCCT binding is missing. | Deliberate gap, now sized. | diff --git a/test/b123d-validation/run-lite.mjs b/test/b123d-validation/run-lite.mjs new file mode 100644 index 00000000..93baa624 --- /dev/null +++ b/test/b123d-validation/run-lite.mjs @@ -0,0 +1,387 @@ +// run-lite.mjs - run every manifest script through CascadeStudio's Python +// (build123d-lite) mode and compare the produced geometry against +// reference.json (real build123d measurements). +// +// Classification per script: +// PASS every reference shape (matched BY VARIABLE NAME) agrees: +// volume within 0.5% relative, bbox within 1e-3 per axis +// MISMATCH runs, but geometry differs / shapes missing +// ERROR Python-mode exception; bucketed by first missing feature +// TIMEOUT evaluation did not finish in time (page is reloaded) +// SKIP reference itself failed natively (excluded from scoring) +// +// Usage: +// node test/b123d-validation/run-lite.mjs [--only substr] [--port 8517] +// [--out results.json] [--report report.md] +// Env: CS_TEST_HEADFUL=1 DISPLAY=:99 to run headful (as on this machine). +// +// Requires: `npm run build` first (serves packages/cascade-studio/dist). + +import { chromium } from 'playwright'; +import { spawn } from 'node:child_process'; +import { existsSync, readFileSync, writeFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import http from 'node:http'; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const ROOT = join(HERE, '..', '..'); +const B123D_SRC = process.env.B123D_SRC || '/tmp/b123d'; + +/** CAD assets a script imports from its own directory (manifest `assets`), + * read out of the build123d clone. The CAD worker has no filesystem, so they + * are handed over before the run and `import_step()` resolves them by base + * name — see CascadeAPI.loadExternalFiles. */ +export function readAssets(entry) { + const names = entry.assets || []; + if (names.length === 0) { return null; } + const files = {}; + for (const name of names) { + const path = join(B123D_SRC, entry.data_dir || '', name); + if (existsSync(path)) { files[name] = readFileSync(path, 'utf8'); } + } + return Object.keys(files).length > 0 ? files : null; +} + +const args = process.argv.slice(2); +const argVal = (name, dflt) => { + const i = args.indexOf(name); + return i >= 0 ? args[i + 1] : dflt; +}; +const ONLY = argVal('--only', null); +const PORT = parseInt(argVal('--port', process.env.CS_TEST_PORT || '8517'), 10); +const OUT = argVal('--out', join(HERE, 'results.json')); +const REPORT = argVal('--report', join(HERE, 'report.md')); +const SCRIPT_TIMEOUT = parseInt(argVal('--timeout', '60000'), 10); +const PAGES = parseInt(argVal('--pages', '4'), 10); +// Which Python interpreter the app should evaluate on: 'brython' (default) +// or the experimental 'pyodide' (CS_PY_RUNTIME=pyodide / --pyruntime). +const PY_RUNTIME = argVal('--pyruntime', process.env.CS_PY_RUNTIME || 'brython'); +const PY_RUNTIME_QUERY = PY_RUNTIME === 'pyodide' ? '?pyruntime=pyodide' : ''; + +const VOL_REL_TOL = 0.005; // 0.5% relative volume tolerance +const VOL_ZERO_ABS = 1e-6; // "zero volume" threshold for 2D/1D shapes +const BBOX_ABS_TOL = 1e-3; // per-axis absolute bbox tolerance + +// Appended to every script; prints one JSON line measured by the worker +// (see Build123dLite.js _measure_globals_json + StandardLibrary MeasureShape). +const MEASURE_FOOTER = ` + +import build123d as _b123d_lite_mod +print("B123D_MEASURE " + _b123d_lite_mod._measure_globals_json(globals())) +`; + +function classifyError(errors) { + // worker error strings carry LITERAL "\n" sequences — normalize so the + // per-gap regexes stop at the message end instead of swallowing tracebacks + const text = errors.join('\n').replace(/\\n/g, '\n'); + let m; + if ((m = text.match(/NameError: name '([^']+)'/))) return `NameError: ${m[1]}`; + if ((m = text.match(/AttributeError:.*?(?:attribute|no attribute) '([^']+)'/))) + return `AttributeError: ${m[1]}`; + if ((m = text.match(/AttributeError: '([^']+)'/))) return `AttributeError: ${m[1]}`; + if ((m = text.match(/(?:ImportError|ModuleNotFoundError):[^'\n]*'?([A-Za-z_0-9.]+)'?/))) + return `ImportError: ${m[1]}`; + if ((m = text.match(/NotImplementedError: ?([^\n]*)/))) return `NotImplemented: ${m[1]}`; + if ((m = text.match(/(TypeError|ValueError|KeyError|IndexError|RuntimeError|ZeroDivisionError): ?([^\n]*)/))) + return `${m[1]}: ${m[2].slice(0, 80)}`; + if ((m = text.match(/SyntaxError: ?([^\n]*)/))) return `SyntaxError: ${m[1].slice(0, 80)}`; + return 'other: ' + text.split('\n')[0].slice(0, 100); +} + +function compareShapes(refShapes, liteShapes) { + const problems = []; + for (const [name, ref] of Object.entries(refShapes)) { + if (ref.measure_error) continue; // reference could not measure it + const lite = liteShapes[name]; + if (!lite) { problems.push(`missing shape '${name}'`); continue; } + if (lite.measure_error) { problems.push(`'${name}' lite measure error: ${lite.measure_error}`); continue; } + // volume + if (ref.volume <= VOL_ZERO_ABS) { + if (lite.volume > 1e-3) problems.push(`'${name}' volume ${lite.volume.toFixed(4)} vs ~0`); + } else if (Math.abs(lite.volume - ref.volume) > VOL_REL_TOL * ref.volume) { + problems.push(`'${name}' volume ${lite.volume.toFixed(3)} vs ${ref.volume.toFixed(3)} ` + + `(${(100 * (lite.volume - ref.volume) / ref.volume).toFixed(2)}%)`); + } + // bbox + if (!lite.bbox) { problems.push(`'${name}' has no lite bbox`); continue; } + for (let i = 0; i < 6; i++) { + const d = Math.abs(lite.bbox[i] - ref.bbox[i]); + if (d > BBOX_ABS_TOL) { + problems.push(`'${name}' bbox[${i}] ${lite.bbox[i].toFixed(4)} vs ${ref.bbox[i].toFixed(4)} (d=${d.toFixed(4)})`); + break; // one bbox problem per shape is enough detail + } + } + } + return problems; +} + +async function ensureServer() { + const alive = await new Promise((res) => { + const req = http.get({ host: 'localhost', port: PORT, path: '/' }, (r) => { + r.resume(); res(r.statusCode < 500); + }); + req.on('error', () => res(false)); + req.setTimeout(2000, () => { req.destroy(); res(false); }); + }); + if (alive) return null; + const proc = spawn('npx', ['http-server', './packages/cascade-studio/dist', + '-p', String(PORT), '-c-1', '--silent'], { cwd: ROOT, stdio: 'ignore' }); + await new Promise((r) => setTimeout(r, 2500)); + return proc; +} + +async function newReadyPage(browser) { + const page = await browser.newPage(); + page.on('pageerror', () => {}); + await page.goto(`http://localhost:${PORT}/${PY_RUNTIME_QUERY}`, { timeout: 60000 }); + await page.waitForFunction(() => window.CascadeAPI && window.CascadeAPI.isReady(), + undefined, { timeout: 90000 }); + await page.waitForFunction(() => !window.CascadeAPI.isWorking(), + undefined, { timeout: 90000 }); + await page.evaluate(() => window.CascadeAPI.setMode('python')); + return page; +} + +/** Hand a script's CAD assets to the worker and WAIT for the import to + * finish (CascadeAPI.loadExternalFiles resolves with the names it imported). + * A worker that has already run many scripts sometimes stops answering — the + * OCCT heap is shared with every previous evaluation — so the caller recycles + * the page and retries when this rejects or times out. */ +async function deliverAssets(page, assets) { + const wanted = Object.keys(assets); + const loaded = await Promise.race([ + page.evaluate((a) => window.CascadeAPI.loadExternalFiles(a), assets), + new Promise((_, rej) => setTimeout( + () => rej(new Error('asset delivery timed out')), 30000)), + ]); + if (!loaded || loaded.length !== wanted.length) { + throw new Error('asset delivery failed: wanted ' + wanted.join(',') + + ' got ' + JSON.stringify(loaded)); + } +} + +async function runScript(page, code, assets) { + if (assets) { await deliverAssets(page, assets); } + // runCode + wait for the async console flush that carries B123D_MEASURE + const result = await page.evaluate(async (c) => { + return await window.CascadeAPI.runCode(c); + }, code); + let measure = null; + // A thrown Python exception means the measurement footer never ran — skip + // the (long) wait for its console line in that case. + const earlyErrors = result && result.errors ? result.errors : []; + const pythonFailed = earlyErrors.some((e) => e.includes('Python ')); + if (!pythonFailed) { + try { + // The measurement print is flushed asynchronously and heavy scripts + // can keep the worker busy well past runCode resolving — wait long + // (the outer per-script timeout still bounds the total). + await page.waitForFunction( + () => window.CascadeAPI.getConsoleLog().some((l) => l.startsWith('B123D_MEASURE ')) || + window.CascadeAPI.getErrors().some((e) => e.includes('Python ')), + undefined, { timeout: 45000 }); + const logs = await page.evaluate(() => window.CascadeAPI.getConsoleLog()); + const line = logs.find((l) => l.startsWith('B123D_MEASURE ')); + if (line) { + // The console panel stores JSON.stringify(arg) minus the outer + // quotes, so inner quotes arrive backslash-escaped — undo that. + let payload = line.slice('B123D_MEASURE '.length); + try { + measure = JSON.parse(payload); + } catch (e) { + measure = JSON.parse(JSON.parse('"' + payload + '"')); + } + } + } catch (e) { /* no measurement line - handled by caller */ } + } + const errors = await page.evaluate(() => window.CascadeAPI.getErrors()); + return { errors, measure }; +} + +/** Make sure the worker is idle so a slow script cannot poison the next + * one's evaluation queue. Returns false if it stayed busy (caller reloads). */ +async function workerIdle(page, timeout) { + try { + await page.waitForFunction(() => !window.CascadeAPI.isWorking(), + undefined, { timeout }); + return true; + } catch (e) { + return false; + } +} + +async function main() { + const manifest = JSON.parse(readFileSync(join(HERE, 'manifest.json'), 'utf8')) + .filter((e) => !ONLY || e.id.includes(ONLY)); + const reference = JSON.parse(readFileSync(join(HERE, 'reference.json'), 'utf8')); + + const serverProc = await ensureServer(); + const headless = !process.env.CS_TEST_HEADFUL; + const browser = await chromium.launch({ + headless, + args: ['--use-gl=angle', '--use-angle=swiftshader'], + }); + + const t0 = Date.now(); + const results = {}; + const queue = []; + let done = 0; + for (const entry of manifest) { + const ref = reference[entry.id]; + if (!ref || ref.status !== 'ok') { + results[entry.id] = { status: 'SKIP', reason: `reference ${ref ? ref.status : 'missing'}` }; + done++; + console.log(`[${done}/${manifest.length}] ${entry.id.padEnd(45)} SKIP (reference)`); + continue; + } + queue.push(entry); + } + + /** One worker: owns a page, pulls scripts off the shared queue. */ + async function pageWorker(wid) { + let page = await newReadyPage(browser); + const freshPage = async () => { + try { await page.close(); } catch (_) {} + page = await newReadyPage(browser); + }; + while (queue.length > 0) { + const entry = queue.shift(); + if (!entry) break; + const ref = reference[entry.id]; + let out; + const assets = readAssets(entry); + // A script with assets needs a worker that still answers messages; give + // it a fresh page rather than losing the script to a stale heap. + if (assets) { + try { + await deliverAssets(page, assets); + } catch (e) { + await freshPage(); + } + } + try { + out = await Promise.race([ + runScript(page, entry.code + MEASURE_FOOTER, assets), + new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), SCRIPT_TIMEOUT)), + ]); + // Don't let a still-busy worker poison this page's next script. + if (!(await workerIdle(page, 20000))) throw new Error('worker stayed busy'); + // Brython's traceback FORMATTER sometimes dies after long run + // sequences ("reading 'substr'"), masking the real Python error — + // retry once on a fresh page to recover the true message. + if (!out.measure && + out.errors.some((e) => e.includes("reading 'substr'"))) { + await freshPage(); + out = await Promise.race([ + runScript(page, entry.code + MEASURE_FOOTER, assets), + new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), SCRIPT_TIMEOUT)), + ]); + await workerIdle(page, 20000); + } + } catch (e) { + results[entry.id] = { status: 'TIMEOUT' }; + done++; + console.log(`[${done}/${manifest.length}] ${entry.id.padEnd(45)} TIMEOUT - recycling page ${wid}`); + await freshPage(); + continue; + } + + const pyErrors = out.errors.filter((e) => /error/i.test(e) || e.includes('Python')); + // A raw wasm kernel abort corrupts the OCCT heap — every following + // script on this page would fail with "memory access out of bounds". + // Classify this script honestly, then recycle the page. + const poisoned = out.errors.some((e) => + e.includes('memory access out of bounds') || + e.includes('table index is out of bounds') || + e.includes('(a raw wasm exception)') || + e.includes('RuntimeError: unreachable')); + done++; + if (!out.measure) { + const gap = classifyError(pyErrors.length ? pyErrors : out.errors.concat(['no measurement produced'])); + results[entry.id] = { status: 'ERROR', gap, errors: pyErrors.slice(0, 3) }; + console.log(`[${done}/${manifest.length}] ${entry.id.padEnd(45)} ERROR ${gap.split('\n')[0]}`); + if (poisoned) { await freshPage(); } + continue; + } + if (poisoned) { await freshPage(); } + const problems = compareShapes(ref.shapes, out.measure); + if (problems.length === 0) { + results[entry.id] = { status: 'PASS', shapes: Object.keys(ref.shapes).length }; + console.log(`[${done}/${manifest.length}] ${entry.id.padEnd(45)} PASS (${Object.keys(ref.shapes).length} shapes)`); + } else { + results[entry.id] = { status: 'MISMATCH', problems: problems.slice(0, 8) }; + console.log(`[${done}/${manifest.length}] ${entry.id.padEnd(45)} MISMATCH ${problems[0]}`); + } + } + try { await page.close(); } catch (_) {} + } + + const workers = []; + for (let i = 0; i < Math.max(1, Math.min(PAGES, queue.length)); i++) { + workers.push(pageWorker(i)); + } + await Promise.all(workers); + + writeFileSync(OUT, JSON.stringify(results, null, 1)); + writeReport(results, REPORT); + await browser.close(); + if (serverProc) serverProc.kill(); + + const counts = {}; + for (const r of Object.values(results)) counts[r.status] = (counts[r.status] || 0) + 1; + console.log(`\n== totals == ${JSON.stringify(counts)} in ${((Date.now() - t0) / 1000).toFixed(0)}s with ${PAGES} pages`); + console.log(`results -> ${OUT}\nreport -> ${REPORT}`); +} + +function writeReport(results, path) { + const buckets = { PASS: [], MISMATCH: [], ERROR: [], TIMEOUT: [], SKIP: [] }; + for (const [id, r] of Object.entries(results)) buckets[r.status].push([id, r]); + + const gapCounts = {}; + for (const [, r] of buckets.ERROR) gapCounts[r.gap] = (gapCounts[r.gap] || 0) + 1; + const gaps = Object.entries(gapCounts).sort((a, b) => b[1] - a[1]); + + const scored = Object.values(results).filter((r) => r.status !== 'SKIP').length; + let md = `# build123d-lite validation report\n\n`; + md += `Generated ${new Date().toISOString()} - ${Object.keys(results).length} scripts ` + + `(${scored} scored, ${buckets.SKIP.length} excluded because real build123d fails natively).\n\n`; + md += `| Status | Count |\n|---|---|\n`; + for (const s of ['PASS', 'MISMATCH', 'ERROR', 'TIMEOUT', 'SKIP']) + md += `| ${s} | ${buckets[s].length} |\n`; + + md += `\n## Feature-gap frequency (ERROR bucket)\n\n| Gap | Scripts |\n|---|---|\n`; + for (const [gap, n] of gaps) md += `| \`${gap}\` | ${n} |\n`; + + md += `\n## Mismatches (runs, but geometry differs)\n\n`; + for (const [id, r] of buckets.MISMATCH) { + md += `- **${id}**\n`; + for (const p of r.problems) md += ` - ${p}\n`; + } + + md += `\n## Passing scripts\n\n`; + for (const [id, r] of buckets.PASS) md += `- ${id} (${r.shapes} shapes)\n`; + + md += `\n## Errors by script\n\n`; + for (const [id, r] of buckets.ERROR) md += `- ${id}: \`${r.gap}\`\n`; + + md += `\n## Timeouts\n\n`; + for (const [id] of buckets.TIMEOUT) md += `- ${id}\n`; + + md += `\n## Excluded (reference failed natively)\n\n`; + for (const [id, r] of buckets.SKIP) md += `- ${id}: ${r.reason}\n`; + + // Hand-maintained root-cause / defaults audit of every non-PASS script, + // kept in its own file so regenerating this report never loses it. + try { + md += `\n` + readFileSync(join(HERE, 'defaults-audit.md'), 'utf8'); + } catch { /* no audit file yet */ } + + writeFileSync(path, md); +} + +// Only sweep the corpus when invoked directly — probe.mjs imports readAssets +// from here. +if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) { + main().catch((e) => { console.error(e); process.exit(1); }); +} diff --git a/test/b123d-validation/run.sh b/test/b123d-validation/run.sh new file mode 100755 index 00000000..fd1a4416 --- /dev/null +++ b/test/b123d-validation/run.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# End-to-end build123d-lite validation: +# 1. collect candidate scripts from the build123d clone (B123D_SRC=/tmp/b123d) +# -> manifest-all.json (includes every docs .rst code-block, most of which +# are prose fragments rather than scripts) +# 2. measure ground truth with REAL build123d (B123D_REF_PY venv python) +# -> reference-all.json, then prune the fragments that never produced +# geometry natively -> manifest.json + reference.json (the scored corpus) +# 3. build CascadeStudio and run every script through Python mode, +# comparing volume/bbox per module-level variable -> report.md +# +# Not part of the default playwright suite. See README.md. +set -euo pipefail +cd "$(dirname "$0")" + +STAGE="${1:-all}" + +if [[ "$STAGE" == "all" || "$STAGE" == "collect" ]]; then + python3 collect.py +fi + +if [[ "$STAGE" == "all" || "$STAGE" == "reference" ]]; then + python3 reference.py --manifest manifest-all.json --out reference-all.json \ + --jobs "${B123D_REF_JOBS:-4}" + python3 collect.py prune +fi + +if [[ "$STAGE" == "all" || "$STAGE" == "lite" ]]; then + (cd ../.. && npm run build) + node run-lite.mjs +fi diff --git a/test/b123d-validation/runtime-comparison.md b/test/b123d-validation/runtime-comparison.md new file mode 100644 index 00000000..8a7dd798 --- /dev/null +++ b/test/b123d-validation/runtime-comparison.md @@ -0,0 +1,246 @@ +# Python runtime comparison: Brython vs Pyodide for build123d-lite + +**Question.** Python mode runs build123d-lite on **Brython** — an early choice +made on instinct ("stay lean") and never measured. Would **Pyodide** (real +CPython 3.14 on wasm) be the better host for the *same* lite modules? This is +about the interpreter only: build123d-lite, the CascadeStudio standard library +and OCCT are unchanged, and running real build123d over OCP.wasm is explicitly +out of scope. + +**Decision criteria, in the project owner's priority order:** memory and +startup time first, download size alongside them, throughput a distant last. A +runtime that is faster per script but heavier at rest or slower to boot loses +unless the difference is dramatic. + +**Answer: keep Brython.** Pyodide turned out to be a genuine drop-in — it +reproduces the validation harness *exactly*, script for script, mismatch +magnitude for mismatch magnitude — and it is marginally faster once running. +But on the three criteria that were asked for it loses by a wide margin: **23x +the download**, **~3x the boot**, and **~2.2x the memory** the runtime adds to +the worker. Nothing a user can see improves. The prototype stays in the tree +behind `?pyruntime=pyodide`, off by default: it is cheap to keep, it is the +only Python runtime that could ever host real build123d, and it turns "we use +Brython to stay lean" from a belief into a number. + +Everything below was measured on this machine (32 cores, Chromium headful on +`DISPLAY=:99`, assets served by `http-server` over localhost), on the same +build, alternating runtimes. + +## 1. Download size (compressed) + +| | Brython | Pyodide core 314.0.4 | +|---|---|---| +| files | `brython.js` | `pyodide.mjs`, `pyodide.asm.mjs`, `pyodide.asm.wasm`, `python_stdlib.zip`, `pyodide-lock.json` | +| raw | 1.32 MB | 12.90 MB | +| gzip -9 | **0.26 MB** | **6.09 MB** (23.4x) | +| brotli | 0.21 MB | 5.21 MB (24.8x) | + +`python_stdlib.zip` (2.43 MB) is already deflated, so it does not compress +again — no server configuration recovers that half of the payload. Both +runtimes are lazily loaded on the FIRST Python evaluation, so JS/OpenSCAD mode +pays nothing either way; the comparison is what a Python-mode user downloads +once (and re-downloads whenever the cache is evicted or the version changes). + +For scale, the app's other big download is the OCCT kernel: 25.7 MB raw / +7.59 MB gzipped. Adding Pyodide would nearly *double* what a first-time +Python-mode visitor pulls down; adding Brython costs 3% of it. + +## 2. Startup + +`firstEval` is the honest user-facing number: `runCode()` on a +page that has loaded but never run Python — interpreter boot + build123d-lite +import + evaluate + mesh. Median of 3 runs; each run used a fresh browser +context (cold HTTP cache) and then a second page in the same context (warm). + +| | Brython | Pyodide | ratio | +|---|---|---|---| +| first Python evaluation, cold cache | **369 ms** (361/369/395) | **1107 ms** (1053/1107/1195) | 3.0x | +| first Python evaluation, warm cache | **366 ms** | **1061 ms** | 2.9x | +| — of which: fetch the interpreter | 7 ms | 3 ms (`pyodide.mjs` only) | | +| — of which: interpreter init | 24 ms | 916–1041 ms (wasm compile + CPython bring-up + stdlib zip) | | +| — of which: compile build123d-lite (8.8k lines) | 273 ms | **74 ms** | 0.27x | +| second evaluation (no boot) | 59 ms | 57 ms | 0.97x | +| starter-script evaluation | 302 ms | 280 ms | 0.93x | +| assets over HTTP, cache bypassed | 7.5 ms / 1.3 MB | 45 ms / 12.9 MB | | + +Two things worth naming: + +* **Localhost hides the download.** Cold and warm are within noise here + because 12.9 MB off a loopback socket costs ~45 ms. On a real connection the + gzipped 6.09 MB is 5+ seconds at 10 Mbps and ~0.5 s on a fast link, all of + it in front of the user's first evaluation. The size table, not the cold + timing, is the real startup penalty. +* **Pyodide is the better *compiler*.** CPython compiles build123d-lite in + 74 ms where Brython needs 273 ms — Brython's cost is translating 8.8k lines + of Python to JavaScript. Pyodide loses anyway, because it must first stand + up a CPython interpreter (~950 ms) that Brython never needs. + +## 3. Memory + +`performance.memory` does not exist in workers, and everything Python costs +lives in the worker — so the measurement is the **RSS of the browser's +renderer processes** (page + workers share one), sampled after forcing GC in +both contexts (`--js-flags=--expose-gc`), plus the exact wasm linear-memory +sizes read inside the worker. RSS is sticky, so read the deltas. + +| Worker memory | Brython | Pyodide | +|---|---|---| +| baseline before any Python (JS mode) | 679–689 MB RSS, of which OCCT wasm 100 MB | same | +| **added by boot + trivial script** | **+63 MB** (48/63/85) | **+141 MB** (116/141/155) | +| **added after the starter script** | **+81 MB** (76/81/94) | **+164 MB** (157/164/168) | +| **added after the whole 232-script corpus** (single page, no reload) | **+1356 MB** | **+1435 MB** | +| OCCT wasm heap after the corpus | 1115 MB | 1115 MB (identical) | +| CPython wasm heap (exact) | — | 43.3 MB at boot, **43.3 MB after the corpus** | + +Pyodide's 43 MB CPython heap is only a third of what it actually costs: the +rest is Chromium holding a compiled 9.6 MB wasm module and the unpacked +stdlib. Brython's ~60–80 MB is the V8 heap holding brython.js plus the +JavaScript it generated for build123d-lite. + +Context for both numbers: the OCCT wasm is 100 MB of linear memory before +either runtime starts, so Brython adds ~10% to an already heavy worker and +Pyodide adds ~25%. + +## 4. Throughput (the criterion that was ranked last) + +Full 232-script validation harness, 4 pages, 3 runs each, alternating: + +| | Brython | Pyodide | +|---|---|---| +| harness wall time | 165 / 161 / 161 s (median **161 s**) | 154 / 153 / 153 s (median **153 s**) | +| per-script, single page, median | 155 ms | 148 ms | +| per-script, single page, p95 | 8172 ms | 7519 ms | +| 232 scripts on ONE page, wall | 680 s | 680 s | + +Pyodide is ~5% faster over the corpus and indistinguishable on a single +evaluation (57 vs 59 ms). That is unsurprising: the wall time of a CAD script +is dominated by OCCT, not by the interpreter, and the interpreter's own +overhead is one JS↔Python boundary crossing per CAD call either way. + +## 5. Correctness: is it a drop-in? + +Yes — and this was the surprise. Running the full corpus on both runtimes, +three times each: + +| Status | Brython | Pyodide | +|---|---|---| +| PASS | 204 | 204 | +| MISMATCH | 8 | 8 | +| ERROR | 9 | 9 | +| TIMEOUT | 1 | 1 | +| SKIP (reference fails natively) | 10 | 10 | + +**Zero per-script status deltas**, in all three paired runs, and the eight +MISMATCH entries carry byte-identical problem strings (e.g. `'slider_arm' +bbox[0] 5.3255 vs -3.7834 (d=9.1089)` on both) — the geometry is not merely +"as good", it is the same geometry. Two ERROR *labels* differ while the +classification does not, and in both cases Pyodide's message is better: + +* `ttt-23-t-24-curved_support`: `ImportError: sympy` (Pyodide) vs + `ImportError: undefined` (Brython) — CPython names the missing module. +* `toy_truck`: the same known OCCT fillet fault, reported as + `pyodide.ffi.JsException` instead of Brython's `JavascriptError`. + +The harness only looks at geometry, so **line mapping was checked separately** +— the feature that pays for the frame-walking seam. The same script produces +the same history steps on both runtimes, down to the line numbers +(`Box@3, Cylinder@5, Difference@7, Sphere@10`, the last one from inside a +helper function), which is what drives the modeling timeline, Select-pick → +editor-line flash and the Fillet tool's variable resolution. + +## 6. What porting the interpreter actually took + +`PyodideRuntime.js` runs the SAME `Build123dLite.js` source string. The work +was entirely in the seams Brython papers over: + +* **The `w` bridge.** `from browser import self as w` is a Brython builtin; on + Pyodide a `browser` module is registered whose `self` proxies the worker's + JS globals *with Brython's conversion behaviour* — Python lists/tuples + become real JS arrays on the way out (Pyodide would otherwise pass a + PyProxy, and `Array.isArray` in the standard library would fail), JS arrays + become list-likes on the way in. +* **Object identity.** build123d-lite compares shapes with `is` + (`any(existing is topo for existing in w.sceneShapes)`). Brython hands out + one stable wrapper per JS object; Pyodide mints a fresh JsProxy per + conversion (`a is b` is False, though `a == b` is True). The bridge memoizes + proxies by `js_id` for the duration of an evaluation. +* **Live arrays.** `w.sceneShapes` is mutated through `.push()`/`.pop()` by + `show()`, so the list-like writes those two through to the JS array. +* **Frame walking.** `getPythonUserLine` (CacheOp's line tagging, which drives + history steps and pick→editor-line) and `_pythonCallerFrame` (the Builder + same-stack-frame rule) are plain `sys._getframe()` walks — simpler than + Brython's `$B.frame_obj` chain, and they work when the call arrives from JS + because the JS call is synchronous from Python. +* **`__file__`.** Twelve upstream doc scripts compute an asset directory from + `os.path.dirname(os.path.abspath(__file__))`. Brython defines `__file__` in + the user module; a bare CPython `exec` does not. Setting the same string on + both runtimes was the ONLY change needed to go from 194 to 204 PASS. +* **Stdlib.** Only the POLICY shims are registered on Pyodide (scipy's + Nelder-Mead/quickhull stand-ins, the `pytest.approx` subset, the `logging` + swallower); `math`, `copy`, `typing`, `functools`, `itertools`, `operator`, + `timeit`, `random` and `os` come from the real stdlib and behaved + identically. +* **Errors.** Same surface — `Python \n` with user line + numbers — built from `traceback.format_exception` with the runner frame + dropped, including the OCCT raw-pointer decode. + +Nothing in build123d-lite had to change, and nothing about it is +Brython-specific beyond those seams. + +## 7. What switching WOULD buy (and what it would cost) + +Honest ledger, since the answer is "no": + +* Real CPython semantics and a real stdlib (`pathlib`, `dataclasses`, `re`, + `decimal`, …) instead of hand-written shims — today lite needs eight of + them, and they are a maintenance surface. +* Real PyPI wheels: `sympy` (+4.0 MB) would close one ERROR; + `numpy`+`scipy` (+16.3 MB) would give `full_round` its real 2-D Voronoi and + replace COMPROMISE(scipy-shim). At 6.1 MB gz just for the core, that is a + 20+ MB Python stack for two scripts. +* It is the only path to running *real* build123d over OCP.wasm (the deferred + roadmap item) — but that project is dominated by the OCP binding surface, + not by the interpreter, and it can adopt Pyodide when it happens. +* Better error messages (see §5) and no Brython traceback-formatter flakiness + (`run-lite.mjs` carries a retry for Brython's `reading 'substr'` failures). + +Against: 23x the download, 3x the boot, 2.2x the resident memory, a second +wasm module to keep in step with the OCCT one, and a vendored 13 MB blob that +does not belong in npm. + +## 8. Recommendation + +1. **Keep Brython as the default.** Nothing here justifies the size/boot/memory + bill. +2. **Keep the Pyodide runtime behind the flag** (`?pyruntime=pyodide`, or + `localStorage['cascade-py-runtime']`), off by default, with its assets + *unvendored* in a clean checkout — the build only copies `vendor/pyodide/` + when someone has fetched it, so the default bundle is byte-identical to + before. +3. **Revisit only if the premise changes** — a real-build123d/OCP.wasm effort, + or a hard dependency on numpy/scipy semantics. The measurement harness + (`bench-runtime.mjs`, `run-lite.mjs --pyruntime`) is committed, so the + revisit is a re-run, not a rewrite. + +## How to reproduce + +```bash +node packages/cascade-core/scripts/fetch-pyodide.cjs # gitignored vendor/pyodide (13 MB) +npm run build + +# classification parity (232 scripts) — compare against the committed results.json +CS_TEST_HEADFUL=1 DISPLAY=:99 CS_PY_RUNTIME=pyodide \ + node test/b123d-validation/run-lite.mjs --pages 4 \ + --out /tmp/results-pyodide.json --report /tmp/report-pyodide.md + +# startup + memory (3 runs), then the corpus memory/percentile pass +CS_TEST_HEADFUL=1 DISPLAY=:99 \ + node test/b123d-validation/bench-runtime.mjs --runtime pyodide --repeat 3 +CS_TEST_HEADFUL=1 DISPLAY=:99 \ + node test/b123d-validation/bench-runtime.mjs --runtime pyodide --repeat 1 --corpus +``` + +`test/py-runtime.spec.js` (part of the default Playwright suite) keeps the flag +honest: Brython is the default, an unknown value falls back to Brython, and — +when `vendor/pyodide` is present — a build123d script really does evaluate on +CPython. diff --git a/test/cascade-studio.spec.js b/test/cascade-studio.spec.js index ced25972..1e784bf4 100644 --- a/test/cascade-studio.spec.js +++ b/test/cascade-studio.spec.js @@ -27,11 +27,14 @@ async function evaluateCode(page, code, timeout = 60000) { /** * Navigate to the app and wait until it's fully ready (WASM loaded, starter code done). + * A parameter-less load now starts in Python (build123d) mode, so the specs that + * evaluate CascadeStudio JS select that mode explicitly. */ -async function gotoAndReady(page) { +async function gotoAndReady(page, mode = 'cascadestudio') { await page.goto('/'); await waitForReady(page); await page.waitForFunction(() => !window.CascadeAPI.isWorking(), { timeout: 60000 }); + if (mode) { await page.evaluate((m) => window.CascadeAPI.setMode(m), mode); } } /** @@ -110,9 +113,10 @@ test.describe('Application Startup & CascadeAPI', () => { const retrieved = await page.evaluate(() => window.CascadeAPI.getCode()); expect(retrieved).toBe(testCode); - // getMode - const mode = await page.evaluate(() => window.CascadeAPI.getMode()); - expect(mode).toBe('cascadestudio'); + // getMode — a parameter-less load starts in Python (build123d) mode + expect(await page.evaluate(() => window.CascadeAPI.getMode())).toBe('python'); + await page.evaluate(() => window.CascadeAPI.setMode('cascadestudio')); + expect(await page.evaluate(() => window.CascadeAPI.getMode())).toBe('cascadestudio'); // screenshot const screenshot = await page.evaluate(() => window.CascadeAPI.screenshot()); @@ -325,9 +329,7 @@ test.describe('Export, GUI & Console', () => { test.describe('Everything Example', () => { test('full gallery renders without errors', async ({ page }) => { - await page.goto('/'); - await waitForReady(page); - await page.waitForFunction(() => !window.CascadeAPI.isWorking(), { timeout: 60000 }); + await gotoAndReady(page); // selects CascadeStudio JS mode (Python is the default) await evaluateCode(page, EVERYTHING_EXAMPLE, 120000); const errors = await page.evaluate(() => window.CascadeAPI.getErrors()); expect(errors).toEqual([]); diff --git a/test/gui-tools.spec.js b/test/gui-tools.spec.js new file mode 100644 index 00000000..632fb6e7 --- /dev/null +++ b/test/gui-tools.spec.js @@ -0,0 +1,450 @@ +// @ts-check +// Tests for the LeapShape-style GUI modeling tools (viewport toolbar). +// Every GUI operation emits JavaScript code into the Monaco editor. +const { test, expect } = require('@playwright/test'); + +/** Wait for the CascadeAPI to become available and ready. */ +async function waitForReady(page, timeout = 60000) { + await page.waitForFunction(() => { + return window.CascadeAPI && window.CascadeAPI.isReady(); + }, { timeout }); +} + +/** Navigate to the app and wait until it's fully ready. Fresh loads start in + * Python mode now, so these JS-emission tests select CascadeStudio JS. */ +async function gotoAndReady(page, mode = 'cascadestudio') { + await page.goto('/'); + await waitForReady(page); + await page.waitForFunction(() => !window.CascadeAPI.isWorking(), { timeout: 60000 }); + if (mode) { await page.evaluate((m) => window.CascadeAPI.setMode(m), mode); } +} + +/** Run code via the API and wait for the mesh render to land + * (shapeLines is populated by renderMeshData, after resetWorking). */ +async function runCodeAndRender(page, code, expectedShapes, timeout = 60000) { + const result = await page.evaluate((c) => window.CascadeAPI.runCode(c), code); + expect(result.errors).toEqual([]); + await page.waitForFunction( + (n) => window.threejsViewport._shapeLines.length === n, + expectedShapes, { timeout } + ); +} + +/** Wait for an in-flight evaluation (started by a tool commit) to finish + * and for the render to produce the expected number of scene shapes. */ +async function waitForToolEvaluation(page, expectedShapes, timeout = 60000) { + await page.waitForFunction(() => !window.CascadeAPI.isWorking(), { timeout }); + await page.waitForFunction( + (n) => window.threejsViewport._shapeLines.length === n, + expectedShapes, { timeout } + ); + const errors = await page.evaluate(() => window.CascadeAPI.getErrors()); + expect(errors).toEqual([]); +} + +/** In-page helper source: project CAD coords to screen and fire pointer + * events on the viewport canvas (exercises the real capture-phase routing). */ +const POINTER_HELPERS = ` + function screenOfCad(x, y, z) { + var env = window.threejsViewport.environment; + var rect = env.renderer.domElement.getBoundingClientRect(); + var v = env.camera.position.clone().set(x, z, -y).project(env.camera); + return { x: rect.left + (v.x + 1) / 2 * rect.width, + y: rect.top + (1 - (v.y + 1) / 2) * rect.height }; + } + function fire(type, pt) { + var canvas = window.threejsViewport.environment.renderer.domElement; + canvas.dispatchEvent(new PointerEvent(type, { + clientX: pt.x, clientY: pt.y, button: 0, + buttons: type === 'pointerup' ? 0 : 1, + bubbles: true, cancelable: true, pointerId: 1 + })); + } +`; + +/** Probe the live camera for two canvas points whose ground-plane hits differ + * in BOTH CAD x and y, plus a point above the second one for a height drag. + * + * Fixed CAD coordinates can project outside the canvas (the mouse event then + * goes to another panel) and fixed screen offsets can happen to run parallel + * to a projected axis (one CAD coordinate then never changes), so ask the + * tool's own raycaster instead of assuming a framing. */ +async function groundDragPoints(page, minDelta = 5) { + const points = await page.evaluate((min) => { + const tools = window.CascadeAPI._tools; + const r = window.threejsViewport.environment.renderer.domElement.getBoundingClientRect(); + const probe = (fx, fy) => { + const pt = { clientX: r.left + r.width * fx, clientY: r.top + r.height * fy }; + const hit = tools.raycastGround(pt); + return hit ? { pt: { x: pt.clientX, y: pt.clientY }, cad: tools.threeToCad(hit).map(Math.round) } : null; + }; + const a = probe(0.5, 0.62); + if (!a) return null; + for (const [fx, fy] of [[0.75, 0.8], [0.25, 0.8], [0.72, 0.68], [0.28, 0.68], + [0.8, 0.9], [0.2, 0.9], [0.62, 0.85], [0.38, 0.85]]) { + const b = probe(fx, fy); + if (b && Math.abs(b.cad[0] - a.cad[0]) >= min && Math.abs(b.cad[1] - a.cad[1]) >= min) { + return { a: a.pt, b: b.pt, up: { x: b.pt.x, y: r.top + r.height * 0.25 } }; + } + } + return null; + }, minDelta); + expect(points, 'the viewport should expose a usable ground-plane drag').not.toBeNull(); + return points; +} + +/** Snapshot of a creation tool's state machine. */ +function toolState(page, tool) { + return page.evaluate((name) => { + const t = window.CascadeAPI._tools.tools[name]; + return { + state: t.state, + pressed: t.stagePressed, + height: t.height, + radius: t.radius, + activeTool: window.CascadeAPI._tools.activeToolName, + controls: window.threejsViewport.environment.controls.enabled, + }; + }, tool); +} + +test.describe('GUI Modeling Tools', () => { + test('toolbar renders with 6 tools, Select active, Escape returns to Select', async ({ page }) => { + await gotoAndReady(page); + + const buttons = page.locator('.cs-toolbar .cs-tool-btn'); + await expect(buttons).toHaveCount(6); + + const toolNames = await buttons.evaluateAll((els) => els.map((el) => el.dataset.tool)); + expect(toolNames).toEqual(['select', 'box', 'cylinder', 'sphere', 'sketch', 'fillet']); + + // Select is the default active tool + const active = await page.evaluate(() => + document.querySelector('.cs-tool-btn.cs-tool-active')?.dataset.tool + ); + expect(active).toBe('select'); + + // Clicking a tool button activates it; Escape returns to Select + await page.evaluate(() => window.CascadeAPI._tools.activate('box')); + expect(await page.evaluate(() => window.CascadeAPI._tools.activeToolName)).toBe('box'); + await page.evaluate(() => + window.dispatchEvent(new KeyboardEvent('keydown', { code: 'Escape', bubbles: true })) + ); + expect(await page.evaluate(() => window.CascadeAPI._tools.activeToolName)).toBe('select'); + }); + + test('Box tool: synthetic pointer drags emit code, evaluate, and round-trip', async ({ page }) => { + await gotoAndReady(page); + await runCodeAndRender(page, 'Box(10, 10, 10);', 1); + + // Drive the box tool: drag footprint (20,20)→(60,50), then height to 25 + const result = await page.evaluate((helpers) => { + eval(helpers); + window.CascadeAPI._tools.activate('box'); + const boxTool = window.CascadeAPI._tools.tools.box; + + fire('pointerdown', screenOfCad(20, 20, 0)); + fire('pointermove', screenOfCad(60, 50, 0)); + fire('pointerup', screenOfCad(60, 50, 0)); + const stateAfterFootprint = boxTool.state; + const controlsDuringDrag = window.threejsViewport.environment.controls.enabled; + fire('pointermove', screenOfCad(40, 35, 25)); + fire('pointerdown', screenOfCad(40, 35, 25)); // commit + fire('pointerup', screenOfCad(40, 35, 25)); + return { + stateAfterFootprint, + controlsDuringDrag, + stateAfterCommit: boxTool.state, + controlsAfterCommit: window.threejsViewport.environment.controls.enabled, + code: window.CascadeAPI.getCode(), + }; + }, POINTER_HELPERS); + + expect(result.stateAfterFootprint).toBe(2); // DRAG_HEIGHT + expect(result.controlsDuringDrag).toBe(false); // OrbitControls disabled mid-interaction + expect(result.stateAfterCommit).toBe(0); // back to IDLE + expect(result.controlsAfterCommit).toBe(true); + expect(result.code).toContain('Box('); + expect(result.code).toContain('let box1 = Translate([20, 20, 0], Box(40, 30, 25));'); + + // The commit triggered an evaluation — the scene gains a shape, no errors + await waitForToolEvaluation(page, 2); + + // Round-trip: the emitted editor code re-runs cleanly through runCode + const editorCode = await page.evaluate(() => window.CascadeAPI.getCode()); + const rerun = await page.evaluate((c) => window.CascadeAPI.runCode(c), editorCode); + expect(rerun.errors).toEqual([]); + expect(rerun.historySteps.length).toBeGreaterThanOrEqual(2); + }); + + // Regression: the old code only accepted "move, then click to commit" for + // the second stage. Pressing to drag the height cancelled the whole box. + // This uses Playwright's mouse API (real CDP input, unlike the synthetic + // PointerEvents above, which is why the old tests never caught it). + test('Box tool: real pointer press-drag-release drives BOTH stages', async ({ page }) => { + await gotoAndReady(page); + await runCodeAndRender(page, 'Box(10, 10, 10);', 1); + await page.evaluate(() => window.CascadeAPI._tools.activate('box')); + + const { a, b, up: c } = await groundDragPoints(page); + + // Stage 1: press, drag the footprint, release + await page.mouse.move(a.x, a.y); + await page.mouse.down(); + expect(await toolState(page, 'box')).toMatchObject({ state: 1, controls: false }); + await page.mouse.move(b.x, b.y, { steps: 8 }); + await page.mouse.up(); + expect(await toolState(page, 'box')).toMatchObject({ state: 2, controls: false }); + + // Stage 2: press to start the height drag. The old code cancelled here + // (state 0, controls re-enabled, nothing emitted). + await page.mouse.down(); + expect(await toolState(page, 'box')).toMatchObject({ + state: 2, pressed: true, controls: false, activeTool: 'box', + }); + + // ...drag up, then release to commit + await page.mouse.move(c.x, c.y, { steps: 8 }); + const dims = await page.evaluate(() => { + const t = window.CascadeAPI._tools.tools.box; + return { + w: Math.abs(t.cornerCAD[0] - t.baseCAD[0]), + d: Math.abs(t.cornerCAD[1] - t.baseCAD[1]), + h: t.height, + minX: Math.min(t.baseCAD[0], t.cornerCAD[0]), + minY: Math.min(t.baseCAD[1], t.cornerCAD[1]), + }; + }); + expect(dims.w).toBeGreaterThan(0); + expect(dims.d).toBeGreaterThan(0); + expect(dims.h).toBeGreaterThan(0); + await page.mouse.up(); + + // Committing is one-shot: the Box tool resets AND the manager returns to + // Select so the camera is usable again (ToolManager.commitCode). + expect(await toolState(page, 'box')).toMatchObject({ + state: 0, controls: true, activeTool: 'select', + }); + // The dragged dimensions are exactly what got emitted + const code = await page.evaluate(() => window.CascadeAPI.getCode()); + expect(code).toContain( + `let box1 = Translate([${dims.minX}, ${dims.minY}, 0], Box(${dims.w}, ${dims.d}, ${dims.h}));`); + + await waitForToolEvaluation(page, 2); + }); + + test('Cylinder tool: real pointer click-move-click drives both stages', async ({ page }) => { + await gotoAndReady(page); + await runCodeAndRender(page, 'Box(10, 10, 10);', 1); + await page.evaluate(() => window.CascadeAPI._tools.activate('cylinder')); + + const { a: center, b: rim, up: top } = await groundDragPoints(page); + + // Click the center (no drag) — must arm the radius stage, not cancel + await page.mouse.move(center.x, center.y); + await page.mouse.click(center.x, center.y); + expect(await toolState(page, 'cylinder')).toMatchObject({ state: 1, controls: false }); + + // Move to size the radius, click to lock it + await page.mouse.move(rim.x, rim.y, { steps: 5 }); + expect((await toolState(page, 'cylinder')).radius).toBeGreaterThan(0); + await page.mouse.click(rim.x, rim.y); + expect(await toolState(page, 'cylinder')).toMatchObject({ state: 2 }); + + // Move to size the height, click to commit + await page.mouse.move(top.x, top.y, { steps: 5 }); + const shape = await page.evaluate(() => { + const t = window.CascadeAPI._tools.tools.cylinder; + return { r: t.radius, h: t.height, cx: t.centerCAD[0], cy: t.centerCAD[1] }; + }); + expect(shape.h).toBeGreaterThan(0); + await page.mouse.click(top.x, top.y); + expect(await toolState(page, 'cylinder')).toMatchObject({ state: 0, controls: true }); + + const code = await page.evaluate(() => window.CascadeAPI.getCode()); + expect(code).toContain( + `let cylinder1 = Translate([${shape.cx}, ${shape.cy}, 0], Cylinder(${shape.r}, ${shape.h}));`); + + await waitForToolEvaluation(page, 2); + }); + + test('Fillet tool: edge click selects, commit emits FilletEdges with picked indices', async ({ page }) => { + await gotoAndReady(page); + await runCodeAndRender(page, 'Box(30, 30, 30);', 1); + + // Click a real edge (top edge midpoint of the box) with the fillet tool + const clicked = await page.evaluate((helpers) => { + eval(helpers); + window.CascadeAPI._tools.activate('fillet'); + const fillet = window.CascadeAPI._tools.tools.fillet; + const pt = screenOfCad(15, 0, 30); + fire('pointerdown', pt); + fire('pointerup', pt); + return { + selectionSize: fillet.selection.size, + selection: [...fillet.selection.values()], + panelVisible: fillet._panel && fillet._panel.style.display !== 'none', + }; + }, POINTER_HELPERS); + + expect(clicked.selectionSize).toBe(1); + expect(clicked.selection[0].shapeIndex).toBe(0); + expect(clicked.selection[0].localEdgeIndex).toBeGreaterThanOrEqual(0); + expect(clicked.panelVisible).toBe(true); + const pickedIndex = clicked.selection[0].localEdgeIndex; + + // Commit with radius 3 — the bare `Box(...)` line gets a variable, and + // a FilletEdges reassignment is appended using the picked edge index + const code = await page.evaluate(() => { + window.CascadeAPI._tools.tools.fillet.commit(3); + return window.CascadeAPI.getCode(); + }); + expect(code).toContain('let box1 = Box(30, 30, 30);'); + expect(code).toContain(`box1 = FilletEdges(box1, 3, [${pickedIndex}]);`); + + // The emitted fillet evaluates with no errors (verifies the hover/pick + // edge indices are exactly the ones FilletEdges consumes) + await waitForToolEvaluation(page, 1); + const steps = await page.evaluate(() => window.CascadeAPI.getHistorySteps()); + expect(steps.some((s) => s.fnName === 'FilletEdges')).toBe(true); + }); + + test('Sketch tool: multi-click profile with arc + fillet extrudes and round-trips', async ({ page }) => { + await gotoAndReady(page); + await runCodeAndRender(page, 'Box(10, 10, 10);', 1); + + // Draw a profile: two lines, a three-point arc (two clicks), a line, + // then close by clicking the first vertex; fillet one corner; extrude. + const result = await page.evaluate((helpers) => { + eval(helpers); + function click(x, y) { + const pt = screenOfCad(x, y, 0); + fire('pointerdown', pt); + fire('pointerup', pt); + } + function key(code) { + window.dispatchEvent(new KeyboardEvent('keydown', { code, bubbles: true })); + } + window.CascadeAPI._tools.activate('sketch'); + const sk = window.CascadeAPI._tools.tools.sketch; + + click(20, 5); // vertex 0 + click(35, 5); // vertex 1 (line) + key('KeyA'); // arc mode + click(42, 12); // arc through-point (click 1 of 2) + const pendingThrough = sk._pendingThrough && sk._pendingThrough.slice(); + click(35, 20); // vertex 2 = arc end (click 2 of 2) + key('KeyL'); // back to line mode + click(20, 20); // vertex 3 + click(20, 5); // close onto vertex 0 + const stateAfterClose = sk.state; + const panelVisible = sk._panel.style.display !== 'none'; + + click(20, 20); // toggle corner fillet on vertex 3 + const filletVerts = [...sk.filletVerts]; + click(20, 5); // vertex 0 must be refused + const filletVertsAfterV0 = [...sk.filletVerts]; + + sk._valueInput.value = '15'; + sk._valueInput.dispatchEvent(new Event('input')); + sk.commit(); + return { + pendingThrough, stateAfterClose, panelVisible, + filletVerts, filletVertsAfterV0, + stateAfterCommit: sk.state, + code: window.CascadeAPI.getCode(), + }; + }, POINTER_HELPERS); + + expect(result.pendingThrough).toEqual([42, 12]); + expect(result.stateAfterClose).toBe(2); // CLOSED + expect(result.panelVisible).toBe(true); + expect(result.filletVerts).toEqual([3]); + expect(result.filletVertsAfterV0).toEqual([3]); // start point not filletable + expect(result.stateAfterCommit).toBe(0); // back to IDLE + expect(result.code).toContain('new Sketch([20, 5])'); + expect(result.code).toContain('.ArcTo([42, 12], [35, 20])'); + expect(result.code).toContain('.LineTo([20, 20]).Fillet(3)'); + expect(result.code).toContain('.End(true).Face();'); + expect(result.code).toContain('let part1 = Extrude(profile1, [0, 0, 15]);'); + + // The emitted sketch evaluates with no errors (Box + extruded part) + await waitForToolEvaluation(page, 2); + + // Round-trip: the emitted editor code re-runs cleanly through runCode + const editorCode = await page.evaluate(() => window.CascadeAPI.getCode()); + const rerun = await page.evaluate((c) => window.CascadeAPI.runCode(c), editorCode); + expect(rerun.errors).toEqual([]); + }); + + test('Sketch tool: Escape steps back one stage at a time', async ({ page }) => { + await gotoAndReady(page); + await runCodeAndRender(page, 'Box(10, 10, 10);', 1); + + const result = await page.evaluate((helpers) => { + eval(helpers); + function click(x, y) { + const pt = screenOfCad(x, y, 0); + fire('pointerdown', pt); + fire('pointerup', pt); + } + function key(code) { + window.dispatchEvent(new KeyboardEvent('keydown', { code, bubbles: true })); + } + window.CascadeAPI._tools.activate('sketch'); + const sk = window.CascadeAPI._tools.tools.sketch; + const steps = []; + const snap = (label) => steps.push({ + label, + verts: sk.vertices.length, + pending: !!sk._pendingThrough, + state: sk.state, + tool: window.CascadeAPI._tools.activeToolName, + }); + + click(20, 5); click(35, 5); click(35, 20); + key('KeyA'); + click(42, 12); // half-placed arc through-point + snap('drawn'); + key('Escape'); snap('esc1'); // drops the through-point only + key('Escape'); snap('esc2'); // removes vertex 2 + key('Escape'); snap('esc3'); // removes vertex 1 + key('Escape'); snap('esc4'); // single vertex left → cancels the sketch + key('Escape'); snap('esc5'); // idle → back to Select + return steps; + }, POINTER_HELPERS); + + expect(result[0]).toMatchObject({ label: 'drawn', verts: 3, pending: true, state: 1 }); + expect(result[1]).toMatchObject({ label: 'esc1', verts: 3, pending: false, state: 1 }); + expect(result[2]).toMatchObject({ label: 'esc2', verts: 2, pending: false, state: 1 }); + expect(result[3]).toMatchObject({ label: 'esc3', verts: 1, pending: false, state: 1 }); + expect(result[4]).toMatchObject({ label: 'esc4', verts: 0, state: 0, tool: 'sketch' }); + expect(result[5]).toMatchObject({ label: 'esc5', tool: 'select' }); + }); + + test('Select tool: clicking a shape maps to its producing code line', async ({ page }) => { + await gotoAndReady(page); + await runCodeAndRender(page, 'Box(20, 20, 10);\nTranslate([40, 0, 0], Sphere(8));', 2); + + // The worker reports each sceneShape's producing line + const shapeLines = await page.evaluate(() => window.threejsViewport._shapeLines); + expect(shapeLines).toEqual([1, 2]); + + // Click the middle of the box's front face → line 1 flashes in Monaco + const result = await page.evaluate((helpers) => { + eval(helpers); + const pt = screenOfCad(10, 0, 5); // front face center of Box(20, 20, 10) + fire('pointerdown', pt); + fire('pointerup', pt); + const editor = window.cascadeApp.editor; + const decorations = editor._flashDecorations || []; + const model = editor.editor.getModel(); + const flashedLines = decorations.map((id) => + model.getDecorationRange(id)?.startLineNumber + ); + return { flashedLines }; + }, POINTER_HELPERS); + + expect(result.flashedLines).toEqual([1]); + }); +}); diff --git a/test/modes-and-urls.spec.js b/test/modes-and-urls.spec.js new file mode 100644 index 00000000..49a08079 --- /dev/null +++ b/test/modes-and-urls.spec.js @@ -0,0 +1,146 @@ +// @ts-check +// Editor language-mode defaults, starter code, and share-URL serialization. +// - a parameter-less load starts in Python (build123d) mode +// - share URLs carry `&mode=` so the language travels with the code +// - links WITHOUT `&mode=` predate mode serialization and must load as +// CascadeStudio JS (the Python default must not capture them) +const { test, expect } = require('@playwright/test'); + +const MODES = ['cascadestudio', 'openscad', 'python']; + +/** Sample code per mode, small enough to evaluate quickly. */ +const SAMPLE = { + cascadestudio: 'Box(3, 4, 5);', + openscad: 'cube([3, 4, 5]);', + python: 'from build123d import *\nshow(Box(3, 4, 5))\n', +}; + +async function waitForReady(page, timeout = 90000) { + await page.waitForFunction(() => window.CascadeAPI && window.CascadeAPI.isReady(), { timeout }); + await page.waitForFunction(() => !window.CascadeAPI.isWorking(), { timeout }); +} + +/** Load a URL (path + query) and wait for the first evaluation to settle. */ +async function load(page, url = '/') { + await page.goto(url); + await waitForReady(page); +} + +/** The mode-related state the app resolved for the current document. */ +function modeState(page) { + return page.evaluate(() => ({ + mode: window.CascadeAPI.getMode(), + select: document.getElementById('editorMode').value, + language: window.monacoEditor.getModel().getLanguageId(), + code: window.CascadeAPI.getCode(), + errors: window.CascadeAPI.getErrors(), + })); +} + +test.describe('Editor modes & share URLs', () => { + test('a fresh load defaults to Python mode with the build123d starter', async ({ page }) => { + await load(page); + + const state = await modeState(page); + expect(state.mode).toBe('python'); + expect(state.select).toBe('python'); // topnav switcher agrees + expect(state.language).toBe('python'); // Monaco tokenizer agrees + expect(state.code).toContain('# CascadeStudio build123d mode'); + expect(state.code).toContain('from build123d import *'); + expect(state.errors).toEqual([]); + + // The starter rendered exactly one solid + await page.waitForFunction(() => window.threejsViewport._shapeLines.length === 1, + null, { timeout: 90000 }); + }); + + test('every mode starter evaluates with zero errors', async ({ page }) => { + await load(page); + + for (const mode of MODES) { + const starter = await page.evaluate((m) => { + const app = window.cascadeApp; + const code = app.constructor.starterCode(m); + window.CascadeAPI.setMode(m); + window.CascadeAPI.setCode(code); + return code; + }, mode); + expect(starter.length).toBeGreaterThan(100); + + await page.evaluate(() => window.CascadeAPI.evaluate()); + await page.waitForFunction(() => !window.CascadeAPI.isWorking(), { timeout: 90000 }); + const errors = await page.evaluate(() => window.CascadeAPI.getErrors()); + expect(errors, `${mode} starter should evaluate cleanly`).toEqual([]); + await page.waitForFunction(() => window.threejsViewport._shapeLines.length > 0, + null, { timeout: 90000 }); + } + }); + + for (const mode of MODES) { + test(`share URL round-trips ${mode} code and mode`, async ({ page }) => { + await load(page); + + // Save-to-URL (the F5 / Ctrl+S path) writes code, gui state and mode + await page.evaluate(({ m, c }) => { + window.CascadeAPI.setMode(m); + window.CascadeAPI.setCode(c); + window.cascadeApp.editor.evaluateCode(true); + }, { m: mode, c: SAMPLE[mode] }); + await page.waitForFunction(() => !window.CascadeAPI.isWorking(), { timeout: 90000 }); + + const url = page.url(); + expect(url).toContain('mode=' + mode); + expect(url).toContain('code='); + + // A fresh page load of that URL restores both the mode and the code + await load(page, url); + const state = await modeState(page); + expect(state.mode).toBe(mode); + expect(state.select).toBe(mode); + expect(state.code).toBe(SAMPLE[mode]); + expect(state.errors).toEqual([]); + }); + } + + test('legacy URLs without &mode= still load as CascadeStudio JS', async ({ page }) => { + await load(page); + + // Build a pre-mode-serialization link exactly as old builds wrote them + const legacy = await page.evaluate(() => { + const App = window.cascadeApp.constructor; + return { + withGui: '/?code=' + App.encode('Box(12, 13, 14);') + '&gui=' + App.encode('{}'), + withoutGui: '/?code=' + App.encode('Sphere(9);'), + }; + }); + + await load(page, legacy.withGui); + let state = await modeState(page); + expect(state.mode).toBe('cascadestudio'); + expect(state.select).toBe('cascadestudio'); + expect(state.language).toBe('typescript'); + expect(state.code).toBe('Box(12, 13, 14);'); + expect(state.errors).toEqual([]); + + // ...and a link with no &gui= at all must not throw while loading + await load(page, legacy.withoutGui); + state = await modeState(page); + expect(state.mode).toBe('cascadestudio'); + expect(state.code).toBe('Sphere(9);'); + expect(state.errors).toEqual([]); + }); + + test('?mode= without &code= opens that mode\'s starter', async ({ page }) => { + await load(page, '/?mode=openscad'); + let state = await modeState(page); + expect(state.mode).toBe('openscad'); + expect(state.language).toBe('openscad'); + expect(state.code).toContain('Parametric Bolt and Nut'); + expect(state.errors).toEqual([]); + + // An unknown mode falls back to the fresh-load default + await load(page, '/?mode=fortran'); + state = await modeState(page); + expect(state.mode).toBe('python'); + }); +}); diff --git a/test/py-runtime.spec.js b/test/py-runtime.spec.js new file mode 100644 index 00000000..0fc68772 --- /dev/null +++ b/test/py-runtime.spec.js @@ -0,0 +1,68 @@ +// @ts-check +// The Python-interpreter flag: Python mode runs on Brython by default and on +// Pyodide (real CPython on wasm) with ?pyruntime=pyodide. Both execute the +// same Build123dLite.js source — see test/b123d-validation/runtime-comparison.md +// for the measurements behind keeping Brython as the default. +const { test, expect } = require('@playwright/test'); + +async function gotoAndReady(page, query = '') { + await page.goto('/' + query); + await page.waitForFunction(() => window.CascadeAPI && window.CascadeAPI.isReady(), + { timeout: 60000 }); + await page.waitForFunction(() => !window.CascadeAPI.isWorking(), { timeout: 60000 }); +} + +/** Pyodide is vendored, not a dependency (13.5 MB): skip when the build had + * nothing to copy (see packages/cascade-core/scripts/fetch-pyodide.cjs). */ +async function pyodideAvailable(page) { + return page.evaluate(async () => { + try { + const response = await fetch('pyodide/pyodide.mjs', { method: 'HEAD' }); + return response.ok; + } catch (e) { return false; } + }); +} + +test('Python runtime defaults to Brython and the flag selects Pyodide', async ({ page }) => { + await gotoAndReady(page); + expect(await page.evaluate(() => window.CascadeAPI.getPyRuntime())).toBe('brython'); + + await gotoAndReady(page, '?pyruntime=pyodide'); + expect(await page.evaluate(() => window.CascadeAPI.getPyRuntime())).toBe('pyodide'); + + // An unknown value must not silently become an experimental runtime. + await gotoAndReady(page, '?pyruntime=nonsense'); + expect(await page.evaluate(() => window.CascadeAPI.getPyRuntime())).toBe('brython'); + + // The default page boots Brython and nothing else: no CPython heap. + const stats = await page.evaluate(() => window.CascadeAPI._memoryStats()); + expect(stats.pyRuntime).toBe('brython'); + expect(stats.pythonWasm).toBe(0); + expect(stats.bootTiming.runtime).toBe('brython'); +}); + +test('?pyruntime=pyodide evaluates build123d-lite on CPython', async ({ page }) => { + await gotoAndReady(page, '?pyruntime=pyodide'); + test.skip(!(await pyodideAvailable(page)), + 'vendor/pyodide is absent — run packages/cascade-core/scripts/fetch-pyodide.cjs'); + + const result = await page.evaluate((code) => window.CascadeAPI.runCode(code), ` +from build123d import * +import sys +part = Box(10, 10, 10) - Cylinder(2, 20) +print("impl", sys.implementation.name, "volume", round(part.volume, 3)) +show(part) +`); + expect(result.errors).toEqual([]); + await page.waitForFunction( + () => window.CascadeAPI.getConsoleLog().some((l) => l.startsWith('impl ')), + { timeout: 90000 }); + const logs = await page.evaluate(() => window.CascadeAPI.getConsoleLog()); + // Real CPython, and the geometry went through the same worker CAD calls. + expect(logs.find((l) => l.startsWith('impl '))).toContain('impl cpython volume 874.336'); + + const stats = await page.evaluate(() => window.CascadeAPI._memoryStats()); + expect(stats.pyRuntime).toBe('pyodide'); + expect(stats.bootTiming.runtime).toBe('pyodide'); + expect(stats.pythonWasm).toBeGreaterThan(16 * 1024 * 1024); +}); diff --git a/test/python-mode-canonical.spec.js b/test/python-mode-canonical.spec.js new file mode 100644 index 00000000..94385453 --- /dev/null +++ b/test/python-mode-canonical.spec.js @@ -0,0 +1,331 @@ +// @ts-check +// Freeze tests for build123d-lite's CANONICAL free-edge parametrization +// (Build123dLite.js `canonical_form` / `Curve.canonical` / `Axis(edge, +// canonical=True)` / `Edge.make_mid_way` / the `ShapeList.sort_by` tie break), +// a port of the upstream canonical-free-edges proposal in +// the research record on zalo/build123d +// branch canonical-research (research/). +// +// The rule: OPEN shapes start at the lexicographically smaller end point; +// CLOSED shapes start at the arc-length midpoint of the extremal band +// {x <= x_min + 1e-6 * bbox} (x -> y -> z fall-through for loops that are flat +// in a coordinate) and wind counter-clockwise about the dominant axis of the +// loop's area vector; positions are normalized arc length. +// +// Cross-kernel evidence that these numbers are the same ones patched upstream +// build123d produces on OCP 7.9.3 lives in +// test/b123d-validation/canonical-cross-kernel.mjs (not part of this suite). +const { test, expect } = require('@playwright/test'); + +async function gotoPythonMode(page) { + await page.goto('/'); + await page.waitForFunction(() => window.CascadeAPI && window.CascadeAPI.isReady(), + { timeout: 60000 }); + await page.waitForFunction(() => !window.CascadeAPI.isWorking(), { timeout: 60000 }); + await page.evaluate(() => window.CascadeAPI.setMode('python')); +} + +/** Run a Python snippet that prints "KEY v0 v1 ..." lines and return them as a + * map of key -> number[] (or the raw string when not numeric). The first + * Python evaluation also fetches + boots Brython, hence the long timeout. */ +async function runAndCollect(page, body, timeout = 180000) { + const code = [ + 'from build123d import *', + 'def p(tag, *vals):', + ' out = [tag]', + ' for v in vals:', + ' if isinstance(v, (int, float)) and not isinstance(v, bool):', + ' out.append(repr(round(float(v), 6)))', + ' elif isinstance(v, (list, tuple)) or hasattr(v, "__iter__"):', + ' for c in tuple(v):', + ' out.append(repr(round(float(c), 6)))', + ' else:', + ' out.append(str(v))', + " print(' '.join(out))", + '', + body, + 'print("CANON_TESTS_DONE")', + ].join('\n'); + + await page.evaluate((c) => window.CascadeAPI.runCode(c), code); + await page.waitForFunction( + () => window.CascadeAPI.getConsoleLog().some((l) => l.includes('CANON_TESTS_DONE')) || + window.CascadeAPI.getErrors().length > 0, + undefined, { timeout } + ); + const errors = await page.evaluate(() => window.CascadeAPI.getErrors()); + expect(errors).toEqual([]); + const logs = await page.evaluate(() => window.CascadeAPI.getConsoleLog()); + const out = {}; + for (const line of logs) { + const parts = line.trim().split(/\s+/); + if (parts.length < 2) continue; + const values = parts.slice(1).map(Number); + out[parts[0]] = values.some(Number.isNaN) ? parts.slice(1).join(' ') : values; + } + return out; +} + +function expectClose(actual, expected, digits = 4) { + expect(actual).toBeDefined(); + expect(actual.length).toBe(expected.length); + for (let i = 0; i < expected.length; i++) { + expect(actual[i]).toBeCloseTo(expected[i], digits); + } +} + +test.describe('Python mode: canonical free-edge parametrization', () => { + test('the rule itself, on inputs that need no CAD kernel', async ({ page }) => { + await gotoPythonMode(page); + const got = await runAndCollect(page, [ + // lexicographic_key orders x, then y, then z + 'p("LEX_X", 1 if lexicographic_key(Vector(-1, 5, 5)) < lexicographic_key(Vector(0, 0, 0)) else 0)', + 'p("LEX_Y", 1 if lexicographic_key(Vector(0, -1, 9)) < lexicographic_key(Vector(0, 0, 0)) else 0)', + 'p("LEX_Z", 1 if lexicographic_key(Vector(0, 0, -1)) < lexicographic_key(Vector(0, 0, 0)) else 0)', + // Newell area of a unit square in the XY plane, both windings + 'square = [Vector(0, 0, 0), Vector(1, 0, 0), Vector(1, 1, 0), Vector(0, 1, 0)]', + 'p("AREA_CCW", loop_area_vector(square))', + 'p("AREA_CW", loop_area_vector(square[::-1]))', + // the rule only needs "the point at arc length d", so a polyline drives it + 'corners = [Vector(-1, -1, 0), Vector(1, -1, 0), Vector(1, 1, 0), Vector(-1, 1, 0)]', + 'perimeter = 8.0', + 'def sampler(d):', + ' d = d % perimeter', + ' i = int(d // 2)', + ' t = (d - 2 * i) / 2', + ' a, b = corners[i], corners[(i + 1) % 4]', + ' return a + (b - a) * t', + 'form = canonical_form(sampler, perimeter, True)', + 'p("SQUARE_SIGN", form.sign)', + 'p("SQUARE_SEAM", sampler(form.start * perimeter))', + 'p("SAMPLES_BAND", CANONICAL_SAMPLES, CANONICAL_BAND)', + // CanonicalForm.position maps canonical u -> the shape's own u + 'p("FORM_POS", CanonicalForm(0.25, -1, True).position(0.5),' + + ' CanonicalForm(0.0, -1, False).position(0.25))', + 'show(Box(1, 1, 1))', + ].join('\n')); + + expectClose(got.LEX_X, [1]); + expectClose(got.LEX_Y, [1]); + expectClose(got.LEX_Z, [1]); + expectClose(got.AREA_CCW, [0, 0, 1], 6); + expectClose(got.AREA_CW, [0, 0, -1], 6); + // counter-clockwise about +Z, seam at the middle of the flat x = -1 side + expectClose(got.SQUARE_SIGN, [1]); + expectClose(got.SQUARE_SEAM, [-1, 0, 0]); + // defaults must match the upstream patch + expectClose(got.SAMPLES_BAND, [512, 1e-6], 12); + // closed: (0.25 - 1*0.5) % 1 = 0.75; open reversed: 1 - 0.25 = 0.75 + expectClose(got.FORM_POS, [0.75, 0.75], 6); + }); + + test('open shapes start at the lexicographically smaller end; closed shapes ' + + 'at the extremal band midpoint, winding CCW', async ({ page }) => { + await gotoPythonMode(page); + const got = await runAndCollect(page, [ + // --- open: all three spellings of the same segment canonicalize alike + 'fwd = Edge.make_line((0, 0, 0), (10, 0, 0))', + 'bwd = Edge.make_line((10, 0, 0), (0, 0, 0))', + 'rev = fwd.reversed()', + 'for name, e in (("FWD", fwd), ("BWD", bwd), ("REV", rev)):', + ' c = e.canonical()', + ' p("OPEN_" + name, tuple(c.position_at(0)) + tuple(c.tangent_at(0)))', + 'p("OPEN_FORM", fwd.canonical_form().start, fwd.canonical_form().sign,' + + ' 1 if fwd.canonical_form().closed else 0)', + // --- Axis(edge): raw by default (disagrees with position_at on a + // REVERSED edge), canonical only when asked + 'p("AXIS_RAW", tuple(Axis(rev).position) + tuple(rev.position_at(0)))', + 'p("AXIS_CANON", tuple(Axis(rev, canonical=True).position)' + + ' + tuple(Axis(rev, canonical=True).direction))', + // --- closed circle: seam at x = -R, CCW about +Z + 'circle = Circle(10, mode=Mode.PRIVATE).edges()[0]', + 'for name, s in (("CIRCLE", circle), ("CIRCLE_REV", circle.reversed())):', + ' c = s.canonical()', + ' p(name, tuple(c.position_at(0)) + tuple(c.position_at(0.25)) + (c.length,))', + // --- closed wire with a STRAIGHT extremal side: the band midpoint is the + // middle of that side, which unlike a corner has a defined tangent + 'rect = Wire(Rectangle(20, 10, mode=Mode.PRIVATE).edges())', + 'rc = rect.canonical()', + 'p("RECT", tuple(rc.position_at(0)) + tuple(rc.tangent_at(0)) + (rc.length,))', + // --- idempotent + 'for name, s in (("IDEM_CIRCLE", circle), ("IDEM_RECT", rect)):', + ' once = s.canonical()', + ' twice = once.canonical()', + ' p(name, tuple(once.position_at(0)) + tuple(twice.position_at(0))' + + ' + tuple(once.position_at(0.3)) + tuple(twice.position_at(0.3)))', + // --- sort_by: the DEFAULT keeps the incoming order on ties (a stable + // sort, which chained sorts rely on); tie_break=True resolves them + // geometrically instead + 'ties = [Edge.make_line((0, -5, 10), (10, -5, 10)),' + + ' Edge.make_line((0, 5, 10), (10, 5, 10))]', + 'p("TIE_DEFAULT", tuple(ShapeList(ties).sort_by(Axis.Z)[0].center())' + + ' + tuple(ShapeList(ties[::-1]).sort_by(Axis.Z)[0].center()))', + 'p("TIE_BREAK", tuple(ShapeList(ties).sort_by(Axis.Z, tie_break=True)[0].center())' + + ' + tuple(ShapeList(ties[::-1]).sort_by(Axis.Z, tie_break=True)[0].center()))', + // chained sorts must survive the default (the heat_exchanger.py idiom) + 'radii = [Edge.make_line((0, 0, 0), (3, 0, 0)), Edge.make_line((0, 1, 0), (1, 1, 0)),' + + ' Edge.make_line((0, 2, 0), (2, 2, 0))]', + 'p("CHAINED", [e.length for e in' + + ' ShapeList(radii).sort_by(SortBy.LENGTH).sort_by(Axis.Z)])', + 'show(Box(1, 1, 1))', + ].join('\n')); + + for (const name of ['OPEN_FWD', 'OPEN_BWD', 'OPEN_REV']) { + expectClose(got[name], [0, 0, 0, 1, 0, 0], 6); + } + expectClose(got.OPEN_FORM, [0, 1, 0], 6); + // legacy Axis(edge) reads the underlying curve at its first parameter + expectClose(got.AXIS_RAW, [0, 0, 0, /* position_at(0) */ 10, 0, 0], 6); + expectClose(got.AXIS_CANON, [0, 0, 0, 1, 0, 0], 6); + // circle: seam (-10, 0, 0); a quarter later, CCW about +Z, is (0, -10, 0) + expectClose(got.CIRCLE, [-10, 0, 0, 0, -10, 0, 2 * Math.PI * 10]); + expectClose(got.CIRCLE_REV, [-10, 0, 0, 0, -10, 0, 2 * Math.PI * 10]); + // 20 x 10 rectangle: seam mid-way up the x = -10 side, heading -Y + expectClose(got.RECT, [-10, 0, 0, 0, -1, 0, 60], 6); + expectClose(got.IDEM_CIRCLE.slice(0, 3), got.IDEM_CIRCLE.slice(3, 6), 5); + expectClose(got.IDEM_CIRCLE.slice(6, 9), got.IDEM_CIRCLE.slice(9, 12), 5); + expectClose(got.IDEM_RECT.slice(0, 3), got.IDEM_RECT.slice(3, 6), 5); + expectClose(got.IDEM_RECT.slice(6, 9), got.IDEM_RECT.slice(9, 12), 5); + // default: ties carry the incoming order, so reversing the input reverses + // the result (y = -5 first vs y = +5 first) + expectClose(got.TIE_DEFAULT, [5, -5, 10, 5, 5, 10], 6); + // tie_break=True: geometry decides, so both orderings agree + expectClose(got.TIE_BREAK.slice(0, 3), got.TIE_BREAK.slice(3, 6), 6); + // a chained sort keeps the inner (length) order inside the tied Z group + expectClose(got.CHAINED, [1, 2, 3], 6); + }); + + test('a closed section loop canonicalizes to hand-computed values and is ' + + 'independent of the operands\' parametric frames', async ({ page }) => { + await gotoPythonMode(page); + const got = await runAndCollect(page, [ + // examples/projection.py Example 3: sphere(R50) cut by a cylinder(r80) + // lying along X at (y = 0, z = -70). Rotating the sphere about its OWN + // axis is the geometrically identical solid but moves the sphere's u = 0 + // meridian, and with it the kernel's seam. + 'def arch(rotation):', + ' sphere = Solid.make_sphere(50)', + ' if rotation:', + ' sphere = sphere.rotate(Axis.Z, rotation)', + ' cutter = Solid.make_cylinder(80, 100, Plane.YZ).locate(Location((-50, 0, -70)))', + ' return sphere.cut(cutter).edges().sort_by(Axis.Z)[0]', + 'for rotation in (0, 45, 90, 180, 270):', + ' e = arch(rotation)', + ' c = e.canonical()', + ' p("RAW_" + str(rotation), e.position_at(0))', + ' p("CANON_" + str(rotation), tuple(c.position_at(0)) + tuple(c.position_at(0.25))' + + ' + tuple(c.position_at(0.5)) + tuple(c.position_at(0.75)) + (c.length,))', + // joints.py's slider axis: two top edges with EQUAL Axis.Z sort keys, + // measured with make_mid_way. Needs the canonical edge traversal AND the + // deterministic sort_by tie break. + 'for rotation in (0, 90, 180):', + ' with BuildPart() as part:', + ' with BuildSketch():', + ' Rectangle(10, 10)', + ' extrude(amount=10, taper=3)', + ' Cylinder(2.5, 10, rotation=(0, 90, rotation), mode=Mode.SUBTRACT)', + // selecting the two TIED top edges deterministically is the caller's + // half of the fix, hence tie_break=True (exactly as upstream states) + ' top = part.part.edges().filter_by(Axis.X, tolerance=30)' + + '.sort_by(Axis.Z, tie_break=True)[-2:]', + ' m = Edge.make_mid_way(top[0], top[1], 0.67)', + ' p("MIDWAY_" + str(rotation), tuple(m.position_at(0)) + tuple(m.position_at(1)))', + 'show(Box(1, 1, 1))', + ].join('\n')); + + // The loop satisfies y^2 + (z + 70)^2 = 6400 and x^2 + y^2 + z^2 = 2500, + // hence x^2 = 1000 + 140 z with -1000/140 <= z <= 10. So: + // * x is smallest at z = 10 (where y = 0): the seam is UNIQUE at + // (-sqrt(2400), 0, 10) = (-48.98979, 0, 10); + // * the loop's area vector is dominated by its XY projection, which winds + // counter-clockwise about +Z, so a quarter turn on from the seam is the + // x = 0 point with NEGATIVE y: z = -1000/140 = -7.142857 and + // y = -sqrt(6400 - (70 - 1000/140)^2) = -49.487166; + // * half a turn on is the seam's mirror (+48.98979, 0, 10) and three + // quarters is (0, +49.487166, -7.142857). + const seam = [-Math.sqrt(2400), 0, 10]; + const quarter = [0, -Math.sqrt(6400 - (70 - 1000 / 140) ** 2), -1000 / 140]; + const half = [Math.sqrt(2400), 0, 10]; + const threeQuarters = [0, -quarter[1], -1000 / 140]; + const expected = [...seam, ...quarter, ...half, ...threeQuarters, 320.9223]; + + // the RAW seam follows the sphere's meridian - up to 98 mm of travel + expectClose(got.RAW_0, [48.9898, 0, 10]); + expectClose(got.RAW_45, [35.3331, 35.3331, 1.7745]); + expectClose(got.RAW_90, [0, 49.4872, -7.1429]); + expectClose(got.RAW_180, [-48.9898, 0, 10]); + expectClose(got.RAW_270, [0, -49.4872, -7.1429]); + + // ... and canonicalizing pins every frame to the same hand-computed values + for (const rotation of [0, 45, 90, 180, 270]) { + expectClose(got[`CANON_${rotation}`], expected, 3); + } + + // joints: same slider axis whatever frame the cutter was created in + for (const rotation of [0, 90, 180]) { + expectClose(got[`MIDWAY_${rotation}`], + [-4.47592, 1.52181, 10, 4.47592, 1.52181, 10], 4); + } + }); + + test('a reassembled section loop canonicalizes the same way from every frame ' + + 'AND from either traversal', async ({ page }) => { + await gotoPythonMode(page); + const got = await runAndCollect(page, [ + // sphere(10) cut by cylinder(r5) standing at x = 6: the section locus is + // one closed loop that the kernel delivers as 1, 2 or 4 Edges depending on + // the sphere's frame, so it has to be reassembled into a Wire first + // (upstream's recipe for the "different NUMBER of edges" case). This is + // the loop whose extremal band comes in a MIRROR-SYMMETRIC PAIR, and it is + // the regression that found the three seam defects fixed in the patch: + // before them, reversing this very Wire canonicalized to the other seam of + // the loop, winding the other way (reproducible on OCP 7.9.3 alone). + 'def section_loop(rotation):', + ' sphere = Solid.make_sphere(10)', + ' if rotation:', + ' sphere = sphere.rotate(Axis.Z, rotation)', + ' cutter = Solid.make_cylinder(5, 40, Plane.XY.offset(-20))' + + '.locate(Location((6, 0, 0)))', + ' loop = [e for e in sphere.cut(cutter).edges()' + + ' if e.geom_type == GeomType.BSPLINE]', + ' return max(edges_to_wires(loop), key=lambda wr: wr.length)', + 'for rotation in (0, 37, 45, 90, 180, 270):', + ' wire = section_loop(rotation)', + ' for tag, shape in (("F", wire), ("R", wire.reversed())):', + ' c = shape.canonical()', + ' p("LOOP_" + str(rotation) + tag, tuple(c.position_at(0))' + + ' + tuple(c.position_at(0.25)) + tuple(c.position_at(0.5)) + (c.length,))', + // a shape whose seam is already its own start must come back UNTOUCHED — + // the "already canonical" test is a circular distance judged at band-width + // resolution, not form.start against TOLERANCE/length + 'for name, shape in (("LOOP", section_loop(0).canonical()),', + ' ("CIRCLE", Circle(10, mode=Mode.PRIVATE).edges()[0].canonical()),', + ' ("RECT", Wire(Rectangle(20, 10, mode=Mode.PRIVATE).edges()).canonical())):', + ' form = shape.canonical_form()', + ' wrapped = form.start % 1.0', + ' box = shape.bounding_box()', + ' resolution = max(1e-6,' + + ' CANONICAL_BAND * max(box.size.X, box.size.Y, box.size.Z))', + ' p("IDENT_" + name,' + + ' 1 if min(wrapped, 1.0 - wrapped) * shape.length <= resolution else 0,', + ' 1 if shape.canonical() is shape else 0)', + 'show(Box(1, 1, 1))', + ].join('\n')); + + // The loop's extremal band in x is the PAIR {(1, 0, +9.9499), (1, 0, -9.9499)} + // — mirror images, so they tie on y once quantised to the band width and z + // decides: the seam is the NEGATIVE one. A quarter turn on (counter-clockwise + // about the dominant axis of the area vector, which is X here) is the loop's + // z = 0 turning point at +y. + const expected = [1, 0, -9.9499, 9.25, 3.7997, 0, 1, 0, 9.9499, 65.027]; + for (const rotation of [0, 37, 45, 90, 180, 270]) { + for (const tag of ['F', 'R']) { + expectClose(got[`LOOP_${rotation}${tag}`], expected, 3); + } + } + for (const name of ['LOOP', 'CIRCLE', 'RECT']) { + // seam already at the start, and canonical() returned the very same object + expectClose(got[`IDENT_${name}`], [1, 1]); + } + }); +}); diff --git a/test/python-mode-examples.spec.js b/test/python-mode-examples.spec.js new file mode 100644 index 00000000..4b05f6d3 --- /dev/null +++ b/test/python-mode-examples.spec.js @@ -0,0 +1,890 @@ +// @ts-check +// Regression tests: representative REAL build123d scripts frozen from the +// validation harness (test/b123d-validation) at build123d 0.11.1. +// Each test runs the upstream script (Apache-2.0, from the build123d repo's +// docs/examples) through Python mode and asserts the volumes of its +// module-level shapes against the values REAL build123d produced natively +// (0.5% relative tolerance — the harness's PASS criterion). +// +// Regenerate expectations with: test/b123d-validation/run.sh +const { test, expect } = require('@playwright/test'); + +/** Measurement footer — same convention as test/b123d-validation/run-lite.mjs */ +const FOOTER = '\n\nimport build123d as _m\nprint("B123D_MEASURE " + _m._measure_globals_json(globals()))\n'; + +async function gotoAndReady(page) { + await page.goto('/'); + await page.waitForFunction(() => window.CascadeAPI && window.CascadeAPI.isReady(), undefined, { timeout: 90000 }); + await page.waitForFunction(() => !window.CascadeAPI.isWorking(), undefined, { timeout: 90000 }); + await page.evaluate(() => window.CascadeAPI.setMode('python')); +} + +async function runAndMeasure(page, code) { + await page.evaluate((c) => window.CascadeAPI.runCode(c), code + FOOTER); + await page.waitForFunction( + () => window.CascadeAPI.getConsoleLog().some((l) => l.startsWith('B123D_MEASURE ')), + undefined, { timeout: 90000 }); + const logs = await page.evaluate(() => window.CascadeAPI.getConsoleLog()); + const payload = logs.find((l) => l.startsWith('B123D_MEASURE ')).slice('B123D_MEASURE '.length); + // the console panel stores JSON.stringify(arg) minus outer quotes + try { return JSON.parse(payload); } catch (e) { return JSON.parse(JSON.parse('"' + payload + '"')); } +} + +test.describe('Python mode: frozen build123d example scripts', () => { + // Heavy scripts (hex-array booleans, Gordon-surface projection) exceed the + // default 120s on slow CI runners (SwiftShader WebGL, 2 cores) — the + // push-event run of bb1ce0f timed out on heat_exchanger/bracelet while the + // pull_request run passed on a faster machine. Give every frozen example + // generous headroom; wall-clock locally is unaffected (they finish early). + test.setTimeout(360000); + + // general_examples/ex02 — builder mode: Box + Mode.SUBTRACT Cylinder + test("general_examples/ex02", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\nfrom math import *\n\n# 2. Plane with Hole\n# [Ex. 2]\nlength, width, thickness = 80.0, 60.0, 10.0\ncenter_hole_dia = 22.0\n\nwith BuildPart() as ex2:\n Box(length, width, thickness)\n Cylinder(radius=center_hole_dia / 2, height=thickness, mode=Mode.SUBTRACT)\n # [Ex. 2]\n# [removed by collect.py] write_svg()\n\n# show_object(ex2.part)\n"); + // real build123d: ex2.volume == 44198.67288915635 + expect(Math.abs(measured["ex2"].volume - 44198.67288915635)) + .toBeLessThan(44198.67288915635 * 0.005); + }); + + // general_examples/ex08 — BuildLine polyline + mirror + make_face + extrude + test("general_examples/ex08", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\nfrom math import *\n\n# 8. Polylines\n# [Ex. 8]\n(L, H, W, t) = (100.0, 20.0, 20.0, 1.0)\npts = [\n (0, H / 2.0),\n (W / 2.0, H / 2.0),\n (W / 2.0, (H / 2.0 - t)),\n (t / 2.0, (H / 2.0 - t)),\n (t / 2.0, (t - H / 2.0)),\n (W / 2.0, (t - H / 2.0)),\n (W / 2.0, H / -2.0),\n (0, H / -2.0),\n]\n\nwith BuildPart() as ex8:\n with BuildSketch(Plane.YZ) as ex8_sk:\n with BuildLine() as ex8_ln:\n Polyline(pts)\n mirror(ex8_ln.line, about=Plane.YZ)\n make_face()\n extrude(amount=L)\n # [Ex. 8]\n# [removed by collect.py] write_svg()\n\n# show_object(ex8.part)\n"); + // real build123d: ex8.volume == 5800.0 + expect(Math.abs(measured["ex8"].volume - 5800.0)) + .toBeLessThan(5800.0 * 0.005); + }); + + // general_examples/ex11 — chamfer/fillet selectors, Select.LAST, Hole, BuildSketch on a face, GridLocations, RegularPolygon + test("general_examples/ex11", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\nfrom math import *\n\n# 11. Use a face as workplane for BuildSketch and introduce GridLocations\n# [Ex. 11]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex11:\n Box(length, width, thickness)\n chamfer(ex11.edges().group_by(Axis.Z)[-1], length=4)\n fillet(ex11.edges().filter_by(Axis.Z), radius=5)\n Hole(radius=width / 4)\n fillet(ex11.edges(Select.LAST).sort_by(Axis.Z)[-1], radius=2)\n with BuildSketch(ex11.faces().sort_by(Axis.Z)[-1]) as ex11_sk:\n with GridLocations(length / 2, width / 2, 2, 2):\n RegularPolygon(radius=5, side_count=5)\n extrude(amount=-thickness, mode=Mode.SUBTRACT)\n # [Ex. 11]\n# [removed by collect.py] write_svg()\n\n# show_object(ex11)\n"); + // real build123d: ex11.volume == 36177.36505728397 + expect(Math.abs(measured["ex11"].volume - 36177.36505728397)) + .toBeLessThan(36177.36505728397 * 0.005); + }); + + // general_examples/ex13 — Locations(face) + PolarLocations + CounterSink/CounterBore holes + test("general_examples/ex13", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\nfrom math import *\n\n# 13. CounterBoreHoles, CounterSinkHoles and PolarLocations\n# [Ex. 13]\na, b = 40, 4\nwith BuildPart() as ex13:\n Cylinder(radius=50, height=10)\n with Locations(ex13.faces().sort_by(Axis.Z)[-1]):\n with PolarLocations(radius=a, count=4):\n CounterSinkHole(radius=b, counter_sink_radius=2 * b)\n with PolarLocations(radius=a, count=4, start_angle=45, angular_range=360):\n CounterBoreHole(radius=b, counter_bore_radius=2 * b, counter_bore_depth=b)\n # [Ex. 13]\n# [removed by collect.py] write_svg()\n\n# show_object(ex13.part)\n"); + // real build123d: ex13.volume == 70872.25969468078 + expect(Math.abs(measured["ex13"].volume - 70872.25969468078)) + .toBeLessThan(70872.25969468078 * 0.005); + }); + + // general_examples/ex14 — JernArc + position/tangent (@/%) + sweep along a BuildLine path + test("general_examples/ex14", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\nfrom math import *\n\n# 14. Position on a line with '@', '%' and introduce sweep\n# [Ex. 14]\na, b = 40, 20\n\nwith BuildPart() as ex14:\n with BuildLine() as ex14_ln:\n l1 = JernArc(start=(0, 0), tangent=(0, 1), radius=a, arc_size=180)\n l2 = JernArc(start=l1 @ 1, tangent=l1 % 1, radius=a, arc_size=-90)\n l3 = Line(l2 @ 1, l2 @ 1 + (-a, a))\n with BuildSketch(Plane.XZ) as ex14_sk:\n Rectangle(b, b)\n sweep()\n # [Ex. 14]\n# [removed by collect.py] write_svg()\n\n# show_object(ex14.part)\n"); + // real build123d: ex14.volume == 91398.2236861551 + expect(Math.abs(measured["ex14"].volume - 91398.2236861551)) + .toBeLessThan(91398.2236861551 * 0.005); + }); + + // general_examples/ex22 — Plane(face).rotated + GridLocations + extrude(both=True, Mode.SUBTRACT) + test("general_examples/ex22", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\nfrom math import *\n\n# 22. Rotated Workplanes\n# [Ex. 22]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex22:\n Box(length, width, thickness)\n pln = Plane(ex22.faces().group_by(Axis.Z)[0][0]).rotated((0, -50, 0))\n with BuildSketch(pln) as ex22_sk:\n with GridLocations(length / 4, width / 4, 2, 2):\n Circle(thickness / 4)\n extrude(amount=-100, both=True, mode=Mode.SUBTRACT)\n # [Ex. 22]\n# [removed by collect.py] write_svg()\n\n# show_object(ex22.part)\n"); + // real build123d: ex22.volume == 46778.13736363063 + expect(Math.abs(measured["ex22"].volume - 46778.13736363063)) + .toBeLessThan(46778.13736363063 * 0.005); + }); + + // general_examples/ex27 — BuildSketch on a face + split(bisect_by=offset plane) + test("general_examples/ex27", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\nfrom math import *\n\n# 27. Splitting an Object\n# [Ex. 27]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex27:\n Box(length, width, thickness)\n with BuildSketch(ex27.faces().sort_by(Axis.Z)[0]) as ex27_sk:\n Circle(width / 4)\n extrude(amount=-thickness, mode=Mode.SUBTRACT)\n split(bisect_by=Plane(ex27.faces().sort_by(Axis.Y)[-1]).offset(-width / 2))\n # [Ex. 27]\n# [removed by collect.py] write_svg()\n\n# show_object(ex27.part)\n"); + // real build123d: ex27.volume == 20465.708264711477 + expect(Math.abs(measured["ex27"].volume - 20465.708264711477)) + .toBeLessThan(20465.708264711477 * 0.005); + }); + + // general_examples_algebra/ex19 — algebra extrude, face/vertex selectors, Pos placement + test("general_examples_algebra/ex19", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\nfrom math import *\n\n# 19. Locating a Workplane on a vertex\n# [Ex. 19]\nlength, thickness = 80.0, 10.0\n\nex19_sk = RegularPolygon(radius=length / 2, side_count=7)\nex19 = extrude(ex19_sk, thickness)\n\ntopf = ex19.faces().sort_by().last\n\nvtx = topf.vertices().group_by(Axis.X)[-1][0]\n\nvtx2Axis = Axis((0, 0, 0), (-1, -0.5, 0))\nvtx2 = topf.vertices().sort_by(vtx2Axis)[-1]\n\nex19_sk2 = Circle(radius=length / 8)\nex19_sk2 = Pos(vtx.X, vtx.Y) * ex19_sk2 + Pos(vtx2.X, vtx2.Y) * ex19_sk2\n\nex19 -= extrude(ex19_sk2, thickness)\n# [Ex. 19]\n# show_object(ex19)\n"); + // real build123d: ex19.volume == 41538.56826564553 + expect(Math.abs(measured["ex19"].volume - 41538.56826564553)) + .toBeLessThan(41538.56826564553 * 0.005); + }); + + // general_examples_algebra/ex24 — Plane(face), plane.offset, loft over a Sketch list + test("general_examples_algebra/ex24", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\nfrom math import *\n\n# 24. Lofts\n# [Ex. 24]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nex24 = Box(length, length, thickness)\nplane = Plane(ex24.faces().sort_by().last)\n\nfaces = Sketch() + [\n plane * Circle(length / 3),\n plane.offset(length / 2) * Rectangle(length / 6, width / 6),\n]\n\nex24 += loft(faces)\n# [Ex. 24]\n# show_object(ex24)\n"); + // real build123d: ex24.volume == 102969.87958520795 + expect(Math.abs(measured["ex24"].volume - 102969.87958520795)) + .toBeLessThan(102969.87958520795 * 0.005); + }); + + // examples/pillow_block_algebra — 2D vertex fillets + CounterBoreHoles + GridLocations (real-world part) + test("examples/pillow_block_algebra", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\n\nheight, width, thickness, padding = 60, 80, 10, 12\nscrew_shaft_radius, screw_head_radius, screw_head_height = 1.5, 3, 3\nbearing_axle_radius, bearing_radius, bearing_thickness = 4, 11, 7\n\n# Build pillow block as an extruded sketch with counter bore holes\nplan = Rectangle(width, height)\nplan = fillet(plan.vertices(), radius=5)\npillow_block = extrude(plan, thickness)\n\nplane = Plane(pillow_block.faces().sort_by().last)\n\npillow_block -= plane * CounterBoreHole(\n bearing_axle_radius, bearing_radius, bearing_thickness, height\n)\nlocs = GridLocations(width - 2 * padding, height - 2 * padding, 2, 2)\npillow_block -= (\n plane\n * locs\n * CounterBoreHole(screw_shaft_radius, screw_head_radius, screw_head_height, height)\n)\n\n# Render the part\nif \"show_object\" in locals():\n show_object(pillow_block)\n"); + // real build123d: pillow_block.volume == 44436.460392133944 + expect(Math.abs(measured["pillow_block"].volume - 44436.460392133944)) + .toBeLessThan(44436.460392133944 * 0.005); + }); + + + // examples/heat_exchanger — HexLocations, SortBy.RADIUS, tube arrays (was a timeout on the old bbox hook) + test("examples/heat_exchanger", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nexchanger_diameter = 10 * CM\nexchanger_length = 30 * CM\nplate_thickness = 5 * MM\n# 149 tubes\ntube_diameter = 5 * MM\ntube_spacing = 2 * MM\ntube_wall_thickness = 0.5 * MM\ntube_extension = 3 * MM\nbundle_diameter = exchanger_diameter - 2 * tube_diameter\nfillet_radius = tube_spacing / 3\nassert tube_extension > fillet_radius\n\n# Build the heat exchanger\nwith BuildPart() as heat_exchanger:\n # Generate list of tube locations\n tube_locations = [\n l\n for l in HexLocations(\n radius=(tube_diameter + tube_spacing) / 2,\n x_count=exchanger_diameter // tube_diameter,\n y_count=exchanger_diameter // tube_diameter,\n )\n if l.position.length < bundle_diameter / 2\n ]\n tube_count = len(tube_locations)\n with BuildSketch() as tube_plan:\n with Locations(*tube_locations):\n Circle(radius=tube_diameter / 2)\n Circle(radius=tube_diameter / 2 - tube_wall_thickness, mode=Mode.SUBTRACT)\n extrude(amount=exchanger_length / 2)\n with BuildSketch(\n Plane(\n origin=(0, 0, exchanger_length / 2 - tube_extension - plate_thickness),\n z_dir=(0, 0, 1),\n )\n ) as plate_plan:\n Circle(radius=exchanger_diameter / 2)\n with Locations(*tube_locations):\n Circle(radius=tube_diameter / 2 - tube_wall_thickness, mode=Mode.SUBTRACT)\n extrude(amount=plate_thickness)\n half_volume_before_fillet = heat_exchanger.part.volume\n # Simulate welded tubes by adding a fillet to the outside radius of the tubes\n fillet(\n heat_exchanger.edges()\n .filter_by(GeomType.CIRCLE)\n .sort_by(SortBy.RADIUS)\n .sort_by(Axis.Z, reverse=True)[2 * tube_count : 3 * tube_count],\n radius=fillet_radius,\n )\n half_volume_after_fillet = heat_exchanger.part.volume\n mirror(about=Plane.XY)\n\nfillet_volume = 2 * (half_volume_after_fillet - half_volume_before_fillet)\nassert abs(fillet_volume - 469.88331045553787) < 1e-3\n\nshow(heat_exchanger)\n# [End]\n"); + // real build123d: heat_exchanger.volume == 363795.07369811094 + expect(Math.abs(measured["heat_exchanger"].volume - 363795.07369811094)) + .toBeLessThan(363795.07369811094 * 0.005); + }); + + // examples/lego — Kind.INTERSECTION 2D offsets + GridLocations wall grid + test("examples/lego", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\nGEN_DOCS = False\npip_count = 6\n\nlego_unit_size = 8\npip_height = 1.8\npip_diameter = 4.8\nblock_length = lego_unit_size * pip_count\nblock_width = 16\nbase_height = 9.6\nblock_height = base_height + pip_height\nsupport_outer_diameter = 6.5\nsupport_inner_diameter = 4.8\nridge_width = 0.6\nridge_depth = 0.3\nwall_thickness = 1.2\n\nwith BuildPart() as lego:\n # Draw the bottom of the block\n with BuildSketch() as plan:\n # Start with a Rectangle the size of the block\n perimeter = Rectangle(width=block_length, height=block_width)\n if GEN_DOCS:\n exporter = ExportSVG(scale=6)\n exporter.add_shape(plan.sketch)\n exporter.write(\"assets/lego_step4.svg\")\n # Subtract an offset to create the block walls\n offset(\n perimeter,\n -wall_thickness,\n kind=Kind.INTERSECTION,\n mode=Mode.SUBTRACT,\n )\n if GEN_DOCS:\n exporter = ExportSVG(scale=6)\n exporter.add_shape(plan.sketch)\n exporter.write(\"assets/lego_step5.svg\")\n # Add a grid of lengthwise and widthwise bars\n with GridLocations(x_spacing=0, y_spacing=lego_unit_size, x_count=1, y_count=2):\n Rectangle(width=block_length, height=ridge_width)\n with GridLocations(lego_unit_size, 0, pip_count, 1):\n Rectangle(width=ridge_width, height=block_width)\n if GEN_DOCS:\n exporter = ExportSVG(scale=6)\n exporter.add_shape(plan.sketch)\n exporter.write(\"assets/lego_step6.svg\")\n # Subtract a rectangle leaving ribs on the block walls\n Rectangle(\n block_length - 2 * (wall_thickness + ridge_depth),\n block_width - 2 * (wall_thickness + ridge_depth),\n mode=Mode.SUBTRACT,\n )\n if GEN_DOCS:\n exporter = ExportSVG(scale=6)\n exporter.add_shape(plan.sketch)\n exporter.write(\"assets/lego_step7.svg\")\n # Add a row of hollow circles to the center\n with GridLocations(\n x_spacing=lego_unit_size, y_spacing=0, x_count=pip_count - 1, y_count=1\n ):\n Circle(radius=support_outer_diameter / 2)\n Circle(radius=support_inner_diameter / 2, mode=Mode.SUBTRACT)\n if GEN_DOCS:\n exporter = ExportSVG(scale=6)\n exporter.add_shape(plan.sketch)\n exporter.write(\"assets/lego_step8.svg\")\n # Extrude this base sketch to the height of the walls\n extrude(amount=base_height - wall_thickness)\n if GEN_DOCS:\n visible, hidden = lego.part.project_to_viewport((-5, -30, 50))\n exporter = ExportSVG(scale=6)\n exporter.add_layer(\"Visible\")\n exporter.add_layer(\n \"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT\n )\n exporter.add_shape(visible, layer=\"Visible\")\n exporter.add_shape(hidden, layer=\"Hidden\")\n exporter.write(\"assets/lego_step9.svg\")\n # Create a box on the top of the walls\n with Locations((0, 0, lego.vertices().sort_by(Axis.Z)[-1].Z)):\n # Create the top of the block\n Box(\n length=block_length,\n width=block_width,\n height=wall_thickness,\n align=(Align.CENTER, Align.CENTER, Align.MIN),\n )\n if GEN_DOCS:\n visible, hidden = lego.part.project_to_viewport((-5, -30, 50))\n exporter = ExportSVG(scale=6)\n exporter.add_layer(\"Visible\")\n exporter.add_layer(\n \"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT\n )\n exporter.add_shape(visible, layer=\"Visible\")\n exporter.add_shape(hidden, layer=\"Hidden\")\n exporter.write(\"assets/lego_step10.svg\")\n # Create a workplane on the top of the block\n with BuildPart(lego.faces().sort_by(Axis.Z)[-1]):\n # Create a grid of pips\n with GridLocations(lego_unit_size, lego_unit_size, pip_count, 2):\n Cylinder(\n radius=pip_diameter / 2,\n height=pip_height,\n align=(Align.CENTER, Align.CENTER, Align.MIN),\n )\n if GEN_DOCS:\n visible, hidden = lego.part.project_to_viewport((-100, -100, 50))\n exporter = ExportSVG(scale=6)\n exporter.add_layer(\"Visible\")\n exporter.add_layer(\n \"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT\n )\n exporter.add_shape(visible, layer=\"Visible\")\n exporter.add_shape(hidden, layer=\"Hidden\")\n exporter.write(\"assets/lego.svg\")\n\nassert abs(lego.part.volume - 3212.187337781355) < 1e-3\n\nshow_object(lego.part, name=\"lego\")\n"); + // real build123d: lego.volume == 3212.1873377813517 + expect(Math.abs(measured["lego"].volume - 3212.1873377813517)) + .toBeLessThan(3212.1873377813517 * 0.005); + }); + + // examples/loft — loft between pending sketches + test("examples/loft", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "# [Code]\n\nfrom math import pi, sin\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nwith BuildPart() as art:\n slice_count = 10\n for i in range(slice_count + 1):\n with BuildSketch(Plane(origin=(0, 0, i * 3), z_dir=(0, 0, 1))) as slice:\n Circle(10 * sin(i * pi / slice_count) + 5)\n loft()\n top_bottom = art.faces().filter_by(GeomType.PLANE)\n offset(openings=top_bottom, amount=0.5)\n\nwant = 1306.3405290344635\ngot = art.part.volume\ndelta = abs(got - want)\ntolerance = want * 1e-5\nassert delta < tolerance, f\"{delta=} is greater than {tolerance=}; {got=}, {want=}\"\n\nshow(art, names=[\"art\"])\n# [End]\n"); + // real build123d: art.volume == 1306.3405290344635 + expect(Math.abs(measured["art"].volume - 1306.3405290344635)) + .toBeLessThan(1306.3405290344635 * 0.005); + }); + + // examples/packed_boxes — pack() port + exact MT19937 random shim + HLR projection + test("examples/packed_boxes", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "import functools\nimport operator\nimport random\nimport build123d as bd\n\nGEN_DOCS = False\n\nrandom.seed(123456)\ntest_boxes = [bd.Box(random.randint(1, 20), random.randint(1, 20), random.randint(1, 5))\n for _ in range(50)]\npacked = bd.pack(test_boxes, 3)\n\n# Lifted from https://build123d.readthedocs.io/en/latest/import_export.html#d-to-2d-projection\ndef export_svg(parts, name):\n part = functools.reduce(operator.add, parts, bd.Part())\n view_port_origin=(0, 0, 150)\n visible, hidden = part.project_to_viewport(view_port_origin)\n max_dimension = max(*bd.Compound(children=visible + hidden).bounding_box().size)\n exporter = bd.ExportSVG(scale=100 / max_dimension)\n exporter.add_layer(\"Visible\")\n exporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=bd.LineType.ISO_DOT)\n exporter.add_shape(visible, layer=\"Visible\")\n exporter.add_shape(hidden, layer=\"Hidden\")\n if GEN_DOCS:\n exporter.write(f\"../docs/assets/{name}.svg\")\n\nexport_svg(test_boxes, \"packed_boxes_input\")\nexport_svg(packed, \"packed_boxes_output\")\n"); + // real build123d: packed[0].volume == 342.0 + expect(Math.abs(measured["packed[0]"].volume - 342.0)) + .toBeLessThan(342.0 * 0.005); + // real build123d: packed[10].volume == 340.00000000000006 + expect(Math.abs(measured["packed[10]"].volume - 340.00000000000006)) + .toBeLessThan(340.00000000000006 * 0.005); + // real build123d: packed[11].volume == 1020.0 + expect(Math.abs(measured["packed[11]"].volume - 1020.0)) + .toBeLessThan(1020.0 * 0.005); + // real build123d: packed[12].volume == 99.99999999999997 + expect(Math.abs(measured["packed[12]"].volume - 99.99999999999997)) + .toBeLessThan(99.99999999999997 * 0.005); + // real build123d: packed[13].volume == 280.0 + expect(Math.abs(measured["packed[13]"].volume - 280.0)) + .toBeLessThan(280.0 * 0.005); + // real build123d: packed[14].volume == 420.0 + expect(Math.abs(measured["packed[14]"].volume - 420.0)) + .toBeLessThan(420.0 * 0.005); + // real build123d: packed[15].volume == 96.0 + expect(Math.abs(measured["packed[15]"].volume - 96.0)) + .toBeLessThan(96.0 * 0.005); + // real build123d: packed[16].volume == 31.999999999999993 + expect(Math.abs(measured["packed[16]"].volume - 31.999999999999993)) + .toBeLessThan(31.999999999999993 * 0.005); + // real build123d: packed[17].volume == 627.0 + expect(Math.abs(measured["packed[17]"].volume - 627.0)) + .toBeLessThan(627.0 * 0.005); + // real build123d: packed[18].volume == 168.0 + expect(Math.abs(measured["packed[18]"].volume - 168.0)) + .toBeLessThan(168.0 * 0.005); + // real build123d: packed[19].volume == 18.0 + expect(Math.abs(measured["packed[19]"].volume - 18.0)) + .toBeLessThan(18.0 * 0.005); + // real build123d: packed[1].volume == 364.0 + expect(Math.abs(measured["packed[1]"].volume - 364.0)) + .toBeLessThan(364.0 * 0.005); + // real build123d: packed[20].volume == 120.0 + expect(Math.abs(measured["packed[20]"].volume - 120.0)) + .toBeLessThan(120.0 * 0.005); + // real build123d: packed[21].volume == 216.0 + expect(Math.abs(measured["packed[21]"].volume - 216.0)) + .toBeLessThan(216.0 * 0.005); + // real build123d: packed[22].volume == 48.0 + expect(Math.abs(measured["packed[22]"].volume - 48.0)) + .toBeLessThan(48.0 * 0.005); + // real build123d: packed[23].volume == 153.0 + expect(Math.abs(measured["packed[23]"].volume - 153.0)) + .toBeLessThan(153.0 * 0.005); + // real build123d: packed[24].volume == 255.99999999999994 + expect(Math.abs(measured["packed[24]"].volume - 255.99999999999994)) + .toBeLessThan(255.99999999999994 * 0.005); + // real build123d: packed[25].volume == 9.0 + expect(Math.abs(measured["packed[25]"].volume - 9.0)) + .toBeLessThan(9.0 * 0.005); + // real build123d: packed[26].volume == 34.0 + expect(Math.abs(measured["packed[26]"].volume - 34.0)) + .toBeLessThan(34.0 * 0.005); + // real build123d: packed[27].volume == 1275.0 + expect(Math.abs(measured["packed[27]"].volume - 1275.0)) + .toBeLessThan(1275.0 * 0.005); + // real build123d: packed[28].volume == 129.99999999999997 + expect(Math.abs(measured["packed[28]"].volume - 129.99999999999997)) + .toBeLessThan(129.99999999999997 * 0.005); + // real build123d: packed[29].volume == 60.0 + expect(Math.abs(measured["packed[29]"].volume - 60.0)) + .toBeLessThan(60.0 * 0.005); + // real build123d: packed[2].volume == 221.0 + expect(Math.abs(measured["packed[2]"].volume - 221.0)) + .toBeLessThan(221.0 * 0.005); + // real build123d: packed[30].volume == 12.0 + expect(Math.abs(measured["packed[30]"].volume - 12.0)) + .toBeLessThan(12.0 * 0.005); + // real build123d: packed[31].volume == 39.99999999999999 + expect(Math.abs(measured["packed[31]"].volume - 39.99999999999999)) + .toBeLessThan(39.99999999999999 * 0.005); + // real build123d: packed[32].volume == 56.0 + expect(Math.abs(measured["packed[32]"].volume - 56.0)) + .toBeLessThan(56.0 * 0.005); + // real build123d: packed[33].volume == 156.0 + expect(Math.abs(measured["packed[33]"].volume - 156.0)) + .toBeLessThan(156.0 * 0.005); + // real build123d: packed[34].volume == 182.00000000000003 + expect(Math.abs(measured["packed[34]"].volume - 182.00000000000003)) + .toBeLessThan(182.00000000000003 * 0.005); + // real build123d: packed[35].volume == 156.0 + expect(Math.abs(measured["packed[35]"].volume - 156.0)) + .toBeLessThan(156.0 * 0.005); + // real build123d: packed[36].volume == 585.0 + expect(Math.abs(measured["packed[36]"].volume - 585.0)) + .toBeLessThan(585.0 * 0.005); + // real build123d: packed[37].volume == 168.0 + expect(Math.abs(measured["packed[37]"].volume - 168.0)) + .toBeLessThan(168.0 * 0.005); + // real build123d: packed[38].volume == 17.999999999999996 + expect(Math.abs(measured["packed[38]"].volume - 17.999999999999996)) + .toBeLessThan(17.999999999999996 * 0.005); + // real build123d: packed[39].volume == 7.999999999999998 + expect(Math.abs(measured["packed[39]"].volume - 7.999999999999998)) + .toBeLessThan(7.999999999999998 * 0.005); + // real build123d: packed[3].volume == 1519.9999999999998 + expect(Math.abs(measured["packed[3]"].volume - 1519.9999999999998)) + .toBeLessThan(1519.9999999999998 * 0.005); + // real build123d: packed[40].volume == 95.99999999999999 + expect(Math.abs(measured["packed[40]"].volume - 95.99999999999999)) + .toBeLessThan(95.99999999999999 * 0.005); + // real build123d: packed[41].volume == 198.0 + expect(Math.abs(measured["packed[41]"].volume - 198.0)) + .toBeLessThan(198.0 * 0.005); + // real build123d: packed[42].volume == 27.0 + expect(Math.abs(measured["packed[42]"].volume - 27.0)) + .toBeLessThan(27.0 * 0.005); + // real build123d: packed[43].volume == 299.99999999999994 + expect(Math.abs(measured["packed[43]"].volume - 299.99999999999994)) + .toBeLessThan(299.99999999999994 * 0.005); + // real build123d: packed[44].volume == 19.999999999999996 + expect(Math.abs(measured["packed[44]"].volume - 19.999999999999996)) + .toBeLessThan(19.999999999999996 * 0.005); + // real build123d: packed[45].volume == 396.0 + expect(Math.abs(measured["packed[45]"].volume - 396.0)) + .toBeLessThan(396.0 * 0.005); + // real build123d: packed[46].volume == 121.0 + expect(Math.abs(measured["packed[46]"].volume - 121.0)) + .toBeLessThan(121.0 * 0.005); + // real build123d: packed[47].volume == 288.0 + expect(Math.abs(measured["packed[47]"].volume - 288.0)) + .toBeLessThan(288.0 * 0.005); + // real build123d: packed[48].volume == 81.0 + expect(Math.abs(measured["packed[48]"].volume - 81.0)) + .toBeLessThan(81.0 * 0.005); + // real build123d: packed[49].volume == 2.9999999999999996 + expect(Math.abs(measured["packed[49]"].volume - 2.9999999999999996)) + .toBeLessThan(2.9999999999999996 * 0.005); + // real build123d: packed[4].volume == 1425.0 + expect(Math.abs(measured["packed[4]"].volume - 1425.0)) + .toBeLessThan(1425.0 * 0.005); + // real build123d: packed[5].volume == 918.0 + expect(Math.abs(measured["packed[5]"].volume - 918.0)) + .toBeLessThan(918.0 * 0.005); + // real build123d: packed[6].volume == 112.0 + expect(Math.abs(measured["packed[6]"].volume - 112.0)) + .toBeLessThan(112.0 * 0.005); + // real build123d: packed[7].volume == 510.0 + expect(Math.abs(measured["packed[7]"].volume - 510.0)) + .toBeLessThan(510.0 * 0.005); + // real build123d: packed[8].volume == 680.0 + expect(Math.abs(measured["packed[8]"].volume - 680.0)) + .toBeLessThan(680.0 * 0.005); + // real build123d: packed[9].volume == 36.0 + expect(Math.abs(measured["packed[9]"].volume - 36.0)) + .toBeLessThan(36.0 * 0.005); + // real build123d: test_boxes[0].volume == 19.999999999999996 + expect(Math.abs(measured["test_boxes[0]"].volume - 19.999999999999996)) + .toBeLessThan(19.999999999999996 * 0.005); + // real build123d: test_boxes[10].volume == 60.0 + expect(Math.abs(measured["test_boxes[10]"].volume - 60.0)) + .toBeLessThan(60.0 * 0.005); + // real build123d: test_boxes[11].volume == 18.0 + expect(Math.abs(measured["test_boxes[11]"].volume - 18.0)) + .toBeLessThan(18.0 * 0.005); + // real build123d: test_boxes[12].volume == 627.0 + expect(Math.abs(measured["test_boxes[12]"].volume - 627.0)) + .toBeLessThan(627.0 * 0.005); + // real build123d: test_boxes[13].volume == 1275.0 + expect(Math.abs(measured["test_boxes[13]"].volume - 1275.0)) + .toBeLessThan(1275.0 * 0.005); + // real build123d: test_boxes[14].volume == 396.0 + expect(Math.abs(measured["test_boxes[14]"].volume - 396.0)) + .toBeLessThan(396.0 * 0.005); + // real build123d: test_boxes[15].volume == 96.0 + expect(Math.abs(measured["test_boxes[15]"].volume - 96.0)) + .toBeLessThan(96.0 * 0.005); + // real build123d: test_boxes[16].volume == 27.0 + expect(Math.abs(measured["test_boxes[16]"].volume - 27.0)) + .toBeLessThan(27.0 * 0.005); + // real build123d: test_boxes[17].volume == 420.0 + expect(Math.abs(measured["test_boxes[17]"].volume - 420.0)) + .toBeLessThan(420.0 * 0.005); + // real build123d: test_boxes[18].volume == 7.999999999999998 + expect(Math.abs(measured["test_boxes[18]"].volume - 7.999999999999998)) + .toBeLessThan(7.999999999999998 * 0.005); + // real build123d: test_boxes[19].volume == 56.0 + expect(Math.abs(measured["test_boxes[19]"].volume - 56.0)) + .toBeLessThan(56.0 * 0.005); + // real build123d: test_boxes[1].volume == 2.9999999999999996 + expect(Math.abs(measured["test_boxes[1]"].volume - 2.9999999999999996)) + .toBeLessThan(2.9999999999999996 * 0.005); + // real build123d: test_boxes[20].volume == 156.0 + expect(Math.abs(measured["test_boxes[20]"].volume - 156.0)) + .toBeLessThan(156.0 * 0.005); + // real build123d: test_boxes[21].volume == 99.99999999999997 + expect(Math.abs(measured["test_boxes[21]"].volume - 99.99999999999997)) + .toBeLessThan(99.99999999999997 * 0.005); + // real build123d: test_boxes[22].volume == 280.0 + expect(Math.abs(measured["test_boxes[22]"].volume - 280.0)) + .toBeLessThan(280.0 * 0.005); + // real build123d: test_boxes[23].volume == 680.0 + expect(Math.abs(measured["test_boxes[23]"].volume - 680.0)) + .toBeLessThan(680.0 * 0.005); + // real build123d: test_boxes[24].volume == 340.00000000000006 + expect(Math.abs(measured["test_boxes[24]"].volume - 340.00000000000006)) + .toBeLessThan(340.00000000000006 * 0.005); + // real build123d: test_boxes[25].volume == 585.0 + expect(Math.abs(measured["test_boxes[25]"].volume - 585.0)) + .toBeLessThan(585.0 * 0.005); + // real build123d: test_boxes[26].volume == 95.99999999999999 + expect(Math.abs(measured["test_boxes[26]"].volume - 95.99999999999999)) + .toBeLessThan(95.99999999999999 * 0.005); + // real build123d: test_boxes[27].volume == 39.99999999999999 + expect(Math.abs(measured["test_boxes[27]"].volume - 39.99999999999999)) + .toBeLessThan(39.99999999999999 * 0.005); + // real build123d: test_boxes[28].volume == 31.999999999999993 + expect(Math.abs(measured["test_boxes[28]"].volume - 31.999999999999993)) + .toBeLessThan(31.999999999999993 * 0.005); + // real build123d: test_boxes[29].volume == 216.0 + expect(Math.abs(measured["test_boxes[29]"].volume - 216.0)) + .toBeLessThan(216.0 * 0.005); + // real build123d: test_boxes[2].volume == 9.0 + expect(Math.abs(measured["test_boxes[2]"].volume - 9.0)) + .toBeLessThan(9.0 * 0.005); + // real build123d: test_boxes[30].volume == 288.0 + expect(Math.abs(measured["test_boxes[30]"].volume - 288.0)) + .toBeLessThan(288.0 * 0.005); + // real build123d: test_boxes[31].volume == 153.0 + expect(Math.abs(measured["test_boxes[31]"].volume - 153.0)) + .toBeLessThan(153.0 * 0.005); + // real build123d: test_boxes[32].volume == 918.0 + expect(Math.abs(measured["test_boxes[32]"].volume - 918.0)) + .toBeLessThan(918.0 * 0.005); + // real build123d: test_boxes[33].volume == 1519.9999999999998 + expect(Math.abs(measured["test_boxes[33]"].volume - 1519.9999999999998)) + .toBeLessThan(1519.9999999999998 * 0.005); + // real build123d: test_boxes[34].volume == 168.0 + expect(Math.abs(measured["test_boxes[34]"].volume - 168.0)) + .toBeLessThan(168.0 * 0.005); + // real build123d: test_boxes[35].volume == 299.99999999999994 + expect(Math.abs(measured["test_boxes[35]"].volume - 299.99999999999994)) + .toBeLessThan(299.99999999999994 * 0.005); + // real build123d: test_boxes[36].volume == 182.00000000000003 + expect(Math.abs(measured["test_boxes[36]"].volume - 182.00000000000003)) + .toBeLessThan(182.00000000000003 * 0.005); + // real build123d: test_boxes[37].volume == 129.99999999999997 + expect(Math.abs(measured["test_boxes[37]"].volume - 129.99999999999997)) + .toBeLessThan(129.99999999999997 * 0.005); + // real build123d: test_boxes[38].volume == 168.0 + expect(Math.abs(measured["test_boxes[38]"].volume - 168.0)) + .toBeLessThan(168.0 * 0.005); + // real build123d: test_boxes[39].volume == 198.0 + expect(Math.abs(measured["test_boxes[39]"].volume - 198.0)) + .toBeLessThan(198.0 * 0.005); + // real build123d: test_boxes[3].volume == 255.99999999999994 + expect(Math.abs(measured["test_boxes[3]"].volume - 255.99999999999994)) + .toBeLessThan(255.99999999999994 * 0.005); + // real build123d: test_boxes[40].volume == 121.0 + expect(Math.abs(measured["test_boxes[40]"].volume - 121.0)) + .toBeLessThan(121.0 * 0.005); + // real build123d: test_boxes[41].volume == 1425.0 + expect(Math.abs(measured["test_boxes[41]"].volume - 1425.0)) + .toBeLessThan(1425.0 * 0.005); + // real build123d: test_boxes[42].volume == 120.0 + expect(Math.abs(measured["test_boxes[42]"].volume - 120.0)) + .toBeLessThan(120.0 * 0.005); + // real build123d: test_boxes[43].volume == 342.0 + expect(Math.abs(measured["test_boxes[43]"].volume - 342.0)) + .toBeLessThan(342.0 * 0.005); + // real build123d: test_boxes[44].volume == 81.0 + expect(Math.abs(measured["test_boxes[44]"].volume - 81.0)) + .toBeLessThan(81.0 * 0.005); + // real build123d: test_boxes[45].volume == 221.0 + expect(Math.abs(measured["test_boxes[45]"].volume - 221.0)) + .toBeLessThan(221.0 * 0.005); + // real build123d: test_boxes[46].volume == 17.999999999999996 + expect(Math.abs(measured["test_boxes[46]"].volume - 17.999999999999996)) + .toBeLessThan(17.999999999999996 * 0.005); + // real build123d: test_boxes[47].volume == 12.0 + expect(Math.abs(measured["test_boxes[47]"].volume - 12.0)) + .toBeLessThan(12.0 * 0.005); + // real build123d: test_boxes[48].volume == 364.0 + expect(Math.abs(measured["test_boxes[48]"].volume - 364.0)) + .toBeLessThan(364.0 * 0.005); + // real build123d: test_boxes[49].volume == 112.0 + expect(Math.abs(measured["test_boxes[49]"].volume - 112.0)) + .toBeLessThan(112.0 * 0.005); + // real build123d: test_boxes[4].volume == 34.0 + expect(Math.abs(measured["test_boxes[4]"].volume - 34.0)) + .toBeLessThan(34.0 * 0.005); + // real build123d: test_boxes[5].volume == 156.0 + expect(Math.abs(measured["test_boxes[5]"].volume - 156.0)) + .toBeLessThan(156.0 * 0.005); + // real build123d: test_boxes[6].volume == 48.0 + expect(Math.abs(measured["test_boxes[6]"].volume - 48.0)) + .toBeLessThan(48.0 * 0.005); + // real build123d: test_boxes[7].volume == 1020.0 + expect(Math.abs(measured["test_boxes[7]"].volume - 1020.0)) + .toBeLessThan(1020.0 * 0.005); + // real build123d: test_boxes[8].volume == 510.0 + expect(Math.abs(measured["test_boxes[8]"].volume - 510.0)) + .toBeLessThan(510.0 * 0.005); + // real build123d: test_boxes[9].volume == 36.0 + expect(Math.abs(measured["test_boxes[9]"].volume - 36.0)) + .toBeLessThan(36.0 * 0.005); + }); + + // examples/clock — 2D vertex fillets (FilletFace2D) + PolarLocations + Text + test("examples/clock", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nclock_radius = 10\nwith BuildSketch() as minute_indicator:\n with BuildLine() as outline:\n l1 = CenterArc((0, 0), clock_radius * 0.975, 0.75, 4.5)\n l2 = CenterArc((0, 0), clock_radius * 0.925, 0.75, 4.5)\n Line(l1 @ 0, l2 @ 0)\n Line(l1 @ 1, l2 @ 1)\n make_face()\n fillet(minute_indicator.vertices(), radius=clock_radius * 0.01)\n\nwith BuildSketch() as clock_face:\n Circle(clock_radius)\n with PolarLocations(0, 60):\n add(minute_indicator.sketch, mode=Mode.SUBTRACT)\n with PolarLocations(clock_radius * 0.875, 12):\n SlotOverall(clock_radius * 0.05, clock_radius * 0.025, mode=Mode.SUBTRACT)\n for hour in range(1, 13):\n with PolarLocations(clock_radius * 0.75, 1, -hour * 30 + 90, 360, rotate=False):\n Text(\n str(hour),\n font_size=clock_radius * 0.175,\n font_style=FontStyle.BOLD,\n mode=Mode.SUBTRACT,\n )\n\nshow(clock_face)\n# [End]\n"); + }); + + // general_examples/ex23 — revolve of pending sketches around Axis.X + test("general_examples/ex23", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\nfrom math import *\n\n# 23. Revolve\n# [Ex. 23]\npts = [\n (-25, 35),\n (-25, 0),\n (-20, 0),\n (-20, 5),\n (-15, 10),\n (-15, 35),\n]\n\nwith BuildPart() as ex23:\n with BuildSketch(Plane.XZ) as ex23_sk:\n with BuildLine() as ex23_ln:\n l1 = Polyline(pts)\n l2 = Line(l1 @ 1, l1 @ 0)\n make_face()\n with Locations((0, 35)):\n Circle(25)\n split(bisect_by=Plane.ZY)\n revolve(axis=Axis.Z)\n # [Ex. 23]\n# [removed by collect.py] write_svg()\n\n# show_object(ex23.part)\n"); + // real build123d: ex23.volume == 88619.09277001212 + expect(Math.abs(measured["ex23"].volume - 88619.09277001212)) + .toBeLessThan(88619.09277001212 * 0.005); + }); + + // general_examples/ex29 — classic OCC bottle: arcs, make_face orientation, offset(openings=) + test("general_examples/ex29", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\nfrom math import *\n\n# 29. The Classic OCC Bottle\n# [Ex. 29]\nL, w, t, b, h, n = 60.0, 18.0, 9.0, 0.9, 90.0, 6.0\n\nwith BuildPart() as ex29:\n with BuildSketch(Plane.XY.offset(-b)) as ex29_ow_sk:\n with BuildLine() as ex29_ow_ln:\n l1 = Line((0, 0), (0, w / 2))\n l2 = ThreePointArc(l1 @ 1, (L / 2.0, w / 2.0 + t), (L, w / 2.0))\n l3 = Line(l2 @ 1, ((l2 @ 1).X, 0, 0))\n mirror(ex29_ow_ln.line)\n make_face()\n extrude(amount=h + b)\n fillet(ex29.edges(), radius=w / 6)\n with BuildSketch(ex29.faces().sort_by(Axis.Z)[-1]):\n Circle(t)\n extrude(amount=n)\n necktopf = ex29.faces().sort_by(Axis.Z)[-1]\n offset(ex29.solids()[0], amount=-b, openings=necktopf)\n # [Ex. 29]\n# [removed by collect.py] write_svg()\n\n# show_object(ex29.part)\n"); + // real build123d: ex29.volume == 15796.616314840601 + expect(Math.abs(measured["ex29"].volume - 15796.616314840601)) + .toBeLessThan(15796.616314840601 * 0.005); + }); + + // general_examples/ex35 — SlotCenterToCenter + SlotArc from raw arc edges + test("general_examples/ex35", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\nfrom math import *\n\n# 35. Slots\n# [Ex. 35]\nlength, width, thickness = 80.0, 60.0, 10.0\n\nwith BuildPart() as ex35:\n Box(length, length, thickness)\n topf = ex35.faces().sort_by(Axis.Z)[-1]\n with BuildSketch(topf) as ex35_sk:\n SlotCenterToCenter(width / 2, 10)\n with BuildLine(mode=Mode.PRIVATE) as ex35_ln:\n RadiusArc((-width / 2, 0), (0, width / 2), radius=width / 2)\n SlotArc(arc=ex35_ln.edges()[0], height=thickness, rotation=0)\n with BuildLine(mode=Mode.PRIVATE) as ex35_ln2:\n RadiusArc((0, -width / 2), (width / 2, 0), radius=-width / 2)\n SlotArc(arc=ex35_ln2.edges()[0], height=thickness, rotation=0)\n extrude(amount=-thickness, mode=Mode.SUBTRACT)\n # [Ex. 35]\n# [removed by collect.py] write_svg()\n\n# show_object(ex35.part)\n"); + // real build123d: ex35.volume == 49219.02754903829 + expect(Math.abs(measured["ex35"].volume - 49219.02754903829)) + .toBeLessThan(49219.02754903829 * 0.005); + }); + + // general_examples/ex36 — extrude(until=Until.NEXT) boolean trim + test("general_examples/ex36", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\nfrom math import *\n\n# 36. Extrude-Until\n# [Ex. 36]\nrad, rev = 6, 50\n\nwith BuildPart() as ex36:\n with BuildSketch() as ex36_sk:\n with Locations((0, rev)):\n Circle(rad)\n revolve(axis=Axis.X, revolution_arc=180)\n with BuildSketch() as ex36_sk2:\n Rectangle(rad, rev)\n extrude(until=Until.NEXT)\n # [Ex. 36]\n# [removed by collect.py] write_svg()\n\n# show_object(ex36.part)\n"); + // real build123d: ex36.volume == 30298.935241110394 + expect(Math.abs(measured["ex36"].volume - 30298.935241110394)) + .toBeLessThan(30298.935241110394 * 0.005); + }); + + // examples/boxes_on_faces — BuildSketch(*faces) with UV-derived Plane(face) x_dir + test("examples/boxes_on_faces", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "# [Imports]\nimport build123d as bd\n# [removed by collect.py] from ocp_vscode import *\n\n# [Code]\nwith bd.BuildPart() as bp:\n bd.Box(3, 3, 3)\n with bd.BuildSketch(*bp.faces()):\n bd.Rectangle(1, 2, rotation=45)\n bd.extrude(amount=0.1)\n\nassert abs(bp.part.volume - (3**3 + 6 * (1 * 2 * 0.1)) < 1e-3)\n\nif \"show_object\" in locals():\n show_object(bp.part.wrapped, name=\"box on faces\")\n# [End]"); + // real build123d: bp.volume == 28.20000000000004 + expect(Math.abs(measured["bp"].volume - 28.20000000000004)) + .toBeLessThan(28.20000000000004 * 0.005); + }); + + // examples/maker_coin — DoubleTangentArc (scipy-family solver) + revolve + PolarLocations detents + project()+emboss + test("examples/maker_coin", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\n# [Code]\n# Coin Parameters\ndiameter, thickness = 50 * MM, 10 * MM\n\nwith BuildPart() as maker_coin:\n # On XZ plane draw the profile of half the coin\n with BuildSketch(Plane.XZ) as profile:\n with BuildLine() as outline:\n l1 = Polyline((0, thickness * 0.6), (0, 0), ((diameter - thickness) / 2, 0))\n l2 = JernArc(\n start=l1 @ 1, tangent=l1 % 1, radius=thickness / 2, arc_size=300\n ) # extend the arc beyond the intersection but not closed\n l3 = DoubleTangentArc(l1 @ 0, tangent=(1, 0), other=l2)\n make_face() # make it a 2D shape\n revolve() # revolve 360\u00b0\n\n # Pattern the detents around the coin\n with BuildSketch() as detents:\n with PolarLocations(radius=(diameter + 5) / 2, count=8):\n Circle(thickness * 1.4 / 2)\n extrude(amount=thickness, mode=Mode.SUBTRACT) # cut away the detents\n\n fillet(maker_coin.edges(Select.NEW), 2) # fillet the cut edges\n\n # Add an embossed label\n with BuildSketch(Plane.XY.offset(thickness)) as label: # above coin\n Text(\"OS\", font_size=15)\n project() # label on top of coin\n extrude(amount=-thickness / 5, mode=Mode.SUBTRACT) # emboss label\n\nshow(maker_coin)\n# [End]\n"); + // real build123d: maker_coin.volume == 13160.217918773385 + expect(Math.abs(measured["maker_coin"].volume - 13160.217918773385)) + .toBeLessThan(13160.217918773385 * 0.005); + }); + + // examples/handle_algebra — exact tangent Splines + curve ^ u locations + MULTISECTION sweep (MakePipeShell) + test("examples/handle_algebra", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show_object\n\nsegment_count = 6\n\n# Create a path for the sweep along the handle - added to pending_edges\nhandle_center_line = Spline(\n (-10, 0, 0),\n (0, 0, 5),\n (10, 0, 0),\n tangents=((0, 0, 1), (0, 0, -1)),\n tangent_scalars=(1.5, 1.5),\n)\n\n# Create the cross sections - added to pending_faces\nsections = Sketch()\nfor i in range(segment_count + 1):\n location = handle_center_line ^ (i / segment_count)\n if i % segment_count == 0:\n circle = location * Circle(1)\n else:\n circle = location * Rectangle(1.25, 3)\n circle = fillet(circle.vertices(), radius=0.2)\n sections += circle\n\n# Create the handle by sweeping along the path\nhandle = sweep(sections, path=handle_center_line, multisection=True)\n\nshow_object(handle_center_line, name=\"handle_path\")\nfor i, circle in enumerate(sections):\n show_object(circle, name=\"section\" + str(i))\nshow_object(handle, name=\"handle\", options=dict(alpha=0.6))\n# [End]\n"); + // real build123d: handle.volume == 94.7736147223482 + expect(Math.abs(measured["handle"].volume - 94.7736147223482)) + .toBeLessThan(94.7736147223482 * 0.005); + }); + + // examples/custom_sketch_objects_algebra — Sketch subclasses + uniform scale (baked gp_Trsf) + offset lids + test("examples/custom_sketch_objects_algebra", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from typing import Union\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n\nclass Club(Sketch):\n def __init__(\n self,\n height: float,\n align: Union[Align, tuple[Align, Align]] = None,\n ):\n l0 = Line((0, -188), (76, -188))\n b0 = Bezier(l0 @ 1, (61, -185), (33, -173), (17, -81))\n b1 = Bezier(b0 @ 1, (49, -128), (146, -145), (167, -67))\n b2 = Bezier(b1 @ 1, (187, 9), (94, 52), (32, 18))\n b3 = Bezier(b2 @ 1, (92, 57), (113, 188), (0, 188))\n club = l0 + b0 + b1 + b2 + b3\n club += mirror(club, Plane.YZ)\n club = make_face(club)\n club = scale(club, height / club.bounding_box().size.Y)\n\n super().__init__(club.wrapped)\n # self._align(align)\n\n\nclass Spade(Sketch):\n def __init__(\n self,\n height: float,\n align: Union[Align, tuple[Align, Align]] = None,\n ):\n b0 = Bezier((0, 198), (6, 190), (41, 127), (112, 61))\n b1 = Bezier(b0 @ 1, (242, -72), (114, -168), (11, -105))\n b2 = Bezier(b1 @ 1, (31, -174), (42, -179), (53, -198))\n l0 = Line(b2 @ 1, (0, -198))\n spade = b0 + b1 + b2 + l0\n spade += mirror(spade, Plane.YZ)\n spade = make_face(spade)\n spade = scale(spade, height / spade.bounding_box().size.Y)\n\n super().__init__(spade.wrapped)\n # self._align(align)\n\n\nclass Heart(Sketch):\n def __init__(\n self,\n height: float,\n align: Union[Align, tuple[Align, Align]] = None,\n ):\n b1 = Bezier((0, 146), (20, 169), (67, 198), (97, 198))\n b2 = Bezier(b1 @ 1, (125, 198), (151, 186), (168, 167))\n b3 = Bezier(b2 @ 1, (197, 133), (194, 88), (158, 31))\n b4 = Bezier(b3 @ 1, (126, -13), (94, -48), (62, -95))\n b5 = Bezier(b4 @ 1, (40, -128), (0, -198))\n heart = b1 + b2 + b3 + b4 + b5\n heart += mirror(heart, Plane.YZ)\n heart = make_face(heart)\n heart = scale(heart, height / heart.bounding_box().size.Y)\n\n super().__init__(heart.wrapped)\n # self._align(align)\n\n\nclass Diamond(Sketch):\n def __init__(\n self,\n height: float,\n align: Union[Align, tuple[Align, Align]] = None,\n ):\n diamond = Bezier((135, 0), (94, 69), (47, 134), (0, 198))\n diamond += mirror(diamond, Plane.XZ)\n diamond += mirror(diamond, Plane.YZ)\n diamond = make_face(diamond)\n diamond = scale(diamond, height / diamond.bounding_box().size.Y)\n\n super().__init__(diamond.wrapped)\n # self._align(align)\n\n\n# The inside of the box fits 2.5x3.5\" playing card deck with a small gap\npocket_w = 2.5 * IN + 2 * MM\npocket_l = 3.5 * IN + 2 * MM\npocket_t = 0.5 * IN + 2 * MM\nwall_t = 3 * MM # Wall thickness\nbottom_t = wall_t / 2 # Top and bottom thickness\nlid_gap = 0.5 * MM # Spacing between base and lid\nlip_t = wall_t / 2 - lid_gap / 2 # Lip thickness\n\n\nbox_plan = RectangleRounded(pocket_w + 2 * wall_t, pocket_l + 2 * wall_t, pocket_w / 15)\nbox = extrude(box_plan, amount=bottom_t + pocket_t / 2)\nbase_top = box.faces().sort_by(Axis.Z).last\nwalls = Plane(base_top) * offset(box_plan, -lip_t)\nbox += extrude(walls, amount=pocket_t / 2)\ntop = Plane.XY.offset(wall_t / 2) * offset(box_plan, -wall_t)\nbox -= extrude(top, amount=pocket_t)\n\n\npocket = extrude(box_plan, amount=pocket_t / 2 + bottom_t)\nlid_bottom = offset(box_plan, -(wall_t - lip_t))\npocket -= extrude(lid_bottom, amount=pocket_t / 2)\npocket = Pos(0, 0, (wall_t + pocket_t) / 2) * pocket\n\nplane = Plane(pocket.faces().sort_by().last)\nsuites = Pos(-0.3 * pocket_w, 0.3 * pocket_l) * Heart(pocket_l / 5)\nsuites += Pos(-0.3 * pocket_w, -0.3 * pocket_l) * Diamond(pocket_l / 5)\nsuites += Pos(0.3 * pocket_w, 0.3 * pocket_l) * Spade(pocket_l / 5)\nsuites += Pos(0.3 * pocket_w, -0.3 * pocket_l) * Club(pocket_l / 5)\nsuites = plane * suites\n\nlid = pocket - extrude(suites, dir=(0, 0, 1), amount=-wall_t)\n\nshow(box, lid, names=[\"box\", \"lid\"], alphas=[1.0, 0.6])\n"); + // real build123d: box.volume == 21485.21909241953 + expect(Math.abs(measured["box"].volume - 21485.21909241953)) + .toBeLessThan(21485.21909241953 * 0.005); + // real build123d: lid.volume == 13597.407606122617 + expect(Math.abs(measured["lid"].volume - 13597.407606122617)) + .toBeLessThan(13597.407606122617 * 0.005); + }); + + // examples/key_cap — taper extrude + non-uniform scale + section() + inner_wires + extrude(until=Until.NEXT) + test("examples/key_cap", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nwith BuildPart() as key_cap:\n # Start with the plan of the key cap and extrude it\n with BuildSketch() as plan:\n Rectangle(18 * MM, 18 * MM)\n extrude(amount=10 * MM, taper=15)\n # Create a dished top\n with Locations((0, -3 * MM, 47 * MM)):\n Sphere(40 * MM, mode=Mode.SUBTRACT, rotation=(90, 0, 0))\n # Fillet all the edges except the bottom\n fillet(\n key_cap.edges().filter_by_position(Axis.Z, 0, 30 * MM, inclusive=(False, True)),\n radius=1 * MM,\n )\n # Hollow out the key by subtracting a scaled version\n scale(by=(0.925, 0.925, 0.85), mode=Mode.SUBTRACT)\n\n # First find the size of the internal cavity at 4*MM\n key_cap_section = section(key_cap.part, Plane.XY.offset(4 * MM)).face()\n key_cap_internal_size = key_cap_section.inner_wires()[0].bounding_box().size\n\n # Add supporting ribs while leaving room for switch activation\n with BuildSketch(Plane(origin=(0, 0, 4 * MM))):\n Rectangle(key_cap_internal_size.X, 0.5 * MM)\n Rectangle(0.5 * MM, key_cap_internal_size.Y)\n Circle(radius=5.5 * MM / 2)\n # Extrude the mount and ribs to the key cap underside\n extrude(until=Until.NEXT)\n # Find the face on the bottom of the ribs to build onto\n rib_bottom = key_cap.faces().filter_by_position(Axis.Z, 4 * MM, 4 * MM)[0]\n # Add the switch socket\n with BuildSketch(rib_bottom) as cruciform:\n Circle(radius=5.5 * MM / 2)\n Rectangle(4.1 * MM, 1.17 * MM, mode=Mode.SUBTRACT)\n Rectangle(1.17 * MM, 4.1 * MM, mode=Mode.SUBTRACT)\n extrude(amount=3.5 * MM, mode=Mode.ADD)\n\nassert abs(key_cap.part.volume - 644.8900473617498) < 1e-3\n\nshow(key_cap, alphas=[0.3])\n# [End]\n"); + // real build123d: key_cap.volume == 644.8900474026628 + expect(Math.abs(measured["key_cap"].volume - 644.8900474026628)) + .toBeLessThan(644.8900474026628 * 0.005); + }); + + // examples/stud_wall — RigidJoints as location algebra + copy.copy joint rebinding + connect_to repositioning + test("examples/stud_wall", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\n# [removed by collect.py] from ocp_vscode import show\nfrom typing import Union\nimport copy\n\n\n# [Code]\nclass Stud(BasePartObject):\n \"\"\"Part Object: Stud\n\n Create a dimensional framing stud.\n\n Args:\n length (float): stud size\n width (float): stud size\n thickness (float): stud size\n rotation (RotationLike, optional): angles to rotate about axes. Defaults to (0, 0, 0).\n align (Union[Align, tuple[Align, Align, Align]], optional): align min, center,\n or max of object. Defaults to (Align.CENTER, Align.CENTER, Align.MIN).\n mode (Mode, optional): combine mode. Defaults to Mode.ADD.\n \"\"\"\n\n _applies_to = [BuildPart._tag]\n\n def __init__(\n self,\n length: float = 8 * FT,\n width: float = 3.5 * IN,\n thickness: float = 1.5 * IN,\n rotation: RotationLike = (0, 0, 0),\n align: Union[None, Align, tuple[Align, Align, Align]] = (\n Align.CENTER,\n Align.CENTER,\n Align.MIN,\n ),\n mode: Mode = Mode.ADD,\n ):\n self.length = length\n self.width = width\n self.thickness = thickness\n\n # Create the basic shape\n with BuildPart() as stud:\n with BuildSketch():\n RectangleRounded(thickness, width, 0.25 * IN)\n extrude(amount=length)\n\n # Create a Part object with appropriate alignment and rotation\n super().__init__(part=stud.part, rotation=rotation, align=align, mode=mode)\n\n # Add joints to the ends of the stud\n RigidJoint(\"end0\", self, Location())\n RigidJoint(\"end1\", self, Location((0, 0, length), (1, 0, 0), 180))\n\n\nclass StudWall(Compound):\n \"\"\"StudWall\n\n A simple stud wall assembly with top and sole plates.\n\n Args:\n length (float): wall length\n depth (float, optional): stud width. Defaults to 3.5*IN.\n height (float, optional): wall height. Defaults to 8*FT.\n stud_spacing (float, optional): center-to-center. Defaults to 16*IN.\n stud_thickness (float, optional): Defaults to 1.5*IN.\n \"\"\"\n\n def __init__(\n self,\n length: float,\n depth: float = 3.5 * IN,\n height: float = 8 * FT,\n stud_spacing: float = 16 * IN,\n stud_thickness: float = 1.5 * IN,\n ):\n # Create the object that will be used for top and sole plates\n plate = Stud(\n length,\n depth,\n rotation=(0, -90, 0),\n align=(Align.MIN, Align.CENTER, Align.MAX),\n )\n # Define where studs will go on the plates\n stud_locations = Pos(stud_thickness / 2, 0, stud_thickness) * GridLocations(\n stud_spacing, 0, int(length / stud_spacing) + 1, 1, align=Align.MIN\n )\n stud_locations.append(Pos(length - stud_thickness / 2, 0, stud_thickness))\n\n # Create a single stud that will be copied for efficiency\n stud = Stud(height - 2 * stud_thickness, depth, stud_thickness)\n\n # For efficiency studs in the walls are copies with their own position\n studs = []\n for i, loc in enumerate(stud_locations):\n stud_joint = RigidJoint(f\"stud{i}\", plate, loc)\n stud_copy = copy.copy(stud)\n stud_joint.connect_to(stud_copy.joints[\"end0\"])\n studs.append(stud_copy)\n top_plate = copy.copy(plate)\n sole_plate = copy.copy(plate)\n\n # Position the top plate relative to the top of the first stud\n studs[0].joints[\"end1\"].connect_to(top_plate.joints[\"stud0\"])\n\n # Build the assembly of parts\n super().__init__(children=[top_plate, sole_plate] + studs)\n\n # Add joints to the wall\n RigidJoint(\"inside0\", self, Location((depth / 2, depth / 2, 0), (0, 0, 1), 90))\n RigidJoint(\"end0\", self, Location())\n\n\nx_wall = StudWall(13 * FT)\ny_wall = StudWall(9 * FT)\nx_wall.joints[\"inside0\"].connect_to(y_wall.joints[\"end0\"])\n\nshow(x_wall, y_wall, render_joints=False)\n# [End]\n"); + // real build123d: x_wall.volume == 113679138.17586128 + expect(Math.abs(measured["x_wall"].volume - 113679138.17586128)) + .toBeLessThan(113679138.17586128 * 0.005); + // real build123d: y_wall.volume == 81746795.99163055 + expect(Math.abs(measured["y_wall"].volume - 81746795.99163055)) + .toBeLessThan(81746795.99163055 * 0.005); + }); + + + // examples/canadian_flag — surface-from-points (GeomAPI_PointsToBSplineSurface + // via Handle_Geom_BSplineSurface.AsGeomSurface), projection onto the wavy + // surface, per-variable bbox parity with real build123d + test("examples/canadian_flag", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "# [Imports]\nfrom math import sin, cos, pi\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show_object, show, show_all\n\n# [Parameters]\n# Canadian Flags have a 2:1 aspect ratio\nheight = 50\nwidth = 2 * height\nwave_amplitude = 3\n\n# [Code]\n\n\ndef surface(amplitude, u, v):\n \"\"\"Calculate the surface displacement of the flag at a given position\"\"\"\n return v * amplitude / 20 * cos(3.5 * pi * u) + amplitude / 10 * v * sin(\n 1.1 * pi * v\n )\n\n\n# Note that the surface to project on must be a little larger than the faces\n# being projected onto it to create valid projected faces\nthe_wind = Face.make_surface_from_array_of_points(\n [\n [\n Vector(\n width * (v * 1.1 / 40 - 0.05),\n height * (u * 1.2 / 40 - 0.1),\n height * surface(wave_amplitude, u / 40, v / 40) / 2,\n )\n for u in range(41)\n ]\n for v in range(41)\n ]\n)\nwith BuildSketch(Plane.XY.offset(10)) as west_field_builder:\n Rectangle(width / 4, height, align=(Align.MIN, Align.MIN))\nwest_field_planar = west_field_builder.sketch.faces()[0]\neast_field_planar = west_field_planar.mirror(Plane.YZ.offset(width / 2))\n\nwith BuildSketch(Plane((width / 2, 0, 10))) as center_field_builder:\n Rectangle(width / 2, height, align=(Align.CENTER, Align.MIN))\n with BuildLine() as outline:\n l1 = Polyline((0.0000, 0.0771), (0.0187, 0.0771), (0.0094, 0.2569))\n l2 = Polyline((0.0325, 0.2773), (0.2115, 0.2458), (0.1873, 0.3125))\n RadiusArc(l1 @ 1, l2 @ 0, 0.0271)\n l3 = Polyline((0.1915, 0.3277), (0.3875, 0.4865), (0.3433, 0.5071))\n TangentArc(l2 @ 1, l3 @ 0, tangent=l2 % 1)\n l4 = Polyline((0.3362, 0.5235), (0.375, 0.6427), (0.2621, 0.6188))\n SagittaArc(l3 @ 1, l4 @ 0, 0.003)\n l5 = Polyline((0.2469, 0.6267), (0.225, 0.6781), (0.1369, 0.5835))\n ThreePointArc(l4 @ 1, (l4 @ 1 + l5 @ 0) * 0.5 + Vector(-0.002, -0.002), l5 @ 0)\n l6 = Polyline((0.1138, 0.5954), (0.1562, 0.8146), (0.0881, 0.7752))\n Spline(\n l5 @ 1,\n l6 @ 0,\n tangents=(l5 % 1, l6 % 0),\n tangent_scalars=(2, 2),\n )\n l7 = Line((0.0692, 0.7808), (0.0000, 0.9167))\n TangentArc(l6 @ 1, l7 @ 0, tangent=l6 % 1)\n mirror(about=Plane.YZ)\n scale(by=height)\n maple_leaf_planar = make_face(mode=Mode.SUBTRACT).face()\n\nmaple_leaf_planar.position += (width / 2, 0, 10) # Created on local Plane.XY\ncenter_field_planar = center_field_builder.sketch.faces()[0]\n\nwest_field = west_field_planar.project_to_shape(the_wind, (0, 0, -1))[0]\nwest_field.color = Color(\"red\")\neast_field = east_field_planar.project_to_shape(the_wind, (0, 0, -1))[0]\neast_field.color = Color(\"red\")\ncenter_field = center_field_planar.project_to_shape(the_wind, (0, 0, -1))[0]\ncenter_field.color = Color(\"white\")\nmaple_leaf = maple_leaf_planar.project_to_shape(the_wind, (0, 0, -1))[0]\nmaple_leaf.color = Color(\"red\")\n\ncanadian_flag = Compound(children=[west_field, east_field, center_field, maple_leaf])\nshow(Rot(90, 0, 0) * canadian_flag)\n# [End]\n"); + // real build123d bboxes (harness tolerance: 1e-3 per axis) + const expected = { + "the_wind": [-5.0, -5.0, -6.0737848329, 105.0, 55.0, 6.2444043552], + "maple_leaf": [30.625, 3.855, 1.4673525215, 69.375, 45.835, 6.2439667576], + }; + for (const [name, bbox] of Object.entries(expected)) { + for (let i = 0; i < 6; i++) { + expect(Math.abs(measured[name].bbox[i] - bbox[i])).toBeLessThan(1e-3); + } + } + }); + + + // examples/platonic_solids — scipy ConvexHull shim (bundled quickhull3d), + // Solid(Shell(faces)) sewing, user BasePartObject subclass + test("examples/platonic_solids", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "# [Code]\nfrom build123d import *\nfrom math import sqrt\nfrom typing import Union, Literal\nfrom scipy.spatial import ConvexHull\n\n# [removed by collect.py] from ocp_vscode import show\n\nPHI = (1 + sqrt(5)) / 2 # The Golden Ratio\n\n\nclass PlatonicSolid(BasePartObject):\n \"\"\"Part Object: Platonic Solid\n\n Create one of the five convex Platonic solids.\n\n Args:\n face_count (Literal[4,6,8,12,20]): number of faces\n diameter (float): double distance to vertices, i.e. maximum size\n rotation (RotationLike, optional): angles to rotate about axes. Defaults to (0, 0, 0).\n align (Union[None, Align, tuple[Align, Align, Align]], optional): align min, center,\n or max of object. Defaults to None.\n mode (Mode, optional): combine mode. Defaults to Mode.ADD.\n \"\"\"\n\n tetrahedron_vertices = [(1, 1, 1), (1, -1, -1), (-1, 1, -1), (-1, -1, 1)]\n\n cube_vertices = [(i, j, k) for i in [-1, 1] for j in [-1, 1] for k in [-1, 1]]\n\n octahedron_vertices = (\n [(i, 0, 0) for i in [-1, 1]]\n + [(0, i, 0) for i in [-1, 1]]\n + [(0, 0, i) for i in [-1, 1]]\n )\n\n dodecahedron_vertices = (\n [(i, j, k) for i in [-1, 1] for j in [-1, 1] for k in [-1, 1]]\n + [(0, i / PHI, j * PHI) for i in [-1, 1] for j in [-1, 1]]\n + [(i / PHI, j * PHI, 0) for i in [-1, 1] for j in [-1, 1]]\n + [(i * PHI, 0, j / PHI) for i in [-1, 1] for j in [-1, 1]]\n )\n\n icosahedron_vertices = (\n [(0, i, j * PHI) for i in [-1, 1] for j in [-1, 1]]\n + [(i, j * PHI, 0) for i in [-1, 1] for j in [-1, 1]]\n + [(i * PHI, 0, j) for i in [-1, 1] for j in [-1, 1]]\n )\n\n vertices_lookup = {\n 4: tetrahedron_vertices,\n 6: cube_vertices,\n 8: octahedron_vertices,\n 12: dodecahedron_vertices,\n 20: icosahedron_vertices,\n }\n _applies_to = [BuildPart._tag]\n\n def __init__(\n self,\n face_count: Literal[4, 6, 8, 12, 20],\n diameter: float = 1.0,\n rotation: RotationLike = (0, 0, 0),\n align: Union[None, Align, tuple[Align, Align, Align]] = None,\n mode: Mode = Mode.ADD,\n ):\n try:\n platonic_vertices = PlatonicSolid.vertices_lookup[face_count]\n except KeyError:\n raise ValueError(\n f\"face_count must be one of 4, 6, 8, 12, or 20 not {face_count}\"\n )\n\n # Create a convex hull from the vertices\n hull = ConvexHull(platonic_vertices).simplices.tolist()\n\n # Create faces from the vertex indices\n platonic_faces = []\n for face_vertex_indices in hull:\n corner_vertices = [platonic_vertices[i] for i in face_vertex_indices]\n platonic_faces.append(Face(Wire.make_polygon(corner_vertices)))\n\n # Create the solid from the Faces\n platonic_solid = Solid(Shell(platonic_faces)).clean()\n\n # By definition, all vertices are the same distance from the origin so\n # scale proportionally to this distance\n platonic_solid = platonic_solid.scale(\n (diameter / 2) / Vector(platonic_solid.vertices()[0]).length\n )\n\n super().__init__(part=platonic_solid, rotation=rotation, align=align, mode=mode)\n\n\nsolids = [\n Rot(0, 0, 72 * i) * Pos(1, 0, 0) * PlatonicSolid(faces)\n for i, faces in enumerate([4, 6, 8, 12, 20])\n]\nshow(solids)\n\n# [End]\n"); + // real build123d: unit-edge platonic solid volumes + const volumes = [0.3481454829, 0.1666666667, 0.3170188388, 0.1924500897, 0.0641500299]; + for (let i = 0; i < 5; i++) { + expect(Math.abs(measured[`solids[${i}]`].volume - volumes[i])) + .toBeLessThan(volumes[i] * 0.005); + } + }); + + + // examples/tea_cup_algebra — offset shells, multisection sweep handle, + // loft/thicken pipeline that used to fault the wasm kernel outright + test("examples/tea_cup_algebra", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "# [Code]\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nwall_thickness = 3 * MM\nfillet_radius = wall_thickness * 0.49\n\n# Create the bowl of the cup as a revolved cross section\n\n# Start & end points with control tangents\ns = Spline(\n (30 * MM, 10 * MM),\n (69 * MM, 105 * MM),\n tangents=((1, 0.5), (0.7, 1)),\n tangent_scalars=(1.75, 1),\n)\n# Lines to finish creating \u00bd the bowl shape\ns += Polyline(s @ 0, s @ 0 + (10 * MM, -10 * MM), (0, 0), (0, (s @ 1).Y), s @ 1)\nbowl_section = Plane.XZ * make_face(s) # Create a filled 2D shape\ntea_cup = revolve(bowl_section, axis=Axis.Z)\n\n# Hollow out the bowl with openings on the top and bottom\ntea_cup = offset(\n tea_cup, -wall_thickness, openings=tea_cup.faces().filter_by(GeomType.PLANE)\n)\n\n# Add a bottom to the bowl\ntea_cup += Pos(0, 0, (s @ 0).Y) * Cylinder(radius=(s @ 0).X, height=wall_thickness)\n\n# Smooth out all the edges\ntea_cup = fillet(tea_cup.edges(), radius=fillet_radius)\n\n# Determine where the handle contacts the bowl\nhandle_intersections = [\n tea_cup.find_intersection_points(\n Axis(origin=(0, 0, vertical_offset), direction=(1, 0, 0))\n )[-1][0]\n for vertical_offset in [35 * MM, 80 * MM]\n]\n\n# Create a path for handle creation\npath_spline = Spline(\n handle_intersections[0] - (wall_thickness / 2, 0, 0),\n handle_intersections[0] + (35 * MM, 0, 30 * MM),\n handle_intersections[0] + (40 * MM, 0, 60 * MM),\n handle_intersections[1] - (wall_thickness / 2, 0, 0),\n tangents=((1, 0, 1.25), (-0.2, 0, -1)),\n)\n\n# Align the cross section to the beginning of the path\nlocation = path_spline ^ 0\nhandle_cross_section = location * RectangleRounded(wall_thickness, 8 * MM, fillet_radius)\n\n# Sweep handle cross section along path\ntea_cup += sweep(handle_cross_section, path=path_spline)\n\n# assert abs(tea_cup.part.volume - 130326.77052487945) < 1e-3\n\nshow(tea_cup, names=[\"tea cup\"])\n# [End]\n"); + // real build123d: tea_cup.volume == 130326.75447606308 + expect(Math.abs(measured["tea_cup"].volume - 130326.75447606308)) + .toBeLessThan(130326.75447606308 * 0.005); + const bbox = [-67.7762442353, -67.7762446391, 0.0, 101.6138783918, 67.7762446391, 105.0]; + for (let i = 0; i < 6; i++) { + expect(Math.abs(measured["tea_cup"].bbox[i] - bbox[i])).toBeLessThan(1e-3); + } + }); + + + // general_examples_algebra/ex34 — embossed/debossed text fused onto a box + // face: per-glyph +Z text normals and the general-fuse fallback for the + // 8.0.1 coplanar-fuse operand-drop fault + test("general_examples_algebra/ex34", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\nfrom math import *\n\n# 34. Embossed and Debossed Text\n# [Ex. 34]\nlength, width, thickness, fontsz, fontht = 80.0, 60.0, 10.0, 25.0, 4.0\n\nex34 = Box(length, width, thickness)\nplane = Plane(ex34.faces().sort_by().last)\nex34_sk = plane * Text(\"Hello\", font_size=fontsz, align=(Align.CENTER, Align.MIN))\nex34 += extrude(ex34_sk, amount=fontht)\nex34_sk2 = plane * Text(\"World\", font_size=fontsz, align=(Align.CENTER, Align.MAX))\nex34 -= extrude(ex34_sk2, amount=-fontht)\n# [Ex. 34]\n# show_object(ex34)\n"); + // real build123d: ex34.volume == 47754.582611832375 + expect(Math.abs(measured["ex34"].volume - 47754.582611832375)) + .toBeLessThan(47754.582611832375 * 0.005); + // embossed "Hello" must rise ABOVE the box top (z=5 -> 9), debossed + // "World" must not push below it (the logo-regression failure mode) + expect(Math.abs(measured["ex34"].bbox[5] - 9.0)).toBeLessThan(1e-3); + expect(Math.abs(measured["ex34"].bbox[2] - (-5.0))).toBeLessThan(1e-3); + }); + + + // examples/bracelet - Gordon curve-network surface (point guides), surface + // location_at + wire projection, sweep of the tip's flat face along an + // elliptical arc, mirrored tips and alignment holes + test("examples/bracelet", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "# [Code]\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\n# Define input parameters\n# - radii: ellipse radii (X, Y) controlling the bracelet centerline shape\n# - width: bracelet width (along Z for the center sweep)\n# - thickness: bracelet thickness (radial thickness of the cross section)\n# - opening_angle: the missing angle that creates the wrist opening\n# - label_str: optional text to emboss on the outside surface\n# - Define input parameters\n# radii, width, thickness, opening_angle, label_str = (45, 30), 25, 5, 80, \"build123d\"\nradii, width, thickness, opening_angle, label_str = (45, 30), 25, 5, 80, \"\"\n\n# Step 1: Create an elliptical arc defining the *centerline* of the bracelet.\n# The arc is truncated to leave an opening (the \"gap\" where the bracelet goes on).\n# Angles are in degrees; 270\u00b0 points downward, which keeps the opening centered at the bottom.\ncenter_arc = EllipticalCenterArc(\n (0, 0), *radii, 270 + opening_angle / 2, arc_size=360 - opening_angle\n)\n\n# Step 2: Create HALF of the end cross-section, positioned at the end of the arc.\n# We build only half so we can later mirror it to enforce symmetry and reduce\n# curve-network complexity when building the freeform tip.\n#\n# location_at(1) returns a local coordinate frame at the arc end (tangent-aware).\n# x_dir is chosen so the section\u2019s local \"X\" is well-defined and stable.\nend_center_arc = center_arc.location_at(1, x_dir=(0, 0, 1))\nhalf_x_section = EllipticalCenterArc(\n (0, 0), width / 2, thickness / 2, 90, arc_size=180\n).locate(end_center_arc)\n\n# Step 3: Create a doubly-curved \"tip edge\" curve.\n# The tip edge must live in 3D and conform to the outside of the bracelet.\n# To do that, we:\n# 1) create a surface by extruding the center_arc into a sheet (a ribbon surface)\n# 2) build a planar arc in a local frame at the end of that surface\n# 3) project the planar arc onto the curved surface to get a true 3D curve\n#\n# The resulting tip_arc is a 3D edge that naturally matches the bracelet curvature.\ncenter_surface = -Face.extrude(center_arc, (0, 0, 2 * width)).moved(\n Location((0, 0, -width), (0, 0, 180))\n)\ntip_center_loc = -center_surface.location_at(center_arc @ 1, x_dir=(1, 0, 0))\nnormal_at_tip_center = tip_center_loc.z_axis.direction\n\n# A planar arc that would represent the outer boundary of the tip *if* the surface\n# were flat. We immediately project it to make it truly conformal in 3D.\nplanar_tip_arc = CenterArc((0, 0), width / 2, 270, 180).locate(tip_center_loc).edge()\ntip_arc = planar_tip_arc.project_to_shape(center_surface, -normal_at_tip_center)[0]\n\n# Step 4: Build the tip as a Gordon surface (a surface fit through a curve network).\n# Gordon surfaces are ideal when:\n# - you don\u2019t have an obvious analytic surface\n# - curvature changes in two directions (doubly-curved \"cap\")\n# - you can define a consistent set of profile curves + guide curves\n#\n# Here:\n# - profiles define \"across the tip\" shape (section -> bulged spline -> mirrored section)\n# - guides define \"along the tip\" rails (start point -> projected 3D arc -> end point)\n#\n# Tangents are used to encourage smoothness where the tip joins the swept center section.\nprofile = Spline(\n half_x_section @ 0,\n tip_arc @ 0.5,\n half_x_section @ 1,\n tangents=(center_arc % 1, -(center_arc % 1)),\n)\ntip_surface = Face.make_gordon_surface(\n profiles=[half_x_section, profile, half_x_section.mirror(Plane.XY)],\n guides=[half_x_section @ 0, tip_arc, half_x_section @ 1],\n)\n\n# Step 5: Close the tip surface into a watertight Solid.\n# tip_surface is the outer \"skin\"; we create a side face from its boundary wire\n# and make a shell, then a solid.\ntip_side = Face(tip_surface.wire())\ntip = Solid(Shell([tip_side, tip_surface]))\n\n# Step 6: Sweep the *flat end face* of the tip around the center arc.\n# This is the trick that makes the center section compatible with the freeform tip:\n# the sweep profile is the same face that bounds the tip, so the join is naturally aligned.\ncenter_section = sweep(tip_side, center_arc).solid()\n\n# Step 7: Assemble the bracelet from the center and two mirrored tips.\n# Mirror across YZ to create the opposite end cap.\nbracelet = Solid() + [tip, center_section, tip.mirror(Plane.YZ)]\n\n# Step 8: Add an embossed label.\n# This is often the hardest operation for OCCT in this model:\n# projecting text onto a doubly-curved surface can create many small faces/edges,\n# and thickening them adds even more boolean complexity.\nif label_str:\n label = Text(label_str, font_size=width * 0.8, align=Align.CENTER)\n\n # Project the text onto the bracelet using a path-based placement along center_arc.\n # The parameter offsets the label so it sits centered along arc-length.\n p_labels = bracelet.project_faces(\n label, center_arc, 0.5 - 0.5 * (label.bounding_box().size.X) / center_arc.length\n )\n # Turn the projected faces into solids via thickening (embossing).\n embossed_label = [Solid.thicken(f, 0.5) for f in p_labels.faces()]\n bracelet += embossed_label\n\n# Step 9: Add alignment holes to aid assembly after 3D printing in two halves.\n# These are placed at evenly spaced locations along the arc (including both ends).\n# A small clearance (+0.15) is included for typical FDM tolerances.\nalignment_holes = [\n Pos(p) * Cylinder(1.75 / 2 + 0.15, 8)\n for p in [center_arc.position_at(i / 4) for i in range(5)]\n]\nbracelet -= alignment_holes\n\nshow(bracelet)\n# [End]\n"); + // real build123d: bracelet.volume == 18972.11597109143 + expect(Math.abs(measured["bracelet"].volume - 18972.11597109143)) + .toBeLessThan(18972.11597109143 * 0.005); + const bbox = [-47.5000068068, -28.4290724178, -12.5000001, 47.5000068068, 32.5000001, 12.5000001]; + for (let i = 0; i < 6; i++) { + expect(Math.abs(measured["bracelet"].bbox[i] - bbox[i])).toBeLessThan(1e-3); + } + // the Gordon tip surface itself (realization accuracy) + expect(Math.abs(measured["tip_surface"].area - 540.9747242723)) + .toBeLessThan(540.9747242723 * 0.005); + }); + + + // examples/bicycle_tire - wrap_faces: a flat tread pattern conformed onto + // the tire's surface of revolution, thickened into nubs and copied around + test("examples/bicycle_tire", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "# [Code]\nimport copy\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import show\n\nwheel_diameter = 740 * MM\n\nwith BuildSketch() as tire_profile:\n with BuildLine() as build_profile:\n l00 = Bezier((0.0, 0.0), (7.05, 0.0), (12.18, 1.54), (15.13, 4.54))\n l01 = Bezier(l00 @ 1, (15.81, 5.22), (15.98, 5.44), (16.5, 6.23))\n l02 = Bezier(l01 @ 1, (18.45, 9.19), (19.61, 13.84), (19.94, 20.06))\n l03 = Bezier(l02 @ 1, (20.1, 23.24), (19.93, 27.48), (19.56, 29.45))\n l04 = Bezier(l03 @ 1, (19.13, 31.69), (18.23, 33.67), (16.91, 35.32))\n l05 = Bezier(l04 @ 1, (16.26, 36.12), (15.57, 36.77), (14.48, 37.58))\n l06 = Bezier(l05 @ 1, (12.77, 38.85), (11.51, 40.28), (10.76, 41.78))\n l07 = Bezier(l06 @ 1, (10.07, 43.16), (10.15, 43.81), (11.03, 43.98))\n l08 = Bezier(l07 @ 1, (11.82, 44.13), (12.15, 44.55), (12.08, 45.33))\n l09 = Bezier(l08 @ 1, (12.01, 46.07), (11.84, 46.43), (11.43, 46.69))\n l10 = Bezier(l09 @ 1, (10.98, 46.97), (10.07, 46.7), (9.47, 46.1))\n l11 = Bezier(l10 @ 1, (9.03, 45.65), (8.88, 45.31), (8.84, 44.65))\n l12 = Bezier(l11 @ 1, (8.78, 43.6), (9.11, 42.26), (9.72, 41.0))\n l13 = Bezier(l12 @ 1, (10.43, 39.54), (11.52, 38.2), (12.78, 37.22))\n l14 = Bezier(l13 @ 1, (15.36, 35.23), (16.58, 33.76), (17.45, 31.62))\n l15 = Bezier(l14 @ 1, (17.91, 30.49), (18.22, 29.27), (18.4, 27.8))\n l16 = Bezier(l15 @ 1, (18.53, 26.78), (18.52, 23.69), (18.37, 22.61))\n l17 = Bezier(l16 @ 1, (17.8, 18.23), (16.15, 14.7), (13.39, 11.94))\n l18 = Bezier(l17 @ 1, (11.89, 10.45), (10.19, 9.31), (8.09, 8.41))\n l19 = Bezier(l18 @ 1, (3.32, 6.35), (0.0, 6.64))\n mirror(about=Plane.YZ)\n make_face()\n\ntire = revolve(Pos(Y=-wheel_diameter / 2) * tire_profile.face(), Axis.X)\n\nwith BuildSketch() as tread_pattern:\n with Locations((1, 1)):\n Trapezoid(15, 12, 60, 120, align=Align.MIN)\n with Locations((1, 8)):\n with GridLocations(0, 5, 1, 2):\n Rectangle(50, 2, mode=Mode.SUBTRACT)\n\n# Define the surface and path that the tread pattern will be wrapped onto\nhalf_road_surface = Face.revolve(Pos(Y=-wheel_diameter / 2) * l00, 360, Axis.X)\ntread_path = half_road_surface.edges().sort_by(Axis.X)[0]\n\n# Wrap the planar tread pattern onto the tire's outside surface\ntread_faces = half_road_surface.wrap_faces(tread_pattern.faces(), tread_path)\n\n# Mirror the faces to the other half of the tire\ntread_faces.extend([mirror(t, Plane.YZ) for t in tread_faces])\n\n# Thicken the tread to become solid nubs\n# tread_prime = [Solid.thicken(f, 3 * MM) for f in tread_faces]\ntread_prime = [thicken(f, 3 * MM) for f in tread_faces]\n\n# Copy the nubs around the whole tire\ntread = [Rot(X=r) * copy.copy(t) for t in tread_prime for r in range(0, 360, 2)]\n\nshow(tire, tread)\n# [End]\n"); + // real build123d: tire.volume == 906269.1100540357 + expect(Math.abs(measured["tire"].volume - 906269.1100540357)) + .toBeLessThan(906269.1100540357 * 0.005); + // the three wrapped tread faces + const wrapped = [12.228202995761057, 24.30283196548578, 28.299963439775834]; + for (let i = 0; i < 3; i++) { + expect(Math.abs(measured[`tread_faces[${i}]`].area - wrapped[i])) + .toBeLessThan(wrapped[i] * 0.005); + } + // thickened nub + one of the 64 rotated copies + expect(Math.abs(measured["tread_prime[0]"].volume - 40.275611681727014)) + .toBeLessThan(40.275611681727014 * 0.005); + expect(Math.abs(measured["tread[0]"].volume - 88.53291874220363)) + .toBeLessThan(88.53291874220363 * 0.005); + }); + + + // one-sided line offsets (offset(side=Side.LEFT/RIGHT)) - the operation + // examples/dual_color_3mf is built on; values from real build123d 0.11.1 + test("offset(side=) on open lines", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\n\n# One-sided offsets of an OPEN line: keep the LEFT / RIGHT side of the offset\n# and close it back onto the original line (build123d offset(side=...)).\nwith BuildSketch() as left_band:\n with BuildLine():\n Polyline((0, 0), (10, 0), (10, 6))\n offset(amount=2, side=Side.LEFT)\n make_face()\n\nwith BuildSketch() as right_band:\n with BuildLine():\n Polyline((0, 0), (10, 0), (10, 6))\n offset(amount=2, side=Side.RIGHT)\n make_face()\n\n# The pattern examples/dual_color_3mf builds with it\nwith BuildSketch() as tile_pattern:\n with BuildLine():\n Polyline((9, 9), (1, 5), (-0.5, 0))\n offset(amount=1, side=Side.LEFT)\n make_face()\n"); + // real build123d: 28.0 (inner band) and 35.141592653589793 (outer band) + expect(Math.abs(measured["left_band"].area - 28.0)).toBeLessThan(28.0 * 0.005); + expect(Math.abs(measured["right_band"].area - 35.141592653589793)) + .toBeLessThan(35.141592653589793 * 0.005); + // real build123d: 13.732352941176471 for the dual_color_3mf tile pattern + expect(Math.abs(measured["tile_pattern"].area - 13.732352941176471)) + .toBeLessThan(13.732352941176471 * 0.005); + }); + + + // ------------------------------------------------------------------ + // Broadened corpus (docs .py scripts, the Too Tall Toby challenge + // parts and docs .rst code-blocks) - frozen when they first passed. + // ------------------------------------------------------------------ + + // ttt/ttt-ppp0101 — Too Tall Toby PPP01-01 bearing bracket: PolarLine(length_mode=VERTICAL), split, mirror, CounterBoreHole - the script's own mass assert is part of the test + test("ttt/ttt-ppp0101", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\nwith BuildPart() as p:\n with BuildSketch() as s:\n Rectangle(115, 50)\n with Locations((5 / 2, 0)):\n SlotOverall(90, 12, mode=Mode.SUBTRACT)\n extrude(amount=15)\n\n with BuildSketch(Plane.XZ.offset(50 / 2)) as s3:\n with Locations((-115 / 2 + 26, 15)):\n SlotOverall(42 + 2 * 26 + 12, 2 * 26, rotation=90)\n zz = extrude(amount=-12)\n split(bisect_by=Plane.XY)\n edgs = p.part.edges().filter_by(Axis.Y).group_by(Axis.X)[-2]\n fillet(edgs, 9)\n\n with Locations(zz.faces().sort_by(Axis.Y)[0]):\n with Locations((42 / 2 + 6, 0)):\n CounterBoreHole(24 / 2, 34 / 2, 4)\n mirror(about=Plane.XZ)\n\n with BuildSketch() as s4:\n RectangleRounded(115, 50, 6)\n extrude(amount=80, mode=Mode.INTERSECT)\n # fillet does not work right, mode intersect is safer\n\n with BuildSketch(Plane.YZ) as s4:\n with BuildLine() as bl:\n l1 = Line((0, 0), (18 / 2, 0))\n l2 = PolarLine(l1 @ 1, 8, 60, length_mode=LengthMode.VERTICAL)\n l3 = Line(l2 @ 1, (0, 8))\n mirror(about=Plane.YZ)\n make_face()\n extrude(amount=115/2, both=True, mode=Mode.SUBTRACT)\n\nshow_object(p)\n\n\ngot_mass = p.part.volume*densa\nwant_mass = 797.15\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n"); + // real build123d: p.volume == 102198.22251481404 + expect(Math.abs(measured["p"].volume - 102198.22251481404)) + .toBeLessThan(102198.22251481404 * 0.005); + // real build123d: zz.volume == 59180.599605920404 + expect(Math.abs(measured["zz"].volume - 59180.599605920404)) + .toBeLessThan(59180.599605920404 * 0.005); + }); + + // ttt/ttt-ppp0102 — Too Tall Toby PPP01-02: PolarLine(length_mode=) + sweep(path=) + test("ttt/ttt-ppp0102", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\n\n# TTT Party Pack 01: PPP0102, mass(abs) = 43.09g\nwith BuildPart() as p:\n with BuildSketch(Plane.XZ) as sk1:\n Rectangle(49, 48 - 8, align=(Align.CENTER, Align.MIN))\n Rectangle(9, 48, align=(Align.CENTER, Align.MIN))\n with Locations((9 / 2, 40)):\n Ellipse(20, 8)\n split(bisect_by=Plane.YZ)\n revolve(axis=Axis.Z)\n\n with BuildSketch(Plane.YZ.offset(-15)) as xc1:\n with Locations((0, 40 / 2 - 17)):\n Ellipse(10 / 2, 4 / 2)\n with BuildLine(Plane.XZ) as l1:\n CenterArc((-15, 40 / 2), 17, 90, 180)\n sweep(path=l1)\n\n fillet(p.edges().filter_by(GeomType.CIRCLE, reverse=True).group_by(Axis.X)[0], 1)\n\n with BuildLine(mode=Mode.PRIVATE) as lc1:\n PolarLine(\n (42 / 2, 0), 37, 94, length_mode=LengthMode.VERTICAL\n ) # construction line\n\n pts = [\n (0, 0),\n (42 / 2, 0),\n ((lc1.line @ 1).X, (lc1.line @ 1).Y),\n (0, (lc1.line @ 1).Y),\n ]\n with BuildSketch(Plane.XZ) as sk2:\n Polygon(*pts, align=None)\n fillet(sk2.vertices().group_by(Axis.X)[1], 3)\n revolve(axis=Axis.Z, mode=Mode.SUBTRACT)\n\nshow(p)\n\n\ngot_mass = p.part.volume*densc\nwant_mass = 43.09\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n\n"); + // real build123d: p.volume == 42248.61825268254 + expect(Math.abs(measured["p"].volume - 42248.61825268254)) + .toBeLessThan(42248.61825268254 * 0.005); + }); + + // ttt/ttt-ppp0103 — Too Tall Toby PPP01-03: revolve profile + PolarLocations bosses, mass assert + test("ttt/ttt-ppp0103", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\n\nwith BuildPart() as ppp0103:\n with BuildSketch() as sk1:\n RectangleRounded(34 * 2, 95, 18)\n with Locations((0, -2)):\n RectangleRounded((34 - 16) * 2, 95 - 18 - 14, 7, mode=Mode.SUBTRACT)\n with Locations((-34 / 2, 0)):\n Rectangle(34, 95, 0, mode=Mode.SUBTRACT)\n extrude(amount=16)\n with BuildSketch(Plane.XZ.offset(-95 / 2)) as cyl1:\n with Locations((0, 16 / 2)):\n Circle(16 / 2)\n extrude(amount=18)\n with BuildSketch(Plane.XZ.offset(95 / 2 - 14)) as cyl2:\n with Locations((0, 16 / 2)):\n Circle(16 / 2)\n extrude(amount=23)\n with Locations(Plane.XZ.offset(95 / 2 + 9)):\n with Locations((0, 16 / 2)):\n CounterSinkHole(5.5 / 2, 11.2 / 2, None, 90)\n\nshow(ppp0103)\n\ngot_mass = ppp0103.part.volume*densb\nwant_mass = 96.13\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n"); + // real build123d: ppp0103.volume == 35605.546935185695 + expect(Math.abs(measured["ppp0103"].volume - 35605.546935185695)) + .toBeLessThan(35605.546935185695 * 0.005); + }); + + // ttt/ttt-ppp0104 — Too Tall Toby PPP01-04: extrude/until + fillet chains, mass assert + test("ttt/ttt-ppp0104", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\nd1, d2, d3 = 38, 26, 16\nh1, h2, h3, h4 = 20, 8, 7, 23\nw1, w2, w3 = 80, 10, 5\nf1, f2, f3 = 4, 10, 5\nsloth1, sloth2 = 18, 12\nslotw1, slotw2 = 17, 14\n\nwith BuildPart() as p:\n with BuildSketch() as s:\n Circle(d1 / 2)\n extrude(amount=h1)\n with BuildSketch(Plane.XY.offset(h1)) as s2:\n Circle(d2 / 2)\n extrude(amount=h2)\n with BuildSketch(Plane.YZ) as s3:\n Rectangle(d1 + 15, h3, align=(Align.CENTER, Align.MIN))\n extrude(amount=w1 - d1 / 2)\n # fillet workaround \\/\n ped = p.part.edges().group_by(Axis.Z)[2].filter_by(GeomType.CIRCLE)\n fillet(ped, f1)\n with BuildSketch(Plane.YZ) as s3a:\n Rectangle(d1 + 15, 15, align=(Align.CENTER, Align.MIN))\n Rectangle(d1, 15, mode=Mode.SUBTRACT, align=(Align.CENTER, Align.MIN))\n extrude(amount=w1 - d1 / 2, mode=Mode.SUBTRACT)\n # end fillet workaround /\\\n with BuildSketch() as s4:\n Circle(d3 / 2)\n extrude(amount=h1 + h2, mode=Mode.SUBTRACT)\n with BuildSketch() as s5:\n with Locations((w1 - d1 / 2 - w2 / 2, 0)):\n Rectangle(w2, d1)\n extrude(amount=-h4)\n fillet(p.part.edges().group_by(Axis.X)[-1].sort_by(Axis.Z)[-1], f2)\n fillet(p.part.edges().group_by(Axis.X)[-4].sort_by(Axis.Z)[-2], f3)\n pln = Plane.YZ.offset(w1 - d1 / 2)\n with BuildSketch(pln) as s6:\n with Locations((0, -h4)):\n SlotOverall(slotw1 * 2, sloth1, 90)\n extrude(amount=-w3, mode=Mode.SUBTRACT)\n with BuildSketch(pln) as s6b:\n with Locations((0, -h4)):\n SlotOverall(slotw2 * 2, sloth2, 90)\n extrude(amount=-w2, mode=Mode.SUBTRACT)\n\nshow(p)\n\n\ngot_mass = p.part.volume*densa\nwant_mass = 310\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n"); + // real build123d: p.volume == 39743.211180667735 + expect(Math.abs(measured["p"].volume - 39743.211180667735)) + .toBeLessThan(39743.211180667735 * 0.005); + }); + + // ttt/ttt-ppp0105 — Too Tall Toby PPP01-05: lofted transition + holes, mass assert + test("ttt/ttt-ppp0105", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\nwith BuildPart() as p:\n with BuildSketch() as s:\n SlotOverall(45, 38)\n offset(amount=3)\n with BuildSketch(Plane.XY.offset(133 - 30)) as s2:\n SlotOverall(60, 4)\n offset(amount=3)\n loft()\n\n with BuildSketch() as s3:\n SlotOverall(45, 38)\n with BuildSketch(Plane.XY.offset(133 - 30)) as s4:\n SlotOverall(60, 4)\n loft(mode=Mode.SUBTRACT)\n\n extrude(p.part.faces().sort_by(Axis.Z)[0], amount=30)\n\nshow(p)\n\n\ngot_mass = p.part.volume*densc\nwant_mass = 57.08\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n\n"); + // real build123d: p.volume == 55617.528016135795 + expect(Math.abs(measured["p"].volume - 55617.528016135795)) + .toBeLessThan(55617.528016135795 * 0.005); + }); + + // ttt/ttt-ppp0108 — Too Tall Toby PPP01-08: the largest of the challenge parts (3.4 kg), mass assert + test("ttt/ttt-ppp0108", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\nwith BuildPart() as p:\n with BuildSketch() as s1:\n Rectangle(188 / 2 - 33, 162, align=(Align.MIN, Align.CENTER))\n with Locations((188 / 2 - 33, 0)):\n SlotOverall(190, 33 * 2, rotation=90)\n mirror(about=Plane.YZ)\n with GridLocations(188 - 2 * 33, 190 - 2 * 33, 2, 2):\n Circle(29 / 2, mode=Mode.SUBTRACT)\n Circle(84 / 2, mode=Mode.SUBTRACT)\n extrude(amount=16)\n\n with BuildPart() as p2:\n with BuildSketch(Plane.XZ) as s2:\n with BuildLine() as l1:\n l1 = Polyline(\n (222 / 2 + 14 - 40 - 40, 0),\n (222 / 2 + 14 - 40, -35 + 16),\n (222 / 2 + 14, -35 + 16),\n (222 / 2 + 14, -35 + 16 + 30),\n (222 / 2 + 14 - 40 - 40, -35 + 16 + 30),\n close=True,\n )\n make_face()\n with Locations((222 / 2, -35 + 16 + 14)):\n Circle(11 / 2, mode=Mode.SUBTRACT)\n extrude(amount=20 / 2, both=True)\n with BuildSketch() as s3:\n with Locations(l1 @ 0):\n Rectangle(40 + 40, 8, align=(Align.MIN, Align.CENTER))\n with Locations((40, 0)):\n Rectangle(40, 20, align=(Align.MIN, Align.CENTER))\n extrude(amount=30, both=True, mode=Mode.INTERSECT)\n mirror(about=Plane.YZ)\n\nshow(p)\n\n\ngot_mass = p.part.volume*densa\nwant_mass = 3387.06\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f'{got_mass=}, {want_mass=}, {delta=}, {tolerance=}'\n"); + // real build123d: p.volume == 434238.2673538104 + expect(Math.abs(measured["p"].volume - 434238.2673538104)) + .toBeLessThan(434238.2673538104 * 0.005); + // real build123d: p2.volume == 57318.67288915634 + expect(Math.abs(measured["p2"].volume - 57318.67288915634)) + .toBeLessThan(57318.67288915634 * 0.005); + }); + + // ttt/ttt-ppp0109 — Too Tall Toby PPP01-09: Edge.find_tangent + tangent construction lines, mass assert + test("ttt/ttt-ppp0109", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from math import sqrt\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\ndensa = 7800 / 1e6 # carbon steel density g/mm^3\ndensb = 2700 / 1e6 # aluminum alloy\ndensc = 1020 / 1e6 # ABS\n\nwith BuildPart() as ppp109:\n with BuildSketch() as one:\n Rectangle(69, 75, align=(Align.MAX, Align.CENTER))\n fillet(one.vertices().group_by(Axis.X)[0], 17)\n extrude(amount=13)\n centers = [\n arc.arc_center\n for arc in ppp109.edges().filter_by(GeomType.CIRCLE).group_by(Axis.Z)[-1]\n ]\n with Locations(*centers):\n CounterBoreHole(radius=8 / 2, counter_bore_radius=15 / 2, counter_bore_depth=4)\n\n with BuildSketch(Plane.YZ) as two:\n with Locations((0, 45)):\n Circle(15)\n with BuildLine() as bl:\n c = Line((75 / 2, 0), (75 / 2, 60), mode=Mode.PRIVATE)\n u = two.edge().find_tangent(75 / 2 + 90)[0] # where is the slope 75/2?\n l1 = IntersectingLine(\n two.edge().position_at(u), -two.edge().tangent_at(u), other=c\n )\n Line(l1 @ 0, (0, 45))\n Polyline((0, 0), c @ 0, l1 @ 1)\n mirror(about=Plane.YZ)\n make_face()\n with Locations((0, 45)):\n Circle(12 / 2, mode=Mode.SUBTRACT)\n extrude(amount=-13)\n\n with BuildSketch(Plane((0, 0, 0), x_dir=(1, 0, 0), z_dir=(1, 0, 1))) as three:\n Rectangle(45 * 2 / sqrt(2) - 37.5, 75, align=(Align.MIN, Align.CENTER))\n with Locations(three.edges().sort_by(Axis.X)[-1].center()):\n Circle(37.5)\n Circle(33 / 2, mode=Mode.SUBTRACT)\n split(bisect_by=Plane.YZ)\n extrude(amount=6)\n f = ppp109.faces().filter_by(Axis((0, 0, 0), (-1, 0, 1)))[0]\n extrude(f, until=Until.NEXT)\n fillet(ppp109.edges().filter_by(Axis.Y).sort_by(Axis.Z)[2], 16)\n # extrude(f, amount=10)\n # fillet(ppp109.edges(Select.NEW), 16)\n\n\nshow(ppp109)\n\ngot_mass = ppp109.part.volume * densb\nwant_mass = 307.23\ntolerance = 1\ndelta = abs(got_mass - want_mass)\nprint(f\"Mass: {got_mass:0.2f} g\")\nassert delta < tolerance, f\"{got_mass=}, {want_mass=}, {delta=}, {tolerance=}\"\n"); + // real build123d: ppp109.volume == 113789.2638826812 + expect(Math.abs(measured["ppp109"].volume - 113789.2638826812)) + .toBeLessThan(113789.2638826812 * 0.005); + }); + + // docs-rst/topology_selection/b08 — new_edges(box, cylinder, combined=part) - the module-level selector + test("docs-rst/topology_selection/b08", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\nfrom math import *\n\nbox = Box(5, 5, 1)\ncircle = Cylinder(2, 5)\npart = box + circle\nedges = new_edges(box, circle, combined=part)\n"); + // real build123d: part.volume == 75.26548245743669 + expect(Math.abs(measured["part"].volume - 75.26548245743669)) + .toBeLessThan(75.26548245743669 * 0.005); + // real build123d: circle.volume == 62.83185307179585 + expect(Math.abs(measured["circle"].volume - 62.83185307179585)) + .toBeLessThan(62.83185307179585 * 0.005); + // real build123d: box.volume == 24.999999999999993 + expect(Math.abs(measured["box"].volume - 24.999999999999993)) + .toBeLessThan(24.999999999999993 * 0.005); + }); + + // docs-rst/topology_selection/b09 — new_edges() after a fillet (upstream's Select.NEW-returns-nothing example) + test("docs-rst/topology_selection/b09", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\nfrom math import *\n\nbox = Box(5, 5, 1)\ncircle = Cylinder(2, 5)\npart_before = box + circle\nedges = part_before.edges().filter_by(lambda a: a.length == 1)\npart = fillet(edges, 1)\nedges = new_edges(part_before, combined=part)\n"); + // real build123d: part_before.volume == 75.26548245743669 + expect(Math.abs(measured["part_before"].volume - 75.26548245743669)) + .toBeLessThan(75.26548245743669 * 0.005); + // real build123d: part.volume == 74.40707511102647 + expect(Math.abs(measured["part"].volume - 74.40707511102647)) + .toBeLessThan(74.40707511102647 * 0.005); + // real build123d: circle.volume == 62.83185307179585 + expect(Math.abs(measured["circle"].volume - 62.83185307179585)) + .toBeLessThan(62.83185307179585 * 0.005); + }); + + // docs-rst/tips/b05 — module-level vertices() context selector on a rotated workplane + test("docs-rst/tips/b05", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\nfrom math import *\n\nwith BuildSketch(Plane.YZ.rotated((123, 45, 6))) as custom_plane:\n Rectangle(1, 1, align=Align.MIN)\n with Locations(vertices().group_by(Axis.X)[-1].sort_by(Axis.Y)[-1]):\n Circle(0.2)\n"); + // real build123d: custom_plane.area == 1.0942477796076904 + expect(Math.abs(measured["custom_plane"].area - 1.0942477796076904)) + .toBeLessThan(1.0942477796076904 * 0.005); + }); + + // docs-rst/OpenSCAD/b01 — fillet(edges().filter_by(lambda e: e.is_interior)) + test("docs-rst/OpenSCAD/b01", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\nfrom math import *\n\n# Builder mode\nwith BuildPart() as angle_iron:\n with BuildSketch() as profile:\n Rectangle(3 * CM, 4 * MM, align=Align.MIN)\n Rectangle(4 * MM, 3 * CM, align=Align.MIN)\n extrude(amount=10 * CM)\n fillet(angle_iron.edges().filter_by(lambda e: e.is_interior), 5 * MM)\n"); + // real build123d: angle_iron.volume == 22936.50459150638 + expect(Math.abs(measured["angle_iron"].volume - 22936.50459150638)) + .toBeLessThan(22936.50459150638 * 0.005); + }); + + // docs-rst/tutorial_design/b07 — FilletPolyline + test("docs-rst/tutorial_design/b07", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\n# [removed by collect.py] from ocp_vscode import show_all\n\nthickness = 3 * MM\nwidth = 25 * MM\nlength = 50 * MM\nheight = 25 * MM\nhole_diameter = 5 * MM\nbend_radius = 5 * MM\nfillet_radius = 2 * MM\n\nwith BuildPart() as bracket:\n with BuildSketch() as sketch:\n with BuildLine() as profile:\n FilletPolyline(\n (0, 0), (length / 2, 0), (length / 2, height), radius=bend_radius\n )\n offset(amount=thickness, side=Side.LEFT)\n make_face()\n mirror(about=Plane.YZ)\n extrude(amount=width / 2)\n mirror(about=Plane.XY)\n corners = bracket.edges().filter_by(Axis.X).group_by(Axis.Y)[-1]\n fillet(corners, fillet_radius)\n with Locations(bracket.faces().sort_by(Axis.X)[-1]):\n Hole(hole_diameter / 2)\n with BuildSketch(bracket.faces().sort_by(Axis.Y)[0]):\n SlotOverall(20 * MM, hole_diameter)\n extrude(amount=-thickness, mode=Mode.SUBTRACT)\n\nshow_all()\n"); + // real build123d: bracket.volume == 6412.652585245836 + expect(Math.abs(measured["bracket"].volume - 6412.652585245836)) + .toBeLessThan(6412.652585245836 * 0.005); + }); + + // docs-rst/key_concepts_builder/b13 — Locations around a nested BuildSketch must NOT replicate (0.11.1 semantics) + test("docs-rst/key_concepts_builder/b13", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\nfrom math import *\n\nwith BuildPart() as model:\n with Locations((-20, 0), (20, 0)):\n with BuildSketch() as holes:\n Circle(3)\n extrude(amount=5)\n"); + // real build123d: model.volume == 141.3716694115407 + expect(Math.abs(measured["model"].volume - 141.3716694115407)) + .toBeLessThan(141.3716694115407 * 0.005); + }); + + + // docs/objects_3d — Wedge (BRepPrimAPI_MakeWedge min/max form) + ConvexPolyhedron + test("docs/objects_3d", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "# [Setup]\nfrom build123d import *\n\n# [Setup]\n\n\ndef write_svg(filename: str, view_port_origin=(-100, -50, 30)):\n \"\"\"Save an image of the BuildPart object as SVG\"\"\"\n builder: BuildPart = BuildPart._get_context()\n\n visible, hidden = builder.part.project_to_viewport(view_port_origin)\n max_dimension = max(*Compound(children=visible + hidden).bounding_box().size)\n exporter = ExportSVG(scale=100 / max_dimension)\n exporter.add_layer(\"Visible\")\n exporter.add_layer(\"Hidden\", line_color=(99, 99, 99), line_type=LineType.ISO_DOT)\n exporter.add_shape(visible, layer=\"Visible\")\n exporter.add_shape(hidden, layer=\"Hidden\")\n exporter.write(f\"assets/{filename}.svg\")\n\n\n# [Ex. 1]\nwith BuildPart() as example_1:\n Box(3, 2, 1)\n # [Ex. 1]\n pass # [removed by collect.py] write_svg(\"box_example\")\n\n# [Ex. 2]\nwith BuildPart() as example_2:\n Cone(2, 1, 2)\n # [Ex. 2]\n pass # [removed by collect.py] write_svg(\"cone_example\")\n\n# [Ex. 3]\nwith BuildPart() as example_3:\n Box(3, 2, 1)\n with Locations(example_3.faces().sort_by(Axis.Z)[-1]):\n CounterBoreHole(0.2, 0.4, 0.5, 0.9)\n # [Ex. 3]\n pass # [removed by collect.py] write_svg(\"counter_bore_hole_example\")\n\n\n# [Ex. 4]\nwith BuildPart() as example_4:\n Box(3, 2, 1)\n with Locations(example_3.faces().sort_by(Axis.Z)[-1]):\n CounterSinkHole(0.2, 0.4, 0.9)\n # [Ex. 4]\n pass # [removed by collect.py] write_svg(\"counter_sink_hole_example\")\n\n# [Ex. 5]\nwith BuildPart() as example_5:\n Cylinder(1, 2)\n # [Ex. 5]\n pass # [removed by collect.py] write_svg(\"cylinder_example\")\n\n# [Ex. 6]\nwith BuildPart() as example_6:\n Box(3, 2, 1)\n Hole(0.4)\n # [Ex. 6]\n pass # [removed by collect.py] write_svg(\"hole_example\")\n\n# [Ex. 7]\nwith BuildPart() as example_7:\n Sphere(1, 0)\n # [Ex. 7]\n pass # [removed by collect.py] write_svg(\"sphere_example\")\n\n# [Ex. 8]\nwith BuildPart() as example_8:\n Torus(1, 0.2)\n # [Ex. 8]\n pass # [removed by collect.py] write_svg(\"torus_example\")\n\n# [Ex. 9]\nwith BuildPart() as example_9:\n Wedge(1, 1, 1, 0, 0, 0.5, 0.5)\n # [Ex. 9]\n pass # [removed by collect.py] write_svg(\"wedge_example\")\n\n# [Ex. 10]\nwith BuildPart() as example_10:\n Box(30, 20, 20)\n Box(20, 30, 20)\n Box(20, 20, 30)\n with Locations((-10, 0, 0)):\n Box(40, 23, 23)\n ConvexPolyhedron(example_10.vertices())\n # [Ex. 10]\n pass # [removed by collect.py] write_svg(\"convex_polyhedron_example\")\n"); + // real build123d: example_9.volume == 0.5833333333333333 + expect(Math.abs(measured["example_9"].volume - 0.5833333333333333)) + .toBeLessThan(0.5833333333333333 * 0.005); + // real build123d: example_10.volume == 33876.666666666664 + expect(Math.abs(measured["example_10"].volume - 33876.666666666664)) + .toBeLessThan(33876.666666666664 * 0.005); + }); + + // docs-rst/tutorial_constraints/b03 — Triangle (the trianglesolver port) + test("docs-rst/tutorial_constraints/b03", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\nfrom math import *\n\nisosceles = Triangle(a=30, b=30, C=60)\nisosceles.c\nisosceles.A\nisosceles.B\nisosceles.vertex_A\n"); + // real build123d: isosceles.area == 389.71143170299746 + expect(Math.abs(measured["isosceles"].area - 389.71143170299746)) + .toBeLessThan(389.71143170299746 * 0.005); + }); + + // docs/objects_1d_parabolic_hyperbolic — ParabolicCenterArc / HyperbolicCenterArc (gp_Parab / gp_Hypr) + test("docs/objects_1d_parabolic_hyperbolic", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "# [Setup]\nfrom build123d import *\n\n# from ocp_vscode import *\n\ndot = Circle(0.05)\n\nwith BuildLine() as parabolic_center_arc:\n ParabolicCenterArc((0, 0), 0.25, -60, arc_size=120)\ns = 100 / max(*parabolic_center_arc.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(parabolic_center_arc.line)\nsvg.add_shape(dot.moved(Location(Vector((0, 0)))))\nsvg.write(\"assets/parabolic_center_arc_example.svg\")\n\nwith BuildLine() as hyperbolic_center_arc:\n HyperbolicCenterArc((0, 0), 0.5, 1, 0, arc_size=180)\ns = 100 / max(*hyperbolic_center_arc.line.bounding_box().size)\nsvg = ExportSVG(scale=s)\nsvg.add_shape(hyperbolic_center_arc.line)\nsvg.add_shape(dot.moved(Location(Vector((0, 0)))))\nsvg.write(\"assets/hyperbolic_center_arc_example.svg\")\n\n# show_all()\n"); + // real build123d: parabolic_center_arc bbox == [0.0, -1.047197551, 0.0, 1.096622711, 1.047197551, 0.0] + const bbox_parabolic_center_arc = [0.0, -1.047197551, 0.0, 1.096622711, 1.047197551, 0.0]; + for (let i = 0; i < 6; i++) { + expect(Math.abs(measured["parabolic_center_arc"].bbox[i] - bbox_parabolic_center_arc[i])).toBeLessThan(1e-3); + } + // real build123d: hyperbolic_center_arc bbox == [-1.150649451, 1.0, 0.0, 1.150649451, 2.509178479, 0.0] + const bbox_hyperbolic_center_arc = [-1.150649451, 1.0, 0.0, 1.150649451, 2.509178479, 0.0]; + for (let i = 0; i < 6; i++) { + expect(Math.abs(measured["hyperbolic_center_arc"].bbox[i] - bbox_hyperbolic_center_arc[i])).toBeLessThan(1e-3); + } + }); + + // docs/slide_latch — BuildSketch's face alignment (localize + orient +Z) and Select.LAST vertices + test("docs/slide_latch", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "from build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nwith BuildPart() as latch:\n # Basic box shape to start with filleted corners\n Box(70, 30, 14)\n end = latch.faces().sort_by(Axis.X)[-1] # save the end with the hole\n fillet(latch.edges().filter_by(Axis.Z), 2)\n fillet(latch.edges().sort_by(Axis.Z)[-1], 1)\n # Make screw tabs\n with BuildSketch(latch.faces().sort_by(Axis.Z)[0]) as l4:\n with Locations((-30, 0), (30, 0)):\n SlotOverall(50, 10, rotation=90)\n Rectangle(50, 30)\n fillet(l4.vertices(Select.LAST), radius=2)\n extrude(amount=-2)\n with GridLocations(60, 40, 2, 2):\n Hole(2)\n # Create the hole from the end saved previously\n with BuildSketch(end) as slide_hole:\n add(end)\n offset(amount=-2)\n fillet(slide_hole.vertices(), 1)\n extrude(amount=-68, mode=Mode.SUBTRACT)\n # Slot for the handle to slide in\n with BuildSketch(latch.faces().sort_by(Axis.Z)[-1]):\n SlotOverall(32, 8)\n extrude(amount=-2, mode=Mode.SUBTRACT)\n # The slider will move align the x axis 12mm in each direction\n LinearJoint(\"latch\", axis=Axis.X, linear_range=(-12, 12))\n\nwith BuildPart() as slide:\n # The slide will be a little smaller than the hole\n with BuildSketch() as s1:\n add(slide_hole.sketch)\n offset(amount=-0.25)\n # The extrusions aren't symmetric\n extrude(amount=46)\n extrude(slide.faces().sort_by(Axis.Z)[0], amount=20)\n # Round off the ends\n fillet(slide.edges().group_by(Axis.Z)[0], 1)\n fillet(slide.edges().group_by(Axis.Z)[-1], 1)\n # Create the knob\n with BuildSketch() as s2:\n with Locations((12, 0)):\n SlotOverall(15, 4, rotation=90)\n Rectangle(12, 7, align=(Align.MIN, Align.CENTER))\n fillet(s2.vertices(Select.LAST), 1)\n split(bisect_by=Plane.XZ)\n revolve(axis=Axis.X)\n # Align the joint to Plane.ZY flipped\n RigidJoint(\"slide\", joint_location=Location(-Plane.ZY))\n\n# Position the slide in the latch: -12 >= position <= 12\nlatch.part.joints[\"latch\"].connect_to(slide.part.joints[\"slide\"], position=12)\n\n# show(latch.part, render_joints=True)\n# show(slide.part, render_joints=True)\nshow(latch.part, slide.part, render_joints=True)\n"); + // real build123d: latch.volume == 11831.250489574682 + expect(Math.abs(measured["latch"].volume - 11831.250489574682)) + .toBeLessThan(11831.250489574682 * 0.005); + // real build123d: slide.volume == 16765.45878762745 + expect(Math.abs(measured["slide"].volume - 16765.45878762745)) + .toBeLessThan(16765.45878762745 * 0.005); + }); + + // docs-selectors/group_properties_with_keys — copy(builder) snapshots + exact convex hull + GroupBy.group(key) + test("docs-selectors/group_properties_with_keys", async ({ page }) => { + await gotoAndReady(page); + const measured = await runAndMeasure(page, "import os\nfrom copy import copy\n\nfrom build123d import *\n# [removed by collect.py] from ocp_vscode import *\n\nworking_path = os.path.dirname(os.path.abspath(__file__))\nfiledir = os.path.join(working_path, \"..\", \"..\", \"assets\", \"topology_selection\")\n\nwith BuildPart() as part:\n with BuildSketch(Plane.XZ) as sketch:\n with BuildLine():\n CenterArc((-6, 12), 10, 0, 360)\n Line((-16, 0), (16, 0))\n make_hull()\n Rectangle(50, 5, align=(Align.CENTER, Align.MAX))\n\n extrude(amount=12)\n\n Box(38, 6, 22, align=(Align.CENTER, Align.MAX, Align.MIN), mode=Mode.SUBTRACT)\n\n circle = part.edges().filter_by(GeomType.CIRCLE).sort_by(Axis.Y)[0]\n with Locations(Plane(circle.arc_center, z_dir=circle.normal())):\n CounterBoreHole(13 / 2, 16 / 2, 4)\n\n mirror(about=Plane.XZ)\n\n before_fillet = copy(part)\n\n length_groups = part.edges().group_by(Edge.length)\n fillet(length_groups.group(6) + length_groups.group(5), 4)\n\n after_fillet = copy(part)\n\n with BuildSketch() as pins:\n with Locations((-21, 0)):\n Circle(3 / 2)\n with Locations((21, 0)):\n SlotCenterToCenter(1, 3)\n extrude(amount=-12, mode=Mode.SUBTRACT)\n\n with GridLocations(42, 16, 2, 2):\n CounterBoreHole(3.5 / 2, 3.5, 0)\n\n after_holes = copy(part)\n\n radius_groups = part.edges().filter_by(GeomType.CIRCLE).group_by(Edge.radius)\n bearing_edges = radius_groups.group(8).group_by(SortBy.DISTANCE)[-1]\n pin_edges = radius_groups.group(1.5).filter_by_position(Axis.Z, -5, -5)\n chamfer([pin_edges, bearing_edges], .5)\n\nlocation = Location((-20, -20))\nitems = [before_fillet.part] + length_groups.group(6) + length_groups.group(5)\nbefore = Compound(items).move(location)\nshow(before, after_fillet.part.move(Location((20, 20))))\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"group_length_key.png\"))\n\nlocation = Location((-20, -20), (180, 0, 0))\nafter = Compound([after_holes.part] + pin_edges + bearing_edges).move(location)\nshow(after, part.part.move(Location((20, 20), (180, 0, 0))))\n# [removed by collect.py] save_screenshot(os.path.join(filedir, \"group_radius_key.png\"))"); + // real build123d: before_fillet.volume == 9751.638840713076 + expect(Math.abs(measured["before_fillet"].volume - 9751.638840713076)) + .toBeLessThan(9751.638840713076 * 0.005); + // real build123d: after_fillet.volume == 9730.739028031032 + expect(Math.abs(measured["after_fillet"].volume - 9730.739028031032)) + .toBeLessThan(9730.739028031032 * 0.005); + }); + +}); diff --git a/test/python-mode.spec.js b/test/python-mode.spec.js new file mode 100644 index 00000000..04117db2 --- /dev/null +++ b/test/python-mode.spec.js @@ -0,0 +1,188 @@ +// @ts-check +// Tests for the Python (build123d-lite) language mode. +// Python code is evaluated in the CAD worker by Brython (lazy-loaded on the +// first Python evaluation); the build123d-lite module wraps the standard +// library, so shapes/booleans flow through the same scene bookkeeping. +const { test, expect } = require('@playwright/test'); + +/** Navigate to the app and wait until it's fully ready. */ +async function gotoAndReady(page) { + await page.goto('/'); + await page.waitForFunction(() => { + return window.CascadeAPI && window.CascadeAPI.isReady(); + }, { timeout: 60000 }); + await page.waitForFunction(() => !window.CascadeAPI.isWorking(), { timeout: 60000 }); +} + +/** Run code via the API and wait for the render to land. The first Python + * evaluation also fetches + boots Brython, hence the generous timeout. */ +async function runCodeAndRender(page, code, expectedShapes, timeout = 90000) { + const result = await page.evaluate((c) => window.CascadeAPI.runCode(c), code); + await page.waitForFunction( + (n) => window.threejsViewport._shapeLines.length === n, + expectedShapes, { timeout } + ); + return result; +} + +/** Worker log/error messages arrive asynchronously (postMessage via + * setTimeout), so poll for expected console content instead of sampling. */ +async function waitForConsole(page, getter, predicate, timeout = 15000) { + await page.waitForFunction( + ({ getter, predicate }) => { + const items = getter === 'errors' + ? window.CascadeAPI.getErrors() : window.CascadeAPI.getConsoleLog(); + return new Function('items', 'return ' + predicate)(items); + }, + { getter, predicate }, { timeout } + ); + return page.evaluate((g) => g === 'errors' + ? window.CascadeAPI.getErrors() : window.CascadeAPI.getConsoleLog(), getter); +} + +/** In-page helper source (same as gui-tools.spec.js): project CAD coords to + * screen and fire pointer events on the viewport canvas. */ +const POINTER_HELPERS = ` + function screenOfCad(x, y, z) { + var env = window.threejsViewport.environment; + var rect = env.renderer.domElement.getBoundingClientRect(); + var v = env.camera.position.clone().set(x, z, -y).project(env.camera); + return { x: rect.left + (v.x + 1) / 2 * rect.width, + y: rect.top + (1 - (v.y + 1) / 2) * rect.height }; + } + function fire(type, pt) { + var canvas = window.threejsViewport.environment.renderer.domElement; + canvas.dispatchEvent(new PointerEvent(type, { + clientX: pt.x, clientY: pt.y, button: 0, + buttons: type === 'pointerup' ? 0 : 1, + bubbles: true, cancelable: true, pointerId: 1 + })); + } +`; + +test.describe('Python (build123d) mode', () => { + test('starter script evaluates with no errors and renders shapes', async ({ page }) => { + await gotoAndReady(page); + + // Python is the default mode on a fresh load, so the starter is already in + // the editor (setMode('python') is a no-op here, and stays correct if the + // default ever moves again) + const starter = await page.evaluate(() => { + window.CascadeAPI.setMode('python'); + return window.CascadeAPI.getCode(); + }); + expect(await page.evaluate(() => window.CascadeAPI.getMode())).toBe('python'); + expect(starter).toContain('from build123d import *'); + expect(starter).toContain('plate = fillet(plate.edges().filter_by(Axis.Z), 12)'); + expect(starter).toContain('mount -= GridLocations('); + expect(starter).toContain('mount = fillet(mount.edges().group_by(Axis.Z)[-1], 1.5)'); + expect(starter).toContain('show(mount)'); + + const result = await runCodeAndRender(page, starter, 1); + expect(result.errors).toEqual([]); + expect(result.success).toBe(true); + + // History steps carry real Python line numbers (via Brython's frame chain) + const fnNames = result.historySteps.map((s) => s.fnName); + expect(fnNames).toEqual(expect.arrayContaining(['Box', 'Cylinder', 'Union', 'Difference', 'FilletEdges'])); + for (const step of result.historySteps) { + expect(step.lineNumber).toBeGreaterThan(0); + } + + // print(volume(...)) reaches the console (posted asynchronously) + const logs = await waitForConsole(page, 'logs', + 'items.some(l => l.startsWith("volume:"))'); + expect(logs.join('\n')).toContain('volume:'); + }); + + test('algebra ops produce sane volumes (union / difference)', async ({ page }) => { + await gotoAndReady(page); + await page.evaluate(() => window.CascadeAPI.setMode('python')); + + const code = [ + 'from build123d import *', + 'a = Box(20, 20, 20)', + 'b = Pos(5, 0, 0) * Box(20, 20, 20)', + 'u = a + b', + 'print("union_volume", volume(u))', + 'd = Pos(0, 40, 0) * (Box(20, 20, 20) - Cylinder(5, 40))', + 'print("diff_volume", volume(d))', + 'show(u, d)', + ].join('\n'); + + const result = await runCodeAndRender(page, code, 2); + expect(result.errors).toEqual([]); + + const logs = await waitForConsole(page, 'logs', + 'items.some(l => l.startsWith("union_volume")) && items.some(l => l.startsWith("diff_volume"))'); + const grab = (tag) => parseFloat(logs.find((l) => l.startsWith(tag)).split(' ')[1]); + // Two 20^3 boxes overlapping by 15 along X: 8000 + 8000 - 15*20*20 = 10000 + expect(grab('union_volume')).toBeCloseTo(10000, 0); + // 20^3 minus a through-hole of r=5: 8000 - pi*25*20 ~= 6429.2 + expect(grab('diff_volume')).toBeCloseTo(8000 - Math.PI * 25 * 20, 0); + }); + + test('Python errors surface as console errors with the traceback', async ({ page }) => { + await gotoAndReady(page); + await page.evaluate(() => window.CascadeAPI.setMode('python')); + + // Runtime error: line numbers refer to the user's editor lines + await page.evaluate((c) => window.CascadeAPI.runCode(c), + 'from build123d import *\nb = Box(10, 10, 10)\nq = undefined_name + 1\n'); + let errors = await waitForConsole(page, 'errors', + 'items.some(e => e.includes("NameError"))'); + const nameError = errors.find((e) => e.includes('NameError')); + expect(nameError).toContain("name 'undefined_name' is not defined"); + expect(nameError).toContain('line 3'); // the user's editor line + + // Syntax error + await page.evaluate((c) => window.CascadeAPI.runCode(c), + 'from build123d import *\ndef f(:\n'); + errors = await waitForConsole(page, 'errors', + 'items.some(e => e.includes("SyntaxError"))'); + expect(errors.find((e) => e.includes('SyntaxError'))).toContain("'(' was never closed"); + }); + + test('GUI Box tool in Python mode emits Pos * Box that round-trips; Sketch is disabled', async ({ page }) => { + await gotoAndReady(page); + await page.evaluate(() => window.CascadeAPI.setMode('python')); + await runCodeAndRender(page, 'from build123d import *\nshow(Box(10, 10, 10))\n', 1); + + // Sketch tool refuses to activate in Python mode (grayed out) + const sketch = await page.evaluate(() => { + window.CascadeAPI._tools.activate('sketch'); + return { + activeTool: window.CascadeAPI._tools.activeToolName, + disabled: !!document.querySelector('.cs-tool-btn[data-tool="sketch"].cs-tool-disabled'), + }; + }); + expect(sketch.activeTool).toBe('select'); + expect(sketch.disabled).toBe(true); + + // Drive the box tool: drag footprint (20,20)->(60,50), then height to 25. + // build123d's Box is centered, so the emitted Pos is the box CENTER. + const code = await page.evaluate((helpers) => { + eval(helpers); + window.CascadeAPI._tools.activate('box'); + fire('pointerdown', screenOfCad(20, 20, 0)); + fire('pointermove', screenOfCad(60, 50, 0)); + fire('pointerup', screenOfCad(60, 50, 0)); + fire('pointermove', screenOfCad(40, 35, 25)); + fire('pointerdown', screenOfCad(40, 35, 25)); // commit + fire('pointerup', screenOfCad(40, 35, 25)); + return window.CascadeAPI.getCode(); + }, POINTER_HELPERS); + expect(code).toContain('box1 = Pos(40, 35, 12.5) * Box(40, 30, 25)'); + + // The commit triggered an evaluation — scene gains a shape, no errors + await page.waitForFunction(() => !window.CascadeAPI.isWorking(), { timeout: 60000 }); + await page.waitForFunction( + () => window.threejsViewport._shapeLines.length === 2, undefined, { timeout: 60000 }); + expect(await page.evaluate(() => window.CascadeAPI.getErrors())).toEqual([]); + + // Round-trip: the emitted editor code re-runs cleanly + const editorCode = await page.evaluate(() => window.CascadeAPI.getCode()); + const rerun = await runCodeAndRender(page, editorCode, 2); + expect(rerun.errors).toEqual([]); + }); +});