forgecad 0.10.2 → 0.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +7 -6
  2. package/dist/assets/{AdminPage-CHY6ZN-p.js → AdminPage-CK7ObBz3.js} +1 -1
  3. package/dist/assets/{BenchmarkPage-BcRT5iGN.js → BenchmarkPage-Ds7Z2doN.js} +1 -1
  4. package/dist/assets/{BlogPage-BssBbnb-.js → BlogPage-DlPbpt6A.js} +1 -1
  5. package/dist/assets/{DocsPage-DsvdiRNK.js → DocsPage-vZb3b3Y0.js} +9 -14
  6. package/dist/assets/{EditorApp-BpjZgzk0.css → EditorApp-C5f24ZN9.css} +8 -0
  7. package/dist/assets/{EditorApp-Bfd3jbtC.js → EditorApp-HLoKfe15.js} +141 -12
  8. package/dist/assets/{EmbedViewer-D5t8WamV.js → EmbedViewer--KnqBKrJ.js} +2 -2
  9. package/dist/assets/{LandingPageProofDriven-DbN7o-Be.js → LandingPageProofDriven-C_LssmnA.js} +1 -1
  10. package/dist/assets/{LegalPage-DNGrrY0p.js → LegalPage-DGsyo4n1.js} +1 -1
  11. package/dist/assets/{PricingPage-Nczr3pRz.js → PricingPage-BOE27B-R.js} +1 -1
  12. package/dist/assets/{SettingsPage-DZlyu4d4.js → SettingsPage-f47cnk39.js} +1 -1
  13. package/dist/assets/{app-C9ct2hRD.js → app-D6ccu2Xx.js} +6854 -7373
  14. package/dist/assets/{backendInit-ymjonyQp.js → backendInit-DbTkQN9J.js} +2557 -809
  15. package/dist/assets/cli/{render-B_0lQwKU.js → render-BsngirjC.js} +114 -9
  16. package/dist/assets/{constructionHistoryWorker-CZ42Dksy.js → constructionHistoryWorker-PCwXrTDB.js} +175 -36
  17. package/dist/assets/{evalWorker-C2pm8LHP.js → evalWorker-CS63PfZu.js} +1125 -447
  18. package/dist/assets/{forgecad_geometry-BlMtqluF.js → forgecad_geometry-CZ_IfuvA.js} +1 -9
  19. package/dist/assets/{forgecad_geometry_bg-BllP_WiL.wasm → forgecad_geometry_bg-C3rQHfwg.wasm} +0 -0
  20. package/dist/assets/{inspectWorker-D5T5VbfK.js → inspectWorker-Y4cOzNyA.js} +4345 -373
  21. package/dist/assets/{jointPose-4r8ed8_5.js → jointPose-AMvCywzS.js} +1 -1
  22. package/dist/assets/{manifold-C4r6B-XY.js → manifold-CBry38ly.js} +2 -2
  23. package/dist/assets/{manifold-5PP1eGLN.js → manifold-Crd_F2qx.js} +1 -1
  24. package/dist/assets/{manifold-DjBkyIc8.js → manifold-k2kRcc85.js} +1 -1
  25. package/dist/assets/{reportWorker-CwenM7wB.js → reportWorker-CWvn0CEv.js} +1095 -400
  26. package/dist/cli/render.html +1 -1
  27. package/dist/docs/index.html +2 -2
  28. package/dist/docs-raw/AI/usage.md +2 -4
  29. package/dist/docs-raw/CLI.md +9 -7
  30. package/dist/docs-raw/README.md +1 -1
  31. package/dist/docs-raw/component-model.md +1 -1
  32. package/dist/docs-raw/generated/assembly.md +1 -1
  33. package/dist/docs-raw/generated/concepts.md +5 -3
  34. package/dist/docs-raw/generated/core.md +70 -1
  35. package/dist/docs-raw/generated/curves.md +8 -1
  36. package/dist/docs-raw/generated/output.md +0 -64
  37. package/dist/docs-raw/generated/runtime-names.md +6 -6
  38. package/dist/docs-raw/generated/viewport.md +3 -12
  39. package/dist/docs-raw/guides/inspection-bundles.md +1 -1
  40. package/dist/docs-raw/simulation-workflow.md +58 -0
  41. package/dist/docs-raw/skills/forgecad-blockout-model.md +1 -1
  42. package/dist/docs-raw/skills/forgecad-image-replicator.md +2 -2
  43. package/dist/docs-raw/skills/forgecad-mujoco-verify.md +78 -0
  44. package/dist/docs-raw/skills/forgecad-spec-by-walking-through-it.md +145 -0
  45. package/dist/docs-raw/skills/forgecad-visual-spec.md +1 -1
  46. package/dist/docs-raw/skills/forgecad.md +24 -24
  47. package/dist/docs-raw/skills/index.md +2 -3
  48. package/dist/index.html +1 -1
  49. package/dist/sitemap.xml +15 -15
  50. package/dist-cli/{check-compiler-SP7FAL7R.js → check-compiler-HPF2T2FS.js} +1 -1
  51. package/dist-cli/{check-query-propagation-BRLSHP22.js → check-query-propagation-HYSLTXAB.js} +1 -1
  52. package/dist-cli/{chunk-RQQ42YCP.js → chunk-WLUKAW3H.js} +1025 -158
  53. package/dist-cli/forgecad.js +2621 -232
  54. package/dist-cli/{forgecad_geometry-7TVSNVUB.js → forgecad_geometry-2IMYCUWW.js} +0 -8
  55. package/dist-cli/forgecad_geometry_bg.wasm +0 -0
  56. package/dist-skill/CONTEXT.md +85 -73
  57. package/dist-skill/SKILL.md +1 -1
  58. package/dist-skill/docs/CLI.md +9 -7
  59. package/dist-skill/docs/generated/assembly.md +1 -1
  60. package/dist-skill/docs/generated/core.md +70 -1
  61. package/dist-skill/docs/generated/curves.md +8 -1
  62. package/dist-skill/docs/generated/output.md +0 -64
  63. package/dist-skill/docs/generated/runtime-names.md +6 -6
  64. package/dist-skill/docs/generated/viewport.md +3 -12
  65. package/dist-skill/docs/guides/inspection-bundles.md +1 -1
  66. package/dist-skill/library/README.md +2 -3
  67. package/dist-skill/library/forgecad-blockout-model/SKILL.md +1 -1
  68. package/dist-skill/library/forgecad-image-replicator/SKILL.md +2 -2
  69. package/dist-skill/library/forgecad-mujoco-verify/SKILL.md +66 -0
  70. package/dist-skill/library/forgecad-mujoco-verify/scripts/mujoco_verify.py +385 -0
  71. package/dist-skill/library/forgecad-spec-by-walking-through-it/SKILL.md +132 -0
  72. package/dist-skill/library/forgecad-visual-spec/SKILL.md +1 -1
  73. package/dist-skill/website/skills/forgecad-blockout-model.md +1 -1
  74. package/dist-skill/website/skills/forgecad-image-replicator.md +2 -2
  75. package/dist-skill/website/skills/forgecad-mujoco-verify.md +78 -0
  76. package/dist-skill/website/skills/forgecad-spec-by-walking-through-it.md +145 -0
  77. package/dist-skill/website/skills/forgecad-visual-spec.md +1 -1
  78. package/dist-skill/website/skills/forgecad.md +24 -24
  79. package/dist-skill/website/skills/index.md +2 -3
  80. package/examples/analysis/clearance-fit.forge.js +31 -0
  81. package/examples/analysis/lever-arm-actuator.forge.js +43 -0
  82. package/examples/analysis/tipping-tripod.forge.js +35 -0
  83. package/examples/products/sportscar.forge.js +77 -0
  84. package/package.json +1 -3
  85. package/dist/docs-raw/skills/forgecad-high-level-spec.md +0 -101
  86. package/dist/docs-raw/skills/forgecad-lld.md +0 -41
  87. package/dist/docs-raw/skills/forgecad-prepare-prompt.md +0 -63
  88. package/dist-skill/library/forgecad-high-level-spec/SKILL.md +0 -94
  89. package/dist-skill/library/forgecad-lld/SKILL.md +0 -34
  90. package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +0 -50
  91. package/dist-skill/website/skills/forgecad-high-level-spec.md +0 -101
  92. package/dist-skill/website/skills/forgecad-lld.md +0 -41
  93. package/dist-skill/website/skills/forgecad-prepare-prompt.md +0 -63
  94. /package/dist-skill/library/{forgecad-prepare-prompt → forgecad-spec-by-walking-through-it}/references/default-profiles.md +0 -0
  95. /package/dist-skill/library/{forgecad-prepare-prompt → forgecad-spec-by-walking-through-it}/references/master-prompt.md +0 -0
@@ -1,9 +1,9 @@
1
1
  const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/app-CjsbDlb7.css"])))=>i.map(i=>d[i]);
2
2
  import { r as reactExports, j as jsxRuntimeExports, R as React, f as useNavigate, c as create$1, d as reactDomExports } from "./vendor-react-6j1Kke-Y.js";
3
- import { u as useAuthStore, c as authApi, d as showToast, A as AuthApiError, e as useProjectStore, t as triggerDownload, g as useForgeStore, m as monacoLanguageForProjectFile, i as isRunnableFile, r as readProjectFilesFromDataTransfer, h as fileSystem, j as copyTextToClipboard, k as useFeatureFlagStore, l as fetchGistModel, n as fetchUrlModel, F as FLAG_DEFINITIONS, o as exportMeshFromStore, p as exportReportFromStore, q as exportViewportImageFromStore, v as exportOrbitVideoFromStore, w as exportSketchFromStore, x as buildGistShareUrl, y as deriveExportStem, z as sanitizeExportStem, C as captureViewportImageBlobFromStore, D as exportExactFromStore, b as authFetch, E as storageQuotaUpgradeMessage, G as isImportableProjectMeshFile, H as isImportableProjectBinaryFile, I as hasExternalFiles, J as isImportableProjectExactFile, K as resolvePreviewFile, L as countParamSnapshotDiff, M as buildProjectShareUrl, N as buildEmbedSnippet, O as publishProjectShare, P as unpublishProjectShare, Q as formatComputeBackendLabel, a as applyTheme, R as themes, S as computeBackendFromParts, T as formatArea, U as sliderToAnimationSpeed, V as animationSpeedToSlider, W as formatAnimationSpeed, X as resolveJointRange, Y as useJointsConfig, Z as useJointAnimationValues, _ as INSPECT_POINT_SAMPLE_COUNT_MAX, $ as INSPECT_POINT_SAMPLE_COUNT_MIN, a0 as VOXEL_INTENT_TOOL_ORDER, a1 as VOXEL_INTENT_TOOL_LABELS, a2 as VOXEL_INTENT_TOOL_COLORS, a3 as VOXEL_INTENT_PLACEMENT_ORDER, a4 as VOXEL_INTENT_PLACEMENT_LABELS, a5 as highlightLanguageForProjectFile, a6 as hasProjectTextFileExtension, a7 as expandBoundsByTransformedAabb, a8 as Canvas, a9 as ActiveCameraBridge, aa as PerspectiveCamera, ab as ControlsInteractionBridge, ac as SceneConfigurator, ad as LocalEnvironment, ae as ForgeObject, af as RenderLabelsOverlay, ag as Grid, ah as OrbitControls, ai as TOUCH_GESTURES_3D, aj as MOUSE_BUTTONS_3D, ak as ViewController, al as ModelJourneyBar, am as useJointAnimationLoop, an as computeJointNodeMatrices, ao as computeObjectJointMatrices, ap as readLastActiveFileForUser, aq as ToastContainer, ar as isMobile, as as useFeatureFlag, at as decodeSharedHash, au as decodeSharedBundle, av as getExternalUrl, aw as getGistId, ax as Viewport, ay as shouldBlockBrowserShortcut, az as useDrawStore, aA as storePendingShareCopy } from "./app-C9ct2hRD.js";
4
- import { dc as computeCuttingLayout, dd as generateCuttingLayoutPdf, aE as getShapeCompilePlan, de as buildDesignTraceNeighborhood, bq as formatDesignTraceAnchor, df as getCameraForwardVector, dg as RENDER_STYLE_OPTIONS, aI as SCAN_PROXY_MATRIX_GRANULARITY_MAX, dh as SCAN_PROXY_GRANULARITY_MAX, di as SCAN_PROXY_GRANULARITY_MIN, _ as __vitePreload, bG as getRenderStylePreset, a0 as Matrix4, ce as getSketchWorldMatrix, J as Box3, h as Vector3, cs as scanProxyGridForBounds, A as ACESFilmicToneMapping, cF as FOCUS_MODE_DIM_OPACITY, cI as initBackendForEvaluation } from "./backendInit-ymjonyQp.js";
3
+ import { u as useAuthStore, c as authApi, d as showToast, A as AuthApiError, e as useProjectStore, t as triggerDownload, g as useForgeStore, m as monacoLanguageForProjectFile, i as isRunnableFile, r as readProjectFilesFromDataTransfer, h as fileSystem, j as copyTextToClipboard, k as useFeatureFlagStore, l as fetchGistModel, n as fetchUrlModel, F as FLAG_DEFINITIONS, o as exportMeshFromStore, p as exportReportFromStore, q as exportViewportImageFromStore, v as exportOrbitVideoFromStore, w as exportSketchFromStore, x as buildGistShareUrl, y as deriveExportStem, z as sanitizeExportStem, C as captureViewportImageBlobFromStore, D as exportExactFromStore, b as authFetch, E as storageQuotaUpgradeMessage, G as isImportableProjectMeshFile, H as isImportableProjectBinaryFile, I as hasExternalFiles, J as isImportableProjectExactFile, K as resolvePreviewFile, L as countParamSnapshotDiff, M as buildProjectShareUrl, N as buildEmbedSnippet, O as publishProjectShare, P as unpublishProjectShare, Q as formatComputeBackendLabel, a as applyTheme, R as themes, S as computeBackendFromParts, T as formatArea, U as sliderToAnimationSpeed, V as animationSpeedToSlider, W as formatAnimationSpeed, X as resolveJointRange, Y as useJointsConfig, Z as useJointAnimationValues, _ as INSPECT_POINT_SAMPLE_COUNT_MAX, $ as INSPECT_POINT_SAMPLE_COUNT_MIN, a0 as VOXEL_INTENT_TOOL_ORDER, a1 as VOXEL_INTENT_TOOL_LABELS, a2 as VOXEL_INTENT_TOOL_COLORS, a3 as VOXEL_INTENT_PLACEMENT_ORDER, a4 as VOXEL_INTENT_PLACEMENT_LABELS, a5 as highlightLanguageForProjectFile, a6 as hasProjectTextFileExtension, a7 as expandBoundsByTransformedAabb, a8 as Canvas, a9 as ActiveCameraBridge, aa as PerspectiveCamera, ab as ControlsInteractionBridge, ac as SceneConfigurator, ad as LocalEnvironment, ae as ForgeObject, af as RenderLabelsOverlay, ag as Grid, ah as OrbitControls, ai as TOUCH_GESTURES_3D, aj as MOUSE_BUTTONS_3D, ak as ViewController, al as ModelJourneyBar, am as useJointAnimationLoop, an as computeJointNodeMatrices, ao as computeObjectJointMatrices, ap as readLastActiveFileForUser, aq as ToastContainer, ar as isMobile, as as useFeatureFlag, at as decodeSharedHash, au as decodeSharedBundle, av as getExternalUrl, aw as getGistId, ax as Viewport, ay as shouldBlockBrowserShortcut, az as useDrawStore, aA as storePendingShareCopy } from "./app-D6ccu2Xx.js";
4
+ import { d8 as computeCuttingLayout, d9 as generateCuttingLayoutPdf, aJ as getShapeCompilePlan, da as buildDesignTraceNeighborhood, bo as formatDesignTraceAnchor, db as getCameraForwardVector, dc as RENDER_STYLE_OPTIONS, aN as SCAN_PROXY_MATRIX_GRANULARITY_MAX, dd as SCAN_PROXY_GRANULARITY_MAX, de as SCAN_PROXY_GRANULARITY_MIN, df as COLORMAP_OPTIONS, aC as resolveColormapName, _ as __vitePreload, bG as getRenderStylePreset, a0 as Matrix4, cb as getSketchWorldMatrix, J as Box3, h as Vector3, cp as scanProxyGridForBounds, A as ACESFilmicToneMapping, cA as FOCUS_MODE_DIM_OPACITY, cE as initBackendForEvaluation } from "./backendInit-DbTkQN9J.js";
5
5
  import { a as PRODUCTION_EXPORT_COPY } from "./copy-DQcpKfiX.js";
6
- import { g as getSceneObjectTreePath, a as getSceneObjectKind, f as formatCliJointPoseFlags, c as formatRenderSceneCliSpec } from "./jointPose-4r8ed8_5.js";
6
+ import { g as getSceneObjectTreePath, a as getSceneObjectKind, f as formatCliJointPoseFlags, c as formatRenderSceneCliSpec } from "./jointPose-AMvCywzS.js";
7
7
  import { H as HighlightJS, a as json, j as javascript, t as typescript } from "./typescript-DBQ6RN5l.js";
8
8
  const RESEND_COOLDOWN_S = 60;
9
9
  function EmailVerificationBanner() {
@@ -308,7 +308,7 @@ function buildDxf(entities) {
308
308
  );
309
309
  return buildSimpleDxf(activeLayers, entities);
310
310
  }
311
- const contextMd = "# ForgeCAD — AI Context (Chat UI)\n\n> **Usage:** Paste this file as context into your AI chat session (Claude.ai, ChatGPT, Gemini, etc.).\n> The AI gets full ForgeCAD API knowledge and guides you through building models.\n>\n> **No CLI access in this session.** The AI cannot run commands directly. It asks you to run\n> commands like `forgecad run <file>` in your terminal and paste back the output for\n> verification and iteration.\n\n## Workflow\n\n1. Tell the AI what you want to build and share any existing `.forge.js` files.\n2. The AI writes or edits model files for you.\n3. To validate, run `forgecad run <file>` in your terminal and paste the output.\n4. Iterate until the model looks right, then `forgecad render 3d <file>` for a PNG.\n\n---\n\n## ForgeCAD API Reference\n\nAuthor or modify ForgeCAD models, sketches, assemblies, and CLI workflows. Prefer documented primitives, import rules, placement strategies, and CLI commands over inventing new APIs.\n\n### Model files\n\n- `.forge.js` — parametric part or assembly script; return a `Shape`, `Sketch`, `ShapeGroup`, `Assembly`, `SolvedAssembly`, an array of renderables, or a metadata object.\n- Model the physical artifact, not an educational diagram. No explanatory labels, arrows, legends, or text plaques unless the user explicitly asks for a presentation or teaching view; product markings only where the real object would carry them.\n- Build the real closed CAD first. Never bake cutaways, sectioned shells, permanently exploded layouts, or hidden-parts views into the default model just to show internals — use viewer-only cut planes, `explodeView`, object hiding, transparency, or `inspect sections` after the artifact exists.\n\n### Import and composition\n\n- Always include the extension in relative imports: `require(\"./file.forge.js\", { Param: value })` for model files, `require(\"./helpers.js\")` for plain helper modules. Extensionless imports such as `require(\"./file\")` do not resolve; ForgeCAD resolves project imports by exact path.\n- ForgeCAD APIs are injected globals in `.forge.js` files. Use `bom()`, `box()`, `scene()`, `Shape`, etc. directly; never destructure those names from helpers (`const { bom } = require(\"./bom.js\")`). Import helper files under a project-specific name such as `const bomHelpers = require(\"./bom.js\")`.\n- For static multi-part models, connectors + `matchTo()` are the default way to assemble touching parts.\n- Top-level scripts can return `Assembly` or `SolvedAssembly` directly. Do not call `.toGroup()` just to render an assembly; use it only when you need `ShapeGroup` composition, transforms, or named-child lookup.\n- `Import.svgSketch()` loads SVG files (file format loader, not a module import).\n- `.placeReference('bottom', [0,0,0])` aligns any built-in anchor to a world coordinate; also works with custom `.withReferences()`.\n- Plain `.js` modules hold shared helpers/constants (not model imports).\n\n### Validation and export commands (ask the user to run these)\n\n```bash\nforgecad run <file.forge.js> [file ...] # geometry diagnostics, verify.* results — run after every edit\nforgecad run <file.forge.js> --debug-imports # trace import-chain failures\nforgecad check print <file.forge.js> [file ...] --json # collisions, mesh health, walls, overhangs, bed contact\nforgecad render 3d <file.forge.js> [file ...] # shaded PNG render\nforgecad render wireframe <file.forge.js> [file ...] # edges only — internal geometry, edge flow\nforgecad render section <file.forge.js> [file ...] --plane XZ # 2D cross-section (SVG/PNG)\nforgecad capture gif <file.forge.js> [file ...] # animated orbit or joint-playback GIF\nforgecad export stl <file.forge.js> [file ...] # mesh for 3D printing\nforgecad export 3mf <file.forge.js> [file ...] # mesh + metadata for 3D printing\nforgecad export step <file.forge.js> [file ...] # exact B-rep for CAD interchange\n```\n\nAdd `--param Name=Value` to test a specific parameter value. Pick the export that matches the goal: STL/3MF for printing, STEP for exact CAD interchange.\n\n### Not in this bundle — request on demand\n\nThe task-specific docs below are omitted to keep this bundle small. They install with `forgecad skill install` (default `~/.agents/skills/forgecad/`). When the task involves one of these topics, ask the user to attach the named file from that directory before writing code.\n\n- **Full CLI reference** (all flags, cameras, `--focus`/`--hide` filtering, inspect, exports, projects): `docs/CLI.md`\n- **Inspection bundles** (manifest/evidence contract for `forgecad inspect`): `docs/guides/inspection-bundles.md`\n- **Viewport & runtime** (cut planes, exploded views, joint animation playback, `scene()` configuration): `docs/generated/viewport.md`\n- **SDF modeling** (smooth booleans, TPMS lattices, twist/bend/displace, `fromFunction`): `docs/generated/sdf.md`\n- **Sheet metal** (flanges, bends, K-factor, flat pattern unfolding): `docs/generated/sheet-metal.md`\n- **Part library** (`lib.*` fasteners, gears, pipes, structural profiles): `docs/generated/lib.md`\n- **Woodworking** (`Wood.*` boards and dado/rabbet/mortise joinery): `docs/generated/wood.md`\n\n---\n\n<!-- API/core/concepts.md -->\n\n# ForgeCAD Core Concepts\n\nA `.forge.js` script is plain JavaScript that returns geometry. The entire forge API is injected as globals — never `import`, `require`-destructure, or shadow ForgeCAD API names (`const lib = ...`, `let slot = ...`, `class Shape {}` all collide). The reserved list is in [Runtime Names](../../generated/runtime-names.md); check it before using natural local names.\n\n## Execution & Return Values\n\nAll geometry operations are **immutable** — shapes, sketches, groups, assemblies, and boards return new values, never mutate in place.\n\nA script must return one of three shapes:\n\n1. **A single renderable** — `Shape`, `Sketch`, `ShapeGroup`, `Assembly`, `SolvedAssembly`, or `SdfShape`.\n2. **An array** of renderables or named descriptors `{ name, tags?, shape | sketch | group, color? }`:\n\n ```javascript\n return [\n { name: \"Base Plate\", tags: [\"printed\", \"structural\"], shape: base, color: \"#888888\" },\n { name: \"M4 Bolt\", tags: \"fastener\", shape: bolt, color: \"#4488cc\" },\n ];\n ```\n\n3. **A metadata object** — a plain object whose renderable values are rendered and whose non-renderable values (numbers, hole tables, builder functions) are silently skipped at render but flow to importers via `require()`. Each key becomes a named group, so don't pile independent parts into one array key (`{ parts: [a, b, c] }`) — the integrity gate reads that as a single fragmented part. Give each part its own key (`{ collar12, collar16, plug }`) or use named descriptors (form 2).\n\nReturn an unsolved `Assembly` directly — ForgeCAD solves it at default joint values for display. Use `assembly.solve(state)` for a specific pose. Never call `.toGroup()` just to make an assembly render; use it only when you need `ShapeGroup` composition or named-child lookup.\n\nFor multi-file projects — import path rules, the metadata pattern, and Forge-aware builder modules — see the [`require()` docs](../../generated/core.md).\n\n## Identity\n\n`union()` merges shapes into one solid with one identity — later operands lose separate colors and names. Use `group(...)` or named return objects when parts need separate colors, tags, or identities.\n\n## Face Labels\n\nShapes carry semantic face labels through their lifecycle:\n\n1. **Primitives** assign canonical names (`box()` → `top`, `bottom`, `side-left`, ...; `cylinder()` → `top`, `bottom`, `side`).\n2. **Extrusions** inherit sketch labels and add `top`/`bottom`.\n3. **Transforms** preserve all labels.\n4. **Booleans** preserve first-operand labels where geometry survives.\n\nResolve labels with `.face(name)` or `.face(query)` — see the Shape class docs for the query API.\n\n## Conventions\n\n**No explanatory text inside CAD geometry.** Model the physical artifact; explain the design through names, comments, BOM entries, and docs. Use `text2d()` only when letters are part of the real object (engraving, branding, gauge ticks); use `Viewport.label()` only for temporary review/debug annotation — never to compensate for unclear geometry.\n\n**SDF shapes preview natively** when returned directly — including plain object/array trees of SDF leaves; call `.toShape()` only when mesh-backed CAD/export behavior is needed. See [SDF docs](../../generated/sdf.md).\n\n---\n\n<!-- generated/runtime-names.md -->\n\n# Runtime Names\n\nGenerated by `scripts/gen-api-docs.mjs` from `src/forge/script-runtime/runScript.ts`. Do not edit by hand.\n\nForgeCAD injects API functions, classes, namespaces, and sandbox guard names into every `.forge.js` script. Top-level lexical declarations using these names collide with the injected runtime bindings when the script runs.\n\nAgents should avoid declaring these names with top-level `const`, `let`, destructured imports, or `class` declarations. Use project-specific local names such as `wheelLib`, `axleSlotSketch`, `driveJointConfig`, or `labelTextSketch` instead.\n\nThese collision-reserved names are case-sensitive:\n\n```text\nactivateBackend, Analysis, arcSlot, assembly, Assembly, Blend, bom, box\ncameraTrajectory, Carrier, chamfer, circle2d, Circle2D, circularLayout, circularPattern, circularPattern2d\ncoalesceEdges, compareWith, connector, console, constrainedSketch, Curve, Curve3D, cutPlane\ncylinder, difference, difference2d, dim, draft, ellipse, explodeView, faceProfile\nfillet, Function, gcode, GCodeBuilder, getActiveBackend, global, globalThis, group\nImport, ImportedAssembly, initKernel, intersection, intersection2d, intersectWithPlane, joint, Laser\nlib, Line2D, linearPattern, linearPattern2d, loadFont, loft, Loft, mirrorCopy\nmock, ngon, NurbsCurve3D, NurbsSurface, offsetSolid, param, Param, path\nPoint2D, Points, polygon, polygonVertices, port, Product, ProductPanelBuilder, ProductRibbonBuilder\nProductSkin, ProductSkinBuilder, ProductStationBuilder, ProductSurfaceBuilder, ProductSurfaceRef, projectToPlane, queueMicrotask, rect\nRectangle2D, robotExport, roundedRect, Route3D, scene, Sculpt, sdf, SdfShape\nselectEdge, selectEdges, self, setActiveBackend, setImmediate, setInterval, setTimeout, Shape\nShapeGroup, sheetMetal, SheetMetalPart, sheetStock, Sim, Sketch, sketchToDxf, sketchToSvg\nslot, SolvedAssembly, spec, sphere, spline2d, stroke, Surface, SurfaceBody\nSurfaceMembers, sweep, text2d, textWidth, torus, toShape, Transform, union\nunion2d, variableSweep, verify, Viewport, window, Wood\n```\n\n`showLabels` is also a runtime global, but it is not part of the top-level collision check. Avoid reusing it unless you intentionally want a local value with that name.\n\n---\n\n<!-- generated/core.md -->\n\n# Core API\n\n3D primitives, boolean operations, transforms, patterns, imports, and parameters.\n\n## Contents\n\n- [3D Primitives](#3d-primitives)\n- [Boolean Operations](#boolean-operations)\n- [Edge Features](#edge-features)\n- [Patterns & Layout](#patterns-layout)\n- [Imports & Composition](#imports-composition)\n- [Parameters](#parameters)\n- [Grouping & Local Coordinates](#grouping-local-coordinates)\n- [Section & Projection](#section-projection)\n- [Verification](#verification)\n- [Shape](#shape) — Appearance, Face Topology, Edge Topology, Transforms, Booleans & Cutting, Features, Placement, Connectors, References, Measurement\n- [Transform](#transform)\n- [ShapeGroup](#shapegroup) — Children, Transforms, Placement, Connectors, References\n- [SurfacePattern](#surfacepattern)\n- [Pattern2D](#pattern2d)\n- [Pattern2DBuilder](#pattern2dbuilder)\n- [ShapeRef](#shaperef)\n- [ANCHOR3D_NAMES](#anchor3d-names)\n- [verify](#verify)\n- [Points](#points)\n- [connector](#connector)\n- [Import](#import)\n\n## Functions\n\n### 3D Primitives\n\n#### `box(width: number, depth: number, height: number): Shape` — Create a rectangular box. Centered on XY, base at Z=0.\n\nAll ForgeCAD dimensions are millimeters; all angles are degrees (applies to every API, not just `box`).\n\nExtents:\n\n- X: `[-width/2, width/2]`\n- Y: `[-depth/2, depth/2]`\n- Z: `[0, height]`\n\nThis origin convention (centered on XY, base at Z=0) applies to all volumetric primitives that have a base. There is no `center: true` option — recenter with `.translate(0, 0, -height/2)` or `.placeReference('center', [0, 0, 0])`.\n\nFor named faces, build from a labeled sketch: `rect(width, depth).labelEdges('s', 'e', 'n', 'w').extrude(height, { labels: { start: 'bottom', end: 'top' } })`.\n\n#### `cylinder(height: number, radius: number, radiusTop?: number, segments?: number): Shape` — Create a cylinder or cone with named faces and edges. Centered on XY, base at Z=0.\n\nExtents:\n\n- X/Y: centered at the origin\n- Z: `[0, height]`\n\n`radiusTop` defaults to `radius`. Set `radiusTop` smaller to taper the side, or `0` for a pointy cone. Use `segments` to create regular prisms (for example `6` for a hexagonal prism).\n\nNamed faces: `top`, `bottom`, `side` Named edges: `top-rim`, `bottom-rim`\n\n#### `sphere(radius: number, segments?: number): Shape` — Create a sphere centered at the origin.\n\nExtents:\n\n- X: `[-radius, radius]`\n- Y: `[-radius, radius]`\n- Z: `[-radius, radius]`\n\nUse `segments` for lower-poly approximations.\n\n#### `torus(majorRadius: number, minorRadius: number, segments?: number): Shape` — Create a torus (donut shape) lying in the XY plane. Centered on all axes.\n\nExtents:\n\n- X: `[-(majorRadius + minorRadius), +(majorRadius + minorRadius)]`\n- Y: `[-(majorRadius + minorRadius), +(majorRadius + minorRadius)]`\n- Z: `[-minorRadius, minorRadius]`\n\nThe origin is the center of the ring.\n\n### Boolean Operations\n\n#### `union(...inputs: ShapeOperandInput[]): Shape` — Combine shapes into a single solid (additive boolean).\n\nAccepts individual shapes, or an array of shapes. `union()` returns one solid, so only the first operand's color is preserved in the result. Use `group()` when you want separate child colors or identities.\n\n#### `difference(...inputs: ShapeOperandInput[]): Shape` — Subtract shapes from a base shape (subtractive boolean).\n\nThe first shape is the base; all subsequent shapes are subtracted from it. Accepts individual shapes, or an array of shapes.\n\n#### `intersection(...inputs: ShapeOperandInput[]): Shape` — Keep only the overlapping volume of the input shapes (intersection boolean).\n\nRequires at least two shapes. Accepts individual shapes, or an array.\n\n### Edge Features\n\n#### `fillet(shape: Shape, radius: number, edges?: EdgeSelector, segments?: number): Shape` — Apply experimental fillets (rounded edges) to one or more edges of a shape.\n\n**Experimental**: edge finishes (fillet and chamfer) are backend-sensitive. The Manifold backend is known to produce incorrect results for some edge-finish cases, and the OCCT backend can be very slow, especially with broad edge selections. Prefer profile-level rounding where the design allows (`sketch.filletCorners(radius)` before extruding — exact and fast); otherwise use targeted edge selectors and inspect the result before treating it as production-ready geometry.\n\nEdge selections compile into backend operations; unsupported selections fail as explicit kernel gaps instead of using TypeScript geometry fallbacks.\n\nThe `edges` parameter is flexible:\n\n- Omit to fillet **all** sharp edges\n- Pass an `EdgeQuery` for an inline filter (most common)\n- Pass an `EdgeSegment` or `EdgeSegment[]` from `selectEdges()` for pre-selected edges\n- Pass a tracked `EdgeRef` from `shape.edge('vert-br')` (vertical edges of `box()` / [`Rectangle2D`](/docs/sketch#rectangle2d) extrusions) — this takes the **exact** compiler-owned path, not the mesh-approximate one\n\nThrows if no edges match the selection, or if `radius` is not a positive finite number.\n\nSelectorless (all-edges) calls draw from a per-run broad edge-feature budget. Exceeding it throws — except in live preview, which skips the finish with a warning for responsiveness. Explicit edge selectors are never budgeted; `FORGECAD_BROAD_EDGE_FEATURE_BUDGET` / `FORGECAD_ALLOW_BROAD_EDGE_FEATURES=1` raise or lift the budget.\n\n```ts\n// Fillet all edges\nfillet(myShape, 2)\n\n// Fillet only top convex edges\nfillet(myShape, 1.5, { atZ: 20, convex: true })\n\n// Fillet vertical edges selected beforehand\nconst edges = selectEdges(myShape, { parallel: [0, 0, 1] })\nfillet(myShape, 3, edges)\n\n// Exact compiler-owned fillet on a tracked box edge\nconst base = box(50, 50, 20)\nfillet(base, 5, base.edge('vert-br'))\n```\n\n#### `chamfer(shape: Shape, size: number, edges?: EdgeSelector): Shape` — Apply experimental chamfers (beveled edges) to one or more edges of a shape.\n\n**Experimental**: same backend caveats as `fillet` — Manifold may be incorrect for some edge-finish cases, OCCT can be very slow on broad selections; prefer profile-level rounding or targeted selectors and inspect the result.\n\nProduces a 45° bevel at the specified `size` (distance from edge). Edge selections compile into backend operations; unsupported selections fail as explicit kernel gaps instead of using TypeScript geometry fallbacks.\n\nSelectorless (all-edges) calls draw from a per-run broad edge-feature budget. Exceeding it throws — except in live preview, which skips the finish with a warning for responsiveness. Explicit edge selectors are never budgeted; `FORGECAD_BROAD_EDGE_FEATURE_BUDGET` / `FORGECAD_ALLOW_BROAD_EDGE_FEATURES=1` raise or lift the budget.\n\nThe `edges` parameter accepts the same options as `fillet()`: inline `EdgeQuery`, pre-selected `EdgeSegment`/`EdgeSegment[]`, a tracked `EdgeRef` from `shape.edge('vert-br')` (exact compiler-owned path), or `undefined` (all sharp edges).\n\n```ts\n// Chamfer all edges\nchamfer(myShape, 1)\n\n// Chamfer only vertical edges\nchamfer(myShape, 2, { parallel: [0, 0, 1] })\n\n// Exact compiler-owned chamfer on a tracked box edge\nconst base = box(50, 50, 20)\nchamfer(base, 3, base.edge('vert-br'))\n```\n\n#### `draft(shape: Shape, angleDeg: number, pullDirection?: Vec3, neutralPlaneOffset?: number): Shape` — Apply a draft angle (taper) to vertical faces for mold extraction.\n\nAdds a taper angle to the vertical faces of a solid so that it can be extracted from a mold. The neutral plane is the Z position where the draft angle is zero — faces above and below are tapered symmetrically. Typical values for injection molding are 1–5°.\n\nSDF, Manifold, and Truck lower supported vertical-prism solids with Z-axis pull directions to a tapered loft. OCCT uses its native draft operation when available.\n\n```ts\n// Add 3° draft to a box for injection molding\ndraft(myBox, 3)\n\n// Draft with custom pull direction and neutral plane\ndraft(myShape, 2, [0, 0, 1], 10)\n```\n\n#### `offsetSolid(shape: Shape, thickness: number): Shape` — Uniformly offset all surfaces of a solid inward or outward.\n\nUnlike `shell()`, which hollows a solid by removing one face, `offsetSolid()` produces a new solid whose every surface is shifted by `thickness`. Positive values grow the shape outward; negative values shrink it inward.\n\nRequires the OCCT backend. Throws on Manifold.\n\n```ts\n// Grow a box outward by 1mm on all sides\noffsetSolid(myBox, 1)\n\n// Shrink a shape inward by 0.5mm\noffsetSolid(myShape, -0.5)\n```\n\n### Patterns & Layout\n\n#### `circularLayout(count: number, radius: number, options?: CircularLayoutOptions): LayoutPoint[]` — Compute evenly-spaced positions around a circle.\n\nEliminates the most common trig pattern in CAD scripts:\n\n```js\n// Before — manual trig\nfor (let i = 0; i < 12; i++) {\n const angle = i * 30 * Math.PI / 180;\n markers.push(marker.translate(r * Math.cos(angle), r * Math.sin(angle), 0));\n}\n\n// After — declarative\nfor (const {x, y} of circularLayout(12, r)) {\n markers.push(marker.translate(x, y, 0));\n}\n```\n\n**`CircularLayoutOptions`**\n- `startDeg?: number` — Angle of the first element in degrees (default: 0 = +X axis).\n- `centerX?: number` — Center X coordinate (default: 0).\n- `centerY?: number` — Center Y coordinate (default: 0).\n\n`LayoutPoint`: `{ x: number, y: number }`\n\n#### `polygonVertices(sides: number, radius: number, options?: PolygonVerticesOptions): LayoutPoint[]` — Compute the vertex positions of a regular polygon.\n\nDefault orientation places the first vertex at the top (90 degrees), matching the convention used by [`ngon()`](/docs/sketch#ngon).\n\nEliminates manual Math.sqrt(3) for triangles, pentagon vertex math, etc:\n\n```js\n// Before — manual equilateral triangle\nconst v1 = [center.x - r/2, center.y + r * Math.sqrt(3)/2];\nconst v2 = [center.x - r/2, center.y - r * Math.sqrt(3)/2];\nconst v3 = [center.x + r, center.y];\n\n// After — declarative\nconst [v1, v2, v3] = polygonVertices(3, r);\n```\n\n**`PolygonVerticesOptions`**\n- `startDeg?: number` — Angle of the first vertex in degrees (default: 90 = top).\n- `centerX?: number` — Center X coordinate (default: 0).\n- `centerY?: number` — Center Y coordinate (default: 0).\n\n#### `linearPattern(shape: Shape, count: number, dx: number, dy: number, dz?: number): Shape` — Repeat a shape in a linear pattern along a direction vector and union the copies.\n\nCreates `count` copies of `shape`, each offset by `(dx*i, dy*i, dz*i)` from the original. All copies are unioned into a single `Shape`. Distinct compiler ownership is assigned to each copy so face identity via owner-scoped canonical queries still works post-merge.\n\n```ts\n// 5 cylinders, 20mm apart along X\nlinearPattern(cylinder(10, 3), 5, 20, 0)\n```\n\n#### `circularPattern(shape: Shape, count: number, centerXOrOpts?: number | CircularPatternOptions, centerY?: number): Shape` — Repeat a shape in a circular pattern around an axis and union the copies.\n\nDistributes `count` copies evenly around the rotation axis (360° / count per step). All copies are unioned into a single `Shape`. Distinct compiler ownership is assigned to each copy — post-merge face identity via owner-scoped canonical queries still works for pattern descendants.\n\nTwo calling conventions:\n\n- **Simple** (Z axis): `circularPattern(shape, 6)` or `circularPattern(shape, 6, centerX, centerY)`\n- **Advanced** (arbitrary axis): `circularPattern(shape, 6, { axis, origin })`\n\n```ts\n// 8 holes evenly spaced around origin\ncircularPattern(cylinder(12, 4).translate(30, 0, -1), 8)\n\n// Circular pattern around X axis\ncircularPattern(myFeature, 4, { axis: [1, 0, 0], origin: [0, 0, 50] })\n```\n\n**`CircularPatternOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `centerX?` | `number` | Center X of the rotation (default: 0). Used when the rotation axis is Z. |\n| `centerY?` | `number` | Center Y of the rotation (default: 0). Used when the rotation axis is Z. |\n| `axis?` | `Vec3` | Rotation axis direction (default: [0, 0, 1] = Z axis). |\n| `origin?` | `Vec3` | Pivot point for the rotation (default: [0, 0, 0]). Overrides centerX/centerY when set. |\n\n#### `linearPattern2d(sketch: Sketch, count: number, dx: number, dy?: number): Sketch` — Repeat a 2D sketch in a linear pattern and union the copies.\n\n#### `circularPattern2d(sketch: Sketch, count: number, centerXOrOpts?: number | { centerX?: number; centerY?: number; startDeg?: number; }, centerY?: number): Sketch` — Repeat a 2D sketch in a circular pattern around a center point and union the copies.\n\n#### `mirrorCopy(shape: Shape, normal: Vec3): Shape` — Mirror a shape across a plane and union the mirror with the original.\n\nThe mirror plane passes through the origin and is defined by its normal vector. The mirrored copy is unioned with the original to produce a single symmetric Shape.\n\n```ts\n// Mirror across the YZ plane (X=0)\nmirrorCopy(box(50, 30, 10), [1, 0, 0])\n```\n\n#### `selectEdges(shape: Shape, query?: EdgeQuery): EdgeSegment[]` — Select all edges from a shape that match the given query.\n\nUses the active kernel's native topology query when available (Truck), otherwise extracts sharp edges from the mesh (dihedral angle > 1°), applies all filters in the query, and returns the matching `EdgeSegment[]`. When `near` is specified the results are sorted closest-first.\n\nWorks on any shape — primitives, booleans, shells, and imported meshes. Use this when tracked topology is unavailable (e.g. after a difference or on imported geometry). For simpler cases, pass an `EdgeQuery` directly to `fillet()` or `chamfer()` instead of calling `selectEdges` separately.\n\n```ts\n// Fillet all top edges of a box\nconst topEdges = selectEdges(part, { atZ: 20, perpendicular: [0, 0, 1] });\nlet result = part;\nfor (const edge of coalesceEdges(topEdges)) {\n result = fillet(result, 2, edge);\n}\n```\n\n**`EdgeQuery`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `near?` | `Vec3` | Sort by proximity to this point (closest first). When used with `selectEdge`, picks the closest match. |\n| `parallel?` | `Vec3` | Filter: edge direction approximately parallel to this vector. |\n| `perpendicular?` | `Vec3` | Filter: edge direction approximately perpendicular to this vector. |\n| `convex?` | `boolean` | Filter: only convex (outside corner) edges. |\n| `concave?` | `boolean` | Filter: only concave (inside corner) edges. |\n| `minAngle?` | `number` | Filter: minimum dihedral angle in degrees. |\n| `maxAngle?` | `number` | Filter: maximum dihedral angle in degrees. |\n| `minLength?` | `number` | Filter: minimum edge length. |\n| `maxLength?` | `number` | Filter: maximum edge length. |\n| `within?` | `BoundingRegion` | Filter: edge midpoint must be within this bounding region. |\n| `atZ?` | `number` | Shorthand: edge midpoint Z is approximately this value within `tolerance`. |\n| `tolerance?` | `number` | Position tolerance for approximate matches. Used by `atZ` and `near`. Default: `1.0`. |\n| `angleTolerance?` | `number` | Angular tolerance in degrees for `parallel`/`perpendicular` filters. Default: `10`. |\n\n`BoundingRegion`: `{ xMin?: number, xMax?: number, yMin?: number, yMax?: number, zMin?: number, zMax?: number }`\n\n**`EdgeSegment`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `index` | `number` | Stable index within the extraction (deterministic for a given mesh). |\n| `direction` | `Vec3` | Normalized direction from start → end. |\n| `dihedralAngle` | `number` | Dihedral angle in degrees (0 = coplanar, 180 = knife edge). |\n| `convex` | `boolean` | true = outside corner (convex), false = inside corner (concave). |\n| `normalA` | `Vec3` | Normal of first adjacent face. |\n| `normalB` | `Vec3` | Normal of second adjacent face (same as normalA for boundary edges). |\n| `boundary` | `boolean` | true if this is a boundary (unmatched) edge — unusual for closed solids. |\n\nAlso: `start: Vec3`, `end: Vec3`, `midpoint: Vec3`, `length: number`.\n\n#### `selectEdge(shape: Shape, query?: EdgeQuery): EdgeSegment` — Select the single best-matching edge from a shape.\n\nWhen `near` is specified, returns the edge whose midpoint is closest to that point. Otherwise returns the first matching edge in mesh order. Throws if no edges match the query — useful as a guard when you expect exactly one result.\n\n```ts\n// Chamfer one specific edge near a known point\nconst bottomEdge = selectEdge(part, { near: [25, 0, 0], atZ: 0 });\nresult = chamfer(result, 1.5, bottomEdge);\n```\n\n#### `coalesceEdges(segments: EdgeSegment[], tolerance?: number): EdgeSegment[]` — Merge collinear edge segments into longer logical edges.\n\nTessellation often splits one geometric edge into multiple short segments. `coalesceEdges` groups adjacent collinear segments and merges each group into a single `EdgeSegment` spanning the full extent. This is usually needed before passing edges to `fillet()` or `chamfer()` on non-primitive shapes.\n\nThe `tolerance` controls the maximum perpendicular distance from collinearity before two segments are considered non-collinear. Default: `0.01`.\n\n```ts\nconst topEdges = selectEdges(part, { atZ: 20 });\nfor (const edge of coalesceEdges(topEdges)) {\n result = fillet(result, 2, edge);\n}\n```\n\n### Imports & Composition\n\n#### `require(path: string, paramOverrides?: Record<string, number | string>): any` — Import a module with optional ForgeCAD parameter overrides. Returns the module's exports.\n\nWhen importing a `.forge.js` file, most return values are passed through exactly as the script returns them. Assembly returns have one extra composition rule: an unsolved [`Assembly`](/docs/assembly#assembly) is wrapped as an [`ImportedAssembly`](/docs/assembly#importedassembly), preserving `solve(state)` and `mergeInto()` across file boundaries, while a returned [`SolvedAssembly`](/docs/assembly#solvedassembly) stays a [`SolvedAssembly`](/docs/assembly#solvedassembly). If the script returns a metadata object (e.g. `{ shape: myShape, bolts: {...} }`), the caller receives the full object — renderable values and metadata together.\n\n**Script return contract:** a `.forge.js` script returns one of three shapes: a single renderable (Shape, ShapeGroup, Sketch, SdfShape, Assembly), an array of renderables or named descriptors (`{ name, shape|sketch|group }`), or a metadata object mixing renderable values with plain data. When a script runs directly, renderable entries of a metadata object are rendered under their key names and non-renderable entries are silently skipped — both halves of the metadata contract: one return value serves the viewport and `require()` callers.\n\n**Assembly return contract**\n\n| `.forge.js` return value | `require()` result |\n|---|---|\n| `Assembly` | `ImportedAssembly` |\n| `SolvedAssembly` | `SolvedAssembly` |\n\n[`ImportedAssembly`](/docs/assembly#importedassembly) exposes default-pose helpers such as `getPart()`, `collisionReport()`, and `minClearance()`. Use `solve(state)` first when inspecting a non-default pose.\n\n**Path rule:** Always include the file extension in relative imports: use `require(\"./part.forge.js\")` for model files and `require(\"./helpers.js\")` for plain helper modules. ForgeCAD does not apply Node-style extension inference, so `require(\"./part\")` will not find `part.forge.js` or `part.js`.\n\n**Parameter scoping:** Parameters declared in required files are automatically namespaced with a `\"filename#N / \"` prefix (e.g. `\"bracket.forge.js#1 / Width\"`). This prevents collisions when multiple files declare same-named params. Each file's params appear as separate sliders.\n\n**Parameter overrides:** When passing overrides, use the bare param name (not the scoped name). Overrides are type-checked — unrecognized keys throw an error with typo suggestions.\n\n**Multi-file assembly pattern** — pass cross-cutting design values from the assembly to parts:\n\n```js\n// assembly.forge.js — owns cross-cutting params, passes to parts\nconst wall = param(\"Wall\", 3);\nconst baseH = param(\"Base Height\", 20);\n\nconst mount = require('./motor-mount.forge.js', { Wall: wall });\nconst base = require('./base-body.forge.js', { Wall: wall, Height: baseH });\n```\n\n**Metadata pattern** — parts publish interface data alongside geometry:\n\n```js\n// motor-mount.forge.js\nreturn { shape: mount, bolts: { dia: 5.3, pos: holePositions } };\n\n// base-body.forge.js\nconst mount = require('./motor-mount.forge.js');\nmount.bolts.pos // access the metadata\nmount.shape // access the geometry\n```\n\n**Forge-aware builder module pattern** — use `.forge.js` modules for reusable sketch, profile, shape, or assembly builders that need ForgeCAD runtime APIs:\n\n```js\n// profiles.forge.js — inspectable on its own, reusable through require()\nfunction wheelProfile() {\n return circle2d(40).subtract(circle2d(18));\n}\n\nreturn {\n preview: [{ name: 'Wheel profile', sketch: wheelProfile() }],\n make: { wheelProfile },\n};\n\n// main.forge.js\nconst profiles = require('./profiles.forge.js');\nconst wheel = profiles.make.wheelProfile().extrude(8);\n```\n\nKeep exported builders pure over top-level constants, top-level `param()` values, or explicit function arguments. Do not declare new `param()` values inside an exported builder if callers need `require('./profiles.forge.js', { Width: 80 })` overrides: import overrides are validated while the module loads, before any exported builder is called. Use plain `.js` modules only for pure constants, tables, math helpers, and formatting code that does not construct ForgeCAD geometry.\n\n**Entry detection (Node semantics):** `require.main` is the entry script's module object, so `require.main === module` is true only in the file being run directly. Part files use it to build standalone preview geometry only when opened directly — importers then skip that work entirely:\n\n```js\n// part.forge.js\nfunction bracket() { ... }\nif (require.main === module) {\n return { preview: [{ name: 'Bracket', shape: bracket() }] }; // direct run: render it\n}\nreturn { make: { bracket } }; // imported: builders only\n```\n\n### Parameters\n\n#### `Param.number(name: string, defaultValue: number, opts?: { min?: number; max?: number; step?: number; unit?: string; integer?: boolean; reverse?: boolean; }): number` — Declare a numeric parameter that renders as a slider in the UI.\n\nEach call registers a slider control. When the user moves the slider the entire script re-executes with the new value. Parameter values are also overridable from `require()` imports or the CLI `--param` flag — the `name` string is the key used in both cases.\n\nDefault range rules when options are omitted:\n\n- `min` defaults to `0`\n- `max` defaults to `defaultValue * 4`\n- `step` is auto-calculated: `1` for integer params, `0.1` for ranges ≤ 100, `1` for larger ranges\n\nThe `unit` option is cosmetic only — no conversion is performed. Use `integer: true` for counts, sides, quantities (rounds to whole numbers; step defaults to `1`).\n\n```ts\nconst width = Param.number(\"Width\", 50);\nconst angle = Param.number(\"Angle\", 45, { min: 0, max: 180, unit: \"°\" });\nconst sides = Param.number(\"Sides\", 6, { min: 3, max: 12, integer: true });\n```\n\n**Parameter overrides** — key must match `name` exactly:\n\n```ts\n// Via require()\nconst bracket = require(\"./bracket.forge.js\", { Width: 80 });\n\n// Via CLI\n// forgecad run model.forge.js --param \"Wall Thickness=3\"\n```\n\nAlso available as the shorthand alias `param()`.\n\n#### `Param.string(name: string, defaultValue: string, opts?: { maxLength?: number; }): string` — Declare a string parameter that renders as a text input in the UI.\n\nString parameters let users type free-form text — labels, names, inscriptions, file paths, etc. The `name` string is the override key.\n\n```ts\nconst label = Param.string(\"Label\", \"Hello World\");\nconst name = Param.string(\"Name\", \"Part-001\", { maxLength: 20 });\n```\n\nOverride via import:\n\n```ts\nconst tag = require(\"./tag.forge.js\", { Label: \"Custom Text\" });\n```\n\nOnly available as `Param.string()` — no standalone alias.\n\n#### `Param.bool(name: string, defaultValue: boolean): boolean` — Declare a boolean parameter that renders as a checkbox in the UI.\n\nInternally stored as `0`/`1`. When overriding from CLI or `require()`, pass `1` for true and `0` for false. The `name` string is the override key.\n\n```ts\nconst showHoles = Param.bool(\"Show Holes\", true);\nif (showHoles) return difference(plate, cylinder(10, 5).translate(50, 30, 0));\nreturn plate;\n```\n\nOverride via import:\n\n```ts\nconst pan = require(\"./pan.forge.js\", { \"Show Lid\": 0 });\n```\n\n#### `Param.choice(name: string, defaultValue: string, choices: string[]): string` — Declare a choice parameter that renders as a dropdown in the UI.\n\n`defaultValue` must exactly match one entry in `choices`. Returns the selected string label. Prefer `Param.choice` over `Param.number` when a slider would hide intent — named choices like `\"wok\"` are self-describing.\n\nOverrides may be passed as the choice label string (preferred) or as a numeric index. The `name` string is the override key.\n\n```ts\nconst panStyle = Param.choice(\"Pan Style\", \"frying-pan\", [\"frying-pan\", \"saute-pan\", \"wok\"]);\nif (panStyle === \"wok\") return buildWok();\n```\n\nOverride via import:\n\n```ts\nconst pan = require(\"./pan.forge.js\", { \"Pan Style\": \"wok\" });\n```\n\nOverride via CLI:\n\n```bash\nforgecad run model.forge.js --param \"Pan Style=wok\"\n```\n\n#### `Param.list<T extends Record<string, number | boolean | string>>(name: string, defaultItems: T[], opts: { ... }): T[]` — Declare a list parameter — an array of struct items with per-field UI controls.\n\nEach item in the list is a struct whose fields each render as their own control (slider, checkbox, or dropdown). The user can add/remove rows up to `minItems`/`maxItems` bounds.\n\nField types:\n\n- Boolean fields (`boolean: true` in field defs) return as `boolean`\n- Choice fields (`choices: [...]` in field defs) return as `string`\n- All other fields return as `number`\n\n`ListParamFieldDef`: `{ min?: number, max?: number, step?: number, unit?: string, integer?: boolean, boolean?: boolean, choices?: string[] }`\n\n### Grouping & Local Coordinates\n\n#### `group(...items: GroupInput[]): ShapeGroup` — Group multiple shapes/sketches for joint transforms without merging into a single mesh.\n\nUnlike union(), child colors and individual identities are preserved. Children can be plain shapes, named descriptors ({ name, shape/sketch/group }), or nested groups. The returned ShapeGroup supports all Shape transforms (translate, rotate, etc.).\n\nNamed descriptors can include `tags` for viewport organization. Tags do not affect geometry; they let the command palette hide, show only, or focus all objects with the same tag.\n\n**Local coordinate pattern:** Build child parts at the origin (local coordinates), then group and translate once to place the whole assembly. This eliminates the error-prone pattern of manually adding parent offsets to every sub-part.\n\n```js\nconst body = roundedBox(100, 20, 32, 4);\nconst panel = box(98, 2, 18).translate(0, -12, 4);\nconst louver = box(88, 2, 6).translate(0, -14, -11);\nconst indoorUnit = group(\n { name: 'Body', shape: body },\n { name: 'Panel', tags: 'cover', shape: panel },\n { name: 'Louver', tags: ['cover', 'moving'], shape: louver },\n).translate(0, -18, 70);\n```\n\n### Section & Projection\n\n#### `intersectWithPlane(shape: Shape, plane: PlaneSpec): Sketch` — Cross-section: slice a 3D shape with a plane and return the intersection as a 2D Sketch.\n\n#### `faceProfile(shape: Shape, face: FaceSelector): Sketch` — Extract the boundary profile of a named face as a 2D sketch.\n\nThe result is returned in the face's local 2D coordinate system, making it convenient for offsets, pocket profiles, or follow-up sketch operations driven by an existing face.\n\n#### `projectToPlane(shape: Shape, plane: PlaneSpec): Sketch` — Orthographically project a 3D shape onto a plane and return the silhouette as a 2D Sketch.\n\n### Verification\n\n#### `verify.that(label: string, check: () => boolean, message?: string): void` — Custom predicate check.\n\n#### `verify.equal(label: string, actual: number, expected: number, tolerance?: number, message?: string): void` — Check that two numbers are approximately equal (within tolerance).\n\n#### `verify.notEqual(label: string, actual: number, unexpected: number, tolerance?: number, message?: string): void` — Check that two numbers are NOT equal (differ by more than tolerance).\n\n#### `verify.greaterThan(label: string, actual: number, min: number, message?: string): void` — Check that actual > min.\n\n#### `verify.lessThan(label: string, actual: number, max: number, message?: string): void` — Check that actual < max.\n\n#### `verify.inRange(label: string, actual: number, min: number, max: number, message?: string): void` — Check that min <= actual <= max.\n\n#### `verify.centersCoincide(label: string, a: ShapeLike, b: ShapeLike, tolerance?: number): void` — Check that the bounding-box centers of two shapes coincide within tolerance (mm).\n\n`ShapeLike`: `{ min: number[], max: number[] }`\n\n#### `verify.connectorDistance(label: string, target: ConnectorDistanceLike, connectorA: string, connectorB: string, expected?: number, tolerance?: number): void` — Check the distance between two named connectors on a shape or group.\n\nUse this when connectors + `matchTo()` define a static assembly interface. It proves the mate at runtime, unlike a plain source-level connector declaration. The common case is `expected = 0`, meaning the two connector origins should coincide after placement.\n\n```ts\nverify.connectorDistance(\"leg is seated\", bench, \"Rail.leg_0\", \"Leg0.head\", 0, 0.01);\n```\n\n#### `verify.physicalComponentCount(label: string, expected: number): void` — Declare the expected physical connectivity component count for the returned visible model.\n\nUse this for generated mechanical models that should have a clear component graph: one connected fixture, a purchased part plus a removable cartridge, a root assembly plus named intentional ghosts, and so on. `forgecad inspect mechanical-integrity` resolves the returned visible objects with the same physical-connectivity analysis used in the quality gate and fails if the actual component count differs.\n\nThis catches the common generated-CAD failure where a script returns a visually plausible artifact but the handle, screw, washer, cover, or terminal block is actually a separate island.\n\n```ts\nverify.physicalComponentCount(\"vise is one connected installed assembly\", 1);\n```\n\n#### `verify.intentionalOverlap(label: string, a: ShapeLike, b: ShapeLike, reason: string): void` — Declare that two visible objects intentionally overlap because the overlap is real manufacturing intent.\n\nUse this only for overlaps that a mechanical reviewer would accept as actual matter sharing volume: welded/fused regions, overmolded inserts, potted electronics, cast-in hardware, or deliberately bonded laminations. This is not a shortcut for screws without holes, shafts without bores, covers without pockets, or parts placed with collision as a positioning hack.\n\n`forgecad inspect mechanical-integrity --collisions` only honors this declaration when both shapes are returned as visible objects and the exact collision report finds that same object pair. Unused or non-visible declarations fail the quality gate so annotations cannot hide unrelated collisions.\n\n```ts\nverify.intentionalOverlap(\"rubber grip is overmolded on handle\", rubberGrip, handleCore, \"overmolded insert\");\n```\n\n#### `verify.notColliding(label: string, a: ShapeLike, b: ShapeLike, searchLength?: number): void` — Check that two shapes do not share positive volume.\n\nFace-to-face contact is allowed; use `verify.minClearance()` when an actual running gap is required.\n\n#### `verify.minClearance(label: string, a: ShapeLike, b: ShapeLike, minGap: number, searchLength?: number): void` — Check that a minimum clearance gap exists between two shapes.\n\n#### `verify.clearanceBetween(label: string, a: ShapeLike, b: ShapeLike, minGap: number, maxGap: number, searchLength?: number): void` — Check that the clearance gap between two shapes is inside an allowed range.\n\nUse this for seated and retained interfaces where a part must be close enough to be mechanically accountable, but must not collide beyond the allowed minimum. It catches both failure modes that make generated CAD look fake: parts floating away from their receiver, and parts intersecting their receiver because the pocket, bore, or running clearance was not modeled.\n\nFor contact, use a narrow range such as `[-0.01, 0.05]` to tolerate tiny numerical noise. For a running fit, use the intended clearance band.\n\nManifold-backed shapes use exact min-gap distance. Other backends use a mesh-derived min-gap check and say so in the verification message; keep `forgecad inspect mechanical-integrity --collisions` in the acceptance gate for positive-volume interference.\n\n```ts\nverify.clearanceBetween(\"cover is seated on gasket\", cover, gasket, -0.01, 0.05);\nverify.clearanceBetween(\"carriage runs inside rail\", carriage, rail, 0.2, 0.5);\n```\n\n#### `verify.parallel(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void` — Check that two face normals are parallel (within toleranceDeg degrees).\n\n`FaceRefLike`: `{ normal: Vec3, center: Vec3 }`\n\n#### `verify.perpendicular(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void` — Check that two face normals are perpendicular (within toleranceDeg degrees).\n\n#### `verify.coplanar(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number, toleranceMm?: number): void` — Check that a face is coplanar with (same plane as) another face, meaning they are parallel AND their centers lie on the same plane.\n\n#### `verify.faceAt(label: string, face: FaceRefLike, expectedPos: Vec3, toleranceMm?: number): void` — Check that a face center lies at a specific position (within toleranceMm).\n\n#### `verify.sameDirection(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void` — Check that two face normals point in the same direction (not antiparallel). Stricter than parallel — both |angle| AND sign must match.\n\n#### `verify.isEmpty(label: string, shape: ShapeLike, message?: string): void` — Check that a shape is empty.\n\n#### `verify.notEmpty(label: string, shape: ShapeLike, message?: string): void` — Check that a shape is NOT empty.\n\n#### `verify.volumeApprox(label: string, shape: ShapeLike, expected: number, tolerance?: number): void` — Check that a shape's volume is approximately equal to expected (mm³).\n\n#### `verify.areaApprox(label: string, shape: ShapeLike, expected: number, tolerance?: number): void` — Check that a shape's surface area is approximately equal to expected (mm²).\n\n#### `verify.boundingBoxSize(label: string, shape: ShapeLike, expectedSize: Vec3, tolerance?: number): void` — Check that a shape's bounding box has approximately the given size.\n\n#### `verify.edgeContinuity(label: string, shape: ShapeLike, options?: EdgeContinuityThresholds): void` — Check that every sampled seam on a shape meets a requested continuity threshold.\n\n**`EdgeContinuityThresholds`**: `continuity?: SurfaceContinuity`, `samples?: number`, `positionTolerance?: number`, `tangentToleranceDeg?: number`, `curvatureTolerance?: number`\n\n#### `verify.noTinyEdges(label: string, shape: ShapeLike, threshold?: number): void` — Check that a shape has no tiny edges below the requested threshold.\n\n#### `verify.noSliverFaces(label: string, shape: ShapeLike, threshold?: number): void` — Check that a shape has no sliver faces below the requested score threshold.\n\n#### `verify.noSelfIntersection(label: string, shape: ShapeLike): void` — Best-effort exact-shape validity guard for self-intersections or broken B-Rep topology.\n\n#### `spec(name: string, checkFn: (...args: any[]) => void): Spec` — Create a named, reusable bundle of verification checks.\n\nA spec groups related `verify.*` calls under a collapsible header in the Checks panel. This makes large check suites scannable. Specs can be applied to multiple shapes and can check relationships between parts.\n\nSpecs can be defined in separate `.forge.js` files and imported via `require()` to share them across models.\n\n`spec.check()` returns a `SpecResult` — you can inspect it programmatically or ignore the return value and let the Checks panel show results.\n\n```ts\nconst printable = spec(\"Fits printer bed\", (shape) => {\n verify.notEmpty(\"Has geometry\", shape);\n const bb = shape.boundingBox();\n verify.lessThan(\"Width < 220mm\", bb.max[0] - bb.min[0], 220);\n verify.lessThan(\"Depth < 220mm\", bb.max[1] - bb.min[1], 220);\n verify.lessThan(\"Height < 250mm\", bb.max[2] - bb.min[2], 250);\n});\n\n// Reuse on multiple shapes\nprintable.check(bracket);\nprintable.check(standoff);\n\n// Check relationships between parts\nconst fitSpec = spec(\"Assembly fit\", (partA, partB) => {\n verify.notColliding(\"No interference\", partA, partB, 10);\n});\nfitSpec.check(bracket, standoff);\n```\n\n**Spec-first workflow:** Write specs before building geometry. Checks go from red to green as you build — effectively TDD for CAD.\n\n**`Spec`**\n- `name: string` — The display name of this spec\n\n---\n\n## Classes\n\n### `Shape`\n\nCore 3D solid shape. All operations are immutable and return new shapes.\n\nSupports transforms (translate, rotate, scale, mirror, transform, rotateAround, pointAlong), booleans (add, subtract, intersect), cutting (split, splitByPlane, trimByPlane), shelling, anchor positioning (attachTo, onFace), placement references, and queries (volume, surfaceArea, boundingBox, isEmpty, numTri, geometryInfo).\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `materialProps` | `ShapeMaterialProps \\| undefined` | — |\n\n**Appearance**\n\n#### `color(value: string | undefined): Shape` — Set the color of this shape (hex string, e.g. \"#ff0000\"). Returns a new Shape with the color applied.\n\n#### `material(props: ShapeMaterialProps): Shape` — Set PBR material properties for this shape's visual appearance.\n\nReturns a new Shape with the specified material properties merged on top of any previously set properties. All properties are optional — omitted keys retain their current value. Material properties survive transforms and boolean operations.\n\nUse `.color()` to set the base diffuse color; `.material()` controls how that color behaves under light (metalness, roughness, clearcoat) and can add emissive glow independent of lighting. Emissive glow pairs naturally with the `postProcessing.bloom` effect in [`scene()`](/docs/viewport#scene).\n\n```js\nbox(50, 50, 50).material({ metalness: 0.9, roughness: 0.1 }); // polished metal\nsphere(30).material({ emissive: '#ff6b35', emissiveIntensity: 2 }); // glowing\ncylinder(40, 20).material({ opacity: 0.4, clearcoat: 1.0, clearcoatRoughness: 0.02 }); // ice\n\n// Chainable with other shape methods\nbox(100, 100, 10).color('#gold').material({ metalness: 0.95, roughness: 0.05 }).translate(0, 0, 50);\n```\n\n**`ShapeMaterialProps`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `metalness?` | `number` | Metalness factor (0 = dielectric, 1 = metal). Default: 0.05 |\n| `roughness?` | `number` | Roughness factor (0 = mirror, 1 = fully diffuse). Default: 0.35 |\n| `emissive?` | `string` | Emissive glow color (hex string, e.g. \"#ff6b35\"). |\n| `emissiveIntensity?` | `number` | Emissive intensity multiplier. Default: 1 |\n| `opacity?` | `number` | Opacity (0 = fully transparent, 1 = fully opaque). Default: 1 |\n| `wireframe?` | `boolean` | Render as wireframe. Default: false |\n| `clearcoat?` | `number` | Clearcoat intensity (0–1). Default: 0.1 |\n| `clearcoatRoughness?` | `number` | Clearcoat roughness (0–1). Default: 0.4 |\n| `transmission?` | `number` | Glass/translucency transmission factor (0–1). Renderer support depends on target. |\n| `ior?` | `number` | Index of refraction for transmissive materials. Typical glass is ~1.45. |\n| `thickness?` | `number` | Approximate transmissive volume thickness in model units. |\n| `specularIntensity?` | `number` | Specular highlight intensity (0–1). |\n| `specularColor?` | `string` | Specular highlight tint. |\n| `reflectivity?` | `number` | Reflection strength for supported renderers (0–1). |\n\n**Face Topology**\n\n#### `face(selector: FaceSelector): FaceRef` — Resolve a face by user-authored label or compiler-owned name. Returns a `FaceRef` that can be passed to `.onFace()`, `projectToPlane()`, or used directly in placement.\n\n`.face(name)` is a pure label lookup — it finds faces by user-authored labels, not by geometric queries. Labels are born in sketches via `.label()` / `.labelEdges()` and grow into face names through extrude, loft, revolve, and sweep. They are stable references that travel with the geometry.\n\nLabels must be unique within a shape. Use `.prefixLabels()` before combining shapes with `union()` / `difference()` to avoid collisions. Collision detection throws a clear error with a fix suggestion.\n\nBoolean survival: `union()` and `intersection()` carry labels from every operand; `difference()` carries only the base (first) operand's labels — cutter labels are dropped. A surviving label addresses whatever portion of its face survives the boolean; cutters may split or erase it, and a lineage shared by multiple union operands resolves as a face set rather than a single face.\n\nFor compile-covered shapes (extrude, loft, etc.) the lookup resolves via the shape's compile plan. As a fallback, planar-faced mesh shapes (e.g. results of boolean ops) are resolved via coplanar triangle clustering.\n\n```ts\n// Edge labels become side face names after extrude\nconst profile = path()\n .moveTo(0, 0)\n .lineTo(100, 0).label('floor')\n .lineTo(100, 50).label('wall')\n .lineTo(0, 50).label('ceiling')\n .closeLabel('left-wall');\nconst room = profile.extrude(30, { labels: { start: 'base', end: 'top' } });\nroom.face('floor'); // side face from the labeled edge\nroom.face('base'); // base cap (user-specified)\n\n// .labelEdges() shorthand for sequential edge labeling\nconst plate = rect(100, 50).labelEdges('south', 'east', 'north', 'west');\nconst solid = plate.extrude(20, { labels: { start: 'bottom', end: 'top' } });\nsolid.face('south'); // side face\n\n// Prefix before combining to avoid collisions\nconst left = wing.prefixLabels('l/');\nconst right = wing.mirror([1, 0, 0]).prefixLabels('r/');\nconst full = union(left, right);\nfull.face('l/upper'); // left wing upper surface\n```\n\n#### `faces(): FaceRef[]` — Return faces matching a query, or label semantic faces when passed a mapping.\n\nMapping form returns a new shape: `shape.faces({ lid: 'top', walls: ['front', 'back', 'left', 'right'] })`.\n\n#### `faceNames(): string[]` — List defined semantic face names currently available on this shape.\n\n#### `prefixLabels(prefix: string): Shape` — Prefix all user-authored face labels, including semantic labels from `faces(mapping)`. Returns a new shape with modified labels.\n\n#### `renameLabel(from: string, to: string): Shape` — Rename a single face label. Returns a new shape.\n\n#### `dropLabels(...names: string[]): Shape` — Remove specific face labels. Returns a new shape.\n\n#### `dropAllLabels(): Shape` — Remove all face labels. Returns a new shape.\n\n#### `faceHistory(name: string): FaceTransformationHistory` — Get the transformation history for a specific face.\n\n**Edge Topology**\n\n#### `edge(name: string): EdgeRef` — Get a named topology edge. Only available on shapes with tracked topology (from box/cylinder/extrude).\n\n#### `edgeNames(): string[]` — List named topology edge names. Returns empty array if shape has no tracked topology.\n\n#### `edgesOf(faceLabel: string, options?: EdgesOfOptions): EdgeSegment[]` — Return all boundary edges of a named face.\n\nFinds edges where one adjacent mesh face belongs to the target face and the other belongs to a different face. The result is coalesced (tessellation fragments merged) and can be passed directly to `fillet()` or `chamfer()`.\n\nThis is a topological query — no coordinates, no tolerances, no minimum-length hacks. It works because an edge is the boundary between two faces.\n\n```js\n// Fillet all top edges of a mounting plate\nlet plate = box(120, 80, 6).faces({ workSurface: 'top' })\nplate = fillet(plate, 3, plate.edgesOf('workSurface'))\n\n// Shelled enclosure — fillet the outer lip\nlet body = box(80, 50, 35).faces({ opening: 'top' })\nbody = body.shell(2, { openFaces: ['top'] })\nbody = fillet(body, 1.5, body.edgesOf('opening'))\n\n// Filter: only concave edges (after a boolean subtraction)\nbody.edgesOf('top', { concave: true })\n```\n\n**`EdgesOfOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `exclude?` | `string \\| string[]` | Exclude edges shared with these named faces. |\n| `convex?` | `boolean` | Additional geometric filter: only convex edges. |\n| `concave?` | `boolean` | Additional geometric filter: only concave edges. |\n| `minLength?` | `number` | Minimum edge length filter. |\n\n#### `edgesBetween(faceA: string, faceB: string | string[]): EdgeSegment[]` — Return edges shared between two named faces.\n\nAn edge is \"between\" faces A and B when one of its adjacent mesh triangles belongs to A and the other belongs to B. This is the most precise topological edge selection — \"fillet the edges where the top meets the wall.\"\n\nThe second argument can be a single face name or an array (edges between A and any of B1, B2, ...).\n\n```js\n// Fillet the edge where lid meets one wall\nlet body = box(100, 60, 30).faces({ lid: 'top', wall: 'side-left' })\nbody = fillet(body, 2, body.edgesBetween('lid', 'wall'))\n\n// Fillet a cylinder rim — where the flat cap meets the curved barrel\nlet tube = cylinder(30, 10).faces({ cap: 'top', barrel: 'side' })\ntube = fillet(tube, 1, tube.edgesBetween('cap', 'barrel'))\n\n// Multiple target faces at once\nbody.edgesBetween('lid', ['left-wall', 'right-wall', 'front-wall', 'back-wall'])\n```\n\n**Transforms**\n\n#### `translate(x: number, y: number, z: number): Shape` — Move the shape relative to its current position. All transforms are immutable and return new shapes.\n\n#### `translatePolar(radius: number, angleDeg: number, z?: number): Shape` — Translate using polar coordinates (radius + angle in degrees). Eliminates manual `r * Math.cos(angle * PI/180)` calculations.\n\nExample: `shape.translatePolar(50, 30)` moves 50mm at 30 degrees from +X.\n\n#### `moveTo(x: number, y: number, z: number): Shape` — Position the shape so its bounding box min corner is at the given global coordinate.\n\n#### `moveToLocal(target: Shape | { toShape(): Shape; }, x: number, y: number, z: number): Shape` — Position the shape relative to another shape's local coordinate system (bounding box min corner).\n\n#### `rotate(axis: Vec3, angleDeg: number, options?: { pivot?: Vec3; }): Shape` — Rotate around an arbitrary axis through the origin. Unlike `Sketch.rotate()` (bounding-box center), this pivots at the world origin — pass `options.pivot` to rotate in place.\n\n#### `rotateX(angleDeg: number, options?: { pivot?: Vec3; }): Shape` — Rotate around the X axis by the given angle in degrees.\n\n#### `rotateY(angleDeg: number, options?: { pivot?: Vec3; }): Shape` — Rotate around the Y axis by the given angle in degrees.\n\n#### `rotateZ(angleDeg: number, options?: { pivot?: Vec3; }): Shape` — Rotate around the Z axis by the given angle in degrees.\n\n#### `rotateAroundTo(axis: Vec3, pivot: Vec3, movingPoint: RotationPointLike, targetPoint: RotationPointLike, options?: RotateAroundToOptions): Shape` — Rotate around an axis until a moving point reaches the target line/plane defined by the axis and target point. `movingPoint` / `targetPoint` may be raw world points or this shape's anchors/references.\n\n`RotateAroundToOptions`: `{ mode?: RotateAroundToMode }`\n\n#### `transform(m: Mat4 | Transform): Shape` — Apply a 4x4 affine transform matrix (column-major) or a Transform object.\n\n#### `scale(v: number | Vec3): Shape` — Scale the shape uniformly or per-axis from the shape's bounding box center. Accepts a single number or [x, y, z] array.\n\n#### `scaleAround(pivot: Vec3, v: number | Vec3): Shape` — Scale the shape uniformly or per-axis from an explicit pivot point.\n\n#### `mirror(normal: Vec3): Shape` — Mirror across a plane through the shape's bounding box center, defined by its normal vector.\n\n#### `mirrorThrough(point: Vec3, normal: Vec3): Shape` — Mirror across a plane through an explicit point, defined by its normal vector.\n\n#### `pointAlong(direction: Vec3): Shape` — Reorient a shape so its primary axis (Z) points along the given direction. Useful for laying cylinders/extrusions along X or Y without thinking about Euler angles. The shape's origin stays at [0,0,0] — translate after pointAlong to position it.\n\nExample: cylinder(40, 5).pointAlong([1, 0, 0]) — lays cylinder along X, starting at origin\n\n**Booleans & Cutting**\n\n#### `add(...others: ShapeOperandInput[]): Shape` — Union this shape with others (additive boolean). Method form of union().\n\n#### `subtract(...others: ShapeOperandInput[]): Shape` — Subtract other shapes from this one. Method form of difference().\n\n#### `intersect(...others: ShapeOperandInput[]): Shape` — Keep only the overlap with other shapes. Method form of intersection().\n\n#### `split(cutter: Shape | { toShape(): Shape; }): [ Shape, Shape ]` — Split into [inside, outside] by another shape.\n\n#### `splitByPlane(normal: Vec3, originOffset?: number): [ Shape, Shape ]` — Split by infinite plane. Returns [positive-side, negative-side].\n\n#### `trimByPlane(normal: Vec3, originOffset?: number): Shape` — Keep the positive side of the plane and discard the opposite side.\n\n**Features**\n\n#### `shell(thickness: number, opts?: { openFaces?: string[]; }): Shape` — Hollow out compile-covered boxes, cylinders, and straight extrudes. `openFaces` names any subset of the base shape's labeled faces to leave open (no wall).\n\n#### `pocket(face: FaceSelector, depth: number, opts?: PocketOptions): Shape` — Cut a pocket (cavity) into this solid through the named face.\n\n```js\nbox(100, 100, 20).pocket('top', 8)\nbox(100, 100, 20).pocket('top', 8, { inset: 5 })\nbox(100, 100, 20).pocket('top', 8, { scale: 0.8 })\n```\n\n**`PocketOptions`**\n- `inset?: number` — Shrink the face boundary inward by this many mm before extruding. Produces angled walls when combined with depth. Default: 0 (full face).\n- `scale?: number` — Scale the face profile uniformly (e.g. 0.8 = 80% of the face area). Mutually exclusive with `inset`; `inset` takes precedence if both are set.\n- `join?: \"Square\" | \"Round\" | \"Miter\"` — Corner join style when using `inset`. Default: 'Round'.\n\n#### `boss(face: FaceSelector, height: number, opts?: BossOptions): Shape` — Add a boss (protrusion) from the named face.\n\n```js\nbox(100, 100, 20).boss('top', 5)\nbox(100, 100, 20).boss('top', 10, { scale: 0.6 })\n```\n\n#### `hole(faceOrRef: SketchFaceTarget | FaceRef, opts: ShapeHoleOptions): Shape` — Drill a hole into this solid at a face.\n\n```js\nbox(50, 50, 20).hole('top', { diameter: 8, depth: 10 })\nbox(50, 50, 20).hole('top', { diameter: 6, counterbore: { diameter: 12, depth: 3 } })\n```\n\n**`FaceRef`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `normal` | `Vec3` | Normal direction of the face |\n| `center` | `Vec3` | Center point of the face |\n| `query?` | `FaceQueryRef` | Compiler-owned face query when available. |\n| `planar?` | `boolean` | True when the face can host a 2D sketch placement frame |\n| `uAxis?` | `Vec3` | Face-local horizontal axis for planar faces |\n| `vAxis?` | `Vec3` | Face-local vertical axis for planar faces |\n| `surface?` | `FaceSurface` | Analytic surface family when the backend can identify one. |\n| `descendant?` | `FaceDescendantMetadata` | Shared descendant-resolution metadata when this face is a semantic region/set. |\n\nAlso: `name: FaceName`.\n\n**`FaceDescendantMetadata`**: `kind: \"single\" | \"face-set\"`, `semantic: FaceDescendantSemantic`, `memberCount: number`, `memberNames: string[]`, `coplanar: boolean`\n\n**`ShapeHoleOptions`**: `diameter: number`, `depth?: number`, `upToFace?: SketchFaceTarget | FaceRef`, `extent?: ShapeFeatureExtentOptions`, `u?: number`, `v?: number`, `counterbore?: { diameter: number; depth: number; }`, `countersink?: { diameter: number; angleDeg?: number; }`, `thread?: ShapeHoleThreadOptions`\n\n`ShapeFeatureExtentOptions`: `{ forward: ShapeFeatureExtentSideOptions, reverse?: ShapeFeatureExtentSideOptions }`\n\n`ShapeFeatureExtentSideOptions`: `{ depth?: number, upToFace?: SketchFaceTarget | FaceRef, through?: boolean }`\n\n**`ShapeHoleThreadOptions`**: `designation?: string`, `pitch?: number`, `class?: string`, `handedness?: \"right\" | \"left\"`, `depth?: number`, `modeled?: boolean`\n\n#### `cutout(sketch: Sketch, opts?: ShapeCutoutOptions): Shape` — Cut a profile-shaped pocket through a face using a placed sketch.\n\nThe sketch must be placed on a face with `Sketch.onFace(...)`. The cut follows the sketch's 2D profile.\n\n```js\nconst profile = circle2d(10).onFace(body, 'top');\nbody.cutout(profile, { depth: 5 })\n```\n\n**`ShapeCutoutOptions`**: `depth?: number`, `upToFace?: SketchFaceTarget | FaceRef`, `extent?: ShapeFeatureExtentOptions`, `taperScale?: number | Vec2`\n\n**Placement**\n\n#### `placeReference(ref: PlacementAnchorLike, target: Vec3, offset?: Vec3): Shape` — Translate the shape so the given anchor or reference lands on the target coordinate.\n\nAccepts any built-in anchor name (`'bottom'`, `'center'`, `'top-front-left'`, etc.) or a custom placement reference attached via `withReferences()`.\n\n```javascript\n// Ground a shape — put its bottom face center at Z = 0\nshape.placeReference('bottom', [0, 0, 0])\n\n// Center at the world origin\nshape.placeReference('center', [0, 0, 0])\n\n// Align left edge to X = 10\nshape.placeReference('left', [10, 0, 0])\n```\n\n#### `attachTo(target: ShapeAnchorTarget, targetAnchor: PlacementAnchorLike, selfAnchor?: PlacementAnchorLike, offset?: Vec3): Shape` — Position this shape relative to another using named 3D anchor points.\n\nAnchors are bounding-box-relative: 'center', face centers ('top', 'front', ...), edge midpoints ('top-front', 'back-left', ...), and corners ('top-front-left', ...). Anchor word order is flexible: 'front-left' and 'left-front' are equivalent. Named placement references (from withReferences) can also be used as anchors.\n\n#### `onFace(parent: ShapeAnchorTarget, face: \"front\" | \"back\" | \"left\" | \"right\" | \"top\" | \"bottom\", opts?: { u?: number; v?: number; protrude?: number; }): Shape` — Place this shape on a face of a parent shape.\n\nThink of it like sticking a label on a box surface:\n\n- `face` picks which surface ('front', 'back', 'top', etc.)\n- `u, v` position within that face's 2D plane (from center)\n- front/back: u = left/right (X), v = up/down (Z)\n- left/right: u = forward/back (Y), v = up/down (Z)\n- top/bottom: u = left/right (X), v = forward/back (Y)\n- `protrude` = how far the child sticks out (positive = outward from face)\n\n#### `seatInto(target: Shape, surface: string, options?: SeatIntoOptions): Shape` — Slide this shape along an axis until a labeled face is embedded in the target body.\n\nPosition the shape roughly first (translate/rotate), then call seatInto to auto-adjust the penetration depth. No manual coordinate math needed.\n\n```js\n// Wing root embeds into fuselage — adapts to any fuselage shape\nwing.translate(0, wingY, 0).seatInto(fuselage, 'root');\n\n// Sensor pod sits flush on fuselage surface\npod.translate(0, station, radius + 20).seatInto(fuselage, 'base', { depth: 'flush' });\n\n// Antenna with 3mm gasket standoff\nmast.translate(0, station, radius + 50).seatInto(fuselage, 'mount', { depth: 'flush', gap: 3 });\n```\n\n**`SeatIntoOptions`**\n- `along?: Vec3` — Movement axis. Default: inverted face normal (points into target).\n- `depth?: \"full\" | \"flush\" | number` — How deep to embed. 'full' = entire face inside. 'flush' = nearest point touches. number = mm past flush. Default: 'full'.\n- `gap?: number` — Standoff gap in mm. Positive = gap between face and target. Negative = extra penetration. Default: 0.\n\n#### `seatOver(target: Shape, targetSurface: string, options?: SeatIntoOptions): Shape` — Slide this shape until a target's labeled face is fully covered (inside this shape).\n\nThe inverse of `seatInto`: instead of embedding *your* face into the target, you move until the *target's* face is embedded inside you.\n\n```js\n// Nacelle moves up until pylon's bottom face is inside the nacelle\nnacelle.translate(rough).seatOver(pylon, 'bottom');\n\n// Cap slides down over a post until post's top face is covered\ncap.translate(rough).seatOver(post, 'top');\n```\n\n**Connectors**\n\n#### `withConnectors(connectors: Record<string, ConnectorInput>): Shape` — Attach named connectors — attachment points that survive transforms and imports. Connectors can be bare (position + orientation) or typed (with connectorType/gender for compatibility matching).\n\n`PortInput`: `{ origin?: Vec3, axis?: Vec3, start?: Vec3, end?: Vec3, up?: Vec3, kind?: JointType, min?: number, max?: number }`\n\n`ConnectorInput`: `{ connectorType?: string, gender?: ConnectorGender, measurements?: Record<string, number | string> }`\n\n#### `connectorNames(): string[]` — List all connector names on this shape.\n\n#### `connectorsByType(type: string): Array<{ name: string; port: ConnectorDef; }>` — Get all connectors of a given type.\n\n#### `connectorDistance(nameA: string, nameB: string): number` — Distance between two connector origins on this shape.\n\n#### `connectorMeasurements(name: string): Record<string, number | string>` — Get measurements metadata from a connector.\n\n#### `matchTo(targetOrPairs: Shape | MatchTarget | Array<[ Shape | MatchTarget, string, string ]>, selfConnOrDict?: string | Record<string, string>, targetConnOrOptions?: string | MatchToOptions, maybeOptions?: MatchToOptions): Shape` — Position this shape by matching connectors to a target.\n\nAlignment: with a single connector pair, the shape translates and rotates so the connector origins coincide and the axes oppose (plug-in model); `up` pins the roll. With multiple pairs, the connector origins define the rigid transform — still author meaningful `axis`/`up` values so the same connectors remain useful for `connect()`, audits, and future matching.\n\nOverloads:\n\n- Single pair: `matchTo(target, selfConn, targetConn, options?)`\n- Dictionary (same target): `matchTo(target, { selfConn: targetConn, ... }, options?)`\n- Multi-target: `matchTo([ [target1, selfConn1, targetConn1], ... ], options?)`\n\n`MatchToOptions`: `{ force?: boolean, angle?: number, distance?: number }`\n\n**References**\n\n#### `withReferences(refs: PlacementReferenceInput): Shape` — Attach named placement references that survive normal transforms and imports.\n\n**`PlacementReferenceInput`**: `points?: Record<string, Vec3>`, `edges?: Record<string, PlacementEdgeRef>`, `surfaces?: Record<string, PlacementSurfaceRef>`, `objects?: Record<string, PlacementObjectInput>`\n\n`PlacementEdgeRef`: `{ start: Vec3, end: Vec3 }`\n\n`PlacementSurfaceRef`: `{ center: Vec3, normal: Vec3 }`\n\n#### `referenceNames(kind?: PlacementReferenceKind): string[]` — List named placement references carried by this shape.\n\n#### `referencePoint(ref: PlacementAnchorLike): Vec3` — Resolve a named placement reference or built-in anchor to a 3D point.\n\n**Measurement**\n\n#### `boundingBox(): ShapeRuntimeBounds` — Get the axis-aligned bounding box as { min: [x,y,z], max: [x,y,z] }.\n\n#### `volume(): number` — Volume in mm cubed.\n\n#### `surfaceArea(): number` — Surface area in mm squared.\n\n#### `isEmpty(): boolean` — True if the shape contains no geometry.\n\n#### `numBodies(): number` — Number of disconnected solid bodies in this shape.\n\n#### `numTri(): number` — Triangle count of the mesh representation.\n\n**Other**\n\n#### `clone(): Shape` — Return a new Shape wrapper for explicit duplication in scripts.\n\n#### `geometryInfo(): GeometryInfo` — Inspect which backend/representation produced this solid.\n\n#### `as(name: string): Shape` — Name this shape as a reference namespace for diagnostics and future published refs.\n\n#### `ref(path: string): ShapeRef` — Resolve a semantic reference path like `lid`, `lid/back`, or a midpoint selector on `lid/back`.\n\n#### `thicken(thickness: number): Shape` — Offset-thicken an exact open surface or shell into a solid.\n\n#### `getMesh(): ShapeRuntimeMesh` — Extract triangle mesh for Three.js rendering\n\n#### `slice(offset?: number): any` — Slice the runtime solid by a plane normal to local Z at the given offset.\n\n#### `project(): any` — Orthographically project the runtime solid onto the local XY plane.\n\n**Compatibility Aliases**\n\n- `withPorts()` -> `withConnectors()`\n- `portNames()` -> `connectorNames()`\n\n### `Transform`\n\n#### `static identity(): Transform` — Return the identity transform.\n\n#### `static from(input: TransformInput): Transform` — Wrap an existing `Transform` or raw 4x4 matrix as a `Transform`.\n\n#### `static compose(...steps: TransformInput[]): Transform` — Compose transforms in chain order: `Transform.compose(a, b, c)` applies `a`, then `b`, then `c` — the same left-to-right order as `Transform.from(a).mul(b).mul(c)`.\n\nPrefer this over manual `.mul()` chains when composing 3+ transforms (e.g. kinematics: `local -> childBase -> jointMotion -> jointFrame -> parentWorld`); the variadic form makes the application order explicit and prevents order mistakes.\n\n```ts\nconst world = Transform.compose(childBase, jointMotion, jointFrame, parentWorld);\n```\n\n#### `static translation(x: number, y: number, z: number): Transform` — Create a translation transform.\n\n#### `static scale(v: number | Vec3): Transform` — Create a uniform or per-axis scale transform.\n\n#### `static rotationAxis(axis: Vec3, angleDeg: number, pivot?: Vec3): Transform` — Create a rotation around an arbitrary axis, optionally about a pivot.\n\n#### `static rotateAroundTo(axis: Vec3, pivot: Vec3, movingPoint: Vec3, targetPoint: Vec3, options?: RotateAroundToOptions): Transform` — Solve the rotation needed to move one point onto a target line or plane.\n\n#### `mul(other: TransformInput): Transform` — Compose transforms in chain order: `a.mul(b)` applies `a`, then `b`.\n\n#### `translate(x: number, y: number, z: number): Transform` — Translate after the current transform.\n\n#### `rotateAxis(axis: Vec3, angleDeg: number, pivot?: Vec3): Transform` — Rotate after the current transform.\n\n#### `rotateX(angleDeg: number, pivot?: Vec3): Transform` — Rotate about the X axis after the current transform (parity with `Shape.rotateX`).\n\n#### `rotateY(angleDeg: number, pivot?: Vec3): Transform` — Rotate about the Y axis after the current transform (parity with `Shape.rotateY`).\n\n#### `rotateZ(angleDeg: number, pivot?: Vec3): Transform` — Rotate about the Z axis after the current transform (parity with `Shape.rotateZ`).\n\n#### `inverse(): Transform` — Return the inverse transform.\n\n#### `point(p: Vec3): Vec3` — Transform a point using homogeneous coordinates.\n\n#### `vector(v: Vec3): Vec3` — Transform a direction vector without translation.\n\n#### `toArray(): Mat4` — Return the transform as a raw 4x4 matrix array.\n\n### `ShapeGroup`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `children` | `GroupChild[]` | — |\n| `childNames` | `Array<string \\| undefined>` | — |\n\n**Children**\n\n#### `child(name: string): GroupChild` — Return the named child by name. Throws if not found. Useful when importing a multipart group and working on components individually.\n\n#### `childName(index: number): string | undefined` — Return the optional name of the child at `index`.\n\n**Transforms**\n\n#### `translate(x: number, y: number, z: number): ShapeGroup` — Move the entire group by (x, y, z). All children move together as a unit.\n\n#### `moveTo(x: number, y: number, z: number): ShapeGroup` — Move the group so its bounding-box min corner lands at the given coordinate.\n\n#### `moveToLocal(target: Shape | ShapeGroup, x: number, y: number, z: number): ShapeGroup` — Move the group relative to another part's bounding-box min corner.\n\n#### `rotate(axis: Vec3, angleDeg: number, options?: { pivot?: Vec3; }): ShapeGroup` — Rotate the group around an arbitrary axis through the origin. Unlike `scale()`/`mirror()` (bounding-box center) and `Sketch.rotate()`, this pivots at the world origin — pass `options.pivot` to rotate in place.\n\n#### `rotateX(angleDeg: number, options?: { pivot?: Vec3; }): ShapeGroup` — Rotate the group around the X axis.\n\n#### `rotateY(angleDeg: number, options?: { pivot?: Vec3; }): ShapeGroup` — Rotate the group around the Y axis.\n\n#### `rotateZ(angleDeg: number, options?: { pivot?: Vec3; }): ShapeGroup` — Rotate the group around the Z axis.\n\n#### `rotateAroundAxis(axis: Vec3, angleDeg: number, pivot?: Vec3): ShapeGroup` — Rotate around an arbitrary axis, optionally through a pivot point.\n\n#### `rotateAroundTo(axis: Vec3, pivot: Vec3, movingPoint: Anchor3D | Vec3, targetPoint: Anchor3D | Vec3, options?: RotateAroundToOptions): ShapeGroup` — Rotate around an axis until a moving point reaches the target line/plane defined by the axis and target point. ShapeGroup string points use built-in anchors only.\n\n#### `pointAlong(direction: Vec3): ShapeGroup` — Reorient the group so its local Z axis points along `direction`.\n\n#### `transform(m: Mat4 | Transform): ShapeGroup` — Apply a 4x4 transform matrix or `Transform` to all 3D children.\n\n#### `scale(v: number | Vec3): ShapeGroup` — Scale uniformly or per-axis from the group's bounding-box center.\n\n#### `scaleAround(pivot: Vec3, v: number | Vec3): ShapeGroup` — Scale uniformly or per-axis from an explicit pivot point.\n\n#### `mirror(normal: Vec3): ShapeGroup` — Mirror across a plane through the group's bounding-box center.\n\n#### `mirrorThrough(point: Vec3, normal: Vec3): ShapeGroup` — Mirror across a plane through an explicit point.\n\n**Placement**\n\n#### `placeReference(ref: PlacementAnchorLike, target: Vec3, offset?: Vec3): ShapeGroup` — Translate the group so the given anchor or reference lands on the target coordinate.\n\nAccepts any built-in anchor name (`'bottom'`, `'center'`, `'top-front-left'`, etc.) or a custom placement reference attached via `withReferences()`.\n\n```javascript\n// Ground a group — put its bottom at Z = 0\nassembly.placeReference('bottom', [0, 0, 0])\n\n// Use a custom reference from a multi-file part\nconst placed = require('./bracket-assembly.forge.js').group\n .placeReference('mountCenter', [0, 0, 50]);\n```\n\n#### `attachTo(target: Shape | ShapeGroup, targetAnchor: Anchor3D | string, selfAnchor?: Anchor3D, offset?: Vec3): ShapeGroup` — Attach this group to a face or anchor on another part.\n\n`targetAnchor` can be a built-in anchor name or a custom reference name on the target. `selfAnchor` selects the anchor on this group to align.\n\n#### `onFace(parent: Shape | ShapeGroup, face: \"front\" | \"back\" | \"left\" | \"right\" | \"top\" | \"bottom\", opts?: { u?: number; v?: number; protrude?: number; }): ShapeGroup` — Place this group on a face of a parent shape. See Shape.onFace() for full documentation.\n\n**Connectors**\n\n#### `withConnectors(connectors: Record<string, ConnectorInput>): ShapeGroup` — Attach named connectors — attachment points that survive transforms. Connectors can be bare (position + orientation) or typed (with connectorType/gender for compatibility matching).\n\n#### `connectorNames(): string[]` — List all connector names, including \"ChildName.connectorName\" from named children.\n\n#### `connectorsByType(type: string): Array<{ name: string; port: ConnectorDef; }>` — Get all connectors of a given type, including from named children.\n\n#### `connectorDistance(nameA: string, nameB: string): number` — Distance between two connector origins on this group (supports dotted child paths).\n\n#### `connectorMeasurements(name: string): Record<string, number | string>` — Get measurements metadata from a connector (supports dotted child paths).\n\n#### `matchTo(targetOrPairs: Shape | ShapeGroup | Array<[ Shape | ShapeGroup, string, string ]>, selfConnOrDict?: string | Record<string, string>, targetConnOrOptions?: string | MatchToOptions, maybeOptions?: MatchToOptions): ShapeGroup` — Position this group by matching connectors to a target. Connector names support dotted paths into named children: \"ChildName.connectorName\".\n\nAlignment: with a single connector pair, the group translates and rotates so the connector origins coincide and the axes oppose (plug-in model); `up` pins the roll. With multiple pairs, the connector origins define the rigid transform — still author meaningful `axis`/`up` values so the same connectors remain useful for `connect()`, audits, and future matching.\n\nOverloads:\n\n- Single pair: `matchTo(target, selfConn, targetConn, options?)`\n- Dictionary (same target): `matchTo(target, { selfConn: targetConn, ... }, options?)`\n- Multi-target: `matchTo([ [target1, selfConn1, targetConn1], ... ], options?)`\n\n**References**\n\n#### `withReferences(refs: PlacementReferenceInput): ShapeGroup` — Attach named placement references to this group. References survive normal transforms (translate/rotate/scale/mirror/transform).\n\n```javascript\nconst bracket = group(\n { name: 'Left', shape: leftShape },\n { name: 'Right', shape: rightShape },\n).withReferences({\n points: { mountCenter: [0, 0, 0] },\n});\n```\n\n#### `referenceNames(kind?: PlacementReferenceKind): string[]` — List named placement references carried by this group.\n\n#### `referencePoint(ref: PlacementAnchorLike): Vec3` — Resolve a named placement reference or built-in Anchor3D to a 3D point. Named refs take priority over built-in anchors.\n\n**Other**\n\n#### `clone(): ShapeGroup` — Return a deep-cloned ShapeGroup tree (refs copied).\n\n#### `boundingBox(): { min: Vec3; max: Vec3; }` — Return the combined 3D bounding box of all children.\n\n#### `color(hex: string): ShapeGroup` — Return a copy of the group with the given display color applied to each child.\n\n**Compatibility Aliases**\n\n- `withPorts()` -> `withConnectors()`\n- `portNames()` -> `connectorNames()`\n\n### `SurfacePattern`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `body` | `string` | Function body: receives (u, v) in surface mm, returns height displacement. |\n| `constants` | `Record<string, number>` | Named constants injected into the function. |\n\n### `Pattern2D`\n\n#### `add(...patterns: Pattern2DInput[]): Pattern2D` — Add this pattern to one or more patterns or constant height offsets.\n\n#### `subtract(pattern: Pattern2DInput): Pattern2D` — Subtract another pattern or constant height offset from this pattern.\n\n#### `multiply(...patterns: Pattern2DInput[]): Pattern2D` — Multiply this pattern by one or more patterns or numeric scale factors.\n\n#### `min(...patterns: Pattern2DInput[]): Pattern2D` — Keep the lower height between this pattern and one or more other patterns.\n\n#### `max(...patterns: Pattern2DInput[]): Pattern2D` — Keep the higher height between this pattern and one or more other patterns.\n\n#### `clamp(min: number, max: number): Pattern2D` — Limit pattern height to the inclusive `[min, max]` range in millimeters.\n\n#### `abs(): Pattern2D` — Convert negative heights to positive heights.\n\n#### `negate(): Pattern2D` — Flip the pattern height sign.\n\n### `Pattern2DBuilder`\n\n#### `constant(value?: number): Pattern2D` — Create a constant-height pattern in millimeters.\n\n#### `sineWave(options: Pattern2DSineWaveOptions): Pattern2D` — Create a sinusoidal wave pattern in UV space.\n\n**`Pattern2DSineWaveOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `direction?` | `Vec2` | Direction the wave advances in UV space. Default: [1, 0]. |\n| `wavelength` | `number` | Distance between wave peaks in surface millimeters. |\n| `amplitude?` | `number` | Height amplitude in millimeters. Default: 1. |\n| `phase?` | `number` | Phase offset in radians. Default: 0. |\n| `bias?` | `number` | Constant height offset in millimeters. Default: 0. |\n\n#### `stripes(options: Pattern2DStripesOptions): Pattern2D` — Create recessed stripe bands in UV space.\n\n**`Pattern2DStripesOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `direction?` | `Vec2` | Direction perpendicular to the stripe bands in UV space. Default: [1, 0]. |\n| `spacing` | `number` | Center-to-center spacing in surface millimeters. |\n| `width` | `number` | Stripe width in surface millimeters. |\n| `depth?` | `number` | Stripe groove depth in millimeters. Default: 1. |\n\n#### `overUnderWeave(options: Pattern2DOverUnderWeaveOptions): Pattern2D` — Create an over-under woven relief pattern in UV space.\n\n**`Pattern2DOverUnderWeaveOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `spacing` | `number \\| Vec2` | Thread center-to-center spacing. A number uses the same spacing for U and V. |\n| `threadWidth` | `number \\| Vec2` | Thread width. A number uses the same width for U and V. |\n| `depth?` | `number` | Thread groove depth in millimeters. Default: 0.8. |\n| `underScale?` | `number` | Relative height of the under-crossing thread. Default: 0.15. |\n\n### `ShapeRef`\n\nA first-class reference path over a shape's semantic faces and face relationships.\n\nCreated with `shape.ref(\"lid/back\")`, then refined through methods such as `.point()` or `.edges()`. The reference stores intent as a readable path and resolves lazily against the current shape metadata.\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `path` | `string` | — |\n\n**Methods:**\n\n#### `resolve(): ShapeReferenceResolution` — Resolve this reference into its current faces, edges, or points.\n\n#### `get kind(): ShapeReferenceKind` — The resolved reference kind, such as `face`, `edge-set`, or `point`.\n\n#### `get cardinality(): ShapeReferenceCardinality` — Whether the reference currently resolves to zero, one, or many matches.\n\n#### `status(): ShapeReferenceStatus` — Return the reference lifecycle status for the current shape state.\n\n#### `explain(): string` — Return a human-readable explanation of how this reference resolved.\n\n#### `as(name: string): ShapeRef` — Name this derived reference so the same shape can resolve it by `shape.ref(name)`.\n\n#### `maybe(): ShapeRef` — Return an optional reference that resolves to zero matches instead of throwing when missing.\n\n#### `all(): ShapeRef` — Mark that a multi-match reference is intentionally being used as a set.\n\n#### `one(): ShapeRef` — Require this reference to resolve to exactly one match.\n\n#### `faces(): FaceRef[]` — Resolve this reference as one or more faces.\n\n#### `face(): FaceRef` — Resolve this reference as exactly one face.\n\n#### `edges(): EdgeSegment[]` — Resolve this reference as one or more edges. Face references return boundary edges.\n\n#### `edge(): EdgeSegment` — Resolve this reference as exactly one edge.\n\n#### `points(): Vec3[]` — Resolve this reference as one or more points. Faces use centers and edges use midpoints.\n\n#### `point(): Vec3` — Resolve this reference as exactly one point.\n\n#### `toJSON(): ShapeReferenceResolution` — Return the structured JSON-friendly reference resolution.\n\n#### `toString(): string` — Return a compact display form for this reference path.\n\n---\n\n## Constants\n\n### `ANCHOR3D_NAMES`\n\n### `verify`\n\nMembers (full entries under [Verification](#verification)): `verify.that`, `verify.equal`, `verify.notEqual`, `verify.greaterThan`, `verify.lessThan`, `verify.inRange`, `verify.centersCoincide`, `verify.connectorDistance`, `verify.physicalComponentCount`, `verify.intentionalOverlap`, `verify.notColliding`, `verify.minClearance`, `verify.clearanceBetween`, `verify.parallel`, `verify.perpendicular`, `verify.coplanar`, `verify.faceAt`, `verify.sameDirection`, `verify.isEmpty`, `verify.notEmpty`, `verify.volumeApprox`, `verify.areaApprox`, `verify.boundingBoxSize`, `verify.edgeContinuity`, `verify.noTinyEdges`, `verify.noSliverFaces`, `verify.noSelfIntersection`.\n\n### `Points`\n\n- `distance(a: Vec3, b: Vec3): number` — Euclidean distance between two 3D points.\n- `midpoint(a: Vec3, b: Vec3): Vec3` — Center point between two 3D points.\n- `lerp(a: Vec3, b: Vec3, t: number): Vec3` — Linearly interpolate between two 3D points. t=0 returns a, t=1 returns b.\n- `direction(a: Vec3, b: Vec3): Vec3` — Unit direction vector from a to b. Throws if a and b are the same point.\n- `offset(point: Vec3, dir: Vec3, amount: number): Vec3` — Move a point along a direction vector by a given amount.\n- `polar(length: number, angleDeg: number, from?: Vec2): Vec2` — Compute a 2D point at distance and angle (degrees) from an optional origin.\n\n### `connector`\n\nConnector factory. Create attachment points: `connector({...})`, `connector.male(type, {...})`, etc.\n\n### `Import`\n\nNamespaced file-format import helpers — the single vocabulary for bringing external geometry files into a model.\n\n- `dxfSketch(fileName: string, options?: DxfImportOptions): Sketch` — Parse a DXF file and return closed 2D profile geometry as a Sketch. The result can be extruded directly.\n- `svgSketch(fileName: string, options?: SvgImportOptions): Sketch` — Parse an SVG file and return it as a Sketch with options for region filtering, scaling, and simplification.\n- `mesh(fileName: string, options?: MeshImportOptions): Shape | ShapeGroup` — Import an external mesh file (STL, OBJ, 3MF).\n\n By default, 3MF build items are flattened into one Shape for compatibility. Use `separateObjects: true` to import 3MF build items/resource objects as a named ShapeGroup whose children are targetable by `forgecad ls`. Use `object` to import one item by the stable ref/name reported by `forgecad run`.\n\n For 3MF sources, `forgecad run` prints a source-structure table with one line per build item: `[3mf:build:NNN:object:N] name type=... verts=... tris=... bbox=[min] → [max]`. Build items are numbered from `001`; files with no build items list resource objects as `3mf:object:N` instead. Per-item bboxes reveal multi-part structure — account for every substantial item before flattening. Pass any listed stable ref or name as `object` to import that item alone.\n\n Use `sourceFrame: { up: \"+Y\" }` when the file was authored in a non-Z-up coordinate system. ForgeCAD remains Z-up; the import is rotated so the named source axis becomes ForgeCAD +Z. Supported values: `\"+X\"`, `\"-X\"`, `\"+Y\"`, `\"-Y\"`, `\"+Z\"`, `\"-Z\"`.\n\n ```js\n const all = Import.mesh(\"./assembly.3mf\", { separateObjects: true });\n const pin = all.child(\"Pin #001\");\n const plate = Import.mesh(\"./assembly.3mf\", { object: \"3mf:build:001:object:7\" });\n const yUpPart = Import.mesh(\"./part.obj\", { sourceFrame: { up: \"+Y\" } });\n ```\n- `step(fileName: string, options?: StepImportOptions): Shape` — Import a STEP file (.step, .stp) as an exact OCCT-backed Shape. Preserves NURBS curves, B-spline surfaces, and exact topology. Requires running with the OCCT backend. Use `sourceFrame: { up: \"+Y\" }` to rotate Y-up source files into ForgeCAD's Z-up world.\n\n---\n\n<!-- guides/coordinate-system.md -->\n\n# Coordinate System\n\nZ-up right-handed: +X right, +Y back, +Z up. Ground plane is XY at Z = 0; extrusion goes along +Z. Units are millimeters; angles are degrees.\n\nModel fronts (face/nose/camera side) point toward **-Y**; rear is +Y; the forward vector is `[0, -1, 0]`. Anchors follow: `front` resolves to the minimum-Y side, `back` to the maximum-Y side.\n\nA `front` view camera sits on the -Y side looking toward +Y, so it sees the model's front face. The other views follow: back +Y, right +X, left -X, top +Z, bottom -Z.\n\n---\n\n<!-- guides/positioning.md -->\n\n# Positioning Decision Ladder\n\nMost positioning bugs come from manual coordinate arithmetic. Pick the **highest applicable rung**; drop down only when the rung above doesn't fit.\n\n## 1. Connectors + `matchTo()` — every real part-to-part interface\n\n**Rule 0:** if parts are meant to stay in contact, define connectors and `matchTo()` — including *static* assemblies (furniture, enclosures, fixtures, toys), not just mechanisms. Connectors win because they are **stable** (don't shift on fillet/chamfer/boolean), **semantic** (type/gender), **oriented** (full frame), **queryable** (`verify.connectorDistance`), and **explode-aware**.\n\n```javascript\nconst shelf = box(200, 120, 10).withConnectors({\n tab: connector.male(\"dovetail\", { origin: [-100, 0, 5], axis: [-1, 0, 0], up: [0, 0, 1] }),\n});\nconst placed = shelf.matchTo(panel, \"tab\", \"shelf_slot\");\n```\n\nAlignment semantics, dictionary form, and dotted group paths: see `matchTo()` JSDoc. For cross-file part alignment, prefer connectors over `withReferences()` placement points.\n\n## 2. `group()` — local coordinates for multi-part assemblies\n\nBuild sub-parts at the **local origin**, group them, then translate the group **once** — never add a parent's global offset to every sub-part. Groups nest; each level has its own local origin. Groups cannot be booleaned: do subtract/intersect first in local coordinates, then group the result. Worked example: `group()` JSDoc.\n\n## 3. `pointAlong()` — orient before positioning\n\nAlways call `pointAlong()` **before** `matchTo()`/`translate()` — it reorients around the origin.\n\n## 4. `attachTo()` — rough bounding-box placement only\n\nAnchor points shift after fillet/chamfer/boolean: fine for quick prototyping, fragile for assembly interfaces — promote real interfaces to connectors.\n\n## 5. `placeReference()` — land a named anchor on a world coordinate\n\nGrounding, centering, edge alignment, custom reference points: see `placeReference()` JSDoc.\n\n## 6. Last resort: `rotateAroundTo()`, `moveToLocal()`, `translate()`\n\nFor computed offsets and free-floating or exploratory layout. Raw `translate()`/`rotate()` is correct only when parts are intentionally unrelated.\n\n## Mechanisms\n\nLink graphs (`link()`, `edgeBetweenLinks()`) solve **point positions** (closed loops); connector-frame joints (`assembly().connect()`) **orient physical parts**. Frame semantics and mirrored-revolute rules: assembly docs; joint geometry: `guides/joint-design.md`.\n\n## Primitive placement\n\nBox and cylinder sit base-at-Z=0, centered on XY; sphere and torus are fully centered. There is no OpenSCAD-style `center: true` — placement is fixed; use `placeReference('center', [0, 0, 0])` to fully center. Exact per-axis extents: primitive JSDoc.\n\n---\n\n<!-- generated/sketch.md -->\n\n# Sketch API\n\n2D geometry creation, transforms, booleans, constrained sketches, and extrusion.\n\n## Contents\n\n- [2D Sketch Primitives](#2d-sketch-primitives)\n- [2D Sketch Booleans](#2d-sketch-booleans)\n- [2D Text](#2d-text)\n- [Constrained Sketches](#constrained-sketches)\n- [Sketch](#sketch) — Transforms, Booleans, Features, Promotion, Placement, Labels, Measurement\n- [ConstrainedSketchBuilder](#constrainedsketchbuilder) — Drawing, Entities, Geometric Constraints, Dimensional Constraints, Coincidence & Equality, Tangent Transitions, Shape Constraints, Positioning, Solving\n- [ConstraintSketch](#constraintsketch)\n- [SketchGroupBuilder](#sketchgroupbuilder)\n- [Point2D](#point2d)\n- [Line2D](#line2d)\n- [Circle2D](#circle2d)\n- [Rectangle2D](#rectangle2d)\n\n## Functions\n\n### 2D Sketch Primitives\n\n#### `path(): PathBuilder` — Create a new [`PathBuilder`](/docs/curves#pathbuilder) for tracing a 2D outline point by point.\n\n[`PathBuilder`](/docs/curves#pathbuilder) is a fluent API for constructing 2D profiles using a mix of line segments, arcs, bezier curves, and splines. Always start with `.moveTo(x, y)` to set the starting point. Call `.close()` to get a filled `Sketch`, or `.stroke(width)` to thicken an open polyline into a solid profile.\n\nEdge labels can be assigned with `.label('name')` after any segment — they propagate through extrusion, revolve, loft, and sweep into named faces on the resulting [`Shape`](/docs/core#shape).\n\n```ts\n// Closed triangle\nconst triangle = path().moveTo(0, 0).lineH(50).lineV(30).close();\n\n// L-shaped bracket as a stroke\nconst bracket = path().moveTo(0, 0).lineH(50).lineV(-70).lineAngled(20, 235).stroke(4);\n\n// Labeled edges for downstream face references\nconst slot = path()\n .moveTo(0, 0)\n .lineTo(30, 0).label('bottom')\n .lineTo(30, 10)\n .lineTo(0, 10).label('top')\n .close();\n```\n\n#### `stroke(points: Vec2[], width: number, join?: \"Round\" | \"Square\"): Sketch` — Thicken a 2D polyline (centerline) into a solid filled profile of uniform width.\n\nStandalone equivalent of `path()...stroke(width, join)`. Use for centerline-based geometry — ribs, wire traces, brackets. For rounding corners of a *closed* outline use `.filletCorners(radius)` (all corners) or `.filletCorner([x, y], radius)` (one corner) instead.\n\n#### `rect(width: number, height: number): Sketch` — Create a 2D rectangle centered at the origin.\n\n```ts\nrect(40, 20).extrude(5);\n```\n\n#### `circle2d(radius: number, segments?: number): Sketch` — Create a 2D circle centered at the origin.\n\nOmit `segments` for a smooth (auto-tessellated) circle. Pass an integer to get a regular polygon approximation — e.g. `6` for a hexagon, `8` for an octagon.\n\n```ts\ncircle2d(25).extrude(10); // smooth cylinder\ncircle2d(25, 6).extrude(10); // hexagonal prism\n```\n\n#### `roundedRect(width: number, height: number, radius: number): Sketch` — Create a 2D rectangle with rounded corners, centered at the origin.\n\nThe corner radius is automatically clamped to `min(width/2, height/2)` so it can never exceed the shape dimensions.\n\n```ts\nroundedRect(60, 30, 5).extrude(3);\n```\n\n#### `polygon(points: (Vec2 | Point2D)[]): Sketch` — Create a 2D polygon from an array of `[x, y]` points or `Point2D` objects.\n\nWinding order is normalized automatically — clockwise (CW) input is silently reversed to CCW before being passed to the geometry kernel.\n\n```ts\npolygon([[0, 0], [50, 0], [25, 40]]).extrude(5); // triangle\n```\n\n#### `ngon(sides: number, radius: number): Sketch` — Create a regular polygon inscribed in a circle of the given radius.\n\n`radius` is the center-to-vertex (circumradius) distance. Use `sides` of `3` for a triangle, `6` for a hexagon, etc. The first vertex is at the top (−90° from +X).\n\n```ts\nngon(6, 20).extrude(10); // hexagonal prism, circumradius 20\n```\n\n#### `ellipse(rx: number, ry: number, segments?: number): Sketch` — Create a 2D ellipse centered at the origin.\n\n```ts\nellipse(30, 15).extrude(5);\nellipse(30, 15, 32).extrude(5); // lower-resolution approximation\n```\n\n#### `slot(length: number, width: number): Sketch` — Create a slot (oblong / stadium shape) — a rectangle with semicircular ends, centered at the origin.\n\n```ts\nslot(40, 10).extrude(3); // 40mm long, 10mm wide slot\n```\n\n#### `arcSlot(pitchRadius: number, sweepDeg: number, thickness: number): Sketch` — Create an arc-shaped slot (banana / annular sector) centered at the origin.\n\nThe slot is symmetric about the +X axis. The two ends are closed with semicircular caps. `pitchRadius` is the distance from the origin to the centerline of the slot, and `thickness` is the radial width of the slot.\n\n```ts\narcSlot(135, 74, 40).extrude(5); // pitch R135, 74° sweep, 40mm wide\n```\n\n### 2D Sketch Booleans\n\n#### `union2d(...inputs: SketchOperandInput[]): Sketch` — Combine 2D sketches into a single profile using an additive boolean union.\n\nAccepts individual sketches or arrays: `union2d(a, b, c)` or `union2d([a, b, c])`. Uses Manifold's batch operation — faster than chaining `.add()` one by one when combining many sketches.\n\n```ts\nconst cross = union2d(rect(60, 10), rect(10, 60));\n```\n\n#### `difference2d(...inputs: SketchOperandInput[]): Sketch` — Subtract one or more 2D sketches from a base sketch.\n\nThe first sketch is the base; all subsequent sketches are subtracted from it. Accepts individual sketches or arrays: `difference2d(base, c1, c2)` or `difference2d([base, c1, c2])`. Uses Manifold's batch operation — faster than chaining `.subtract()` one by one.\n\n```ts\nconst donut = difference2d(circle2d(50), circle2d(30));\n```\n\n#### `intersection2d(...inputs: SketchOperandInput[]): Sketch` — Keep only the area where all input sketches overlap (intersection boolean).\n\nAccepts individual sketches or arrays: `intersection2d(a, b)` or `intersection2d([a, b, c])`. Uses Manifold's batch operation — faster than chaining `.intersect()` one by one.\n\n```ts\nconst lens = intersection2d(circle2d(30).translate(-10, 0), circle2d(30).translate(10, 0));\n```\n\n### 2D Text\n\n#### `loadFont(source: string | ArrayBuffer, cacheKey?: string): opentype.Font` — Pre-load and cache a font for use with `text2d()`.\n\nFonts are cached by their source string (or `cacheKey` for `ArrayBuffer` sources), so repeated calls with the same path are free. Pre-loading is useful when you call `text2d()` many times with the same font — it avoids repeated disk reads.\n\nBuilt-in font names that work everywhere (browser + CLI):\n\n- `'sans-serif'` or `'inter'` — bundled Inter Regular\n\n```ts\nconst font = loadFont('/path/to/Arial Bold.ttf');\ntext2d('Title', { size: 12, font }).extrude(1.5);\ntext2d('Subtitle', { size: 8, font }).extrude(1);\n```\n\n#### `text2d(content: string, options?: TextOptions): Sketch` — Build a filled 2D Sketch from a text string.\n\nThe Sketch origin is at the left end of the text baseline by default. Use `align` and `baseline` options to adjust placement. Text is rendered using the bundled Inter font by default, or any TTF/OTF/WOFF font you provide.\n\n`text2d()` creates real geometry. For temporary viewport annotations, prefer `Viewport.label()` so the text stays off the geometry and OCCT compile paths. Do not use either form of text to make unclear production geometry readable; model the physical artifact clearly instead.\n\nAlignment reference table:\n\n| `align` | `baseline` | Origin |\n|------------|--------------|-------------------------------------|\n| `'left'` | `'baseline'` | Bottom-left of first char (default) |\n| `'center'` | `'center'` | Dead center of text block |\n| `'right'` | `'top'` | Top-right corner |\n\n```ts\n// Extruded nameplate\ntext2d('FORGE CAD', { size: 8 }).extrude(1.2);\n\n// Centered label on the XY plane\ntext2d('V 2.0', { size: 6, align: 'center', baseline: 'center' });\n\n// Engraved text cut into the top face of a box\nconst label = text2d('REV A', { size: 5, align: 'center', baseline: 'center' });\nplate.subtract(label.onFace(plate, 'top', { protrude: -0.5 }).extrude(1));\n\n// Custom TTF font\ntext2d('Hello', { size: 10, font: '/path/to/Arial.ttf' }).extrude(1);\n\n// Pre-loaded font for reuse\nconst font = loadFont('/path/to/Arial Bold.ttf');\ntext2d('Title', { size: 12, font }).extrude(1.5);\n```\n\n**`TextOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `size?` | `number` | Cap height of the text in model units. All other dimensions (stroke weight, spacing) scale proportionally. |\n| `letterSpacing?` | `number` | Extra space between characters in model units. Negative values tighten the tracking. |\n| `align?` | `\"left\" \\| \"center\" \\| \"right\"` | Horizontal alignment relative to x = 0. - `'left'` — left edge at x = 0 (default) - `'center'` — centred on x = 0 - `'right'` — right edge at x = 0 |\n| `baseline?` | `\"baseline\" \\| \"center\" \\| \"top\"` | Vertical alignment relative to y = 0. - `'baseline'` — y = 0 is the text baseline (bottom of capital letters) - `'center'` — y = 0 is the vertical midpoint of the cap height - `'top'` — y = 0 is the top of capital letters |\n| `font?` | `string \\| opentype.Font` | Font to use for text rendering. - `'sans-serif'` or `'inter'` — bundled Inter font (works everywhere, including browser) - **file path** — path to a TTF, OTF, or WOFF font file (CLI/Node only) - **Font object** — a previously loaded opentype.js Font (from `loadFont()`) - **omitted** — uses the bundled Inter font (same as `'sans-serif'`) |\n| `flattenTolerance?` | `number` | Bezier flattening tolerance in model units. Smaller = more polygon segments = smoother curves. |\n\n#### `textWidth(content: string, options?: Pick<TextOptions, \"size\" | \"letterSpacing\" | \"font\">): number` — Measure the rendered advance width of a string without creating any geometry.\n\nUses the same font metrics as `text2d()`. Useful for computing layout dimensions before building the actual sketch — e.g. sizing a plate to fit a label.\n\n```ts\nconst w = textWidth('SERIAL: 001', { size: 6 });\nconst plate = box(w + 10, 12, 2);\n```\n\n### Constrained Sketches\n\n#### `constrainedSketch(options?: ConstrainedSketchOptions): ConstrainedSketchBuilder` — Create a parametric 2D sketch driven by geometric constraints and a nonlinear solver.\n\n**Workflow**\n\n1. Create a builder with `constrainedSketch()`.\n2. Add geometry — points, lines, circles, arcs — using the builder methods.\n3. Add constraints (`horizontal`, `length`, `fix`, etc.) to drive the geometry.\n4. Call `.solve()` to run the solver and get a `ConstraintSketch` (which extends `Sketch`).\n\n```ts\nconst sk = constrainedSketch();\nconst p1 = sk.point(0, 0);\nconst p2 = sk.point(50, 0);\nconst l1 = sk.line(p1, p2);\nsk.fix(p1, 0, 0);\nsk.horizontal(l1);\nsk.length(l1, 50);\nreturn sk.solve().extrude(10);\n```\n\n**Solver status**\n\n```ts\nconst result = sk.solve();\nresult.constraintMeta.status; // 'fully' | 'under' | 'over' | 'over-redundant'\nresult.constraintMeta.dof; // 0 = fully constrained\nresult.constraintMeta.maxError; // residual — should be < 1e-6\nresult.inspect(); // human-readable summary\nresult.withUpdatedConstraint('cst-5', 120); // update a dimension without rebuilding\n```\n\n**`ConstrainedSketchOptions`**\n- `strict?: boolean` — When true, adding a constraint that cannot be satisfied throws instead of silently discarding it.\n\n---\n\n## Classes\n\n### `Sketch`\n\nImmutable 2D profile for extrusion, revolve, and other operations.\n\n`Sketch` wraps Manifold's `CrossSection` with a chainable 2D API. Every method returns a new `Sketch` — the original is never mutated. Colors, edge labels, and placement data are preserved through all transforms and boolean operations.\n\nSupported operations:\n\n- **Transforms** — `translate`, `rotate`, `rotateAround`, `scale`, `mirror`\n- **Booleans** — `add` (union), `subtract` (difference), `intersect`\n- **Operations** — `offset`, `simplify`, `filletCorners`, `filletCorner`, `chamferCorners`, `chamferCorner`\n- **Queries** — `area`, `bounds`, `isEmpty`, `numVert`\n- **3D operations** — `extrude`, `revolve`, `onFace`\n- **Regions** — `regions`, `region`\n- **Placement** — `attachTo`\n\nNamed anchor positions used by `attachTo()`: `'center'` | `'top-left'` | `'top-right'` | `'bottom-left'` | `'bottom-right'` | `'top'` | `'bottom'` | `'left'` | `'right'`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `cross` | `ProfileBackend` | — |\n\n**Transforms**\n\n#### `translate(x: number, y?: number): Sketch` — Move the sketch by the given X and Y offset.\n\n#### `rotate(degrees: number): Sketch` — Rotate the sketch around its bounding-box center.\n\n#### `rotateAround(degrees: number, pivot: Vec2): Sketch` — Rotate the sketch around a specific pivot point.\n\n```ts\nrect(20, 20).rotateAround(45, [0, 0]);\n```\n\n#### `scale(v: number | Vec2): Sketch` — Scale the sketch relative to its bounding-box center.\n\nPass a single number for uniform scaling, or `[sx, sy]` for per-axis scaling.\n\n#### `scaleAround(pivot: Vec2, v: number | Vec2): Sketch` — Scale the sketch relative to an arbitrary pivot point.\n\n#### `mirror(normal: Vec2): Sketch` — Mirror the sketch across a line through its bounding-box center.\n\n`normal` is the normal vector of the mirror line (not the line direction). For example, `[1, 0]` mirrors across a vertical line (Y axis direction), and `[0, 1]` mirrors across a horizontal line.\n\n#### `mirrorThrough(point: Vec2, normal: Vec2): Sketch` — Mirror the sketch across a line defined by a point and a normal direction.\n\n**Booleans**\n\n#### `add(...others: SketchOperandInput[]): Sketch` — Add (union) one or more sketches to this sketch.\n\nAccepts individual sketches or arrays: `sketch.add(a, b)` or `sketch.add([a, b])`. For combining many sketches at once, prefer the free function `union2d()` which uses Manifold's batch operation and is faster than chaining.\n\n```ts\ncircle2d(20).add(rect(10, 30)).extrude(5);\n```\n\n#### `subtract(...others: SketchOperandInput[]): Sketch` — Subtract one or more sketches from this sketch.\n\nAccepts individual sketches or arrays: `sketch.subtract(a, b)` or `sketch.subtract([a, b])`. For subtracting many cutters at once, prefer the free function `difference2d()`.\n\n```ts\nrect(40, 40).subtract(circle2d(10)).extrude(5);\n```\n\n#### `intersect(...others: SketchOperandInput[]): Sketch` — Intersect this sketch with one or more others (keep overlapping area only).\n\nAccepts individual sketches or arrays: `sketch.intersect(a, b)` or `sketch.intersect([a, b])`. For intersecting many sketches, prefer the free function `intersection2d()`.\n\n**Features**\n\n#### `offset(delta: number, join?: \"Square\" | \"Round\" | \"Miter\"): Sketch` — Inflate (positive delta) or deflate (negative delta) the sketch contour.\n\nFor rounding corners, prefer `filletCorners(radius)` (all corners) or `filletCorner([x, y], radius)` (one corner) — they round only true corners and keep concave geometry exact.\n\n- `'Round'` — smooth arc at each corner (default)\n- `'Square'` — flat mitered extension\n- `'Miter'` — sharp pointed extension\n\n```ts\nrect(40, 20).offset(3); // expand by 3\n```\n\n#### `filletCorners(radius: number): Sketch` — Round every significant corner of this sketch with the same fillet radius.\n\nWorks on any sketch — primitives, boolean results, attached or composed profiles. A vertex counts as a corner when its turn angle is at least 30°, so vertices that belong to tessellated circles or earlier fillets are left untouched. Both convex and concave corners are rounded. Holes are preserved, and their corners are rounded too.\n\nThrows if the sketch has no significant corners, or if the radius does not fit a corner (the error names the corner and the maximum radius).\n\n```ts\nrect(60, 30).filletCorners(5).extrude(4); // rounded plate\npolygon(bracketPts).filletCorners(3); // every corner of an outline\n```\n\n#### `filletCorner(at: Vec2, radius: number): Sketch` — Round the single corner of this sketch nearest to a seed point.\n\nSelects the significant corner (turn angle ≥ 30°) closest to `at` — the seed does not need to be exact, just nearer to the intended corner than to any other (like `region([x, y])` seed selection). Throws if two corners are equidistant from the seed, naming both so you can move the seed.\n\nChain calls to round several corners with different radii.\n\n```ts\npolygon(roofPts)\n .filletCorner([45, 86], 14) // peak\n .filletCorner([24, 74], 8); // left shoulder\n```\n\n#### `chamferCorners(size: number): Sketch` — Bevel every significant corner of this sketch with a straight chamfer.\n\nReplaces each significant corner (turn angle ≥ 30°) with a straight cut set back `size` along both adjacent edges. Tessellated arcs are left untouched; holes are preserved. Throws if the sketch has no significant corners or the size does not fit a corner.\n\n```ts\nrect(60, 30).chamferCorners(4).extrude(4); // beveled plate outline\n```\n\n#### `chamferCorner(at: Vec2, size: number): Sketch` — Bevel the single corner of this sketch nearest to a seed point.\n\nSelects the significant corner (turn angle ≥ 30°) closest to `at` and replaces it with a straight cut set back `size` along both adjacent edges. Throws if two corners are equidistant from the seed. Chain calls to bevel several corners with different sizes.\n\n```ts\nrect(60, 30).chamferCorner([30, 15], 6); // bevel only the top-right corner\n```\n\n#### `regions(): Sketch[]` — Decompose this sketch into its distinct filled regions, sorted largest-first by area.\n\nA single sketch can contain several disconnected filled areas (e.g., two separate rectangles, or a ring shape with a hole). This method enumerates all top-level connected regions as independent `Sketch` objects, each with its own outer boundary and associated holes.\n\n```ts\nconst pair = union2d(rect(40, 40), rect(40, 40).translate(60, 0));\nconst [left, right] = pair.regions(); // largest first\nleft.extrude(5);\n```\n\n#### `region(seed: Vec2): Sketch` — Select the single filled region that contains the given 2D seed point.\n\nThe seed must lie strictly inside the filled area — not on a boundary edge and not inside a hole. Throws a descriptive error if the seed is outside all regions. If unsure where regions are, use `.regions()` first — each result has `.bounds()`.\n\n```ts\nconst donut = circle2d(50).subtract(circle2d(30));\ndonut.region([40, 0]).extrude(10); // seed at radius 40, inside the ring\n```\n\n**Promotion**\n\n#### `extrude(height: number, opts?: { twist?: number; divisions?: number; scaleTop?: number | Vec2; }): Shape` — Extrude this 2D sketch along Z to create a 3D solid. Supports twist and scale tapering.\n\n#### `revolve(degrees?: number, segments?: number): Shape` — Revolve this 2D sketch around the world Z axis. Sketch X is radius; sketch Y becomes world Z height. Keep the profile at X > 0 unless it intentionally touches the axis.\n\n**Placement**\n\n#### `attachTo(target: Sketch, targetAnchor: Anchor, selfAnchor?: Anchor, offset?: Vec2): Sketch` — Position this sketch relative to another using named anchor points.\n\nComputes the translation needed to align `selfAnchor` on this sketch with `targetAnchor` on the target sketch, then applies an optional pixel-exact offset.\n\nAnchor positions: `'center'` | `'top-left'` | `'top-right'` | `'bottom-left'` | `'bottom-right'` | `'top'` | `'bottom'` | `'left'` | `'right'`\n\n```ts\nconst arm = rect(4, 70).attachTo(plate, 'bottom-left', 'top-left');\nconst shifted = rect(4, 70).attachTo(plate, 'bottom-left', 'top-left', [5, 0]);\n```\n\n#### `onFace(parentOrFace: Shape | { toShape(): Shape; } | { _bbox(): { min: number[]; max: number[]; }; } | FaceRef, faceOrOpts?: \"front\" | \"back\" | \"left\" | \"right\" | \"top\" | \"bottom\" | string | FaceRef | { u?: number; v?: number; protrude?: number; selfAnchor?: Anchor; }, opts?: { u?: number; v?: number; protrude?: number; selfAnchor?: Anchor; }): Sketch` — Place this sketch on a face or planar target in 3D space.\n\nUse this when a 2D profile should be oriented onto a 3D face before extrusion or other downstream operations.\n\n`FaceRef` — defined in [core](/docs/core).\n\n**Labels**\n\n#### `labelEdge(name: string): Sketch` — Label the single boundary edge (for circles, single-loop profiles). Returns a new sketch.\n\n#### `labelEdges(...args: (string | null)[] | [ Record<string, string> ]): Sketch` — Label edges in winding order, or by named map for rect.\n\nPositional: `labelEdges('bottom', 'right', 'top', 'left')` — one per edge, `null` to skip. Named (rect only): `labelEdges({ bottom: 'floor', top: 'ceiling' })`. Returns a new sketch.\n\n#### `edgeLabels(): string[]` — List current edge label names.\n\n#### `prefixLabels(prefix: string): Sketch` — Prefix all edge labels. Returns a new sketch with prefixed labels.\n\n#### `renameLabel(from: string, to: string): Sketch` — Rename a single edge label. Returns a new sketch.\n\n#### `dropLabels(...names: string[]): Sketch` — Remove specific labels. Returns a new sketch.\n\n#### `dropAllLabels(): Sketch` — Remove all labels. Returns a new sketch.\n\n**Measurement**\n\n#### `area(): number` — Return the total filled area of the sketch.\n\n#### `bounds(): ProfileBounds` — Return the axis-aligned bounding box of the sketch.\n\n#### `isEmpty(): boolean` — Return `true` if the sketch contains no filled area.\n\n#### `numVert(): number` — Return the number of vertices in the polygon representation of the sketch contours.\n\n#### `toPolygons(): number[][][]` — Return the sketch as a list of polygons matching its contour topology.\n\nUseful when you need raw polygon data for inspection or custom export.\n\n**Other**\n\n#### `color(value: string | undefined): Sketch` — Set the display color of this sketch.\n\nColor is preserved through all transforms and boolean operations. Pass `undefined` to clear the color.\n\n```ts\ncircle2d(20).color('#ff0000').extrude(5);\n```\n\n#### `clone(): Sketch` — Create an explicit copy of this sketch for branching variants.\n\nBecause all Sketch operations are immutable, `clone()` is rarely needed. Use it when you want to assign the same sketch to multiple names and continue modifying each independently without confusion.\n\n### `ConstrainedSketchBuilder`\n\n**Drawing**\n\n#### `moveTo(x: number, y: number): this` — Move the cursor to `(x, y)` and start a new profile loop.\n\n#### `lineTo(x: number, y: number): this` — Draw a line from the current cursor to `(x, y)`.\n\n#### `lineH(dx: number): this` — Draw a horizontal line of length `dx` from the current cursor.\n\n#### `lineV(dy: number): this` — Draw a vertical line of length `dy` from the current cursor.\n\n#### `lineAngled(length: number, degrees: number): this` — Draw a line of the given `length` at `degrees` from +X.\n\n#### `arcTo(x: number, y: number, radius: number, clockwise?: boolean): this` — Draw a circular arc from the current cursor to `(x, y)` with the given radius.\n\n#### `arcByCenter(centerId: PointId, startId: PointId, endId: PointId, clockwise?: boolean, name?: string, fixedRadius?: boolean): ArcId` — Create an arc from an explicit center point and endpoint IDs.\n\n#### `bezier(p0: any, p1: any, p2: any, p3: any, name?: string): BezierId` — Create a cubic Bezier curve from four control points.\n\n#### `bezierTo(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number): this` — Draw a cubic Bezier from the current cursor to `(x3, y3)`.\n\n#### `blendTo(x: number, y: number, weight?: number): this` — Draw a smooth Bezier tangent to the previous arc.\n\n#### `label(name: string): this` — Label the current path segment.\n\n#### `close(): this` — Close the current path and register the loop.\n\n#### `addLoopCircle(center: PointId, radius: number, segments?: number): this` — Add a circle loop to the path.\n\n#### `addLoop(points: any[]): this` — Add a closed polygon loop from point IDs.\n\n#### `addProfileLoop(segments: Array<{ kind: \"line\"; line: any; } | { kind: \"arc\"; arc: any; } | { kind: \"bezier\"; bezier: any; }>): this` — Add a profile loop from prebuilt line/arc/bezier segments.\n\n**Entities**\n\n#### `point(x?: number, y?: number, fixed?: boolean): PointId` — Add a free point to the sketch at `(x, y)`.\n\nIf `x` or `y` are omitted, the point is placed at the bounding-box center of existing geometry so it starts near other entities rather than at the origin. Throws if either coordinate is `NaN` or `Infinity`.\n\n#### `pointAt(index: number): PointId` — Return the `PointId` of the point created at the given insertion index.\n\n#### `line(a: PointId, b: PointId, construction?: boolean, name?: string): LineId` — Connect two existing points with a line segment.\n\nPass `construction = true` for a helper line that participates in constraints but is excluded from the solved sketch output (not part of any profile loop).\n\n```ts\nconst axis = sk.line(sk.point(0, -50), sk.point(0, 50), true);\nsk.symmetric(p1, p2, axis);\n```\n\n#### `lineAt(index: number): LineId` — Return the `LineId` of the line created at the given insertion index.\n\n#### `circle(center: PointId, radius: number, construction?: boolean, segments?: number, name?: string): CircleId` — Add a circle to the sketch with the given center point and initial radius.\n\nThe radius is a starting value — if you add a `radius()` or `diameter()` constraint, the solver will adjust it. Non-construction circles automatically register a loop.\n\n#### `circleAt(index: number): CircleId` — Return the `CircleId` of the circle created at the given insertion index.\n\n#### `shape(lines: LineId[]): ShapeId` — Register a named shape (closed polygon) from an ordered list of line IDs.\n\nThe `ShapeId` can be passed to `shapeWidth()`, `shapeHeight()`, `shapeArea()`, `shapeCentroidX()`, `shapeCentroidY()`, and `shapeEqualCentroid()` constraints. Shape registration is done automatically by concept factories like `rect()` and `addPolygon()`.\n\n#### `group(opts?: { x?: number; y?: number; theta?: number; id?: string; }): SketchGroupBuilder` — Create a rigid-body group with a local coordinate frame.\n\nPoints and lines added to the group move together as a unit — the solver sees 3 DOF (x, y, θ) instead of 2N per point. After configuring the group, call `.done()` to register it and receive a `SketchGroupHandle`.\n\nGroup points are addressable by their `PointId` in all sketch constraints (e.g. `sk.coincident`, `sk.distance`) just like any other points.\n\n```ts\nconst g = sk.group({ x: 50, y: 30 });\nconst p0 = g.point(0, 0); // local origin → world (50, 30)\nconst p1 = g.point(100, 0); // local (100,0) → world (150, 30)\nconst l = g.line(p0, p1);\ng.fixRotation();\nconst handle = g.done();\n// p0, p1 work in constraints like any other PointId:\nsk.coincident(p0, someExternalPoint);\n```\n\n#### `rect(options?: RectOptions): ConstrainedRect` — Add an axis-aligned rectangle concept. Returns a `ConstrainedRect` handle with named vertices, sides, and center.\n\n**`RectOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `x?` | `number` | Bottom-left x coordinate. Default: 0. |\n| `y?` | `number` | Bottom-left y coordinate. Default: 0. |\n| `width?` | `number` | Width (along x). Default: 10. |\n| `height?` | `number` | Height (along y). Default: 10. |\n| `blockRotation?` | `boolean` | Prevent 180° rotation (ensures bottom edge points rightward). Default: false. |\n\n#### `addPolygon(options: PolygonOptions): ConstrainedPolygon` — Add a general polygon concept (CCW winding enforced). Returns a `ConstrainedPolygon` handle.\n\n**`PolygonOptions`**\n- `points: ReadonlyArray<readonly Vec2>` — Initial vertex coordinates. Minimum 3 points.\n- `addLoop?: boolean` — Whether to register a closed loop for sketch generation. Default: true.\n- `blockRotation?: boolean` — Prevent 180° rotation (ensures first edge maintains its initial direction). Default: false.\n\n#### `regularPolygon(options: RegularPolygonOptions): ConstrainedRegularPolygon` — Add a regular n-gon concept (equal sides, CCW winding). Returns a `ConstrainedRegularPolygon` handle with a center point.\n\n**`RegularPolygonOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `sides` | `number` | Number of sides (minimum 3). |\n| `radius?` | `number` | Circumradius — distance from center to vertex. Default: 10. |\n| `cx?` | `number` | Center x coordinate. Default: 0. |\n| `cy?` | `number` | Center y coordinate. Default: 0. |\n| `startAngle?` | `number` | Angle (in degrees) of vertex[0] measured from the +X axis (CCW positive). Default: 0 (rightmost vertex). |\n| `blockRotation?` | `boolean` | Prevent 180° rotation (ensures first edge maintains its initial direction). Default: false. |\n\n#### `groupRect(options: GroupRectOptions): ConstrainedGroupRect` — Add a rigid rectangle as a group concept. Returns a `ConstrainedGroupRect` handle with named vertices and sides. The rectangle is fixed in shape — only position (and optionally rotation) varies.\n\n**`GroupRectOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `x?` | `number` | Bottom-left x coordinate (world). Default: 0. |\n| `y?` | `number` | Bottom-left y coordinate (world). Default: 0. |\n| `width` | `number` | Width (along x in local coords). Required. |\n| `height` | `number` | Height (along y in local coords). Required. |\n| `allowRotation?` | `boolean` | Allow the solver to rotate this rectangle. Default: false. |\n\n**Geometric Constraints**\n\n#### `horizontal(line: any): this` — Constrain a line to be horizontal (parallel to the X axis).\n\n#### `vertical(line: any): this` — Constrain a line to be vertical (parallel to the Y axis).\n\n#### `parallel(a: any, b: any): this` — Constrain two lines to be parallel.\n\n#### `sameDirection(a: any, b: any): this` — Constrain two lines to point in the same direction.\n\n#### `oppositeDirection(a: any, b: any): this` — Constrain two lines to point in opposite directions.\n\n#### `perpendicular(a: any, b: any): this` — Constrain two lines to be perpendicular.\n\n#### `tangent(a: any, b: any): this` — Constrain a line/circle or circle/circle tangency relationship.\n\n#### `collinear(point: any, line: any): this` — Constrain a point to lie on the infinite extension of a line.\n\n#### `symmetric(a: any, b: any, axis: any): this` — Constrain two points to be symmetric about an axis line.\n\n#### `blockRotation(points: any[], axis?: \"x\" | \"y\"): this` — Prevent 180° rotation of a polygon by anchoring its first edge.\n\n**Dimensional Constraints**\n\n#### `distance(a: any, b: any, value: number): this` — Constrain the Euclidean distance between two points.\n\n#### `length(line: any, value: number): this` — Constrain the length of a line segment.\n\n#### `angle(a: any, b: any, value: number): this` — Constrain the signed angle from line `a` to line `b`.\n\n#### `radius(circle: any, value: number): this` — Constrain the radius of a circle.\n\n#### `diameter(circle: any, value: number): this` — Constrain the diameter of a circle.\n\n#### `hDistance(a: any, b: any, value: number): this` — Constrain the horizontal distance between two points.\n\n#### `vDistance(a: any, b: any, value: number): this` — Constrain the vertical distance between two points.\n\n#### `pointLineDistance(point: any, line: any, value: number): this` — Constrain the signed perpendicular distance from a point to a line.\n\n#### `lineDistance(a: any, b: any, value: number): this` — Constrain the perpendicular offset distance between two lines.\n\n#### `absoluteAngle(line: any, value: number): this` — Constrain the absolute angle of a line measured from +X.\n\n#### `arcLength(arc: any, value: number): this` — Constrain the arc length of an arc.\n\n#### `equalRadius(a: any, b: any): this` — Constrain two circles to have equal radii.\n\n#### `angleBetween(a: any, b: any, value: number): this` — Constrain the unsigned angle between two lines.\n\n**Coincidence & Equality**\n\n#### `equal(a: any, b: any): this` — Constrain two lines to have equal length.\n\n#### `coincident(a: any, b: any): this` — Constrain two points to coincide.\n\n#### `concentric(a: any, b: any): this` — Constrain two circles to share a center.\n\n#### `fix(point: any, x?: number, y?: number): this` — Pin a point at a specific world location.\n\n#### `midpoint(point: any, line: any): this` — Constrain a point to lie at the midpoint of a line.\n\n#### `pointOnCircle(point: any, circle: any): this` — Constrain a point to lie on the perimeter of a circle.\n\n#### `pointOnLine(point: any, line: any): this` — Constrain a point to lie on the bounded segment of a line.\n\n#### `ccw(...points: any[]): this` — Constrain all given points to be in counter-clockwise order.\n\n**Tangent Transitions**\n\n#### `lineTangentArc(line: any, arc: any, atStart: boolean): this` — Constrain a line to be tangent to an arc at its start or end point.\n\n#### `arcTangentArc(arcA: any, arcB: any, aAtStart?: boolean, bAtStart?: boolean): this` — Constrain two arcs to be tangent at their shared junction point.\n\n#### `bezierTangentArc(bezier: any, arc: any, atBezierStart: boolean, atArcStart: boolean): this` — Constrain a Bezier to be tangent to an arc at one endpoint.\n\n#### `smoothBlend(arc1: any, arc2: any, options?: { weight?: number; arc1End?: \"start\" | \"end\"; arc2End?: \"start\" | \"end\"; }): BezierId` — Create a Bezier blend between two arcs.\n\n**Shape Constraints**\n\n#### `shapeWidth(shape: any, value: number): this` — Constrain a shape's width.\n\n#### `shapeHeight(shape: any, value: number): this` — Constrain a shape's height.\n\n#### `shapeCentroidX(shape: any, value: number): this` — Constrain a shape's centroid X position.\n\n#### `shapeCentroidY(shape: any, value: number): this` — Constrain a shape's centroid Y position.\n\n#### `shapeArea(shape: any, value: number): this` — Constrain a shape's area.\n\n#### `shapeEqualCentroid(a: any, b: any): this` — Constrain two shapes to have the same centroid.\n\n**Positioning**\n\n#### `offsetX(a: any, b: any, value: number): this` — Constrain the horizontal (X-axis) offset between two lines. Uses the start-point of each line to measure horizontal distance. `value` is the signed distance: b.startPt.x − a.startPt.x = value.\n\n#### `offsetY(a: any, b: any, value: number): this` — Constrain the vertical (Y-axis) offset between two lines. Uses the start-point of each line to measure vertical distance. `value` is the signed distance: b.startPt.y − a.startPt.y = value.\n\n#### `referencePoint(x: number, y: number): PointId` — Add a fixed reference point at `(x, y)`.\n\n#### `referenceLine(x1: number, y1: number, x2: number, y2: number): LineId` — Add a fixed reference line from `(x1, y1)` to `(x2, y2)`.\n\n#### `referenceFrom(source: ConstraintSketch, entityId: string): PointId | LineId | null` — Import a single named entity from a solved sketch as fixed reference geometry.\n\n#### `referenceAllFrom(source: ConstraintSketch): { points: Map<string, PointId>; lines: Map<string, LineId>; }` — Import all non-construction entities from a solved sketch as fixed references.\n\n**Solving**\n\n#### `constrain(constraint: Omit<SketchConstraint, \"id\">): this` — Add a raw constraint object to the builder.\n\n#### `solve(options?: SolveOptions): ConstraintSketch | Sketch` — Run the constraint solver and return a solved sketch.\n\nThe returned `ConstraintSketch` extends `Sketch` and can be used directly in all 3D operations (`extrude`, `revolve`, etc.). It also exposes `constraintMeta` with the solver status:\n\n```ts\nconst result = sk.solve();\nresult.constraintMeta.status; // 'fully' | 'under' | 'over' | 'over-redundant'\nresult.constraintMeta.dof; // 0 = fully constrained\nresult.constraintMeta.maxError; // residual — should be < 1e-6\nresult.inspect(); // human-readable summary\nresult.withUpdatedConstraint('cst-5', 120); // update a dimension without rebuilding\n```\n\n**Troubleshooting**\n\n- **Under-constrained (dof > 0)** — add `fix()`, `length()`, or other dimensional constraints.\n- **Over-constrained** — conflicting constraints are auto-rejected. Check `result.constraintMeta.constraints` and `result.inspect()`.\n- **maxError > 1e-6** — solver did not converge; check for contradictory constraints.\n\n**`SolveOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `iterations?` | `number` | Maximum number of LM outer iterations per restart. |\n| `tolerance?` | `number` | Infinity-norm residual tolerance for declaring convergence. |\n| `restarts?` | `number` | Number of deterministic restart seeds used by the global solver. |\n| `warmStartIterations?` | `number` | Optional projector iterations used only for initialisation, not as the main solver. |\n| `maxScaledStep?` | `number` | Maximum LM step length in scaled variable space. Larger = bolder, smaller = safer. |\n| `skipRedundancyCheck?` | `boolean` | Skip redundancy detection (safe when topology is unchanged and previous DOF >= 0). |\n| `presolveConstraintId?` | `string` | Run the targeted presolve hook for this constraint before the main solve. |\n| `fallbackRestarts?` | `number` | When set and the first solve exceeds tolerance*5, retry with this many restarts. |\n| `progressive?` | `boolean` | Add constraints progressively with short LM solves, all in one WASM call. |\n| `timeBudgetMs?` | `number` | Wall-clock time budget in ms for the entire solve. 0 = no limit. |\n| `debugConstructiveTranscript?` | `boolean` | Capture a readable constructive transcript in `constraintMeta.debug`. |\n| `debugSvgSnapshots?` | `boolean` | Capture SVG snapshots for constructive steps in `constraintMeta.debug`. |\n\n#### `solveConstraintsOnly(options?: SolveOptions): { maxError: number; rejectedCount: number; definition: ConstraintDefinition; }` — Run the solver without building a full `ConstraintSketch`.\n\nLighter than `solve()` — skips profile and DOF analysis. Useful for lightweight constraint validation or progress monitoring mid-construction.\n\n#### `route(x: number, y: number): RouteBuilder` — Start a directional route from coordinates.\n\nReturns a [`RouteBuilder`](/docs/viewport#routebuilder) - describe the path with up/down/left/right/arcLeft/arcRight. Each method returns the entity ID (`LineId` or `ArcId`) for use in `sk.*` constraints.\n\n```js\nconst r = sk.route(0, 0);\nconst stem = r.up(18);\nr.arcLeft(8.9);\nconst neck = r.down();\nr.done();\nsk.offsetX(stem, neck, 10.8);\n```\n\n### `ConstraintSketch`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `constraintMeta` | `SketchConstraintMeta` | — |\n| `definition` | `ConstraintDefinition` | — |\n\n**Methods:**\n\n#### `detectArrangement(): Sketch[]` — Enumerate all bounded regions formed by the line arrangement of this sketch. Construction lines are excluded. Regions are returned largest-first by area.\n\n#### `detectArrangementRegion(_seed: Vec2): Sketch` — Select the single arrangement region that contains the given seed point. Throws if no region contains the seed.\n\n#### `toPolyline(samples?: number): Vec2[]` — Return the solved constrained path as a sampled 2D polyline.\n\nUse this when a construction rail was authored with `constrainedSketch()` and should feed another operation such as `Loft.pathOnXz(...)`. The sketch must contain exactly one profile path.\n\n#### `withUpdatedConstraint(constraintId: string, value: number): ConstraintSketch` — Re-solve the sketch after changing the value of one existing constraint.\n\nUse this for interactive dimension edits without rebuilding the whole sketch graph. It attempts a warm-started solve first, then falls back to a full solve if needed.\n\n#### `inspect(): string` — Return a human-readable diagnostic string of the solved state.\n\n### `SketchGroupBuilder`\n\n#### `point(lx: number, ly: number): PointId` — Add a point in local coordinates. Returns its globally-addressable PointId.\n\n#### `line(a: PointId, b: PointId, name?: string): LineId` — Connect two group points with a line. Both must be PointIds from this group.\n\n#### `fixRotation(): this` — Freeze rotation (theta). Group can still translate - 2 DOF remain.\n\n#### `fix(): this` — Freeze all 3 DOF - group is completely fixed.\n\n#### `done(): SketchGroupHandle` — Finalize and register the group with the builder.\n\n### `Point2D`\n\nAn immutable 2D point with measurement and construction helpers.\n\nUsed as construction geometry in sketches, constraints, and analytic measurements. All methods return new instances — `Point2D` is immutable.\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `x` | `number` | — |\n| `y` | `number` | — |\n\n**Methods:**\n\n#### `distanceTo(other: Point2D): number` — Measure straight-line distance to another point.\n\n#### `midpointTo(other: Point2D): Point2D` — Compute the midpoint between this point and another point.\n\n#### `translate(dx: number, dy: number): Point2D` — Return a point shifted by the given delta.\n\n#### `toTuple(): Vec2` — Convert this point to a plain `[x, y]` tuple.\n\n### `Line2D`\n\nAn immutable 2D line segment with length, angle, intersection, and parallel helpers.\n\nProvides both segment-only (`intersectSegment`) and infinite-line (`intersect`) intersection queries. All methods return new instances.\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `start` | `Point2D` | — |\n| `end` | `Point2D` | — |\n\n**Methods:**\n\n#### `get length(): number` — Length of the line segment.\n\n#### `get midpoint(): Point2D` — Midpoint of the line segment.\n\n#### `get angle(): number` — Direction angle in degrees, measured CCW from +X.\n\n#### `get direction(): Vec2` — Unit direction vector from start to end.\n\n#### `parallel(distance: number): Line2D` — Create a parallel line offset by the given distance.\n\nPositive distance shifts to the left of the line direction.\n\n#### `intersect(other: Line2D): Point2D | null` — Intersect this line with another infinite line.\n\n#### `intersectSegment(other: Line2D): Point2D | null` — Intersect this line with another as bounded segments.\n\n#### `static fromCoordinates(x1: number, y1: number, x2: number, y2: number): Line2D` — Create a line from raw coordinates.\n\n#### `static fromPointAndAngle(origin: Point2D, angleDeg: number, length: number): Line2D` — Create a line from a start point, angle, and length.\n\n#### `static fromPointAndDirection(origin: Point2D, dir: Vec2, length: number): Line2D` — Create a line from a start point, direction vector, and length.\n\n### `Circle2D`\n\nAn immutable 2D circle with area, circumference, and extrusion support.\n\nExtruding a `Circle2D` produces a cylinder with named `top`, `bottom`, and `side` faces accessible via the topology API.\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `center` | `Point2D` | — |\n| `radius` | `number` | — |\n\n**Methods:**\n\n#### `get diameter(): number` — Diameter of the circle.\n\n#### `get circumference(): number` — Circumference of the circle.\n\n#### `get area(): number` — Area of the circle.\n\n#### `pointAtAngle(angleDeg: number): Point2D` — Return a point on the circle at the given angle.\n\n#### `translate(dx: number, dy: number): Circle2D` — Return a translated circle.\n\n#### `toSketch(segments?: number): Sketch` — Convert this circle to a sketch profile.\n\n#### `extrude(height: number, segments?: number): Shape` — Extrude the circle into a solid cylinder.\n\n#### `static fromCenterAndRadius(center: Point2D, radius: number): Circle2D` — Create a circle from its center and radius.\n\n#### `static fromDiameter(center: Point2D, diameter: number): Circle2D` — Create a circle from its center and diameter.\n\n### `Rectangle2D`\n\nA rectangle with named sides, vertices, and extrusion support.\n\nSides are named based on the rectangle's local orientation at construction time. Vertices go: bottom-left, bottom-right, top-right, top-left (CCW).\n\nUse `rect()` for the normal centered sketch primitive. Use `Rectangle2D` when you need named sides/vertices, or an extrusion with tracked vertical edges such as `vert-br` for `filletTrackedEdge()` / `chamferTrackedEdge()`.\n\nExtruding a `Rectangle2D` produces a [`Shape`](/docs/core#shape) with named faces: `top`, `bottom`, `side-left`, `side-right`, `side-top`, `side-bottom`. These are accessible via the topology API (`.face()`, `.edge()`).\n\n```ts\nconst r = Rectangle2D.fromDimensions(0, 0, 100, 60);\nr.side('top'); r.side('left'); // Line2D\nr.vertex('top-left'); // Point2D\nr.width; r.height; r.center;\nconst [d1, d2] = r.diagonals(); // [bl-tr, br-tl]\n\nr.toSketch(); // Sketch (for 2D operations)\nr.extrude(20); // Shape with named faces\n\nRectangle2D.fromCenterAndDimensions(new Point2D(50, 30), 100, 60);\nRectangle2D.from2Corners(new Point2D(0, 0), new Point2D(100, 60));\nRectangle2D.from3Points(p1, p2, p3); // free-angle rectangle\n```\n\n#### `get width(): number` — Width of the rectangle.\n\n#### `get height(): number` — Height of the rectangle.\n\n#### `get center(): Point2D` — Geometric center of the rectangle.\n\n#### `side(name: RectSide): Line2D` — Return a named side of the rectangle.\n\n#### `sideAt(index: number): Line2D` — Return a side by index.\n\n#### `vertex(name: RectVertex): Point2D` — Return a named vertex of the rectangle.\n\n#### `diagonals(): [ Line2D, Line2D ]` — Return the two diagonals of the rectangle.\n\n#### `toSketch(): Sketch` — Convert the rectangle to a sketch profile.\n\n#### `translate(dx: number, dy: number): Rectangle2D` — Return a translated rectangle.\n\n#### `static fromDimensions(x: number, y: number, width: number, height: number): Rectangle2D` — Create an axis-aligned rectangle from origin corner plus width and height.\n\n#### `static fromCenterAndDimensions(center: Point2D, width: number, height: number): Rectangle2D` — Create a rectangle centered on a point.\n\n#### `static from2Corners(p1: Point2D, p2: Point2D): Rectangle2D` — Create an axis-aligned rectangle from two opposite corners.\n\n#### `static from3Points(p1: Point2D, p2: Point2D, p3: Point2D): Rectangle2D` — Create a free-angle rectangle from three points.\n\n`p1` and `p2` define one edge, and `p3` chooses the perpendicular side.\n\n#### `extrude(height: number, up?: boolean): Shape` — Extrude the rectangle into a solid prism with named topology.\n\n---\n\n<!-- guides/surface-members.md -->\n\n# Surface Members: When To Route Through Carrier + SurfaceBody\n\nSurface members model physical material that follows a carrier surface: bottle-cage arms, grip inlays, brace ribs, prop guards, helmet vents. Full API, parameter rules, and worked examples: [../generated/curves.md](../generated/curves.md).\n\n## When to use\n\nRoute through this layer when the model has: a carrier surface (cylinder, plane, or `ProductSkin`); paths or bands in carrier-local coordinates; member-local features (slots, cutouts, lips, cups, ribs, section thickness/edge radius); or explicit joins between named members. Not for plain boxes, simple extrusions, sheet-metal bends, exact machined faces, or free-floating connector-positioned assemblies.\n\n## Mental model\n\n- `Carrier` owns surface coordinates and frames. `SurfaceBody(name)` owns named members — `band()` or `plate()` — and the joins between them.\n- Features attach to a member's local coordinate system before lowering. This is not a global boolean recipe.\n- Cylinder paths take degrees and handle seam wrapping — never compute angles with trig.\n- A ProductSkin path stays on one side. For multi-side detail, split into one member per side and join them at named transition anchors; `sideTransition` / `sideTransitionChain` / `sideRoute` generate the matching side-local endpoints.\n- `Product.ribbon()` stays the simple path for a one-side conformal ribbon. Upgrade to `Carrier.productSkin(skin)` + `SurfaceBody` when the detail needs member-local features, repeated ribs, explicit joins, or mirrored members.\n\n## Verification loop\n\n- `build()` returns the member geometry. `buildWithDiagnostics()` adds a serializable member graph + IR; `buildDebug()` adds visible debug markers (anchors, join radii, centerlines, frame axes) alongside the normal shapes.\n- Every diagnostic carries a stable `code` field (e.g. `region.centerOutOfBounds`). Repair loops must match on `code`, never on English prose. Clipped or crossing regions and invalid joins are reported as diagnostics, never silently accepted as valid geometry.\n- Only a limited join set lowers to real geometry: close endpoint pairs, selected named-anchor pairs, sampled landing pads, and unambiguous shared endpoints via `autoJoinAtSharedAnchors()`. Farther, missing-anchor, or ambiguous joins remain diagnostic-only intent — decompose the design into supported joins instead of expecting a fallback.\n\n---\n\n<!-- generated/curves.md -->\n\n# Curves & Surfacing\n\nSmooth curves, lofted surfaces, swept solids, splines, and high-level product skins.\n\n## Contents\n\n- [Curves & Surfacing](#curves-surfacing)\n- [Surface Members](#surface-members)\n- [Curve3D](#curve3d)\n- [Route3D](#route3d)\n- [NurbsCurve3D](#nurbscurve3d)\n- [NurbsSurface](#nurbssurface)\n- [PathBuilder](#pathbuilder) — Line Segments, Arcs, Curves, Closing & Output\n- [ProductSkin](#productskin)\n- [ProductSurfaceRef](#productsurfaceref)\n- [ProductSurfaceBuilder](#productsurfacebuilder)\n- [ProductSkinBuilder](#productskinbuilder)\n- [ProductStationBuilder](#productstationbuilder)\n- [ProductPanelBuilder](#productpanelbuilder)\n- [ProductRibbonBuilder](#productribbonbuilder)\n- [CylinderCarrier](#cylindercarrier)\n- [PlaneCarrier](#planecarrier)\n- [ProductSkinCarrier](#productskincarrier)\n- [SurfacePath](#surfacepath)\n- [SurfacePathBuilder](#surfacepathbuilder)\n- [SurfaceBand](#surfaceband)\n- [SurfaceBodyBuilder](#surfacebodybuilder)\n- [SurfaceMemberBuilder](#surfacememberbuilder)\n- [SurfaceJoinBuilder](#surfacejoinbuilder)\n- [CounterboreBuilder](#counterborebuilder)\n- [RoundedSlotBuilder](#roundedslotbuilder)\n- [Curve](#curve)\n- [Surface](#surface)\n- [Blend](#blend)\n- [Analysis](#analysis)\n- [Product](#product)\n- [Carrier](#carrier)\n- [SurfaceMembers](#surfacemembers)\n\n## Functions\n\n### Curves & Surfacing\n\n#### `Curve.Blend(start: CurveBlendEndpoint, end: CurveBlendEndpoint): NurbsCurve3D` — Create an exact G1 blend curve between two directed endpoints.\n\nThe returned curve is a cubic non-rational `NurbsCurve3D`: ForgeCAD converts the endpoint positions and tangents into Bezier control points, so the curve can feed `sweep` and exact surface boundaries through the existing `nurbs` IR rather than a sampled polyline.\n\n```js\nconst rail = Curve.Blend(\n { point: [0, 0, 0], tangent: [1, 0, 0], weight: 0.8 },\n { point: [40, 20, 8], tangent: [0, 1, 0], weight: 0.8 },\n);\nconst tube = sweep(circle2d(2), rail);\n```\n\n**`CurveBlendEndpoint`**\n- `point: Vec3` — Endpoint position.\n- `tangent: Vec3` — Tangent direction at this endpoint. Magnitude is ignored.\n- `weight?: number` — Tangent reach relative to the endpoint chord length. Default 1.\n\n#### `Curve.BlendG2(start: CurveBlendG2Endpoint, end: CurveBlendG2Endpoint): NurbsCurve3D` — Create an exact G2 blend curve between two directed endpoints.\n\nThis is the curvature-aware companion to `Curve.Blend()`. It returns a degree-5 non-rational `NurbsCurve3D` that matches endpoint position, tangent direction, and optional curvature/second-derivative vectors.\n\n```js\nconst rail = Curve.BlendG2(\n { point: [0, 0, 0], tangent: [1, 0, 0], curvature: [0, 0.02, 0] },\n { point: [50, 20, 0], tangent: [0, 1, 0], curvature: [-0.02, 0, 0] },\n);\n```\n\n**`CurveBlendG2Endpoint`** extends CurveBlendEndpoint\n- `curvature?: Vec3` — Optional endpoint curvature/second-derivative vector. Default is zero.\n\n#### `Curve.Arc(options: CurveArcOptions): NurbsCurve3D` — Create an exact circular 3D arc from start, end, and start tangent.\n\nThe returned curve is a rational quadratic `NurbsCurve3D`, split into stable spans when needed, so it can feed `sweep` without sampling the authoring intent away.\n\n```js\nconst rail = Curve.Arc({\n start: [40, 0, 0],\n end: [0, 40, 0],\n tangent: [0, 1, 0],\n});\nconst tube = sweep(circle2d(2), rail);\n```\n\n**`CurveArcOptions`**\n- `start: Vec3` — Arc start point.\n- `end: Vec3` — Arc end point.\n- `tangent: Vec3` — Tangent direction at the start point. Magnitude is ignored.\n\n#### `Curve.Line(start: Vec3, end: Vec3): NurbsCurve3D` — Create an exact straight 3D NURBS line segment.\n\n```js\nconst rail = Curve.Line([0, 0, 0], [80, 0, 15]);\nconst rib = sweep(circle2d(2), rail);\n```\n\n#### `Curve.Nurbs(points: Vec3[], options?: NurbsCurve3DOptions): NurbsCurve3D` — Create an exact NURBS 3D curve from control points, weights, knots, and degree.\n\n```js\nconst rail = Curve.Nurbs([[0, 0, 0], [30, 4, 12], [60, -4, 12], [90, 0, 0]]);\nconst tube = sweep(circle2d(2), rail);\n```\n\n**`NurbsCurve3DOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `degree?` | `number` | Polynomial degree (default 3 = cubic). Must be ≥ 1. |\n| `weights?` | `number[]` | Rational weights, one per control point (default: all 1.0 = non-rational). |\n| `knots?` | `number[]` | Knot vector (default: uniform clamped). Must have length = controlPoints.length + degree + 1. |\n| `closed?` | `boolean` | Whether the curve is closed/periodic (default false). |\n\n#### `Curve.Fit(points: Vec3[], options?: CurveFitOptions): NurbsCurve3D` — Fit a non-rational NURBS curve that interpolates every input point.\n\nThis is global B-spline interpolation, not approximate curve reduction: ForgeCAD computes chord-length parameters, averaged clamped knots, solves the control points, then verifies the interpolation residual against `tolerance`. With `{ closed: true }` the fit is standard periodic B-spline interpolation: the curve loops smoothly from the last point back to the first (do not repeat the first point at the end).\n\n```js\nconst rail = Curve.Fit(\n [[0, 0, 0], [20, 8, 12], [50, -4, 18], [80, 0, 0]],\n { degree: 3, tolerance: 0.001 },\n);\nconst tube = sweep(circle2d(2), rail);\n\n// Closed loop through four points — no duplicated closing point\nconst loop = Curve.Fit(\n [[30, 0, 0], [0, 30, 0], [-30, 0, 0], [0, -30, 0]],\n { closed: true },\n);\n```\n\n**`CurveFitOptions`**\n- `degree?: number` — Polynomial degree. Default is cubic, reduced automatically for short point lists.\n- `tolerance?: number` — Maximum allowed interpolation residual in model units. Default 1e-7.\n- `closed?: boolean` — Interpolate a closed periodic loop through the points. The loop closes from the last point back to the first automatically — do not repeat the first point at the end.\n\n#### `Curve.Trim<T extends CurveTrimInput>(curve: T, start: number, end: number): CurveTrimOutput<T>` — Extract an exact curve segment from normalized parameter `start` to `end`.\n\n`NurbsCurve3D` inputs are trimmed with exact knot insertion/subdomain extraction. Polyline point arrays are trimmed by arclength over their exact line segments. Sampled `Curve3D` splines are rejected until ForgeCAD has a tolerance-controlled rebuild path.\n\n#### `Curve.Reverse<T extends CurveTrimInput>(curve: T): CurveTrimOutput<T>` — Reverse an exact curve without changing its geometry.\n\n`NurbsCurve3D` inputs reverse control points, weights, and knots. Polyline point arrays are cloned and reversed. Sampled `Curve3D` splines are rejected until ForgeCAD has a tolerance-controlled rebuild path.\n\n#### `Curve.Route: typeof Route3D` — Build analytic 3D line/arc routes for sweeps.\n\n`Curve.Route.fromPolyline()` is the canonical route API. It returns a `Route3D` value object, preserving exact route segments, named port frames, and the lowerable `route3d` sweep compile plan.\n\n```js\nconst route = Curve.Route.fromPolyline(\n [[0, 0, 0], [0, 0, 50], [40, 0, 50]],\n { cornerRadius: 12, startPort: 'inlet', endPort: 'outlet' },\n);\nconst tube = sweep(circle2d(4), route);\n```\n\n#### `Curve.Helix: { path(options: HelixOptions): CurveHelixPath; coil: CurveHelixCoil; }` — Build helical paths and swept coils.\n\n`Curve.Helix` is the canonical namespace for helical paths and coils. It uses the same sweep-based lowering as other curve paths.\n\n```js\nconst guide = Curve.Helix.path({ radius: 20, pitch: 6, turns: 4 });\nconst spring = Curve.Helix.coil({ radius: 20, pitch: 6, turns: 4, wireRadius: 1 });\n```\n\n**`HelixOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `radius` | `number` | Radius from the central Z axis to the helix centerline. |\n| `pitch?` | `number` | Axial distance per full turn. Provide any two of `pitch`, `turns`, and `height`. |\n| `turns?` | `number` | Number of full rotations around the axis. Provide any two of `pitch`, `turns`, and `height`. |\n| `height?` | `number` | Total height along +Z. Provide any two of `pitch`, `turns`, and `height`. |\n| `startAngle?` | `number` | Start angle in degrees. Default 0 starts on +X. |\n| `clockwise?` | `boolean` | Reverse winding direction when viewed from +Z. |\n| `samplesPerTurn?` | `number` | Point samples per turn for the metadata path. Default 32. |\n\n`CurveHelixPath`: `{ radius: number, pitch: number, turns: number, height: number, startAngle: number, clockwise: boolean }`\n\n#### `Loft.station(profile: Sketch, position: number): LoftStation` — Create a loft station from a 2D profile and an axis position.\n\n`LoftStation`: `{ profile: Sketch, position: number }`\n\n#### `Loft.field(profiles: Sketch[], heights: number[], options?: FieldLoftOptions): Shape` — Loft by interpolating signed-distance fields instead of matching vertices.\n\nUse this path when profiles change character, such as round shafts blending into flat, cruciform, or lobed tips. It is Manifold-only, mesh-based, and slower than stitched lofting, but it avoids profile-point correspondence artifacts because it blends profile fields instead of boundary vertices.\n\n**`LoftOptions`**\n- `edgeLength?: number` — Marching-grid edge length for level-set meshing. Smaller = finer.\n- `boundsPadding?: number` — Optional extra bounds padding.\n\n**`FieldLoftOptions`** extends LoftOptions\n- `simplify?: boolean | \"safe\"` — Simplification control after field extraction. Default is topology-safe simplification.\n- `maxTriangles?: number` — Hard post-extraction triangle budget. Must be a positive integer. If safe simplification cannot reach it, the build fails.\n\n#### `Loft.leftRail(path: LoftGuideRailPath): LoftGuideRail` — Create a guide rail that constrains the section-local negative-X side.\n\n`LoftGuideRail`: `{ side: LoftGuideRailSide, path: LoftGuideRailPath }`\n\n#### `Loft.rightRail(path: LoftGuideRailPath): LoftGuideRail` — Create a guide rail that constrains the section-local positive-X side.\n\n#### `Loft.frontRail(path: LoftGuideRailPath): LoftGuideRail` — Create a guide rail that constrains the section-local positive-Y side.\n\n#### `Loft.backRail(path: LoftGuideRailPath): LoftGuideRail` — Create a guide rail that constrains the section-local negative-Y side.\n\n#### `Loft.centerRail(path: LoftGuideRailPath): LoftGuideRail` — Create a guide rail that moves section centers along the loft.\n\n#### `Loft.pathOnXz(path: LoftPath2D, y?: number): Vec3[]` — Place a 2D guide path onto the XZ plane.\n\nThe path's first coordinate becomes X and its second coordinate becomes Z. Use this for left/right silhouette rails authored with [`path()`](/docs/sketch#path) or [`constrainedSketch()`](/docs/sketch#constrainedsketch).\n\n#### `Loft.pathOnYz(path: LoftPath2D, x?: number): Vec3[]` — Place a 2D guide path onto the YZ plane.\n\nThe path's first coordinate becomes Y and its second coordinate becomes Z. Use this for front/back crown rails authored with [`path()`](/docs/sketch#path) or [`constrainedSketch()`](/docs/sketch#constrainedsketch).\n\n#### `Loft.pathOnXy(path: LoftPath2D, z?: number): Vec3[]` — Place a 2D guide path onto the XY plane.\n\nThe path's first coordinate becomes X and its second coordinate becomes Y. Use this when lofting along X or Y and a rail lives in a horizontal sketch plane.\n\n#### `Loft.withGuideRails(stations: LoftStation[], rails: LoftGuideRail[], options?: LoftWithGuideRailsOptions): Shape` — Loft through profile stations while forcing generated sections to follow guide rails.\n\nStations define the cross-section family. Guide rails define the side or center paths the loft must pass through. With opposite side rails, the section is scaled to touch both rails. With one side rail, the section keeps its interpolated size unless a center rail is also present.\n\n**`LoftWithGuideRailsOptions`** extends LoftOptions\n- `axis?: LoftAxis` — Primary station axis. Default Z.\n- `samples?: number` — Number of generated loft stations including ends. Default scales with station count.\n- `railSamples?: number` — Number of points sampled from curve-backed rails before axis interpolation. Default 64.\n\n#### `spline2d(points: Vec2[], options?: Spline2DOptions): Sketch` — Build a smooth Catmull-Rom spline sketch from 2D control points.\n\nA closed spline (default) returns a filled profile. An open spline requires a strokeWidth option to produce a solid sketch. Use tension (0..1, default 0.5) to control curve tightness.\n\n**`Spline2DOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `closed?` | `boolean` | Closed loop (default true). |\n| `tension?` | `number` | Catmull-Rom tension in [0, 1]. 0 = very round, 1 = linear-ish. Default 0.5. |\n| `samplesPerSegment?` | `number` | Samples per segment (minimum 3). Default 16. |\n| `strokeWidth?` | `number` | For open splines, provide stroke width to return a solid Sketch. If omitted for open splines, an error is thrown. |\n| `join?` | `\"Round\" \\| \"Square\"` | Stroke join for open splines. Default 'Round'. |\n\n#### `loft(profiles: Sketch[], heights: number[], options?: LoftOptions): Shape` — Loft between multiple sketches along Z stations.\n\nProfiles can differ in topology and vertex count: interpolation is done on signed-distance fields and meshed with level-set extraction. Heights must be strictly increasing. Compatible loft stacks can also stay on the maintained export-backend path.\n\nThe surface is smooth through 3+ stations (C1 spanwise interpolation, like CAD lofts), so it can bow slightly past the straight ruling between stations; sections are matched exactly at their stations. Two-station lofts are ruled. `edgeLength` caps the sample spacing in curved or twisted regions (quality presets scale it); straight regions keep input density.\n\nPerformance note: loft is significantly heavier than primitive/extrude/revolve. If the part is axis-symmetric (bottles, vases, knobs), prefer revolve().\n\n#### `sweep(profile: Sketch, path: SweepPathInput, options?: SweepOptions): Shape`\n\n**`SweepOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `samples?` | `number` | Number of samples when path is a Curve3D. Default 48. |\n| `edgeLength?` | `number` | Marching-grid edge length for level-set meshing. Smaller = finer. |\n| `boundsPadding?` | `number` | Optional extra bounds padding. |\n| `up?` | `Vec3` | Preferred \"up\" vector for local profile frame. Auto fallback is used near parallel segments. |\n\n#### `variableSweep(spine: SweepPathInput, sections: VariableSweepSection[], options?: VariableSweepOptions): Shape` — Sweep a variable cross-section along a 3D spine curve.\n\nUnlike sweep(), which uses a single constant profile, variableSweep() interpolates between multiple profiles at different stations along the spine. This enables organic shapes like tapering tubes, bone-like structures, and sculptural forms.\n\nEach section specifies a t parameter (0 = start, 1 = end of spine) and a 2D profile sketch. The SDF-based level-set mesher smoothly blends between profiles at intermediate positions.\n\nPerformance note: like sweep(), this uses level-set meshing internally.\n\n**`VariableSweepSection`**\n- `t: number` — Parameter along the spine (0 = start, 1 = end).\n- `profile: Sketch` — Cross-section profile at this station.\n\n**`VariableSweepOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `samples?` | `number` | Number of samples when spine is a Curve3D. Default 48. |\n| `edgeLength?` | `number` | Marching-grid edge length for level-set meshing. Smaller = finer. |\n| `boundsPadding?` | `number` | Optional extra bounds padding. |\n| `up?` | `Vec3` | Preferred \"up\" vector for local profile frame. Auto fallback is used near parallel segments. |\n\n### Surface Members\n\n#### `surfaceBand<C extends SurfaceCoordinate>(path: SurfacePath<C> | SurfacePathBuilder<C>, width: WidthProfile, cap?: SurfaceBandCap): SurfaceBand<C>`\n\n#### `SurfaceBody(name: string): SurfaceBodyBuilder` — Start a surface-member body builder for straps, inlays, guards, braces, cuffs, and similar physical members that live on a carrier surface.\n\n```js\nconst carrier = Carrier.cylinder('guard-envelope').diameter(84).height(36).clearance(2);\nconst guard = SurfaceBody('simple-guard')\n .carrier(carrier)\n .member('left-strut')\n .band()\n .path(carrier.path().from({ angle: -132, z: 6 }).to({ angle: -58, z: 18 }))\n .section({ width: 5.5, thickness: 2.8, edgeRadius: 0.6 })\n .member('right-strut')\n .mirrorOf('left-strut')\n .member('front-hoop')\n .band()\n .path(carrier.path().around({ z: 18, fromAngle: -58, toAngle: 58 }))\n .section({ width: 6.2, thickness: 3, edgeRadius: 0.7 })\n .join('left-strut', 'front-hoop').blend({ radius: 3.2 })\n .join('right-strut', 'front-hoop').blend({ radius: 3.2 })\n .build();\n```\n\n---\n\n## Classes\n\n### `Curve3D`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `points` | `Vec3[]` | — |\n| `closed` | `boolean` | — |\n| `tension` | `number` | — |\n\n**Methods:**\n\n#### `sampleBySegment(samplesPerSegment?: number): Vec3[]` — Sample the curve with a fixed number of points per segment.\n\n#### `sample(count?: number): Vec3[]` — Sample the curve to an approximate total point count.\n\n#### `pointAt(t: number): Vec3` — Return the position on the curve at normalized parameter `t` in `[0, 1]`. O(1), no allocations.\n\n#### `tangentAt(t: number): Vec3` — Return a unit tangent vector at normalized parameter `t` in `[0, 1]`. O(1), analytical derivative.\n\n#### `length(samples?: number): number` — Approximate the curve length by polyline sampling.\n\n### `Route3D`\n\nMetadata-bearing analytic 3D route made from line and arc segments.\n\nUse `Curve.Route.fromPolyline()` when you know the virtual design skeleton points and bend radius. ForgeCAD computes tangent trim points, bend arcs, total length, and named start/end port frames. Pass the route directly to `sweep()`.\n\n```js\nconst route = Curve.Route.fromPolyline(\n [[0, 0, 0], [0, 0, 80], [60, 0, 80]],\n { cornerRadius: 24, startPort: \"inlet\", endPort: \"outlet\" },\n);\nconst pipe = sweep(difference2d(circle2d(8), circle2d(6)), route);\nconst outlet = route.port(\"outlet\");\n```\n\n#### `static fromPolyline(points: Route3DVec3[], options?: Route3DFromPolylineOptions): Route3D` — Build a line/arc route from virtual polyline corner points.\n\n**`Route3DFromPolylineOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `cornerRadius?` | `number` | Bend radius applied to every virtual interior corner. Default 0 keeps sharp polyline corners. |\n| `startPort?` | `string` | Name for the start port. Default \"start\". |\n| `endPort?` | `string` | Name for the end port. Default \"end\". |\n| `up?` | `Vec3` | Preferred up vector for deterministic port frames. Default [0, 0, 1]. |\n\n#### `get length(): number` — Total centerline length, including line and bend arc segments.\n\n#### `get segments(): Route3DSegment[]` — Exact line and arc segments that make up this route.\n\n#### `get ports(): Record<string, RoutePortFrame>` — Named port frames, keyed by port name.\n\n#### `port(name: string): RoutePortFrame` — Return one named route port frame.\n\n#### `toSweepPathPlan(): SweepPathCompilePlan` — Convert this route to the compile plan consumed by sweep().\n\n#### `toPolyline(options?: number | Route3DToPolylineOptions): Route3DVec3[]` — Sample this analytic route as a polyline for inspection or backend lowering.\n\n**`Route3DToPolylineOptions`**\n- `samples?: number` — Approximate target point count for the full route.\n- `maxAngleDeg?: number` — Maximum angular spacing on arc segments. Default 6 degrees.\n\n### `NurbsCurve3D`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `controlPoints` | `Vec3[]` | — |\n| `weights` | `number[]` | — |\n| `knots` | `number[]` | — |\n| `degree` | `number` | — |\n| `closed` | `boolean` | — |\n\n**Methods:**\n\n#### `pointAt(t: number): Vec3` — Evaluate the curve at parameter t ∈ [0, 1]. Uses De Boor's algorithm — exact, O(degree²).\n\n#### `tangentAt(t: number): Vec3` — Evaluate the unit tangent vector at parameter t ∈ [0, 1].\n\n#### `sample(count?: number): Vec3[]` — Sample the curve uniformly at `count` points.\n\n#### `sampleAdaptive(minCount?: number, maxCount?: number): Vec3[]` — Sample with adaptive density — more points in high-curvature regions.\n\n#### `length(samples?: number): number` — Approximate arc length by summing polyline segment lengths.\n\n#### `toPolyline(samples?: number): Vec3[]` — Convert to a format compatible with sweep() path input.\n\n### `NurbsSurface`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `controlGrid` | `Vec3[][]` | — |\n| `weightsGrid` | `number[][]` | — |\n| `knotsU` | `number[]` | — |\n| `knotsV` | `number[]` | — |\n| `degreeU` | `number` | — |\n| `degreeV` | `number` | — |\n| `nU` | `number` | — |\n| `nV` | `number` | — |\n| `domain` | `SurfaceDomainCompilePlan` | — |\n\n**Methods:**\n\n#### `pointAt(u: number, v: number): Vec3` — Evaluate the surface at parameters (u, v) ∈ [0, 1]². Uses tensor product evaluation: evaluate basis functions in U and V independently.\n\n#### `normalAt(u: number, v: number): Vec3` — Evaluate the surface normal at (u, v) via cross product of partial derivatives.\n\n#### `tessellate(resU?: number, resV?: number): { positions: Vec3[]; normals: Vec3[]; indices: number[]; }` — Tessellate the surface into a triangle mesh. Returns positions, normals, and triangle indices.\n\n### `PathBuilder`\n\n**Line Segments**\n\n#### `moveTo(x: number, y: number): this` — Move the cursor to an absolute position without drawing a segment.\n\nWhen called after the initial [`path()`](/docs/sketch#path), this establishes the start of the outline. Calling `moveTo` again mid-path starts a new sub-path (hole in `close()`, separate segment for [`stroke()`](/docs/sketch#stroke)).\n\n#### `lineTo(x: number, y: number): this` — Draw a straight line from the current cursor to an absolute position.\n\n#### `lineH(dx: number): this` — Draw a horizontal line segment by `dx` units from the current cursor.\n\nPositive `dx` moves right; negative moves left.\n\n#### `lineV(dy: number): this` — Draw a vertical line segment by `dy` units from the current cursor.\n\nPositive `dy` moves up; negative moves down.\n\n#### `lineAngled(length: number, degrees: number): this` — Draw a line at the given angle and length from the current cursor.\n\nAngle convention: `0°` points right (+X), `90°` points up (+Y).\n\n```ts\n// L-bracket with angled return\npath().moveTo(0, 0).lineH(50).lineV(-70).lineAngled(20, 235).stroke(4);\n```\n\n**Arcs**\n\n#### `arc(cx: number, cy: number, radius: number, startDeg: number, endDeg: number): this` — Draw an arc defined by center, radius, and angle range (no trig needed). If the path has no segments yet, automatically moves to the arc start. Positive sweep (startDeg < endDeg) = CCW, negative = CW.\n\n```js\n// Arc centered at (10, 0), radius 50, from -30° to +30°\npath().arc(10, 0, 50, -30, 30).stroke(8, 'Round')\n```\n\n#### `arcTo(x: number, y: number, radius: number, clockwise?: boolean): this` — Draw a circular arc from the current position to (x, y) with the given radius. `clockwise=true` → arc curves to the right of the start→end direction. `clockwise=false` → arc curves to the left of the start→end direction.\n\n#### `tangentArcTo(x: number, y: number): this` — G1-continuous arc — radius derived from current tangent + endpoint. Throws if endpoint is collinear with current direction.\n\n**Curves**\n\n#### `bezierTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): this` — Cubic bezier from current position to (x, y) via two control points.\n\n**Closing & Output**\n\n#### `close(): Sketch` — Close the path and return a filled [`Sketch`](/docs/sketch#sketch).\n\nThe winding of the polygon is automatically corrected to CCW (the expected orientation for ForgeCAD sketches). If the path contains multiple sub-paths (started with subsequent `moveTo` calls), the first sub-path is the outer contour and subsequent sub-paths become holes subtracted from it.\n\nEdge labels (assigned with `.label('name')`) are transferred to the resulting sketch and propagate through `extrude()`, `revolve()`, `loft()`, and `sweep()` into named faces on the resulting [`Shape`](/docs/core#shape).\n\n```ts\nconst triangle = path().moveTo(0, 0).lineH(50).lineV(30).close();\n\n// With a hole (second sub-path)\nconst frame = path()\n .moveTo(0, 0).lineH(40).lineV(30).lineH(-40).close(); // outer\n // (hole would be added with another moveTo and line sequence before close)\n```\n\n#### `closeLabel(name: string): Sketch` — Label the closing segment and close the path. Shorthand for labeling the implicit line from the last point back to the start, then closing.\n\n#### `stroke(width: number, join?: \"Round\" | \"Square\"): Sketch` — Thicken an open polyline (centerline) into a solid filled profile with uniform width.\n\nExpands the path into a closed profile `width` units wide (half-width on each side of the centerline). Use `'Round'` for ribs, wire traces, and organic profiles — it adds semicircular endcaps and rounds joins. Use `'Square'` (default) for sharp miter joins without endcaps.\n\nNot the same as rounding corners of a closed polygon — for mixed sharp-and-rounded outlines, build the polygon first and apply `.filletCorner([x, y], radius)` per corner.\n\n```ts\n// Square-join L-bracket\nconst bracket = path().moveTo(0, 0).lineH(50).lineV(-70).lineAngled(20, 235).stroke(4);\n\n// Round-join rib\nconst rib = path().moveTo(0, 0).lineH(60).stroke(6, 'Round');\n\n// Equivalent standalone form\nconst wire = stroke([[0, 0], [50, 0], [50, -70]], 4);\n```\n\nand semicircular endcaps.\n\n#### `label(name: string): this` — Label the most recently added segment. Labels are born here and grow into face names when the sketch is extruded, lofted, swept, or revolved.\n\nLabels must be unique within a path. Each segment can have at most one label.\n\n**Other**\n\n#### `getX(): number` — Current cursor X position.\n\n#### `getY(): number` — Current cursor Y position.\n\n#### `lineBy(dx: number, dy: number): this` — Draw a line by a relative `(dx, dy)` displacement from the current cursor.\n\n#### `arcBy(dx: number, dy: number, radius: number, clockwise?: boolean): this` — Draw an arc to a point offset from the current cursor.\n\n#### `bezierBy(dcp1x: number, dcp1y: number, dcp2x: number, dcp2y: number, dx: number, dy: number): this` — Draw a cubic Bezier using control points relative to the current cursor.\n\n#### `arcAround(cx: number, cy: number, sweepDeg: number): this` — Arc around a known center point, sweeping by the given angle. Radius is derived from the distance between the current position and the center. Positive sweep = CCW (math convention), negative = CW.\n\n```js\n// Arc 90° CCW around (50, 50)\npath().moveTo(70, 50).arcAround(50, 50, 90)\n// Arc 45° CW around the origin\npath().moveTo(10, 0).arcAround(0, 0, -45)\n```\n\n#### `arcAroundRelative(dx: number, dy: number, sweepDeg: number): this` — Arc around a center point given as an offset from the current position. `(dx, dy)` is the vector from the current point to the center. Positive sweep = CCW (math convention), negative = CW.\n\n```js\n// Arc 90° CCW around a center 20 units to the right\npath().moveTo(50, 50).arcAroundRelative(20, 0, 90)\n// Equivalent to: path().moveTo(50, 50).arcAround(70, 50, 90)\n```\n\n#### `smoothCapTo(endX: number, endY: number, cornerRadius: number, capRadius: number): this` — Smooth three-arc end cap from the current position to (endX, endY). Inserts: small corner arc → large cap arc → small corner arc, all G1-continuous.\n\n#### `tangentBezierTo(cp2x: number, cp2y: number, x: number, y: number, weight?: number): this` — G1-continuous cubic bezier — first control point is auto-derived from the current tangent direction. `weight` controls how far the auto-placed control point extends along the tangent (default: 1/3 of the chord).\n\nThe second control point `(cp2x, cp2y)` must be provided — it controls the arrival curvature. For a fully automatic smooth curve, see `smoothThrough`.\n\n#### `smoothThrough(waypoints: Vec2[], tension?: number): this` — Catmull-Rom spline through a list of waypoints from the current position. The current position is included as the first point. The last waypoint becomes the new cursor position.\n\n#### `nurbsTo(controlPoints: Vec2[], opts?: { weights?: number[]; degree?: number; }): this` — Rational B-spline edge to (x, y) with explicit control points and weights.\n\nThe control points define the B-spline shape between the current position and (x, y). The current position is NOT included in `controlPoints` — it is automatically prepended. The endpoint (x, y) is the last control point.\n\n#### `exactArcTo(x: number, y: number, opts?: { radius?: number; clockwise?: boolean; }): this` — Exact circular arc to (x, y) using a rational quadratic NURBS.\n\nUnlike `arcTo()` which tessellates to a polyline, this preserves the exact arc definition. When extruded through the OCCT backend, it produces a true cylindrical face — not a faceted approximation.\n\n#### `fillet(radius: number): this` — Round the last corner (the junction between the previous two segments) with a tangent arc of the given radius.\n\nMust be called after at least two line/arc segments that form a corner. The fillet trims back both segments and inserts a tangent arc.\n\n```js\npath().moveTo(0,0).lineTo(10,0).lineTo(10,10).fillet(2).lineTo(0,10).close()\n```\n\n#### `chamfer(distance: number): this` — Chamfer the last corner with a straight cut of the given distance.\n\n```js\npath().moveTo(0,0).lineTo(10,0).lineTo(10,10).chamfer(2).lineTo(0,10).close()\n```\n\n#### `mirror(axis: \"x\" | \"y\" | Vec2): this` — Mirror all existing segments across an axis and append the mirrored copy in reverse order, creating a symmetric path. The axis passes through the current cursor position.\n\n'y' mirrors across the local Y-axis (flips X), or `[nx, ny]` for an arbitrary axis direction.\n\n```js\n// Build right half, mirror to get full symmetric profile\npath().moveTo(0,0).lineTo(10,0).lineTo(10,5).mirror('x').close()\n```\n\n#### `toPolyline(): Vec2[]` — Return the open path as a sampled 2D polyline.\n\nThis is for construction geometry such as guide rails, measured centerlines, and curve-driven helpers where the authored path should stay open instead of becoming a filled sketch or stroked profile.\n\n```ts\nconst rail = path()\n .moveTo(24, 0)\n .bezierTo(32, 44, 28, 92, 18, 120)\n .toPolyline();\n```\n\n#### `closeOffset(delta: number, join?: \"Round\" | \"Square\" | \"Miter\"): Sketch` — Close the path and return an offset version of the filled Sketch. Positive delta expands outward, negative shrinks inward.\n\n### `ProductSkin`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `name` | `string` | — |\n| `shape` | `Shape` | — |\n| `axis` | `ProductSkinAxis` | — |\n| `stations` | `ProductStationSpec[]` | — |\n| `rails` | `Record<string, ProductRailSpec>` | — |\n\n**Methods:**\n\n#### `toShape(): Shape` — Return the renderable shape generated for this product skin.\n\n#### `with(...children: GroupInput[]): ShapeGroup` — Create a group containing this skin plus named child details.\n\n#### `integrate(...details: Shape[]): Shape` — Boolean-union structural details into the skin body.\n\n#### `uv(side: ProductSkinSide, u?: number, v?: number): ProductSkinRefQuery` — Create a side/u/v surface-ref query on this skin.\n\n**`ProductSkinSide`** — Semantic side of a ProductSkin. `back` is accepted as an alias for `rear`.\n\n`\"left\" | \"right\" | \"top\" | \"bottom\" | \"front\" | \"rear\" | \"back\"`\n\n**`ProductSkinRefQuery`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `side` | `ProductSkinSide` | Side of the product skin. `front` is the minimum axis cap, `rear`/`back` is the maximum axis cap. |\n| `u?` | `number` | Across-side parameter for side refs. Defaults to 0.5. |\n| `v?` | `number` | Along-axis parameter, 0 at the first cap and 1 at the rear/back cap. Defaults to 0.5. |\n| `offset?` | `number` | Positive distance away from the surface along the resolved normal. |\n\n#### `ref(name: string): ProductSurfaceRef` — Resolve a named ref published with Product.skin().refs(...).\n\n#### `curveOnSurface(name: string, points: Array<Partial<ProductSkinRefQuery> & { side: ProductSkinSide; }>): ProductSurfaceRef[]` — Create a sampled curve as a sequence of surface refs on this skin.\n\n#### `surface(side: ProductSkinSide): ProductSurfaceBuilder` — Create a fluent surface helper for refs and conformal features on one side of this skin.\n\nUse this when several refs or ribbons share the same skin side; side-local helpers keep path points concise and make it harder to mix sides accidentally.\n\n#### `stationAt(vOrAxis: number): { ... }` — Interpolate center, width, and depth at a normalized v or absolute axis value.\n\n**`ProductProfileKind`**\n\n`\"oval\" | \"roundedRect\" | \"circle\" | \"superEllipse\" | \"custom\"`\n\n#### `frame(query: ProductSkinRefQuery): ProductSurfaceFrame` — Build a local surface frame from a side/u/v query.\n\n`ProductSurfaceFrame`: `{ point: Vec3, normal: Vec3, tangentU: Vec3, tangentV: Vec3, matrix: Mat4, skin: string }`\n\n### `ProductSurfaceRef`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `name` | `string \\| undefined` | — |\n\n**Methods:**\n\n#### `frame(overrides?: Partial<ProductSkinRefQuery>): ProductSurfaceFrame` — Resolve this semantic surface ref into a point, normal, tangents, and placement matrix.\n\n#### `with(overrides: Partial<ProductSkinRefQuery>): ProductSurfaceRef` — Return a copy of this ref with side/u/v/offset overrides.\n\n#### `attach(detail: Shape | ShapeGroup, options?: ProductAttachOptions): Shape | ShapeGroup` — Place a detail shape or group on this ref's local surface frame.\n\n`ProductAttachOptions`: `{ offset?: number, inset?: number }`\n\n#### `querySpec(): ProductSkinRefQuery` — Return the serializable side/u/v query behind this ref.\n\n### `ProductSurfaceBuilder`\n\nFluent helper bound to one ProductSkin side for refs and side-local conformal features.\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `side` | `ProductSkinSide` | — |\n\n**Methods:**\n\n#### `ref(u?: number, v?: number, offset?: number): ProductSurfaceRef` — Create a ref on this skin side.\n\n#### `uv(u?: number, v?: number, offset?: number): ProductSkinRefQuery` — Create a side/u/v query on this skin side.\n\n#### `frame(query?: Partial<ProductSkinRefQuery>): ProductSurfaceFrame` — Resolve a point/frame on this surface using the builder's side.\n\n#### `ribbon(name: string, points: ProductSurfacePathPoint[], options?: ProductRibbonBuildOptions): ProductRibbonBuilder` — Start a conformal ribbon on this skin side.\n\nPath points use side-local `u`/`v` coordinates; this builder supplies the side. The returned ProductRibbonBuilder is already bound to the source skin and can be further configured before build(). Use `widthSamples` >= 3 when the ribbon must visibly wrap over curved product sections instead of behaving like a flat strip.\n\n**`ProductSurfacePathPoint`** — Side-local path point for Product.surface(side).ribbon(...); the surface helper supplies `side`.\n- `u?: number` — Across-side parameter on the bound side. Defaults to 0.5.\n- `v?: number` — Along-axis parameter, 0 at the first cap and 1 at the rear/back cap. Defaults to 0.5.\n- `offset?: number` — Positive distance away from the surface along the resolved normal.\n\n**`ProductRibbonBuildOptions`** — Options shared by Product.ribbon() builders and Product.surface(...).ribbon(...).\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `width?` | `number` | Width across the surface in millimeters. |\n| `thickness?` | `number` | Solid thickness outward from the source surface in millimeters. |\n| `offset?` | `number` | Positive clearance between the source surface and the ribbon's inner face. |\n| `samples?` | `number` | Samples along the ribbon path. Higher values bend more smoothly. |\n| `widthSamples?` | `number` | Samples across the ribbon width. Use 3+ to visibly wrap over curved cross-sections. |\n| `resolution?` | `number` | Tessellation resolution passed to the lowered NURBS surface. |\n| `material?` | `ProductMaterial` | Apply a product material preset to the ribbon. |\n| `color?` | `string` | Apply a simple color override. |\n\n`ProductMaterial`: `{ color?: string, material?: ShapeMaterialProps }`\n\n`ShapeMaterialProps` — defined in [core](/docs/core).\n\n### `ProductSkinBuilder`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `name` | `string` | — |\n\n**Methods:**\n\n#### `axis(axis: ProductSkinAxis): this` — Choose the primary station axis for the skin loft.\n\n**`ProductSkinAxis`** — Primary world axis used to order ProductSkin loft stations.\n\n`\"X\" | \"Y\" | \"Z\"`\n\n#### `stations(stations: Array<ProductStationBuilder | ProductStationSpec>): this` — Set named cross-section stations for the product skin.\n\n`ProductStationSpec`: `{ name: string, center: Vec3, profile: ProductStationProfile, crown?: number }`\n\n`ProductStationProfile`: `{ sketch: Sketch, width: number, depth: number, kind: ProductProfileKind, radius?: number, exponent?: number }`\n\n#### `rails(rails: Record<string, ProductRailSpec>): this` — Attach named guide rails for product-skin construction and downstream surface references.\n\n`ProductRailSpec`: `{ kind: ProductRailKind, points: Vec3[], degree?: number, name?: string }`\n\n**`ProductRailKind`**\n\n`\"bezier\" | \"nurbs\" | \"polyline\"`\n\n#### `ref(name: string, query: ProductSkinRefQuery): this` — Publish a named semantic surface ref on the skin.\n\n#### `refs(refs: Record<string, ProductSkinRefQuery>): this` — Publish multiple named semantic surface refs on the skin.\n\n#### `uv(side: ProductSkinSide, u?: number, v?: number): ProductSkinRefQuery` — Create a side/u/v surface-ref query for use in refs(...) or Product.ref(...).\n\n#### `material(material: ProductMaterial): this` — Apply a product material preset to the lowered skin.\n\n#### `color(color: string): this` — Apply a simple color override to the lowered skin.\n\n#### `edgeLength(value: number): this` — Set the sampled loft target edge length.\n\n#### `wall(thickness: number): this` — Record intended wall thickness for product design metadata. Use explicit shelling when the model needs real inner-wall geometry.\n\n#### `build(): ProductSkin` — Lower stations and refs into a ProductSkin body.\n\n### `ProductStationBuilder`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `name` | `string` | — |\n\n**Methods:**\n\n#### `at(point: Vec3): this` — Position this station in world coordinates.\n\n#### `z(z: number): this` — Convenience for traditional Z-up section stacks.\n\n#### `y(y: number): this` — Convenience for product bodies running front-to-back along Y.\n\n#### `x(x: number): this` — Convenience for product bodies running left-to-right along X.\n\n#### `oval(width: number, depth: number, options?: { segments?: number; }): this` — Use an oval cross-section with full width and depth dimensions.\n\n#### `superEllipse(width: number, depth: number, options?: ProductStationSuperEllipseOptions): this` — Use a superellipse cross-section for soft-square product surfaces.\n\n`ProductStationSuperEllipseOptions`: `{ segments?: number, exponent?: number }`\n\n#### `roundedRect(width: number, depth: number, radius: number): this` — Use a rounded-rectangle cross-section with the given corner radius.\n\n#### `circle(diameter: number, options?: { segments?: number; }): this` — Use a circular cross-section from a full diameter.\n\n#### `custom(sketch: Sketch, width: number, depth: number): this` — Use a custom 2D sketch as the station cross-section.\n\n#### `crown(amount: number): this` — Set the station crown amount for soft product-section intent.\n\n#### `toSpec(): ProductStationSpec` — Return the immutable station spec consumed by Product.skin().\n\n### `ProductPanelBuilder`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `name` | `string` | — |\n\n**Methods:**\n\n#### `rounded(width: number, height: number, radius?: number): this` — Use a rounded rectangle panel profile.\n\n#### `oval(width: number, height: number): this` — Use an oval panel profile.\n\n#### `profile(profile: Sketch): this` — Use a custom 2D panel profile.\n\n#### `thickness(thickness: number): this` — Set panel extrusion thickness.\n\n#### `material(material: ProductMaterial): this` — Apply a product material preset to the panel.\n\n#### `color(color: string): this` — Apply a simple color override to the panel.\n\n#### `build(): Shape` — Build the panel in local coordinates.\n\n#### `attachTo(ref: ProductRefInput, options?: ProductPanelAttachOptions): Shape` — Build and attach this panel to a ProductSurfaceRef.\n\n**`ProductRefInput`**\n\n`ProductSurfaceRef`\n\n`ProductPanelAttachOptions`: `{ at?: Partial<ProductSkinRefQuery>, thickness?: number, material?: ProductMaterial, color?: string }`\n\n### `ProductRibbonBuilder`\n\nBuilder for thin trim, label, grip, and split-line features that bend with a ProductSkin surface.\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `name` | `string` | — |\n\n**Methods:**\n\n#### `on(skin: ProductSkin, points: ProductRibbonPathPoint[], options?: ProductRibbonBuildOptions): this` — Follow a ProductSkin with side/u/v path queries or refs.\n\nThis is the highest-fidelity mode because every interpolated sample is resolved through ProductSkin.frame(), so the ribbon bends along the selected side as station width/depth changes. All query path points must stay on one side; split side transitions into separate ribbons.\n\n**`ProductRibbonPathPoint`** — Path point for Product.ribbon().on(...): either a side/u/v query or a resolved surface ref.\n\n`ProductSkinRefQuery | ProductSurfaceRef`\n\n#### `fromRefs(points: ProductSurfaceRef[], options?: ProductRibbonBuildOptions): this` — Follow explicit surface refs.\n\nUseful for named refs or paths assembled elsewhere. The builder resolves each ref frame and interpolates between those frames; use on(skin, points) when you need full skin-side sampling between sparse control points.\n\n#### `width(width: number): this` — Set ribbon width in millimeters.\n\n#### `thickness(thickness: number): this` — Set solid thickness outward from the source surface in millimeters.\n\n#### `offset(offset: number): this` — Set positive clearance between the source surface and the ribbon's inner face.\n\n#### `samples(samples: number): this` — Set samples along the path.\n\n#### `widthSamples(samples: number): this` — Set samples across the width. Use 3+ to bend over curved cross-sections.\n\n#### `resolution(resolution: number): this` — Set NURBS tessellation resolution.\n\n#### `material(material: ProductMaterial): this` — Apply a product material preset.\n\n#### `color(color: string): this` — Apply a simple color override.\n\n#### `build(options?: ProductRibbonBuildOptions): Shape` — Build a conformal ribbon as a thin NURBS surface solid.\n\n### `CylinderCarrier`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `name` | `string` | — |\n| `kind` | `\"cylinder\"` | — |\n\n**Methods:**\n\n- `diameter(value: number): this`\n- `radius(value: number): this`\n- `height(value: number): this`\n- `clearance(value: number): this`\n- `center(point: Vec3): this`\n- `path(): SurfacePathBuilder<CylinderSurfaceCoordinate>`\n- `anchor(angle: number, z?: number, options?: { offset?: number; }): SurfaceAnchor<CylinderSurfaceCoordinate>`\n- `front(options?: { z?: number; offset?: number; }): SurfaceAnchor<CylinderSurfaceCoordinate>`\n- `back(options?: { z?: number; offset?: number; }): SurfaceAnchor<CylinderSurfaceCoordinate>`\n- `left(options?: { z?: number; offset?: number; }): SurfaceAnchor<CylinderSurfaceCoordinate>`\n- `right(options?: { z?: number; offset?: number; }): SurfaceAnchor<CylinderSurfaceCoordinate>`\n- `top(options?: { angle?: number; offset?: number; }): SurfaceAnchor<CylinderSurfaceCoordinate>`\n- `bottom(options?: { angle?: number; offset?: number; }): SurfaceAnchor<CylinderSurfaceCoordinate>`\n- `pointAt(coordinate: CylinderSurfaceCoordinate): Vec3`\n- `mirrorPoint(point: Vec3): Vec3`\n- `normalAt(coordinate: CylinderSurfaceCoordinate): Vec3`\n- `tangentAt(coordinate: CylinderSurfaceCoordinate, tangentHint?: Vec3): Vec3`\n- `frameAt(coordinate: CylinderSurfaceCoordinate, tangentHint?: Vec3): SurfaceFrame`\n- `bounds(): SurfaceBounds`\n- `offset(distance: number): CylinderCarrier`\n- `mirrorCoordinate(coordinate: CylinderSurfaceCoordinate): CylinderSurfaceCoordinate`\n- `radiusValueWithClearance(): number`\n\n`CylinderSurfaceCoordinate`: `{ kind?: \"cylinder\", angle: number, z: number, offset?: number }`\n\n### `PlaneCarrier`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `name` | `string` | — |\n| `kind` | `\"plane\"` | — |\n\n**Methods:**\n\n- `size(width: number, height: number): this`\n- `origin(point: Vec3): this`\n- `normal(normal: Vec3): this`\n- `path(): SurfacePathBuilder<PlaneSurfaceCoordinate>`\n- `anchor(x?: number, y?: number, options?: { offset?: number; }): SurfaceAnchor<PlaneSurfaceCoordinate>`\n- `left(options?: { y?: number; offset?: number; }): SurfaceAnchor<PlaneSurfaceCoordinate>`\n- `right(options?: { y?: number; offset?: number; }): SurfaceAnchor<PlaneSurfaceCoordinate>`\n- `top(options?: { x?: number; offset?: number; }): SurfaceAnchor<PlaneSurfaceCoordinate>`\n- `bottom(options?: { x?: number; offset?: number; }): SurfaceAnchor<PlaneSurfaceCoordinate>`\n- `pointAt(coordinate: PlaneSurfaceCoordinate): Vec3`\n- `mirrorPoint(point: Vec3): Vec3`\n- `normalAt(): Vec3`\n- `tangentAt(coordinate: PlaneSurfaceCoordinate, tangentHint?: Vec3): Vec3`\n- `frameAt(coordinate: PlaneSurfaceCoordinate, tangentHint?: Vec3): SurfaceFrame`\n- `bounds(): SurfaceBounds`\n- `offset(distance: number): PlaneCarrier`\n- `mirrorCoordinate(coordinate: PlaneSurfaceCoordinate): PlaneSurfaceCoordinate`\n\n`PlaneSurfaceCoordinate`: `{ kind?: \"plane\", x: number, y: number, offset?: number }`\n\n### `ProductSkinCarrier`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `skin` | `ProductSkin` | — |\n| `name` | `string` | — |\n| `kind` | `\"productSkin\"` | — |\n\n**Methods:**\n\n#### `sideTransition(fromSide: ProductSkinSide, toSide: ProductSkinSide, input?: ProductSkinSideTransitionInput): ProductSkinSideTransition` — Return matching side-local coordinates for an explicit split-member transition.\n\nEach SurfacePath still stays on one ProductSkin side. Use this helper to create one member ending on `from`, another starting on `to`, then join named anchors.\n\nRules: only adjacent `left`/`top`/`right`/`bottom` sides are supported — for front/rear caps use `Product.panel()`. `v` is normalized 0–1 along the shared boundary (default 0.5); `name` must be non-empty when provided; `offset` lifts both coordinates off the surface. Throws if the returned boundary coordinates are not physically coincident — check side order, `v`, and `offset`.\n\n`ProductSkinSideTransitionInput`: `{ name?: string, v?: number, offset?: number }`\n\n`ProductSkinSideTransition`: `{ name?: string, from: ProductSkinSurfaceCoordinate, to: ProductSkinSurfaceCoordinate }`\n\n`ProductSkinSurfaceCoordinate`: `{ kind?: \"productSkin\", side?: ProductSkinSide, u?: number, v?: number, offset?: number }`\n\n#### `sideTransitionChain(sides: ProductSkinSide[], input?: ProductSkinSideTransitionInput): ProductSkinSideTransition[]` — Return a sequence of matching side-local coordinates for an explicit multi-side split-member route.\n\nEach adjacent side pair becomes one named transition. Build one member per side segment, add transition anchors at each returned pair, then join the anchors. The same validation as `sideTransition()` applies to every adjacent pair.\n\n#### `sideRoute(input: ProductSkinSideRouteInput): ProductSkinSideRoute` — Return side-local member segments for a generated multi-side split-member route.\n\nThe route still compiles as explicit members plus named-anchor joins. This helper only generates the per-side segment endpoints and transition names.\n\n**`ProductSkinSideRouteInput`**: `name?: string`, `sides: ProductSkinSide[]`, `from: ProductSkinSurfaceCoordinate`, `to: ProductSkinSurfaceCoordinate`, `v?: number`, `offset?: number`\n\n`ProductSkinSideRoute`: `{ name?: string, transitions: ProductSkinSideTransition[], segments: ProductSkinSideRouteSegment[] }`\n\n**`ProductSkinSideRouteSegment`**: `name: string`, `side: ProductSkinSide`, `from: ProductSkinSurfaceCoordinate`, `to: ProductSkinSurfaceCoordinate`, `startAnchorName?: string`, `endAnchorName?: string`\n\n- `surface(side: ProductSkinSide): ProductSkinCarrier`\n- `path(): SurfacePathBuilder<ProductSkinSurfaceCoordinate>`\n- `pointAt(coordinate: ProductSkinSurfaceCoordinate): Vec3`\n- `mirrorPoint(point: Vec3): Vec3`\n- `normalAt(coordinate: ProductSkinSurfaceCoordinate): Vec3`\n- `tangentAt(coordinate: ProductSkinSurfaceCoordinate, tangentHint?: Vec3): Vec3`\n- `frameAt(coordinate: ProductSkinSurfaceCoordinate, tangentHint?: Vec3): SurfaceFrame`\n- `bounds(): SurfaceBounds`\n- `offset(distance: number): ProductSkinCarrier`\n- `mirrorCoordinate(coordinate: ProductSkinSurfaceCoordinate): ProductSkinSurfaceCoordinate`\n\n### `SurfacePath`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `carrier` | `CarrierSurface<C>` | — |\n| `points` | `C[]` | — |\n| `closedValue` | `boolean` | — |\n\n**Methods:**\n\n- `closed(): SurfacePath<C>`\n- `mirror(): SurfacePath<C>`\n- `coordinateAt(t: number): C`\n- `sample(count?: number): SurfacePathSample<C>[]`\n- `length(samples?: number): number`\n\n### `SurfacePathBuilder`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `carrier` | `CarrierSurface<C>` | — |\n\n**Methods:**\n\n- `from(coordinate: C): this`\n- `through(coordinate: C): this`\n- `to(coordinate: C): this`\n- `around(input: { z: number; fromAngle: number; toAngle: number; offset?: number; }): this`\n- `closed(): this`\n- `mirror(): SurfacePath<C>`\n- `build(): SurfacePath<C>`\n- `sample(count?: number): SurfacePathSample<C>[]`\n\n### `SurfaceBand`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `centerPath` | `SurfacePath<C>` | — |\n| `widthProfile` | `WidthProfile` | — |\n| `capStyle` | `SurfaceBandCap` | — |\n\n**Methods:**\n\n#### `withHole(name: string, input: SurfaceBandHoleInput): SurfaceBand<C>` — Return a new band with a named member-local rounded-slot hole region recorded as inspectable intent.\n\n`SurfaceBandHoleInput`: `{ length: number, width: number, along?: number, across?: number }`\n\n#### `holes(): SurfaceBandHoleRegion[]` — Resolve recorded hole regions into member-local across/along loops.\n\n- `widthAt(t: number): number`\n- `boundaries(samples?: number): SurfaceBandBoundarySample[]`\n\n### `SurfaceBodyBuilder`\n\nBuilder for a named surface-member body. Owns named members — `band()` or `plate()` — and the joins between them. Features (slots, cutouts, lips, cups, ribs) attach to a member's local coordinate system before lowering; this is not a global boolean recipe.\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `name` | `string` | — |\n\n**Methods:**\n\n#### `join(from: string, to: string | string[]): SurfaceJoinBuilder` — Declare a join between named members. Only a limited join set lowers to real geometry: close endpoint pairs, selected named-anchor pairs (`.betweenAnchors()`), and sampled band/plate landing pads. Farther, missing-anchor, or ambiguous joins remain diagnostic-only intent — decompose the design into supported joins instead of expecting a fallback.\n\n#### `autoJoinAtSharedAnchors(): this` — Lower only unambiguous shared-endpoint pairs (exactly two members sharing a point) into junction geometry. Shared points with more than two members produce a warning diagnostic — declare explicit `.join(...)` relationships instead.\n\n#### `build(): Shape | ShapeGroup` — Build and return only the member + junction geometry. Use `buildWithDiagnostics()` for the member graph and diagnostic codes.\n\n- `carrier(carrier: CarrierSurface): this`\n- `member(name: string): SurfaceMemberBuilder`\n\n`CarrierSurface`: `{ name: string, kind: SurfaceCarrierKind }`\n\n### `SurfaceMemberBuilder`\n\n#### `anchorAt(name: string, coordinate: C | SurfaceAnchor<C>): this` — Add a named anchor at a carrier surface coordinate for explicit member joins.\n\n`SurfaceAnchor`: `{ carrier: CarrierSurface<C>, coordinate: C }`\n\n- `plate(): this`\n- `band(): this`\n- `at(anchor: SurfaceAnchor<C>): this`\n- `size(width: number, height: number): this`\n- `path(path: SurfacePath<C> | SurfacePathBuilder<C>): this`\n- `section(section: MemberSectionInput): this`\n- `cap(style: SurfaceBandCap): this`\n- `slot(name: string, feature: MemberFeature | RoundedSlotBuilder): this`\n- `cutout(name: string, feature: MemberFeature | RoundedSlotBuilder): this`\n- `counterbore(name: string, feature: MemberFeature | CounterboreBuilder): this`\n- `features(features: MemberFeature | MemberFeature[]): this`\n- `profile(name: string, options?: { depth?: number; height?: number; }): this`\n- `mirrorOf(memberName: string): SurfaceBodyBuilder`\n- `member(name: string): SurfaceMemberBuilder`\n- `join(from: string, to: string | string[]): SurfaceJoinBuilder`\n- `autoJoinAtSharedAnchors(): SurfaceBodyBuilder`\n- `build(): Shape | ShapeGroup`\n\n**`MemberSectionInput`**: `width?: number`, `thickness: number`, `edgeRadius?: number`, `direction?: MemberOutwardDirection`, `material?: ProductMaterial`, `stations?: MemberSectionStation[]`\n\n`MemberSectionStation`: `{ t: number, width?: number, thickness?: number }`\n\n**`MemberFeature`**: `type: MemberFeatureType`, `name?: string`, `length?: number`, `width?: number`, `diameter?: number`, `counterboreDiameter?: number`, `clearanceDiameter?: number`, `height?: number`, `depth?: number`, `count?: number`, `along?: number`, `across?: number`, `verticalTravel?: number`\n\n### `SurfaceJoinBuilder`\n\n#### `betweenAnchors(fromAnchor: string, toAnchor: string): this` — Select named anchors on the source and target members before lowering this join.\n\n- `blend(input?: { radius?: number; style?: string; priority?: number; continuity?: string; }): SurfaceBodyBuilder`\n\n### `CounterboreBuilder`\n\n- `at(input: { along?: number; across?: number; z?: number; }): this`\n- `named(name: string): MemberFeature`\n- `toFeature(name?: string): MemberFeature`\n\n### `RoundedSlotBuilder`\n\n- `verticalTravel(value: number): this`\n- `at(input: { along?: number; across?: number; z?: number; }): this`\n- `named(name: string): MemberFeature`\n- `toFeature(name?: string): MemberFeature`\n\n---\n\n## Constants\n\n### `Curve`\n\nCanonical exact/smooth 3D curve constructors.\n\n`Curve.*` is the public home for reference curves and route centerlines that feed `sweep`, `variableSweep`, route visualization, and future path consumers. Standalone 3D curve constructors have been collapsed into this namespace.\n\nMembers (full entries under [Curves & Surfacing](#curves-surfacing)): `Curve.Blend`, `Curve.BlendG2`, `Curve.Arc`, `Curve.Line`, `Curve.Nurbs`, `Curve.Fit`, `Curve.Trim`, `Curve.Reverse`, `Curve.Route`, `Curve.Helix`.\n\n### `Surface`\n\n- `Plane(options: SurfacePlaneOptions): Shape` — Create a finite analytic plane sheet that can be trimmed, sewn, thickened, or used as a low-level face.\n- `Cylinder(options: SurfaceCylinderOptions): Shape` — Create a finite analytic cylindrical sheet, optionally bounded by start/end angles.\n- `Cone(options: SurfaceConeOptions): Shape` — Create a finite analytic conical or frustum sheet, optionally bounded by start/end angles.\n- `Sphere(options: SurfaceSphereOptions): Shape` — Create a finite analytic spherical sheet bounded by longitude and latitude ranges.\n- `Torus(options: SurfaceTorusOptions): Shape` — Create a finite analytic torus sheet bounded by major and tube angle ranges.\n- `Nurbs(controlGrid: Vec3[][], options?: NurbsSurfaceOptions): Shape` — Create an exact NURBS surface from a grid of control points.\n\n The control grid is indexed as `controlGrid[u][v]` — each row is a curve in the V direction, and columns trace curves in the U direction. With default options this builds a bicubic non-rational B-spline sheet with uniform clamped knots; `NurbsSurfaceOptions` controls degrees, weights, knots, trim loops, tessellation, domain, and an optional `thickness` to return a thin solid instead of an open sheet.\n\n ```js\n // Simple 4×4 control grid — a gently curved surface\n const grid = [\n [[0,0,0], [10,0,2], [20,0,2], [30,0,0]],\n [[0,10,1], [10,10,5], [20,10,5], [30,10,1]],\n [[0,20,1], [10,20,5], [20,20,5], [30,20,1]],\n [[0,30,0], [10,30,2], [20,30,2], [30,30,0]],\n ];\n const sheet = Surface.Nurbs(grid);\n const panel = Surface.Nurbs(grid, { thickness: 2 });\n ```\n- `Ruled(curveA: ExactCurveInput, curveB: ExactCurveInput, options?: SurfaceCommonOptions): Shape`\n- `Patch(curves: { bottom: ExactCurveInput; top: ExactCurveInput; left: ExactCurveInput; right: ExactCurveInput; }, options?: SurfacePatchOptions): Shape` — Create a smooth open surface sheet from 4 boundary curves (Coons patch).\n\n The four curves form the boundary of a quadrilateral patch and should meet at corners (small gaps are tolerated). Boundaries are exact by default: pass `NurbsCurve3D` values or `Shape.edge()` refs, or set `{ approximate: true }` to accept sampled `Curve3D`/`Vec3[]` boundaries. The result is an open sheet — call `.thicken(t)` for a thin solid.\n\n ```js\n const sheet = Surface.Patch({ bottom, top, left, right });\n const panel = Surface.Patch({ bottom, top, left, right }).thicken(1.5);\n ```\n- `Boundary(input: SurfaceBoundaryInput): Shape`\n- `Fill(input: SurfaceFillInput): Shape`\n- `Sew(shapes: Shape[], options?: { tolerance?: number; }): Shape`\n- `Solid(input: Shape | Shape[], options?: SurfaceSolidOptions): Shape` — Sew surface faces or consume an existing sewn shell and make a solid B-rep.\n- `Extend(shape: Shape, options: SurfaceExtendOptions): Shape`\n- `Trim(shape: Shape, tool: Shape | SurfacePlaneOp): Shape`\n- `Split(shape: Shape, tool: Shape | SurfacePlaneOp): [ Shape, Shape ]`\n- `Match(shape: Shape, options: { edge: \"u0\" | \"u1\" | \"v0\" | \"v1\"; target: EdgeRef; continuity?: SurfaceContinuity; }): Shape`\n\n### `Blend`\n\n- `Edge(options: BlendEdgeOptions): Shape`\n- `Surface(options: BlendSurfaceOptions): Shape`\n\n### `Analysis`\n\n- `EdgeContinuity(shape: Shape, options?: EdgeContinuityThresholds): EdgeContinuityReport`\n- `SurfaceContinuity(shape: Shape, options?: EdgeContinuityThresholds): EdgeContinuityReport`\n- `CurvatureComb(input: NurbsCurve3D | EdgeRef, options?: { samples?: number; }): CurvatureSample[]`\n- `SurfaceHealth(shape: Shape, options?: { tinyEdgeThreshold?: number; sliverThreshold?: number; }): SurfaceHealthReport`\n- `BRepValidity(shape: Shape, options?: BRepValidityOptions): BRepValidityReport` — Validate B-rep/shell/solid structure and return closedness, manifoldness, orientation, and issue diagnostics.\n\n### `Product`\n\n- `skin(name: string): ProductSkinBuilder` — Start a named product skin builder.\n- `station(name: string): ProductStationBuilder` — Start a named cross-section station for Product.skin(...).stations(...).\n- `rail: { ... }` — Namespaced rail builders for product skin guide rails and handle spines.\n- `profiles: { ... }` — Product profile helper namespace: oval, superEllipse, roundedRect, and circle — for stations, panels, trims, and openings.\n- `materials: { ... }` — Namespaced product material presets for molded plastic, rubber, metal, and transparent parts.\n- `applyMaterial(shape: Shape, preset: ProductMaterial | undefined): Shape` — Apply a product material preset to a Shape.\n- `scenePreset(name: ProductScenePreset): void` — Apply an opinionated scene preset for product review renders.\n- `profileSize(sketch: Sketch): { width: number; depth: number; }` — Measure the width and depth of a 2D profile sketch.\n- `describeProfile(sketch: Sketch, kind?: ProductProfileKind, radius?: number): ProductProfileDescriptor` — Describe a custom sketch as a product profile.\n- `scaleProfileTo(sketch: Sketch, width: number, depth: number): Sketch` — Scale an existing profile sketch to a target width/depth.\n- `ref(skin: ProductSkin, query: ProductSkinRefQuery): ProductSurfaceRef` — Create an ad-hoc ProductSurfaceRef from a skin and side/u/v query.\n- `surface(skin: ProductSkin, side: ProductSkinSide): ProductSurfaceBuilder` — Create a fluent surface helper for refs and conformal features on one side of a skin.\n\n Equivalent to skin.surface(side), useful when writing in Product.* namespace style.\n- `panel(name: string): ProductPanelBuilder` — Start a panel feature builder.\n- `ribbon(name: string): ProductRibbonBuilder` — Start a conformal ribbon/trim builder for details that should bend with a ProductSkin.\n\n Call .on(skin, points) for side/u/v sampling or .fromRefs(points) for explicit surface refs, then configure width, thickness, offset, sampling, material, and color before build().\n- `place(detail: Shape | ShapeGroup, ref: ProductRefInput, options?: ProductAttachOptions): Shape | ShapeGroup` — Place a shape or group on a ProductSurfaceRef.\n- `landing(name: string, radius?: number, material?: ProductMaterial): Shape` — Small blended landing volume for manual structural bridges and connection proofs.\n\n### `Carrier`\n\nFactory for carrier surfaces — the coordinate-and-frame owners that surface members (`SurfaceBody`) live on.\n\nA carrier owns surface-local coordinates and 3D frames; members and paths are authored in carrier coordinates, never in raw Cartesian math. Cylinder coordinates are `{ angle, z }` with `angle` in degrees — paths handle seam wrapping, so never compute positions with trig. `clearance()`/`offset()` lift geometry off the nominal surface. A ProductSkin carrier path stays on one side (`left`/`right`/`top`/`bottom`); for multi-side detail, split into one member per side and join them at the matching side-local coordinates from `sideTransition()` / `sideTransitionChain()` / `sideRoute()`.\n\n```ts\n// Bottle-cage arm: a curved band on a cylinder, authored in degrees + mm\nconst bottle = Carrier.cylinder('bottle').diameter(74).height(170).clearance(1.5);\nconst arm = bottle.path()\n .from({ angle: -145, z: 18 })\n .through({ angle: -80, z: 72 })\n .to({ angle: -34, z: 112 });\n```\n\n- `cylinder(name: string): CylinderCarrier` — Create an analytic cylinder carrier for bottles, limbs, tubes, guards, and cuffs.\n- `plane(name: string): PlaneCarrier` — Create an analytic plane carrier for plates and local flat construction surfaces.\n- `productSkin(skin: ProductSkin): ProductSkinCarrier` — Adapt an existing ProductSkin into the general surface-member carrier protocol.\n\n### `SurfaceMembers`\n\n- `Body(name: string): SurfaceBodyBuilder` — Start a surface-member body builder for straps, inlays, guards, braces, cuffs, and similar physical members that live on a carrier surface.\n\n ```js\n const carrier = Carrier.cylinder('guard-envelope').diameter(84).height(36).clearance(2);\n const guard = SurfaceBody('simple-guard')\n .carrier(carrier)\n .member('left-strut')\n .band()\n .path(carrier.path().from({ angle: -132, z: 6 }).to({ angle: -58, z: 18 }))\n .section({ width: 5.5, thickness: 2.8, edgeRadius: 0.6 })\n .member('right-strut')\n .mirrorOf('left-strut')\n .member('front-hoop')\n .band()\n .path(carrier.path().around({ z: 18, fromAngle: -58, toAngle: 58 }))\n .section({ width: 6.2, thickness: 3, edgeRadius: 0.7 })\n .join('left-strut', 'front-hoop').blend({ radius: 3.2 })\n .join('right-strut', 'front-hoop').blend({ radius: 3.2 })\n .build();\n ```\n- `Band: typeof SurfaceBand`\n- `band<C extends SurfaceCoordinate>(path: SurfacePath<C> | SurfacePathBuilder<C>, width: WidthProfile, cap?: SurfaceBandCap): SurfaceBand<C>`\n- `roundedSlot(input: { length: number; width: number; }): RoundedSlotBuilder` — Create a rounded member-local slot feature for `SurfaceMemberBuilder.slot()`/`.cutout()`.\n\n Returns a fluent `RoundedSlotBuilder`: chain `.verticalTravel(mm)` to extend the slot for vertical bottle-drop style insertion (travel is summed into the slot length) and `.at({ along, across })` (or `{ z }`) to position it in member-local coordinates.\n\n ```js\n const arm = body.member('arm', armPath)\n .slot('upper-mount-slot', SurfaceMembers.roundedSlot({ length: 12, width: 5.7 }).verticalTravel(6).at({ z: 82 }));\n ```\n- `counterbore(input: { diameter: number; clearanceDiameter: number; depth: number; }): CounterboreBuilder` — Create a cylindrical member-local counterbore feature for `SurfaceMemberBuilder.counterbore()`.\n\n `diameter` is the counterbore pocket diameter and must be larger than `clearanceDiameter`, the through-hole for the fastener shank. Chain `.at({ along, across })` (or `{ z }`) to position it in member-local coordinates.\n\n ```js\n const strap = body.member('strap', strapPath)\n .counterbore('head-pocket', SurfaceMembers.counterbore({ diameter: 9.8, clearanceDiameter: 5.7, depth: 3 }).at({ z: 58 }));\n ```\n- `ribs(input: { count: number; height: number; }): MemberFeature` — Create a repeated-rib stiffening feature for `SurfaceMemberBuilder.features()`.\n\n Ribs belong to the surface member and follow its carrier-surface lowering; `count` ribs of the given `height` are distributed along the member.\n\n ```js\n const grip = body.member('grip', gripPath)\n .features(SurfaceMembers.ribs({ count: 18, height: 0.35 }));\n ```\n\n---\n\n<!-- generated/assembly.md -->\n\n# Assembly API\n\nAssembly-owned links, constraints, connectors, solved poses, and source-level simulation metadata.\n\n## Contents\n\n- [Assembly & Joints](#assembly-joints)\n- [Assembly](#assembly) — Kinematics, Structure, Connectors, References, Solving\n- [ImportedAssembly](#importedassembly)\n- [SolvedAssembly](#solvedassembly)\n\n## Functions\n\n### Assembly & Joints\n\n#### `Sim.material(name: string, options?: SimMaterialOptions): SimMaterialDef` — Create a named physical material with density and contact coefficients for simulation export and checks.\n\n`SimMaterialOptions`: `{ densityKgM3?: number, staticFriction?: number, dynamicFriction?: number, restitution?: number }`\n\n`SimMaterialDef`: `{ kind: \"material\", name: string }`\n\n#### `Sim.body(options: SimBodyOptions): SimBodyDef` — Describe one assembly part as a physical body with mass/density, material, collider intent, and optional contact surfaces.\n\n**`SimBodyOptions`**: `massKg?: number`, `densityKgM3?: number`, `material?: SimMaterialDef`, `collider?: SimColliderDef`, `contacts?: Record<string, SimContactDef>`\n\n`SimColliderDef`: `{ kind: \"collider\", mode: SimColliderMode, reason?: string }`\n\n`SimContactDef`: `{ kind: \"wheelSurface\" | \"gripperSurface\", connectorName: string }`\n\n`SimBodyDef`: `{ kind: \"body\" }`\n\n#### `Sim.collider` — Collision-geometry intent constructors for physical parts.\n\n- `Sim.collider.convexHull(): SimColliderDef` — Use a generated collision mesh for the part. This is the default fast rigid-body collider for irregular parts.\n- `Sim.collider.boundingBox(): SimColliderDef` — Use the part bounding box as the collision geometry. This is fastest and works well for chassis and simple blocks.\n- `Sim.collider.visualMesh(): SimColliderDef` — Use the visual mesh as collision geometry. This is exact but usually slower in physics engines.\n- `Sim.collider.none(reason: string): SimColliderDef` — Disable collision for a part with an explicit reason, such as a sensor-only or decorative object.\n\n#### `Sim.drive` — Joint-drive intent constructors for passive or powered assembly joints.\n\n- `Sim.drive.passive(options?: SimPassiveDriveOptions): SimDriveDef` — Mark a joint as passive while preserving damping and friction metadata for simulation export.\n- `Sim.drive.velocity(options: SimVelocityDriveOptions): SimDriveDef` — Mark a revolute joint as velocity-driven with torque and speed limits. Speed is authored in rpm and exported as deg/s or rad/s as needed.\n\n`SimPassiveDriveOptions`: `{ damping?: number, friction?: number }`\n\n`SimVelocityDriveOptions`: `{ maxTorqueNm: number, maxSpeedRpm: number }`\n\n#### `Sim.contact` — Contact-surface metadata over existing part connectors.\n\n- `Sim.contact.wheelSurface(connectorName: string): SimContactDef` — Mark a connector as the wheel tread contact surface for offline checks and downstream simulation metadata.\n- `Sim.contact.gripperSurface(connectorName: string): SimContactDef` — Mark a connector as a gripper pad/contact surface for offline checks and downstream grasp-readiness metadata.\n\n#### `Sim.profile` — Named validation/export profile constructors.\n\n- `Sim.profile.robotBodyRunnable(): SimProfileDef` — SimReady-style profile for a robot body that should be runnable in a physics simulator.\n- `Sim.profile.robotBodyIsaac(): SimProfileDef` — SimReady-style profile for robot bodies targeting Isaac Sim readiness.\n- `Sim.profile.roboticsAssetPhysx(): SimProfileDef` — SimReady-style profile for robotics assets with PhysX-ready rigid bodies and colliders.\n\n`SimProfileDef`: `{ kind: \"profile\", name: SimProfileName }`\n\n#### `Sim.controller` — Standard controller metadata constructors for simulator package generation.\n\n- `Sim.controller.diffDrive(options: SimDiffDriveControllerOptions): SimDiffDriveControllerDef` — Describe a differential-drive controller from left/right wheel joints and wheel dimensions.\n\n**`SimDiffDriveControllerOptions`**: `leftJoints: string[]`, `rightJoints: string[]`, `wheelSeparationMm: number`, `wheelRadiusMm: number`, `topic?: string`, `odomTopic?: string`, `tfTopic?: string`, `frameId?: string`, `odomFrameId?: string`, `maxLinearVelocity?: number`, `maxAngularVelocity?: number`, `linearAcceleration?: number`, `angularAcceleration?: number`\n\n`SimDiffDriveControllerDef`: `{ kind: \"diffDrive\" }`\n\n#### `assembly(name?: string): Assembly` — Create an assembly container with named parts, connectors, and kinematic links.\n\n**Use this from iteration 1 for any model with moving parts.** Do not build one static pose and retrofit motion later.\n\nTwo motion tools:\n\n- **Link-graph kinematics** (`link()`, `edgeBetweenLinks()`, `addAngleBetweenLinks()`) solve named point positions — a link is a point, not a rigid-body frame. Use when the hard part is solving positions, especially closed loops.\n- **Connector-frame joints** (`connect()` / `match()`) align full connector frames (`origin`, `axis`, `up`) and derive joint frame + axis. Use for serial articulated parts whose orientation matters: hips, hinges, drums, sliders, wheels.\n\n`addPart(..., { mate })` places geometry on the solved link graph by **translation only**: one mate pins a connector origin to a link, two mates orient a part to span two solved links, a third pins roll. Right for markers and point-following geometry; use `connect()`/`match()` when the part needs a deterministic rest orientation.\n\nReturn the `Assembly` itself to expose its joints and driven link controls in the editor; moving a control re-runs `solve(state)`, so closed loops move through the real solver instead of a viewport-only FK approximation.\n\nIf no link in a connected kinematic component is fixed, ForgeCAD chooses a deterministic gauge link for solving and reports a floating-component warning.\n\nA file that returns an `Assembly` is importable via [`require()`](/docs/core#require) and yields an `ImportedAssembly`; use `mergeInto()` to flatten it into a parent assembly.\n\n**Point-link example** (mates a marker to the solved `tip` point; does not orient a bar along `ground -> tip`):\n\n```ts\nconst marker = box(8, 8, 4).withConnectors({\n center: connector({ origin: [0, 0, 0], axis: [0, 0, 1] }),\n});\n\nconst mech = assembly(\"Linkage\")\n .link(\"ground\", { at: [0, 0, 0], fixed: true })\n .link(\"worldX\", { at: [10, 0, 0], fixed: true })\n .link(\"tip\", { at: [40, 0, 0] })\n .edgeBetweenLinks(\"ground\", \"tip\", { name: \"bar\" })\n .addAngleBetweenLinks(\"worldX\", \"ground\", \"tip\", {\n name: \"theta\",\n control: { min: 0, max: 120, default: 30 },\n })\n .addPart(\"Tip marker\", marker, { mate: { connector: \"center\", toLink: \"tip\" } });\n\nreturn mech;\n```\n\n---\n\n## Classes\n\n### `Assembly`\n\nContainer for a kinematic mechanism made up of links, relationships, and parts. See `assembly` for the link-graph vs connector-frame decision rules.\n\nReturning an unsolved `Assembly` keeps the graph available to the runtime; return `mech.solve({ theta: 60 })` for a fixed pose instead.\n\n**Return types**\n\n| Return value | Standalone | `require()` result type |\n|---|---|---|\n| `Assembly` (unsolved) | yes | `ImportedAssembly` |\n| `SolvedAssembly` | yes | `SolvedAssembly` |\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `name` | `string` | — |\n\n**Kinematics**\n\n#### `link(name: string, options?: AssemblyLinkOptions): Assembly` — Add a named kinematic link to the assembly graph.\n\nLinks are assembly-native solved points. They can exist before any geometry is attached, can be displayed by the viewport, and are solved by link/edge/angle constraints.\n\nA link is not a rigid-body frame. It has a world position but no orientation basis. Use `connect()` when a physical part must inherit a connector frame and rotate about a real hinge/slider axis.\n\n**`AssemblyLinkOptions`**\n- `at?: Vec3` — Initial world-space position of this link before kinematic constraints solve it.\n- `fixed?: boolean` — Keep the link locked at its authored `at` position during solves.\n- `metadata?: Record<string, unknown>` — User metadata carried through the kinematic graph for inspection and tooling.\n\n#### `linkAlong(name: string, fromLink: string, towardLink: string, distance: number): Assembly` — Create a derived link on the line through `fromLink` and `towardLink`, at a **signed** distance from `fromLink`.\n\n**Sign convention** (read this first):\n\n- `distance > 0` — the point moves from `fromLink` **toward** `towardLink`.\n- `distance < 0` — the point moves from `fromLink` **away from** `towardLink` (the coupler-extension case, e.g. the Chebyshev lambda linkage's trace point beyond the rocker joint).\n- `distance` greater than the solved edge length places the point **beyond** `towardLink`, still on the same line.\n\nDerived links are trace/reference points. They are recomputed after the primary link solve and cannot participate in structural edges or angle constraints. Because the distance is one signed parameter, a `param()`-driven value can sweep continuously from extension (negative) through `fromLink` (zero) to beyond `towardLink` (large positive).\n\n```ts\n// Chebyshev lambda linkage: trace point C3 extends beyond C2, away from C1.\nmech.linkAlong('C3', 'C2', 'C1', -2.5 * a);\n// Midpoint-style reference 30 mm from A toward B:\nmech.linkAlong('probe', 'A', 'B', 30);\n```\n\n#### `edgeBetweenLinks(a: string, b: string, options?: AssemblyEdgeBetweenLinksOptions): Assembly` — Add a relationship edge between two kinematic links.\n\nBy default the edge captures the authored distance between links as a structural length. Pass `{ length: 'free' }` or `{ visualOnly: true }` for a non-structural overlay edge.\n\n**`AssemblyEdgeBetweenLinksOptions`**: `name?: string`, `length?: number | \"lockCurrent\" | \"free\"`, `min?: number`, `max?: number`, `visualOnly?: boolean`, `control?: AssemblyKinematicControlOptions`, `metadata?: Record<string, unknown>`\n\n`AssemblyKinematicControlOptions`: `{ min?: number, max?: number, default?: number, unit?: string }`\n\n#### `addAngleBetweenLinks(a: string, b: string, c: string, options?: AssemblyAngleBetweenLinksOptions): Assembly` — Add an angle relationship among three kinematic links.\n\nThe middle link is the vertex. When `control` is set, `solve(state)` reads the control value from `state[name]` and solves dependent links from that driven angle.\n\n**`AssemblyAngleBetweenLinksOptions`**: `name?: string`, `value?: number`, `min?: number`, `max?: number`, `control?: boolean | AssemblyKinematicControlOptions`, `limit?: AssemblyKinematicLimitOptions`, `metadata?: Record<string, unknown>`\n\n`AssemblyKinematicLimitOptions`: `{ min?: number, max?: number }`\n\n#### `addAngleBetweenLinkSegmentAndWorldDirection(fromLink: string, toLink: string, direction: Vec3, options?: AssemblyAngleBetweenLinksOptions): Assembly` — Add an absolute angle relationship from a world direction to a link segment.\n\nThe first link is the vertex/pivot and the second link is the moving point. A value of `0` places `fromLink -> toLink` along `direction` in the mechanism plane; positive angles rotate counter-clockwise in that plane.\n\nUse `Points.polar(1, angleDeg)` when the reference direction is planar and angle-based instead of axis-aligned.\n\n#### `describeKinematics(): AssemblyKinematicGraphDef` — Return the assembly-native kinematic graph definition.\n\n**Structure**\n\n#### `addPart(name: string, part: AssemblyPart, options?: PartOptions): Assembly` — Add a named part to the assembly.\n\nConnectors declared on the part (via `withConnectors()`) are captured automatically. Parts are positioned at world origin by default unless a `transform` is provided in `options`. For root parts (no incoming joint), `transform` is their final world position.\n\n`options.mate` is for point-link attachments. During `solve()`, ForgeCAD translates the part so the named connector origin lands on the solved link position. The part keeps its existing orientation; connector `axis` and `up` are not used for link mating. Use this for markers, sensors, labels, and other geometry that should ride on a solved point. Use `connect()` for oriented physical parts such as limbs, levers, hinges, and wheels.\n\nWhen a part is a [`ShapeGroup`](/docs/core#shapegroup), name the group children explicitly to get readable viewport labels (e.g. `\"Base Assembly.Body\"` instead of `\"Base Assembly.1\"`):\n\n```ts\nconst housing = group(\n { name: \"Body\", shape: body },\n { name: \"Lid\", shape: lid },\n);\nassembly.addPart(\"Base Assembly\", housing);\n```\n\n**`PartOptions`**: `transform?: TransformInput`, `metadata?: PartMetadata`, `sim?: SimBodyDef`, `mate?: AssemblyPartMateInput | AssemblyPartMateInput[]`, `bindToFrame?: string`\n\n**`PartMetadata`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `tags?` | `string \\| readonly string[]` | Viewport organization tags applied to scene objects produced from this part. |\n\nAlso: `material?: string`, `process?: string`, `tolerance?: string`, `qty?: number`, `notes?: string`, `densityKgM3?: number`, `massKg?: number`.\n\n**`AssemblyPartMateInput`**\n- `connector: string` — Name of a connector declared on the part (via `withConnectors()`).\n- `toLink: string` — Name of the link this connector's origin is pinned to.\n- `aimLink?: string` — Optional second link to orient toward. When set, the part is rotated so the connector's **axis** aims from `toLink` toward `aimLink`, posing an oriented bone instead of only translating it. For full pose without relying on a connector axis, declare a second mate (two connectors → two links).\n\n#### `frame(name: string, options: AssemblyFrameOptions): Assembly` — Add a named rig frame to the assembly.\n\nA frame is a solved pose: `origin` plus orientation. `axis` is the frame's primary direction and `up` fixes roll around that axis. Use frames for robot links, joint axes, and parts that must carry orientation. Use `link()` for solved points in distance/angle graphs.\n\n`AssemblyFrameOptions`: `{ origin: Vec3, axis: Vec3, up: Vec3, fixed?: boolean, metadata?: Record<string, unknown> }`\n\n#### `fixedJoint(name: string, options: AssemblyFixedFrameJointOptions): Assembly` — Rigidly attach a child rig frame to a parent rig frame.\n\nFixed joints carry frame hierarchy but do not expose a Motion control.\n\n`AssemblyFixedFrameJointOptions`: `{ parent: string, child: string, metadata?: Record<string, unknown> }`\n\n#### `revoluteJoint(name: string, options: AssemblyMovingFrameJointOptions): Assembly` — Add a revolute rig-frame joint.\n\nThe child frame rotates around the parent frame's `axis` direction. Moving frame joints appear in Motion by default; pass `control: false` to keep the joint solved at its default value without showing a Motion control.\n\n**`AssemblyMovingFrameJointOptions`**: `parent: string`, `child: string`, `min?: number`, `max?: number`, `default?: number`, `unit?: string`, `control?: boolean`, `metadata?: Record<string, unknown>`\n\n#### `prismaticJoint(name: string, options: AssemblyMovingFrameJointOptions): Assembly` — Add a prismatic rig-frame joint.\n\nThe child frame translates along the parent frame's `axis` direction. Moving frame joints appear in Motion by default; pass `control: false` to keep the joint solved at its default value without showing a Motion control.\n\n**Connectors**\n\n#### `get usedConnectorRefs(): ReadonlySet<string>` — Connector refs (e.g. \"PartName.connectorName\") consumed by connect/match calls.\n\n#### `withConnectors(partName: string, connectors: Record<string, ConnectorInput>): Assembly` — Attach named connectors to a specific part or the assembly as a whole.\n\nConnectors declared this way are in the part's local coordinate system. They are captured automatically if the incoming [`Shape`](/docs/core#shape) already has connectors via `shape.withConnectors(...)`, but you can also add or override connectors after the fact with this method.\n\nUse the single-argument overload to attach assembly-level connectors — these are exposed when this assembly is imported as a sub-assembly.\n\n`ConnectorInput` — defined in [core](/docs/core).\n\n#### `getConnectors(partName: string): ConnectorMap` — Get connectors declared on a part in part-local space.\n\n#### `getConnector(ref: string): { partName: string; connectorName: string; connector: ConnectorDef; }` — Parse a \"PartName.connectorName\" reference and return the resolved connector. Throws descriptive errors if the part or connector doesn't exist.\n\n#### `connect(parentConnectorRef: string, childConnectorRef: string, options?: ConnectOptions): Assembly` — Connect two parts by aligning their declared connectors, automatically computing frame and axis.\n\nConnector refs use `\"PartName.connectorName\"`. The child connector origin lands exactly on the parent connector origin; joint frame and axis are derived from the connector geometry — no manual `frame`/`axis` math.\n\nFrame semantics: `origin` is the pivot/contact point, `axis` the hinge or slide direction, `up` locks the part's zero-state twist. Omitted `up` gets a deterministic perpendicular — provide `up` whenever rest orientation matters. (`addPart(..., { mate })` translates only; see `addPart`.)\n\n**Face-to-face:** each connector's axis points outward from its part; mating makes the axes anti-parallel, like a plug meeting a socket (same convention as `matchTo()`).\n\n**Revolute sign:** a positive joint value follows the right-hand rule about the **child** connector's placed axis. Because face-to-face mating makes the axes anti-parallel, that is the *left*-hand rule about the parent connector's outward axis — if `+30` swings the opposite way you expected, you predicted from the parent's axis. `forgecad debug assembly` prints each joint's resolved world axis.\n\n**Mirrored revolute axes:** because of the right-hand rule, a mirrored hinge axis (`[1, 0, 0]` vs `[-1, 0, 0]`) rotates oppositely for the same `+theta`: negate the mirrored side's value and mirror limits as `[min, max] -> [-max, -min]`. Prismatic joints have no handedness flip. Use an explicit per-side sign mapping (or side-neutral link controls) for bilateral mechanisms.\n\nJoint type defaults to the connector's `kind`. For `start`/`end` connectors, `align` / `parentAlign` / `childAlign` (`'start' | 'middle' | 'end'`) choose which point meets.\n\n```ts\nconst frame = box(100, 10, 80).withConnectors({\n hinge: connector(\"hinge\", { origin: [0, 0, 40], axis: [0, 0, 1], up: [1, 0, 0] }),\n});\nconst door = box(60, 4, 80).withConnectors({\n hinge: connector(\"hinge\", { origin: [0, 0, 40], axis: [0, 0, -1], up: [1, 0, 0] }),\n});\nassembly(\"Door\").addPart(\"Frame\", frame).addPart(\"Door\", door)\n .connect(\"Frame.hinge\", \"Door.hinge\", { as: \"swing\", min: 0, max: 110 });\n```\n\n**`ConnectOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `min?` | `number` | Lower joint-slider limit; solve clamps to it with a warning. Not a physical stop — enforce real travel limits with stop geometry. |\n| `max?` | `number` | Upper joint-slider limit; same semantics as `min`. |\n| `flip?` | `boolean` | This parameter is ignored. If your connectors produce wrong orientation, fix the connector axis directions instead of using flip. |\n| `parentAlign?` | `PortAlign` | Which point on the parent connector to align: 'start', 'middle' (default), or 'end'. |\n| `childAlign?` | `PortAlign` | Which point on the child connector to align: 'start', 'middle' (default), or 'end'. |\n| `align?` | `PortAlign` | Shorthand: set both parentAlign and childAlign at once. |\n| `follows?` | `JointFollowOptions` | Slave this joint to another joint: `value = ratio × source + offset` (e.g. a mirrored jaw with `ratio: -1`). |\n\nAlso: `as?: string`, `type?: JointType`, `default?: number`, `unit?: string`, `effort?: number`, `velocity?: number`, `damping?: number`, `friction?: number`, `drive?: SimDriveDef`.\n\n**`JointFollowOptions`**\n- `joint: string` — Name of the source joint that drives this one.\n- `ratio?: number` — Multiplier applied to the source joint value (default 1).\n- `offset?: number` — Constant added after the ratio (default 0).\n\n#### `match(childPartName: string, parentPartName: string, pairs: Record<string, string>, options?: MatchToOptions & { as?: string; }): Assembly` — Auto-create a joint by matching typed connectors between two parts.\n\nConnectors can carry a `connectorType` string and a `gender` (`'male'`, `'female'`, or `'neutral'`). `match()` validates type and gender compatibility (use `{ force: true }` to skip validation) and creates the joint automatically from the connector's `kind` metadata.\n\nThe `pairs` map is `{ childConnector: parentConnector }`. The first pair drives joint creation; additional pairs are validated but do not create additional joints (they constrain the same rigid connection).\n\nDefine connectors on shapes with `shape.withConnectors(...)`:\n\n```ts\nconst door = doorShape.withConnectors({\n hinge_top: connector.male(\"hinge\", { origin: [0, 0, 90], axis: [0, 0, 1] }),\n hinge_bottom: connector.male(\"hinge\", { origin: [0, 0, 10], axis: [0, 0, 1] }),\n});\n```\n\nThen match in the assembly:\n\n```ts\nconst mech = assembly(\"Door\")\n .addPart(\"Frame\", frame)\n .addPart(\"Door\", door)\n .match(\"Door\", \"Frame\", { hinge_top: \"hinge_top\", hinge_bottom: \"hinge_bottom\" });\n// Matching connectors computes the placement relationship automatically.\n```\n\n`MatchToOptions` — defined in [core](/docs/core).\n\n**References**\n\n#### `withReferences(refs: Pick<PlacementReferenceInput, \"points\">): Assembly` — Attach named placement reference points to this assembly. These are surfaced automatically on the ImportedAssembly when this file is imported via require(), so consumers can use placeReference() without re-declaring them. Returns a new Assembly — does not mutate.\n\n`PlacementReferenceInput` — defined in [core](/docs/core).\n\n**Solving**\n\n#### `solve(state?: JointState): SolvedAssembly` — Solve the assembly at the given control state and return positioned parts.\n\nSolves assembly-native kinematic links first. Controlled `addAngleBetweenLinks()` relationships read values from `state` by name, clamp to their declared limits, and expose the solved graph on `SolvedAssembly.kinematics`. Angles solve in the plane of their three authored link positions, so a limb that swings out of the `z = 0` plane poses correctly; structural edges hold their bone lengths so a fully angle-driven serial chain follows forward kinematics.\n\nConnector mates declared on `addPart(..., { mate })` attach geometry to solved links while preserving part and connector identity:\n\n- one mate **positions** the connector origin on its link;\n- a mate with `aimLink` (or a second mate to another link) also **orients** the part, rotating an oriented bone to span its links rather than only translating it;\n- a third mate **pins the roll** about the bone axis (full frame), e.g. a bore or clevis that must face a specific way.\n\nConnector-frame joints created by `connect()` / `match()` are also evaluated; their values are read from `state` by joint name and clamped to joint limits.\n\n```ts\nreturn mech.solve({ theta: 45 });\n```\n\n**Other**\n\n#### `withSimulation(options: SimAssemblySimulationOptions): Assembly` — Attach the root simulation contract for this assembly.\n\nUse this after adding physical parts and joints. Robot-body profiles require `rootPart`; asset profiles can describe one-part or multi-part physical assets. URDF/SDF exporters and `forgecad check simready` read this contract directly, so model files no longer need a separate `robotExport(...)` side effect.\n\n`SimAssemblySimulationOptions`: `{ profile: SimProfileDef, rootPart?: string, controllers?: SimControllerDef[] }`\n\n#### `edgeBetweenFrames(a: string, b: string, options?: AssemblyFrameEdgeOptions): Assembly` — Add a visual skeleton edge between two rig frame origins.\n\nFrame edges follow the solved frame poses produced by `fixedJoint()`, `revoluteJoint()`, and `prismaticJoint()`. They do not add constraints, degrees of freedom, parts, or geometry; use them to make a frame-only rig readable in the Motion/rig inspection overlay.\n\n`AssemblyFrameEdgeOptions`: `{ name?: string, metadata?: Record<string, unknown> }`\n\n#### `addAnimation(name: string, options: AssemblyAnimationOptions): Assembly` — Register a named keyframe animation for this assembly's Motion view.\n\nWorks with the returned-assembly controls path: return the unsolved `Assembly` and the animation appears in the Motion tab alongside the solver-backed joint controls. Keyframes hold control values by joint name; joints declared with `follows` are derived automatically and must not appear in keyframes.\n\n```ts\nrobot.addAnimation(\"Pick and place\", {\n duration: 12,\n loop: true,\n keyframes: [\n { values: { J1: 0, J2: -90 } },\n { values: { J1: 45, J2: -30 } },\n { values: { J1: 0, J2: -90 } },\n ],\n});\nreturn robot;\n```\n\n**`AssemblyAnimationOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `duration?` | `number` | Animation length in seconds (default chosen by the viewer). |\n| `loop?` | `boolean` | Loop the animation (default false). |\n| `continuous?` | `boolean` | Interpolate continuously through keyframes instead of pausing on each. |\n| `keyframes` | `JointViewAnimationInput[\"keyframes\"]` | Keyframes of control values by joint/control name. `at` (0..1) or `ticks` control timing. |\n| `default?` | `boolean` | Make this the animation that plays when the model loads. |\n\n`JointViewAnimationInput`: `{ name: string, duration?: number, loop?: boolean, continuous?: boolean, keyframes: JointViewAnimationKeyframeInput[] }`\n\n**`JointViewAnimationKeyframeInput`**\n- `at?: number` — Timeline position [0, 1]. If omitted from ALL keyframes, positions are auto-computed from tick weights.\n- `ticks?: number` — Relative weight of the segment from this keyframe to the next (default 1). Only used in tick-based mode (when `at` is omitted). Last keyframe's ticks value is ignored.\n- Also: `values: Record<string, number>`.\n\n#### `describe(): AssemblyDefinition` — Return the serializable assembly definition used by solve/inspect pipelines.\n\n**Compatibility Aliases**\n\n- `withPorts()` -> `withConnectors()`\n- `getPorts()` -> `getConnectors()`\n\n### `ImportedAssembly`\n\nA wrapper around an imported `Assembly` that provides kinematic access and convenient transform helpers.\n\nWhen a `.forge.js` file returns an unsolved `Assembly`, [`require()`](/docs/core#require) wraps it in an `ImportedAssembly`. This preserves the kinematic structure — you can call `solve()` and `mergeInto()` — and converts to a static [`ShapeGroup`](/docs/core#shapegroup) via the explicit `toGroup(state?)` boundary when group-style transforms are needed.\n\n**Kinematic access**\n\n```ts\nconst arm = require(\"./arm.forge.js\");\n\nconst solved = arm.solve({ shoulder: 45 }); // full kinematic solve\nconst link = arm.getPart(\"Link\", { shoulder: 60 }); // single part at state\nconst group = arm.toGroup({ shoulder: 45 }); // only when ShapeGroup behavior is needed\n```\n\n**Static positioning** — convert explicitly, then transform the group (`toGroup()` solves at default joint values and discards kinematics):\n\n```ts\nconst positioned = arm.toGroup().rotateZ(-90).translate(0, -20, 50);\n```\n\n**Merging into a parent**\n\n```ts\nrequire(\"./arm.forge.js\").mergeInto(robot, {\n prefix: \"Left Arm\",\n mountParent: \"Chassis\",\n mountJoint: \"leftMount\",\n mountOptions: { frame: Transform.identity().translate(-70, 0, 10) },\n});\n```\n\n#### `get assembly(): Assembly` — The underlying Assembly, for advanced composition and inspection.\n\n#### `solve(state?: JointState): SolvedAssembly` — Solve the assembly at the given joint state (defaults to each joint's default value).\n\n#### `getPart(partName: string, state?: JointState): AssemblyPart` — Return a specific named part positioned at the solved pose, with any stored placement offset applied.\n\nThis mirrors `SolvedAssembly.getPart()` for imported assemblies, with one addition: any offset stored by `placeReference()` is applied, so the part lands where the imported assembly was placed. (`solve(state).getPart(name)` returns the part in the assembly's own coordinates, without that offset.)\n\n#### `toGroup(state?: JointState): ShapeGroup` — Convert all assembly parts to a ShapeGroup with named children. Use this for composition, transforms, or child lookup — not as a required render step for assemblies. Child names match the part names used in the assembly. Any stored placement offset and placement references are forwarded to the group.\n\n#### `withReferences(refs: Pick<PlacementReferenceInput, \"points\">): ImportedAssembly` — Attach named placement reference points to this assembly. Points are simple 3D coordinates (relative to the assembly's own origin). Returns a new ImportedAssembly — does not mutate.\n\n#### `referenceNames(kind?: PlacementReferenceKind): string[]` — List all attached placement reference names.\n\n#### `placeReference(ref: string, target: Vec3, offset?: Vec3): ImportedAssembly` — Translate the assembly so the named reference point lands on `target`. Returns a new ImportedAssembly — does not mutate. All point refs are translated by the same delta.\n\n#### `child(name: string): Shape | Sketch | ShapeGroup` — Solve at defaults, get a named child part from the resulting group.\n\n#### `collisionReport(options?: CollisionOptions): CollisionFinding[]` — Detect overlapping part pairs at the default solved pose.\n\nThis mirrors `SolvedAssembly.collisionReport()` for imported assemblies. Use `solve(state).collisionReport(options)` when inspecting a non-default joint state.\n\n`CollisionOptions`: `{ parts?: string[], ignorePairs?: Array<[ string, string ]>, minOverlapVolume?: number }`\n\n#### `minClearance(partA: string, partB: string, searchLength?: number): number` — Compute the minimum gap between two parts at the default solved pose.\n\nThis mirrors `SolvedAssembly.minClearance()` for imported assemblies. Use `solve(state).minClearance(partA, partB, searchLength)` when inspecting a non-default joint state.\n\n#### `mergeInto(parent: Assembly, options: MergeIntoOptions): Assembly` — Flatten this sub-assembly's parts and relationships into `parent` and wire a mount relationship.\n\nAll part, link, and legacy joint names from the sub-assembly are prefixed with `\"${options.prefix}.\"` to avoid collisions; connectors are forwarded with the same prefix. After the merge, drive controls from the parent using the prefixed names:\n\n```ts\nparent.solve({ \"Left Arm.theta\": 45, \"Right Arm.theta\": -20 })\n```\n\nThe sub-assembly must have exactly one root part before it can be merged (collapse multiple roots with `addFixed()` first). See the `ImportedAssembly` class docs for a full merge example.\n\n**`MergeIntoOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `prefix?` | `string` | Prefix applied to every part name and joint name from the sub-assembly. E.g. prefix \"Left Arm\" turns part \"Base\" into \"Left Arm.Base\". Strongly recommended to avoid name collisions when merging multiple instances. |\n| `mountParent` | `string` | Part name in the parent assembly to attach the sub-assembly root to. |\n| `mountJoint` | `string` | Name for the new mount joint in the parent graph. |\n| `mountType?` | `JointType` | Joint type for the mount connection (default: 'fixed'). |\n| `mountOptions?` | `JointOptions` | Frame, axis, limits, and other options for the mount joint. |\n\n**`JointOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `connectorRefs?` | `JointConnectorRefs` | Connector refs that define this joint contract. Usually set by `connect()` / `match()`. |\n| `follows?` | `JointFollowOptions` | Slave this joint to another joint: `value = ratio × source + offset`. Use for mechanisms with one physical DOF expressed through several joints — a mirrored gripper jaw (`ratio: -1`), a gear pair, a drive crank turning with its servo. A followed joint stops being an independent control: the Motion view drives it from its source, `solve()` derives its value (a direct state override is ignored with a warning), and limits still clamp the derived value. |\n\nAlso: `frame?: TransformInput`, `origin?: Vec3`, `axis?: Vec3`, `min?: number`, `max?: number`, `default?: number`, `unit?: string`, `effort?: number`, `velocity?: number`, `damping?: number`, `friction?: number`, `drive?: SimDriveDef`.\n\n`JointConnectorRefs`: `{ parent: string, child: string, parentAlign?: PortAlign, childAlign?: PortAlign }`\n\n### `SolvedAssembly`\n\nThe result of solving an assembly at a specific joint state.\n\n`SolvedAssembly` holds world-space transforms for every part at a given pose. Top-level scripts can return a `SolvedAssembly` directly for display. Use `toGroup()` when you specifically need a [`ShapeGroup`](/docs/core#shapegroup) for composition, group-style transforms, or named-child lookup. Do not call `toGroup()` just to make a solved assembly render. Use `getPart()` / `getTransform()` to inspect individual parts programmatically.\n\n**Validation**\n\nCall `collisionReport()` to detect overlapping parts at this solved pose.\n\n```ts\nconst solved = mech.solve({ shoulder: 45, elbow: -20 });\nconsole.log(\"Collisions\", solved.collisionReport());\nreturn solved;\n```\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `name` | `string` | — |\n\n**Methods:**\n\n#### `warnings(): string[]` — Return any warnings generated during solve (clamped joints, unconverged mates, etc.).\n\n#### `getJointState(): JointState` — Return a snapshot of resolved joint values (after clamping and coupling).\n\n#### `get kinematics(): SolvedAssemblyKinematics | null` — Solved assembly-native kinematic or frame-edge overlay data, or null when no rig overlay data was declared.\n\n#### `getLinkPosition(linkName: string): Vec3` — Return the solved world position of a kinematic link.\n\n#### `getFrame(frameName: string): Transform` — Return the solved world transform for a named rig frame.\n\n#### `get frames(): SolvedAssemblyFrameDef[]` — Return solved rig frames, including origin, axis, up, and transform.\n\n#### `getTransform(partName: string): Transform` — Return the world-space [`Transform`](/docs/core#transform) for the named part at the solved pose.\n\n#### `getPart(partName: string): AssemblyPart` — Return the named part already positioned at its solved world transform.\n\n#### `toGroup(): ShapeGroup` — Convert all solved parts into a [`ShapeGroup`](/docs/core#shapegroup) with named children.\n\nEach part becomes a named child in the group, already positioned at its solved world transform. Use this only when you specifically need a [`ShapeGroup`](/docs/core#shapegroup) for composition, [`ShapeGroup`](/docs/core#shapegroup) transforms, or named-child access. Top-level scripts can return the `SolvedAssembly` directly; do not call `toGroup()` just to make a solved assembly render.\n\n```ts\nconst armGroup = mech.solve({ shoulder: 60 }).toGroup(); // only because we need rotateZ()\nreturn armGroup.rotateZ(90);\n```\n\n#### `toSceneObjects(): Array<{ ... }>` — Return an array of named scene objects for the viewport renderer.\n\nEach part becomes `{ name, shape }` or `{ name, group: [...] }` if the part is a [`ShapeGroup`](/docs/core#shapegroup). Top-level scripts should normally return the `SolvedAssembly` directly. Use `toGroup()` when you need [`ShapeGroup`](/docs/core#shapegroup) behavior; use this method only for advanced scene-graph control where you need access to the flat per-part array with metadata.\n\n#### `bom(): BomRow[]` — Generate a bill of materials for all parts in the solved assembly.\n\n#### `bomCsv(): string` — Generate a bill of materials as a CSV string.\n\n#### `collisionReport(options?: CollisionOptions): CollisionFinding[]` — Detect overlapping (colliding) part pairs in this solved pose.\n\nComputes boolean intersections between all part pairs and returns findings where the overlap volume exceeds `minOverlapVolume` (default 0.1 mm³).\n\n```ts\nconst solved = mech.solve({ shoulder: 35, elbow: 60 });\nconsole.log(\"Collisions\", solved.collisionReport());\n```\n\n#### `minClearance(partA: string, partB: string, searchLength?: number): number` — Compute the minimum gap (clearance) between two parts in this solved pose.\n\nReturns `0` if the parts are touching or overlapping. Manifold-backed parts use the exact Manifold gap query. SDF-backed parts use a mesh-derived sampled gap. `searchLength` bounds the Manifold search radius in mm — increase it for widely separated Manifold parts.\n\n---\n\n<!-- generated/output.md -->\n\n# Output & Annotations\n\nDimensions, BOM entries, verification checks, and sketch export.\n\n## Contents\n\n- [Annotations & Output](#annotations-output)\n- [Sketch Export](#sketch-export)\n\n## Functions\n\n### Annotations & Output\n\n#### `bom(quantity: number, description: string, opts?: BomOpts): void` — Register a Bill of Materials entry for report export.\n\nBOM entries are accumulated during script execution and exported alongside the model in report views. Rows are grouped by normalized `description + unit`. Pass an explicit `key` to force multiple descriptions to collapse into a single line item.\n\n- `quantity` must be a finite number `>= 0`. A quantity of `0` is silently ignored (useful for conditional scripting with `param()`-driven counts).\n- `unit` defaults to `\"pieces\"` when omitted or empty.\n- The assembly `solved.bom()` / `solved.bomCsv()` API is separate and covers per-part assembly metadata; this function is for free-form purchased-item annotation.\n- `bom()` is injected into every `.forge.js` script. Call it directly; do not write `const { bom } = require(...)`, because top-level declarations named `bom` collide with the built-in runtime name.\n\n```ts\nconst tubeLen = param(\"Tube Length\", 1200, { min: 300, max: 4000, unit: \"mm\" });\nconst boltCount = param(\"Bolt Count\", 16, { min: 0, max: 200, integer: true });\n\nbom(tubeLen, \"iron tube 30 x 20\", { unit: \"mm\" });\nbom(boltCount, \"M4 bolt, 16 mm length\");\nbom(4, \"rubber foot\", { key: \"foot-rubber\" }); // explicit aggregation key\n\n// Structured metadata for richer reports:\nbom(tubeLen, \"rectangular steel tube\", {\n unit: \"mm\",\n material: \"steel\",\n section: [30, 20],\n wall: 3,\n});\n```\n\n**`BomOpts`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `unit?` | `string` | Quantity unit label, e.g. \"mm\", \"pieces\", \"kg\". Default: \"pieces\" |\n| `key?` | `string` | Optional explicit grouping key used during report aggregation. |\n| `material?` | `string` | Material name, e.g. \"steel\", \"birch plywood\", \"nylon\" |\n| `dimensions?` | `number[]` | Overall dimensions `[width, height]` or `[width, height, thickness]` in the entry's unit |\n| `section?` | `number[]` | Cross-section dimensions `[w, h]` for tubes and profiles |\n| `wall?` | `number` | Wall thickness for hollow sections (mm) |\n| `diameter?` | `number` | Diameter for round stock, bolts, dowels (mm) |\n| `length?` | `number` | Length for fasteners (mm) |\n| `process?` | `string` | Manufacturing process, e.g. \"laser cut\", \"CNC\", \"welded\" |\n| `notes?` | `string` | Free-form notes |\n| `grain?` | `string` | Wood grain direction, e.g. \"long\", \"cross\" |\n\n#### `robotExport(options: RobotExportOptions): CollectedRobotExport` — Compatibility shim for SDF/URDF robot package metadata.\n\nPrefer returning `assembly(...).withSimulation(...)` with `Sim.body(...)`, `Sim.drive.*(...)`, and `Sim.controller.*(...)` metadata. The CLI commands `forgecad export sdf` and `forgecad export urdf` now read that assembly simulation contract directly.\n\n`robotExport()` remains available for one compatibility window. It converts the legacy descriptor into the same internal simulation model used by source-authored `Sim` metadata and produces a robot package with:\n\n- Mesh-based inertia tensors (full 6-component, not bounding-box approximations)\n- Separate collision meshes\n- Joint limits, effort/velocity/damping/friction metadata from assembly joints\n\n**Collision mesh modes** (set per-link via `links[\"PartName\"].collision`):\n\n| Mode | Description | Default |\n|------|-------------|---------|\n| `'convex'` | Convex hull (separate `_collision.stl`) | Yes |\n| `'box'` | AABB primitive — fastest physics | |\n| `'visual'` | Same mesh as visual — exact but slow | |\n| `'none'` | No collision geometry | |\n\n**Unit conventions:**\n\n- Revolute `velocity` is in degrees/second in Forge; exporters convert to rad/s.\n- Prismatic distances are in mm in Forge; exported in meters.\n- `massKg` is preferred; `densityKgM3` is used when mass is unknown.\n- Compatibility coupling metadata, when present, maps only the primary term (largest ratio) to `<mimic>` — SDF/URDF support single-leader mimic only. Dropped terms emit a warning.\n\n**Legacy example**\n\n```ts\nrobotExport({\n assembly: rover, // assembly() with parts + revolute wheel joints\n modelName: \"Scout\",\n links: { Chassis: { massKg: 10 }, \"Left Wheel\": { massKg: 0.8 } },\n plugins: {\n diffDrive: {\n leftJoints: [\"leftWheel\"], rightJoints: [\"rightWheel\"],\n wheelSeparationMm: 280, wheelRadiusMm: 60,\n },\n },\n world: { generateDemoWorld: true },\n});\n```\n\n**Preferred CLI usage**\n\n```bash\nforgecad export sdf model.forge.js # SDF package (Gazebo/Ignition)\nforgecad export urdf model.forge.js # URDF package (ROS/PyBullet/MuJoCo)\n```\n\n**`RobotExportOptions`**: `assembly: Assembly`, `modelName?: string`, `state?: JointState`, `static?: boolean`, `selfCollide?: boolean`, `allowAutoDisable?: boolean`, `links?: Record<string, RobotLinkExportOptions>`, `joints?: Record<string, RobotJointExportOptions>`, `plugins?: { diffDrive?: RobotDiffDrivePluginOptions; jointStatePublisher?: RobotJointStatePublisherOptions; }`, `world?: RobotWorldOptions`\n\n`RobotLinkExportOptions`: `{ massKg?: number, densityKgM3?: number, collision?: \"visual\" | \"convex\" | \"box\" | \"none\" }`\n\n`RobotJointExportOptions`: `{ effort?: number, velocity?: number, damping?: number, friction?: number }`\n\n**`RobotDiffDrivePluginOptions`**: `leftJoints: string[]`, `rightJoints: string[]`, `wheelSeparationMm: number`, `wheelRadiusMm: number`, `topic?: string`, `odomTopic?: string`, `tfTopic?: string`, `frameId?: string`, `odomFrameId?: string`, `maxLinearVelocity?: number`, `maxAngularVelocity?: number`, `linearAcceleration?: number`, `angularAcceleration?: number`\n\n`RobotJointStatePublisherOptions`: `{ enabled?: boolean, joints?: string[], topic?: string, updateRate?: number }`\n\n`RobotWorldOptions`: `{ name?: string, generateDemoWorld?: boolean, spawnPose?: RobotPose6, keyboardTeleop?: RobotWorldKeyboardTeleopOptions }`\n\n`RobotWorldKeyboardTeleopOptions`: `{ enabled?: boolean, linearStep?: number, angularStep?: number }`\n\n#### `dim()` — Add a dimension annotation between two points, or along an entity.\n\nOverloads:\n\n- `dim(line: Line2D, opts?: DimOpts): void`\n- `dim(edge: EdgeRef, opts?: DimOpts): void`\n- `dim(from: PointArg, to: PointArg, opts?: DimOpts): void`\n\nDimension annotations are purely visual callouts rendered in the viewport and report export. They do not affect geometry or constrain the model.\n\nPoint arguments accept 2D tuples `[x, y]`, 3D tuples `[x, y, z]`, or [`Point2D`](/docs/sketch#point2d) objects (Z is treated as 0 for 2D inputs).\n\nEntity arguments: pass a single [`Line2D`](/docs/sketch#line2d) (from a constrained sketch) or an `EdgeRef` (from `shape.edge('left')`) as the first argument to dimension along that entity directly — no manual endpoint extraction needed.\n\n**Ownership Rules (Report Pages)**\n\n- `currentComponent: true` — deterministic ownership by the calling import instance. Use when authoring reusable imported parts.\n- `component: \"Part Name\"` — route dimension to another named returned object.\n- Multiple owners: dimension is shared and appears on the assembly overview page.\n- No ownership set: report export infers ownership via endpoint-in-bbox.\n\n```ts\ndim([-w / 2, 0, 0], [w / 2, 0, 0], { label: \"Width\" });\ndim([0, 0, -h / 2], [0, 0, h / 2], { label: \"Height\", offset: 14 });\ndim([0, 0, 0], [100, 0, 0], { component: \"Base\", color: \"#00AAFF\" });\ndim(sk.line(a, b), { label: \"Span\", offset: -8 }); // Line2D entity\ndim(myBox.edge(\"top-right\"), { label: \"Depth\" }); // EdgeRef entity\n```\n\n[`Line2D`](/docs/sketch#line2d) / `EdgeRef` entity (then pass `opts` as the second argument)\n\n`component` (string or string[] — report ownership), `currentComponent` (boolean)\n\n`DimOpts`: `{ offset?: number, label?: string, color?: string, component?: string | string[], currentComponent?: boolean }`\n\n**`EdgeRef`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `start` | `Vec3` | Start point |\n| `end` | `Vec3` | End point |\n| `query?` | `EdgeQueryRef` | Compiler-owned edge query when available. |\n| `curve?` | `EdgeCurve` | Exact or parametric curve family when the backend/source can identify one. |\n| `faceName?` | `string` | Owning face name when the edge is associated with one face in a larger topology. |\n\nAlso: `name: EdgeName`.\n\n### Sketch Export\n\n#### `sketchToDxf(sketch: Sketch, options?: SketchDxfOptions): string` — Export a 2D sketch as a DXF string (R12/AC1009 — maximally compatible).\n\nFor regular sketches, each polygon loop becomes a closed `LWPOLYLINE`. For constrained sketches, exports raw `LINE`, `CIRCLE`, and `ARC` entities from the constraint edge geometry, which preserves internal/shared edges that `toPolygons()` would merge away.\n\nThe R12 format is chosen for maximum compatibility with CAM tools, laser-cutter software, and older CAD readers.\n\n```ts\nconst s = rect(100, 60);\nconst dxf = sketchToDxf(s, { layer: 'cut' });\n```\n\n**`SketchDxfOptions`**\n- `layer?: string` — DXF layer name. Default: \"0\"\n- `colorIndex?: number` — DXF color index (1–255, AutoCAD ACI). Default: 7 (white/black)\n\n#### `sketchToSvg(sketch: Sketch, options?: SketchSvgOptions): string` — Export a 2D sketch as an SVG string.\n\nFor regular sketches, exports filled polygon regions. For constrained sketches, exports raw edge geometry (LINE, ARC, CIRCLE) which preserves internal/shared edges that `toPolygons()` would merge away.\n\nThe SVG uses the sketch's native coordinate system (Y-up) with a CSS transform that flips Y so the output renders correctly in SVG's Y-down space. Coordinates are in sketch units (typically mm).\n\n```ts\nconst s = rect(100, 60);\nconst svg = sketchToSvg(s, { stroke: '#333', strokeWidth: 0.8 });\n```\n\n**`SketchSvgOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `stroke?` | `string` | Stroke color. Default: \"black\" |\n| `strokeWidth?` | `number` | Stroke width in sketch units. Default: 0.5 |\n| `fill?` | `string` | Fill color. Default: \"none\" |\n| `padding?` | `number` | Padding around the sketch bounding box in sketch units. Default: 2 |\n| `pixelsPerUnit?` | `number` | If set, scale so 1 sketch-unit = this many px. Otherwise auto-fit. |\n\n---\n\n<!-- guides/scene-presentation.md -->\n\n# Scene Presentation Recipes\n\nWorked `scene()` setups. The option schema and behavioral cliffs (e.g. setting `lights` replaces all defaults) live in the `scene()` API docs (viewport group) — this file is copyable recipes only.\n\n## Baseline studio setup\n\nEvery model deserves at least this; default lighting looks flat.\n\n```js\nscene({\n background: { top: '#1a1a2e', bottom: '#0a0a14' },\n camera: { position: [x, y, z], target: [0, 0, 0], fov: 42 },\n environment: { preset: 'studio', intensity: 0.6 },\n lights: [\n { type: 'ambient', color: '#c8cdd4', intensity: 0.15 },\n { type: 'directional', position: [80, -60, 120], target: [0, 0, 0], color: '#fff4e0', intensity: 1.8, castShadow: true },\n { type: 'directional', position: [-60, 40, 80], target: [0, 0, 0], color: '#b0c4de', intensity: 0.7 },\n ],\n ground: { visible: true, color: '#111118', height: -10, receiveShadow: true },\n postProcessing: {\n bloom: { intensity: 0.3, threshold: 0.85, radius: 0.3 },\n vignette: { darkness: 0.5, offset: 0.4 },\n toneMappingExposure: 1.3,\n },\n});\n```\n\nAdapt to the material family: metallic/jewelry → `studio`, exposure 1.2–1.5, subtle bloom; organic/wood/matte → `warehouse`/`apartment`, warmer ambient, lower bloom; dark/dramatic → `night`, bloom + vignette. Ground with `receiveShadow: true` only for objects that stand on something.\n\nCamera: 3/4 angle, `fov` 35–50 (lower = flatter/telephoto), `target` at the visual center of mass — not necessarily `[0,0,0]`.\n\n## Matte industrial hero shot\n\nFor mechanisms, tools, product prototypes, and vehicles, prefer a matte studio look over gloss or atmosphere (ranges = tuning room, not options syntax):\n\n```js\nscene({\n background: { top: '#c3ccd7', bottom: '#566474' },\n camera: { position: [430, -540, 340], target: [0, 30, 125], fov: 38 },\n environment: { preset: 'studio', intensity: 0.2, background: false }, // 0.15–0.25\n lights: [\n { type: 'ambient', color: '#efe7dc', intensity: 0.15 }, // 0.12–0.2\n { type: 'directional', position: [260, -320, 420], color: '#ffe2bf', intensity: 2.8, castShadow: true }, // 2.6–3.2\n { type: 'directional', position: [-260, 210, 220], color: '#d4e6fb', intensity: 0.85 }, // 0.7–1.0\n { type: 'hemisphere', skyColor: '#c7d3df', groundColor: '#495463', intensity: 0.15 }, // 0.1–0.2\n ],\n postProcessing: {\n bloom: { intensity: 0.04, threshold: 0.94, radius: 0.28 },\n vignette: { darkness: 0.4, offset: 0.32 },\n toneMappingExposure: 1.1, // 1.05–1.18\n },\n});\n```\n\nStage the model on an intentionally matte plinth:\n\n```js\nconst stage = cylinder(16, 226).translate(0, 0, -26)\n .color('#8b97a4').material({ metalness: 0.04, roughness: 0.78 });\nmock(stage, 'StudioPlinth');\n```\n\nIteration rules that held up in practice:\n\n- Prefer roughness over fog for softness — fog flattens form; matte materials keep shadow definition.\n- Keep bloom near zero for mechanical scenes; too much reads toy-like.\n- If the render is close but not right, nudge `toneMappingExposure` by ~0.05 before touching the light rig; avoid large ambient jumps — they kill contrast fastest.\n- Add accent point lights near focal features, localized with `distance` and `decay`.\n\n## Named render views\n\nFor repeatable review or hero renders, declare views in `scene({ views })` — wrap each camera in `{ camera: ... }`:\n\n```js\nscene({\n camera: { position: [430, -540, 340], target: [0, 30, 125], fov: 38 },\n views: {\n hero: { camera: { position: [430, -540, 340], target: [0, 30, 125], up: [0, 0, 1], fov: 38 } },\n side: { camera: { position: [700, 0, 180], target: [0, 30, 100], up: [0, 0, 1], fov: 32 } },\n },\n});\n```\n\nRender one with `forgecad render 3d model.forge.js --view hero`.\n\n---\n\n<!-- guides/joint-design.md -->\n\n# Joint Design Recipes\n\nGeometry recipes for joints that actually rotate without binding — clevis-tongue hinges, hinge chains, hard stops.\n\nAnything that must rotate or slide gets connector frames: `origin` = pivot, `axis` = hinge line, `up` = rest twist. Always set `up` on hinges, wheels, and levers. Connector/link/mate semantics and mirrored-axis sign rules: see the assembly API reference.\n\n## The Cavity Rule\n\nEvery joint is a **cavity** in one part plus a **tenon** in the other, and the cavity must be a real empty volume — not a gap implied by separate solids. A body that runs solid through the joint zone (e.g. a stadium cap under the clevis slot) blocks rotation even though the rest pose looks fine. End the body FLAT before the joint; extend the tines forward to the pivot; the inter-tine volume must be genuinely empty.\n\nDiagnostic: adjacent-part collision volume > expected clearance in `forgecad run` = missing cavity (both parts have solid material at the joint position). After fixing, the collision volume should drop to ~0 (or a few mm³ of clearance overlap).\n\n## Structural Sizing\n\n**Yoke (connecting cantilevers).** Clevis tines at Y = ±Y_OFF are physically disconnected from a body of thickness TONG_T when Y_OFF > TONG_T/2 + clearance — the tines float and would snap under load. Always bridge with a yoke slab spanning the full clevis width, `(Y_OFF + TINE_T/2) * 2`, with a few mm of structural overlap along the joint axis, so material runs continuously from body to each tine.\n\n**Knuckle radius.** For body height H, require `KNUCK_R >= H/2`. Smaller, and the body corners protrude past the knuckle's cylindrical envelope and sweep into the adjacent part during rotation. `KNUCK_R = H/2` makes the body cross-section a stadium that exactly fits the envelope.\n\n## Hard Stops vs Slider Limits\n\nDeclared joint min/max are not geometry — they only constrain the viewport slider; the geometry still permits any rotation. A physical stop requires an interfering protrusion:\n\n- **Extension stop at 0°**: a small lip on the dorsal side of the child's proximal end, sized to just touch the parent's distal dorsal corner at 0°; backbending is then blocked by contact.\n- **Flexion stop at θmax**: a palmar lip, or body-on-body contact when bodies meet.\n\nVerify: ~0 mm³ collision exactly at the limit pose (just touching), non-zero past it.\n\n## Verification Workflow\n\nCheck the loop, not just the rest pose:\n\n1. Build at rest; `forgecad run`; check collision volumes.\n2. Overlap > clearance volume between joint neighbors → apply the cavity rule.\n3. Render each part with `--focus PartName`; the clevis end must show a visible gap between tines.\n4. Re-check at swept angles (30°/60°/90°) — rotation reveals collisions the rest pose hides.\n5. Backbend test at -10°: blocked = hard stop exists; rotates = add a stop.\n";
311
+ const contextMd = "# ForgeCAD — AI Context (Chat UI)\n\n> **Usage:** Paste this file as context into your AI chat session (Claude.ai, ChatGPT, Gemini, etc.).\n> The AI gets full ForgeCAD API knowledge and guides you through building models.\n>\n> **No CLI access in this session.** The AI cannot run commands directly. It asks you to run\n> commands like `forgecad run <file>` in your terminal and paste back the output for\n> verification and iteration.\n\n## Workflow\n\n1. Tell the AI what you want to build and share any existing `.forge.js` files.\n2. The AI writes or edits model files for you.\n3. To validate, run `forgecad run <file>` in your terminal and paste the output.\n4. Iterate until the model looks right, then `forgecad render 3d <file>` for a PNG.\n\n---\n\n## ForgeCAD API Reference\n\nAuthor or modify ForgeCAD models, sketches, assemblies, and CLI workflows. Prefer documented primitives, import rules, placement strategies, and CLI commands over inventing new APIs.\n\n### Model files\n\n- `.forge.js` — parametric part or assembly script; return a `Shape`, `Sketch`, `ShapeGroup`, `Assembly`, `SolvedAssembly`, an array of renderables, or a metadata object.\n- Model the physical artifact, not an educational diagram. No explanatory labels, arrows, legends, or text plaques unless the user explicitly asks for a presentation or teaching view; product markings only where the real object would carry them.\n- Build the real closed CAD first. Never bake cutaways, sectioned shells, permanently exploded layouts, or hidden-parts views into the default model just to show internals — use viewer-only cut planes, `explodeView`, object hiding, transparency, or `inspect sections` after the artifact exists.\n\n### Import and composition\n\n- Always include the extension in relative imports: `require(\"./file.forge.js\", { Param: value })` for model files, `require(\"./helpers.js\")` for plain helper modules. Extensionless imports such as `require(\"./file\")` do not resolve; ForgeCAD resolves project imports by exact path.\n- ForgeCAD APIs are injected globals in `.forge.js` files. Use `bom()`, `box()`, `scene()`, `Shape`, etc. directly; never destructure those names from helpers (`const { bom } = require(\"./bom.js\")`). Import helper files under a project-specific name such as `const bomHelpers = require(\"./bom.js\")`.\n- For static multi-part models, connectors + `matchTo()` are the default way to assemble touching parts.\n- Top-level scripts can return `Assembly` or `SolvedAssembly` directly. Do not call `.toGroup()` just to render an assembly; use it only when you need `ShapeGroup` composition, transforms, or named-child lookup.\n- `Import.svgSketch()` loads SVG files (file format loader, not a module import).\n- `.placeReference('bottom', [0,0,0])` aligns any built-in anchor to a world coordinate; also works with custom `.withReferences()`.\n- Plain `.js` modules hold shared helpers/constants (not model imports).\n\n### Validation and export commands (ask the user to run these)\n\n```bash\nforgecad run <file.forge.js> [file ...] # geometry diagnostics, verify.* results — run after every edit\nforgecad run <file.forge.js> --debug-imports # trace import-chain failures\nforgecad check print <file.forge.js> [file ...] --json # collisions, mesh health, walls, overhangs, bed contact\nforgecad render 3d <file.forge.js> [file ...] # shaded PNG render\nforgecad render wireframe <file.forge.js> [file ...] # edges only — internal geometry, edge flow\nforgecad render section <file.forge.js> [file ...] --plane XZ # 2D cross-section (SVG/PNG)\nforgecad capture gif <file.forge.js> [file ...] # animated orbit or joint-playback GIF\nforgecad export stl <file.forge.js> [file ...] # mesh for 3D printing\nforgecad export 3mf <file.forge.js> [file ...] # mesh + metadata for 3D printing\nforgecad export step <file.forge.js> [file ...] # exact B-rep for CAD interchange\n```\n\nAdd `--param Name=Value` to test a specific parameter value. Pick the export that matches the goal: STL/3MF for printing, STEP for exact CAD interchange.\n\n### Not in this bundle — request on demand\n\nThe task-specific docs below are omitted to keep this bundle small. They install with `forgecad skill install` (default `~/.agents/skills/forgecad/`). When the task involves one of these topics, ask the user to attach the named file from that directory before writing code.\n\n- **Full CLI reference** (all flags, cameras, `--focus`/`--hide` filtering, inspect, exports, projects): `docs/CLI.md`\n- **Inspection bundles** (manifest/evidence contract for `forgecad inspect`): `docs/guides/inspection-bundles.md`\n- **Viewport & runtime** (cut planes, exploded views, joint animation playback, `scene()` configuration): `docs/generated/viewport.md`\n- **SDF modeling** (smooth booleans, TPMS lattices, twist/bend/displace, `fromFunction`): `docs/generated/sdf.md`\n- **Sheet metal** (flanges, bends, K-factor, flat pattern unfolding): `docs/generated/sheet-metal.md`\n- **Part library** (`lib.*` fasteners, gears, pipes, structural profiles): `docs/generated/lib.md`\n- **Woodworking** (`Wood.*` boards and dado/rabbet/mortise joinery): `docs/generated/wood.md`\n\n---\n\n<!-- API/core/concepts.md -->\n\n# ForgeCAD Core Concepts\n\nA `.forge.js` script is plain JavaScript that returns geometry. The entire forge API is injected as globals — never `import`, `require`-destructure, or shadow ForgeCAD API names (`const lib = ...`, `let slot = ...`, `class Shape {}` all collide). The reserved list is in [Runtime Names](../../generated/runtime-names.md); check it before using natural local names.\n\n## Execution & Return Values\n\nAll geometry operations are **immutable** — shapes, sketches, groups, assemblies, and boards return new values, never mutate in place.\n\nA script must return one of three shapes:\n\n1. **A single renderable** — `Shape`, `Sketch`, `ShapeGroup`, `Assembly`, `SolvedAssembly`, or `SdfShape`.\n2. **An array** of renderables or named descriptors `{ name, tags?, shape | sketch | group, color? }`:\n\n ```javascript\n return [\n { name: \"Base Plate\", tags: [\"printed\", \"structural\"], shape: base, color: \"#888888\" },\n { name: \"M4 Bolt\", tags: \"fastener\", shape: bolt, color: \"#4488cc\" },\n ];\n ```\n\n3. **A metadata object** — a plain object whose renderable values are rendered and whose non-renderable values (numbers, hole tables, builder functions) are silently skipped at render but flow to importers via `require()`. Each key becomes a named group, so don't pile independent parts into one array key (`{ parts: [a, b, c] }`) — the integrity gate reads that as a single fragmented part. Give each part its own key (`{ collar12, collar16, plug }`) or use named descriptors (form 2).\n\nReturn an unsolved `Assembly` directly — ForgeCAD solves it at default joint values for display. Use `assembly.solve(state)` for a specific pose. Never call `.toGroup()` just to make an assembly render; use it only when you need `ShapeGroup` composition or named-child lookup.\n\nFor multi-file projects — import path rules, the metadata pattern, and Forge-aware builder modules — see the [`require()` docs](../../generated/core.md).\n\n## Identity\n\n`union()` merges shapes into one solid with one identity — later operands lose separate colors and names. Use `group(...)` or named return objects when parts need separate colors, tags, or identities.\n\n## Face Labels\n\nShapes carry semantic face labels through their lifecycle:\n\n1. **Primitives** assign canonical names (`box()` → `top`, `bottom`, `side-left`, ...; `cylinder()` → `top`, `bottom`, `side`).\n2. **Extrusions** inherit sketch labels and add `top`/`bottom`.\n3. **Transforms** preserve all labels.\n4. **Booleans** preserve first-operand labels where geometry survives.\n\nResolve labels with `.face(name)` or `.face(query)` — see the Shape class docs for the query API.\n\n## Conventions\n\n**No explanatory text inside CAD geometry.** Model the physical artifact; explain the design through names, comments, BOM entries, and docs. Use `text2d()` only when letters are part of the real object (engraving, branding, gauge ticks); use `Viewport.label()` only for temporary review/debug annotation — never to compensate for unclear geometry.\n\n**SDF shapes preview natively** when returned directly — including plain object/array trees of SDF leaves; call `.toShape()` only when mesh-backed CAD/export behavior is needed. See [SDF docs](../../generated/sdf.md).\n\n---\n\n<!-- generated/runtime-names.md -->\n\n# Runtime Names\n\nGenerated by `scripts/gen-api-docs.mjs` from `src/forge/script-runtime/runScript.ts`. Do not edit by hand.\n\nForgeCAD injects API functions, classes, namespaces, and sandbox guard names into every `.forge.js` script. Top-level lexical declarations using these names collide with the injected runtime bindings when the script runs.\n\nAgents should avoid declaring these names with top-level `const`, `let`, destructured imports, or `class` declarations. Use project-specific local names such as `wheelLib`, `axleSlotSketch`, `driveJointConfig`, or `labelTextSketch` instead.\n\nThese collision-reserved names are case-sensitive:\n\n```text\nactivateBackend, Analysis, arcSlot, assembly, Assembly, Blend, bom, box\ncameraTrajectory, Carrier, chamfer, circle2d, Circle2D, circularLayout, circularPattern, circularPattern2d\ncoalesceEdges, compareWith, connector, console, constrainedSketch, Curve, Curve3D, cutPlane\ncylinder, difference, difference2d, dim, draft, ellipse, explodeView, faceProfile\nfillet, Function, gcode, GCodeBuilder, getActiveBackend, global, globalThis, group\nImport, ImportedAssembly, initKernel, intersection, intersection2d, intersectWithPlane, joint, Laser\nlib, Line2D, linearPattern, linearPattern2d, loadFont, loft, Loft, mirrorCopy\nmock, ngon, NurbsCurve3D, NurbsSurface, offsetSolid, param, Param, path\nPoint2D, Points, polygon, polygonVertices, port, Product, ProductPanelBuilder, ProductRibbonBuilder\nProductSkin, ProductSkinBuilder, ProductStationBuilder, ProductSurfaceBuilder, ProductSurfaceRef, projectToPlane, queueMicrotask, rect\nRectangle2D, roundedRect, Route3D, scene, Sculpt, sdf, SdfShape, selectEdge\nselectEdges, self, setActiveBackend, setImmediate, setInterval, setTimeout, Shape, ShapeGroup\nsheetMetal, SheetMetalPart, sheetStock, Sim, Sketch, sketchToDxf, sketchToSvg, slot\nSolvedAssembly, spec, sphere, spline2d, stroke, Surface, SurfaceBody, SurfaceMembers\nsweep, text2d, textWidth, torus, toShape, Transform, union, union2d\nvariableSweep, verify, Viewport, window, Wood\n```\n\n`showLabels` is also a runtime global, but it is not part of the top-level collision check. Avoid reusing it unless you intentionally want a local value with that name.\n\n---\n\n<!-- generated/core.md -->\n\n# Core API\n\n3D primitives, boolean operations, transforms, patterns, imports, and parameters.\n\n## Contents\n\n- [3D Primitives](#3d-primitives)\n- [Boolean Operations](#boolean-operations)\n- [Edge Features](#edge-features)\n- [Patterns & Layout](#patterns-layout)\n- [Imports & Composition](#imports-composition)\n- [Parameters](#parameters)\n- [Grouping & Local Coordinates](#grouping-local-coordinates)\n- [Section & Projection](#section-projection)\n- [Verification](#verification)\n- [Shape](#shape) — Appearance, Face Topology, Edge Topology, Transforms, Booleans & Cutting, Features, Placement, Connectors, References, Measurement\n- [Transform](#transform)\n- [ShapeGroup](#shapegroup) — Children, Transforms, Placement, Connectors, References\n- [SurfacePattern](#surfacepattern)\n- [Pattern2D](#pattern2d)\n- [Pattern2DBuilder](#pattern2dbuilder)\n- [Sheet](#sheet)\n- [CurveNetBuilder](#curvenetbuilder)\n- [MatchEdgeBuilder](#matchedgebuilder)\n- [BridgeBuilder](#bridgebuilder)\n- [ShapeRef](#shaperef)\n- [ANCHOR3D_NAMES](#anchor3d-names)\n- [verify](#verify)\n- [Points](#points)\n- [connector](#connector)\n- [Import](#import)\n\n## Functions\n\n### 3D Primitives\n\n#### `box(width: number, depth: number, height: number): Shape` — Create a rectangular box. Centered on XY, base at Z=0.\n\nAll ForgeCAD dimensions are millimeters; all angles are degrees (applies to every API, not just `box`).\n\nExtents:\n\n- X: `[-width/2, width/2]`\n- Y: `[-depth/2, depth/2]`\n- Z: `[0, height]`\n\nThis origin convention (centered on XY, base at Z=0) applies to all volumetric primitives that have a base. There is no `center: true` option — recenter with `.translate(0, 0, -height/2)` or `.placeReference('center', [0, 0, 0])`.\n\nFor named faces, build from a labeled sketch: `rect(width, depth).labelEdges('s', 'e', 'n', 'w').extrude(height, { labels: { start: 'bottom', end: 'top' } })`.\n\n#### `cylinder(height: number, radius: number, radiusTop?: number, segments?: number): Shape` — Create a cylinder or cone with named faces and edges. Centered on XY, base at Z=0.\n\nExtents:\n\n- X/Y: centered at the origin\n- Z: `[0, height]`\n\n`radiusTop` defaults to `radius`. Set `radiusTop` smaller to taper the side, or `0` for a pointy cone. Use `segments` to create regular prisms (for example `6` for a hexagonal prism).\n\nNamed faces: `top`, `bottom`, `side` Named edges: `top-rim`, `bottom-rim`\n\n#### `sphere(radius: number, segments?: number): Shape` — Create a sphere centered at the origin.\n\nExtents:\n\n- X: `[-radius, radius]`\n- Y: `[-radius, radius]`\n- Z: `[-radius, radius]`\n\nUse `segments` for lower-poly approximations.\n\n#### `torus(majorRadius: number, minorRadius: number, segments?: number): Shape` — Create a torus (donut shape) lying in the XY plane. Centered on all axes.\n\nExtents:\n\n- X: `[-(majorRadius + minorRadius), +(majorRadius + minorRadius)]`\n- Y: `[-(majorRadius + minorRadius), +(majorRadius + minorRadius)]`\n- Z: `[-minorRadius, minorRadius]`\n\nThe origin is the center of the ring.\n\n### Boolean Operations\n\n#### `union(...inputs: ShapeOperandInput[]): Shape` — Combine shapes into a single solid (additive boolean).\n\nAccepts individual shapes, or an array of shapes. `union()` returns one solid, so only the first operand's color is preserved in the result. Use `group()` when you want separate child colors or identities.\n\n#### `difference(...inputs: ShapeOperandInput[]): Shape` — Subtract shapes from a base shape (subtractive boolean).\n\nThe first shape is the base; all subsequent shapes are subtracted from it. Accepts individual shapes, or an array of shapes.\n\n#### `intersection(...inputs: ShapeOperandInput[]): Shape` — Keep only the overlapping volume of the input shapes (intersection boolean).\n\nRequires at least two shapes. Accepts individual shapes, or an array.\n\n### Edge Features\n\n#### `fillet(shape: Shape, radius: number, edges?: EdgeSelector, segments?: number): Shape` — Apply experimental fillets (rounded edges) to one or more edges of a shape.\n\n**Experimental**: edge finishes (fillet and chamfer) are backend-sensitive. The Manifold backend is known to produce incorrect results for some edge-finish cases, and the OCCT backend can be very slow, especially with broad edge selections. Prefer profile-level rounding where the design allows (`sketch.filletCorners(radius)` before extruding — exact and fast); otherwise use targeted edge selectors and inspect the result before treating it as production-ready geometry.\n\nEdge selections compile into backend operations; unsupported selections fail as explicit kernel gaps instead of using TypeScript geometry fallbacks.\n\nThe `edges` parameter is flexible:\n\n- Omit to fillet **all** sharp edges\n- Pass an `EdgeQuery` for an inline filter (most common)\n- Pass an `EdgeSegment` or `EdgeSegment[]` from `selectEdges()` for pre-selected edges\n- Pass a tracked `EdgeRef` from `shape.edge('vert-br')` (vertical edges of `box()` / [`Rectangle2D`](/docs/sketch#rectangle2d) extrusions) — this takes the **exact** compiler-owned path, not the mesh-approximate one\n\nThrows if no edges match the selection, or if `radius` is not a positive finite number.\n\nSelectorless (all-edges) calls draw from a per-run broad edge-feature budget. Exceeding it throws — except in live preview, which skips the finish with a warning for responsiveness. Explicit edge selectors are never budgeted; `FORGECAD_BROAD_EDGE_FEATURE_BUDGET` / `FORGECAD_ALLOW_BROAD_EDGE_FEATURES=1` raise or lift the budget.\n\n```ts\n// Fillet all edges\nfillet(myShape, 2)\n\n// Fillet only top convex edges\nfillet(myShape, 1.5, { atZ: 20, convex: true })\n\n// Fillet vertical edges selected beforehand\nconst edges = selectEdges(myShape, { parallel: [0, 0, 1] })\nfillet(myShape, 3, edges)\n\n// Exact compiler-owned fillet on a tracked box edge\nconst base = box(50, 50, 20)\nfillet(base, 5, base.edge('vert-br'))\n```\n\n#### `chamfer(shape: Shape, size: number, edges?: EdgeSelector): Shape` — Apply experimental chamfers (beveled edges) to one or more edges of a shape.\n\n**Experimental**: same backend caveats as `fillet` — Manifold may be incorrect for some edge-finish cases, OCCT can be very slow on broad selections; prefer profile-level rounding or targeted selectors and inspect the result.\n\nProduces a 45° bevel at the specified `size` (distance from edge). Edge selections compile into backend operations; unsupported selections fail as explicit kernel gaps instead of using TypeScript geometry fallbacks.\n\nSelectorless (all-edges) calls draw from a per-run broad edge-feature budget. Exceeding it throws — except in live preview, which skips the finish with a warning for responsiveness. Explicit edge selectors are never budgeted; `FORGECAD_BROAD_EDGE_FEATURE_BUDGET` / `FORGECAD_ALLOW_BROAD_EDGE_FEATURES=1` raise or lift the budget.\n\nThe `edges` parameter accepts the same options as `fillet()`: inline `EdgeQuery`, pre-selected `EdgeSegment`/`EdgeSegment[]`, a tracked `EdgeRef` from `shape.edge('vert-br')` (exact compiler-owned path), or `undefined` (all sharp edges).\n\n```ts\n// Chamfer all edges\nchamfer(myShape, 1)\n\n// Chamfer only vertical edges\nchamfer(myShape, 2, { parallel: [0, 0, 1] })\n\n// Exact compiler-owned chamfer on a tracked box edge\nconst base = box(50, 50, 20)\nchamfer(base, 3, base.edge('vert-br'))\n```\n\n#### `draft(shape: Shape, angleDeg: number, pullDirection?: Vec3, neutralPlaneOffset?: number): Shape` — Apply a draft angle (taper) to vertical faces for mold extraction.\n\nAdds a taper angle to the vertical faces of a solid so that it can be extracted from a mold. The neutral plane is the Z position where the draft angle is zero — faces above and below are tapered symmetrically. Typical values for injection molding are 1–5°.\n\nSDF, Manifold, and Truck lower supported vertical-prism solids with Z-axis pull directions to a tapered loft. OCCT uses its native draft operation when available.\n\n```ts\n// Add 3° draft to a box for injection molding\ndraft(myBox, 3)\n\n// Draft with custom pull direction and neutral plane\ndraft(myShape, 2, [0, 0, 1], 10)\n```\n\n#### `offsetSolid(shape: Shape, thickness: number): Shape` — Uniformly offset all surfaces of a solid inward or outward.\n\nUnlike `shell()`, which hollows a solid by removing one face, `offsetSolid()` produces a new solid whose every surface is shifted by `thickness`. Positive values grow the shape outward; negative values shrink it inward.\n\nRequires the OCCT backend. Throws on Manifold.\n\n```ts\n// Grow a box outward by 1mm on all sides\noffsetSolid(myBox, 1)\n\n// Shrink a shape inward by 0.5mm\noffsetSolid(myShape, -0.5)\n```\n\n### Patterns & Layout\n\n#### `circularLayout(count: number, radius: number, options?: CircularLayoutOptions): LayoutPoint[]` — Compute evenly-spaced positions around a circle.\n\nEliminates the most common trig pattern in CAD scripts:\n\n```js\n// Before — manual trig\nfor (let i = 0; i < 12; i++) {\n const angle = i * 30 * Math.PI / 180;\n markers.push(marker.translate(r * Math.cos(angle), r * Math.sin(angle), 0));\n}\n\n// After — declarative\nfor (const {x, y} of circularLayout(12, r)) {\n markers.push(marker.translate(x, y, 0));\n}\n```\n\n**`CircularLayoutOptions`**\n- `startDeg?: number` — Angle of the first element in degrees (default: 0 = +X axis).\n- `centerX?: number` — Center X coordinate (default: 0).\n- `centerY?: number` — Center Y coordinate (default: 0).\n\n`LayoutPoint`: `{ x: number, y: number }`\n\n#### `polygonVertices(sides: number, radius: number, options?: PolygonVerticesOptions): LayoutPoint[]` — Compute the vertex positions of a regular polygon.\n\nDefault orientation places the first vertex at the top (90 degrees), matching the convention used by [`ngon()`](/docs/sketch#ngon).\n\nEliminates manual Math.sqrt(3) for triangles, pentagon vertex math, etc:\n\n```js\n// Before — manual equilateral triangle\nconst v1 = [center.x - r/2, center.y + r * Math.sqrt(3)/2];\nconst v2 = [center.x - r/2, center.y - r * Math.sqrt(3)/2];\nconst v3 = [center.x + r, center.y];\n\n// After — declarative\nconst [v1, v2, v3] = polygonVertices(3, r);\n```\n\n**`PolygonVerticesOptions`**\n- `startDeg?: number` — Angle of the first vertex in degrees (default: 90 = top).\n- `centerX?: number` — Center X coordinate (default: 0).\n- `centerY?: number` — Center Y coordinate (default: 0).\n\n#### `linearPattern(shape: Shape, count: number, dx: number, dy: number, dz?: number): Shape` — Repeat a shape in a linear pattern along a direction vector and union the copies.\n\nCreates `count` copies of `shape`, each offset by `(dx*i, dy*i, dz*i)` from the original. All copies are unioned into a single `Shape`. Distinct compiler ownership is assigned to each copy so face identity via owner-scoped canonical queries still works post-merge.\n\n```ts\n// 5 cylinders, 20mm apart along X\nlinearPattern(cylinder(10, 3), 5, 20, 0)\n```\n\n#### `circularPattern(shape: Shape, count: number, centerXOrOpts?: number | CircularPatternOptions, centerY?: number): Shape` — Repeat a shape in a circular pattern around an axis and union the copies.\n\nDistributes `count` copies evenly around the rotation axis (360° / count per step). All copies are unioned into a single `Shape`. Distinct compiler ownership is assigned to each copy — post-merge face identity via owner-scoped canonical queries still works for pattern descendants.\n\nTwo calling conventions:\n\n- **Simple** (Z axis): `circularPattern(shape, 6)` or `circularPattern(shape, 6, centerX, centerY)`\n- **Advanced** (arbitrary axis): `circularPattern(shape, 6, { axis, origin })`\n\n```ts\n// 8 holes evenly spaced around origin\ncircularPattern(cylinder(12, 4).translate(30, 0, -1), 8)\n\n// Circular pattern around X axis\ncircularPattern(myFeature, 4, { axis: [1, 0, 0], origin: [0, 0, 50] })\n```\n\n**`CircularPatternOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `centerX?` | `number` | Center X of the rotation (default: 0). Used when the rotation axis is Z. |\n| `centerY?` | `number` | Center Y of the rotation (default: 0). Used when the rotation axis is Z. |\n| `axis?` | `Vec3` | Rotation axis direction (default: [0, 0, 1] = Z axis). |\n| `origin?` | `Vec3` | Pivot point for the rotation (default: [0, 0, 0]). Overrides centerX/centerY when set. |\n\n#### `linearPattern2d(sketch: Sketch, count: number, dx: number, dy?: number): Sketch` — Repeat a 2D sketch in a linear pattern and union the copies.\n\n#### `circularPattern2d(sketch: Sketch, count: number, centerXOrOpts?: number | { centerX?: number; centerY?: number; startDeg?: number; }, centerY?: number): Sketch` — Repeat a 2D sketch in a circular pattern around a center point and union the copies.\n\n#### `mirrorCopy(shape: Shape, normal: Vec3): Shape` — Mirror a shape across a plane and union the mirror with the original.\n\nThe mirror plane passes through the origin and is defined by its normal vector. The mirrored copy is unioned with the original to produce a single symmetric Shape.\n\n```ts\n// Mirror across the YZ plane (X=0)\nmirrorCopy(box(50, 30, 10), [1, 0, 0])\n```\n\n#### `selectEdges(shape: Shape, query?: EdgeQuery): EdgeSegment[]` — Select all edges from a shape that match the given query.\n\nUses the active kernel's native topology query when available (Truck), otherwise extracts sharp edges from the mesh (dihedral angle > 1°), applies all filters in the query, and returns the matching `EdgeSegment[]`. When `near` is specified the results are sorted closest-first.\n\nWorks on any shape — primitives, booleans, shells, and imported meshes. Use this when tracked topology is unavailable (e.g. after a difference or on imported geometry). For simpler cases, pass an `EdgeQuery` directly to `fillet()` or `chamfer()` instead of calling `selectEdges` separately.\n\n```ts\n// Fillet all top edges of a box\nconst topEdges = selectEdges(part, { atZ: 20, perpendicular: [0, 0, 1] });\nlet result = part;\nfor (const edge of coalesceEdges(topEdges)) {\n result = fillet(result, 2, edge);\n}\n```\n\n**`EdgeQuery`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `near?` | `Vec3` | Sort by proximity to this point (closest first). When used with `selectEdge`, picks the closest match. |\n| `parallel?` | `Vec3` | Filter: edge direction approximately parallel to this vector. |\n| `perpendicular?` | `Vec3` | Filter: edge direction approximately perpendicular to this vector. |\n| `convex?` | `boolean` | Filter: only convex (outside corner) edges. |\n| `concave?` | `boolean` | Filter: only concave (inside corner) edges. |\n| `minAngle?` | `number` | Filter: minimum dihedral angle in degrees. |\n| `maxAngle?` | `number` | Filter: maximum dihedral angle in degrees. |\n| `minLength?` | `number` | Filter: minimum edge length. |\n| `maxLength?` | `number` | Filter: maximum edge length. |\n| `within?` | `BoundingRegion` | Filter: edge midpoint must be within this bounding region. |\n| `atZ?` | `number` | Shorthand: edge midpoint Z is approximately this value within `tolerance`. |\n| `tolerance?` | `number` | Position tolerance for approximate matches. Used by `atZ` and `near`. Default: `1.0`. |\n| `angleTolerance?` | `number` | Angular tolerance in degrees for `parallel`/`perpendicular` filters. Default: `10`. |\n\n`BoundingRegion`: `{ xMin?: number, xMax?: number, yMin?: number, yMax?: number, zMin?: number, zMax?: number }`\n\n**`EdgeSegment`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `index` | `number` | Stable index within the extraction (deterministic for a given mesh). |\n| `direction` | `Vec3` | Normalized direction from start → end. |\n| `dihedralAngle` | `number` | Dihedral angle in degrees (0 = coplanar, 180 = knife edge). |\n| `convex` | `boolean` | true = outside corner (convex), false = inside corner (concave). |\n| `normalA` | `Vec3` | Normal of first adjacent face. |\n| `normalB` | `Vec3` | Normal of second adjacent face (same as normalA for boundary edges). |\n| `boundary` | `boolean` | true if this is a boundary (unmatched) edge — unusual for closed solids. |\n\nAlso: `start: Vec3`, `end: Vec3`, `midpoint: Vec3`, `length: number`.\n\n#### `selectEdge(shape: Shape, query?: EdgeQuery): EdgeSegment` — Select the single best-matching edge from a shape.\n\nWhen `near` is specified, returns the edge whose midpoint is closest to that point. Otherwise returns the first matching edge in mesh order. Throws if no edges match the query — useful as a guard when you expect exactly one result.\n\n```ts\n// Chamfer one specific edge near a known point\nconst bottomEdge = selectEdge(part, { near: [25, 0, 0], atZ: 0 });\nresult = chamfer(result, 1.5, bottomEdge);\n```\n\n#### `coalesceEdges(segments: EdgeSegment[], tolerance?: number): EdgeSegment[]` — Merge collinear edge segments into longer logical edges.\n\nTessellation often splits one geometric edge into multiple short segments. `coalesceEdges` groups adjacent collinear segments and merges each group into a single `EdgeSegment` spanning the full extent. This is usually needed before passing edges to `fillet()` or `chamfer()` on non-primitive shapes.\n\nThe `tolerance` controls the maximum perpendicular distance from collinearity before two segments are considered non-collinear. Default: `0.01`.\n\n```ts\nconst topEdges = selectEdges(part, { atZ: 20 });\nfor (const edge of coalesceEdges(topEdges)) {\n result = fillet(result, 2, edge);\n}\n```\n\n### Imports & Composition\n\n#### `require(path: string, paramOverrides?: Record<string, number | string>): any` — Import a module with optional ForgeCAD parameter overrides. Returns the module's exports.\n\nWhen importing a `.forge.js` file, most return values are passed through exactly as the script returns them. Assembly returns have one extra composition rule: an unsolved [`Assembly`](/docs/assembly#assembly) is wrapped as an [`ImportedAssembly`](/docs/assembly#importedassembly), preserving `solve(state)` and `mergeInto()` across file boundaries, while a returned [`SolvedAssembly`](/docs/assembly#solvedassembly) stays a [`SolvedAssembly`](/docs/assembly#solvedassembly). If the script returns a metadata object (e.g. `{ shape: myShape, bolts: {...} }`), the caller receives the full object — renderable values and metadata together.\n\n**Script return contract:** a `.forge.js` script returns one of three shapes: a single renderable (Shape, ShapeGroup, Sketch, SdfShape, Assembly), an array of renderables or named descriptors (`{ name, shape|sketch|group }`), or a metadata object mixing renderable values with plain data. When a script runs directly, renderable entries of a metadata object are rendered under their key names and non-renderable entries are silently skipped — both halves of the metadata contract: one return value serves the viewport and `require()` callers.\n\n**Assembly return contract**\n\n| `.forge.js` return value | `require()` result |\n|---|---|\n| `Assembly` | `ImportedAssembly` |\n| `SolvedAssembly` | `SolvedAssembly` |\n\n[`ImportedAssembly`](/docs/assembly#importedassembly) exposes default-pose helpers such as `getPart()`, `collisionReport()`, and `minClearance()`. Use `solve(state)` first when inspecting a non-default pose.\n\n**Path rule:** Always include the file extension in relative imports: use `require(\"./part.forge.js\")` for model files and `require(\"./helpers.js\")` for plain helper modules. ForgeCAD does not apply Node-style extension inference, so `require(\"./part\")` will not find `part.forge.js` or `part.js`.\n\n**Parameter scoping:** Parameters declared in required files are automatically namespaced with a `\"filename#N / \"` prefix (e.g. `\"bracket.forge.js#1 / Width\"`). This prevents collisions when multiple files declare same-named params. Each file's params appear as separate sliders.\n\n**Parameter overrides:** When passing overrides, use the bare param name (not the scoped name). Overrides are type-checked — unrecognized keys throw an error with typo suggestions.\n\n**Multi-file assembly pattern** — pass cross-cutting design values from the assembly to parts:\n\n```js\n// assembly.forge.js — owns cross-cutting params, passes to parts\nconst wall = param(\"Wall\", 3);\nconst baseH = param(\"Base Height\", 20);\n\nconst mount = require('./motor-mount.forge.js', { Wall: wall });\nconst base = require('./base-body.forge.js', { Wall: wall, Height: baseH });\n```\n\n**Metadata pattern** — parts publish interface data alongside geometry:\n\n```js\n// motor-mount.forge.js\nreturn { shape: mount, bolts: { dia: 5.3, pos: holePositions } };\n\n// base-body.forge.js\nconst mount = require('./motor-mount.forge.js');\nmount.bolts.pos // access the metadata\nmount.shape // access the geometry\n```\n\n**Forge-aware builder module pattern** — use `.forge.js` modules for reusable sketch, profile, shape, or assembly builders that need ForgeCAD runtime APIs:\n\n```js\n// profiles.forge.js — inspectable on its own, reusable through require()\nfunction wheelProfile() {\n return circle2d(40).subtract(circle2d(18));\n}\n\nreturn {\n preview: [{ name: 'Wheel profile', sketch: wheelProfile() }],\n make: { wheelProfile },\n};\n\n// main.forge.js\nconst profiles = require('./profiles.forge.js');\nconst wheel = profiles.make.wheelProfile().extrude(8);\n```\n\nKeep exported builders pure over top-level constants, top-level `param()` values, or explicit function arguments. Do not declare new `param()` values inside an exported builder if callers need `require('./profiles.forge.js', { Width: 80 })` overrides: import overrides are validated while the module loads, before any exported builder is called. Use plain `.js` modules only for pure constants, tables, math helpers, and formatting code that does not construct ForgeCAD geometry.\n\n**Entry detection (Node semantics):** `require.main` is the entry script's module object, so `require.main === module` is true only in the file being run directly. Part files use it to build standalone preview geometry only when opened directly — importers then skip that work entirely:\n\n```js\n// part.forge.js\nfunction bracket() { ... }\nif (require.main === module) {\n return { preview: [{ name: 'Bracket', shape: bracket() }] }; // direct run: render it\n}\nreturn { make: { bracket } }; // imported: builders only\n```\n\n### Parameters\n\n#### `Param.number(name: string, defaultValue: number, opts?: { min?: number; max?: number; step?: number; unit?: string; integer?: boolean; reverse?: boolean; }): number` — Declare a numeric parameter that renders as a slider in the UI.\n\nEach call registers a slider control. When the user moves the slider the entire script re-executes with the new value. Parameter values are also overridable from `require()` imports or the CLI `--param` flag — the `name` string is the key used in both cases.\n\nDefault range rules when options are omitted:\n\n- `min` defaults to `0`\n- `max` defaults to `defaultValue * 4`\n- `step` is auto-calculated: `1` for integer params, `0.1` for ranges ≤ 100, `1` for larger ranges\n\nThe `unit` option is cosmetic only — no conversion is performed. Use `integer: true` for counts, sides, quantities (rounds to whole numbers; step defaults to `1`).\n\n```ts\nconst width = Param.number(\"Width\", 50);\nconst angle = Param.number(\"Angle\", 45, { min: 0, max: 180, unit: \"°\" });\nconst sides = Param.number(\"Sides\", 6, { min: 3, max: 12, integer: true });\n```\n\n**Parameter overrides** — key must match `name` exactly:\n\n```ts\n// Via require()\nconst bracket = require(\"./bracket.forge.js\", { Width: 80 });\n\n// Via CLI\n// forgecad run model.forge.js --param \"Wall Thickness=3\"\n```\n\nAlso available as the shorthand alias `param()`.\n\n#### `Param.string(name: string, defaultValue: string, opts?: { maxLength?: number; }): string` — Declare a string parameter that renders as a text input in the UI.\n\nString parameters let users type free-form text — labels, names, inscriptions, file paths, etc. The `name` string is the override key.\n\n```ts\nconst label = Param.string(\"Label\", \"Hello World\");\nconst name = Param.string(\"Name\", \"Part-001\", { maxLength: 20 });\n```\n\nOverride via import:\n\n```ts\nconst tag = require(\"./tag.forge.js\", { Label: \"Custom Text\" });\n```\n\nOnly available as `Param.string()` — no standalone alias.\n\n#### `Param.bool(name: string, defaultValue: boolean): boolean` — Declare a boolean parameter that renders as a checkbox in the UI.\n\nInternally stored as `0`/`1`. When overriding from CLI or `require()`, pass `1` for true and `0` for false. The `name` string is the override key.\n\n```ts\nconst showHoles = Param.bool(\"Show Holes\", true);\nif (showHoles) return difference(plate, cylinder(10, 5).translate(50, 30, 0));\nreturn plate;\n```\n\nOverride via import:\n\n```ts\nconst pan = require(\"./pan.forge.js\", { \"Show Lid\": 0 });\n```\n\n#### `Param.choice(name: string, defaultValue: string, choices: string[]): string` — Declare a choice parameter that renders as a dropdown in the UI.\n\n`defaultValue` must exactly match one entry in `choices`. Returns the selected string label. Prefer `Param.choice` over `Param.number` when a slider would hide intent — named choices like `\"wok\"` are self-describing.\n\nOverrides may be passed as the choice label string (preferred) or as a numeric index. The `name` string is the override key.\n\n```ts\nconst panStyle = Param.choice(\"Pan Style\", \"frying-pan\", [\"frying-pan\", \"saute-pan\", \"wok\"]);\nif (panStyle === \"wok\") return buildWok();\n```\n\nOverride via import:\n\n```ts\nconst pan = require(\"./pan.forge.js\", { \"Pan Style\": \"wok\" });\n```\n\nOverride via CLI:\n\n```bash\nforgecad run model.forge.js --param \"Pan Style=wok\"\n```\n\n#### `Param.list<T extends Record<string, number | boolean | string>>(name: string, defaultItems: T[], opts: { ... }): T[]` — Declare a list parameter — an array of struct items with per-field UI controls.\n\nEach item in the list is a struct whose fields each render as their own control (slider, checkbox, or dropdown). The user can add/remove rows up to `minItems`/`maxItems` bounds.\n\nField types:\n\n- Boolean fields (`boolean: true` in field defs) return as `boolean`\n- Choice fields (`choices: [...]` in field defs) return as `string`\n- All other fields return as `number`\n\n`ListParamFieldDef`: `{ min?: number, max?: number, step?: number, unit?: string, integer?: boolean, boolean?: boolean, choices?: string[] }`\n\n### Grouping & Local Coordinates\n\n#### `group(...items: GroupInput[]): ShapeGroup` — Group multiple shapes/sketches for joint transforms without merging into a single mesh.\n\nUnlike union(), child colors and individual identities are preserved. Children can be plain shapes, named descriptors ({ name, shape/sketch/group }), or nested groups. The returned ShapeGroup supports all Shape transforms (translate, rotate, etc.).\n\nNamed descriptors can include `tags` for viewport organization. Tags do not affect geometry; they let the command palette hide, show only, or focus all objects with the same tag.\n\n**Local coordinate pattern:** Build child parts at the origin (local coordinates), then group and translate once to place the whole assembly. This eliminates the error-prone pattern of manually adding parent offsets to every sub-part.\n\n```js\nconst body = roundedBox(100, 20, 32, 4);\nconst panel = box(98, 2, 18).translate(0, -12, 4);\nconst louver = box(88, 2, 6).translate(0, -14, -11);\nconst indoorUnit = group(\n { name: 'Body', shape: body },\n { name: 'Panel', tags: 'cover', shape: panel },\n { name: 'Louver', tags: ['cover', 'moving'], shape: louver },\n).translate(0, -18, 70);\n```\n\n### Section & Projection\n\n#### `intersectWithPlane(shape: Shape, plane: PlaneSpec): Sketch` — Cross-section: slice a 3D shape with a plane and return the intersection as a 2D Sketch.\n\n#### `faceProfile(shape: Shape, face: FaceSelector): Sketch` — Extract the boundary profile of a named face as a 2D sketch.\n\nThe result is returned in the face's local 2D coordinate system, making it convenient for offsets, pocket profiles, or follow-up sketch operations driven by an existing face.\n\n#### `projectToPlane(shape: Shape, plane: PlaneSpec): Sketch` — Orthographically project a 3D shape onto a plane and return the silhouette as a 2D Sketch.\n\n### Verification\n\n#### `verify.that(label: string, check: () => boolean, message?: string): void` — Custom predicate check.\n\n#### `verify.equal(label: string, actual: number, expected: number, tolerance?: number, message?: string): void` — Check that two numbers are approximately equal (within tolerance).\n\n#### `verify.notEqual(label: string, actual: number, unexpected: number, tolerance?: number, message?: string): void` — Check that two numbers are NOT equal (differ by more than tolerance).\n\n#### `verify.greaterThan(label: string, actual: number, min: number, message?: string): void` — Check that actual > min.\n\n#### `verify.lessThan(label: string, actual: number, max: number, message?: string): void` — Check that actual < max.\n\n#### `verify.inRange(label: string, actual: number, min: number, max: number, message?: string): void` — Check that min <= actual <= max.\n\n#### `verify.centersCoincide(label: string, a: ShapeLike, b: ShapeLike, tolerance?: number): void` — Check that the bounding-box centers of two shapes coincide within tolerance (mm).\n\n`ShapeLike`: `{ min: number[], max: number[] }`\n\n#### `verify.connectorDistance(label: string, target: ConnectorDistanceLike, connectorA: string, connectorB: string, expected?: number, tolerance?: number): void` — Check the distance between two named connectors on a shape or group.\n\nUse this when connectors + `matchTo()` define a static assembly interface. It proves the mate at runtime, unlike a plain source-level connector declaration. The common case is `expected = 0`, meaning the two connector origins should coincide after placement.\n\n```ts\nverify.connectorDistance(\"leg is seated\", bench, \"Rail.leg_0\", \"Leg0.head\", 0, 0.01);\n```\n\n#### `verify.physicalComponentCount(label: string, expected: number): void` — Declare the expected physical connectivity component count for the returned visible model.\n\nUse this for generated mechanical models that should have a clear component graph: one connected fixture, a purchased part plus a removable cartridge, a root assembly plus named intentional ghosts, and so on. `forgecad inspect mechanical-integrity` resolves the returned visible objects with the same physical-connectivity analysis used in the quality gate and fails if the actual component count differs.\n\nThis catches the common generated-CAD failure where a script returns a visually plausible artifact but the handle, screw, washer, cover, or terminal block is actually a separate island.\n\n```ts\nverify.physicalComponentCount(\"vise is one connected installed assembly\", 1);\n```\n\n#### `verify.intentionalOverlap(label: string, a: ShapeLike, b: ShapeLike, reason: string): void` — Declare that two visible objects intentionally overlap because the overlap is real manufacturing intent.\n\nUse this only for overlaps that a mechanical reviewer would accept as actual matter sharing volume: welded/fused regions, overmolded inserts, potted electronics, cast-in hardware, or deliberately bonded laminations. This is not a shortcut for screws without holes, shafts without bores, covers without pockets, or parts placed with collision as a positioning hack.\n\n`forgecad inspect mechanical-integrity --collisions` only honors this declaration when both shapes are returned as visible objects and the exact collision report finds that same object pair. Unused or non-visible declarations fail the quality gate so annotations cannot hide unrelated collisions.\n\n```ts\nverify.intentionalOverlap(\"rubber grip is overmolded on handle\", rubberGrip, handleCore, \"overmolded insert\");\n```\n\n#### `verify.notColliding(label: string, a: ShapeLike, b: ShapeLike, searchLength?: number): void` — Check that two shapes do not share positive volume.\n\nFace-to-face contact is allowed; use `verify.minClearance()` when an actual running gap is required.\n\n#### `verify.minClearance(label: string, a: ShapeLike, b: ShapeLike, minGap: number, searchLength?: number): void` — Check that a minimum clearance gap exists between two shapes.\n\n#### `verify.clearanceBetween(label: string, a: ShapeLike, b: ShapeLike, minGap: number, maxGap: number, searchLength?: number): void` — Check that the clearance gap between two shapes is inside an allowed range.\n\nUse this for seated and retained interfaces where a part must be close enough to be mechanically accountable, but must not collide beyond the allowed minimum. It catches both failure modes that make generated CAD look fake: parts floating away from their receiver, and parts intersecting their receiver because the pocket, bore, or running clearance was not modeled.\n\nFor contact, use a narrow range such as `[-0.01, 0.05]` to tolerate tiny numerical noise. For a running fit, use the intended clearance band.\n\nManifold-backed shapes use exact min-gap distance. Other backends use a mesh-derived min-gap check and say so in the verification message; keep `forgecad inspect mechanical-integrity --collisions` in the acceptance gate for positive-volume interference.\n\n```ts\nverify.clearanceBetween(\"cover is seated on gasket\", cover, gasket, -0.01, 0.05);\nverify.clearanceBetween(\"carriage runs inside rail\", carriage, rail, 0.2, 0.5);\n```\n\n#### `verify.parallel(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void` — Check that two face normals are parallel (within toleranceDeg degrees).\n\n`FaceRefLike`: `{ normal: Vec3, center: Vec3 }`\n\n#### `verify.perpendicular(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void` — Check that two face normals are perpendicular (within toleranceDeg degrees).\n\n#### `verify.coplanar(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number, toleranceMm?: number): void` — Check that a face is coplanar with (same plane as) another face, meaning they are parallel AND their centers lie on the same plane.\n\n#### `verify.faceAt(label: string, face: FaceRefLike, expectedPos: Vec3, toleranceMm?: number): void` — Check that a face center lies at a specific position (within toleranceMm).\n\n#### `verify.sameDirection(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void` — Check that two face normals point in the same direction (not antiparallel). Stricter than parallel — both |angle| AND sign must match.\n\n#### `verify.isEmpty(label: string, shape: ShapeLike, message?: string): void` — Check that a shape is empty.\n\n#### `verify.notEmpty(label: string, shape: ShapeLike, message?: string): void` — Check that a shape is NOT empty.\n\n#### `verify.volumeApprox(label: string, shape: ShapeLike, expected: number, tolerance?: number): void` — Check that a shape's volume is approximately equal to expected (mm³).\n\n#### `verify.areaApprox(label: string, shape: ShapeLike, expected: number, tolerance?: number): void` — Check that a shape's surface area is approximately equal to expected (mm²).\n\n#### `verify.boundingBoxSize(label: string, shape: ShapeLike, expectedSize: Vec3, tolerance?: number): void` — Check that a shape's bounding box has approximately the given size.\n\n#### `verify.edgeContinuity(label: string, shape: ShapeLike, options?: EdgeContinuityThresholds): void` — Check that every sampled seam on a shape meets a requested continuity threshold.\n\n**`EdgeContinuityThresholds`**: `continuity?: SurfaceContinuity`, `samples?: number`, `positionTolerance?: number`, `tangentToleranceDeg?: number`, `curvatureTolerance?: number`\n\n#### `verify.noTinyEdges(label: string, shape: ShapeLike, threshold?: number): void` — Check that a shape has no tiny edges below the requested threshold.\n\n#### `verify.noSliverFaces(label: string, shape: ShapeLike, threshold?: number): void` — Check that a shape has no sliver faces below the requested score threshold.\n\n#### `verify.noSelfIntersection(label: string, shape: ShapeLike): void` — Best-effort exact-shape validity guard for self-intersections or broken B-Rep topology.\n\n#### `spec(name: string, checkFn: (...args: any[]) => void): Spec` — Create a named, reusable bundle of verification checks.\n\nA spec groups related `verify.*` calls under a collapsible header in the Checks panel. This makes large check suites scannable. Specs can be applied to multiple shapes and can check relationships between parts.\n\nSpecs can be defined in separate `.forge.js` files and imported via `require()` to share them across models.\n\n`spec.check()` returns a `SpecResult` — you can inspect it programmatically or ignore the return value and let the Checks panel show results.\n\n```ts\nconst printable = spec(\"Fits printer bed\", (shape) => {\n verify.notEmpty(\"Has geometry\", shape);\n const bb = shape.boundingBox();\n verify.lessThan(\"Width < 220mm\", bb.max[0] - bb.min[0], 220);\n verify.lessThan(\"Depth < 220mm\", bb.max[1] - bb.min[1], 220);\n verify.lessThan(\"Height < 250mm\", bb.max[2] - bb.min[2], 250);\n});\n\n// Reuse on multiple shapes\nprintable.check(bracket);\nprintable.check(standoff);\n\n// Check relationships between parts\nconst fitSpec = spec(\"Assembly fit\", (partA, partB) => {\n verify.notColliding(\"No interference\", partA, partB, 10);\n});\nfitSpec.check(bracket, standoff);\n```\n\n**Spec-first workflow:** Write specs before building geometry. Checks go from red to green as you build — effectively TDD for CAD.\n\n**`Spec`**\n- `name: string` — The display name of this spec\n\n---\n\n## Classes\n\n### `Shape`\n\nCore 3D solid shape. All operations are immutable and return new shapes.\n\nSupports transforms (translate, rotate, scale, mirror, transform, rotateAround, pointAlong), booleans (add, subtract, intersect), cutting (split, splitByPlane, trimByPlane), shelling, anchor positioning (attachTo, onFace), placement references, and queries (volume, surfaceArea, boundingBox, isEmpty, numTri, geometryInfo).\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `materialProps` | `ShapeMaterialProps \\| undefined` | — |\n\n**Appearance**\n\n#### `color(value: string | undefined): Shape` — Set the color of this shape (hex string, e.g. \"#ff0000\"). Returns a new Shape with the color applied.\n\n#### `material(props: ShapeMaterialProps): Shape` — Set PBR material properties for this shape's visual appearance.\n\nReturns a new Shape with the specified material properties merged on top of any previously set properties. All properties are optional — omitted keys retain their current value. Material properties survive transforms and boolean operations.\n\nUse `.color()` to set the base diffuse color; `.material()` controls how that color behaves under light (metalness, roughness, clearcoat) and can add emissive glow independent of lighting.\n\n```js\nbox(50, 50, 50).material({ metalness: 0.9, roughness: 0.1 }); // polished metal\nsphere(30).material({ emissive: '#ff6b35', emissiveIntensity: 2 }); // glowing\ncylinder(40, 20).material({ opacity: 0.4, clearcoat: 1.0, clearcoatRoughness: 0.02 }); // ice\n\n// Chainable with other shape methods\nbox(100, 100, 10).color('#gold').material({ metalness: 0.95, roughness: 0.05 }).translate(0, 0, 50);\n```\n\n**`ShapeMaterialProps`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `metalness?` | `number` | Metalness factor (0 = dielectric, 1 = metal). Default: 0.05 |\n| `roughness?` | `number` | Roughness factor (0 = mirror, 1 = fully diffuse). Default: 0.35 |\n| `emissive?` | `string` | Emissive glow color (hex string, e.g. \"#ff6b35\"). |\n| `emissiveIntensity?` | `number` | Emissive intensity multiplier. Default: 1 |\n| `opacity?` | `number` | Opacity (0 = fully transparent, 1 = fully opaque). Default: 1 |\n| `wireframe?` | `boolean` | Render as wireframe. Default: false |\n| `clearcoat?` | `number` | Clearcoat intensity (0–1). Default: 0.1 |\n| `clearcoatRoughness?` | `number` | Clearcoat roughness (0–1). Default: 0.4 |\n| `transmission?` | `number` | Glass/translucency transmission factor (0–1). Renderer support depends on target. |\n| `ior?` | `number` | Index of refraction for transmissive materials. Typical glass is ~1.45. |\n| `thickness?` | `number` | Approximate transmissive volume thickness in model units. |\n| `specularIntensity?` | `number` | Specular highlight intensity (0–1). |\n| `specularColor?` | `string` | Specular highlight tint. |\n| `reflectivity?` | `number` | Reflection strength for supported renderers (0–1). |\n\n**Face Topology**\n\n#### `face(selector: FaceSelector): FaceRef` — Resolve a face by user-authored label or compiler-owned name. Returns a `FaceRef` that can be passed to `.onFace()`, `projectToPlane()`, or used directly in placement.\n\n`.face(name)` is a pure label lookup — it finds faces by user-authored labels, not by geometric queries. Labels are born in sketches via `.label()` / `.labelEdges()` and grow into face names through extrude, loft, revolve, and sweep. They are stable references that travel with the geometry.\n\nLabels must be unique within a shape. Use `.prefixLabels()` before combining shapes with `union()` / `difference()` to avoid collisions. Collision detection throws a clear error with a fix suggestion.\n\nBoolean survival: `union()` and `intersection()` carry labels from every operand; `difference()` carries only the base (first) operand's labels — cutter labels are dropped. A surviving label addresses whatever portion of its face survives the boolean; cutters may split or erase it, and a lineage shared by multiple union operands resolves as a face set rather than a single face.\n\nFor compile-covered shapes (extrude, loft, etc.) the lookup resolves via the shape's compile plan. As a fallback, planar-faced mesh shapes (e.g. results of boolean ops) are resolved via coplanar triangle clustering.\n\n```ts\n// Edge labels become side face names after extrude\nconst profile = path()\n .moveTo(0, 0)\n .lineTo(100, 0).label('floor')\n .lineTo(100, 50).label('wall')\n .lineTo(0, 50).label('ceiling')\n .closeLabel('left-wall');\nconst room = profile.extrude(30, { labels: { start: 'base', end: 'top' } });\nroom.face('floor'); // side face from the labeled edge\nroom.face('base'); // base cap (user-specified)\n\n// .labelEdges() shorthand for sequential edge labeling\nconst plate = rect(100, 50).labelEdges('south', 'east', 'north', 'west');\nconst solid = plate.extrude(20, { labels: { start: 'bottom', end: 'top' } });\nsolid.face('south'); // side face\n\n// Prefix before combining to avoid collisions\nconst left = wing.prefixLabels('l/');\nconst right = wing.mirror([1, 0, 0]).prefixLabels('r/');\nconst full = union(left, right);\nfull.face('l/upper'); // left wing upper surface\n```\n\n#### `faces(): FaceRef[]` — Return faces matching a query, or label semantic faces when passed a mapping.\n\nMapping form returns a new shape: `shape.faces({ lid: 'top', walls: ['front', 'back', 'left', 'right'] })`.\n\n#### `faceNames(): string[]` — List defined semantic face names currently available on this shape.\n\n#### `prefixLabels(prefix: string): Shape` — Prefix all user-authored face labels, including semantic labels from `faces(mapping)`. Returns a new shape with modified labels.\n\n#### `renameLabel(from: string, to: string): Shape` — Rename a single face label. Returns a new shape.\n\n#### `dropLabels(...names: string[]): Shape` — Remove specific face labels. Returns a new shape.\n\n#### `dropAllLabels(): Shape` — Remove all face labels. Returns a new shape.\n\n#### `faceHistory(name: string): FaceTransformationHistory` — Get the transformation history for a specific face.\n\n**Edge Topology**\n\n#### `edge(name: string): EdgeRef` — Get a named topology edge. Only available on shapes with tracked topology (from box/cylinder/extrude).\n\n#### `edgeNames(): string[]` — List named topology edge names. Returns empty array if shape has no tracked topology.\n\n#### `edgesOf(faceLabel: string, options?: EdgesOfOptions): EdgeSegment[]` — Return all boundary edges of a named face.\n\nFinds edges where one adjacent mesh face belongs to the target face and the other belongs to a different face. The result is coalesced (tessellation fragments merged) and can be passed directly to `fillet()` or `chamfer()`.\n\nThis is a topological query — no coordinates, no tolerances, no minimum-length hacks. It works because an edge is the boundary between two faces.\n\n```js\n// Fillet all top edges of a mounting plate\nlet plate = box(120, 80, 6).faces({ workSurface: 'top' })\nplate = fillet(plate, 3, plate.edgesOf('workSurface'))\n\n// Shelled enclosure — fillet the outer lip\nlet body = box(80, 50, 35).faces({ opening: 'top' })\nbody = body.shell(2, { openFaces: ['top'] })\nbody = fillet(body, 1.5, body.edgesOf('opening'))\n\n// Filter: only concave edges (after a boolean subtraction)\nbody.edgesOf('top', { concave: true })\n```\n\n**`EdgesOfOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `exclude?` | `string \\| string[]` | Exclude edges shared with these named faces. |\n| `convex?` | `boolean` | Additional geometric filter: only convex edges. |\n| `concave?` | `boolean` | Additional geometric filter: only concave edges. |\n| `minLength?` | `number` | Minimum edge length filter. |\n\n#### `edgesBetween(faceA: string, faceB: string | string[]): EdgeSegment[]` — Return edges shared between two named faces.\n\nAn edge is \"between\" faces A and B when one of its adjacent mesh triangles belongs to A and the other belongs to B. This is the most precise topological edge selection — \"fillet the edges where the top meets the wall.\"\n\nThe second argument can be a single face name or an array (edges between A and any of B1, B2, ...).\n\n```js\n// Fillet the edge where lid meets one wall\nlet body = box(100, 60, 30).faces({ lid: 'top', wall: 'side-left' })\nbody = fillet(body, 2, body.edgesBetween('lid', 'wall'))\n\n// Fillet a cylinder rim — where the flat cap meets the curved barrel\nlet tube = cylinder(30, 10).faces({ cap: 'top', barrel: 'side' })\ntube = fillet(tube, 1, tube.edgesBetween('cap', 'barrel'))\n\n// Multiple target faces at once\nbody.edgesBetween('lid', ['left-wall', 'right-wall', 'front-wall', 'back-wall'])\n```\n\n**Transforms**\n\n#### `translate(x: number, y: number, z: number): Shape` — Move the shape relative to its current position. All transforms are immutable and return new shapes.\n\n#### `translatePolar(radius: number, angleDeg: number, z?: number): Shape` — Translate using polar coordinates (radius + angle in degrees). Eliminates manual `r * Math.cos(angle * PI/180)` calculations.\n\nExample: `shape.translatePolar(50, 30)` moves 50mm at 30 degrees from +X.\n\n#### `moveTo(x: number, y: number, z: number): Shape` — Position the shape so its bounding box min corner is at the given global coordinate.\n\n#### `moveToLocal(target: Shape | { toShape(): Shape; }, x: number, y: number, z: number): Shape` — Position the shape relative to another shape's local coordinate system (bounding box min corner).\n\n#### `rotate(axis: Vec3, angleDeg: number, options?: { pivot?: Vec3; }): Shape` — Rotate around an arbitrary axis through the origin. Unlike `Sketch.rotate()` (bounding-box center), this pivots at the world origin — pass `options.pivot` to rotate in place.\n\n#### `rotateX(angleDeg: number, options?: { pivot?: Vec3; }): Shape` — Rotate around the X axis by the given angle in degrees.\n\n#### `rotateY(angleDeg: number, options?: { pivot?: Vec3; }): Shape` — Rotate around the Y axis by the given angle in degrees.\n\n#### `rotateZ(angleDeg: number, options?: { pivot?: Vec3; }): Shape` — Rotate around the Z axis by the given angle in degrees.\n\n#### `rotateAroundTo(axis: Vec3, pivot: Vec3, movingPoint: RotationPointLike, targetPoint: RotationPointLike, options?: RotateAroundToOptions): Shape` — Rotate around an axis until a moving point reaches the target line/plane defined by the axis and target point. `movingPoint` / `targetPoint` may be raw world points or this shape's anchors/references.\n\n`RotateAroundToOptions`: `{ mode?: RotateAroundToMode }`\n\n#### `transform(m: Mat4 | Transform): Shape` — Apply a 4x4 affine transform matrix (column-major) or a Transform object.\n\n#### `scale(v: number | Vec3): Shape` — Scale the shape uniformly or per-axis from the shape's bounding box center. Accepts a single number or [x, y, z] array.\n\n#### `scaleAround(pivot: Vec3, v: number | Vec3): Shape` — Scale the shape uniformly or per-axis from an explicit pivot point.\n\n#### `mirror(normal: Vec3): Shape` — Mirror across a plane through the shape's bounding box center, defined by its normal vector.\n\n#### `mirrorThrough(point: Vec3, normal: Vec3): Shape` — Mirror across a plane through an explicit point, defined by its normal vector.\n\n#### `pointAlong(direction: Vec3): Shape` — Reorient a shape so its primary axis (Z) points along the given direction. Useful for laying cylinders/extrusions along X or Y without thinking about Euler angles. The shape's origin stays at [0,0,0] — translate after pointAlong to position it.\n\nExample: cylinder(40, 5).pointAlong([1, 0, 0]) — lays cylinder along X, starting at origin\n\n**Booleans & Cutting**\n\n#### `add(...others: ShapeOperandInput[]): Shape` — Union this shape with others (additive boolean). Method form of union().\n\n#### `subtract(...others: ShapeOperandInput[]): Shape` — Subtract other shapes from this one. Method form of difference().\n\n#### `intersect(...others: ShapeOperandInput[]): Shape` — Keep only the overlap with other shapes. Method form of intersection().\n\n#### `split(cutter: Shape | { toShape(): Shape; }): [ Shape, Shape ]` — Split into [inside, outside] by another shape.\n\n#### `splitByPlane(normal: Vec3, originOffset?: number): [ Shape, Shape ]` — Split by infinite plane. Returns [positive-side, negative-side].\n\n#### `trimByPlane(normal: Vec3, originOffset?: number): Shape` — Keep the positive side of the plane and discard the opposite side.\n\n**Features**\n\n#### `shell(thickness: number, opts?: { openFaces?: string[]; }): Shape` — Hollow out compile-covered boxes, cylinders, and straight extrudes. `openFaces` names any subset of the base shape's labeled faces to leave open (no wall).\n\n#### `pocket(face: FaceSelector, depth: number, opts?: PocketOptions): Shape` — Cut a pocket (cavity) into this solid through the named face.\n\n```js\nbox(100, 100, 20).pocket('top', 8)\nbox(100, 100, 20).pocket('top', 8, { inset: 5 })\nbox(100, 100, 20).pocket('top', 8, { scale: 0.8 })\n```\n\n**`PocketOptions`**\n- `inset?: number` — Shrink the face boundary inward by this many mm before extruding. Produces angled walls when combined with depth. Default: 0 (full face).\n- `scale?: number` — Scale the face profile uniformly (e.g. 0.8 = 80% of the face area). Mutually exclusive with `inset`; `inset` takes precedence if both are set.\n- `join?: \"Square\" | \"Round\" | \"Miter\"` — Corner join style when using `inset`. Default: 'Round'.\n\n#### `boss(face: FaceSelector, height: number, opts?: BossOptions): Shape` — Add a boss (protrusion) from the named face.\n\n```js\nbox(100, 100, 20).boss('top', 5)\nbox(100, 100, 20).boss('top', 10, { scale: 0.6 })\n```\n\n#### `hole(faceOrRef: SketchFaceTarget | FaceRef, opts: ShapeHoleOptions): Shape` — Drill a hole into this solid at a face.\n\n```js\nbox(50, 50, 20).hole('top', { diameter: 8, depth: 10 })\nbox(50, 50, 20).hole('top', { diameter: 6, counterbore: { diameter: 12, depth: 3 } })\n```\n\n**`FaceRef`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `normal` | `Vec3` | Normal direction of the face |\n| `center` | `Vec3` | Center point of the face |\n| `query?` | `FaceQueryRef` | Compiler-owned face query when available. |\n| `planar?` | `boolean` | True when the face can host a 2D sketch placement frame |\n| `uAxis?` | `Vec3` | Face-local horizontal axis for planar faces |\n| `vAxis?` | `Vec3` | Face-local vertical axis for planar faces |\n| `surface?` | `FaceSurface` | Analytic surface family when the backend can identify one. |\n| `descendant?` | `FaceDescendantMetadata` | Shared descendant-resolution metadata when this face is a semantic region/set. |\n\nAlso: `name: FaceName`.\n\n**`FaceDescendantMetadata`**: `kind: \"single\" | \"face-set\"`, `semantic: FaceDescendantSemantic`, `memberCount: number`, `memberNames: string[]`, `coplanar: boolean`\n\n**`ShapeHoleOptions`**: `diameter: number`, `depth?: number`, `upToFace?: SketchFaceTarget | FaceRef`, `extent?: ShapeFeatureExtentOptions`, `u?: number`, `v?: number`, `counterbore?: { diameter: number; depth: number; }`, `countersink?: { diameter: number; angleDeg?: number; }`, `thread?: ShapeHoleThreadOptions`\n\n`ShapeFeatureExtentOptions`: `{ forward: ShapeFeatureExtentSideOptions, reverse?: ShapeFeatureExtentSideOptions }`\n\n`ShapeFeatureExtentSideOptions`: `{ depth?: number, upToFace?: SketchFaceTarget | FaceRef, through?: boolean }`\n\n**`ShapeHoleThreadOptions`**: `designation?: string`, `pitch?: number`, `class?: string`, `handedness?: \"right\" | \"left\"`, `depth?: number`, `modeled?: boolean`\n\n#### `cutout(sketch: Sketch, opts?: ShapeCutoutOptions): Shape` — Cut a profile-shaped pocket through a face using a placed sketch.\n\nThe sketch must be placed on a face with `Sketch.onFace(...)`. The cut follows the sketch's 2D profile.\n\n```js\nconst profile = circle2d(10).onFace(body, 'top');\nbody.cutout(profile, { depth: 5 })\n```\n\n**`ShapeCutoutOptions`**: `depth?: number`, `upToFace?: SketchFaceTarget | FaceRef`, `extent?: ShapeFeatureExtentOptions`, `taperScale?: number | Vec2`\n\n**Placement**\n\n#### `placeReference(ref: PlacementAnchorLike, target: Vec3, offset?: Vec3): Shape` — Translate the shape so the given anchor or reference lands on the target coordinate.\n\nAccepts any built-in anchor name (`'bottom'`, `'center'`, `'top-front-left'`, etc.) or a custom placement reference attached via `withReferences()`.\n\n```javascript\n// Ground a shape — put its bottom face center at Z = 0\nshape.placeReference('bottom', [0, 0, 0])\n\n// Center at the world origin\nshape.placeReference('center', [0, 0, 0])\n\n// Align left edge to X = 10\nshape.placeReference('left', [10, 0, 0])\n```\n\n#### `attachTo(target: ShapeAnchorTarget, targetAnchor: PlacementAnchorLike, selfAnchor?: PlacementAnchorLike, offset?: Vec3): Shape` — Position this shape relative to another using named 3D anchor points.\n\nAnchors are bounding-box-relative: 'center', face centers ('top', 'front', ...), edge midpoints ('top-front', 'back-left', ...), and corners ('top-front-left', ...). Anchor word order is flexible: 'front-left' and 'left-front' are equivalent. Named placement references (from withReferences) can also be used as anchors.\n\n#### `onFace(parent: ShapeAnchorTarget, face: \"front\" | \"back\" | \"left\" | \"right\" | \"top\" | \"bottom\", opts?: { u?: number; v?: number; protrude?: number; }): Shape` — Place this shape on a face of a parent shape.\n\nThink of it like sticking a label on a box surface:\n\n- `face` picks which surface ('front', 'back', 'top', etc.)\n- `u, v` position within that face's 2D plane (from center)\n- front/back: u = left/right (X), v = up/down (Z)\n- left/right: u = forward/back (Y), v = up/down (Z)\n- top/bottom: u = left/right (X), v = forward/back (Y)\n- `protrude` = how far the child sticks out (positive = outward from face)\n\n#### `seatInto(target: Shape, surface: string, options?: SeatIntoOptions): Shape` — Slide this shape along an axis until a labeled face is embedded in the target body.\n\nPosition the shape roughly first (translate/rotate), then call seatInto to auto-adjust the penetration depth. No manual coordinate math needed.\n\n```js\n// Wing root embeds into fuselage — adapts to any fuselage shape\nwing.translate(0, wingY, 0).seatInto(fuselage, 'root');\n\n// Sensor pod sits flush on fuselage surface\npod.translate(0, station, radius + 20).seatInto(fuselage, 'base', { depth: 'flush' });\n\n// Antenna with 3mm gasket standoff\nmast.translate(0, station, radius + 50).seatInto(fuselage, 'mount', { depth: 'flush', gap: 3 });\n```\n\n**`SeatIntoOptions`**\n- `along?: Vec3` — Movement axis. Default: inverted face normal (points into target).\n- `depth?: \"full\" | \"flush\" | number` — How deep to embed. 'full' = entire face inside. 'flush' = nearest point touches. number = mm past flush. Default: 'full'.\n- `gap?: number` — Standoff gap in mm. Positive = gap between face and target. Negative = extra penetration. Default: 0.\n\n#### `seatOver(target: Shape, targetSurface: string, options?: SeatIntoOptions): Shape` — Slide this shape until a target's labeled face is fully covered (inside this shape).\n\nThe inverse of `seatInto`: instead of embedding *your* face into the target, you move until the *target's* face is embedded inside you.\n\n```js\n// Nacelle moves up until pylon's bottom face is inside the nacelle\nnacelle.translate(rough).seatOver(pylon, 'bottom');\n\n// Cap slides down over a post until post's top face is covered\ncap.translate(rough).seatOver(post, 'top');\n```\n\n**Connectors**\n\n#### `withConnectors(connectors: Record<string, ConnectorInput>): Shape` — Attach named connectors — attachment points that survive transforms and imports. Connectors can be bare (position + orientation) or typed (with connectorType/gender for compatibility matching).\n\n`PortInput`: `{ origin?: Vec3, axis?: Vec3, start?: Vec3, end?: Vec3, up?: Vec3, kind?: JointType, min?: number, max?: number }`\n\n`ConnectorInput`: `{ connectorType?: string, gender?: ConnectorGender, measurements?: Record<string, number | string> }`\n\n#### `connectorNames(): string[]` — List all connector names on this shape.\n\n#### `connectorsByType(type: string): Array<{ name: string; port: ConnectorDef; }>` — Get all connectors of a given type.\n\n#### `connectorDistance(nameA: string, nameB: string): number` — Distance between two connector origins on this shape.\n\n#### `connectorMeasurements(name: string): Record<string, number | string>` — Get measurements metadata from a connector.\n\n#### `matchTo(targetOrPairs: Shape | MatchTarget | Array<[ Shape | MatchTarget, string, string ]>, selfConnOrDict?: string | Record<string, string>, targetConnOrOptions?: string | MatchToOptions, maybeOptions?: MatchToOptions): Shape` — Position this shape by matching connectors to a target.\n\nAlignment: with a single connector pair, the shape translates and rotates so the connector origins coincide and the axes oppose (plug-in model); `up` pins the roll. With multiple pairs, the connector origins define the rigid transform — still author meaningful `axis`/`up` values so the same connectors remain useful for `connect()`, audits, and future matching.\n\nOverloads:\n\n- Single pair: `matchTo(target, selfConn, targetConn, options?)`\n- Dictionary (same target): `matchTo(target, { selfConn: targetConn, ... }, options?)`\n- Multi-target: `matchTo([ [target1, selfConn1, targetConn1], ... ], options?)`\n\n`MatchToOptions`: `{ force?: boolean, angle?: number, distance?: number }`\n\n**References**\n\n#### `withReferences(refs: PlacementReferenceInput): Shape` — Attach named placement references that survive normal transforms and imports.\n\n**`PlacementReferenceInput`**: `points?: Record<string, Vec3>`, `edges?: Record<string, PlacementEdgeRef>`, `surfaces?: Record<string, PlacementSurfaceRef>`, `objects?: Record<string, PlacementObjectInput>`\n\n`PlacementEdgeRef`: `{ start: Vec3, end: Vec3 }`\n\n`PlacementSurfaceRef`: `{ center: Vec3, normal: Vec3 }`\n\n#### `referenceNames(kind?: PlacementReferenceKind): string[]` — List named placement references carried by this shape.\n\n#### `referencePoint(ref: PlacementAnchorLike): Vec3` — Resolve a named placement reference or built-in anchor to a 3D point.\n\n**Measurement**\n\n#### `boundingBox(): ShapeRuntimeBounds` — Get the axis-aligned bounding box as { min: [x,y,z], max: [x,y,z] }.\n\n#### `volume(): number` — Volume in mm cubed.\n\n#### `surfaceArea(): number` — Surface area in mm squared.\n\n#### `isEmpty(): boolean` — True if the shape contains no geometry.\n\n#### `numBodies(): number` — Number of disconnected solid bodies in this shape.\n\n#### `numTri(): number` — Triangle count of the mesh representation.\n\n**Other**\n\n#### `clone(): Shape` — Return a new Shape wrapper for explicit duplication in scripts.\n\n#### `geometryInfo(): GeometryInfo` — Inspect which backend/representation produced this solid.\n\n#### `as(name: string): Shape` — Name this shape as a reference namespace for diagnostics and future published refs.\n\n#### `ref(path: string): ShapeRef` — Resolve a semantic reference path like `lid`, `lid/back`, or a midpoint selector on `lid/back`.\n\n#### `thicken(thickness: number): Shape` — Offset-thicken an exact open surface or shell into a solid.\n\n#### `getMesh(): ShapeRuntimeMesh` — Extract triangle mesh for Three.js rendering\n\n#### `slice(offset?: number): any` — Slice the runtime solid by a plane normal to local Z at the given offset.\n\n#### `project(): any` — Orthographically project the runtime solid onto the local XY plane.\n\n**Compatibility Aliases**\n\n- `withPorts()` -> `withConnectors()`\n- `portNames()` -> `connectorNames()`\n\n### `Transform`\n\n#### `static identity(): Transform` — Return the identity transform.\n\n#### `static from(input: TransformInput): Transform` — Wrap an existing `Transform` or raw 4x4 matrix as a `Transform`.\n\n#### `static compose(...steps: TransformInput[]): Transform` — Compose transforms in chain order: `Transform.compose(a, b, c)` applies `a`, then `b`, then `c` — the same left-to-right order as `Transform.from(a).mul(b).mul(c)`.\n\nPrefer this over manual `.mul()` chains when composing 3+ transforms (e.g. kinematics: `local -> childBase -> jointMotion -> jointFrame -> parentWorld`); the variadic form makes the application order explicit and prevents order mistakes.\n\n```ts\nconst world = Transform.compose(childBase, jointMotion, jointFrame, parentWorld);\n```\n\n#### `static translation(x: number, y: number, z: number): Transform` — Create a translation transform.\n\n#### `static scale(v: number | Vec3): Transform` — Create a uniform or per-axis scale transform.\n\n#### `static rotationAxis(axis: Vec3, angleDeg: number, pivot?: Vec3): Transform` — Create a rotation around an arbitrary axis, optionally about a pivot.\n\n#### `static rotateAroundTo(axis: Vec3, pivot: Vec3, movingPoint: Vec3, targetPoint: Vec3, options?: RotateAroundToOptions): Transform` — Solve the rotation needed to move one point onto a target line or plane.\n\n#### `mul(other: TransformInput): Transform` — Compose transforms in chain order: `a.mul(b)` applies `a`, then `b`.\n\n#### `translate(x: number, y: number, z: number): Transform` — Translate after the current transform.\n\n#### `rotateAxis(axis: Vec3, angleDeg: number, pivot?: Vec3): Transform` — Rotate after the current transform.\n\n#### `rotateX(angleDeg: number, pivot?: Vec3): Transform` — Rotate about the X axis after the current transform (parity with `Shape.rotateX`).\n\n#### `rotateY(angleDeg: number, pivot?: Vec3): Transform` — Rotate about the Y axis after the current transform (parity with `Shape.rotateY`).\n\n#### `rotateZ(angleDeg: number, pivot?: Vec3): Transform` — Rotate about the Z axis after the current transform (parity with `Shape.rotateZ`).\n\n#### `inverse(): Transform` — Return the inverse transform.\n\n#### `point(p: Vec3): Vec3` — Transform a point using homogeneous coordinates.\n\n#### `vector(v: Vec3): Vec3` — Transform a direction vector without translation.\n\n#### `toArray(): Mat4` — Return the transform as a raw 4x4 matrix array.\n\n### `ShapeGroup`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `children` | `GroupChild[]` | — |\n| `childNames` | `Array<string \\| undefined>` | — |\n\n**Children**\n\n#### `child(name: string): GroupChild` — Return the named child by name. Throws if not found. Useful when importing a multipart group and working on components individually.\n\n#### `childName(index: number): string | undefined` — Return the optional name of the child at `index`.\n\n**Transforms**\n\n#### `translate(x: number, y: number, z: number): ShapeGroup` — Move the entire group by (x, y, z). All children move together as a unit.\n\n#### `moveTo(x: number, y: number, z: number): ShapeGroup` — Move the group so its bounding-box min corner lands at the given coordinate.\n\n#### `moveToLocal(target: Shape | ShapeGroup, x: number, y: number, z: number): ShapeGroup` — Move the group relative to another part's bounding-box min corner.\n\n#### `rotate(axis: Vec3, angleDeg: number, options?: { pivot?: Vec3; }): ShapeGroup` — Rotate the group around an arbitrary axis through the origin. Unlike `scale()`/`mirror()` (bounding-box center) and `Sketch.rotate()`, this pivots at the world origin — pass `options.pivot` to rotate in place.\n\n#### `rotateX(angleDeg: number, options?: { pivot?: Vec3; }): ShapeGroup` — Rotate the group around the X axis.\n\n#### `rotateY(angleDeg: number, options?: { pivot?: Vec3; }): ShapeGroup` — Rotate the group around the Y axis.\n\n#### `rotateZ(angleDeg: number, options?: { pivot?: Vec3; }): ShapeGroup` — Rotate the group around the Z axis.\n\n#### `rotateAroundAxis(axis: Vec3, angleDeg: number, pivot?: Vec3): ShapeGroup` — Rotate around an arbitrary axis, optionally through a pivot point.\n\n#### `rotateAroundTo(axis: Vec3, pivot: Vec3, movingPoint: Anchor3D | Vec3, targetPoint: Anchor3D | Vec3, options?: RotateAroundToOptions): ShapeGroup` — Rotate around an axis until a moving point reaches the target line/plane defined by the axis and target point. ShapeGroup string points use built-in anchors only.\n\n#### `pointAlong(direction: Vec3): ShapeGroup` — Reorient the group so its local Z axis points along `direction`.\n\n#### `transform(m: Mat4 | Transform): ShapeGroup` — Apply a 4x4 transform matrix or `Transform` to all 3D children.\n\n#### `scale(v: number | Vec3): ShapeGroup` — Scale uniformly or per-axis from the group's bounding-box center.\n\n#### `scaleAround(pivot: Vec3, v: number | Vec3): ShapeGroup` — Scale uniformly or per-axis from an explicit pivot point.\n\n#### `mirror(normal: Vec3): ShapeGroup` — Mirror across a plane through the group's bounding-box center.\n\n#### `mirrorThrough(point: Vec3, normal: Vec3): ShapeGroup` — Mirror across a plane through an explicit point.\n\n**Placement**\n\n#### `placeReference(ref: PlacementAnchorLike, target: Vec3, offset?: Vec3): ShapeGroup` — Translate the group so the given anchor or reference lands on the target coordinate.\n\nAccepts any built-in anchor name (`'bottom'`, `'center'`, `'top-front-left'`, etc.) or a custom placement reference attached via `withReferences()`.\n\n```javascript\n// Ground a group — put its bottom at Z = 0\nassembly.placeReference('bottom', [0, 0, 0])\n\n// Use a custom reference from a multi-file part\nconst placed = require('./bracket-assembly.forge.js').group\n .placeReference('mountCenter', [0, 0, 50]);\n```\n\n#### `attachTo(target: Shape | ShapeGroup, targetAnchor: Anchor3D | string, selfAnchor?: Anchor3D, offset?: Vec3): ShapeGroup` — Attach this group to a face or anchor on another part.\n\n`targetAnchor` can be a built-in anchor name or a custom reference name on the target. `selfAnchor` selects the anchor on this group to align.\n\n#### `onFace(parent: Shape | ShapeGroup, face: \"front\" | \"back\" | \"left\" | \"right\" | \"top\" | \"bottom\", opts?: { u?: number; v?: number; protrude?: number; }): ShapeGroup` — Place this group on a face of a parent shape. See Shape.onFace() for full documentation.\n\n**Connectors**\n\n#### `withConnectors(connectors: Record<string, ConnectorInput>): ShapeGroup` — Attach named connectors — attachment points that survive transforms. Connectors can be bare (position + orientation) or typed (with connectorType/gender for compatibility matching).\n\n#### `connectorNames(): string[]` — List all connector names, including \"ChildName.connectorName\" from named children.\n\n#### `connectorsByType(type: string): Array<{ name: string; port: ConnectorDef; }>` — Get all connectors of a given type, including from named children.\n\n#### `connectorDistance(nameA: string, nameB: string): number` — Distance between two connector origins on this group (supports dotted child paths).\n\n#### `connectorMeasurements(name: string): Record<string, number | string>` — Get measurements metadata from a connector (supports dotted child paths).\n\n#### `matchTo(targetOrPairs: Shape | ShapeGroup | Array<[ Shape | ShapeGroup, string, string ]>, selfConnOrDict?: string | Record<string, string>, targetConnOrOptions?: string | MatchToOptions, maybeOptions?: MatchToOptions): ShapeGroup` — Position this group by matching connectors to a target. Connector names support dotted paths into named children: \"ChildName.connectorName\".\n\nAlignment: with a single connector pair, the group translates and rotates so the connector origins coincide and the axes oppose (plug-in model); `up` pins the roll. With multiple pairs, the connector origins define the rigid transform — still author meaningful `axis`/`up` values so the same connectors remain useful for `connect()`, audits, and future matching.\n\nOverloads:\n\n- Single pair: `matchTo(target, selfConn, targetConn, options?)`\n- Dictionary (same target): `matchTo(target, { selfConn: targetConn, ... }, options?)`\n- Multi-target: `matchTo([ [target1, selfConn1, targetConn1], ... ], options?)`\n\n**References**\n\n#### `withReferences(refs: PlacementReferenceInput): ShapeGroup` — Attach named placement references to this group. References survive normal transforms (translate/rotate/scale/mirror/transform).\n\n```javascript\nconst bracket = group(\n { name: 'Left', shape: leftShape },\n { name: 'Right', shape: rightShape },\n).withReferences({\n points: { mountCenter: [0, 0, 0] },\n});\n```\n\n#### `referenceNames(kind?: PlacementReferenceKind): string[]` — List named placement references carried by this group.\n\n#### `referencePoint(ref: PlacementAnchorLike): Vec3` — Resolve a named placement reference or built-in Anchor3D to a 3D point. Named refs take priority over built-in anchors.\n\n**Other**\n\n#### `clone(): ShapeGroup` — Return a deep-cloned ShapeGroup tree (refs copied).\n\n#### `boundingBox(): { min: Vec3; max: Vec3; }` — Return the combined 3D bounding box of all children.\n\n#### `color(hex: string): ShapeGroup` — Return a copy of the group with the given display color applied to each child.\n\n**Compatibility Aliases**\n\n- `withPorts()` -> `withConnectors()`\n- `portNames()` -> `connectorNames()`\n\n### `SurfacePattern`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `body` | `string` | Function body: receives (u, v) in surface mm, returns height displacement. |\n| `constants` | `Record<string, number>` | Named constants injected into the function. |\n\n### `Pattern2D`\n\n#### `add(...patterns: Pattern2DInput[]): Pattern2D` — Add this pattern to one or more patterns or constant height offsets.\n\n#### `subtract(pattern: Pattern2DInput): Pattern2D` — Subtract another pattern or constant height offset from this pattern.\n\n#### `multiply(...patterns: Pattern2DInput[]): Pattern2D` — Multiply this pattern by one or more patterns or numeric scale factors.\n\n#### `min(...patterns: Pattern2DInput[]): Pattern2D` — Keep the lower height between this pattern and one or more other patterns.\n\n#### `max(...patterns: Pattern2DInput[]): Pattern2D` — Keep the higher height between this pattern and one or more other patterns.\n\n#### `clamp(min: number, max: number): Pattern2D` — Limit pattern height to the inclusive `[min, max]` range in millimeters.\n\n#### `abs(): Pattern2D` — Convert negative heights to positive heights.\n\n#### `negate(): Pattern2D` — Flip the pattern height sign.\n\n### `Pattern2DBuilder`\n\n#### `constant(value?: number): Pattern2D` — Create a constant-height pattern in millimeters.\n\n#### `sineWave(options: Pattern2DSineWaveOptions): Pattern2D` — Create a sinusoidal wave pattern in UV space.\n\n**`Pattern2DSineWaveOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `direction?` | `Vec2` | Direction the wave advances in UV space. Default: [1, 0]. |\n| `wavelength` | `number` | Distance between wave peaks in surface millimeters. |\n| `amplitude?` | `number` | Height amplitude in millimeters. Default: 1. |\n| `phase?` | `number` | Phase offset in radians. Default: 0. |\n| `bias?` | `number` | Constant height offset in millimeters. Default: 0. |\n\n#### `stripes(options: Pattern2DStripesOptions): Pattern2D` — Create recessed stripe bands in UV space.\n\n**`Pattern2DStripesOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `direction?` | `Vec2` | Direction perpendicular to the stripe bands in UV space. Default: [1, 0]. |\n| `spacing` | `number` | Center-to-center spacing in surface millimeters. |\n| `width` | `number` | Stripe width in surface millimeters. |\n| `depth?` | `number` | Stripe groove depth in millimeters. Default: 1. |\n\n#### `overUnderWeave(options: Pattern2DOverUnderWeaveOptions): Pattern2D` — Create an over-under woven relief pattern in UV space.\n\n**`Pattern2DOverUnderWeaveOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `spacing` | `number \\| Vec2` | Thread center-to-center spacing. A number uses the same spacing for U and V. |\n| `threadWidth` | `number \\| Vec2` | Thread width. A number uses the same width for U and V. |\n| `depth?` | `number` | Thread groove depth in millimeters. Default: 0.8. |\n| `underScale?` | `number` | Relative height of the under-crossing thread. Default: 0.15. |\n\n### `Sheet`\n\nA parametric open surface value (control grid + knots + analytic differential geometry).\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `surface` | `BSplineSurface` | — |\n\n**Methods:**\n\n#### `get frontEdge(): SheetEdge` — Edge naming follows parameter direction (documented): front=v0, rear=v1, left=u0, right=u1.\n\n#### `thicken(wall: number, options?: { resolution?: number; }): Shape` — Offset the sheet along its analytic normals into a watertight solid shell of the given wall thickness. Throws if the wall would self-intersect on a concave region (no silent degenerate solid).\n\n#### `matchEdge(edge: SheetEdge): MatchEdgeBuilder` — Per-edge continuity match against a neighbor (returns a NEW Sheet).\n\n**`SheetEdge`**\n- `fixed: \"u\" | \"v\"` — Which parameter is held fixed along this edge.\n- `value: 0 | 1` — The fixed value (0 or 1).\n- Also: `sheet: Sheet`.\n\n- `get rearEdge(): SheetEdge`\n- `get leftEdge(): SheetEdge`\n- `get rightEdge(): SheetEdge`\n- `pointAt(u: number, v: number): Vec3`\n- `normalAt(u: number, v: number): Vec3`\n- `curvatureAt(u: number, v: number): SurfaceCurvature`\n\n### `CurveNetBuilder`\n\n#### `toSheet(): Sheet` — Build (once) and return the Sheet.\n\n- `lengthwise(...curves: CurveInput[]): this`\n- `crosswise(...curves: CurveInput[]): this`\n- `alongRails(railA: CurveInput, railB: CurveInput): this`\n- `sections(...curves: CurveInput[]): this`\n- `cage(grid: Vec3[][]): this`\n- `degree(u: number, v: number): this`\n- `get frontEdge(): SheetEdge`\n- `get rearEdge(): SheetEdge`\n- `get leftEdge(): SheetEdge`\n- `get rightEdge(): SheetEdge`\n- `get surface(): BSplineSurface`\n- `pointAt(u: number, v: number): Vec3`\n- `normalAt(u: number, v: number): Vec3`\n- `curvatureAt(u: number, v: number): SurfaceCurvature`\n- `thicken(wall: number, options?: { resolution?: number; }): Shape`\n- `matchEdge(edge: SheetEdge): MatchEdgeBuilder`\n\n### `MatchEdgeBuilder`\n\n- `toG0(neighbor: SheetEdge): Sheet`\n- `toG1(neighbor: SheetEdge): Sheet`\n- `toG2(neighbor: SheetEdge): Sheet`\n\n### `BridgeBuilder`\n\n#### `bulge(a: number, b: number): this` — Tune the influence of each side (Rhino-style bulge).\n\n- `g0(): Sheet`\n- `g1(): Sheet`\n- `g2(): Sheet`\n\n### `ShapeRef`\n\nA first-class reference path over a shape's semantic faces and face relationships.\n\nCreated with `shape.ref(\"lid/back\")`, then refined through methods such as `.point()` or `.edges()`. The reference stores intent as a readable path and resolves lazily against the current shape metadata.\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `path` | `string` | — |\n\n**Methods:**\n\n#### `resolve(): ShapeReferenceResolution` — Resolve this reference into its current faces, edges, or points.\n\n#### `get kind(): ShapeReferenceKind` — The resolved reference kind, such as `face`, `edge-set`, or `point`.\n\n#### `get cardinality(): ShapeReferenceCardinality` — Whether the reference currently resolves to zero, one, or many matches.\n\n#### `status(): ShapeReferenceStatus` — Return the reference lifecycle status for the current shape state.\n\n#### `explain(): string` — Return a human-readable explanation of how this reference resolved.\n\n#### `as(name: string): ShapeRef` — Name this derived reference so the same shape can resolve it by `shape.ref(name)`.\n\n#### `maybe(): ShapeRef` — Return an optional reference that resolves to zero matches instead of throwing when missing.\n\n#### `all(): ShapeRef` — Mark that a multi-match reference is intentionally being used as a set.\n\n#### `one(): ShapeRef` — Require this reference to resolve to exactly one match.\n\n#### `faces(): FaceRef[]` — Resolve this reference as one or more faces.\n\n#### `face(): FaceRef` — Resolve this reference as exactly one face.\n\n#### `edges(): EdgeSegment[]` — Resolve this reference as one or more edges. Face references return boundary edges.\n\n#### `edge(): EdgeSegment` — Resolve this reference as exactly one edge.\n\n#### `points(): Vec3[]` — Resolve this reference as one or more points. Faces use centers and edges use midpoints.\n\n#### `point(): Vec3` — Resolve this reference as exactly one point.\n\n#### `toJSON(): ShapeReferenceResolution` — Return the structured JSON-friendly reference resolution.\n\n#### `toString(): string` — Return a compact display form for this reference path.\n\n---\n\n## Constants\n\n### `ANCHOR3D_NAMES`\n\n### `verify`\n\nMembers (full entries under [Verification](#verification)): `verify.that`, `verify.equal`, `verify.notEqual`, `verify.greaterThan`, `verify.lessThan`, `verify.inRange`, `verify.centersCoincide`, `verify.connectorDistance`, `verify.physicalComponentCount`, `verify.intentionalOverlap`, `verify.notColliding`, `verify.minClearance`, `verify.clearanceBetween`, `verify.parallel`, `verify.perpendicular`, `verify.coplanar`, `verify.faceAt`, `verify.sameDirection`, `verify.isEmpty`, `verify.notEmpty`, `verify.volumeApprox`, `verify.areaApprox`, `verify.boundingBoxSize`, `verify.edgeContinuity`, `verify.noTinyEdges`, `verify.noSliverFaces`, `verify.noSelfIntersection`.\n\n### `Points`\n\n- `distance(a: Vec3, b: Vec3): number` — Euclidean distance between two 3D points.\n- `midpoint(a: Vec3, b: Vec3): Vec3` — Center point between two 3D points.\n- `lerp(a: Vec3, b: Vec3, t: number): Vec3` — Linearly interpolate between two 3D points. t=0 returns a, t=1 returns b.\n- `direction(a: Vec3, b: Vec3): Vec3` — Unit direction vector from a to b. Throws if a and b are the same point.\n- `offset(point: Vec3, dir: Vec3, amount: number): Vec3` — Move a point along a direction vector by a given amount.\n- `polar(length: number, angleDeg: number, from?: Vec2): Vec2` — Compute a 2D point at distance and angle (degrees) from an optional origin.\n\n### `connector`\n\nConnector factory. Create attachment points: `connector({...})`, `connector.male(type, {...})`, etc.\n\n### `Import`\n\nNamespaced file-format import helpers — the single vocabulary for bringing external geometry files into a model.\n\n- `dxfSketch(fileName: string, options?: DxfImportOptions): Sketch` — Parse a DXF file and return closed 2D profile geometry as a Sketch. The result can be extruded directly.\n- `svgSketch(fileName: string, options?: SvgImportOptions): Sketch` — Parse an SVG file and return it as a Sketch with options for region filtering, scaling, and simplification.\n- `mesh(fileName: string, options?: MeshImportOptions): Shape | ShapeGroup` — Import an external mesh file (STL, OBJ, 3MF).\n\n By default, 3MF build items are flattened into one Shape for compatibility. Use `separateObjects: true` to import 3MF build items/resource objects as a named ShapeGroup whose children are targetable by `forgecad ls`. Use `object` to import one item by the stable ref/name reported by `forgecad run`.\n\n For 3MF sources, `forgecad run` prints a source-structure table with one line per build item: `[3mf:build:NNN:object:N] name type=... verts=... tris=... bbox=[min] → [max]`. Build items are numbered from `001`; files with no build items list resource objects as `3mf:object:N` instead. Per-item bboxes reveal multi-part structure — account for every substantial item before flattening. Pass any listed stable ref or name as `object` to import that item alone.\n\n Use `sourceFrame: { up: \"+Y\" }` when the file was authored in a non-Z-up coordinate system. ForgeCAD remains Z-up; the import is rotated so the named source axis becomes ForgeCAD +Z. Supported values: `\"+X\"`, `\"-X\"`, `\"+Y\"`, `\"-Y\"`, `\"+Z\"`, `\"-Z\"`.\n\n ```js\n const all = Import.mesh(\"./assembly.3mf\", { separateObjects: true });\n const pin = all.child(\"Pin #001\");\n const plate = Import.mesh(\"./assembly.3mf\", { object: \"3mf:build:001:object:7\" });\n const yUpPart = Import.mesh(\"./part.obj\", { sourceFrame: { up: \"+Y\" } });\n ```\n- `step(fileName: string, options?: StepImportOptions): Shape` — Import a STEP file (.step, .stp) as an exact OCCT-backed Shape. Preserves NURBS curves, B-spline surfaces, and exact topology. Requires running with the OCCT backend. Use `sourceFrame: { up: \"+Y\" }` to rotate Y-up source files into ForgeCAD's Z-up world.\n\n---\n\n<!-- guides/coordinate-system.md -->\n\n# Coordinate System\n\nZ-up right-handed: +X right, +Y back, +Z up. Ground plane is XY at Z = 0; extrusion goes along +Z. Units are millimeters; angles are degrees.\n\nModel fronts (face/nose/camera side) point toward **-Y**; rear is +Y; the forward vector is `[0, -1, 0]`. Anchors follow: `front` resolves to the minimum-Y side, `back` to the maximum-Y side.\n\nA `front` view camera sits on the -Y side looking toward +Y, so it sees the model's front face. The other views follow: back +Y, right +X, left -X, top +Z, bottom -Z.\n\n---\n\n<!-- guides/positioning.md -->\n\n# Positioning Decision Ladder\n\nMost positioning bugs come from manual coordinate arithmetic. Pick the **highest applicable rung**; drop down only when the rung above doesn't fit.\n\n## 1. Connectors + `matchTo()` — every real part-to-part interface\n\n**Rule 0:** if parts are meant to stay in contact, define connectors and `matchTo()` — including *static* assemblies (furniture, enclosures, fixtures, toys), not just mechanisms. Connectors win because they are **stable** (don't shift on fillet/chamfer/boolean), **semantic** (type/gender), **oriented** (full frame), **queryable** (`verify.connectorDistance`), and **explode-aware**.\n\n```javascript\nconst shelf = box(200, 120, 10).withConnectors({\n tab: connector.male(\"dovetail\", { origin: [-100, 0, 5], axis: [-1, 0, 0], up: [0, 0, 1] }),\n});\nconst placed = shelf.matchTo(panel, \"tab\", \"shelf_slot\");\n```\n\nAlignment semantics, dictionary form, and dotted group paths: see `matchTo()` JSDoc. For cross-file part alignment, prefer connectors over `withReferences()` placement points.\n\n## 2. `group()` — local coordinates for multi-part assemblies\n\nBuild sub-parts at the **local origin**, group them, then translate the group **once** — never add a parent's global offset to every sub-part. Groups nest; each level has its own local origin. Groups cannot be booleaned: do subtract/intersect first in local coordinates, then group the result. Worked example: `group()` JSDoc.\n\n## 3. `pointAlong()` — orient before positioning\n\nAlways call `pointAlong()` **before** `matchTo()`/`translate()` — it reorients around the origin.\n\n## 4. `attachTo()` — rough bounding-box placement only\n\nAnchor points shift after fillet/chamfer/boolean: fine for quick prototyping, fragile for assembly interfaces — promote real interfaces to connectors.\n\n## 5. `placeReference()` — land a named anchor on a world coordinate\n\nGrounding, centering, edge alignment, custom reference points: see `placeReference()` JSDoc.\n\n## 6. Last resort: `rotateAroundTo()`, `moveToLocal()`, `translate()`\n\nFor computed offsets and free-floating or exploratory layout. Raw `translate()`/`rotate()` is correct only when parts are intentionally unrelated.\n\n## Mechanisms\n\nLink graphs (`link()`, `edgeBetweenLinks()`) solve **point positions** (closed loops); connector-frame joints (`assembly().connect()`) **orient physical parts**. Frame semantics and mirrored-revolute rules: assembly docs; joint geometry: `guides/joint-design.md`.\n\n## Primitive placement\n\nBox and cylinder sit base-at-Z=0, centered on XY; sphere and torus are fully centered. There is no OpenSCAD-style `center: true` — placement is fixed; use `placeReference('center', [0, 0, 0])` to fully center. Exact per-axis extents: primitive JSDoc.\n\n---\n\n<!-- generated/sketch.md -->\n\n# Sketch API\n\n2D geometry creation, transforms, booleans, constrained sketches, and extrusion.\n\n## Contents\n\n- [2D Sketch Primitives](#2d-sketch-primitives)\n- [2D Sketch Booleans](#2d-sketch-booleans)\n- [2D Text](#2d-text)\n- [Constrained Sketches](#constrained-sketches)\n- [Sketch](#sketch) — Transforms, Booleans, Features, Promotion, Placement, Labels, Measurement\n- [ConstrainedSketchBuilder](#constrainedsketchbuilder) — Drawing, Entities, Geometric Constraints, Dimensional Constraints, Coincidence & Equality, Tangent Transitions, Shape Constraints, Positioning, Solving\n- [ConstraintSketch](#constraintsketch)\n- [SketchGroupBuilder](#sketchgroupbuilder)\n- [Point2D](#point2d)\n- [Line2D](#line2d)\n- [Circle2D](#circle2d)\n- [Rectangle2D](#rectangle2d)\n\n## Functions\n\n### 2D Sketch Primitives\n\n#### `path(): PathBuilder` — Create a new [`PathBuilder`](/docs/curves#pathbuilder) for tracing a 2D outline point by point.\n\n[`PathBuilder`](/docs/curves#pathbuilder) is a fluent API for constructing 2D profiles using a mix of line segments, arcs, bezier curves, and splines. Always start with `.moveTo(x, y)` to set the starting point. Call `.close()` to get a filled `Sketch`, or `.stroke(width)` to thicken an open polyline into a solid profile.\n\nEdge labels can be assigned with `.label('name')` after any segment — they propagate through extrusion, revolve, loft, and sweep into named faces on the resulting [`Shape`](/docs/core#shape).\n\n```ts\n// Closed triangle\nconst triangle = path().moveTo(0, 0).lineH(50).lineV(30).close();\n\n// L-shaped bracket as a stroke\nconst bracket = path().moveTo(0, 0).lineH(50).lineV(-70).lineAngled(20, 235).stroke(4);\n\n// Labeled edges for downstream face references\nconst slot = path()\n .moveTo(0, 0)\n .lineTo(30, 0).label('bottom')\n .lineTo(30, 10)\n .lineTo(0, 10).label('top')\n .close();\n```\n\n#### `stroke(points: Vec2[], width: number, join?: \"Round\" | \"Square\"): Sketch` — Thicken a 2D polyline (centerline) into a solid filled profile of uniform width.\n\nStandalone equivalent of `path()...stroke(width, join)`. Use for centerline-based geometry — ribs, wire traces, brackets. For rounding corners of a *closed* outline use `.filletCorners(radius)` (all corners) or `.filletCorner([x, y], radius)` (one corner) instead.\n\n#### `rect(width: number, height: number): Sketch` — Create a 2D rectangle centered at the origin.\n\n```ts\nrect(40, 20).extrude(5);\n```\n\n#### `circle2d(radius: number, segments?: number): Sketch` — Create a 2D circle centered at the origin.\n\nOmit `segments` for a smooth (auto-tessellated) circle. Pass an integer to get a regular polygon approximation — e.g. `6` for a hexagon, `8` for an octagon.\n\n```ts\ncircle2d(25).extrude(10); // smooth cylinder\ncircle2d(25, 6).extrude(10); // hexagonal prism\n```\n\n#### `roundedRect(width: number, height: number, radius: number): Sketch` — Create a 2D rectangle with rounded corners, centered at the origin.\n\nThe corner radius is automatically clamped to `min(width/2, height/2)` so it can never exceed the shape dimensions.\n\n```ts\nroundedRect(60, 30, 5).extrude(3);\n```\n\n#### `polygon(points: (Vec2 | Point2D)[]): Sketch` — Create a 2D polygon from an array of `[x, y]` points or `Point2D` objects.\n\nWinding order is normalized automatically — clockwise (CW) input is silently reversed to CCW before being passed to the geometry kernel.\n\n```ts\npolygon([[0, 0], [50, 0], [25, 40]]).extrude(5); // triangle\n```\n\n#### `ngon(sides: number, radius: number): Sketch` — Create a regular polygon inscribed in a circle of the given radius.\n\n`radius` is the center-to-vertex (circumradius) distance. Use `sides` of `3` for a triangle, `6` for a hexagon, etc. The first vertex is at the top (−90° from +X).\n\n```ts\nngon(6, 20).extrude(10); // hexagonal prism, circumradius 20\n```\n\n#### `ellipse(rx: number, ry: number, segments?: number): Sketch` — Create a 2D ellipse centered at the origin.\n\n```ts\nellipse(30, 15).extrude(5);\nellipse(30, 15, 32).extrude(5); // lower-resolution approximation\n```\n\n#### `slot(length: number, width: number): Sketch` — Create a slot (oblong / stadium shape) — a rectangle with semicircular ends, centered at the origin.\n\n```ts\nslot(40, 10).extrude(3); // 40mm long, 10mm wide slot\n```\n\n#### `arcSlot(pitchRadius: number, sweepDeg: number, thickness: number): Sketch` — Create an arc-shaped slot (banana / annular sector) centered at the origin.\n\nThe slot is symmetric about the +X axis. The two ends are closed with semicircular caps. `pitchRadius` is the distance from the origin to the centerline of the slot, and `thickness` is the radial width of the slot.\n\n```ts\narcSlot(135, 74, 40).extrude(5); // pitch R135, 74° sweep, 40mm wide\n```\n\n### 2D Sketch Booleans\n\n#### `union2d(...inputs: SketchOperandInput[]): Sketch` — Combine 2D sketches into a single profile using an additive boolean union.\n\nAccepts individual sketches or arrays: `union2d(a, b, c)` or `union2d([a, b, c])`. Uses Manifold's batch operation — faster than chaining `.add()` one by one when combining many sketches.\n\n```ts\nconst cross = union2d(rect(60, 10), rect(10, 60));\n```\n\n#### `difference2d(...inputs: SketchOperandInput[]): Sketch` — Subtract one or more 2D sketches from a base sketch.\n\nThe first sketch is the base; all subsequent sketches are subtracted from it. Accepts individual sketches or arrays: `difference2d(base, c1, c2)` or `difference2d([base, c1, c2])`. Uses Manifold's batch operation — faster than chaining `.subtract()` one by one.\n\n```ts\nconst donut = difference2d(circle2d(50), circle2d(30));\n```\n\n#### `intersection2d(...inputs: SketchOperandInput[]): Sketch` — Keep only the area where all input sketches overlap (intersection boolean).\n\nAccepts individual sketches or arrays: `intersection2d(a, b)` or `intersection2d([a, b, c])`. Uses Manifold's batch operation — faster than chaining `.intersect()` one by one.\n\n```ts\nconst lens = intersection2d(circle2d(30).translate(-10, 0), circle2d(30).translate(10, 0));\n```\n\n### 2D Text\n\n#### `loadFont(source: string | ArrayBuffer, cacheKey?: string): opentype.Font` — Pre-load and cache a font for use with `text2d()`.\n\nFonts are cached by their source string (or `cacheKey` for `ArrayBuffer` sources), so repeated calls with the same path are free. Pre-loading is useful when you call `text2d()` many times with the same font — it avoids repeated disk reads.\n\nBuilt-in font names that work everywhere (browser + CLI):\n\n- `'sans-serif'` or `'inter'` — bundled Inter Regular\n\n```ts\nconst font = loadFont('/path/to/Arial Bold.ttf');\ntext2d('Title', { size: 12, font }).extrude(1.5);\ntext2d('Subtitle', { size: 8, font }).extrude(1);\n```\n\n#### `text2d(content: string, options?: TextOptions): Sketch` — Build a filled 2D Sketch from a text string.\n\nThe Sketch origin is at the left end of the text baseline by default. Use `align` and `baseline` options to adjust placement. Text is rendered using the bundled Inter font by default, or any TTF/OTF/WOFF font you provide.\n\n`text2d()` creates real geometry. For temporary viewport annotations, prefer `Viewport.label()` so the text stays off the geometry and OCCT compile paths. Do not use either form of text to make unclear production geometry readable; model the physical artifact clearly instead.\n\nAlignment reference table:\n\n| `align` | `baseline` | Origin |\n|------------|--------------|-------------------------------------|\n| `'left'` | `'baseline'` | Bottom-left of first char (default) |\n| `'center'` | `'center'` | Dead center of text block |\n| `'right'` | `'top'` | Top-right corner |\n\n```ts\n// Extruded nameplate\ntext2d('FORGE CAD', { size: 8 }).extrude(1.2);\n\n// Centered label on the XY plane\ntext2d('V 2.0', { size: 6, align: 'center', baseline: 'center' });\n\n// Engraved text cut into the top face of a box\nconst label = text2d('REV A', { size: 5, align: 'center', baseline: 'center' });\nplate.subtract(label.onFace(plate, 'top', { protrude: -0.5 }).extrude(1));\n\n// Custom TTF font\ntext2d('Hello', { size: 10, font: '/path/to/Arial.ttf' }).extrude(1);\n\n// Pre-loaded font for reuse\nconst font = loadFont('/path/to/Arial Bold.ttf');\ntext2d('Title', { size: 12, font }).extrude(1.5);\n```\n\n**`TextOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `size?` | `number` | Cap height of the text in model units. All other dimensions (stroke weight, spacing) scale proportionally. |\n| `letterSpacing?` | `number` | Extra space between characters in model units. Negative values tighten the tracking. |\n| `align?` | `\"left\" \\| \"center\" \\| \"right\"` | Horizontal alignment relative to x = 0. - `'left'` — left edge at x = 0 (default) - `'center'` — centred on x = 0 - `'right'` — right edge at x = 0 |\n| `baseline?` | `\"baseline\" \\| \"center\" \\| \"top\"` | Vertical alignment relative to y = 0. - `'baseline'` — y = 0 is the text baseline (bottom of capital letters) - `'center'` — y = 0 is the vertical midpoint of the cap height - `'top'` — y = 0 is the top of capital letters |\n| `font?` | `string \\| opentype.Font` | Font to use for text rendering. - `'sans-serif'` or `'inter'` — bundled Inter font (works everywhere, including browser) - **file path** — path to a TTF, OTF, or WOFF font file (CLI/Node only) - **Font object** — a previously loaded opentype.js Font (from `loadFont()`) - **omitted** — uses the bundled Inter font (same as `'sans-serif'`) |\n| `flattenTolerance?` | `number` | Bezier flattening tolerance in model units. Smaller = more polygon segments = smoother curves. |\n\n#### `textWidth(content: string, options?: Pick<TextOptions, \"size\" | \"letterSpacing\" | \"font\">): number` — Measure the rendered advance width of a string without creating any geometry.\n\nUses the same font metrics as `text2d()`. Useful for computing layout dimensions before building the actual sketch — e.g. sizing a plate to fit a label.\n\n```ts\nconst w = textWidth('SERIAL: 001', { size: 6 });\nconst plate = box(w + 10, 12, 2);\n```\n\n### Constrained Sketches\n\n#### `constrainedSketch(options?: ConstrainedSketchOptions): ConstrainedSketchBuilder` — Create a parametric 2D sketch driven by geometric constraints and a nonlinear solver.\n\n**Workflow**\n\n1. Create a builder with `constrainedSketch()`.\n2. Add geometry — points, lines, circles, arcs — using the builder methods.\n3. Add constraints (`horizontal`, `length`, `fix`, etc.) to drive the geometry.\n4. Call `.solve()` to run the solver and get a `ConstraintSketch` (which extends `Sketch`).\n\n```ts\nconst sk = constrainedSketch();\nconst p1 = sk.point(0, 0);\nconst p2 = sk.point(50, 0);\nconst l1 = sk.line(p1, p2);\nsk.fix(p1, 0, 0);\nsk.horizontal(l1);\nsk.length(l1, 50);\nreturn sk.solve().extrude(10);\n```\n\n**Solver status**\n\n```ts\nconst result = sk.solve();\nresult.constraintMeta.status; // 'fully' | 'under' | 'over' | 'over-redundant'\nresult.constraintMeta.dof; // 0 = fully constrained\nresult.constraintMeta.maxError; // residual — should be < 1e-6\nresult.inspect(); // human-readable summary\nresult.withUpdatedConstraint('cst-5', 120); // update a dimension without rebuilding\n```\n\n**`ConstrainedSketchOptions`**\n- `strict?: boolean` — When true, adding a constraint that cannot be satisfied throws instead of silently discarding it.\n\n---\n\n## Classes\n\n### `Sketch`\n\nImmutable 2D profile for extrusion, revolve, and other operations.\n\n`Sketch` wraps Manifold's `CrossSection` with a chainable 2D API. Every method returns a new `Sketch` — the original is never mutated. Colors, edge labels, and placement data are preserved through all transforms and boolean operations.\n\nSupported operations:\n\n- **Transforms** — `translate`, `rotate`, `rotateAround`, `scale`, `mirror`\n- **Booleans** — `add` (union), `subtract` (difference), `intersect`\n- **Operations** — `offset`, `simplify`, `filletCorners`, `filletCorner`, `chamferCorners`, `chamferCorner`\n- **Queries** — `area`, `bounds`, `isEmpty`, `numVert`\n- **3D operations** — `extrude`, `revolve`, `onFace`\n- **Regions** — `regions`, `region`\n- **Placement** — `attachTo`\n\nNamed anchor positions used by `attachTo()`: `'center'` | `'top-left'` | `'top-right'` | `'bottom-left'` | `'bottom-right'` | `'top'` | `'bottom'` | `'left'` | `'right'`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `cross` | `ProfileBackend` | — |\n\n**Transforms**\n\n#### `translate(x: number, y?: number): Sketch` — Move the sketch by the given X and Y offset.\n\n#### `rotate(degrees: number): Sketch` — Rotate the sketch around its bounding-box center.\n\n#### `rotateAround(degrees: number, pivot: Vec2): Sketch` — Rotate the sketch around a specific pivot point.\n\n```ts\nrect(20, 20).rotateAround(45, [0, 0]);\n```\n\n#### `scale(v: number | Vec2): Sketch` — Scale the sketch relative to its bounding-box center.\n\nPass a single number for uniform scaling, or `[sx, sy]` for per-axis scaling.\n\n#### `scaleAround(pivot: Vec2, v: number | Vec2): Sketch` — Scale the sketch relative to an arbitrary pivot point.\n\n#### `mirror(normal: Vec2): Sketch` — Mirror the sketch across a line through its bounding-box center.\n\n`normal` is the normal vector of the mirror line (not the line direction). For example, `[1, 0]` mirrors across a vertical line (Y axis direction), and `[0, 1]` mirrors across a horizontal line.\n\n#### `mirrorThrough(point: Vec2, normal: Vec2): Sketch` — Mirror the sketch across a line defined by a point and a normal direction.\n\n**Booleans**\n\n#### `add(...others: SketchOperandInput[]): Sketch` — Add (union) one or more sketches to this sketch.\n\nAccepts individual sketches or arrays: `sketch.add(a, b)` or `sketch.add([a, b])`. For combining many sketches at once, prefer the free function `union2d()` which uses Manifold's batch operation and is faster than chaining.\n\n```ts\ncircle2d(20).add(rect(10, 30)).extrude(5);\n```\n\n#### `subtract(...others: SketchOperandInput[]): Sketch` — Subtract one or more sketches from this sketch.\n\nAccepts individual sketches or arrays: `sketch.subtract(a, b)` or `sketch.subtract([a, b])`. For subtracting many cutters at once, prefer the free function `difference2d()`.\n\n```ts\nrect(40, 40).subtract(circle2d(10)).extrude(5);\n```\n\n#### `intersect(...others: SketchOperandInput[]): Sketch` — Intersect this sketch with one or more others (keep overlapping area only).\n\nAccepts individual sketches or arrays: `sketch.intersect(a, b)` or `sketch.intersect([a, b])`. For intersecting many sketches, prefer the free function `intersection2d()`.\n\n**Features**\n\n#### `offset(delta: number, join?: \"Square\" | \"Round\" | \"Miter\"): Sketch` — Inflate (positive delta) or deflate (negative delta) the sketch contour.\n\nFor rounding corners, prefer `filletCorners(radius)` (all corners) or `filletCorner([x, y], radius)` (one corner) — they round only true corners and keep concave geometry exact.\n\n- `'Round'` — smooth arc at each corner (default)\n- `'Square'` — flat mitered extension\n- `'Miter'` — sharp pointed extension\n\n```ts\nrect(40, 20).offset(3); // expand by 3\n```\n\n#### `filletCorners(radius: number): Sketch` — Round every significant corner of this sketch with the same fillet radius.\n\nWorks on any sketch — primitives, boolean results, attached or composed profiles. A vertex counts as a corner when its turn angle is at least 30°, so vertices that belong to tessellated circles or earlier fillets are left untouched. Both convex and concave corners are rounded. Holes are preserved, and their corners are rounded too.\n\nThrows if the sketch has no significant corners, or if the radius does not fit a corner (the error names the corner and the maximum radius).\n\n```ts\nrect(60, 30).filletCorners(5).extrude(4); // rounded plate\npolygon(bracketPts).filletCorners(3); // every corner of an outline\n```\n\n#### `filletCorner(at: Vec2, radius: number): Sketch` — Round the single corner of this sketch nearest to a seed point.\n\nSelects the significant corner (turn angle ≥ 30°) closest to `at` — the seed does not need to be exact, just nearer to the intended corner than to any other (like `region([x, y])` seed selection). Throws if two corners are equidistant from the seed, naming both so you can move the seed.\n\nChain calls to round several corners with different radii.\n\n```ts\npolygon(roofPts)\n .filletCorner([45, 86], 14) // peak\n .filletCorner([24, 74], 8); // left shoulder\n```\n\n#### `chamferCorners(size: number): Sketch` — Bevel every significant corner of this sketch with a straight chamfer.\n\nReplaces each significant corner (turn angle ≥ 30°) with a straight cut set back `size` along both adjacent edges. Tessellated arcs are left untouched; holes are preserved. Throws if the sketch has no significant corners or the size does not fit a corner.\n\n```ts\nrect(60, 30).chamferCorners(4).extrude(4); // beveled plate outline\n```\n\n#### `chamferCorner(at: Vec2, size: number): Sketch` — Bevel the single corner of this sketch nearest to a seed point.\n\nSelects the significant corner (turn angle ≥ 30°) closest to `at` and replaces it with a straight cut set back `size` along both adjacent edges. Throws if two corners are equidistant from the seed. Chain calls to bevel several corners with different sizes.\n\n```ts\nrect(60, 30).chamferCorner([30, 15], 6); // bevel only the top-right corner\n```\n\n#### `regions(): Sketch[]` — Decompose this sketch into its distinct filled regions, sorted largest-first by area.\n\nA single sketch can contain several disconnected filled areas (e.g., two separate rectangles, or a ring shape with a hole). This method enumerates all top-level connected regions as independent `Sketch` objects, each with its own outer boundary and associated holes.\n\n```ts\nconst pair = union2d(rect(40, 40), rect(40, 40).translate(60, 0));\nconst [left, right] = pair.regions(); // largest first\nleft.extrude(5);\n```\n\n#### `region(seed: Vec2): Sketch` — Select the single filled region that contains the given 2D seed point.\n\nThe seed must lie strictly inside the filled area — not on a boundary edge and not inside a hole. Throws a descriptive error if the seed is outside all regions. If unsure where regions are, use `.regions()` first — each result has `.bounds()`.\n\n```ts\nconst donut = circle2d(50).subtract(circle2d(30));\ndonut.region([40, 0]).extrude(10); // seed at radius 40, inside the ring\n```\n\n**Promotion**\n\n#### `extrude(height: number, opts?: { twist?: number; divisions?: number; scaleTop?: number | Vec2; }): Shape` — Extrude this 2D sketch along Z to create a 3D solid. Supports twist and scale tapering.\n\n#### `revolve(degrees?: number, segments?: number): Shape` — Revolve this 2D sketch around the world Z axis. Sketch X is radius; sketch Y becomes world Z height. Keep the profile at X > 0 unless it intentionally touches the axis.\n\n**Placement**\n\n#### `attachTo(target: Sketch, targetAnchor: Anchor, selfAnchor?: Anchor, offset?: Vec2): Sketch` — Position this sketch relative to another using named anchor points.\n\nComputes the translation needed to align `selfAnchor` on this sketch with `targetAnchor` on the target sketch, then applies an optional pixel-exact offset.\n\nAnchor positions: `'center'` | `'top-left'` | `'top-right'` | `'bottom-left'` | `'bottom-right'` | `'top'` | `'bottom'` | `'left'` | `'right'`\n\n```ts\nconst arm = rect(4, 70).attachTo(plate, 'bottom-left', 'top-left');\nconst shifted = rect(4, 70).attachTo(plate, 'bottom-left', 'top-left', [5, 0]);\n```\n\n#### `onFace(parentOrFace: Shape | { toShape(): Shape; } | { _bbox(): { min: number[]; max: number[]; }; } | FaceRef, faceOrOpts?: \"front\" | \"back\" | \"left\" | \"right\" | \"top\" | \"bottom\" | string | FaceRef | { u?: number; v?: number; protrude?: number; selfAnchor?: Anchor; }, opts?: { u?: number; v?: number; protrude?: number; selfAnchor?: Anchor; }): Sketch` — Place this sketch on a face or planar target in 3D space.\n\nUse this when a 2D profile should be oriented onto a 3D face before extrusion or other downstream operations.\n\n`FaceRef` — defined in [core](/docs/core).\n\n**Labels**\n\n#### `labelEdge(name: string): Sketch` — Label the single boundary edge (for circles, single-loop profiles). Returns a new sketch.\n\n#### `labelEdges(...args: (string | null)[] | [ Record<string, string> ]): Sketch` — Label edges in winding order, or by named map for rect.\n\nPositional: `labelEdges('bottom', 'right', 'top', 'left')` — one per edge, `null` to skip. Named (rect only): `labelEdges({ bottom: 'floor', top: 'ceiling' })`. Returns a new sketch.\n\n#### `edgeLabels(): string[]` — List current edge label names.\n\n#### `prefixLabels(prefix: string): Sketch` — Prefix all edge labels. Returns a new sketch with prefixed labels.\n\n#### `renameLabel(from: string, to: string): Sketch` — Rename a single edge label. Returns a new sketch.\n\n#### `dropLabels(...names: string[]): Sketch` — Remove specific labels. Returns a new sketch.\n\n#### `dropAllLabels(): Sketch` — Remove all labels. Returns a new sketch.\n\n**Measurement**\n\n#### `area(): number` — Return the total filled area of the sketch.\n\n#### `bounds(): ProfileBounds` — Return the axis-aligned bounding box of the sketch.\n\n#### `isEmpty(): boolean` — Return `true` if the sketch contains no filled area.\n\n#### `numVert(): number` — Return the number of vertices in the polygon representation of the sketch contours.\n\n#### `toPolygons(): number[][][]` — Return the sketch as a list of polygons matching its contour topology.\n\nUseful when you need raw polygon data for inspection or custom export.\n\n**Other**\n\n#### `color(value: string | undefined): Sketch` — Set the display color of this sketch.\n\nColor is preserved through all transforms and boolean operations. Pass `undefined` to clear the color.\n\n```ts\ncircle2d(20).color('#ff0000').extrude(5);\n```\n\n#### `clone(): Sketch` — Create an explicit copy of this sketch for branching variants.\n\nBecause all Sketch operations are immutable, `clone()` is rarely needed. Use it when you want to assign the same sketch to multiple names and continue modifying each independently without confusion.\n\n### `ConstrainedSketchBuilder`\n\n**Drawing**\n\n#### `moveTo(x: number, y: number): this` — Move the cursor to `(x, y)` and start a new profile loop.\n\n#### `lineTo(x: number, y: number): this` — Draw a line from the current cursor to `(x, y)`.\n\n#### `lineH(dx: number): this` — Draw a horizontal line of length `dx` from the current cursor.\n\n#### `lineV(dy: number): this` — Draw a vertical line of length `dy` from the current cursor.\n\n#### `lineAngled(length: number, degrees: number): this` — Draw a line of the given `length` at `degrees` from +X.\n\n#### `arcTo(x: number, y: number, radius: number, clockwise?: boolean): this` — Draw a circular arc from the current cursor to `(x, y)` with the given radius.\n\n#### `arcByCenter(centerId: PointId, startId: PointId, endId: PointId, clockwise?: boolean, name?: string, fixedRadius?: boolean): ArcId` — Create an arc from an explicit center point and endpoint IDs.\n\n#### `bezier(p0: any, p1: any, p2: any, p3: any, name?: string): BezierId` — Create a cubic Bezier curve from four control points.\n\n#### `bezierTo(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number): this` — Draw a cubic Bezier from the current cursor to `(x3, y3)`.\n\n#### `blendTo(x: number, y: number, weight?: number): this` — Draw a smooth Bezier tangent to the previous arc.\n\n#### `label(name: string): this` — Label the current path segment.\n\n#### `close(): this` — Close the current path and register the loop.\n\n#### `addLoopCircle(center: PointId, radius: number, segments?: number): this` — Add a circle loop to the path.\n\n#### `addLoop(points: any[]): this` — Add a closed polygon loop from point IDs.\n\n#### `addProfileLoop(segments: Array<{ kind: \"line\"; line: any; } | { kind: \"arc\"; arc: any; } | { kind: \"bezier\"; bezier: any; }>): this` — Add a profile loop from prebuilt line/arc/bezier segments.\n\n**Entities**\n\n#### `point(x?: number, y?: number, fixed?: boolean): PointId` — Add a free point to the sketch at `(x, y)`.\n\nIf `x` or `y` are omitted, the point is placed at the bounding-box center of existing geometry so it starts near other entities rather than at the origin. Throws if either coordinate is `NaN` or `Infinity`.\n\n#### `pointAt(index: number): PointId` — Return the `PointId` of the point created at the given insertion index.\n\n#### `line(a: PointId, b: PointId, construction?: boolean, name?: string): LineId` — Connect two existing points with a line segment.\n\nPass `construction = true` for a helper line that participates in constraints but is excluded from the solved sketch output (not part of any profile loop).\n\n```ts\nconst axis = sk.line(sk.point(0, -50), sk.point(0, 50), true);\nsk.symmetric(p1, p2, axis);\n```\n\n#### `lineAt(index: number): LineId` — Return the `LineId` of the line created at the given insertion index.\n\n#### `circle(center: PointId, radius: number, construction?: boolean, segments?: number, name?: string): CircleId` — Add a circle to the sketch with the given center point and initial radius.\n\nThe radius is a starting value — if you add a `radius()` or `diameter()` constraint, the solver will adjust it. Non-construction circles automatically register a loop.\n\n#### `circleAt(index: number): CircleId` — Return the `CircleId` of the circle created at the given insertion index.\n\n#### `shape(lines: LineId[]): ShapeId` — Register a named shape (closed polygon) from an ordered list of line IDs.\n\nThe `ShapeId` can be passed to `shapeWidth()`, `shapeHeight()`, `shapeArea()`, `shapeCentroidX()`, `shapeCentroidY()`, and `shapeEqualCentroid()` constraints. Shape registration is done automatically by concept factories like `rect()` and `addPolygon()`.\n\n#### `group(opts?: { x?: number; y?: number; theta?: number; id?: string; }): SketchGroupBuilder` — Create a rigid-body group with a local coordinate frame.\n\nPoints and lines added to the group move together as a unit — the solver sees 3 DOF (x, y, θ) instead of 2N per point. After configuring the group, call `.done()` to register it and receive a `SketchGroupHandle`.\n\nGroup points are addressable by their `PointId` in all sketch constraints (e.g. `sk.coincident`, `sk.distance`) just like any other points.\n\n```ts\nconst g = sk.group({ x: 50, y: 30 });\nconst p0 = g.point(0, 0); // local origin → world (50, 30)\nconst p1 = g.point(100, 0); // local (100,0) → world (150, 30)\nconst l = g.line(p0, p1);\ng.fixRotation();\nconst handle = g.done();\n// p0, p1 work in constraints like any other PointId:\nsk.coincident(p0, someExternalPoint);\n```\n\n#### `rect(options?: RectOptions): ConstrainedRect` — Add an axis-aligned rectangle concept. Returns a `ConstrainedRect` handle with named vertices, sides, and center.\n\n**`RectOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `x?` | `number` | Bottom-left x coordinate. Default: 0. |\n| `y?` | `number` | Bottom-left y coordinate. Default: 0. |\n| `width?` | `number` | Width (along x). Default: 10. |\n| `height?` | `number` | Height (along y). Default: 10. |\n| `blockRotation?` | `boolean` | Prevent 180° rotation (ensures bottom edge points rightward). Default: false. |\n\n#### `addPolygon(options: PolygonOptions): ConstrainedPolygon` — Add a general polygon concept (CCW winding enforced). Returns a `ConstrainedPolygon` handle.\n\n**`PolygonOptions`**\n- `points: ReadonlyArray<readonly Vec2>` — Initial vertex coordinates. Minimum 3 points.\n- `addLoop?: boolean` — Whether to register a closed loop for sketch generation. Default: true.\n- `blockRotation?: boolean` — Prevent 180° rotation (ensures first edge maintains its initial direction). Default: false.\n\n#### `regularPolygon(options: RegularPolygonOptions): ConstrainedRegularPolygon` — Add a regular n-gon concept (equal sides, CCW winding). Returns a `ConstrainedRegularPolygon` handle with a center point.\n\n**`RegularPolygonOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `sides` | `number` | Number of sides (minimum 3). |\n| `radius?` | `number` | Circumradius — distance from center to vertex. Default: 10. |\n| `cx?` | `number` | Center x coordinate. Default: 0. |\n| `cy?` | `number` | Center y coordinate. Default: 0. |\n| `startAngle?` | `number` | Angle (in degrees) of vertex[0] measured from the +X axis (CCW positive). Default: 0 (rightmost vertex). |\n| `blockRotation?` | `boolean` | Prevent 180° rotation (ensures first edge maintains its initial direction). Default: false. |\n\n#### `groupRect(options: GroupRectOptions): ConstrainedGroupRect` — Add a rigid rectangle as a group concept. Returns a `ConstrainedGroupRect` handle with named vertices and sides. The rectangle is fixed in shape — only position (and optionally rotation) varies.\n\n**`GroupRectOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `x?` | `number` | Bottom-left x coordinate (world). Default: 0. |\n| `y?` | `number` | Bottom-left y coordinate (world). Default: 0. |\n| `width` | `number` | Width (along x in local coords). Required. |\n| `height` | `number` | Height (along y in local coords). Required. |\n| `allowRotation?` | `boolean` | Allow the solver to rotate this rectangle. Default: false. |\n\n**Geometric Constraints**\n\n#### `horizontal(line: any): this` — Constrain a line to be horizontal (parallel to the X axis).\n\n#### `vertical(line: any): this` — Constrain a line to be vertical (parallel to the Y axis).\n\n#### `parallel(a: any, b: any): this` — Constrain two lines to be parallel.\n\n#### `sameDirection(a: any, b: any): this` — Constrain two lines to point in the same direction.\n\n#### `oppositeDirection(a: any, b: any): this` — Constrain two lines to point in opposite directions.\n\n#### `perpendicular(a: any, b: any): this` — Constrain two lines to be perpendicular.\n\n#### `tangent(a: any, b: any): this` — Constrain a line/circle or circle/circle tangency relationship.\n\n#### `collinear(point: any, line: any): this` — Constrain a point to lie on the infinite extension of a line.\n\n#### `symmetric(a: any, b: any, axis: any): this` — Constrain two points to be symmetric about an axis line.\n\n#### `blockRotation(points: any[], axis?: \"x\" | \"y\"): this` — Prevent 180° rotation of a polygon by anchoring its first edge.\n\n**Dimensional Constraints**\n\n#### `distance(a: any, b: any, value: number): this` — Constrain the Euclidean distance between two points.\n\n#### `length(line: any, value: number): this` — Constrain the length of a line segment.\n\n#### `angle(a: any, b: any, value: number): this` — Constrain the signed angle from line `a` to line `b`.\n\n#### `radius(circle: any, value: number): this` — Constrain the radius of a circle.\n\n#### `diameter(circle: any, value: number): this` — Constrain the diameter of a circle.\n\n#### `hDistance(a: any, b: any, value: number): this` — Constrain the horizontal distance between two points.\n\n#### `vDistance(a: any, b: any, value: number): this` — Constrain the vertical distance between two points.\n\n#### `pointLineDistance(point: any, line: any, value: number): this` — Constrain the signed perpendicular distance from a point to a line.\n\n#### `lineDistance(a: any, b: any, value: number): this` — Constrain the perpendicular offset distance between two lines.\n\n#### `absoluteAngle(line: any, value: number): this` — Constrain the absolute angle of a line measured from +X.\n\n#### `arcLength(arc: any, value: number): this` — Constrain the arc length of an arc.\n\n#### `equalRadius(a: any, b: any): this` — Constrain two circles to have equal radii.\n\n#### `angleBetween(a: any, b: any, value: number): this` — Constrain the unsigned angle between two lines.\n\n**Coincidence & Equality**\n\n#### `equal(a: any, b: any): this` — Constrain two lines to have equal length.\n\n#### `coincident(a: any, b: any): this` — Constrain two points to coincide.\n\n#### `concentric(a: any, b: any): this` — Constrain two circles to share a center.\n\n#### `fix(point: any, x?: number, y?: number): this` — Pin a point at a specific world location.\n\n#### `midpoint(point: any, line: any): this` — Constrain a point to lie at the midpoint of a line.\n\n#### `pointOnCircle(point: any, circle: any): this` — Constrain a point to lie on the perimeter of a circle.\n\n#### `pointOnLine(point: any, line: any): this` — Constrain a point to lie on the bounded segment of a line.\n\n#### `ccw(...points: any[]): this` — Constrain all given points to be in counter-clockwise order.\n\n**Tangent Transitions**\n\n#### `lineTangentArc(line: any, arc: any, atStart: boolean): this` — Constrain a line to be tangent to an arc at its start or end point.\n\n#### `arcTangentArc(arcA: any, arcB: any, aAtStart?: boolean, bAtStart?: boolean): this` — Constrain two arcs to be tangent at their shared junction point.\n\n#### `bezierTangentArc(bezier: any, arc: any, atBezierStart: boolean, atArcStart: boolean): this` — Constrain a Bezier to be tangent to an arc at one endpoint.\n\n#### `smoothBlend(arc1: any, arc2: any, options?: { weight?: number; arc1End?: \"start\" | \"end\"; arc2End?: \"start\" | \"end\"; }): BezierId` — Create a Bezier blend between two arcs.\n\n**Shape Constraints**\n\n#### `shapeWidth(shape: any, value: number): this` — Constrain a shape's width.\n\n#### `shapeHeight(shape: any, value: number): this` — Constrain a shape's height.\n\n#### `shapeCentroidX(shape: any, value: number): this` — Constrain a shape's centroid X position.\n\n#### `shapeCentroidY(shape: any, value: number): this` — Constrain a shape's centroid Y position.\n\n#### `shapeArea(shape: any, value: number): this` — Constrain a shape's area.\n\n#### `shapeEqualCentroid(a: any, b: any): this` — Constrain two shapes to have the same centroid.\n\n**Positioning**\n\n#### `offsetX(a: any, b: any, value: number): this` — Constrain the horizontal (X-axis) offset between two lines. Uses the start-point of each line to measure horizontal distance. `value` is the signed distance: b.startPt.x − a.startPt.x = value.\n\n#### `offsetY(a: any, b: any, value: number): this` — Constrain the vertical (Y-axis) offset between two lines. Uses the start-point of each line to measure vertical distance. `value` is the signed distance: b.startPt.y − a.startPt.y = value.\n\n#### `referencePoint(x: number, y: number): PointId` — Add a fixed reference point at `(x, y)`.\n\n#### `referenceLine(x1: number, y1: number, x2: number, y2: number): LineId` — Add a fixed reference line from `(x1, y1)` to `(x2, y2)`.\n\n#### `referenceFrom(source: ConstraintSketch, entityId: string): PointId | LineId | null` — Import a single named entity from a solved sketch as fixed reference geometry.\n\n#### `referenceAllFrom(source: ConstraintSketch): { points: Map<string, PointId>; lines: Map<string, LineId>; }` — Import all non-construction entities from a solved sketch as fixed references.\n\n**Solving**\n\n#### `constrain(constraint: Omit<SketchConstraint, \"id\">): this` — Add a raw constraint object to the builder.\n\n#### `solve(options?: SolveOptions): ConstraintSketch | Sketch` — Run the constraint solver and return a solved sketch.\n\nThe returned `ConstraintSketch` extends `Sketch` and can be used directly in all 3D operations (`extrude`, `revolve`, etc.). It also exposes `constraintMeta` with the solver status:\n\n```ts\nconst result = sk.solve();\nresult.constraintMeta.status; // 'fully' | 'under' | 'over' | 'over-redundant'\nresult.constraintMeta.dof; // 0 = fully constrained\nresult.constraintMeta.maxError; // residual — should be < 1e-6\nresult.inspect(); // human-readable summary\nresult.withUpdatedConstraint('cst-5', 120); // update a dimension without rebuilding\n```\n\n**Troubleshooting**\n\n- **Under-constrained (dof > 0)** — add `fix()`, `length()`, or other dimensional constraints.\n- **Over-constrained** — conflicting constraints are auto-rejected. Check `result.constraintMeta.constraints` and `result.inspect()`.\n- **maxError > 1e-6** — solver did not converge; check for contradictory constraints.\n\n**`SolveOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `iterations?` | `number` | Maximum number of LM outer iterations per restart. |\n| `tolerance?` | `number` | Infinity-norm residual tolerance for declaring convergence. |\n| `restarts?` | `number` | Number of deterministic restart seeds used by the global solver. |\n| `warmStartIterations?` | `number` | Optional projector iterations used only for initialisation, not as the main solver. |\n| `maxScaledStep?` | `number` | Maximum LM step length in scaled variable space. Larger = bolder, smaller = safer. |\n| `skipRedundancyCheck?` | `boolean` | Skip redundancy detection (safe when topology is unchanged and previous DOF >= 0). |\n| `presolveConstraintId?` | `string` | Run the targeted presolve hook for this constraint before the main solve. |\n| `fallbackRestarts?` | `number` | When set and the first solve exceeds tolerance*5, retry with this many restarts. |\n| `progressive?` | `boolean` | Add constraints progressively with short LM solves, all in one WASM call. |\n| `timeBudgetMs?` | `number` | Wall-clock time budget in ms for the entire solve. 0 = no limit. |\n| `debugConstructiveTranscript?` | `boolean` | Capture a readable constructive transcript in `constraintMeta.debug`. |\n| `debugSvgSnapshots?` | `boolean` | Capture SVG snapshots for constructive steps in `constraintMeta.debug`. |\n\n#### `solveConstraintsOnly(options?: SolveOptions): { maxError: number; rejectedCount: number; definition: ConstraintDefinition; }` — Run the solver without building a full `ConstraintSketch`.\n\nLighter than `solve()` — skips profile and DOF analysis. Useful for lightweight constraint validation or progress monitoring mid-construction.\n\n#### `route(x: number, y: number): RouteBuilder` — Start a directional route from coordinates.\n\nReturns a [`RouteBuilder`](/docs/viewport#routebuilder) - describe the path with up/down/left/right/arcLeft/arcRight. Each method returns the entity ID (`LineId` or `ArcId`) for use in `sk.*` constraints.\n\n```js\nconst r = sk.route(0, 0);\nconst stem = r.up(18);\nr.arcLeft(8.9);\nconst neck = r.down();\nr.done();\nsk.offsetX(stem, neck, 10.8);\n```\n\n### `ConstraintSketch`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `constraintMeta` | `SketchConstraintMeta` | — |\n| `definition` | `ConstraintDefinition` | — |\n\n**Methods:**\n\n#### `detectArrangement(): Sketch[]` — Enumerate all bounded regions formed by the line arrangement of this sketch. Construction lines are excluded. Regions are returned largest-first by area.\n\n#### `detectArrangementRegion(_seed: Vec2): Sketch` — Select the single arrangement region that contains the given seed point. Throws if no region contains the seed.\n\n#### `toPolyline(samples?: number): Vec2[]` — Return the solved constrained path as a sampled 2D polyline.\n\nUse this when a construction rail was authored with `constrainedSketch()` and should feed another operation such as `Loft.pathOnXz(...)`. The sketch must contain exactly one profile path.\n\n#### `withUpdatedConstraint(constraintId: string, value: number): ConstraintSketch` — Re-solve the sketch after changing the value of one existing constraint.\n\nUse this for interactive dimension edits without rebuilding the whole sketch graph. It attempts a warm-started solve first, then falls back to a full solve if needed.\n\n#### `inspect(): string` — Return a human-readable diagnostic string of the solved state.\n\n### `SketchGroupBuilder`\n\n#### `point(lx: number, ly: number): PointId` — Add a point in local coordinates. Returns its globally-addressable PointId.\n\n#### `line(a: PointId, b: PointId, name?: string): LineId` — Connect two group points with a line. Both must be PointIds from this group.\n\n#### `fixRotation(): this` — Freeze rotation (theta). Group can still translate - 2 DOF remain.\n\n#### `fix(): this` — Freeze all 3 DOF - group is completely fixed.\n\n#### `done(): SketchGroupHandle` — Finalize and register the group with the builder.\n\n### `Point2D`\n\nAn immutable 2D point with measurement and construction helpers.\n\nUsed as construction geometry in sketches, constraints, and analytic measurements. All methods return new instances — `Point2D` is immutable.\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `x` | `number` | — |\n| `y` | `number` | — |\n\n**Methods:**\n\n#### `distanceTo(other: Point2D): number` — Measure straight-line distance to another point.\n\n#### `midpointTo(other: Point2D): Point2D` — Compute the midpoint between this point and another point.\n\n#### `translate(dx: number, dy: number): Point2D` — Return a point shifted by the given delta.\n\n#### `toTuple(): Vec2` — Convert this point to a plain `[x, y]` tuple.\n\n### `Line2D`\n\nAn immutable 2D line segment with length, angle, intersection, and parallel helpers.\n\nProvides both segment-only (`intersectSegment`) and infinite-line (`intersect`) intersection queries. All methods return new instances.\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `start` | `Point2D` | — |\n| `end` | `Point2D` | — |\n\n**Methods:**\n\n#### `get length(): number` — Length of the line segment.\n\n#### `get midpoint(): Point2D` — Midpoint of the line segment.\n\n#### `get angle(): number` — Direction angle in degrees, measured CCW from +X.\n\n#### `get direction(): Vec2` — Unit direction vector from start to end.\n\n#### `parallel(distance: number): Line2D` — Create a parallel line offset by the given distance.\n\nPositive distance shifts to the left of the line direction.\n\n#### `intersect(other: Line2D): Point2D | null` — Intersect this line with another infinite line.\n\n#### `intersectSegment(other: Line2D): Point2D | null` — Intersect this line with another as bounded segments.\n\n#### `static fromCoordinates(x1: number, y1: number, x2: number, y2: number): Line2D` — Create a line from raw coordinates.\n\n#### `static fromPointAndAngle(origin: Point2D, angleDeg: number, length: number): Line2D` — Create a line from a start point, angle, and length.\n\n#### `static fromPointAndDirection(origin: Point2D, dir: Vec2, length: number): Line2D` — Create a line from a start point, direction vector, and length.\n\n### `Circle2D`\n\nAn immutable 2D circle with area, circumference, and extrusion support.\n\nExtruding a `Circle2D` produces a cylinder with named `top`, `bottom`, and `side` faces accessible via the topology API.\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `center` | `Point2D` | — |\n| `radius` | `number` | — |\n\n**Methods:**\n\n#### `get diameter(): number` — Diameter of the circle.\n\n#### `get circumference(): number` — Circumference of the circle.\n\n#### `get area(): number` — Area of the circle.\n\n#### `pointAtAngle(angleDeg: number): Point2D` — Return a point on the circle at the given angle.\n\n#### `translate(dx: number, dy: number): Circle2D` — Return a translated circle.\n\n#### `toSketch(segments?: number): Sketch` — Convert this circle to a sketch profile.\n\n#### `extrude(height: number, segments?: number): Shape` — Extrude the circle into a solid cylinder.\n\n#### `static fromCenterAndRadius(center: Point2D, radius: number): Circle2D` — Create a circle from its center and radius.\n\n#### `static fromDiameter(center: Point2D, diameter: number): Circle2D` — Create a circle from its center and diameter.\n\n### `Rectangle2D`\n\nA rectangle with named sides, vertices, and extrusion support.\n\nSides are named based on the rectangle's local orientation at construction time. Vertices go: bottom-left, bottom-right, top-right, top-left (CCW).\n\nUse `rect()` for the normal centered sketch primitive. Use `Rectangle2D` when you need named sides/vertices, or an extrusion with tracked vertical edges such as `vert-br` for `filletTrackedEdge()` / `chamferTrackedEdge()`.\n\nExtruding a `Rectangle2D` produces a [`Shape`](/docs/core#shape) with named faces: `top`, `bottom`, `side-left`, `side-right`, `side-top`, `side-bottom`. These are accessible via the topology API (`.face()`, `.edge()`).\n\n```ts\nconst r = Rectangle2D.fromDimensions(0, 0, 100, 60);\nr.side('top'); r.side('left'); // Line2D\nr.vertex('top-left'); // Point2D\nr.width; r.height; r.center;\nconst [d1, d2] = r.diagonals(); // [bl-tr, br-tl]\n\nr.toSketch(); // Sketch (for 2D operations)\nr.extrude(20); // Shape with named faces\n\nRectangle2D.fromCenterAndDimensions(new Point2D(50, 30), 100, 60);\nRectangle2D.from2Corners(new Point2D(0, 0), new Point2D(100, 60));\nRectangle2D.from3Points(p1, p2, p3); // free-angle rectangle\n```\n\n#### `get width(): number` — Width of the rectangle.\n\n#### `get height(): number` — Height of the rectangle.\n\n#### `get center(): Point2D` — Geometric center of the rectangle.\n\n#### `side(name: RectSide): Line2D` — Return a named side of the rectangle.\n\n#### `sideAt(index: number): Line2D` — Return a side by index.\n\n#### `vertex(name: RectVertex): Point2D` — Return a named vertex of the rectangle.\n\n#### `diagonals(): [ Line2D, Line2D ]` — Return the two diagonals of the rectangle.\n\n#### `toSketch(): Sketch` — Convert the rectangle to a sketch profile.\n\n#### `translate(dx: number, dy: number): Rectangle2D` — Return a translated rectangle.\n\n#### `static fromDimensions(x: number, y: number, width: number, height: number): Rectangle2D` — Create an axis-aligned rectangle from origin corner plus width and height.\n\n#### `static fromCenterAndDimensions(center: Point2D, width: number, height: number): Rectangle2D` — Create a rectangle centered on a point.\n\n#### `static from2Corners(p1: Point2D, p2: Point2D): Rectangle2D` — Create an axis-aligned rectangle from two opposite corners.\n\n#### `static from3Points(p1: Point2D, p2: Point2D, p3: Point2D): Rectangle2D` — Create a free-angle rectangle from three points.\n\n`p1` and `p2` define one edge, and `p3` chooses the perpendicular side.\n\n#### `extrude(height: number, up?: boolean): Shape` — Extrude the rectangle into a solid prism with named topology.\n\n---\n\n<!-- guides/surface-members.md -->\n\n# Surface Members: When To Route Through Carrier + SurfaceBody\n\nSurface members model physical material that follows a carrier surface: bottle-cage arms, grip inlays, brace ribs, prop guards, helmet vents. Full API, parameter rules, and worked examples: [../generated/curves.md](../generated/curves.md).\n\n## When to use\n\nRoute through this layer when the model has: a carrier surface (cylinder, plane, or `ProductSkin`); paths or bands in carrier-local coordinates; member-local features (slots, cutouts, lips, cups, ribs, section thickness/edge radius); or explicit joins between named members. Not for plain boxes, simple extrusions, sheet-metal bends, exact machined faces, or free-floating connector-positioned assemblies.\n\n## Mental model\n\n- `Carrier` owns surface coordinates and frames. `SurfaceBody(name)` owns named members — `band()` or `plate()` — and the joins between them.\n- Features attach to a member's local coordinate system before lowering. This is not a global boolean recipe.\n- Cylinder paths take degrees and handle seam wrapping — never compute angles with trig.\n- A ProductSkin path stays on one side. For multi-side detail, split into one member per side and join them at named transition anchors; `sideTransition` / `sideTransitionChain` / `sideRoute` generate the matching side-local endpoints.\n- `Product.ribbon()` stays the simple path for a one-side conformal ribbon. Upgrade to `Carrier.productSkin(skin)` + `SurfaceBody` when the detail needs member-local features, repeated ribs, explicit joins, or mirrored members.\n\n## Verification loop\n\n- `build()` returns the member geometry. `buildWithDiagnostics()` adds a serializable member graph + IR; `buildDebug()` adds visible debug markers (anchors, join radii, centerlines, frame axes) alongside the normal shapes.\n- Every diagnostic carries a stable `code` field (e.g. `region.centerOutOfBounds`). Repair loops must match on `code`, never on English prose. Clipped or crossing regions and invalid joins are reported as diagnostics, never silently accepted as valid geometry.\n- Only a limited join set lowers to real geometry: close endpoint pairs, selected named-anchor pairs, sampled landing pads, and unambiguous shared endpoints via `autoJoinAtSharedAnchors()`. Farther, missing-anchor, or ambiguous joins remain diagnostic-only intent — decompose the design into supported joins instead of expecting a fallback.\n\n---\n\n<!-- generated/curves.md -->\n\n# Curves & Surfacing\n\nSmooth curves, lofted surfaces, swept solids, splines, and high-level product skins.\n\n## Contents\n\n- [Curves & Surfacing](#curves-surfacing)\n- [Surface Members](#surface-members)\n- [Curve3D](#curve3d)\n- [Route3D](#route3d)\n- [NurbsCurve3D](#nurbscurve3d)\n- [NurbsSurface](#nurbssurface)\n- [PathBuilder](#pathbuilder) — Line Segments, Arcs, Curves, Closing & Output\n- [ProductSkin](#productskin)\n- [ProductSurfaceRef](#productsurfaceref)\n- [ProductSurfaceBuilder](#productsurfacebuilder)\n- [ProductSkinBuilder](#productskinbuilder)\n- [ProductStationBuilder](#productstationbuilder)\n- [ProductPanelBuilder](#productpanelbuilder)\n- [ProductRibbonBuilder](#productribbonbuilder)\n- [CylinderCarrier](#cylindercarrier)\n- [PlaneCarrier](#planecarrier)\n- [ProductSkinCarrier](#productskincarrier)\n- [SurfacePath](#surfacepath)\n- [SurfacePathBuilder](#surfacepathbuilder)\n- [SurfaceBand](#surfaceband)\n- [SurfaceBodyBuilder](#surfacebodybuilder)\n- [SurfaceMemberBuilder](#surfacememberbuilder)\n- [SurfaceJoinBuilder](#surfacejoinbuilder)\n- [CounterboreBuilder](#counterborebuilder)\n- [RoundedSlotBuilder](#roundedslotbuilder)\n- [Curve](#curve)\n- [Surface](#surface)\n- [Blend](#blend)\n- [Analysis](#analysis)\n- [Product](#product)\n- [Carrier](#carrier)\n- [SurfaceMembers](#surfacemembers)\n\n## Functions\n\n### Curves & Surfacing\n\n#### `Curve.Blend(start: CurveBlendEndpoint, end: CurveBlendEndpoint): NurbsCurve3D` — Create an exact G1 blend curve between two directed endpoints.\n\nThe returned curve is a cubic non-rational `NurbsCurve3D`: ForgeCAD converts the endpoint positions and tangents into Bezier control points, so the curve can feed `sweep` and exact surface boundaries through the existing `nurbs` IR rather than a sampled polyline.\n\n```js\nconst rail = Curve.Blend(\n { point: [0, 0, 0], tangent: [1, 0, 0], weight: 0.8 },\n { point: [40, 20, 8], tangent: [0, 1, 0], weight: 0.8 },\n);\nconst tube = sweep(circle2d(2), rail);\n```\n\n**`CurveBlendEndpoint`**\n- `point: Vec3` — Endpoint position.\n- `tangent: Vec3` — Tangent direction at this endpoint. Magnitude is ignored.\n- `weight?: number` — Tangent reach relative to the endpoint chord length. Default 1.\n\n#### `Curve.BlendG2(start: CurveBlendG2Endpoint, end: CurveBlendG2Endpoint): NurbsCurve3D` — Create an exact G2 blend curve between two directed endpoints.\n\nThis is the curvature-aware companion to `Curve.Blend()`. It returns a degree-5 non-rational `NurbsCurve3D` that matches endpoint position, tangent direction, and optional curvature/second-derivative vectors.\n\n```js\nconst rail = Curve.BlendG2(\n { point: [0, 0, 0], tangent: [1, 0, 0], curvature: [0, 0.02, 0] },\n { point: [50, 20, 0], tangent: [0, 1, 0], curvature: [-0.02, 0, 0] },\n);\n```\n\n**`CurveBlendG2Endpoint`** extends CurveBlendEndpoint\n- `curvature?: Vec3` — Optional endpoint curvature/second-derivative vector. Default is zero.\n\n#### `Curve.Arc(options: CurveArcOptions): NurbsCurve3D` — Create an exact circular 3D arc from start, end, and start tangent.\n\nThe returned curve is a rational quadratic `NurbsCurve3D`, split into stable spans when needed, so it can feed `sweep` without sampling the authoring intent away.\n\n```js\nconst rail = Curve.Arc({\n start: [40, 0, 0],\n end: [0, 40, 0],\n tangent: [0, 1, 0],\n});\nconst tube = sweep(circle2d(2), rail);\n```\n\n**`CurveArcOptions`**\n- `start: Vec3` — Arc start point.\n- `end: Vec3` — Arc end point.\n- `tangent: Vec3` — Tangent direction at the start point. Magnitude is ignored.\n\n#### `Curve.Line(start: Vec3, end: Vec3): NurbsCurve3D` — Create an exact straight 3D NURBS line segment.\n\n```js\nconst rail = Curve.Line([0, 0, 0], [80, 0, 15]);\nconst rib = sweep(circle2d(2), rail);\n```\n\n#### `Curve.Nurbs(points: Vec3[], options?: NurbsCurve3DOptions): NurbsCurve3D` — Create an exact NURBS 3D curve from control points, weights, knots, and degree.\n\n```js\nconst rail = Curve.Nurbs([[0, 0, 0], [30, 4, 12], [60, -4, 12], [90, 0, 0]]);\nconst tube = sweep(circle2d(2), rail);\n```\n\n**`NurbsCurve3DOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `degree?` | `number` | Polynomial degree (default 3 = cubic). Must be ≥ 1. |\n| `weights?` | `number[]` | Rational weights, one per control point (default: all 1.0 = non-rational). |\n| `knots?` | `number[]` | Knot vector (default: uniform clamped). Must have length = controlPoints.length + degree + 1. |\n| `closed?` | `boolean` | Whether the curve is closed/periodic (default false). |\n\n#### `Curve.Fit(points: Vec3[], options?: CurveFitOptions): NurbsCurve3D` — Fit a non-rational NURBS curve that interpolates every input point.\n\nThis is global B-spline interpolation, not approximate curve reduction: ForgeCAD computes chord-length parameters, averaged clamped knots, solves the control points, then verifies the interpolation residual against `tolerance`. With `{ closed: true }` the fit is standard periodic B-spline interpolation: the curve loops smoothly from the last point back to the first (do not repeat the first point at the end).\n\n```js\nconst rail = Curve.Fit(\n [[0, 0, 0], [20, 8, 12], [50, -4, 18], [80, 0, 0]],\n { degree: 3, tolerance: 0.001 },\n);\nconst tube = sweep(circle2d(2), rail);\n\n// Closed loop through four points — no duplicated closing point\nconst loop = Curve.Fit(\n [[30, 0, 0], [0, 30, 0], [-30, 0, 0], [0, -30, 0]],\n { closed: true },\n);\n```\n\n**`CurveFitOptions`**\n- `degree?: number` — Polynomial degree. Default is cubic, reduced automatically for short point lists.\n- `tolerance?: number` — Maximum allowed interpolation residual in model units. Default 1e-7.\n- `closed?: boolean` — Interpolate a closed periodic loop through the points. The loop closes from the last point back to the first automatically — do not repeat the first point at the end.\n\n#### `Curve.Trim<T extends CurveTrimInput>(curve: T, start: number, end: number): CurveTrimOutput<T>` — Extract an exact curve segment from normalized parameter `start` to `end`.\n\n`NurbsCurve3D` inputs are trimmed with exact knot insertion/subdomain extraction. Polyline point arrays are trimmed by arclength over their exact line segments. Sampled `Curve3D` splines are rejected until ForgeCAD has a tolerance-controlled rebuild path.\n\n#### `Curve.Reverse<T extends CurveTrimInput>(curve: T): CurveTrimOutput<T>` — Reverse an exact curve without changing its geometry.\n\n`NurbsCurve3D` inputs reverse control points, weights, and knots. Polyline point arrays are cloned and reversed. Sampled `Curve3D` splines are rejected until ForgeCAD has a tolerance-controlled rebuild path.\n\n#### `Curve.Route: typeof Route3D` — Build analytic 3D line/arc routes for sweeps.\n\n`Curve.Route.fromPolyline()` is the canonical route API. It returns a `Route3D` value object, preserving exact route segments, named port frames, and the lowerable `route3d` sweep compile plan.\n\n```js\nconst route = Curve.Route.fromPolyline(\n [[0, 0, 0], [0, 0, 50], [40, 0, 50]],\n { cornerRadius: 12, startPort: 'inlet', endPort: 'outlet' },\n);\nconst tube = sweep(circle2d(4), route);\n```\n\n#### `Curve.Helix: { path(options: HelixOptions): CurveHelixPath; coil: CurveHelixCoil; }` — Build helical paths and swept coils.\n\n`Curve.Helix` is the canonical namespace for helical paths and coils. It uses the same sweep-based lowering as other curve paths.\n\n```js\nconst guide = Curve.Helix.path({ radius: 20, pitch: 6, turns: 4 });\nconst spring = Curve.Helix.coil({ radius: 20, pitch: 6, turns: 4, wireRadius: 1 });\n```\n\n**`HelixOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `radius` | `number` | Radius from the central Z axis to the helix centerline. |\n| `pitch?` | `number` | Axial distance per full turn. Provide any two of `pitch`, `turns`, and `height`. |\n| `turns?` | `number` | Number of full rotations around the axis. Provide any two of `pitch`, `turns`, and `height`. |\n| `height?` | `number` | Total height along +Z. Provide any two of `pitch`, `turns`, and `height`. |\n| `startAngle?` | `number` | Start angle in degrees. Default 0 starts on +X. |\n| `clockwise?` | `boolean` | Reverse winding direction when viewed from +Z. |\n| `samplesPerTurn?` | `number` | Point samples per turn for the metadata path. Default 32. |\n\n`CurveHelixPath`: `{ radius: number, pitch: number, turns: number, height: number, startAngle: number, clockwise: boolean }`\n\n#### `Loft.station(profile: Sketch, position: number): LoftStation` — Create a loft station from a 2D profile and an axis position.\n\n`LoftStation`: `{ profile: Sketch, position: number }`\n\n#### `Loft.field(profiles: Sketch[], heights: number[], options?: FieldLoftOptions): Shape` — Loft by interpolating signed-distance fields instead of matching vertices.\n\nUse this path when profiles change character, such as round shafts blending into flat, cruciform, or lobed tips. It is Manifold-only, mesh-based, and slower than stitched lofting, but it avoids profile-point correspondence artifacts because it blends profile fields instead of boundary vertices.\n\n**`LoftOptions`**\n- `edgeLength?: number` — Marching-grid edge length for level-set meshing. Smaller = finer.\n- `boundsPadding?: number` — Optional extra bounds padding.\n\n**`FieldLoftOptions`** extends LoftOptions\n- `simplify?: boolean | \"safe\"` — Simplification control after field extraction. Default is topology-safe simplification.\n- `maxTriangles?: number` — Hard post-extraction triangle budget. Must be a positive integer. If safe simplification cannot reach it, the build fails.\n\n#### `Loft.leftRail(path: LoftGuideRailPath): LoftGuideRail` — Create a guide rail that constrains the section-local negative-X side.\n\n`LoftGuideRail`: `{ side: LoftGuideRailSide, path: LoftGuideRailPath }`\n\n#### `Loft.rightRail(path: LoftGuideRailPath): LoftGuideRail` — Create a guide rail that constrains the section-local positive-X side.\n\n#### `Loft.frontRail(path: LoftGuideRailPath): LoftGuideRail` — Create a guide rail that constrains the section-local positive-Y side.\n\n#### `Loft.backRail(path: LoftGuideRailPath): LoftGuideRail` — Create a guide rail that constrains the section-local negative-Y side.\n\n#### `Loft.centerRail(path: LoftGuideRailPath): LoftGuideRail` — Create a guide rail that moves section centers along the loft.\n\n#### `Loft.pathOnXz(path: LoftPath2D, y?: number): Vec3[]` — Place a 2D guide path onto the XZ plane.\n\nThe path's first coordinate becomes X and its second coordinate becomes Z. Use this for left/right silhouette rails authored with [`path()`](/docs/sketch#path) or [`constrainedSketch()`](/docs/sketch#constrainedsketch).\n\n#### `Loft.pathOnYz(path: LoftPath2D, x?: number): Vec3[]` — Place a 2D guide path onto the YZ plane.\n\nThe path's first coordinate becomes Y and its second coordinate becomes Z. Use this for front/back crown rails authored with [`path()`](/docs/sketch#path) or [`constrainedSketch()`](/docs/sketch#constrainedsketch).\n\n#### `Loft.pathOnXy(path: LoftPath2D, z?: number): Vec3[]` — Place a 2D guide path onto the XY plane.\n\nThe path's first coordinate becomes X and its second coordinate becomes Y. Use this when lofting along X or Y and a rail lives in a horizontal sketch plane.\n\n#### `Loft.withGuideRails(stations: LoftStation[], rails: LoftGuideRail[], options?: LoftWithGuideRailsOptions): Shape` — Loft through profile stations while forcing generated sections to follow guide rails.\n\nStations define the cross-section family. Guide rails define the side or center paths the loft must pass through. With opposite side rails, the section is scaled to touch both rails. With one side rail, the section keeps its interpolated size unless a center rail is also present.\n\n**`LoftWithGuideRailsOptions`** extends LoftOptions\n- `axis?: LoftAxis` — Primary station axis. Default Z.\n- `samples?: number` — Number of generated loft stations including ends. Default scales with station count.\n- `railSamples?: number` — Number of points sampled from curve-backed rails before axis interpolation. Default 64.\n\n#### `spline2d(points: Vec2[], options?: Spline2DOptions): Sketch` — Build a smooth Catmull-Rom spline sketch from 2D control points.\n\nA closed spline (default) returns a filled profile. An open spline requires a strokeWidth option to produce a solid sketch. Use tension (0..1, default 0.5) to control curve tightness.\n\n**`Spline2DOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `closed?` | `boolean` | Closed loop (default true). |\n| `tension?` | `number` | Catmull-Rom tension in [0, 1]. 0 = very round, 1 = linear-ish. Default 0.5. |\n| `samplesPerSegment?` | `number` | Samples per segment (minimum 3). Default 16. |\n| `strokeWidth?` | `number` | For open splines, provide stroke width to return a solid Sketch. If omitted for open splines, an error is thrown. |\n| `join?` | `\"Round\" \\| \"Square\"` | Stroke join for open splines. Default 'Round'. |\n\n#### `loft(profiles: Sketch[], heights: number[], options?: LoftOptions): Shape` — Loft between multiple sketches along Z stations.\n\nProfiles can differ in topology and vertex count: interpolation is done on signed-distance fields and meshed with level-set extraction. Heights must be strictly increasing. Compatible loft stacks can also stay on the maintained export-backend path.\n\nThe surface is smooth through 3+ stations (C1 spanwise interpolation, like CAD lofts), so it can bow slightly past the straight ruling between stations; sections are matched exactly at their stations. Two-station lofts are ruled. `edgeLength` caps the sample spacing in curved or twisted regions (quality presets scale it); straight regions keep input density.\n\nPerformance note: loft is significantly heavier than primitive/extrude/revolve. If the part is axis-symmetric (bottles, vases, knobs), prefer revolve().\n\n#### `sweep(profile: Sketch, path: SweepPathInput, options?: SweepOptions): Shape`\n\n**`SweepOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `samples?` | `number` | Number of samples when path is a Curve3D. Default 48. |\n| `edgeLength?` | `number` | Marching-grid edge length for level-set meshing. Smaller = finer. |\n| `boundsPadding?` | `number` | Optional extra bounds padding. |\n| `up?` | `Vec3` | Preferred \"up\" vector for local profile frame. Auto fallback is used near parallel segments. |\n\n#### `variableSweep(spine: SweepPathInput, sections: VariableSweepSection[], options?: VariableSweepOptions): Shape` — Sweep a variable cross-section along a 3D spine curve.\n\nUnlike sweep(), which uses a single constant profile, variableSweep() interpolates between multiple profiles at different stations along the spine. This enables organic shapes like tapering tubes, bone-like structures, and sculptural forms.\n\nEach section specifies a t parameter (0 = start, 1 = end of spine) and a 2D profile sketch. The SDF-based level-set mesher smoothly blends between profiles at intermediate positions.\n\nPerformance note: like sweep(), this uses level-set meshing internally.\n\n**`VariableSweepSection`**\n- `t: number` — Parameter along the spine (0 = start, 1 = end).\n- `profile: Sketch` — Cross-section profile at this station.\n\n**`VariableSweepOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `samples?` | `number` | Number of samples when spine is a Curve3D. Default 48. |\n| `edgeLength?` | `number` | Marching-grid edge length for level-set meshing. Smaller = finer. |\n| `boundsPadding?` | `number` | Optional extra bounds padding. |\n| `up?` | `Vec3` | Preferred \"up\" vector for local profile frame. Auto fallback is used near parallel segments. |\n\n### Surface Members\n\n#### `surfaceBand<C extends SurfaceCoordinate>(path: SurfacePath<C> | SurfacePathBuilder<C>, width: WidthProfile, cap?: SurfaceBandCap): SurfaceBand<C>`\n\n#### `SurfaceBody(name: string): SurfaceBodyBuilder` — Start a surface-member body builder for straps, inlays, guards, braces, cuffs, and similar physical members that live on a carrier surface.\n\n```js\nconst carrier = Carrier.cylinder('guard-envelope').diameter(84).height(36).clearance(2);\nconst guard = SurfaceBody('simple-guard')\n .carrier(carrier)\n .member('left-strut')\n .band()\n .path(carrier.path().from({ angle: -132, z: 6 }).to({ angle: -58, z: 18 }))\n .section({ width: 5.5, thickness: 2.8, edgeRadius: 0.6 })\n .member('right-strut')\n .mirrorOf('left-strut')\n .member('front-hoop')\n .band()\n .path(carrier.path().around({ z: 18, fromAngle: -58, toAngle: 58 }))\n .section({ width: 6.2, thickness: 3, edgeRadius: 0.7 })\n .join('left-strut', 'front-hoop').blend({ radius: 3.2 })\n .join('right-strut', 'front-hoop').blend({ radius: 3.2 })\n .build();\n```\n\n---\n\n## Classes\n\n### `Curve3D`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `points` | `Vec3[]` | — |\n| `closed` | `boolean` | — |\n| `tension` | `number` | — |\n\n**Methods:**\n\n#### `sampleBySegment(samplesPerSegment?: number): Vec3[]` — Sample the curve with a fixed number of points per segment.\n\n#### `sample(count?: number): Vec3[]` — Sample the curve to an approximate total point count.\n\n#### `pointAt(t: number): Vec3` — Return the position on the curve at normalized parameter `t` in `[0, 1]`. O(1), no allocations.\n\n#### `tangentAt(t: number): Vec3` — Return a unit tangent vector at normalized parameter `t` in `[0, 1]`. O(1), analytical derivative.\n\n#### `length(samples?: number): number` — Approximate the curve length by polyline sampling.\n\n### `Route3D`\n\nMetadata-bearing analytic 3D route made from line and arc segments.\n\nUse `Curve.Route.fromPolyline()` when you know the virtual design skeleton points and bend radius. ForgeCAD computes tangent trim points, bend arcs, total length, and named start/end port frames. Pass the route directly to `sweep()`.\n\n```js\nconst route = Curve.Route.fromPolyline(\n [[0, 0, 0], [0, 0, 80], [60, 0, 80]],\n { cornerRadius: 24, startPort: \"inlet\", endPort: \"outlet\" },\n);\nconst pipe = sweep(difference2d(circle2d(8), circle2d(6)), route);\nconst outlet = route.port(\"outlet\");\n```\n\n#### `static fromPolyline(points: Route3DVec3[], options?: Route3DFromPolylineOptions): Route3D` — Build a line/arc route from virtual polyline corner points.\n\n**`Route3DFromPolylineOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `cornerRadius?` | `number` | Bend radius applied to every virtual interior corner. Default 0 keeps sharp polyline corners. |\n| `startPort?` | `string` | Name for the start port. Default \"start\". |\n| `endPort?` | `string` | Name for the end port. Default \"end\". |\n| `up?` | `Vec3` | Preferred up vector for deterministic port frames. Default [0, 0, 1]. |\n\n#### `get length(): number` — Total centerline length, including line and bend arc segments.\n\n#### `get segments(): Route3DSegment[]` — Exact line and arc segments that make up this route.\n\n#### `get ports(): Record<string, RoutePortFrame>` — Named port frames, keyed by port name.\n\n#### `port(name: string): RoutePortFrame` — Return one named route port frame.\n\n#### `toSweepPathPlan(): SweepPathCompilePlan` — Convert this route to the compile plan consumed by sweep().\n\n#### `toPolyline(options?: number | Route3DToPolylineOptions): Route3DVec3[]` — Sample this analytic route as a polyline for inspection or backend lowering.\n\n**`Route3DToPolylineOptions`**\n- `samples?: number` — Approximate target point count for the full route.\n- `maxAngleDeg?: number` — Maximum angular spacing on arc segments. Default 6 degrees.\n\n### `NurbsCurve3D`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `controlPoints` | `Vec3[]` | — |\n| `weights` | `number[]` | — |\n| `knots` | `number[]` | — |\n| `degree` | `number` | — |\n| `closed` | `boolean` | — |\n\n**Methods:**\n\n#### `pointAt(t: number): Vec3` — Evaluate the curve at parameter t ∈ [0, 1]. Uses De Boor's algorithm — exact, O(degree²).\n\n#### `tangentAt(t: number): Vec3` — Evaluate the unit tangent vector at parameter t ∈ [0, 1].\n\n#### `sample(count?: number): Vec3[]` — Sample the curve uniformly at `count` points.\n\n#### `sampleAdaptive(minCount?: number, maxCount?: number): Vec3[]` — Sample with adaptive density — more points in high-curvature regions.\n\n#### `length(samples?: number): number` — Approximate arc length by summing polyline segment lengths.\n\n#### `toPolyline(samples?: number): Vec3[]` — Convert to a format compatible with sweep() path input.\n\n### `NurbsSurface`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `controlGrid` | `Vec3[][]` | — |\n| `weightsGrid` | `number[][]` | — |\n| `knotsU` | `number[]` | — |\n| `knotsV` | `number[]` | — |\n| `degreeU` | `number` | — |\n| `degreeV` | `number` | — |\n| `nU` | `number` | — |\n| `nV` | `number` | — |\n| `domain` | `SurfaceDomainCompilePlan` | — |\n\n**Methods:**\n\n#### `pointAt(u: number, v: number): Vec3` — Evaluate the surface at parameters (u, v) ∈ [0, 1]². Uses tensor product evaluation: evaluate basis functions in U and V independently.\n\n#### `normalAt(u: number, v: number): Vec3` — Evaluate the surface unit normal at (u, v) from analytic first derivatives.\n\nUses Algorithm A2.3 basis-function derivatives with the rational quotient rule, so the normal is exact (no finite-difference epsilon, no error near the boundary). Constant chain-rule factors from the parameter remap scale Su and Sv positively and cancel under normalization, so they are omitted.\n\n#### `derivativesAt(u: number, v: number): { S: Vec3; Su: Vec3; Sv: Vec3; }` — Analytic first partial derivatives S_u, S_v (rational quotient rule).\n\n#### `tessellate(resU?: number, resV?: number): { positions: Vec3[]; normals: Vec3[]; indices: number[]; }` — Tessellate the surface into a triangle mesh. Returns positions, normals, and triangle indices.\n\n### `PathBuilder`\n\n**Line Segments**\n\n#### `moveTo(x: number, y: number): this` — Move the cursor to an absolute position without drawing a segment.\n\nWhen called after the initial [`path()`](/docs/sketch#path), this establishes the start of the outline. Calling `moveTo` again mid-path starts a new sub-path (hole in `close()`, separate segment for [`stroke()`](/docs/sketch#stroke)).\n\n#### `lineTo(x: number, y: number): this` — Draw a straight line from the current cursor to an absolute position.\n\n#### `lineH(dx: number): this` — Draw a horizontal line segment by `dx` units from the current cursor.\n\nPositive `dx` moves right; negative moves left.\n\n#### `lineV(dy: number): this` — Draw a vertical line segment by `dy` units from the current cursor.\n\nPositive `dy` moves up; negative moves down.\n\n#### `lineAngled(length: number, degrees: number): this` — Draw a line at the given angle and length from the current cursor.\n\nAngle convention: `0°` points right (+X), `90°` points up (+Y).\n\n```ts\n// L-bracket with angled return\npath().moveTo(0, 0).lineH(50).lineV(-70).lineAngled(20, 235).stroke(4);\n```\n\n**Arcs**\n\n#### `arc(cx: number, cy: number, radius: number, startDeg: number, endDeg: number): this` — Draw an arc defined by center, radius, and angle range (no trig needed). If the path has no segments yet, automatically moves to the arc start. Positive sweep (startDeg < endDeg) = CCW, negative = CW.\n\n```js\n// Arc centered at (10, 0), radius 50, from -30° to +30°\npath().arc(10, 0, 50, -30, 30).stroke(8, 'Round')\n```\n\n#### `arcTo(x: number, y: number, radius: number, clockwise?: boolean): this` — Draw a circular arc from the current position to (x, y) with the given radius. `clockwise=true` → arc curves to the right of the start→end direction. `clockwise=false` → arc curves to the left of the start→end direction.\n\n#### `tangentArcTo(x: number, y: number): this` — G1-continuous arc — radius derived from current tangent + endpoint. Throws if endpoint is collinear with current direction.\n\n**Curves**\n\n#### `bezierTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): this` — Cubic bezier from current position to (x, y) via two control points.\n\n**Closing & Output**\n\n#### `close(): Sketch` — Close the path and return a filled [`Sketch`](/docs/sketch#sketch).\n\nThe winding of the polygon is automatically corrected to CCW (the expected orientation for ForgeCAD sketches). If the path contains multiple sub-paths (started with subsequent `moveTo` calls), the first sub-path is the outer contour and subsequent sub-paths become holes subtracted from it.\n\nEdge labels (assigned with `.label('name')`) are transferred to the resulting sketch and propagate through `extrude()`, `revolve()`, `loft()`, and `sweep()` into named faces on the resulting [`Shape`](/docs/core#shape).\n\n```ts\nconst triangle = path().moveTo(0, 0).lineH(50).lineV(30).close();\n\n// With a hole (second sub-path)\nconst frame = path()\n .moveTo(0, 0).lineH(40).lineV(30).lineH(-40).close(); // outer\n // (hole would be added with another moveTo and line sequence before close)\n```\n\n#### `closeLabel(name: string): Sketch` — Label the closing segment and close the path. Shorthand for labeling the implicit line from the last point back to the start, then closing.\n\n#### `stroke(width: number, join?: \"Round\" | \"Square\"): Sketch` — Thicken an open polyline (centerline) into a solid filled profile with uniform width.\n\nExpands the path into a closed profile `width` units wide (half-width on each side of the centerline). Use `'Round'` for ribs, wire traces, and organic profiles — it adds semicircular endcaps and rounds joins. Use `'Square'` (default) for sharp miter joins without endcaps.\n\nNot the same as rounding corners of a closed polygon — for mixed sharp-and-rounded outlines, build the polygon first and apply `.filletCorner([x, y], radius)` per corner.\n\n```ts\n// Square-join L-bracket\nconst bracket = path().moveTo(0, 0).lineH(50).lineV(-70).lineAngled(20, 235).stroke(4);\n\n// Round-join rib\nconst rib = path().moveTo(0, 0).lineH(60).stroke(6, 'Round');\n\n// Equivalent standalone form\nconst wire = stroke([[0, 0], [50, 0], [50, -70]], 4);\n```\n\nand semicircular endcaps.\n\n#### `label(name: string): this` — Label the most recently added segment. Labels are born here and grow into face names when the sketch is extruded, lofted, swept, or revolved.\n\nLabels must be unique within a path. Each segment can have at most one label.\n\n**Other**\n\n#### `getX(): number` — Current cursor X position.\n\n#### `getY(): number` — Current cursor Y position.\n\n#### `lineBy(dx: number, dy: number): this` — Draw a line by a relative `(dx, dy)` displacement from the current cursor.\n\n#### `arcBy(dx: number, dy: number, radius: number, clockwise?: boolean): this` — Draw an arc to a point offset from the current cursor.\n\n#### `bezierBy(dcp1x: number, dcp1y: number, dcp2x: number, dcp2y: number, dx: number, dy: number): this` — Draw a cubic Bezier using control points relative to the current cursor.\n\n#### `arcAround(cx: number, cy: number, sweepDeg: number): this` — Arc around a known center point, sweeping by the given angle. Radius is derived from the distance between the current position and the center. Positive sweep = CCW (math convention), negative = CW.\n\n```js\n// Arc 90° CCW around (50, 50)\npath().moveTo(70, 50).arcAround(50, 50, 90)\n// Arc 45° CW around the origin\npath().moveTo(10, 0).arcAround(0, 0, -45)\n```\n\n#### `arcAroundRelative(dx: number, dy: number, sweepDeg: number): this` — Arc around a center point given as an offset from the current position. `(dx, dy)` is the vector from the current point to the center. Positive sweep = CCW (math convention), negative = CW.\n\n```js\n// Arc 90° CCW around a center 20 units to the right\npath().moveTo(50, 50).arcAroundRelative(20, 0, 90)\n// Equivalent to: path().moveTo(50, 50).arcAround(70, 50, 90)\n```\n\n#### `smoothCapTo(endX: number, endY: number, cornerRadius: number, capRadius: number): this` — Smooth three-arc end cap from the current position to (endX, endY). Inserts: small corner arc → large cap arc → small corner arc, all G1-continuous.\n\n#### `tangentBezierTo(cp2x: number, cp2y: number, x: number, y: number, weight?: number): this` — G1-continuous cubic bezier — first control point is auto-derived from the current tangent direction. `weight` controls how far the auto-placed control point extends along the tangent (default: 1/3 of the chord).\n\nThe second control point `(cp2x, cp2y)` must be provided — it controls the arrival curvature. For a fully automatic smooth curve, see `smoothThrough`.\n\n#### `smoothThrough(waypoints: Vec2[], tension?: number): this` — Catmull-Rom spline through a list of waypoints from the current position. The current position is included as the first point. The last waypoint becomes the new cursor position.\n\n#### `nurbsTo(controlPoints: Vec2[], opts?: { weights?: number[]; degree?: number; }): this` — Rational B-spline edge to (x, y) with explicit control points and weights.\n\nThe control points define the B-spline shape between the current position and (x, y). The current position is NOT included in `controlPoints` — it is automatically prepended. The endpoint (x, y) is the last control point.\n\n#### `exactArcTo(x: number, y: number, opts?: { radius?: number; clockwise?: boolean; }): this` — Exact circular arc to (x, y) using a rational quadratic NURBS.\n\nUnlike `arcTo()` which tessellates to a polyline, this preserves the exact arc definition. When extruded through the OCCT backend, it produces a true cylindrical face — not a faceted approximation.\n\n#### `fillet(radius: number): this` — Round the last corner (the junction between the previous two segments) with a tangent arc of the given radius.\n\nMust be called after at least two line/arc segments that form a corner. The fillet trims back both segments and inserts a tangent arc.\n\n```js\npath().moveTo(0,0).lineTo(10,0).lineTo(10,10).fillet(2).lineTo(0,10).close()\n```\n\n#### `chamfer(distance: number): this` — Chamfer the last corner with a straight cut of the given distance.\n\n```js\npath().moveTo(0,0).lineTo(10,0).lineTo(10,10).chamfer(2).lineTo(0,10).close()\n```\n\n#### `mirror(axis: \"x\" | \"y\" | Vec2): this` — Mirror all existing segments across an axis and append the mirrored copy in reverse order, creating a symmetric path. The axis passes through the current cursor position.\n\n'y' mirrors across the local Y-axis (flips X), or `[nx, ny]` for an arbitrary axis direction.\n\n```js\n// Build right half, mirror to get full symmetric profile\npath().moveTo(0,0).lineTo(10,0).lineTo(10,5).mirror('x').close()\n```\n\n#### `toPolyline(): Vec2[]` — Return the open path as a sampled 2D polyline.\n\nThis is for construction geometry such as guide rails, measured centerlines, and curve-driven helpers where the authored path should stay open instead of becoming a filled sketch or stroked profile.\n\n```ts\nconst rail = path()\n .moveTo(24, 0)\n .bezierTo(32, 44, 28, 92, 18, 120)\n .toPolyline();\n```\n\n#### `closeOffset(delta: number, join?: \"Round\" | \"Square\" | \"Miter\"): Sketch` — Close the path and return an offset version of the filled Sketch. Positive delta expands outward, negative shrinks inward.\n\n### `ProductSkin`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `name` | `string` | — |\n| `shape` | `Shape` | — |\n| `axis` | `ProductSkinAxis` | — |\n| `stations` | `ProductStationSpec[]` | — |\n| `rails` | `Record<string, ProductRailSpec>` | — |\n\n**Methods:**\n\n#### `toShape(): Shape` — Return the renderable shape generated for this product skin.\n\n#### `with(...children: GroupInput[]): ShapeGroup` — Create a group containing this skin plus named child details.\n\n#### `integrate(...details: Shape[]): Shape` — Boolean-union structural details into the skin body.\n\n#### `uv(side: ProductSkinSide, u?: number, v?: number): ProductSkinRefQuery` — Create a side/u/v surface-ref query on this skin.\n\n**`ProductSkinSide`** — Semantic side of a ProductSkin. `back` is accepted as an alias for `rear`.\n\n`\"left\" | \"right\" | \"top\" | \"bottom\" | \"front\" | \"rear\" | \"back\"`\n\n**`ProductSkinRefQuery`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `side` | `ProductSkinSide` | Side of the product skin. `front` is the minimum axis cap, `rear`/`back` is the maximum axis cap. |\n| `u?` | `number` | Across-side parameter for side refs. Defaults to 0.5. |\n| `v?` | `number` | Along-axis parameter, 0 at the first cap and 1 at the rear/back cap. Defaults to 0.5. |\n| `offset?` | `number` | Positive distance away from the surface along the resolved normal. |\n\n#### `ref(name: string): ProductSurfaceRef` — Resolve a named ref published with Product.skin().refs(...).\n\n#### `curveOnSurface(name: string, points: Array<Partial<ProductSkinRefQuery> & { side: ProductSkinSide; }>): ProductSurfaceRef[]` — Create a sampled curve as a sequence of surface refs on this skin.\n\n#### `surface(side: ProductSkinSide): ProductSurfaceBuilder` — Create a fluent surface helper for refs and conformal features on one side of this skin.\n\nUse this when several refs or ribbons share the same skin side; side-local helpers keep path points concise and make it harder to mix sides accidentally.\n\n#### `stationAt(vOrAxis: number): { ... }` — Interpolate center, width, and depth at a normalized v or absolute axis value.\n\n**`ProductProfileKind`**\n\n`\"oval\" | \"roundedRect\" | \"circle\" | \"superEllipse\" | \"custom\"`\n\n#### `frame(query: ProductSkinRefQuery): ProductSurfaceFrame` — Build a local surface frame from a side/u/v query.\n\n`ProductSurfaceFrame`: `{ point: Vec3, normal: Vec3, tangentU: Vec3, tangentV: Vec3, matrix: Mat4, skin: string }`\n\n### `ProductSurfaceRef`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `name` | `string \\| undefined` | — |\n\n**Methods:**\n\n#### `frame(overrides?: Partial<ProductSkinRefQuery>): ProductSurfaceFrame` — Resolve this semantic surface ref into a point, normal, tangents, and placement matrix.\n\n#### `with(overrides: Partial<ProductSkinRefQuery>): ProductSurfaceRef` — Return a copy of this ref with side/u/v/offset overrides.\n\n#### `attach(detail: Shape | ShapeGroup, options?: ProductAttachOptions): Shape | ShapeGroup` — Place a detail shape or group on this ref's local surface frame.\n\n`ProductAttachOptions`: `{ offset?: number, inset?: number }`\n\n#### `querySpec(): ProductSkinRefQuery` — Return the serializable side/u/v query behind this ref.\n\n### `ProductSurfaceBuilder`\n\nFluent helper bound to one ProductSkin side for refs and side-local conformal features.\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `side` | `ProductSkinSide` | — |\n\n**Methods:**\n\n#### `ref(u?: number, v?: number, offset?: number): ProductSurfaceRef` — Create a ref on this skin side.\n\n#### `uv(u?: number, v?: number, offset?: number): ProductSkinRefQuery` — Create a side/u/v query on this skin side.\n\n#### `frame(query?: Partial<ProductSkinRefQuery>): ProductSurfaceFrame` — Resolve a point/frame on this surface using the builder's side.\n\n#### `ribbon(name: string, points: ProductSurfacePathPoint[], options?: ProductRibbonBuildOptions): ProductRibbonBuilder` — Start a conformal ribbon on this skin side.\n\nPath points use side-local `u`/`v` coordinates; this builder supplies the side. The returned ProductRibbonBuilder is already bound to the source skin and can be further configured before build(). Use `widthSamples` >= 3 when the ribbon must visibly wrap over curved product sections instead of behaving like a flat strip.\n\n**`ProductSurfacePathPoint`** — Side-local path point for Product.surface(side).ribbon(...); the surface helper supplies `side`.\n- `u?: number` — Across-side parameter on the bound side. Defaults to 0.5.\n- `v?: number` — Along-axis parameter, 0 at the first cap and 1 at the rear/back cap. Defaults to 0.5.\n- `offset?: number` — Positive distance away from the surface along the resolved normal.\n\n**`ProductRibbonBuildOptions`** — Options shared by Product.ribbon() builders and Product.surface(...).ribbon(...).\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `width?` | `number` | Width across the surface in millimeters. |\n| `thickness?` | `number` | Solid thickness outward from the source surface in millimeters. |\n| `offset?` | `number` | Positive clearance between the source surface and the ribbon's inner face. |\n| `samples?` | `number` | Samples along the ribbon path. Higher values bend more smoothly. |\n| `widthSamples?` | `number` | Samples across the ribbon width. Use 3+ to visibly wrap over curved cross-sections. |\n| `resolution?` | `number` | Tessellation resolution passed to the lowered NURBS surface. |\n| `material?` | `ProductMaterial` | Apply a product material preset to the ribbon. |\n| `color?` | `string` | Apply a simple color override. |\n\n`ProductMaterial`: `{ color?: string, material?: ShapeMaterialProps }`\n\n`ShapeMaterialProps` — defined in [core](/docs/core).\n\n### `ProductSkinBuilder`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `name` | `string` | — |\n\n**Methods:**\n\n#### `axis(axis: ProductSkinAxis): this` — Choose the primary station axis for the skin loft.\n\n**`ProductSkinAxis`** — Primary world axis used to order ProductSkin loft stations.\n\n`\"X\" | \"Y\" | \"Z\"`\n\n#### `stations(stations: Array<ProductStationBuilder | ProductStationSpec>): this` — Set named cross-section stations for the product skin.\n\n`ProductStationSpec`: `{ name: string, center: Vec3, profile: ProductStationProfile, crown?: number }`\n\n`ProductStationProfile`: `{ sketch: Sketch, width: number, depth: number, kind: ProductProfileKind, radius?: number, exponent?: number }`\n\n#### `rails(rails: Record<string, ProductRailSpec>): this` — Attach named guide rails for product-skin construction and downstream surface references.\n\n`ProductRailSpec`: `{ kind: ProductRailKind, points: Vec3[], degree?: number, name?: string }`\n\n**`ProductRailKind`**\n\n`\"bezier\" | \"nurbs\" | \"polyline\"`\n\n#### `ref(name: string, query: ProductSkinRefQuery): this` — Publish a named semantic surface ref on the skin.\n\n#### `refs(refs: Record<string, ProductSkinRefQuery>): this` — Publish multiple named semantic surface refs on the skin.\n\n#### `uv(side: ProductSkinSide, u?: number, v?: number): ProductSkinRefQuery` — Create a side/u/v surface-ref query for use in refs(...) or Product.ref(...).\n\n#### `material(material: ProductMaterial): this` — Apply a product material preset to the lowered skin.\n\n#### `color(color: string): this` — Apply a simple color override to the lowered skin.\n\n#### `edgeLength(value: number): this` — Set the sampled loft target edge length.\n\n#### `wall(thickness: number): this` — Record intended wall thickness for product design metadata. Use explicit shelling when the model needs real inner-wall geometry.\n\n#### `build(): ProductSkin` — Lower stations and refs into a ProductSkin body.\n\n### `ProductStationBuilder`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `name` | `string` | — |\n\n**Methods:**\n\n#### `at(point: Vec3): this` — Position this station in world coordinates.\n\n#### `z(z: number): this` — Convenience for traditional Z-up section stacks.\n\n#### `y(y: number): this` — Convenience for product bodies running front-to-back along Y.\n\n#### `x(x: number): this` — Convenience for product bodies running left-to-right along X.\n\n#### `oval(width: number, depth: number, options?: { segments?: number; }): this` — Use an oval cross-section with full width and depth dimensions.\n\n#### `superEllipse(width: number, depth: number, options?: ProductStationSuperEllipseOptions): this` — Use a superellipse cross-section for soft-square product surfaces.\n\n`ProductStationSuperEllipseOptions`: `{ segments?: number, exponent?: number }`\n\n#### `roundedRect(width: number, depth: number, radius: number): this` — Use a rounded-rectangle cross-section with the given corner radius.\n\n#### `circle(diameter: number, options?: { segments?: number; }): this` — Use a circular cross-section from a full diameter.\n\n#### `custom(sketch: Sketch, width: number, depth: number): this` — Use a custom 2D sketch as the station cross-section.\n\n#### `crown(amount: number): this` — Set the station crown amount for soft product-section intent.\n\n#### `toSpec(): ProductStationSpec` — Return the immutable station spec consumed by Product.skin().\n\n### `ProductPanelBuilder`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `name` | `string` | — |\n\n**Methods:**\n\n#### `rounded(width: number, height: number, radius?: number): this` — Use a rounded rectangle panel profile.\n\n#### `oval(width: number, height: number): this` — Use an oval panel profile.\n\n#### `profile(profile: Sketch): this` — Use a custom 2D panel profile.\n\n#### `thickness(thickness: number): this` — Set panel extrusion thickness.\n\n#### `material(material: ProductMaterial): this` — Apply a product material preset to the panel.\n\n#### `color(color: string): this` — Apply a simple color override to the panel.\n\n#### `build(): Shape` — Build the panel in local coordinates.\n\n#### `attachTo(ref: ProductRefInput, options?: ProductPanelAttachOptions): Shape` — Build and attach this panel to a ProductSurfaceRef.\n\n**`ProductRefInput`**\n\n`ProductSurfaceRef`\n\n`ProductPanelAttachOptions`: `{ at?: Partial<ProductSkinRefQuery>, thickness?: number, material?: ProductMaterial, color?: string }`\n\n### `ProductRibbonBuilder`\n\nBuilder for thin trim, label, grip, and split-line features that bend with a ProductSkin surface.\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `name` | `string` | — |\n\n**Methods:**\n\n#### `on(skin: ProductSkin, points: ProductRibbonPathPoint[], options?: ProductRibbonBuildOptions): this` — Follow a ProductSkin with side/u/v path queries or refs.\n\nThis is the highest-fidelity mode because every interpolated sample is resolved through ProductSkin.frame(), so the ribbon bends along the selected side as station width/depth changes. All query path points must stay on one side; split side transitions into separate ribbons.\n\n**`ProductRibbonPathPoint`** — Path point for Product.ribbon().on(...): either a side/u/v query or a resolved surface ref.\n\n`ProductSkinRefQuery | ProductSurfaceRef`\n\n#### `fromRefs(points: ProductSurfaceRef[], options?: ProductRibbonBuildOptions): this` — Follow explicit surface refs.\n\nUseful for named refs or paths assembled elsewhere. The builder resolves each ref frame and interpolates between those frames; use on(skin, points) when you need full skin-side sampling between sparse control points.\n\n#### `width(width: number): this` — Set ribbon width in millimeters.\n\n#### `thickness(thickness: number): this` — Set solid thickness outward from the source surface in millimeters.\n\n#### `offset(offset: number): this` — Set positive clearance between the source surface and the ribbon's inner face.\n\n#### `samples(samples: number): this` — Set samples along the path.\n\n#### `widthSamples(samples: number): this` — Set samples across the width. Use 3+ to bend over curved cross-sections.\n\n#### `resolution(resolution: number): this` — Set NURBS tessellation resolution.\n\n#### `material(material: ProductMaterial): this` — Apply a product material preset.\n\n#### `color(color: string): this` — Apply a simple color override.\n\n#### `build(options?: ProductRibbonBuildOptions): Shape` — Build a conformal ribbon as a thin NURBS surface solid.\n\n### `CylinderCarrier`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `name` | `string` | — |\n| `kind` | `\"cylinder\"` | — |\n\n**Methods:**\n\n- `diameter(value: number): this`\n- `radius(value: number): this`\n- `height(value: number): this`\n- `clearance(value: number): this`\n- `center(point: Vec3): this`\n- `path(): SurfacePathBuilder<CylinderSurfaceCoordinate>`\n- `anchor(angle: number, z?: number, options?: { offset?: number; }): SurfaceAnchor<CylinderSurfaceCoordinate>`\n- `front(options?: { z?: number; offset?: number; }): SurfaceAnchor<CylinderSurfaceCoordinate>`\n- `back(options?: { z?: number; offset?: number; }): SurfaceAnchor<CylinderSurfaceCoordinate>`\n- `left(options?: { z?: number; offset?: number; }): SurfaceAnchor<CylinderSurfaceCoordinate>`\n- `right(options?: { z?: number; offset?: number; }): SurfaceAnchor<CylinderSurfaceCoordinate>`\n- `top(options?: { angle?: number; offset?: number; }): SurfaceAnchor<CylinderSurfaceCoordinate>`\n- `bottom(options?: { angle?: number; offset?: number; }): SurfaceAnchor<CylinderSurfaceCoordinate>`\n- `pointAt(coordinate: CylinderSurfaceCoordinate): Vec3`\n- `mirrorPoint(point: Vec3): Vec3`\n- `normalAt(coordinate: CylinderSurfaceCoordinate): Vec3`\n- `tangentAt(coordinate: CylinderSurfaceCoordinate, tangentHint?: Vec3): Vec3`\n- `frameAt(coordinate: CylinderSurfaceCoordinate, tangentHint?: Vec3): SurfaceFrame`\n- `bounds(): SurfaceBounds`\n- `offset(distance: number): CylinderCarrier`\n- `mirrorCoordinate(coordinate: CylinderSurfaceCoordinate): CylinderSurfaceCoordinate`\n- `radiusValueWithClearance(): number`\n\n`CylinderSurfaceCoordinate`: `{ kind?: \"cylinder\", angle: number, z: number, offset?: number }`\n\n### `PlaneCarrier`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `name` | `string` | — |\n| `kind` | `\"plane\"` | — |\n\n**Methods:**\n\n- `size(width: number, height: number): this`\n- `origin(point: Vec3): this`\n- `normal(normal: Vec3): this`\n- `path(): SurfacePathBuilder<PlaneSurfaceCoordinate>`\n- `anchor(x?: number, y?: number, options?: { offset?: number; }): SurfaceAnchor<PlaneSurfaceCoordinate>`\n- `left(options?: { y?: number; offset?: number; }): SurfaceAnchor<PlaneSurfaceCoordinate>`\n- `right(options?: { y?: number; offset?: number; }): SurfaceAnchor<PlaneSurfaceCoordinate>`\n- `top(options?: { x?: number; offset?: number; }): SurfaceAnchor<PlaneSurfaceCoordinate>`\n- `bottom(options?: { x?: number; offset?: number; }): SurfaceAnchor<PlaneSurfaceCoordinate>`\n- `pointAt(coordinate: PlaneSurfaceCoordinate): Vec3`\n- `mirrorPoint(point: Vec3): Vec3`\n- `normalAt(): Vec3`\n- `tangentAt(coordinate: PlaneSurfaceCoordinate, tangentHint?: Vec3): Vec3`\n- `frameAt(coordinate: PlaneSurfaceCoordinate, tangentHint?: Vec3): SurfaceFrame`\n- `bounds(): SurfaceBounds`\n- `offset(distance: number): PlaneCarrier`\n- `mirrorCoordinate(coordinate: PlaneSurfaceCoordinate): PlaneSurfaceCoordinate`\n\n`PlaneSurfaceCoordinate`: `{ kind?: \"plane\", x: number, y: number, offset?: number }`\n\n### `ProductSkinCarrier`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `skin` | `ProductSkin` | — |\n| `name` | `string` | — |\n| `kind` | `\"productSkin\"` | — |\n\n**Methods:**\n\n#### `sideTransition(fromSide: ProductSkinSide, toSide: ProductSkinSide, input?: ProductSkinSideTransitionInput): ProductSkinSideTransition` — Return matching side-local coordinates for an explicit split-member transition.\n\nEach SurfacePath still stays on one ProductSkin side. Use this helper to create one member ending on `from`, another starting on `to`, then join named anchors.\n\nRules: only adjacent `left`/`top`/`right`/`bottom` sides are supported — for front/rear caps use `Product.panel()`. `v` is normalized 0–1 along the shared boundary (default 0.5); `name` must be non-empty when provided; `offset` lifts both coordinates off the surface. Throws if the returned boundary coordinates are not physically coincident — check side order, `v`, and `offset`.\n\n`ProductSkinSideTransitionInput`: `{ name?: string, v?: number, offset?: number }`\n\n`ProductSkinSideTransition`: `{ name?: string, from: ProductSkinSurfaceCoordinate, to: ProductSkinSurfaceCoordinate }`\n\n`ProductSkinSurfaceCoordinate`: `{ kind?: \"productSkin\", side?: ProductSkinSide, u?: number, v?: number, offset?: number }`\n\n#### `sideTransitionChain(sides: ProductSkinSide[], input?: ProductSkinSideTransitionInput): ProductSkinSideTransition[]` — Return a sequence of matching side-local coordinates for an explicit multi-side split-member route.\n\nEach adjacent side pair becomes one named transition. Build one member per side segment, add transition anchors at each returned pair, then join the anchors. The same validation as `sideTransition()` applies to every adjacent pair.\n\n#### `sideRoute(input: ProductSkinSideRouteInput): ProductSkinSideRoute` — Return side-local member segments for a generated multi-side split-member route.\n\nThe route still compiles as explicit members plus named-anchor joins. This helper only generates the per-side segment endpoints and transition names.\n\n**`ProductSkinSideRouteInput`**: `name?: string`, `sides: ProductSkinSide[]`, `from: ProductSkinSurfaceCoordinate`, `to: ProductSkinSurfaceCoordinate`, `v?: number`, `offset?: number`\n\n`ProductSkinSideRoute`: `{ name?: string, transitions: ProductSkinSideTransition[], segments: ProductSkinSideRouteSegment[] }`\n\n**`ProductSkinSideRouteSegment`**: `name: string`, `side: ProductSkinSide`, `from: ProductSkinSurfaceCoordinate`, `to: ProductSkinSurfaceCoordinate`, `startAnchorName?: string`, `endAnchorName?: string`\n\n- `surface(side: ProductSkinSide): ProductSkinCarrier`\n- `path(): SurfacePathBuilder<ProductSkinSurfaceCoordinate>`\n- `pointAt(coordinate: ProductSkinSurfaceCoordinate): Vec3`\n- `mirrorPoint(point: Vec3): Vec3`\n- `normalAt(coordinate: ProductSkinSurfaceCoordinate): Vec3`\n- `tangentAt(coordinate: ProductSkinSurfaceCoordinate, tangentHint?: Vec3): Vec3`\n- `frameAt(coordinate: ProductSkinSurfaceCoordinate, tangentHint?: Vec3): SurfaceFrame`\n- `bounds(): SurfaceBounds`\n- `offset(distance: number): ProductSkinCarrier`\n- `mirrorCoordinate(coordinate: ProductSkinSurfaceCoordinate): ProductSkinSurfaceCoordinate`\n\n### `SurfacePath`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `carrier` | `CarrierSurface<C>` | — |\n| `points` | `C[]` | — |\n| `closedValue` | `boolean` | — |\n\n**Methods:**\n\n- `closed(): SurfacePath<C>`\n- `mirror(): SurfacePath<C>`\n- `coordinateAt(t: number): C`\n- `sample(count?: number): SurfacePathSample<C>[]`\n- `length(samples?: number): number`\n\n### `SurfacePathBuilder`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `carrier` | `CarrierSurface<C>` | — |\n\n**Methods:**\n\n- `from(coordinate: C): this`\n- `through(coordinate: C): this`\n- `to(coordinate: C): this`\n- `around(input: { z: number; fromAngle: number; toAngle: number; offset?: number; }): this`\n- `closed(): this`\n- `mirror(): SurfacePath<C>`\n- `build(): SurfacePath<C>`\n- `sample(count?: number): SurfacePathSample<C>[]`\n\n### `SurfaceBand`\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `centerPath` | `SurfacePath<C>` | — |\n| `widthProfile` | `WidthProfile` | — |\n| `capStyle` | `SurfaceBandCap` | — |\n\n**Methods:**\n\n#### `withHole(name: string, input: SurfaceBandHoleInput): SurfaceBand<C>` — Return a new band with a named member-local rounded-slot hole region recorded as inspectable intent.\n\n`SurfaceBandHoleInput`: `{ length: number, width: number, along?: number, across?: number }`\n\n#### `holes(): SurfaceBandHoleRegion[]` — Resolve recorded hole regions into member-local across/along loops.\n\n- `widthAt(t: number): number`\n- `boundaries(samples?: number): SurfaceBandBoundarySample[]`\n\n### `SurfaceBodyBuilder`\n\nBuilder for a named surface-member body. Owns named members — `band()` or `plate()` — and the joins between them. Features (slots, cutouts, lips, cups, ribs) attach to a member's local coordinate system before lowering; this is not a global boolean recipe.\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `name` | `string` | — |\n\n**Methods:**\n\n#### `join(from: string, to: string | string[]): SurfaceJoinBuilder` — Declare a join between named members. Only a limited join set lowers to real geometry: close endpoint pairs, selected named-anchor pairs (`.betweenAnchors()`), and sampled band/plate landing pads. Farther, missing-anchor, or ambiguous joins remain diagnostic-only intent — decompose the design into supported joins instead of expecting a fallback.\n\n#### `autoJoinAtSharedAnchors(): this` — Lower only unambiguous shared-endpoint pairs (exactly two members sharing a point) into junction geometry. Shared points with more than two members produce a warning diagnostic — declare explicit `.join(...)` relationships instead.\n\n#### `build(): Shape | ShapeGroup` — Build and return only the member + junction geometry. Use `buildWithDiagnostics()` for the member graph and diagnostic codes.\n\n- `carrier(carrier: CarrierSurface): this`\n- `member(name: string): SurfaceMemberBuilder`\n\n`CarrierSurface`: `{ name: string, kind: SurfaceCarrierKind }`\n\n### `SurfaceMemberBuilder`\n\n#### `anchorAt(name: string, coordinate: C | SurfaceAnchor<C>): this` — Add a named anchor at a carrier surface coordinate for explicit member joins.\n\n`SurfaceAnchor`: `{ carrier: CarrierSurface<C>, coordinate: C }`\n\n- `plate(): this`\n- `band(): this`\n- `at(anchor: SurfaceAnchor<C>): this`\n- `size(width: number, height: number): this`\n- `path(path: SurfacePath<C> | SurfacePathBuilder<C>): this`\n- `section(section: MemberSectionInput): this`\n- `cap(style: SurfaceBandCap): this`\n- `slot(name: string, feature: MemberFeature | RoundedSlotBuilder): this`\n- `cutout(name: string, feature: MemberFeature | RoundedSlotBuilder): this`\n- `counterbore(name: string, feature: MemberFeature | CounterboreBuilder): this`\n- `features(features: MemberFeature | MemberFeature[]): this`\n- `profile(name: string, options?: { depth?: number; height?: number; }): this`\n- `mirrorOf(memberName: string): SurfaceBodyBuilder`\n- `member(name: string): SurfaceMemberBuilder`\n- `join(from: string, to: string | string[]): SurfaceJoinBuilder`\n- `autoJoinAtSharedAnchors(): SurfaceBodyBuilder`\n- `build(): Shape | ShapeGroup`\n\n**`MemberSectionInput`**: `width?: number`, `thickness: number`, `edgeRadius?: number`, `direction?: MemberOutwardDirection`, `material?: ProductMaterial`, `stations?: MemberSectionStation[]`\n\n`MemberSectionStation`: `{ t: number, width?: number, thickness?: number }`\n\n**`MemberFeature`**: `type: MemberFeatureType`, `name?: string`, `length?: number`, `width?: number`, `diameter?: number`, `counterboreDiameter?: number`, `clearanceDiameter?: number`, `height?: number`, `depth?: number`, `count?: number`, `along?: number`, `across?: number`, `verticalTravel?: number`\n\n### `SurfaceJoinBuilder`\n\n#### `betweenAnchors(fromAnchor: string, toAnchor: string): this` — Select named anchors on the source and target members before lowering this join.\n\n- `blend(input?: { radius?: number; style?: string; priority?: number; continuity?: string; }): SurfaceBodyBuilder`\n\n### `CounterboreBuilder`\n\n- `at(input: { along?: number; across?: number; z?: number; }): this`\n- `named(name: string): MemberFeature`\n- `toFeature(name?: string): MemberFeature`\n\n### `RoundedSlotBuilder`\n\n- `verticalTravel(value: number): this`\n- `at(input: { along?: number; across?: number; z?: number; }): this`\n- `named(name: string): MemberFeature`\n- `toFeature(name?: string): MemberFeature`\n\n---\n\n## Constants\n\n### `Curve`\n\nCanonical exact/smooth 3D curve constructors.\n\n`Curve.*` is the public home for reference curves and route centerlines that feed `sweep`, `variableSweep`, route visualization, and future path consumers. Standalone 3D curve constructors have been collapsed into this namespace.\n\nMembers (full entries under [Curves & Surfacing](#curves-surfacing)): `Curve.Blend`, `Curve.BlendG2`, `Curve.Arc`, `Curve.Line`, `Curve.Nurbs`, `Curve.Fit`, `Curve.Trim`, `Curve.Reverse`, `Curve.Route`, `Curve.Helix`.\n\n### `Surface`\n\n- `Plane(options: SurfacePlaneOptions): Shape` — Create a finite analytic plane sheet that can be trimmed, sewn, thickened, or used as a low-level face.\n- `Cylinder(options: SurfaceCylinderOptions): Shape` — Create a finite analytic cylindrical sheet, optionally bounded by start/end angles.\n- `Cone(options: SurfaceConeOptions): Shape` — Create a finite analytic conical or frustum sheet, optionally bounded by start/end angles.\n- `Sphere(options: SurfaceSphereOptions): Shape` — Create a finite analytic spherical sheet bounded by longitude and latitude ranges.\n- `Torus(options: SurfaceTorusOptions): Shape` — Create a finite analytic torus sheet bounded by major and tube angle ranges.\n- `Nurbs(controlGrid: Vec3[][], options?: NurbsSurfaceOptions): Shape` — Create an exact NURBS surface from a grid of control points.\n\n The control grid is indexed as `controlGrid[u][v]` — each row is a curve in the V direction, and columns trace curves in the U direction. With default options this builds a bicubic non-rational B-spline sheet with uniform clamped knots; `NurbsSurfaceOptions` controls degrees, weights, knots, trim loops, tessellation, domain, and an optional `thickness` to return a thin solid instead of an open sheet.\n\n ```js\n // Simple 4×4 control grid — a gently curved surface\n const grid = [\n [[0,0,0], [10,0,2], [20,0,2], [30,0,0]],\n [[0,10,1], [10,10,5], [20,10,5], [30,10,1]],\n [[0,20,1], [10,20,5], [20,20,5], [30,20,1]],\n [[0,30,0], [10,30,2], [20,30,2], [30,30,0]],\n ];\n const sheet = Surface.Nurbs(grid);\n const panel = Surface.Nurbs(grid, { thickness: 2 });\n ```\n- `Ruled(curveA: ExactCurveInput, curveB: ExactCurveInput, options?: SurfaceCommonOptions): Shape`\n- `Patch(curves: { bottom: ExactCurveInput; top: ExactCurveInput; left: ExactCurveInput; right: ExactCurveInput; }, options?: SurfacePatchOptions): Shape` — Create a smooth open surface sheet from 4 boundary curves (Coons patch).\n\n The four curves form the boundary of a quadrilateral patch and should meet at corners (small gaps are tolerated). Boundaries are exact by default: pass `NurbsCurve3D` values or `Shape.edge()` refs, or set `{ approximate: true }` to accept sampled `Curve3D`/`Vec3[]` boundaries. The result is an open sheet — call `.thicken(t)` for a thin solid.\n\n ```js\n const sheet = Surface.Patch({ bottom, top, left, right });\n const panel = Surface.Patch({ bottom, top, left, right }).thicken(1.5);\n ```\n- `Boundary(input: SurfaceBoundaryInput): Shape`\n- `Fill(input: SurfaceFillInput): Shape`\n- `Sew(shapes: Shape[], options?: { tolerance?: number; }): Shape`\n- `Solid(input: Shape | Shape[], options?: SurfaceSolidOptions): Shape` — Sew surface faces or consume an existing sewn shell and make a solid B-rep.\n- `Extend(shape: Shape, options: SurfaceExtendOptions): Shape`\n- `Trim(shape: Shape, tool: Shape | SurfacePlaneOp): Shape`\n- `Split(shape: Shape, tool: Shape | SurfacePlaneOp): [ Shape, Shape ]`\n- `Match(shape: Shape, options: { edge: \"u0\" | \"u1\" | \"v0\" | \"v1\"; target: EdgeRef; continuity?: SurfaceContinuity; }): Shape`\n- `Net(): CurveNet` — Begin a curve-network (Gordon) surface — the class-A keystone. Chain `.lengthwise(...)/.crosswise(...)` (or `.alongRails(a,b).sections(...)`, or `.cage(grid)`), then `.thicken(wall)` to get a solid Shape. Returns a fluent [`Sheet`](/docs/core#sheet) builder with analytic point/normal/curvature queries and named edges.\n\n### `Blend`\n\n- `Edge(options: BlendEdgeOptions): Shape`\n- `Surface(options: BlendSurfaceOptions): Shape`\n- `Bridge(edgeA: SheetEdge, edgeB: SheetEdge): BridgeBuilder` — Build a transition strip between two `Surface.Net` sheet edges. Chain `.bulge(a, b)` then `.g0()/.g1()/.g2()` for the continuity order. Returns a [`Sheet`](/docs/core#sheet); verify the seam with `Analysis.EdgeMatch`.\n\n### `Analysis`\n\n- `EdgeContinuity(shape: Shape, options?: EdgeContinuityThresholds): EdgeContinuityReport`\n- `SurfaceContinuity(shape: Shape, options?: EdgeContinuityThresholds): EdgeContinuityReport`\n- `EdgeMatch(edgeA: SheetEdge, edgeB: SheetEdge, options?: { samples?: number; }): ContinuityReport` — Measure G0/G1/G2 agreement between two `Surface.Net` sheet edges: worst position gap, cross-boundary tangent angle (0 = G1), and normal-curvature mismatch (0 = G2). The reflection/fairness check for matched panel seams.\n- `CurvatureComb(input: NurbsCurve3D | EdgeRef, options?: { samples?: number; }): CurvatureSample[]`\n- `SurfaceHealth(shape: Shape, options?: { tinyEdgeThreshold?: number; sliverThreshold?: number; }): SurfaceHealthReport`\n- `BRepValidity(shape: Shape, options?: BRepValidityOptions): BRepValidityReport` — Validate B-rep/shell/solid structure and return closedness, manifoldness, orientation, and issue diagnostics.\n\n### `Product`\n\n- `skin(name: string): ProductSkinBuilder` — Start a named product skin builder.\n- `station(name: string): ProductStationBuilder` — Start a named cross-section station for Product.skin(...).stations(...).\n- `rail: { ... }` — Namespaced rail builders for product skin guide rails and handle spines.\n- `profiles: { ... }` — Product profile helper namespace: oval, superEllipse, roundedRect, and circle — for stations, panels, trims, and openings.\n- `materials: { ... }` — Namespaced product material presets for molded plastic, rubber, metal, and transparent parts.\n- `applyMaterial(shape: Shape, preset: ProductMaterial | undefined): Shape` — Apply a product material preset to a Shape.\n- `scenePreset(name: ProductScenePreset): void` — Apply an opinionated scene preset for product review renders.\n- `profileSize(sketch: Sketch): { width: number; depth: number; }` — Measure the width and depth of a 2D profile sketch.\n- `describeProfile(sketch: Sketch, kind?: ProductProfileKind, radius?: number): ProductProfileDescriptor` — Describe a custom sketch as a product profile.\n- `scaleProfileTo(sketch: Sketch, width: number, depth: number): Sketch` — Scale an existing profile sketch to a target width/depth.\n- `ref(skin: ProductSkin, query: ProductSkinRefQuery): ProductSurfaceRef` — Create an ad-hoc ProductSurfaceRef from a skin and side/u/v query.\n- `surface(skin: ProductSkin, side: ProductSkinSide): ProductSurfaceBuilder` — Create a fluent surface helper for refs and conformal features on one side of a skin.\n\n Equivalent to skin.surface(side), useful when writing in Product.* namespace style.\n- `panel(name: string): ProductPanelBuilder` — Start a panel feature builder.\n- `ribbon(name: string): ProductRibbonBuilder` — Start a conformal ribbon/trim builder for details that should bend with a ProductSkin.\n\n Call .on(skin, points) for side/u/v sampling or .fromRefs(points) for explicit surface refs, then configure width, thickness, offset, sampling, material, and color before build().\n- `place(detail: Shape | ShapeGroup, ref: ProductRefInput, options?: ProductAttachOptions): Shape | ShapeGroup` — Place a shape or group on a ProductSurfaceRef.\n- `landing(name: string, radius?: number, material?: ProductMaterial): Shape` — Small blended landing volume for manual structural bridges and connection proofs.\n\n### `Carrier`\n\nFactory for carrier surfaces — the coordinate-and-frame owners that surface members (`SurfaceBody`) live on.\n\nA carrier owns surface-local coordinates and 3D frames; members and paths are authored in carrier coordinates, never in raw Cartesian math. Cylinder coordinates are `{ angle, z }` with `angle` in degrees — paths handle seam wrapping, so never compute positions with trig. `clearance()`/`offset()` lift geometry off the nominal surface. A ProductSkin carrier path stays on one side (`left`/`right`/`top`/`bottom`); for multi-side detail, split into one member per side and join them at the matching side-local coordinates from `sideTransition()` / `sideTransitionChain()` / `sideRoute()`.\n\n```ts\n// Bottle-cage arm: a curved band on a cylinder, authored in degrees + mm\nconst bottle = Carrier.cylinder('bottle').diameter(74).height(170).clearance(1.5);\nconst arm = bottle.path()\n .from({ angle: -145, z: 18 })\n .through({ angle: -80, z: 72 })\n .to({ angle: -34, z: 112 });\n```\n\n- `cylinder(name: string): CylinderCarrier` — Create an analytic cylinder carrier for bottles, limbs, tubes, guards, and cuffs.\n- `plane(name: string): PlaneCarrier` — Create an analytic plane carrier for plates and local flat construction surfaces.\n- `productSkin(skin: ProductSkin): ProductSkinCarrier` — Adapt an existing ProductSkin into the general surface-member carrier protocol.\n\n### `SurfaceMembers`\n\n- `Body(name: string): SurfaceBodyBuilder` — Start a surface-member body builder for straps, inlays, guards, braces, cuffs, and similar physical members that live on a carrier surface.\n\n ```js\n const carrier = Carrier.cylinder('guard-envelope').diameter(84).height(36).clearance(2);\n const guard = SurfaceBody('simple-guard')\n .carrier(carrier)\n .member('left-strut')\n .band()\n .path(carrier.path().from({ angle: -132, z: 6 }).to({ angle: -58, z: 18 }))\n .section({ width: 5.5, thickness: 2.8, edgeRadius: 0.6 })\n .member('right-strut')\n .mirrorOf('left-strut')\n .member('front-hoop')\n .band()\n .path(carrier.path().around({ z: 18, fromAngle: -58, toAngle: 58 }))\n .section({ width: 6.2, thickness: 3, edgeRadius: 0.7 })\n .join('left-strut', 'front-hoop').blend({ radius: 3.2 })\n .join('right-strut', 'front-hoop').blend({ radius: 3.2 })\n .build();\n ```\n- `Band: typeof SurfaceBand`\n- `band<C extends SurfaceCoordinate>(path: SurfacePath<C> | SurfacePathBuilder<C>, width: WidthProfile, cap?: SurfaceBandCap): SurfaceBand<C>`\n- `roundedSlot(input: { length: number; width: number; }): RoundedSlotBuilder` — Create a rounded member-local slot feature for `SurfaceMemberBuilder.slot()`/`.cutout()`.\n\n Returns a fluent `RoundedSlotBuilder`: chain `.verticalTravel(mm)` to extend the slot for vertical bottle-drop style insertion (travel is summed into the slot length) and `.at({ along, across })` (or `{ z }`) to position it in member-local coordinates.\n\n ```js\n const arm = body.member('arm', armPath)\n .slot('upper-mount-slot', SurfaceMembers.roundedSlot({ length: 12, width: 5.7 }).verticalTravel(6).at({ z: 82 }));\n ```\n- `counterbore(input: { diameter: number; clearanceDiameter: number; depth: number; }): CounterboreBuilder` — Create a cylindrical member-local counterbore feature for `SurfaceMemberBuilder.counterbore()`.\n\n `diameter` is the counterbore pocket diameter and must be larger than `clearanceDiameter`, the through-hole for the fastener shank. Chain `.at({ along, across })` (or `{ z }`) to position it in member-local coordinates.\n\n ```js\n const strap = body.member('strap', strapPath)\n .counterbore('head-pocket', SurfaceMembers.counterbore({ diameter: 9.8, clearanceDiameter: 5.7, depth: 3 }).at({ z: 58 }));\n ```\n- `ribs(input: { count: number; height: number; }): MemberFeature` — Create a repeated-rib stiffening feature for `SurfaceMemberBuilder.features()`.\n\n Ribs belong to the surface member and follow its carrier-surface lowering; `count` ribs of the given `height` are distributed along the member.\n\n ```js\n const grip = body.member('grip', gripPath)\n .features(SurfaceMembers.ribs({ count: 18, height: 0.35 }));\n ```\n\n---\n\n<!-- generated/assembly.md -->\n\n# Assembly API\n\nAssembly-owned links, constraints, connectors, solved poses, and source-level simulation metadata.\n\n## Contents\n\n- [Assembly & Joints](#assembly-joints)\n- [Assembly](#assembly) — Kinematics, Structure, Connectors, References, Solving\n- [ImportedAssembly](#importedassembly)\n- [SolvedAssembly](#solvedassembly)\n\n## Functions\n\n### Assembly & Joints\n\n#### `Sim.material(name: string, options?: SimMaterialOptions): SimMaterialDef` — Create a named physical material with density and contact coefficients for simulation export and checks.\n\n`SimMaterialOptions`: `{ densityKgM3?: number, staticFriction?: number, dynamicFriction?: number, restitution?: number }`\n\n`SimMaterialDef`: `{ kind: \"material\", name: string }`\n\n#### `Sim.body(options: SimBodyOptions): SimBodyDef` — Describe one assembly part as a physical body with mass/density, material, collider intent, and optional contact surfaces.\n\n**`SimBodyOptions`**: `massKg?: number`, `densityKgM3?: number`, `material?: SimMaterialDef`, `collider?: SimColliderDef`, `contacts?: Record<string, SimContactDef>`\n\n`SimColliderDef`: `{ kind: \"collider\", mode: SimColliderMode, reason?: string }`\n\n`SimContactDef`: `{ kind: \"wheelSurface\" | \"gripperSurface\", connectorName: string }`\n\n`SimBodyDef`: `{ kind: \"body\" }`\n\n#### `Sim.collider` — Collision-geometry intent constructors for physical parts.\n\n- `Sim.collider.convexHull(): SimColliderDef` — Use a generated collision mesh for the part. This is the default fast rigid-body collider for irregular parts.\n- `Sim.collider.boundingBox(): SimColliderDef` — Use the part bounding box as the collision geometry. This is fastest and works well for chassis and simple blocks.\n- `Sim.collider.visualMesh(): SimColliderDef` — Use the visual mesh as collision geometry. This is exact but usually slower in physics engines.\n- `Sim.collider.none(reason: string): SimColliderDef` — Disable collision for a part with an explicit reason, such as a sensor-only or decorative object.\n\n#### `Sim.drive` — Joint-drive intent constructors for passive or powered assembly joints.\n\n- `Sim.drive.passive(options?: SimPassiveDriveOptions): SimDriveDef` — Mark a joint as passive while preserving damping and friction metadata for simulation export.\n- `Sim.drive.velocity(options: SimVelocityDriveOptions): SimDriveDef` — Mark a revolute joint as velocity-driven with torque and speed limits. Speed is authored in rpm and exported as deg/s or rad/s as needed.\n\n`SimPassiveDriveOptions`: `{ damping?: number, friction?: number }`\n\n`SimVelocityDriveOptions`: `{ maxTorqueNm: number, maxSpeedRpm: number }`\n\n#### `Sim.contact` — Contact-surface metadata over existing part connectors.\n\n- `Sim.contact.wheelSurface(connectorName: string): SimContactDef` — Mark a connector as the wheel tread contact surface for offline checks and downstream simulation metadata.\n- `Sim.contact.gripperSurface(connectorName: string): SimContactDef` — Mark a connector as a gripper pad/contact surface for offline checks and downstream grasp-readiness metadata.\n\n#### `Sim.profile` — Named validation/export profile constructors.\n\n- `Sim.profile.robotBodyRunnable(): SimProfileDef` — SimReady-style profile for a robot body that should be runnable in a physics simulator.\n- `Sim.profile.robotBodyIsaac(): SimProfileDef` — SimReady-style profile for robot bodies targeting Isaac Sim readiness.\n- `Sim.profile.roboticsAssetPhysx(): SimProfileDef` — SimReady-style profile for robotics assets with PhysX-ready rigid bodies and colliders.\n\n`SimProfileDef`: `{ kind: \"profile\", name: SimProfileName }`\n\n#### `Sim.controller` — Standard controller metadata constructors for simulator package generation.\n\n- `Sim.controller.diffDrive(options: SimDiffDriveControllerOptions): SimDiffDriveControllerDef` — Describe a differential-drive controller from left/right wheel joints and wheel dimensions.\n\n**`SimDiffDriveControllerOptions`**: `leftJoints: string[]`, `rightJoints: string[]`, `wheelSeparationMm: number`, `wheelRadiusMm: number`, `topic?: string`, `odomTopic?: string`, `tfTopic?: string`, `frameId?: string`, `odomFrameId?: string`, `maxLinearVelocity?: number`, `maxAngularVelocity?: number`, `linearAcceleration?: number`, `angularAcceleration?: number`\n\n`SimDiffDriveControllerDef`: `{ kind: \"diffDrive\" }`\n\n#### `assembly(name?: string): Assembly` — Create an assembly container with named parts, connectors, and kinematic links.\n\n**Use this from iteration 1 for any model with moving parts.** Do not build one static pose and retrofit motion later.\n\nTwo motion tools:\n\n- **Link-graph kinematics** (`link()`, `edgeBetweenLinks()`, `addAngleBetweenLinks()`) solve named point positions — a link is a point, not a rigid-body frame. Use when the hard part is solving positions, especially closed loops.\n- **Connector-frame joints** (`connect()` / `match()`) align full connector frames (`origin`, `axis`, `up`) and derive joint frame + axis. Use for serial articulated parts whose orientation matters: hips, hinges, drums, sliders, wheels.\n\n`addPart(..., { mate })` places geometry on the solved link graph by **translation only**: one mate pins a connector origin to a link, two mates orient a part to span two solved links, a third pins roll. Right for markers and point-following geometry; use `connect()`/`match()` when the part needs a deterministic rest orientation.\n\nReturn the `Assembly` itself to expose its joints and driven link controls in the editor; moving a control re-runs `solve(state)`, so closed loops move through the real solver instead of a viewport-only FK approximation.\n\nIf no link in a connected kinematic component is fixed, ForgeCAD chooses a deterministic gauge link for solving and reports a floating-component warning.\n\nA file that returns an `Assembly` is importable via [`require()`](/docs/core#require) and yields an `ImportedAssembly`; use `mergeInto()` to flatten it into a parent assembly.\n\n**Point-link example** (mates a marker to the solved `tip` point; does not orient a bar along `ground -> tip`):\n\n```ts\nconst marker = box(8, 8, 4).withConnectors({\n center: connector({ origin: [0, 0, 0], axis: [0, 0, 1] }),\n});\n\nconst mech = assembly(\"Linkage\")\n .link(\"ground\", { at: [0, 0, 0], fixed: true })\n .link(\"worldX\", { at: [10, 0, 0], fixed: true })\n .link(\"tip\", { at: [40, 0, 0] })\n .edgeBetweenLinks(\"ground\", \"tip\", { name: \"bar\" })\n .addAngleBetweenLinks(\"worldX\", \"ground\", \"tip\", {\n name: \"theta\",\n control: { min: 0, max: 120, default: 30 },\n })\n .addPart(\"Tip marker\", marker, { mate: { connector: \"center\", toLink: \"tip\" } });\n\nreturn mech;\n```\n\n---\n\n## Classes\n\n### `Assembly`\n\nContainer for a kinematic mechanism made up of links, relationships, and parts. See `assembly` for the link-graph vs connector-frame decision rules.\n\nReturning an unsolved `Assembly` keeps the graph available to the runtime; return `mech.solve({ theta: 60 })` for a fixed pose instead.\n\n**Return types**\n\n| Return value | Standalone | `require()` result type |\n|---|---|---|\n| `Assembly` (unsolved) | yes | `ImportedAssembly` |\n| `SolvedAssembly` | yes | `SolvedAssembly` |\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `name` | `string` | — |\n\n**Kinematics**\n\n#### `link(name: string, options?: AssemblyLinkOptions): Assembly` — Add a named kinematic link to the assembly graph.\n\nLinks are assembly-native solved points. They can exist before any geometry is attached, can be displayed by the viewport, and are solved by link/edge/angle constraints.\n\nA link is not a rigid-body frame. It has a world position but no orientation basis. Use `connect()` when a physical part must inherit a connector frame and rotate about a real hinge/slider axis.\n\n**`AssemblyLinkOptions`**\n- `at?: Vec3` — Initial world-space position of this link before kinematic constraints solve it.\n- `fixed?: boolean` — Keep the link locked at its authored `at` position during solves.\n- `metadata?: Record<string, unknown>` — User metadata carried through the kinematic graph for inspection and tooling.\n\n#### `linkAlong(name: string, fromLink: string, towardLink: string, distance: number): Assembly` — Create a derived link on the line through `fromLink` and `towardLink`, at a **signed** distance from `fromLink`.\n\n**Sign convention** (read this first):\n\n- `distance > 0` — the point moves from `fromLink` **toward** `towardLink`.\n- `distance < 0` — the point moves from `fromLink` **away from** `towardLink` (the coupler-extension case, e.g. the Chebyshev lambda linkage's trace point beyond the rocker joint).\n- `distance` greater than the solved edge length places the point **beyond** `towardLink`, still on the same line.\n\nDerived links are trace/reference points. They are recomputed after the primary link solve and cannot participate in structural edges or angle constraints. Because the distance is one signed parameter, a `param()`-driven value can sweep continuously from extension (negative) through `fromLink` (zero) to beyond `towardLink` (large positive).\n\n```ts\n// Chebyshev lambda linkage: trace point C3 extends beyond C2, away from C1.\nmech.linkAlong('C3', 'C2', 'C1', -2.5 * a);\n// Midpoint-style reference 30 mm from A toward B:\nmech.linkAlong('probe', 'A', 'B', 30);\n```\n\n#### `edgeBetweenLinks(a: string, b: string, options?: AssemblyEdgeBetweenLinksOptions): Assembly` — Add a relationship edge between two kinematic links.\n\nBy default the edge captures the authored distance between links as a structural length. Pass `{ length: 'free' }` or `{ visualOnly: true }` for a non-structural overlay edge.\n\n**`AssemblyEdgeBetweenLinksOptions`**: `name?: string`, `length?: number | \"lockCurrent\" | \"free\"`, `min?: number`, `max?: number`, `visualOnly?: boolean`, `control?: AssemblyKinematicControlOptions`, `metadata?: Record<string, unknown>`\n\n`AssemblyKinematicControlOptions`: `{ min?: number, max?: number, default?: number, unit?: string }`\n\n#### `addAngleBetweenLinks(a: string, b: string, c: string, options?: AssemblyAngleBetweenLinksOptions): Assembly` — Add an angle relationship among three kinematic links.\n\nThe middle link is the vertex. When `control` is set, `solve(state)` reads the control value from `state[name]` and solves dependent links from that driven angle.\n\n**`AssemblyAngleBetweenLinksOptions`**: `name?: string`, `value?: number`, `min?: number`, `max?: number`, `control?: boolean | AssemblyKinematicControlOptions`, `limit?: AssemblyKinematicLimitOptions`, `metadata?: Record<string, unknown>`\n\n`AssemblyKinematicLimitOptions`: `{ min?: number, max?: number }`\n\n#### `addAngleBetweenLinkSegmentAndWorldDirection(fromLink: string, toLink: string, direction: Vec3, options?: AssemblyAngleBetweenLinksOptions): Assembly` — Add an absolute angle relationship from a world direction to a link segment.\n\nThe first link is the vertex/pivot and the second link is the moving point. A value of `0` places `fromLink -> toLink` along `direction` in the mechanism plane; positive angles rotate counter-clockwise in that plane.\n\nUse `Points.polar(1, angleDeg)` when the reference direction is planar and angle-based instead of axis-aligned.\n\n#### `describeKinematics(): AssemblyKinematicGraphDef` — Return the assembly-native kinematic graph definition.\n\n**Structure**\n\n#### `addPart(name: string, part: AssemblyPart, options?: PartOptions): Assembly` — Add a named part to the assembly.\n\nConnectors declared on the part (via `withConnectors()`) are captured automatically. Parts are positioned at world origin by default unless a `transform` is provided in `options`. For root parts (no incoming joint), `transform` is their final world position.\n\n`options.mate` is for point-link attachments. During `solve()`, ForgeCAD translates the part so the named connector origin lands on the solved link position. The part keeps its existing orientation; connector `axis` and `up` are not used for link mating. Use this for markers, sensors, labels, and other geometry that should ride on a solved point. Use `connect()` for oriented physical parts such as limbs, levers, hinges, and wheels.\n\nWhen a part is a [`ShapeGroup`](/docs/core#shapegroup), name the group children explicitly to get readable viewport labels (e.g. `\"Base Assembly.Body\"` instead of `\"Base Assembly.1\"`):\n\n```ts\nconst housing = group(\n { name: \"Body\", shape: body },\n { name: \"Lid\", shape: lid },\n);\nassembly.addPart(\"Base Assembly\", housing);\n```\n\n**`PartOptions`**: `transform?: TransformInput`, `metadata?: PartMetadata`, `sim?: SimBodyDef`, `mate?: AssemblyPartMateInput | AssemblyPartMateInput[]`, `bindToFrame?: string`\n\n**`PartMetadata`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `tags?` | `string \\| readonly string[]` | Viewport organization tags applied to scene objects produced from this part. |\n\nAlso: `material?: string`, `process?: string`, `tolerance?: string`, `qty?: number`, `notes?: string`, `densityKgM3?: number`, `massKg?: number`.\n\n**`AssemblyPartMateInput`**\n- `connector: string` — Name of a connector declared on the part (via `withConnectors()`).\n- `toLink: string` — Name of the link this connector's origin is pinned to.\n- `aimLink?: string` — Optional second link to orient toward. When set, the part is rotated so the connector's **axis** aims from `toLink` toward `aimLink`, posing an oriented bone instead of only translating it. For full pose without relying on a connector axis, declare a second mate (two connectors → two links).\n\n#### `frame(name: string, options: AssemblyFrameOptions): Assembly` — Add a named rig frame to the assembly.\n\nA frame is a solved pose: `origin` plus orientation. `axis` is the frame's primary direction and `up` fixes roll around that axis. Use frames for robot links, joint axes, and parts that must carry orientation. Use `link()` for solved points in distance/angle graphs.\n\n`AssemblyFrameOptions`: `{ origin: Vec3, axis: Vec3, up: Vec3, fixed?: boolean, metadata?: Record<string, unknown> }`\n\n#### `fixedJoint(name: string, options: AssemblyFixedFrameJointOptions): Assembly` — Rigidly attach a child rig frame to a parent rig frame.\n\nFixed joints carry frame hierarchy but do not expose a Motion control.\n\n`AssemblyFixedFrameJointOptions`: `{ parent: string, child: string, metadata?: Record<string, unknown> }`\n\n#### `revoluteJoint(name: string, options: AssemblyMovingFrameJointOptions): Assembly` — Add a revolute rig-frame joint.\n\nThe child frame rotates around the parent frame's `axis` direction. Moving frame joints appear in Motion by default; pass `control: false` to keep the joint solved at its default value without showing a Motion control.\n\n**`AssemblyMovingFrameJointOptions`**: `parent: string`, `child: string`, `min?: number`, `max?: number`, `default?: number`, `unit?: string`, `control?: boolean`, `metadata?: Record<string, unknown>`\n\n#### `prismaticJoint(name: string, options: AssemblyMovingFrameJointOptions): Assembly` — Add a prismatic rig-frame joint.\n\nThe child frame translates along the parent frame's `axis` direction. Moving frame joints appear in Motion by default; pass `control: false` to keep the joint solved at its default value without showing a Motion control.\n\n**Connectors**\n\n#### `get usedConnectorRefs(): ReadonlySet<string>` — Connector refs (e.g. \"PartName.connectorName\") consumed by connect/match calls.\n\n#### `withConnectors(partName: string, connectors: Record<string, ConnectorInput>): Assembly` — Attach named connectors to a specific part or the assembly as a whole.\n\nConnectors declared this way are in the part's local coordinate system. They are captured automatically if the incoming [`Shape`](/docs/core#shape) already has connectors via `shape.withConnectors(...)`, but you can also add or override connectors after the fact with this method.\n\nUse the single-argument overload to attach assembly-level connectors — these are exposed when this assembly is imported as a sub-assembly.\n\n`ConnectorInput` — defined in [core](/docs/core).\n\n#### `getConnectors(partName: string): ConnectorMap` — Get connectors declared on a part in part-local space.\n\n#### `getConnector(ref: string): { partName: string; connectorName: string; connector: ConnectorDef; }` — Parse a \"PartName.connectorName\" reference and return the resolved connector. Throws descriptive errors if the part or connector doesn't exist.\n\n#### `connect(parentConnectorRef: string, childConnectorRef: string, options?: ConnectOptions): Assembly` — Connect two parts by aligning their declared connectors, automatically computing frame and axis.\n\nConnector refs use `\"PartName.connectorName\"`. The child connector origin lands exactly on the parent connector origin; joint frame and axis are derived from the connector geometry — no manual `frame`/`axis` math.\n\nFrame semantics: `origin` is the pivot/contact point, `axis` the hinge or slide direction, `up` locks the part's zero-state twist. Omitted `up` gets a deterministic perpendicular — provide `up` whenever rest orientation matters. (`addPart(..., { mate })` translates only; see `addPart`.)\n\n**Face-to-face:** each connector's axis points outward from its part; mating makes the axes anti-parallel, like a plug meeting a socket (same convention as `matchTo()`).\n\n**Revolute sign:** a positive joint value follows the right-hand rule about the **child** connector's placed axis. Because face-to-face mating makes the axes anti-parallel, that is the *left*-hand rule about the parent connector's outward axis — if `+30` swings the opposite way you expected, you predicted from the parent's axis. `forgecad debug assembly` prints each joint's resolved world axis.\n\n**Mirrored revolute axes:** because of the right-hand rule, a mirrored hinge axis (`[1, 0, 0]` vs `[-1, 0, 0]`) rotates oppositely for the same `+theta`: negate the mirrored side's value and mirror limits as `[min, max] -> [-max, -min]`. Prismatic joints have no handedness flip. Use an explicit per-side sign mapping (or side-neutral link controls) for bilateral mechanisms.\n\nJoint type defaults to the connector's `kind`. For `start`/`end` connectors, `align` / `parentAlign` / `childAlign` (`'start' | 'middle' | 'end'`) choose which point meets.\n\n```ts\nconst frame = box(100, 10, 80).withConnectors({\n hinge: connector(\"hinge\", { origin: [0, 0, 40], axis: [0, 0, 1], up: [1, 0, 0] }),\n});\nconst door = box(60, 4, 80).withConnectors({\n hinge: connector(\"hinge\", { origin: [0, 0, 40], axis: [0, 0, -1], up: [1, 0, 0] }),\n});\nassembly(\"Door\").addPart(\"Frame\", frame).addPart(\"Door\", door)\n .connect(\"Frame.hinge\", \"Door.hinge\", { as: \"swing\", min: 0, max: 110 });\n```\n\n**`ConnectOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `min?` | `number` | Lower joint-slider limit; solve clamps to it with a warning. Not a physical stop — enforce real travel limits with stop geometry. |\n| `max?` | `number` | Upper joint-slider limit; same semantics as `min`. |\n| `flip?` | `boolean` | This parameter is ignored. If your connectors produce wrong orientation, fix the connector axis directions instead of using flip. |\n| `parentAlign?` | `PortAlign` | Which point on the parent connector to align: 'start', 'middle' (default), or 'end'. |\n| `childAlign?` | `PortAlign` | Which point on the child connector to align: 'start', 'middle' (default), or 'end'. |\n| `align?` | `PortAlign` | Shorthand: set both parentAlign and childAlign at once. |\n| `follows?` | `JointFollowOptions` | Slave this joint to another joint: `value = ratio × source + offset` (e.g. a mirrored jaw with `ratio: -1`). |\n\nAlso: `as?: string`, `type?: JointType`, `default?: number`, `unit?: string`, `effort?: number`, `velocity?: number`, `damping?: number`, `friction?: number`, `drive?: SimDriveDef`.\n\n**`JointFollowOptions`**\n- `joint: string` — Name of the source joint that drives this one.\n- `ratio?: number` — Multiplier applied to the source joint value (default 1).\n- `offset?: number` — Constant added after the ratio (default 0).\n\n#### `match(childPartName: string, parentPartName: string, pairs: Record<string, string>, options?: MatchToOptions & { as?: string; }): Assembly` — Auto-create a joint by matching typed connectors between two parts.\n\nConnectors can carry a `connectorType` string and a `gender` (`'male'`, `'female'`, or `'neutral'`). `match()` validates type and gender compatibility (use `{ force: true }` to skip validation) and creates the joint automatically from the connector's `kind` metadata.\n\nThe `pairs` map is `{ childConnector: parentConnector }`. The first pair drives joint creation; additional pairs are validated but do not create additional joints (they constrain the same rigid connection).\n\nDefine connectors on shapes with `shape.withConnectors(...)`:\n\n```ts\nconst door = doorShape.withConnectors({\n hinge_top: connector.male(\"hinge\", { origin: [0, 0, 90], axis: [0, 0, 1] }),\n hinge_bottom: connector.male(\"hinge\", { origin: [0, 0, 10], axis: [0, 0, 1] }),\n});\n```\n\nThen match in the assembly:\n\n```ts\nconst mech = assembly(\"Door\")\n .addPart(\"Frame\", frame)\n .addPart(\"Door\", door)\n .match(\"Door\", \"Frame\", { hinge_top: \"hinge_top\", hinge_bottom: \"hinge_bottom\" });\n// Matching connectors computes the placement relationship automatically.\n```\n\n`MatchToOptions` — defined in [core](/docs/core).\n\n**References**\n\n#### `withReferences(refs: Pick<PlacementReferenceInput, \"points\">): Assembly` — Attach named placement reference points to this assembly. These are surfaced automatically on the ImportedAssembly when this file is imported via require(), so consumers can use placeReference() without re-declaring them. Returns a new Assembly — does not mutate.\n\n`PlacementReferenceInput` — defined in [core](/docs/core).\n\n**Solving**\n\n#### `solve(state?: JointState): SolvedAssembly` — Solve the assembly at the given control state and return positioned parts.\n\nSolves assembly-native kinematic links first. Controlled `addAngleBetweenLinks()` relationships read values from `state` by name, clamp to their declared limits, and expose the solved graph on `SolvedAssembly.kinematics`. Angles solve in the plane of their three authored link positions, so a limb that swings out of the `z = 0` plane poses correctly; structural edges hold their bone lengths so a fully angle-driven serial chain follows forward kinematics.\n\nConnector mates declared on `addPart(..., { mate })` attach geometry to solved links while preserving part and connector identity:\n\n- one mate **positions** the connector origin on its link;\n- a mate with `aimLink` (or a second mate to another link) also **orients** the part, rotating an oriented bone to span its links rather than only translating it;\n- a third mate **pins the roll** about the bone axis (full frame), e.g. a bore or clevis that must face a specific way.\n\nConnector-frame joints created by `connect()` / `match()` are also evaluated; their values are read from `state` by joint name and clamped to joint limits.\n\n```ts\nreturn mech.solve({ theta: 45 });\n```\n\n**Other**\n\n#### `withSimulation(options: SimAssemblySimulationOptions): Assembly` — Attach the root simulation contract for this assembly.\n\nUse this after adding physical parts and joints. Robot-body profiles require `rootPart`; asset profiles can describe one-part or multi-part physical assets. URDF/SDF/MJCF/USD exporters and `forgecad check simready` read this contract directly from the returned assembly.\n\n`SimAssemblySimulationOptions`: `{ profile: SimProfileDef, rootPart?: string, controllers?: SimControllerDef[] }`\n\n#### `edgeBetweenFrames(a: string, b: string, options?: AssemblyFrameEdgeOptions): Assembly` — Add a visual skeleton edge between two rig frame origins.\n\nFrame edges follow the solved frame poses produced by `fixedJoint()`, `revoluteJoint()`, and `prismaticJoint()`. They do not add constraints, degrees of freedom, parts, or geometry; use them to make a frame-only rig readable in the Motion/rig inspection overlay.\n\n`AssemblyFrameEdgeOptions`: `{ name?: string, metadata?: Record<string, unknown> }`\n\n#### `addAnimation(name: string, options: AssemblyAnimationOptions): Assembly` — Register a named keyframe animation for this assembly's Motion view.\n\nWorks with the returned-assembly controls path: return the unsolved `Assembly` and the animation appears in the Motion tab alongside the solver-backed joint controls. Keyframes hold control values by joint name; joints declared with `follows` are derived automatically and must not appear in keyframes.\n\n```ts\nrobot.addAnimation(\"Pick and place\", {\n duration: 12,\n loop: true,\n keyframes: [\n { values: { J1: 0, J2: -90 } },\n { values: { J1: 45, J2: -30 } },\n { values: { J1: 0, J2: -90 } },\n ],\n});\nreturn robot;\n```\n\n**`AssemblyAnimationOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `duration?` | `number` | Animation length in seconds (default chosen by the viewer). |\n| `loop?` | `boolean` | Loop the animation (default false). |\n| `continuous?` | `boolean` | Interpolate continuously through keyframes instead of pausing on each. |\n| `keyframes` | `JointViewAnimationInput[\"keyframes\"]` | Keyframes of control values by joint/control name. `at` (0..1) or `ticks` control timing. |\n| `default?` | `boolean` | Make this the animation that plays when the model loads. |\n\n`JointViewAnimationInput`: `{ name: string, duration?: number, loop?: boolean, continuous?: boolean, keyframes: JointViewAnimationKeyframeInput[] }`\n\n**`JointViewAnimationKeyframeInput`**\n- `at?: number` — Timeline position [0, 1]. If omitted from ALL keyframes, positions are auto-computed from tick weights.\n- `ticks?: number` — Relative weight of the segment from this keyframe to the next (default 1). Only used in tick-based mode (when `at` is omitted). Last keyframe's ticks value is ignored.\n- Also: `values: Record<string, number>`.\n\n#### `describe(): AssemblyDefinition` — Return the serializable assembly definition used by solve/inspect pipelines.\n\n**Compatibility Aliases**\n\n- `withPorts()` -> `withConnectors()`\n- `getPorts()` -> `getConnectors()`\n\n### `ImportedAssembly`\n\nA wrapper around an imported `Assembly` that provides kinematic access and convenient transform helpers.\n\nWhen a `.forge.js` file returns an unsolved `Assembly`, [`require()`](/docs/core#require) wraps it in an `ImportedAssembly`. This preserves the kinematic structure — you can call `solve()` and `mergeInto()` — and converts to a static [`ShapeGroup`](/docs/core#shapegroup) via the explicit `toGroup(state?)` boundary when group-style transforms are needed.\n\n**Kinematic access**\n\n```ts\nconst arm = require(\"./arm.forge.js\");\n\nconst solved = arm.solve({ shoulder: 45 }); // full kinematic solve\nconst link = arm.getPart(\"Link\", { shoulder: 60 }); // single part at state\nconst group = arm.toGroup({ shoulder: 45 }); // only when ShapeGroup behavior is needed\n```\n\n**Static positioning** — convert explicitly, then transform the group (`toGroup()` solves at default joint values and discards kinematics):\n\n```ts\nconst positioned = arm.toGroup().rotateZ(-90).translate(0, -20, 50);\n```\n\n**Merging into a parent**\n\n```ts\nrequire(\"./arm.forge.js\").mergeInto(robot, {\n prefix: \"Left Arm\",\n mountParent: \"Chassis\",\n mountJoint: \"leftMount\",\n mountOptions: { frame: Transform.identity().translate(-70, 0, 10) },\n});\n```\n\n#### `get assembly(): Assembly` — The underlying Assembly, for advanced composition and inspection.\n\n#### `solve(state?: JointState): SolvedAssembly` — Solve the assembly at the given joint state (defaults to each joint's default value).\n\n#### `getPart(partName: string, state?: JointState): AssemblyPart` — Return a specific named part positioned at the solved pose, with any stored placement offset applied.\n\nThis mirrors `SolvedAssembly.getPart()` for imported assemblies, with one addition: any offset stored by `placeReference()` is applied, so the part lands where the imported assembly was placed. (`solve(state).getPart(name)` returns the part in the assembly's own coordinates, without that offset.)\n\n#### `toGroup(state?: JointState): ShapeGroup` — Convert all assembly parts to a ShapeGroup with named children. Use this for composition, transforms, or child lookup — not as a required render step for assemblies. Child names match the part names used in the assembly. Any stored placement offset and placement references are forwarded to the group.\n\n#### `withReferences(refs: Pick<PlacementReferenceInput, \"points\">): ImportedAssembly` — Attach named placement reference points to this assembly. Points are simple 3D coordinates (relative to the assembly's own origin). Returns a new ImportedAssembly — does not mutate.\n\n#### `referenceNames(kind?: PlacementReferenceKind): string[]` — List all attached placement reference names.\n\n#### `placeReference(ref: string, target: Vec3, offset?: Vec3): ImportedAssembly` — Translate the assembly so the named reference point lands on `target`. Returns a new ImportedAssembly — does not mutate. All point refs are translated by the same delta.\n\n#### `child(name: string): Shape | Sketch | ShapeGroup` — Solve at defaults, get a named child part from the resulting group.\n\n#### `collisionReport(options?: CollisionOptions): CollisionFinding[]` — Detect overlapping part pairs at the default solved pose.\n\nThis mirrors `SolvedAssembly.collisionReport()` for imported assemblies. Use `solve(state).collisionReport(options)` when inspecting a non-default joint state.\n\n`CollisionOptions`: `{ parts?: string[], ignorePairs?: Array<[ string, string ]>, minOverlapVolume?: number }`\n\n#### `minClearance(partA: string, partB: string, searchLength?: number): number` — Compute the minimum gap between two parts at the default solved pose.\n\nThis mirrors `SolvedAssembly.minClearance()` for imported assemblies. Use `solve(state).minClearance(partA, partB, searchLength)` when inspecting a non-default joint state.\n\n#### `mergeInto(parent: Assembly, options: MergeIntoOptions): Assembly` — Flatten this sub-assembly's parts and relationships into `parent` and wire a mount relationship.\n\nAll part, link, and legacy joint names from the sub-assembly are prefixed with `\"${options.prefix}.\"` to avoid collisions; connectors are forwarded with the same prefix. After the merge, drive controls from the parent using the prefixed names:\n\n```ts\nparent.solve({ \"Left Arm.theta\": 45, \"Right Arm.theta\": -20 })\n```\n\nThe sub-assembly must have exactly one root part before it can be merged (collapse multiple roots with `addFixed()` first). See the `ImportedAssembly` class docs for a full merge example.\n\n**`MergeIntoOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `prefix?` | `string` | Prefix applied to every part name and joint name from the sub-assembly. E.g. prefix \"Left Arm\" turns part \"Base\" into \"Left Arm.Base\". Strongly recommended to avoid name collisions when merging multiple instances. |\n| `mountParent` | `string` | Part name in the parent assembly to attach the sub-assembly root to. |\n| `mountJoint` | `string` | Name for the new mount joint in the parent graph. |\n| `mountType?` | `JointType` | Joint type for the mount connection (default: 'fixed'). |\n| `mountOptions?` | `JointOptions` | Frame, axis, limits, and other options for the mount joint. |\n\n**`JointOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `connectorRefs?` | `JointConnectorRefs` | Connector refs that define this joint contract. Usually set by `connect()` / `match()`. |\n| `follows?` | `JointFollowOptions` | Slave this joint to another joint: `value = ratio × source + offset`. Use for mechanisms with one physical DOF expressed through several joints — a mirrored gripper jaw (`ratio: -1`), a gear pair, a drive crank turning with its servo. A followed joint stops being an independent control: the Motion view drives it from its source, `solve()` derives its value (a direct state override is ignored with a warning), and limits still clamp the derived value. |\n\nAlso: `frame?: TransformInput`, `origin?: Vec3`, `axis?: Vec3`, `min?: number`, `max?: number`, `default?: number`, `unit?: string`, `effort?: number`, `velocity?: number`, `damping?: number`, `friction?: number`, `drive?: SimDriveDef`.\n\n`JointConnectorRefs`: `{ parent: string, child: string, parentAlign?: PortAlign, childAlign?: PortAlign }`\n\n### `SolvedAssembly`\n\nThe result of solving an assembly at a specific joint state.\n\n`SolvedAssembly` holds world-space transforms for every part at a given pose. Top-level scripts can return a `SolvedAssembly` directly for display. Use `toGroup()` when you specifically need a [`ShapeGroup`](/docs/core#shapegroup) for composition, group-style transforms, or named-child lookup. Do not call `toGroup()` just to make a solved assembly render. Use `getPart()` / `getTransform()` to inspect individual parts programmatically.\n\n**Validation**\n\nCall `collisionReport()` to detect overlapping parts at this solved pose.\n\n```ts\nconst solved = mech.solve({ shoulder: 45, elbow: -20 });\nconsole.log(\"Collisions\", solved.collisionReport());\nreturn solved;\n```\n\n**Properties:**\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `name` | `string` | — |\n\n**Methods:**\n\n#### `warnings(): string[]` — Return any warnings generated during solve (clamped joints, unconverged mates, etc.).\n\n#### `getJointState(): JointState` — Return a snapshot of resolved joint values (after clamping and coupling).\n\n#### `get kinematics(): SolvedAssemblyKinematics | null` — Solved assembly-native kinematic or frame-edge overlay data, or null when no rig overlay data was declared.\n\n#### `getLinkPosition(linkName: string): Vec3` — Return the solved world position of a kinematic link.\n\n#### `getFrame(frameName: string): Transform` — Return the solved world transform for a named rig frame.\n\n#### `get frames(): SolvedAssemblyFrameDef[]` — Return solved rig frames, including origin, axis, up, and transform.\n\n#### `getTransform(partName: string): Transform` — Return the world-space [`Transform`](/docs/core#transform) for the named part at the solved pose.\n\n#### `getPart(partName: string): AssemblyPart` — Return the named part already positioned at its solved world transform.\n\n#### `toGroup(): ShapeGroup` — Convert all solved parts into a [`ShapeGroup`](/docs/core#shapegroup) with named children.\n\nEach part becomes a named child in the group, already positioned at its solved world transform. Use this only when you specifically need a [`ShapeGroup`](/docs/core#shapegroup) for composition, [`ShapeGroup`](/docs/core#shapegroup) transforms, or named-child access. Top-level scripts can return the `SolvedAssembly` directly; do not call `toGroup()` just to make a solved assembly render.\n\n```ts\nconst armGroup = mech.solve({ shoulder: 60 }).toGroup(); // only because we need rotateZ()\nreturn armGroup.rotateZ(90);\n```\n\n#### `toSceneObjects(): Array<{ ... }>` — Return an array of named scene objects for the viewport renderer.\n\nEach part becomes `{ name, shape }` or `{ name, group: [...] }` if the part is a [`ShapeGroup`](/docs/core#shapegroup). Top-level scripts should normally return the `SolvedAssembly` directly. Use `toGroup()` when you need [`ShapeGroup`](/docs/core#shapegroup) behavior; use this method only for advanced scene-graph control where you need access to the flat per-part array with metadata.\n\n#### `bom(): BomRow[]` — Generate a bill of materials for all parts in the solved assembly.\n\n#### `bomCsv(): string` — Generate a bill of materials as a CSV string.\n\n#### `collisionReport(options?: CollisionOptions): CollisionFinding[]` — Detect overlapping (colliding) part pairs in this solved pose.\n\nComputes boolean intersections between all part pairs and returns findings where the overlap volume exceeds `minOverlapVolume` (default 0.1 mm³).\n\n```ts\nconst solved = mech.solve({ shoulder: 35, elbow: 60 });\nconsole.log(\"Collisions\", solved.collisionReport());\n```\n\n#### `minClearance(partA: string, partB: string, searchLength?: number): number` — Compute the minimum gap (clearance) between two parts in this solved pose.\n\nReturns `0` if the parts are touching or overlapping. Manifold-backed parts use the exact Manifold gap query. SDF-backed parts use a mesh-derived sampled gap. `searchLength` bounds the Manifold search radius in mm — increase it for widely separated Manifold parts.\n\n---\n\n<!-- generated/output.md -->\n\n# Output & Annotations\n\nDimensions, BOM entries, verification checks, and sketch export.\n\n## Contents\n\n- [Annotations & Output](#annotations-output)\n- [Sketch Export](#sketch-export)\n\n## Functions\n\n### Annotations & Output\n\n#### `bom(quantity: number, description: string, opts?: BomOpts): void` — Register a Bill of Materials entry for report export.\n\nBOM entries are accumulated during script execution and exported alongside the model in report views. Rows are grouped by normalized `description + unit`. Pass an explicit `key` to force multiple descriptions to collapse into a single line item.\n\n- `quantity` must be a finite number `>= 0`. A quantity of `0` is silently ignored (useful for conditional scripting with `param()`-driven counts).\n- `unit` defaults to `\"pieces\"` when omitted or empty.\n- The assembly `solved.bom()` / `solved.bomCsv()` API is separate and covers per-part assembly metadata; this function is for free-form purchased-item annotation.\n- `bom()` is injected into every `.forge.js` script. Call it directly; do not write `const { bom } = require(...)`, because top-level declarations named `bom` collide with the built-in runtime name.\n\n```ts\nconst tubeLen = param(\"Tube Length\", 1200, { min: 300, max: 4000, unit: \"mm\" });\nconst boltCount = param(\"Bolt Count\", 16, { min: 0, max: 200, integer: true });\n\nbom(tubeLen, \"iron tube 30 x 20\", { unit: \"mm\" });\nbom(boltCount, \"M4 bolt, 16 mm length\");\nbom(4, \"rubber foot\", { key: \"foot-rubber\" }); // explicit aggregation key\n\n// Structured metadata for richer reports:\nbom(tubeLen, \"rectangular steel tube\", {\n unit: \"mm\",\n material: \"steel\",\n section: [30, 20],\n wall: 3,\n});\n```\n\n**`BomOpts`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `unit?` | `string` | Quantity unit label, e.g. \"mm\", \"pieces\", \"kg\". Default: \"pieces\" |\n| `key?` | `string` | Optional explicit grouping key used during report aggregation. |\n| `material?` | `string` | Material name, e.g. \"steel\", \"birch plywood\", \"nylon\" |\n| `dimensions?` | `number[]` | Overall dimensions `[width, height]` or `[width, height, thickness]` in the entry's unit |\n| `section?` | `number[]` | Cross-section dimensions `[w, h]` for tubes and profiles |\n| `wall?` | `number` | Wall thickness for hollow sections (mm) |\n| `diameter?` | `number` | Diameter for round stock, bolts, dowels (mm) |\n| `length?` | `number` | Length for fasteners (mm) |\n| `process?` | `string` | Manufacturing process, e.g. \"laser cut\", \"CNC\", \"welded\" |\n| `notes?` | `string` | Free-form notes |\n| `grain?` | `string` | Wood grain direction, e.g. \"long\", \"cross\" |\n\n#### `dim()` — Add a dimension annotation between two points, or along an entity.\n\nOverloads:\n\n- `dim(line: Line2D, opts?: DimOpts): void`\n- `dim(edge: EdgeRef, opts?: DimOpts): void`\n- `dim(from: PointArg, to: PointArg, opts?: DimOpts): void`\n\nDimension annotations are purely visual callouts rendered in the viewport and report export. They do not affect geometry or constrain the model.\n\nPoint arguments accept 2D tuples `[x, y]`, 3D tuples `[x, y, z]`, or [`Point2D`](/docs/sketch#point2d) objects (Z is treated as 0 for 2D inputs).\n\nEntity arguments: pass a single [`Line2D`](/docs/sketch#line2d) (from a constrained sketch) or an `EdgeRef` (from `shape.edge('left')`) as the first argument to dimension along that entity directly — no manual endpoint extraction needed.\n\n**Ownership Rules (Report Pages)**\n\n- `currentComponent: true` — deterministic ownership by the calling import instance. Use when authoring reusable imported parts.\n- `component: \"Part Name\"` — route dimension to another named returned object.\n- Multiple owners: dimension is shared and appears on the assembly overview page.\n- No ownership set: report export infers ownership via endpoint-in-bbox.\n\n```ts\ndim([-w / 2, 0, 0], [w / 2, 0, 0], { label: \"Width\" });\ndim([0, 0, -h / 2], [0, 0, h / 2], { label: \"Height\", offset: 14 });\ndim([0, 0, 0], [100, 0, 0], { component: \"Base\", color: \"#00AAFF\" });\ndim(sk.line(a, b), { label: \"Span\", offset: -8 }); // Line2D entity\ndim(myBox.edge(\"top-right\"), { label: \"Depth\" }); // EdgeRef entity\n```\n\n[`Line2D`](/docs/sketch#line2d) / `EdgeRef` entity (then pass `opts` as the second argument)\n\n`component` (string or string[] — report ownership), `currentComponent` (boolean)\n\n`DimOpts`: `{ offset?: number, label?: string, color?: string, component?: string | string[], currentComponent?: boolean }`\n\n**`EdgeRef`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `start` | `Vec3` | Start point |\n| `end` | `Vec3` | End point |\n| `query?` | `EdgeQueryRef` | Compiler-owned edge query when available. |\n| `curve?` | `EdgeCurve` | Exact or parametric curve family when the backend/source can identify one. |\n| `faceName?` | `string` | Owning face name when the edge is associated with one face in a larger topology. |\n\nAlso: `name: EdgeName`.\n\n### Sketch Export\n\n#### `sketchToDxf(sketch: Sketch, options?: SketchDxfOptions): string` — Export a 2D sketch as a DXF string (R12/AC1009 — maximally compatible).\n\nFor regular sketches, each polygon loop becomes a closed `LWPOLYLINE`. For constrained sketches, exports raw `LINE`, `CIRCLE`, and `ARC` entities from the constraint edge geometry, which preserves internal/shared edges that `toPolygons()` would merge away.\n\nThe R12 format is chosen for maximum compatibility with CAM tools, laser-cutter software, and older CAD readers.\n\n```ts\nconst s = rect(100, 60);\nconst dxf = sketchToDxf(s, { layer: 'cut' });\n```\n\n**`SketchDxfOptions`**\n- `layer?: string` — DXF layer name. Default: \"0\"\n- `colorIndex?: number` — DXF color index (1–255, AutoCAD ACI). Default: 7 (white/black)\n\n#### `sketchToSvg(sketch: Sketch, options?: SketchSvgOptions): string` — Export a 2D sketch as an SVG string.\n\nFor regular sketches, exports filled polygon regions. For constrained sketches, exports raw edge geometry (LINE, ARC, CIRCLE) which preserves internal/shared edges that `toPolygons()` would merge away.\n\nThe SVG uses the sketch's native coordinate system (Y-up) with a CSS transform that flips Y so the output renders correctly in SVG's Y-down space. Coordinates are in sketch units (typically mm).\n\n```ts\nconst s = rect(100, 60);\nconst svg = sketchToSvg(s, { stroke: '#333', strokeWidth: 0.8 });\n```\n\n**`SketchSvgOptions`**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `stroke?` | `string` | Stroke color. Default: \"black\" |\n| `strokeWidth?` | `number` | Stroke width in sketch units. Default: 0.5 |\n| `fill?` | `string` | Fill color. Default: \"none\" |\n| `padding?` | `number` | Padding around the sketch bounding box in sketch units. Default: 2 |\n| `pixelsPerUnit?` | `number` | If set, scale so 1 sketch-unit = this many px. Otherwise auto-fit. |\n\n---\n\n<!-- guides/scene-presentation.md -->\n\n# Scene Presentation Recipes\n\nWorked `scene()` setups. The option schema and behavioral cliffs (e.g. setting `lights` replaces all defaults) live in the `scene()` API docs (viewport group) — this file is copyable recipes only.\n\n## Baseline studio setup\n\nEvery model deserves at least this; default lighting looks flat.\n\n```js\nscene({\n background: { top: '#1a1a2e', bottom: '#0a0a14' },\n camera: { position: [x, y, z], target: [0, 0, 0], fov: 42 },\n environment: { preset: 'studio', intensity: 0.6 },\n lights: [\n { type: 'ambient', color: '#c8cdd4', intensity: 0.15 },\n { type: 'directional', position: [80, -60, 120], target: [0, 0, 0], color: '#fff4e0', intensity: 1.8, castShadow: true },\n { type: 'directional', position: [-60, 40, 80], target: [0, 0, 0], color: '#b0c4de', intensity: 0.7 },\n ],\n ground: { visible: true, color: '#111118', height: -10, receiveShadow: true },\n postProcessing: {\n bloom: { intensity: 0.3, threshold: 0.85, radius: 0.3 },\n vignette: { darkness: 0.5, offset: 0.4 },\n toneMappingExposure: 1.3,\n },\n});\n```\n\nAdapt to the material family: metallic/jewelry → `studio`, exposure 1.2–1.5, subtle bloom; organic/wood/matte → `warehouse`/`apartment`, warmer ambient, lower bloom; dark/dramatic → `night`, bloom + vignette. Ground with `receiveShadow: true` only for objects that stand on something.\n\nCamera: 3/4 angle, `fov` 35–50 (lower = flatter/telephoto), `target` at the visual center of mass — not necessarily `[0,0,0]`.\n\n## Matte industrial hero shot\n\nFor mechanisms, tools, product prototypes, and vehicles, prefer a matte studio look over gloss or atmosphere (ranges = tuning room, not options syntax):\n\n```js\nscene({\n background: { top: '#c3ccd7', bottom: '#566474' },\n camera: { position: [430, -540, 340], target: [0, 30, 125], fov: 38 },\n environment: { preset: 'studio', intensity: 0.2, background: false }, // 0.15–0.25\n lights: [\n { type: 'ambient', color: '#efe7dc', intensity: 0.15 }, // 0.12–0.2\n { type: 'directional', position: [260, -320, 420], color: '#ffe2bf', intensity: 2.8, castShadow: true }, // 2.6–3.2\n { type: 'directional', position: [-260, 210, 220], color: '#d4e6fb', intensity: 0.85 }, // 0.7–1.0\n { type: 'hemisphere', skyColor: '#c7d3df', groundColor: '#495463', intensity: 0.15 }, // 0.1–0.2\n ],\n postProcessing: {\n bloom: { intensity: 0.04, threshold: 0.94, radius: 0.28 },\n vignette: { darkness: 0.4, offset: 0.32 },\n toneMappingExposure: 1.1, // 1.05–1.18\n },\n});\n```\n\nStage the model on an intentionally matte plinth:\n\n```js\nconst stage = cylinder(16, 226).translate(0, 0, -26)\n .color('#8b97a4').material({ metalness: 0.04, roughness: 0.78 });\nmock(stage, 'StudioPlinth');\n```\n\nIteration rules that held up in practice:\n\n- Prefer roughness over fog for softness — fog flattens form; matte materials keep shadow definition.\n- Keep bloom near zero for mechanical scenes; too much reads toy-like.\n- If the render is close but not right, nudge `toneMappingExposure` by ~0.05 before touching the light rig; avoid large ambient jumps — they kill contrast fastest.\n- Add accent point lights near focal features, localized with `distance` and `decay`.\n\n## Named render views\n\nFor repeatable review or hero renders, declare views in `scene({ views })` — wrap each camera in `{ camera: ... }`:\n\n```js\nscene({\n camera: { position: [430, -540, 340], target: [0, 30, 125], fov: 38 },\n views: {\n hero: { camera: { position: [430, -540, 340], target: [0, 30, 125], up: [0, 0, 1], fov: 38 } },\n side: { camera: { position: [700, 0, 180], target: [0, 30, 100], up: [0, 0, 1], fov: 32 } },\n },\n});\n```\n\nRender one with `forgecad render 3d model.forge.js --view hero`.\n\n---\n\n<!-- guides/joint-design.md -->\n\n# Joint Design Recipes\n\nGeometry recipes for joints that actually rotate without binding — clevis-tongue hinges, hinge chains, hard stops.\n\nAnything that must rotate or slide gets connector frames: `origin` = pivot, `axis` = hinge line, `up` = rest twist. Always set `up` on hinges, wheels, and levers. Connector/link/mate semantics and mirrored-axis sign rules: see the assembly API reference.\n\n## The Cavity Rule\n\nEvery joint is a **cavity** in one part plus a **tenon** in the other, and the cavity must be a real empty volume — not a gap implied by separate solids. A body that runs solid through the joint zone (e.g. a stadium cap under the clevis slot) blocks rotation even though the rest pose looks fine. End the body FLAT before the joint; extend the tines forward to the pivot; the inter-tine volume must be genuinely empty.\n\nDiagnostic: adjacent-part collision volume > expected clearance in `forgecad run` = missing cavity (both parts have solid material at the joint position). After fixing, the collision volume should drop to ~0 (or a few mm³ of clearance overlap).\n\n## Structural Sizing\n\n**Yoke (connecting cantilevers).** Clevis tines at Y = ±Y_OFF are physically disconnected from a body of thickness TONG_T when Y_OFF > TONG_T/2 + clearance — the tines float and would snap under load. Always bridge with a yoke slab spanning the full clevis width, `(Y_OFF + TINE_T/2) * 2`, with a few mm of structural overlap along the joint axis, so material runs continuously from body to each tine.\n\n**Knuckle radius.** For body height H, require `KNUCK_R >= H/2`. Smaller, and the body corners protrude past the knuckle's cylindrical envelope and sweep into the adjacent part during rotation. `KNUCK_R = H/2` makes the body cross-section a stadium that exactly fits the envelope.\n\n## Hard Stops vs Slider Limits\n\nDeclared joint min/max are not geometry — they only constrain the viewport slider; the geometry still permits any rotation. A physical stop requires an interfering protrusion:\n\n- **Extension stop at 0°**: a small lip on the dorsal side of the child's proximal end, sized to just touch the parent's distal dorsal corner at 0°; backbending is then blocked by contact.\n- **Flexion stop at θmax**: a palmar lip, or body-on-body contact when bodies meet.\n\nVerify: ~0 mm³ collision exactly at the limit pose (just touching), non-zero past it.\n\n## Verification Workflow\n\nCheck the loop, not just the rest pose:\n\n1. Build at rest; `forgecad run`; check collision volumes.\n2. Overlap > clearance volume between joint neighbors → apply the cavity rule.\n3. Render each part with `--focus PartName`; the clevis end must show a visible gap between tines.\n4. Re-check at swept angles (30°/60°/90°) — rotation reveals collisions the rest pose hides.\n5. Backbend test at -10°: blocked = hard stop exists; rotates = add a stop.\n";
312
312
  const AI_USAGE_DOC = "/docs/ai-usage";
313
313
  function copyText(text) {
314
314
  var _a;
@@ -1181,7 +1181,7 @@ function Ve({ defaultValue: e, defaultLanguage: r, defaultPath: n, value: t, lan
1181
1181
  var fe = Ve;
1182
1182
  var de = reactExports.memo(fe);
1183
1183
  var Ft = de;
1184
- const FORGE_TYPES = '// AUTO-GENERATED — do not edit by hand.\n// Regenerate: npm run gen:types (source: src/forge/forge-public-api.ts)\n// External type stubs (opaque — not user-facing)\ntype opentype$1 = unknown;\ntype Manifold = unknown;\n\n// Generated by dts-bundle-generator v9.5.1\n\ntype Vec3 = [\n number,\n number,\n number\n];\ntype Mat4 = [\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number\n];\ntype TransformInput = Transform | Mat4;\ntype RotateAroundToMode = "plane" | "line";\ninterface RotateAroundToOptions {\n mode?: RotateAroundToMode;\n}\ndeclare class Transform {\n private readonly m;\n private constructor();\n /** Return the identity transform. */\n static identity(): Transform;\n /** Wrap an existing `Transform` or raw 4x4 matrix as a `Transform`. */\n static from(input: TransformInput): Transform;\n /**\n * Compose transforms in chain order: `Transform.compose(a, b, c)` applies\n * `a`, then `b`, then `c` — the same left-to-right order as\n * `Transform.from(a).mul(b).mul(c)`.\n *\n * Prefer this over manual `.mul()` chains when composing 3+ transforms\n * (e.g. kinematics: `local -> childBase -> jointMotion -> jointFrame ->\n * parentWorld`); the variadic form makes the application order explicit and\n * prevents order mistakes.\n *\n * **Example**\n *\n * ```ts\n * const world = Transform.compose(childBase, jointMotion, jointFrame, parentWorld);\n * ```\n *\n * @param steps Transforms (or raw 4x4 matrices) applied left to right.\n * @returns The composed transform. With no arguments, the identity.\n */\n static compose(...steps: TransformInput[]): Transform;\n /** Create a translation transform. */\n static translation(x: number, y: number, z: number): Transform;\n /** Create a uniform or per-axis scale transform. */\n static scale(v: number | Vec3): Transform;\n /** Create a rotation around an arbitrary axis, optionally about a pivot. */\n static rotationAxis(axis: Vec3, angleDeg: number, pivot?: Vec3): Transform;\n /** Solve the rotation needed to move one point onto a target line or plane. */\n static rotateAroundTo(axis: Vec3, pivot: Vec3, movingPoint: Vec3, targetPoint: Vec3, options?: RotateAroundToOptions): Transform;\n /** Compose transforms in chain order: `a.mul(b)` applies `a`, then `b`. */\n mul(other: TransformInput): Transform;\n /** Translate after the current transform. */\n translate(x: number, y: number, z: number): Transform;\n /** Rotate after the current transform. */\n rotateAxis(axis: Vec3, angleDeg: number, pivot?: Vec3): Transform;\n /** Rotate about the X axis after the current transform (parity with `Shape.rotateX`). */\n rotateX(angleDeg: number, pivot?: Vec3): Transform;\n /** Rotate about the Y axis after the current transform (parity with `Shape.rotateY`). */\n rotateY(angleDeg: number, pivot?: Vec3): Transform;\n /** Rotate about the Z axis after the current transform (parity with `Shape.rotateZ`). */\n rotateZ(angleDeg: number, pivot?: Vec3): Transform;\n /** Scale after the current transform. */\n scale(v: number | Vec3): Transform;\n /** Return the inverse transform. */\n inverse(): Transform;\n /** Transform a point using homogeneous coordinates. */\n point(p: Vec3): Vec3;\n /** Transform a direction vector without translation. */\n vector(v: Vec3): Vec3;\n /** Return the transform as a raw 4x4 matrix array. */\n toArray(): Mat4;\n}\n/**\n * Compose transforms in chain order.\n * Equivalent to Transform.identity().mul(a).mul(b).mul(c)...\n *\n * @softDeprecated Transform.compose(a, b, ...)\n * @deprecated use Transform.compose(a, b, ...)\n */\ndeclare function composeChain(...steps: TransformInput[]): Transform;\ntype ConnectorGender = "male" | "female" | "neutral";\ninterface PortDef {\n origin: Vec3;\n axis: Vec3;\n up: Vec3;\n extent?: number;\n kind?: JointType;\n min?: number;\n max?: number;\n connectorType?: string;\n gender?: ConnectorGender;\n measurements?: Record<string, number | string>;\n}\ntype PortAlign = "middle" | "start" | "end";\ninterface PortInput {\n origin?: [\n number,\n number,\n number\n ];\n axis?: [\n number,\n number,\n number\n ];\n start?: [\n number,\n number,\n number\n ];\n end?: [\n number,\n number,\n number\n ];\n up?: [\n number,\n number,\n number\n ];\n kind?: JointType;\n min?: number;\n max?: number;\n}\ntype PortMap = Record<string, PortDef>;\ntype ConnectorGender$1 = "male" | "female" | "neutral";\ninterface ConnectorInput extends PortInput {\n connectorType?: string;\n gender?: ConnectorGender$1;\n measurements?: Record<string, number | string>;\n}\ntype ConnectorDef = PortDef;\ntype ConnectorMap = PortMap;\ninterface MatchToOptions {\n force?: boolean;\n angle?: number;\n distance?: number;\n}\n/**\n * Create a connector — a named attachment point on a shape.\n *\n * Overloads:\n * - `connector(geometry)` — bare connector (position + orientation only)\n * - `connector(type, geometry)` — typed connector for compatibility matching\n * - `connector(type, geometry, measurements)` — typed with measurement metadata\n */\ndeclare function connectorFactory(typeOrInput: string | PortInput, inputOrMeasurements?: PortInput | Record<string, number | string>, measurements?: Record<string, number | string>): ConnectorInput;\ndeclare namespace connectorFactory {\n var male: (typeOrInput: string | PortInput, inputOrMeasurements?: PortInput | Record<string, number | string>, measurements?: Record<string, number | string>) => ConnectorInput;\n var female: (typeOrInput: string | PortInput, inputOrMeasurements?: PortInput | Record<string, number | string>, measurements?: Record<string, number | string>) => ConnectorInput;\n var neutral: (typeOrInput: string | PortInput, inputOrMeasurements?: PortInput | Record<string, number | string>, measurements?: Record<string, number | string>) => ConnectorInput;\n}\ndeclare const ANCHOR3D_NAMES: readonly [\n "center",\n "front",\n "back",\n "left",\n "right",\n "top",\n "bottom",\n "front-left",\n "front-right",\n "back-left",\n "back-right",\n "top-front",\n "top-back",\n "top-left",\n "top-right",\n "bottom-front",\n "bottom-back",\n "bottom-left",\n "bottom-right",\n "top-front-left",\n "top-front-right",\n "top-back-left",\n "top-back-right",\n "bottom-front-left",\n "bottom-front-right",\n "bottom-back-left",\n "bottom-back-right"\n];\ntype Anchor3D = (typeof ANCHOR3D_NAMES)[number];\ntype SketchFace3D = "front" | "back" | "left" | "right" | "top" | "bottom";\ninterface ShapeQueryOwner {\n id: string;\n operation: string;\n}\ntype TopologyRewriteQueryOutcome = "preserved" | "split" | "merged";\ninterface CanonicalFaceQueryRef {\n kind: "canonical-face";\n face: SketchFace3D;\n owner?: ShapeQueryOwner;\n}\ninterface TrackedFaceQueryRef {\n kind: "tracked-face";\n faceName: string;\n owner?: ShapeQueryOwner;\n}\ninterface DirectFaceQueryRef {\n kind: "face-ref";\n faceName?: string;\n owner?: ShapeQueryOwner;\n}\ninterface PropagatedFaceQueryRef {\n kind: "propagated-face";\n rewriteId: string;\n outcome: TopologyRewriteQueryOutcome;\n source: FaceQueryRef;\n owner?: ShapeQueryOwner;\n}\ninterface CreatedFaceQueryRef {\n kind: "created-face";\n rewriteId: string;\n operation: string;\n slot: string;\n owner?: ShapeQueryOwner;\n}\ntype FaceQueryRef = CanonicalFaceQueryRef | TrackedFaceQueryRef | DirectFaceQueryRef | PropagatedFaceQueryRef | CreatedFaceQueryRef;\ntype EdgeQuerySelector = "edge" | "start" | "end" | "midpoint";\ninterface TrackedEdgeQueryRef {\n kind: "tracked-edge";\n edgeName: string;\n selector: EdgeQuerySelector;\n owner?: ShapeQueryOwner;\n}\ninterface DirectEdgeQueryRef {\n kind: "edge-ref";\n edgeName?: string;\n selector: EdgeQuerySelector;\n owner?: ShapeQueryOwner;\n}\ninterface PropagatedEdgeQueryRef {\n kind: "propagated-edge";\n rewriteId: string;\n outcome: TopologyRewriteQueryOutcome;\n source: EdgeQueryRef;\n selector: EdgeQuerySelector;\n owner?: ShapeQueryOwner;\n}\ninterface CreatedEdgeQueryRef {\n kind: "created-edge";\n rewriteId: string;\n operation: string;\n slot: string;\n selector: EdgeQuerySelector;\n owner?: ShapeQueryOwner;\n}\ntype EdgeQueryRef = TrackedEdgeQueryRef | DirectEdgeQueryRef | PropagatedEdgeQueryRef | CreatedEdgeQueryRef;\ntype FaceDescendantSemantic = "face" | "region" | "set";\ninterface FaceDescendantMetadata {\n kind: "single" | "face-set";\n semantic: FaceDescendantSemantic;\n memberCount: number;\n memberNames: string[];\n coplanar: boolean;\n}\ninterface ConstraintTypeMap {\n /**\n * Forces two points to occupy the same position.\n *\n * This is the most fundamental connectivity constraint — use it to join\n * line endpoints, close a polygon, or snap a point to another point.\n * Contributes **2 equations** (one per axis).\n */\n coincident: {\n a: PointId;\n b: PointId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces a line to be horizontal (parallel to the X axis).\n *\n * Both endpoints are moved to their average Y coordinate so the line\n * remains centered in place. Contributes **1 equation**: `b.y − a.y = 0`.\n */\n horizontal: {\n line: LineId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces a line to be vertical (parallel to the Y axis).\n *\n * Both endpoints are moved to their average X coordinate so the line\n * remains centered in place. Contributes **1 equation**: `b.x − a.x = 0`.\n */\n vertical: {\n line: LineId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces lines `a` and `b` to be parallel.\n *\n * The direction of `b` is rotated to match `a`\'s direction (either\n * co-directional or anti-parallel — whichever is closer to the current\n * orientation). Line `a` is treated as the reference; only `b` is moved.\n * Contributes **1 equation**: `cross(unit_a, unit_b) = 0`.\n */\n parallel: {\n a: LineId;\n b: LineId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces lines `a` and `b` to be perpendicular (90° apart).\n *\n * The direction of `b` is rotated to be ±90° from `a`, choosing the\n * sign closest to the current orientation. Line `a` is the reference;\n * only `b` is moved. Contributes **1 equation**: `dot(unit_a, unit_b) = 0`.\n */\n perpendicular: {\n a: LineId;\n b: LineId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Constrains tangency between a line and a circle, or between two circles.\n *\n * **Line–circle** (`line` + `circle`): the perpendicular distance from the\n * circle\'s center to the infinite line equals the circle\'s radius.\n *\n * **Circle–circle** (`a` + `b`): the two circles are externally tangent —\n * the distance between centers equals the sum of their radii.\n *\n * Exactly one mode must be active (provide either `line`+`circle` or `a`+`b`).\n * Contributes **1 equation**.\n */\n tangent: {\n line?: LineId;\n circle?: CircleId;\n a?: CircleId;\n b?: CircleId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces lines `a` and `b` to have the same length.\n *\n * Line `a`\'s length is used as the target; `b`\'s endpoints are scaled\n * symmetrically along `b`\'s current direction to match it.\n * Contributes **1 equation**: `|b| − |a| = 0`.\n */\n equal: {\n a: LineId;\n b: LineId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces points `a` and `b` to be mirror images of each other across\n * the infinite line through `axis`.\n *\n * When neither point is fixed, `b` is moved to the reflection of `a`.\n * When `b` is fixed, `a` is moved instead. Contributes **2 equations**\n * (one per axis): `b − reflect(a, axis) = [0, 0]`.\n */\n symmetric: {\n a: PointId;\n b: PointId;\n axis: LineId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces two circles to share the same center point.\n *\n * The centers are merged to their average position (or snapped to the fixed\n * one if either is fixed). Contributes **2 equations**\n * (one per axis): `center_b − center_a = [0, 0]`.\n */\n concentric: {\n a: CircleId;\n b: CircleId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces a point to lie on the infinite line passing through a line segment.\n *\n * The point is projected onto the line\'s infinite extension (not clamped to\n * the segment). Contributes **1 equation**: signed distance from the point\n * to the line = 0.\n */\n collinear: {\n point: PointId;\n line: LineId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Pins a point to an absolute position `(x, y)` in sketch space.\n *\n * Rust treats the point as externally pinned geometry. `equations: 0`\n * is intentional because the point\'s mobility is removed through the\n * `fixed` flag rather than by adding a residual row.\n */\n fixed: {\n point: PointId;\n x: number;\n y: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces a point to sit at the exact midpoint of a line segment.\n *\n * Rust enforces this as two scalar equations, one per axis:\n * `point - (a + b) / 2 = [0, 0]`.\n */\n midpoint: {\n point: PointId;\n line: LineId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces a point to lie exactly on the circumference of a circle.\n *\n * The point is moved radially so its distance from the center equals the\n * radius. Contributes **1 equation**: `|point − center| − radius = 0`.\n */\n pointOnCircle: {\n point: PointId;\n circle: CircleId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces a point to lie on a **bounded** line segment — not merely the\n * infinite extension of the line.\n *\n * The point is projected onto the segment and the parameter `t` is clamped\n * to `[0, 1]`. When `t` is already inside that range the behaviour is\n * identical to `collinear`. When the point would project outside the\n * segment it is snapped to the nearest endpoint instead.\n *\n * Contributes **1 equation** (like `collinear`): the point can still slide\n * along the segment, giving it one remaining degree of freedom.\n */\n pointOnLine: {\n point: PointId;\n line: LineId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the Euclidean distance between two points to `value`.\n *\n * Points are moved symmetrically along the current direction vector so the\n * center of the pair stays fixed. Contributes **1 equation**:\n * `|b − a| − value = 0`.\n */\n distance: {\n a: PointId;\n b: PointId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the length of a line segment to `value`.\n *\n * Endpoints are scaled symmetrically about the line\'s midpoint while\n * preserving its direction. Contributes **1 equation**:\n * `|b − a| − value = 0`.\n */\n length: {\n line: LineId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the angle from line `a` to line `b` to `value` degrees (CCW).\n * Line `a` is the reference; only `b` is rotated. Contributes **1 equation**.\n */\n angle: {\n a: LineId;\n b: LineId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the radius of a circle to `value`.\n *\n * Has no effect if the circle\'s `fixedRadius` flag is set.\n * Contributes **1 equation**: `radius − value = 0`.\n */\n radius: {\n circle: CircleId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the diameter of a circle to `value` (i.e. `radius = value / 2`).\n *\n * Has no effect if the circle\'s `fixedRadius` flag is set.\n * Contributes **1 equation**: `radius − value / 2 = 0`.\n */\n diameter: {\n circle: CircleId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the signed horizontal distance from point `a` to point `b` to `value`.\n *\n * The constraint is directional: `b.x − a.x = value`. A positive value places\n * `b` to the right of `a`; negative places it to the left.\n * Contributes **1 equation**.\n */\n hDistance: {\n a: PointId;\n b: PointId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the signed vertical distance from point `a` to point `b` to `value`.\n *\n * The constraint is directional: `b.y − a.y = value`. A positive value places\n * `b` above `a`; negative places it below.\n * Contributes **1 equation**.\n */\n vDistance: {\n a: PointId;\n b: PointId;\n value: number;\n };\n}\ninterface HighlightDef {\n /** Entity ID to highlight (edge, point, surface, circle, arc). */\n entityId: string;\n /** Override color (CSS color string). Default: \'#ff00ff\' (magenta). */\n color?: string;\n /** Optional label to display near the entity. */\n label?: string;\n /** When true, animate opacity between 0.5 and 1.0 for attention. */\n pulse?: boolean;\n}\ninterface HighlightOptions {\n color?: string;\n label?: string;\n pulse?: boolean;\n /** Size hint for points (radius in mm) or planes (disc radius in mm). */\n size?: number;\n /**\n * Shape inputs only: when true, annotate every user-authored labeled face\n * with its label name (one plane highlight per labeled face). The whole-shape\n * tint is added only when other visual options (`color`, `label`, `pulse`)\n * are also passed.\n */\n labels?: boolean;\n}\ndeclare const __brand: unique symbol;\ntype Brand<T, B extends string> = T & {\n readonly [__brand]: B;\n};\ntype PointId = Brand<string, "PointId">;\ntype LineId = Brand<string, "LineId">;\ntype CircleId = Brand<string, "CircleId">;\ntype ArcId = Brand<string, "ArcId">;\ntype BezierId = Brand<string, "BezierId">;\ntype ShapeId = Brand<string, "ShapeId">;\ntype GroupId = Brand<string, "GroupId">;\ninterface SketchArc {\n id: ArcId;\n /** Center point of the arc\'s circle. */\n center: PointId;\n /** Point on the arc where it begins. Must lie on the circle. */\n start: PointId;\n /** Point on the arc where it ends. Must lie on the circle. */\n end: PointId;\n /** Current solved radius — kept consistent with |center–start| and |center–end| by the solver. */\n radius: number;\n /** True → arc sweeps clockwise from start to end; false → counter-clockwise. */\n clockwise: boolean;\n /** When true, the solver treats radius as a constant (no variable, no perturbation). */\n fixedRadius: boolean;\n construction: boolean;\n /** Optional human-readable name for display. */\n name?: string;\n}\ninterface SketchShape {\n id: ShapeId;\n /** Ordered list of line IDs forming a closed polygon. */\n lines: LineId[];\n}\ninterface SketchGroupLocalPoint {\n id: PointId;\n lx: number;\n ly: number;\n}\ninterface SketchGroup {\n id: GroupId;\n /** World position of the group\'s local origin. */\n x: number;\n y: number;\n /** Rotation angle (radians) of the group frame. */\n theta: number;\n /** When true, all 3 DOF are frozen. */\n fixed: boolean;\n /** When true, θ is frozen — only translation DOF remain. */\n fixedRotation: boolean;\n /** Points in local coordinates. */\n points: SketchGroupLocalPoint[];\n /** Lines connecting local points (using their global IDs). */\n lines: {\n id: LineId;\n a: PointId;\n b: PointId;\n }[];\n}\ninterface SketchPoint {\n id: PointId;\n x: number;\n y: number;\n fixed: boolean;\n}\ninterface SketchLine {\n id: LineId;\n a: PointId;\n b: PointId;\n construction: boolean;\n /** Optional human-readable name for display (e.g. "top", "bottom"). */\n name?: string;\n}\ninterface SketchCircle {\n id: CircleId;\n center: PointId;\n radius: number;\n construction: boolean;\n fixedRadius: boolean;\n segments: number;\n /** Optional human-readable name for display. */\n name?: string;\n}\ninterface SketchBezier {\n id: BezierId;\n /** First control point (start of curve). */\n p0: PointId;\n /** Second control point (controls tangent at start). */\n p1: PointId;\n /** Third control point (controls tangent at end). */\n p2: PointId;\n /** Fourth control point (end of curve). */\n p3: PointId;\n construction: boolean;\n /** Optional human-readable name for display. */\n name?: string;\n}\ntype ProfileSegment = {\n kind: "line";\n line: LineId;\n label?: string;\n} | {\n kind: "arc";\n arc: ArcId;\n label?: string;\n} | {\n kind: "bezier";\n bezier: BezierId;\n label?: string;\n};\ntype SketchLoop = {\n type: "poly";\n points: PointId[];\n} | {\n type: "circle";\n circle: CircleId;\n}\n/** Mixed profile of line and arc segments forming a closed loop. */\n | {\n type: "profile";\n segments: ProfileSegment[];\n};\ntype AnnotationElement = \n/** Symbol placed at a specific position (geometric constraints like parallel, equal, fixed). */\n{\n kind: "symbol";\n position: [\n number,\n number\n ];\n symbol: ConstraintSymbol;\n rotation?: number;\n}\n/** Dimension line with extension lines (length, distance). */\n | {\n kind: "dimension";\n from: [\n number,\n number\n ];\n to: [\n number,\n number\n ];\n offset: number;\n value: string;\n}\n/** Angle arc between two directions (angle, absoluteAngle). */\n | {\n kind: "angle-arc";\n center: [\n number,\n number\n ];\n startAngle: number;\n endAngle: number;\n radius: number;\n value: string;\n}\n/** Fallback text label for constraints not yet migrated to annotations. */\n | {\n kind: "text";\n position: [\n number,\n number\n ];\n text: string;\n};\ntype ConstraintSymbol = "parallel" | "equal" | "perpendicular" | "horizontal" | "vertical" | "fixed" | "midpoint" | "coincident" | "collinear" | "tangent" | "concentric" | "ccw" | "symmetric";\ninterface ConstraintDisplay {\n id: string;\n type: string;\n label: string;\n /** Text position used as fallback when annotations are not defined. */\n position: [\n number,\n number\n ];\n value?: number;\n isDimension: boolean;\n /** True when the solver failed to satisfy this constraint (genuinely conflicting geometry). */\n isConflicting: boolean;\n /** True when this constraint is mathematically redundant — it duplicates an equation already\n * provided by another constraint, making the DOF count negative even though the solver converges. */\n isRedundant: boolean;\n /** For rejected constraints: why the builder rejected it (maxError, constraint params, blame). */\n rejectionReason?: string;\n /** Entity IDs referenced by this constraint (points, lines, circles, etc.). */\n entityIds: string[];\n /** Per-equation residual error for this constraint (how far off it is). */\n residual: number;\n /** Annotation elements for this constraint. Empty array → use text fallback. */\n annotations: AnnotationElement[];\n}\ninterface SurfaceDisplay {\n /** Zero-based index, largest-first by area. */\n index: number;\n /** Region area in mm². */\n area: number;\n /** Centroid of the region polygon. */\n centroid: [\n number,\n number\n ];\n /** Axis-aligned bounding box. */\n bounds: {\n min: [\n number,\n number\n ];\n max: [\n number,\n number\n ];\n };\n /** A point guaranteed to be inside the region — usable as seed for detectArrangementRegion(). */\n seed: [\n number,\n number\n ];\n /** Polygon vertices (CCW winding) for rendering the region fill. */\n polygon: [\n number,\n number\n ][];\n}\ninterface SketchConstraintMeta {\n status: "under" | "fully" | "over" | "over-redundant";\n /** Net degrees of freedom: positive = under-constrained, 0 = fully, negative = over-constrained. */\n dof: number;\n maxError: number;\n constraints: ConstraintDisplay[];\n rejected: ConstraintDisplay[];\n /** Detected surfaces from line arrangement (DCEL face detection). Empty if no closed regions. */\n surfaces: SurfaceDisplay[];\n construction: {\n lines: {\n id: string;\n a: [\n number,\n number\n ];\n b: [\n number,\n number\n ];\n }[];\n circles: {\n id: string;\n center: [\n number,\n number\n ];\n radius: number;\n }[];\n arcs: {\n id: string;\n center: [\n number,\n number\n ];\n start: [\n number,\n number\n ];\n end: [\n number,\n number\n ];\n radius: number;\n clockwise: boolean;\n }[];\n };\n /** Non-construction geometry edges rendered as solid wireframe overlay. */\n edges: {\n lines: {\n id: string;\n name?: string;\n a: [\n number,\n number\n ];\n b: [\n number,\n number\n ];\n }[];\n circles: {\n id: string;\n name?: string;\n center: [\n number,\n number\n ];\n radius: number;\n }[];\n arcs: {\n id: string;\n name?: string;\n center: [\n number,\n number\n ];\n start: [\n number,\n number\n ];\n end: [\n number,\n number\n ];\n radius: number;\n clockwise: boolean;\n }[];\n beziers: {\n id: string;\n name?: string;\n points: [\n number,\n number\n ][];\n }[];\n points: {\n id: string;\n pos: [\n number,\n number\n ];\n }[];\n };\n /** True when the solver hit its time budget before fully converging. */\n timedOut?: boolean;\n /** Opt-in solver debug artifacts returned by the Rust planner. */\n debug?: SolverDebugArtifacts;\n /** Programmatic debug highlights from user code. */\n highlights?: HighlightDef[];\n}\ninterface ConstraintDefinition {\n points: SketchPoint[];\n lines: SketchLine[];\n circles: SketchCircle[];\n arcs: SketchArc[];\n beziers: SketchBezier[];\n shapes: SketchShape[];\n /** Rigid-body groups — the solver sees 3 DOF per group instead of 2N per point. */\n groups: SketchGroup[];\n loops: SketchLoop[];\n constraints: SketchConstraint[];\n rejectedConstraints: SketchConstraint[];\n /** Maps rejected constraint ID → human-readable reason. Populated by the builder. */\n rejectionReasons?: Map<string, string>;\n}\ninterface SolveOptions {\n /** Maximum number of LM outer iterations per restart. */\n iterations?: number;\n /** Infinity-norm residual tolerance for declaring convergence. */\n tolerance?: number;\n /** Number of deterministic restart seeds used by the global solver. */\n restarts?: number;\n /** Optional projector iterations used only for initialisation, not as the main solver. */\n warmStartIterations?: number;\n /** Maximum LM step length in scaled variable space. Larger = bolder, smaller = safer. */\n maxScaledStep?: number;\n /** Skip redundancy detection (safe when topology is unchanged and previous DOF >= 0). */\n skipRedundancyCheck?: boolean;\n /** Run the targeted presolve hook for this constraint before the main solve. */\n presolveConstraintId?: string;\n /** When set and the first solve exceeds tolerance*5, retry with this many restarts. */\n fallbackRestarts?: number;\n /** Add constraints progressively with short LM solves, all in one WASM call. */\n progressive?: boolean;\n /** Wall-clock time budget in ms for the entire solve. 0 = no limit. */\n timeBudgetMs?: number;\n /** Capture a readable constructive transcript in `constraintMeta.debug`. */\n debugConstructiveTranscript?: boolean;\n /** Capture SVG snapshots for constructive steps in `constraintMeta.debug`. */\n debugSvgSnapshots?: boolean;\n}\ninterface SolverConstraintResidual {\n id: string;\n residual: number;\n}\ninterface ConstructiveTranscriptStep {\n index: number;\n label: string;\n kind: string;\n summary: string;\n outcome: string;\n localError: number;\n topResiduals: SolverConstraintResidual[];\n branch?: string;\n snapshotLabel?: string;\n}\ninterface SolverSvgSnapshot {\n label: string;\n error: number;\n svg: string;\n}\ninterface SolverDebugArtifacts {\n constructiveTranscript: ConstructiveTranscriptStep[];\n svgSnapshots: SolverSvgSnapshot[];\n}\ninterface ConstraintTypeMap {\n}\ninterface ConstraintBuilderMethods {\n moveTo(x: number, y: number): this;\n lineTo(x: number, y: number): this;\n lineH(dx: number): this;\n lineV(dy: number): this;\n lineAngled(length: number, degrees: number): this;\n arcTo(x: number, y: number, radius: number, clockwise?: boolean): this;\n arcByCenter(centerId: PointId, startId: PointId, endId: PointId, clockwise?: boolean, name?: string): ArcId;\n bezier(p0: any, p1: any, p2: any, p3: any, name?: string): BezierId;\n bezierTo(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number): this;\n blendTo(x: number, y: number, weight?: number): this;\n close(): this;\n addLoopCircle(center: PointId, radius: number, segments?: number): this;\n addLoop(points: any[]): this;\n addProfileLoop(segments: Array<{\n kind: "line";\n line: any;\n } | {\n kind: "arc";\n arc: any;\n } | {\n kind: "bezier";\n bezier: any;\n }>): this;\n horizontal(line: any): this;\n vertical(line: any): this;\n parallel(a: any, b: any): this;\n sameDirection(a: any, b: any): this;\n oppositeDirection(a: any, b: any): this;\n blockRotation(points: any[], axis?: "x" | "y"): this;\n perpendicular(a: any, b: any): this;\n tangent(a: any, b: any): this;\n equal(a: any, b: any): this;\n coincident(a: any, b: any): this;\n concentric(a: any, b: any): this;\n collinear(point: any, line: any): this;\n symmetric(a: any, b: any, axis: any): this;\n fix(point: any, x?: number, y?: number): this;\n midpoint(point: any, line: any): this;\n pointOnCircle(point: any, circle: any): this;\n pointOnLine(point: any, line: any): this;\n distance(a: any, b: any, value: number): this;\n length(line: any, value: number): this;\n angle(a: any, b: any, value: number): this;\n radius(circle: any, value: number): this;\n diameter(circle: any, value: number): this;\n hDistance(a: any, b: any, value: number): this;\n vDistance(a: any, b: any, value: number): this;\n pointLineDistance(point: any, line: any, value: number): this;\n lineDistance(a: any, b: any, value: number): this;\n absoluteAngle(line: any, value: number): this;\n equalRadius(a: any, b: any): this;\n arcLength(arc: any, value: number): this;\n lineTangentArc(line: any, arc: any, atStart: boolean): this;\n arcTangentArc(arcA: any, arcB: any, aAtStart?: boolean, bAtStart?: boolean): this;\n bezierTangentArc(bezier: any, arc: any, atBezierStart: boolean, atArcStart: boolean): this;\n smoothBlend(arc1: any, arc2: any, options?: {\n weight?: number;\n arc1End?: "start" | "end";\n arc2End?: "start" | "end";\n }): BezierId;\n shapeWidth(shape: any, value: number): this;\n shapeHeight(shape: any, value: number): this;\n shapeCentroidX(shape: any, value: number): this;\n shapeCentroidY(shape: any, value: number): this;\n shapeArea(shape: any, value: number): this;\n shapeEqualCentroid(a: any, b: any): this;\n angleBetween(a: any, b: any, value: number): this;\n ccw(...points: any[]): this;\n importPoint(pt: {\n x: number;\n y: number;\n }, fixed?: boolean): PointId;\n importLine(l: {\n start: {\n x: number;\n y: number;\n };\n end: {\n x: number;\n y: number;\n };\n }, fixed?: boolean): LineId;\n importRectangle(r: {\n vertices: [\n {\n x: number;\n y: number;\n },\n {\n x: number;\n y: number;\n },\n {\n x: number;\n y: number;\n },\n {\n x: number;\n y: number;\n }\n ];\n }, fixed?: boolean): {\n bottom: LineId;\n right: LineId;\n top: LineId;\n left: LineId;\n points: [\n PointId,\n PointId,\n PointId,\n PointId\n ];\n };\n referencePoint(x: number, y: number): PointId;\n referenceLine(x1: number, y1: number, x2: number, y2: number): LineId;\n referenceFrom(source: any, entityId: string): PointId | LineId | null;\n referenceAllFrom(source: any): {\n points: Map<string, PointId>;\n lines: Map<string, LineId>;\n };\n}\ntype SketchConstraint = {\n [K in keyof ConstraintTypeMap]: {\n id: string;\n type: K;\n } & ConstraintTypeMap[K];\n}[keyof ConstraintTypeMap];\ninterface ConstraintTypeMap {\n /**\n * Forces lines `a` and `b` to be parallel **and** separated by a signed\n * perpendicular distance of `value`.\n *\n * The distance is measured from the midpoint of `a` to the midpoint of `b`\n * along `a`\'s left-normal direction. Positive values place `b` to the left\n * of `a` (when facing `a`\'s direction).\n *\n * This constraint combines two equations:\n * 1. `cross(unit_a, unit_b) = 0` — parallelism\n * 2. `perpDist(mid_b, line_a) − value = 0` — offset distance\n *\n * Contributes **2 equations**.\n */\n lineDistance: {\n a: LineId;\n b: LineId;\n value: number;\n };\n}\n/** Exported for backward-compatibility with forge-public-api.ts */\ntype LineDistanceConstraint = {\n id: string;\n type: "lineDistance";\n} & {\n a: LineId;\n b: LineId;\n value: number;\n};\ninterface ConstraintTypeMap {\n /**\n * Sets the angle of a line from the positive X axis to exactly `value` degrees.\n * The direction is enforced as-is (a→b). Contributes **1 equation**:\n * `normalizeAngle(angle − target) = 0`.\n */\n absoluteAngle: {\n line: LineId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces two circles to have the same radius.\n *\n * Rust enforces one scalar equality: `radius_b - radius_a = 0`.\n */\n equalRadius: {\n a: CircleId;\n b: CircleId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the arc length of an arc to `value`.\n *\n * Arc length is defined as `radius × sweep`, where `sweep` is the angle\n * (in radians) from the start point to the end point in the arc\'s direction.\n * A zero-length sweep is treated as a full circle (2π).\n *\n * Rust enforces this as one scalar equation: `radius × sweep - value = 0`.\n */\n arcLength: {\n arc: ArcId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Constrains a line to be tangent to an arc at the arc\'s start or end point.\n *\n * Tangency requires the line\'s direction to be perpendicular to the arc\'s\n * radius at the contact point. Set `atStart: true` to use the arc\'s start\n * point as the tangency contact; `false` uses the end point.\n *\n * Rust enforces tangency as one scalar orthogonality equation between the\n * line direction and the chosen radius direction.\n */\n lineTangentArc: {\n line: LineId;\n arc: ArcId;\n atStart: boolean;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the X coordinate of a polygon\'s arithmetic centroid to `value`.\n *\n * All non-fixed vertices are translated horizontally by the same amount so\n * that `mean(vertices.x) = value`. The shape\'s size, proportions, and Y\n * position are unaffected. Contributes **1 equation**.\n */\n shapeCentroidX: {\n shape: ShapeId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the Y coordinate of a polygon\'s arithmetic centroid to `value`.\n *\n * All non-fixed vertices are translated vertically by the same amount so\n * that `mean(vertices.y) = value`. The shape\'s size, proportions, and X\n * position are unaffected. Contributes **1 equation**.\n */\n shapeCentroidY: {\n shape: ShapeId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the axis-aligned bounding-box width of a polygon shape to `value`.\n *\n * All non-fixed vertices are scaled horizontally (`x` only) from the\n * bounding-box center: `pt.x = cx + (pt.x − cx) × (value / width)`.\n * The shape\'s height and Y position are unaffected.\n * Contributes **1 equation**.\n */\n shapeWidth: {\n shape: ShapeId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the axis-aligned bounding-box height of a polygon shape to `value`.\n *\n * All non-fixed vertices are scaled vertically (`y` only) from the\n * bounding-box center: `pt.y = cy + (pt.y − cy) × (value / height)`.\n * The shape\'s width and X position are unaffected.\n * Contributes **1 equation**.\n */\n shapeHeight: {\n shape: ShapeId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the enclosed area of a polygon shape to `value`.\n *\n * Area is computed via the shoelace formula on the ordered vertex list.\n * All non-fixed vertices are scaled uniformly from the polygon\'s arithmetic\n * centroid: `scale = sqrt(target / current)`, so the shape\'s proportions\n * and position are preserved. Contributes **1 equation**.\n */\n shapeArea: {\n shape: ShapeId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces two shapes to share the same centroid.\n *\n * Translates the non-fixed vertices of each shape so that their arithmetic\n * centroids coincide at the mean of the two current centroids.\n * Contributes **2 equations**: `cx(a) − cx(b) = 0` and `cy(a) − cy(b) = 0`.\n */\n shapeEqualCentroid: {\n a: ShapeId;\n b: ShapeId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Constrains the signed perpendicular distance from a point to an infinite line.\n *\n * Positive `value` places the point to the **left** of the line\n * (when facing the line\'s direction from `a` to `b`). Negative places it\n * to the right. Zero is equivalent to `collinear`.\n * Contributes **1 equation**: `perpDist(point, line) − value = 0`.\n */\n pointLineDistance: {\n point: PointId;\n line: LineId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Enforces counter-clockwise winding order on a polygon defined by `points`.\n *\n * This resolves the discrete orientation ambiguity that arises when a polygon\'s\n * shape is fully determined but its mirror image also satisfies all constraints\n * (e.g. an equilateral triangle with a fixed vertex and side angle).\n *\n * Rust owns the actual branch handling and one-sided residual logic.\n * `equations: 0` is intentional: this constraint removes the mirror branch\n * without claiming an extra continuous degree of freedom.\n */\n ccw: {\n points: PointId[];\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the unsigned angle between lines `a` and `b` to `value` degrees.\n *\n * Unlike `angle` (which is directional — 90 ≠ −90), this constraint\n * accepts both orientations of `b`: whichever of `+value` or `+value+180°`\n * is closer to the current direction is chosen. Use this when you care\n * about the magnitude of the angle but not the sign (e.g. "these two lines\n * are 60° apart" without specifying which side).\n *\n * Contributes **1 equation**: `sin(angleB − angleA − target) = 0`.\n */\n angleBetween: {\n a: LineId;\n b: LineId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces lines `a` and `b` to point in the **same direction** (co-directional).\n *\n * Unlike `parallel` (which allows either co-directional or anti-parallel),\n * this constraint pins the relative direction: `dot(unit_a, unit_b) > 0`\n * AND `cross(unit_a, unit_b) = 0`.\n *\n * Use this with `lineDistance` when the sign of the distance matters —\n * `sameDirection` guarantees the normals point the same way, so positive\n * distance means the same physical side for both lines.\n *\n * Rust uses one continuous parallelism equation plus orientation-aware\n * branch handling to keep `b` facing the same way as `a`.\n */\n sameDirection: {\n a: LineId;\n b: LineId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces lines `a` and `b` to point in **opposite directions** (anti-parallel).\n *\n * Unlike `parallel` (which allows either co-directional or anti-parallel),\n * this constraint pins the relative direction: `dot(unit_a, unit_b) < 0`\n * AND `cross(unit_a, unit_b) = 0`.\n *\n * Use this with `lineDistance` when you need two lines facing each other —\n * e.g. the top of one rect facing the bottom of another.\n *\n * Rust uses one continuous parallelism equation plus orientation-aware\n * branch handling to keep `b` facing opposite to `a`.\n */\n oppositeDirection: {\n a: LineId;\n b: LineId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Prevents 180° rotation of a polygon by ensuring the first edge\n * (p0 → p1) maintains its initial direction sign.\n *\n * For an axis-aligned rectangle [bl, br, tr, tl], this guarantees\n * `br.x > bl.x` — i.e. the "bottom" edge points rightward (+x).\n * Without this, a rect can satisfy CCW winding while being inside-out\n * (negative width AND negative height → positive area → CCW blind spot).\n *\n * Rust treats this as an orientation guard with `equations: 0`, so it\n * preserves the intended branch without claiming an extra continuous DOF.\n *\n * The constraint stores `axis: \'x\' | \'y\'` — which coordinate of the\n * first edge must increase. For rects, this is `\'x\'` (bottom goes right).\n */\n blockRotation: {\n points: PointId[];\n axis: "x" | "y";\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Constrains two arcs to be tangent (G1 smooth) at a shared junction point.\n *\n * The radius vectors of both arcs at the junction must be collinear\n * (cross product of unit radii = 0), ensuring the tangent directions match.\n *\n * Use `coincident` separately to enforce the shared endpoint.\n * Contributes **1 equation**.\n */\n arcTangentArc: {\n arcA: ArcId;\n arcB: ArcId;\n /** Use arc A\'s start (true) or end (false) as the junction. */\n aAtStart: boolean;\n /** Use arc B\'s start (true) or end (false) as the junction. */\n bAtStart: boolean;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Constrains a cubic Bezier curve to be tangent to an arc at one of their endpoints.\n *\n * The Bezier\'s tangent direction (tangent_control − tangent_base) must be\n * perpendicular to the arc\'s radius at the contact point.\n *\n * The builder resolves the Bezier entity to two point IDs:\n * - at bezier start: tangentBase=P0, tangentControl=P1\n * - at bezier end: tangentBase=P3, tangentControl=P2\n *\n * Contributes **1 equation**.\n */\n bezierTangentArc: {\n /** Point on the Bezier at the tangent end (P0 for start, P3 for end). */\n tangentBase: PointId;\n /** Control point defining tangent direction (P1 for start, P2 for end). */\n tangentControl: PointId;\n arc: ArcId;\n /** Use arc start (true) or end (false) as the contact point. */\n atArcStart: boolean;\n };\n}\ninterface ConstrainedSketchBuilder {\n /** Move the cursor to `(x, y)` and start a new profile loop. */\n moveTo(x: number, y: number): this;\n /** Draw a line from the current cursor to `(x, y)`. */\n lineTo(x: number, y: number): this;\n /** Draw a horizontal line of length `dx` from the current cursor. */\n lineH(dx: number): this;\n /** Draw a vertical line of length `dy` from the current cursor. */\n lineV(dy: number): this;\n /** Draw a line of the given `length` at `degrees` from +X. */\n lineAngled(length: number, degrees: number): this;\n /** Draw a circular arc from the current cursor to `(x, y)` with the given radius. */\n arcTo(x: number, y: number, radius: number, clockwise?: boolean): this;\n /** Create an arc from an explicit center point and endpoint IDs. */\n arcByCenter(centerId: PointId, startId: PointId, endId: PointId, clockwise?: boolean, name?: string, fixedRadius?: boolean): ArcId;\n /** Create a cubic Bezier curve from four control points. */\n bezier(p0: any, p1: any, p2: any, p3: any, name?: string): BezierId;\n /** Draw a cubic Bezier from the current cursor to `(x3, y3)`. */\n bezierTo(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number): this;\n /** Draw a smooth Bezier tangent to the previous arc. */\n blendTo(x: number, y: number, weight?: number): this;\n /** Label the current path segment. */\n label(name: string): this;\n /** Close the current path and register the loop. */\n close(): this;\n /** Add a circle loop to the path. */\n addLoopCircle(center: PointId, radius: number, segments?: number): this;\n /** Add a closed polygon loop from point IDs. */\n addLoop(points: any[]): this;\n /** Add a profile loop from prebuilt line/arc/bezier segments. */\n addProfileLoop(segments: Array<{\n kind: "line";\n line: any;\n } | {\n kind: "arc";\n arc: any;\n } | {\n kind: "bezier";\n bezier: any;\n }>): this;\n}\ninterface ConstrainedSketchBuilder {\n /** Constrain a line to be horizontal (parallel to the X axis). */\n horizontal(line: any): this;\n /** Constrain a line to be vertical (parallel to the Y axis). */\n vertical(line: any): this;\n /** Constrain two lines to be parallel. */\n parallel(a: any, b: any): this;\n /** Constrain two lines to point in the same direction. */\n sameDirection(a: any, b: any): this;\n /** Constrain two lines to point in opposite directions. */\n oppositeDirection(a: any, b: any): this;\n /** Prevent 180° rotation of a polygon by anchoring its first edge. */\n blockRotation(points: any[], axis?: "x" | "y"): this;\n /** Constrain two lines to be perpendicular. */\n perpendicular(a: any, b: any): this;\n /** Constrain a line/circle or circle/circle tangency relationship. */\n tangent(a: any, b: any): this;\n /** Constrain two lines to have equal length. */\n equal(a: any, b: any): this;\n /** Constrain two points to coincide. */\n coincident(a: any, b: any): this;\n /** Constrain two circles to share a center. */\n concentric(a: any, b: any): this;\n /** Constrain a point to lie on the infinite extension of a line. */\n collinear(point: any, line: any): this;\n /** Constrain two points to be symmetric about an axis line. */\n symmetric(a: any, b: any, axis: any): this;\n /** Pin a point at a specific world location. */\n fix(point: any, x?: number, y?: number): this;\n /** Constrain a point to lie at the midpoint of a line. */\n midpoint(point: any, line: any): this;\n /** Constrain a point to lie on the perimeter of a circle. */\n pointOnCircle(point: any, circle: any): this;\n /** Constrain a point to lie on the bounded segment of a line. */\n pointOnLine(point: any, line: any): this;\n}\ninterface ConstrainedSketchBuilder {\n /** Constrain the Euclidean distance between two points. */\n distance(a: any, b: any, value: number): this;\n /** Constrain the length of a line segment. */\n length(line: any, value: number): this;\n /** Constrain the signed angle from line `a` to line `b`. */\n angle(a: any, b: any, value: number): this;\n /** Constrain the radius of a circle. */\n radius(circle: any, value: number): this;\n /** Constrain the diameter of a circle. */\n diameter(circle: any, value: number): this;\n /** Constrain the horizontal distance between two points. */\n hDistance(a: any, b: any, value: number): this;\n /** Constrain the vertical distance between two points. */\n vDistance(a: any, b: any, value: number): this;\n /** Constrain the signed perpendicular distance from a point to a line. */\n pointLineDistance(point: any, line: any, value: number): this;\n /** Constrain the perpendicular offset distance between two lines. */\n lineDistance(a: any, b: any, value: number): this;\n /** Constrain the absolute angle of a line measured from +X. */\n absoluteAngle(line: any, value: number): this;\n /** Constrain two circles to have equal radii. */\n equalRadius(a: any, b: any): this;\n /** Constrain the arc length of an arc. */\n arcLength(arc: any, value: number): this;\n /** Constrain a line to be tangent to an arc at its start or end point. */\n lineTangentArc(line: any, arc: any, atStart: boolean): this;\n /** Constrain two arcs to be tangent at their shared junction point. */\n arcTangentArc(arcA: any, arcB: any, aAtStart?: boolean, bAtStart?: boolean): this;\n /** Constrain a Bezier to be tangent to an arc at one endpoint. */\n bezierTangentArc(bezier: any, arc: any, atBezierStart: boolean, atArcStart: boolean): this;\n /** Create a Bezier blend between two arcs. */\n smoothBlend(arc1: any, arc2: any, options?: {\n weight?: number;\n arc1End?: "start" | "end";\n arc2End?: "start" | "end";\n }): BezierId;\n /** Constrain a shape\'s width. */\n shapeWidth(shape: any, value: number): this;\n /** Constrain a shape\'s height. */\n shapeHeight(shape: any, value: number): this;\n /** Constrain a shape\'s centroid X position. */\n shapeCentroidX(shape: any, value: number): this;\n /** Constrain a shape\'s centroid Y position. */\n shapeCentroidY(shape: any, value: number): this;\n /** Constrain a shape\'s area. */\n shapeArea(shape: any, value: number): this;\n /** Constrain two shapes to have the same centroid. */\n shapeEqualCentroid(a: any, b: any): this;\n /** Constrain the unsigned angle between two lines. */\n angleBetween(a: any, b: any, value: number): this;\n /** Constrain all given points to be in counter-clockwise order. */\n ccw(...points: any[]): this;\n /**\n * Constrain the horizontal (X-axis) offset between two lines.\n * Uses the start-point of each line to measure horizontal distance.\n * `value` is the signed distance: b.startPt.x − a.startPt.x = value.\n */\n offsetX(a: any, b: any, value: number): this;\n /**\n * Constrain the vertical (Y-axis) offset between two lines.\n * Uses the start-point of each line to measure vertical distance.\n * `value` is the signed distance: b.startPt.y − a.startPt.y = value.\n */\n offsetY(a: any, b: any, value: number): this;\n}\ndeclare const SHAPE_BACKEND_MARKER: unique symbol;\ninterface ShapeRuntimeBounds {\n readonly min: [\n number,\n number,\n number\n ];\n readonly max: [\n number,\n number,\n number\n ];\n}\ninterface ShapeRuntimeMesh {\n readonly numProp: number;\n readonly numTri: number;\n readonly triVerts: Uint32Array;\n readonly vertProperties: Float32Array;\n readonly numVert?: number;\n readonly mergeFromVert?: Uint32Array;\n readonly mergeToVert?: Uint32Array;\n readonly runIndex?: Uint32Array;\n readonly runOriginalID?: Uint32Array;\n readonly runTransform?: Float32Array;\n readonly faceID?: Uint32Array | Int32Array;\n /** Face ID to semantic face name map. Required when faceID values are used outside the backend. */\n readonly faceIdNames?: string[];\n readonly halfedgeTangent?: Float32Array;\n}\ntype ShapeRuntimeCrossSection = any;\ninterface EdgeNativeTopologyRef {\n backend: "truck";\n edge: number;\n}\ninterface ShapeBackend {\n readonly [SHAPE_BACKEND_MARKER]: true;\n clone(): ShapeBackend;\n translate(x: number, y: number, z: number): ShapeBackend;\n rotate(x: number, y: number, z: number): ShapeBackend;\n transform(m: Mat4): ShapeBackend;\n scale(v: number | [\n number,\n number,\n number\n ]): ShapeBackend;\n mirror(normal: [\n number,\n number,\n number\n ]): ShapeBackend;\n split(other: ShapeBackend): [\n ShapeBackend,\n ShapeBackend\n ];\n splitByPlane(normal: [\n number,\n number,\n number\n ], originOffset: number): [\n ShapeBackend,\n ShapeBackend\n ];\n trimByPlane(normal: [\n number,\n number,\n number\n ], originOffset: number): ShapeBackend;\n boundingBox(): ShapeRuntimeBounds;\n volume(): number;\n surfaceArea(): number;\n isEmpty(): boolean;\n /** Number of disconnected solid bodies in this shape. */\n numBodies(): number;\n numTri(): number;\n getMesh(): ShapeRuntimeMesh;\n slice(offset: number): ShapeRuntimeCrossSection;\n project(): ShapeRuntimeCrossSection;\n dispose?(): void;\n}\ndeclare const PROFILE_BACKEND_MARKER: unique symbol;\ninterface ProfileBounds {\n min: [\n number,\n number\n ];\n max: [\n number,\n number\n ];\n}\ninterface ProfileBackend {\n readonly [PROFILE_BACKEND_MARKER]: true;\n area(): number;\n bounds(): ProfileBounds;\n isEmpty(): boolean;\n numVert(): number;\n toPolygons(): number[][][];\n translate(x: number, y: number): ProfileBackend;\n rotate(degrees: number): ProfileBackend;\n scale(v: number | [\n number,\n number\n ]): ProfileBackend;\n mirror(ax: [\n number,\n number\n ]): ProfileBackend;\n offset(delta: number, join: "Square" | "Round" | "Miter"): ProfileBackend;\n simplify(epsilon: number): ProfileBackend;\n subtract(other: ProfileBackend): ProfileBackend;\n extrude(height: number, divisions: number, twist: number, scaleTop?: [\n number,\n number\n ]): ShapeBackend;\n revolve(segments: number, degrees: number): ShapeBackend;\n}\ntype Anchor = "center" | "top-left" | "top-right" | "bottom-left" | "bottom-right" | "top" | "bottom" | "left" | "right";\ntype SketchOperandInput = Sketch | readonly Sketch[];\n/**\n * Immutable 2D profile for extrusion, revolve, and other operations.\n *\n * **Details**\n *\n * `Sketch` wraps Manifold\'s `CrossSection` with a chainable 2D API. Every method\n * returns a new `Sketch` — the original is never mutated. Colors, edge labels, and\n * placement data are preserved through all transforms and boolean operations.\n *\n * Supported operations:\n * - **Transforms** — `translate`, `rotate`, `rotateAround`, `scale`, `mirror`\n * - **Booleans** — `add` (union), `subtract` (difference), `intersect`\n * - **Operations** — `offset`, `simplify`, `filletCorners`, `filletCorner`, `chamferCorners`, `chamferCorner`\n * - **Queries** — `area`, `bounds`, `isEmpty`, `numVert`\n * - **3D operations** — `extrude`, `revolve`, `onFace`\n * - **Regions** — `regions`, `region`\n * - **Placement** — `attachTo`\n *\n * Named anchor positions used by `attachTo()`:\n * `\'center\'` | `\'top-left\'` | `\'top-right\'` | `\'bottom-left\'` | `\'bottom-right\'`\n * | `\'top\'` | `\'bottom\'` | `\'left\'` | `\'right\'`\n */\ndeclare class Sketch {\n readonly cross: ProfileBackend;\n colorHex: string | undefined;\n constructor(cross: ProfileBackend, color?: string);\n /**\n * Set the display color of this sketch.\n *\n * **Details**\n *\n * Color is preserved through all transforms and boolean operations. Pass\n * `undefined` to clear the color.\n *\n * **Example**\n *\n * ```ts\n * circle2d(20).color(\'#ff0000\').extrude(5);\n * ```\n *\n * @param value - Hex color string (e.g. `\'#ff0000\'`) or `undefined` to clear\n * @returns A new sketch with the color set\n * @category Sketch Core\n */\n color(value: string | undefined): Sketch;\n /**\n * Create an explicit copy of this sketch for branching variants.\n *\n * **Details**\n *\n * Because all Sketch operations are immutable, `clone()` is rarely needed.\n * Use it when you want to assign the same sketch to multiple names and\n * continue modifying each independently without confusion.\n *\n * @returns A new Sketch with the same geometry and metadata\n * @see {@link duplicate} for an alias\n * @category Sketch Core\n */\n clone(): Sketch;\n /**\n * Alias for `clone()` — create an explicit copy handle for branching variants.\n *\n * @returns A new Sketch with the same geometry and metadata\n * @see {@link clone}\n * @category Sketch Core\n */\n duplicate(): Sketch;\n /**\n * Return the total filled area of the sketch.\n *\n * @returns Area in square model units\n * @category Sketch Core\n */\n area(): number;\n /**\n * Return the axis-aligned bounding box of the sketch.\n *\n * @returns `{ min: [x, y], max: [x, y] }`\n * @category Sketch Core\n */\n bounds(): ProfileBounds;\n /**\n * Return `true` if the sketch contains no filled area.\n *\n * @returns `true` when the sketch has no geometry\n * @category Sketch Core\n */\n isEmpty(): boolean;\n /**\n * Return the number of vertices in the polygon representation of the sketch contours.\n *\n * @returns Vertex count\n * @category Sketch Core\n */\n numVert(): number;\n /**\n * Return the sketch as a list of polygons matching its contour topology.\n *\n * Useful when you need raw polygon data for inspection or custom export.\n * @category Sketch Core\n */\n toPolygons(): number[][][];\n /**\n * Move the sketch by the given X and Y offset.\n *\n * @param x - Offset along X\n * @param y - Offset along Y (default 0)\n * @returns A translated sketch\n * @category Sketch Transforms\n */\n translate(x: number, y?: number): Sketch;\n /**\n * Rotate the sketch around its bounding-box center.\n *\n * @param degrees - Rotation angle in degrees (CCW positive)\n * @returns A rotated sketch\n * @see {@link rotateAround} to rotate around an arbitrary point\n * @category Sketch Transforms\n */\n rotate(degrees: number): Sketch;\n /**\n * Rotate the sketch around a specific pivot point.\n *\n * **Example**\n *\n * ```ts\n * rect(20, 20).rotateAround(45, [0, 0]);\n * ```\n *\n * @param degrees - Rotation angle in degrees (CCW positive)\n * @param pivot - 2D pivot point `[x, y]`\n * @returns A rotated sketch\n * @category Sketch Transforms\n */\n rotateAround(degrees: number, pivot: [\n number,\n number\n ]): Sketch;\n /**\n * Scale the sketch relative to its bounding-box center.\n *\n * **Details**\n *\n * Pass a single number for uniform scaling, or `[sx, sy]` for per-axis scaling.\n *\n * @param v - Scale factor — uniform `number` or per-axis `[sx, sy]`\n * @returns A scaled sketch\n * @see {@link scaleAround} to scale relative to an arbitrary point\n * @category Sketch Transforms\n */\n scale(v: number | [\n number,\n number\n ]): Sketch;\n /**\n * Mirror the sketch across a line through its bounding-box center.\n *\n * **Details**\n *\n * `normal` is the normal vector of the mirror line (not the line direction).\n * For example, `[1, 0]` mirrors across a vertical line (Y axis direction),\n * and `[0, 1]` mirrors across a horizontal line.\n *\n * @param normal - Normal of the mirror line as `[x, y]`\n * @returns A mirrored sketch\n * @see {@link mirrorThrough} to mirror across a line through an arbitrary point\n * @category Sketch Transforms\n */\n mirror(normal: [\n number,\n number\n ]): Sketch;\n /**\n * Scale the sketch relative to an arbitrary pivot point.\n *\n * @param pivot - 2D pivot point `[x, y]`\n * @param v - Scale factor — uniform `number` or per-axis `[sx, sy]`\n * @returns A scaled sketch\n * @category Sketch Transforms\n */\n scaleAround(pivot: [\n number,\n number\n ], v: number | [\n number,\n number\n ]): Sketch;\n /**\n * Mirror the sketch across a line defined by a point and a normal direction.\n *\n * @param point - A point on the mirror line\n * @param normal - Normal of the mirror line as `[x, y]`\n * @returns A mirrored sketch\n * @category Sketch Transforms\n */\n mirrorThrough(point: [\n number,\n number\n ], normal: [\n number,\n number\n ]): Sketch;\n /**\n * Add (union) one or more sketches to this sketch.\n *\n * **Details**\n *\n * Accepts individual sketches or arrays: `sketch.add(a, b)` or `sketch.add([a, b])`.\n * For combining many sketches at once, prefer the free function `union2d()` which\n * uses Manifold\'s batch operation and is faster than chaining.\n *\n * **Example**\n *\n * ```ts\n * circle2d(20).add(rect(10, 30)).extrude(5);\n * ```\n *\n * @returns A new sketch with all inputs unioned together\n * @see {@link union2d} for the batch free-function alternative\n * @category Sketch Booleans\n */\n add(...others: SketchOperandInput[]): Sketch;\n /**\n * Subtract one or more sketches from this sketch.\n *\n * **Details**\n *\n * Accepts individual sketches or arrays: `sketch.subtract(a, b)` or `sketch.subtract([a, b])`.\n * For subtracting many cutters at once, prefer the free function `difference2d()`.\n *\n * **Example**\n *\n * ```ts\n * rect(40, 40).subtract(circle2d(10)).extrude(5);\n * ```\n *\n * @returns A new sketch with all inputs subtracted\n * @see {@link difference2d} for the batch free-function alternative\n * @category Sketch Booleans\n */\n subtract(...others: SketchOperandInput[]): Sketch;\n /**\n * Intersect this sketch with one or more others (keep overlapping area only).\n *\n * **Details**\n *\n * Accepts individual sketches or arrays: `sketch.intersect(a, b)` or `sketch.intersect([a, b])`.\n * For intersecting many sketches, prefer the free function `intersection2d()`.\n *\n * @returns A new sketch containing only the area shared by all inputs\n * @see {@link intersection2d} for the batch free-function alternative\n * @category Sketch Booleans\n */\n intersect(...others: SketchOperandInput[]): Sketch;\n /**\n * Alias for `add()` — matches the free-function `union2d()` naming.\n *\n * @see {@link add}\n * @category Sketch Booleans\n */\n union(...others: SketchOperandInput[]): Sketch;\n /**\n * Alias for `subtract()` — matches the free-function `difference2d()` naming.\n *\n * @see {@link subtract}\n * @category Sketch Booleans\n */\n difference(...others: SketchOperandInput[]): Sketch;\n /**\n * Alias for `intersect()` — matches the free-function `intersection2d()` naming.\n *\n * @see {@link intersect}\n * @category Sketch Booleans\n */\n intersection(...others: SketchOperandInput[]): Sketch;\n /**\n * Inflate (positive delta) or deflate (negative delta) the sketch contour.\n *\n * **Details**\n *\n * For rounding corners, prefer `filletCorners(radius)` (all corners) or\n * `filletCorner([x, y], radius)` (one corner) — they round only true corners\n * and keep concave geometry exact.\n *\n * - `\'Round\'` — smooth arc at each corner (default)\n * - `\'Square\'` — flat mitered extension\n * - `\'Miter\'` — sharp pointed extension\n *\n * **Example**\n *\n * ```ts\n * rect(40, 20).offset(3); // expand by 3\n * ```\n *\n * @param delta - Offset distance (positive = expand, negative = shrink)\n * @param join - Corner join style: `\'Round\'` | `\'Square\'` | `\'Miter\'` (default `\'Round\'`)\n * @returns A new offset sketch\n * @category Sketch Operations\n */\n offset(delta: number, join?: "Square" | "Round" | "Miter"): Sketch;\n /**\n * Round every significant corner of this sketch with the same fillet radius.\n *\n * **Details**\n *\n * Works on any sketch — primitives, boolean results, attached or composed\n * profiles. A vertex counts as a corner when its turn angle is at least 30°,\n * so vertices that belong to tessellated circles or earlier fillets are left\n * untouched. Both convex and concave corners are rounded. Holes are\n * preserved, and their corners are rounded too.\n *\n * Throws if the sketch has no significant corners, or if the radius does not\n * fit a corner (the error names the corner and the maximum radius).\n *\n * **Example**\n *\n * ```ts\n * rect(60, 30).filletCorners(5).extrude(4); // rounded plate\n * polygon(bracketPts).filletCorners(3); // every corner of an outline\n * ```\n *\n * @param radius - Fillet radius applied to every significant corner\n * @returns A new sketch with all significant corners rounded\n * @see {@link filletCorner} to round a single corner picked by a seed point\n * @see {@link chamferCorners} for straight bevels instead of arcs\n * @category Sketch Operations\n */\n filletCorners(radius: number): Sketch;\n /**\n * Round the single corner of this sketch nearest to a seed point.\n *\n * **Details**\n *\n * Selects the significant corner (turn angle ≥ 30°) closest to `at` — the\n * seed does not need to be exact, just nearer to the intended corner than to\n * any other (like `region([x, y])` seed selection). Throws if two corners\n * are equidistant from the seed, naming both so you can move the seed.\n *\n * Chain calls to round several corners with different radii.\n *\n * **Example**\n *\n * ```ts\n * polygon(roofPts)\n * .filletCorner([45, 86], 14) // peak\n * .filletCorner([24, 74], 8); // left shoulder\n * ```\n *\n * @param at - Seed point `[x, y]` near the corner to round\n * @param radius - Fillet radius\n * @returns A new sketch with that corner rounded\n * @see {@link filletCorners} to round all significant corners at once\n * @see {@link chamferCorner} for a straight bevel instead of an arc\n * @category Sketch Operations\n */\n filletCorner(at: [\n number,\n number\n ], radius: number): Sketch;\n /**\n * Bevel every significant corner of this sketch with a straight chamfer.\n *\n * **Details**\n *\n * Replaces each significant corner (turn angle ≥ 30°) with a straight cut\n * set back `size` along both adjacent edges. Tessellated arcs are left\n * untouched; holes are preserved. Throws if the sketch has no significant\n * corners or the size does not fit a corner.\n *\n * **Example**\n *\n * ```ts\n * rect(60, 30).chamferCorners(4).extrude(4); // beveled plate outline\n * ```\n *\n * @param size - Setback distance along each adjacent edge\n * @returns A new sketch with all significant corners beveled\n * @see {@link chamferCorner} to bevel a single corner picked by a seed point\n * @see {@link filletCorners} for rounded corners instead of bevels\n * @category Sketch Operations\n */\n chamferCorners(size: number): Sketch;\n /**\n * Bevel the single corner of this sketch nearest to a seed point.\n *\n * **Details**\n *\n * Selects the significant corner (turn angle ≥ 30°) closest to `at` and\n * replaces it with a straight cut set back `size` along both adjacent edges.\n * Throws if two corners are equidistant from the seed. Chain calls to bevel\n * several corners with different sizes.\n *\n * **Example**\n *\n * ```ts\n * rect(60, 30).chamferCorner([30, 15], 6); // bevel only the top-right corner\n * ```\n *\n * @param at - Seed point `[x, y]` near the corner to bevel\n * @param size - Setback distance along each adjacent edge\n * @returns A new sketch with that corner beveled\n * @see {@link chamferCorners} to bevel all significant corners at once\n * @see {@link filletCorner} for a rounded corner instead of a bevel\n * @category Sketch Operations\n */\n chamferCorner(at: [\n number,\n number\n ], size: number): Sketch;\n /**\n * Decompose this sketch into its distinct filled regions, sorted largest-first by area.\n *\n * **Details**\n *\n * A single sketch can contain several disconnected filled areas (e.g., two separate\n * rectangles, or a ring shape with a hole). This method enumerates all top-level\n * connected regions as independent `Sketch` objects, each with its own outer boundary\n * and associated holes.\n *\n * **Example**\n *\n * ```ts\n * const pair = union2d(rect(40, 40), rect(40, 40).translate(60, 0));\n * const [left, right] = pair.regions(); // largest first\n * left.extrude(5);\n * ```\n *\n * @returns Array of region sketches, sorted by area descending\n * @see {@link region} to pick one region by seed point\n * @category Sketch Regions\n */\n regions(): Sketch[];\n /**\n * Select the single filled region that contains the given 2D seed point.\n *\n * **Details**\n *\n * The seed must lie strictly inside the filled area — not on a boundary edge and\n * not inside a hole. Throws a descriptive error if the seed is outside all regions.\n * If unsure where regions are, use `.regions()` first — each result has `.bounds()`.\n *\n * **Example**\n *\n * ```ts\n * const donut = circle2d(50).subtract(circle2d(30));\n * donut.region([40, 0]).extrude(10); // seed at radius 40, inside the ring\n * ```\n *\n * @param seed - A 2D point `[x, y]` strictly inside the desired region\n * @returns The sketch region containing the seed point\n * @see {@link regions} to enumerate all regions\n * @category Sketch Regions\n */\n region(seed: [\n number,\n number\n ]): Sketch;\n /** Extrude this 2D sketch along Z to create a 3D solid. Supports twist and scale tapering. */\n extrude(height: number, opts?: {\n twist?: number;\n divisions?: number;\n scaleTop?: number | [\n number,\n number\n ];\n }): Shape;\n /** Revolve this 2D sketch around the world Z axis. Sketch X is radius; sketch Y becomes world Z height. Keep the profile at X > 0 unless it intentionally touches the axis. */\n revolve(degrees?: number, segments?: number): Shape;\n /**\n * Position this sketch relative to another using named anchor points.\n *\n * **Details**\n *\n * Computes the translation needed to align `selfAnchor` on this sketch with\n * `targetAnchor` on the target sketch, then applies an optional pixel-exact offset.\n *\n * Anchor positions: `\'center\'` | `\'top-left\'` | `\'top-right\'` | `\'bottom-left\'`\n * | `\'bottom-right\'` | `\'top\'` | `\'bottom\'` | `\'left\'` | `\'right\'`\n *\n * **Example**\n *\n * ```ts\n * const arm = rect(4, 70).attachTo(plate, \'bottom-left\', \'top-left\');\n * const shifted = rect(4, 70).attachTo(plate, \'bottom-left\', \'top-left\', [5, 0]);\n * ```\n *\n * @param target - The sketch to attach to\n * @param targetAnchor - Named anchor point on the target sketch\n * @param selfAnchor - Named anchor point on this sketch to align (default `\'center\'`)\n * @param offset - Additional `[dx, dy]` offset applied after alignment\n * @returns A translated sketch\n * @category Sketch Transforms\n */\n attachTo(target: Sketch, targetAnchor: Anchor, selfAnchor?: Anchor, offset?: [\n number,\n number\n ]): Sketch;\n /**\n * Place this sketch on a face or planar target in 3D space.\n *\n * Use this when a 2D profile should be oriented onto a 3D face before\n * extrusion or other downstream operations.\n *\n * @param parentOrFace - Parent shape, face reference, or shape-like target.\n * @param faceOrOpts - Face selector or placement options.\n * @param opts - Additional placement options when a face selector is provided separately.\n * @returns A sketch positioned on the target face.\n * @category Sketch Transforms\n */\n onFace(parentOrFace: Shape | {\n toShape(): Shape;\n } | {\n _bbox(): {\n min: number[];\n max: number[];\n };\n } | FaceRef, faceOrOpts?: "front" | "back" | "left" | "right" | "top" | "bottom" | string | FaceRef | {\n u?: number;\n v?: number;\n protrude?: number;\n selfAnchor?: Anchor;\n }, opts?: {\n u?: number;\n v?: number;\n protrude?: number;\n selfAnchor?: Anchor;\n }): Sketch;\n /** Label the single boundary edge (for circles, single-loop profiles). Returns a new sketch. */\n labelEdge(name: string): Sketch;\n /**\n * Label edges in winding order, or by named map for rect.\n *\n * Positional: `labelEdges(\'bottom\', \'right\', \'top\', \'left\')` — one per edge, `null` to skip.\n * Named (rect only): `labelEdges({ bottom: \'floor\', top: \'ceiling\' })`.\n * Returns a new sketch.\n */\n labelEdges(...args: (string | null)[] | [\n Record<string, string>\n ]): Sketch;\n /** List current edge label names. */\n edgeLabels(): string[];\n /** Prefix all edge labels. Returns a new sketch with prefixed labels. */\n prefixLabels(prefix: string): Sketch;\n /** Rename a single edge label. Returns a new sketch. */\n renameLabel(from: string, to: string): Sketch;\n /** Remove specific labels. Returns a new sketch. */\n dropLabels(...names: string[]): Sketch;\n /** Remove all labels. Returns a new sketch. */\n dropAllLabels(): Sketch;\n}\ndeclare class ConstraintSketch extends Sketch {\n readonly constraintMeta: SketchConstraintMeta;\n readonly definition: ConstraintDefinition;\n constructor(cross: Sketch["cross"], constraintMeta: SketchConstraintMeta, definition: ConstraintDefinition);\n /**\n * Enumerate all bounded regions formed by the line arrangement of this sketch.\n * Construction lines are excluded. Regions are returned largest-first by area.\n */\n detectArrangement(): Sketch[];\n /**\n * Select the single arrangement region that contains the given seed point.\n * Throws if no region contains the seed.\n */\n detectArrangementRegion(_seed: [\n number,\n number\n ]): Sketch;\n /**\n * Return the solved constrained path as a sampled 2D polyline.\n *\n * Use this when a construction rail was authored with `constrainedSketch()`\n * and should feed another operation such as `Loft.pathOnXz(...)`.\n * The sketch must contain exactly one profile path.\n *\n * @param samples - Samples per curved segment. Default 32.\n * @returns The solved path as an open polyline.\n */\n toPolyline(samples?: number): [\n number,\n number\n ][];\n /**\n * Re-solve the sketch after changing the value of one existing constraint.\n *\n * Use this for interactive dimension edits without rebuilding the whole sketch graph.\n * It attempts a warm-started solve first, then falls back to a full solve if needed.\n *\n * @param constraintId - ID of the existing constraint to update.\n * @param value - New numeric value for the constraint.\n * @returns A new solved `ConstraintSketch`.\n */\n withUpdatedConstraint(constraintId: string, value: number): ConstraintSketch;\n /**\n * Return a human-readable diagnostic string of the solved state.\n */\n inspect(): string;\n}\ninterface ConstrainedSketchBuilder {\n /**\n * Import a `Point2D` object into the sketch.\n * @softDeprecated sk.point(pt.x, pt.y, fixed) — constraint methods auto-import Point2D values directly\n * @deprecated use sk.point(pt.x, pt.y, fixed) — constraint methods auto-import Point2D values directly\n */\n importPoint(pt: {\n x: number;\n y: number;\n }, fixed?: boolean): PointId;\n /**\n * Import a `Line2D` object into the sketch.\n * @softDeprecated sk.line(sk.point(l.start.x, l.start.y, fixed), sk.point(l.end.x, l.end.y, fixed)) — constraint methods auto-import Line2D values directly\n * @deprecated use sk.line(sk.point(l.start.x, l.start.y, fixed), sk.point(l.end.x, l.end.y, fixed)) — constraint methods auto-import Line2D values directly\n */\n importLine(l: {\n start: {\n x: number;\n y: number;\n };\n end: {\n x: number;\n y: number;\n };\n }, fixed?: boolean): LineId;\n /**\n * Import a `Rectangle2D` as four points and four lines.\n * @softDeprecated sk.rect({ x, y, width, height }) — returns richer named sides (bottom/right/top/left) and vertices\n * @deprecated use sk.rect({ x, y, width, height }) — returns richer named sides (bottom/right/top/left) and vertices\n */\n importRectangle(r: {\n vertices: [\n {\n x: number;\n y: number;\n },\n {\n x: number;\n y: number;\n },\n {\n x: number;\n y: number;\n },\n {\n x: number;\n y: number;\n }\n ];\n }, fixed?: boolean): {\n bottom: LineId;\n right: LineId;\n top: LineId;\n left: LineId;\n points: [\n PointId,\n PointId,\n PointId,\n PointId\n ];\n };\n /** Add a fixed reference point at `(x, y)`. */\n referencePoint(x: number, y: number): PointId;\n /** Add a fixed reference line from `(x1, y1)` to `(x2, y2)`. */\n referenceLine(x1: number, y1: number, x2: number, y2: number): LineId;\n /** Import a single named entity from a solved sketch as fixed reference geometry. */\n referenceFrom(source: ConstraintSketch, entityId: string): PointId | LineId | null;\n /** Import all non-construction entities from a solved sketch as fixed references. */\n referenceAllFrom(source: ConstraintSketch): {\n points: Map<string, PointId>;\n lines: Map<string, LineId>;\n };\n}\ninterface ConstrainedSketchOptions {\n /** When true, adding a constraint that cannot be satisfied throws instead of silently discarding it. */\n strict?: boolean;\n}\ninterface ConstrainedSketchBuilder extends ConstraintBuilderMethods {\n}\ndeclare class ConstrainedSketchBuilder {\n private points;\n private lines;\n private circles;\n private arcs;\n private beziers;\n private shapes;\n private _groups;\n /** Point IDs owned by groups — excluded from the serialized points array. */\n private groupOwnedPointIds;\n /** Line IDs owned by groups — excluded from the serialized lines array. */\n private groupOwnedLineIds;\n private constraints;\n private loops;\n private rejectedConstraints;\n /** Maps rejected constraint ID → human-readable reason string. */\n private rejectionReasons;\n protected cursor: PointId | null;\n protected loopStart: PointId | null;\n /** Last arc created by the path API (arcTo), used by blendTo. */\n protected lastPathArc: ArcId | null;\n private nextId;\n private strict;\n /** Cumulative time spent in seedIncrementalGeometry calls (ms). */\n private seedTimeMs;\n /** Max cumulative time for all seed calls (ms). After this, seeding is skipped. */\n private static readonly SEED_BUDGET_MS;\n /** WASM solver session handle — persists state across seed steps. */\n private _sessionHandle;\n private _sessionApi;\n private _sessionFailed;\n constructor(options?: ConstrainedSketchOptions);\n /** Try to create a WASM solver session. Returns true if session is active. */\n private ensureSession;\n private destroySession;\n /**\n * Add a free point to the sketch at `(x, y)`.\n *\n * If `x` or `y` are omitted, the point is placed at the bounding-box center\n * of existing geometry so it starts near other entities rather than at the origin.\n * Throws if either coordinate is `NaN` or `Infinity`.\n *\n * @param x - X coordinate. Defaults to existing geometry center.\n * @param y - Y coordinate. Defaults to existing geometry center.\n * @param fixed - When true the point is pinned at (x, y) and acts like a `fix` constraint.\n * @returns A `PointId` that can be passed to `line()`, `circle()`, and constraints.\n * @category Constrained Sketches\n */\n point(x?: number, y?: number, fixed?: boolean): PointId;\n /**\n * Return the `PointId` of the point created at the given insertion index (0-based).\n *\n * Useful for referencing points by order when the original `PointId` was not stored.\n *\n * @param index - 0-based creation index.\n * @returns The `PointId` at that index.\n * @category Constrained Sketches\n */\n /** Return the `PointId` of the point created at the given insertion index. */\n pointAt(index: number): PointId;\n /**\n * Connect two existing points with a line segment.\n *\n * Pass `construction = true` for a helper line that participates in constraints\n * but is excluded from the solved sketch output (not part of any profile loop).\n *\n * **Example** (construction axis for symmetry)\n *\n * ```ts\n * const axis = sk.line(sk.point(0, -50), sk.point(0, 50), true);\n * sk.symmetric(p1, p2, axis);\n * ```\n *\n * @param a - Start `PointId`.\n * @param b - End `PointId`.\n * @param construction - When true this line is excluded from the solved output. Default: false.\n * @param name - Optional label for debugging.\n * @returns A `LineId` for use in constraints and path loops.\n * @category Constrained Sketches\n */\n line(a: PointId, b: PointId, construction?: boolean, name?: string): LineId;\n /**\n * Return the `LineId` of the line created at the given insertion index (0-based).\n *\n * Useful in path-drawing workflows where lines are created implicitly via\n * `lineTo()` and you need to reference one by position.\n *\n * ```ts\n * sk.moveTo(0, 0).lineTo(50, 0).lineTo(50, 30).close();\n * sk.length(sk.lineAt(0), 50);\n * ```\n *\n * @param index - 0-based creation index.\n * @returns The `LineId` at that index.\n * @category Constrained Sketches\n */\n /** Return the `LineId` of the line created at the given insertion index. */\n lineAt(index: number): LineId;\n /**\n * Add a circle to the sketch with the given center point and initial radius.\n *\n * The radius is a starting value — if you add a `radius()` or `diameter()` constraint,\n * the solver will adjust it. Non-construction circles automatically register a loop.\n *\n * @param center - Center `PointId`.\n * @param radius - Initial radius (must be finite and positive).\n * @param construction - When true the circle is excluded from the solved output. Default: false.\n * @param segments - Tessellation resolution for display. Default: 48.\n * @param name - Optional label for debugging.\n * @returns A `CircleId` for use in constraints.\n * @category Constrained Sketches\n */\n circle(center: PointId, radius: number, construction?: boolean, segments?: number, name?: string): CircleId;\n /**\n * Return the `CircleId` of the circle created at the given insertion index (0-based).\n *\n * @param index - 0-based creation index.\n * @returns The `CircleId` at that index.\n * @category Constrained Sketches\n */\n /** Return the `CircleId` of the circle created at the given insertion index. */\n circleAt(index: number): CircleId;\n /**\n * Register a named shape (closed polygon) from an ordered list of line IDs.\n *\n * The `ShapeId` can be passed to `shapeWidth()`, `shapeHeight()`, `shapeArea()`,\n * `shapeCentroidX()`, `shapeCentroidY()`, and `shapeEqualCentroid()` constraints.\n * Shape registration is done automatically by concept factories like `rect()` and `addPolygon()`.\n *\n * @param lines - Ordered line IDs forming the closed polygon boundary.\n * @returns A `ShapeId` for use in shape constraints.\n * @category Constrained Sketches\n */\n shape(lines: LineId[]): ShapeId;\n /**\n * Create a rigid-body group with a local coordinate frame.\n *\n * Points and lines added to the group move together as a unit — the solver\n * sees 3 DOF (x, y, θ) instead of 2N per point. After configuring the group,\n * call `.done()` to register it and receive a `SketchGroupHandle`.\n *\n * Group points are addressable by their `PointId` in all sketch constraints\n * (e.g. `sk.coincident`, `sk.distance`) just like any other points.\n *\n * **Example**\n *\n * ```ts\n * const g = sk.group({ x: 50, y: 30 });\n * const p0 = g.point(0, 0); // local origin → world (50, 30)\n * const p1 = g.point(100, 0); // local (100,0) → world (150, 30)\n * const l = g.line(p0, p1);\n * g.fixRotation();\n * const handle = g.done();\n * // p0, p1 work in constraints like any other PointId:\n * sk.coincident(p0, someExternalPoint);\n * ```\n *\n * @param opts - Initial frame position (x, y) in degrees theta, and optional id.\n * @returns A `SketchGroupBuilder` — call `.done()` to finalize.\n * @category Constrained Sketches\n */\n group(opts?: {\n x?: number;\n y?: number;\n theta?: number;\n id?: string;\n }): SketchGroupBuilder;\n /**\n * Register a group directly (called by SketchGroupBuilder).\n * @internal\n */\n _registerGroup(group: SketchGroup): void;\n /** Add a raw constraint object to the builder. */\n constrain(constraint: Omit<SketchConstraint, "id">): this;\n /**\n * Keep the live builder geometry near a solved state as constraints are added.\n * This restores the progressive seeding path used by large sketches in the browser\n * without rejecting constraints or changing the final solve API.\n */\n private seedIncrementalGeometry;\n /** Serialize a constraint for the session API (matches Rust serde format). */\n private serializeConstraintForSession;\n protected resolvePointId(p: any): PointId;\n protected resolveLineId(l: any): LineId;\n protected resolveCircleId(c: any): CircleId;\n protected resolveArcId(a: any): ArcId;\n protected resolveBezierId(b: any): BezierId;\n protected resolveShapeId(s: any): ShapeId;\n protected requireFinite(value: number, constraintName: string): void;\n /** Sync point positions from session state back to builder entities. */\n private syncPointsFromSession;\n /**\n * Run the constraint solver and return a solved sketch.\n *\n * The returned `ConstraintSketch` extends `Sketch` and can be used directly\n * in all 3D operations (`extrude`, `revolve`, etc.). It also exposes\n * `constraintMeta` with the solver status:\n *\n * ```ts\n * const result = sk.solve();\n * result.constraintMeta.status; // \'fully\' | \'under\' | \'over\' | \'over-redundant\'\n * result.constraintMeta.dof; // 0 = fully constrained\n * result.constraintMeta.maxError; // residual — should be < 1e-6\n * result.inspect(); // human-readable summary\n * result.withUpdatedConstraint(\'cst-5\', 120); // update a dimension without rebuilding\n * ```\n *\n * **Troubleshooting**\n *\n * - **Under-constrained (dof > 0)** — add `fix()`, `length()`, or other dimensional constraints.\n * - **Over-constrained** — conflicting constraints are auto-rejected. Check `result.constraintMeta.constraints` and `result.inspect()`.\n * - **maxError > 1e-6** — solver did not converge; check for contradictory constraints.\n *\n * @param options - Advanced solver options (iterations, restarts, tolerance, etc.).\n * @returns A `ConstraintSketch` (also a valid `Sketch`) with solved geometry.\n * @category Constrained Sketches\n */\n solve(options?: SolveOptions): ConstraintSketch | Sketch;\n /**\n * Run the solver without building a full `ConstraintSketch`.\n *\n * Lighter than `solve()` — skips profile and DOF analysis. Useful for\n * lightweight constraint validation or progress monitoring mid-construction.\n *\n * @param options - Advanced solver options (iterations, restarts, tolerance, etc.).\n * @returns Object with `maxError`, `rejectedCount`, and the solved `ConstraintDefinition`.\n * @category Constrained Sketches\n */\n solveConstraintsOnly(options?: SolveOptions): {\n maxError: number;\n rejectedCount: number;\n definition: ConstraintDefinition;\n };\n private buildDefinition;\n /** Sync solved positions from a definition back to the builder\'s live entities. */\n private syncFromDefinition;\n private getPoint;\n /** Compute arc center from start/end points, radius, and clockwise flag; create the arc entity. */\n protected addArc(startId: PointId, endId: PointId, radius: number, clockwise: boolean): ArcId;\n /**\n * Bounding box of all existing non-construction points.\n * Returns null when no points exist yet.\n * Used by concept factories to auto-offset initial geometry.\n */\n _pointBounds(): {\n minX: number;\n maxX: number;\n minY: number;\n maxY: number;\n } | null;\n}\n/**\n * Create a parametric 2D sketch driven by geometric constraints and a nonlinear solver.\n *\n * **Workflow**\n *\n * 1. Create a builder with `constrainedSketch()`.\n * 2. Add geometry — points, lines, circles, arcs — using the builder methods.\n * 3. Add constraints (`horizontal`, `length`, `fix`, etc.) to drive the geometry.\n * 4. Call `.solve()` to run the solver and get a `ConstraintSketch` (which extends `Sketch`).\n *\n * **Example**\n *\n * ```ts\n * const sk = constrainedSketch();\n * const p1 = sk.point(0, 0);\n * const p2 = sk.point(50, 0);\n * const l1 = sk.line(p1, p2);\n * sk.fix(p1, 0, 0);\n * sk.horizontal(l1);\n * sk.length(l1, 50);\n * return sk.solve().extrude(10);\n * ```\n *\n * **Solver status**\n *\n * ```ts\n * const result = sk.solve();\n * result.constraintMeta.status; // \'fully\' | \'under\' | \'over\' | \'over-redundant\'\n * result.constraintMeta.dof; // 0 = fully constrained\n * result.constraintMeta.maxError; // residual — should be < 1e-6\n * result.inspect(); // human-readable summary\n * result.withUpdatedConstraint(\'cst-5\', 120); // update a dimension without rebuilding\n * ```\n *\n * @param options - Optional builder configuration.\n * @returns A `ConstrainedSketchBuilder` ready to accept geometry and constraints.\n * @category Constrained Sketches\n */\ndeclare function constrainedSketch(options?: ConstrainedSketchOptions): ConstrainedSketchBuilder;\ninterface SketchGroupHandle {\n readonly id: GroupId;\n /** Get a group vertex PointId by its index (order of `.point()` calls). */\n point(index: number): PointId;\n /** Get a group line LineId by its index (order of `.line()` calls). */\n line(index: number): LineId;\n /** All group vertex PointIds in creation order. */\n readonly vertices: PointId[];\n /** All group line LineIds in creation order. */\n readonly sides: LineId[];\n}\ndeclare class SketchGroupBuilder {\n private sk;\n private groupId;\n private gx;\n private gy;\n private gtheta;\n private localPoints;\n private localLines;\n private isFixed;\n private isFixedRotation;\n private registered;\n constructor(sk: ConstrainedSketchBuilder, opts: {\n x?: number;\n y?: number;\n theta?: number;\n id?: string;\n });\n /** Add a point in local coordinates. Returns its globally-addressable PointId. */\n /**\n * Add a point in local coordinates. Returns its globally-addressable PointId.\n */\n point(lx: number, ly: number): PointId;\n /** Connect two group points with a line. Both must be PointIds from this group. */\n /**\n * Connect two group points with a line. Both must be PointIds from this group.\n */\n line(a: PointId, b: PointId, name?: string): LineId;\n /** Freeze rotation (θ). Group can still translate - 2 DOF remain. */\n /** Freeze rotation (theta). Group can still translate - 2 DOF remain. */\n fixRotation(): this;\n /** Freeze all 3 DOF - group is completely fixed. */\n /** Freeze all 3 DOF - group is completely fixed. */\n fix(): this;\n /**\n * Finalize and register the group with the builder.\n * Returns a handle for referencing group points/lines in constraints.\n */\n /** Finalize and register the group with the builder. */\n done(): SketchGroupHandle;\n}\ndeclare class RouteBuilder {\n private readonly sk;\n private readonly _startPt;\n private cursorPt;\n private cursorX;\n private cursorY;\n /**\n * Current travel direction in radians from +X.\n * null = direction not yet established (first segment will set it).\n */\n private direction;\n private segments;\n private lastLineId;\n private lastArcId;\n private finished;\n constructor(sk: ConstrainedSketchBuilder, startPt: PointId, x: number, y: number);\n /** Vertical line going +Y. Length is optional (solver determines it from constraints). */\n up(length?: number): LineId;\n /** Vertical line going -Y. Length is optional. */\n down(length?: number): LineId;\n /** Horizontal line going +X. Length is optional. */\n right(length?: number): LineId;\n /** Horizontal line going -X. Length is optional. */\n left(length?: number): LineId;\n /** Line at an arbitrary angle (degrees from +X). Length is optional. */\n lineAt(angleDeg: number, length?: number): LineId;\n /**\n * Line with solver-determined direction. Length is optional.\n * Direction comes from tangency to previous arc or from constraints.\n */\n line(length?: number): LineId;\n /**\n * Line toward a specific point.\n * Length defaults to the distance to that point.\n */\n toward(x: number, y: number): LineId;\n /**\n * Tangent arc turning left relative to travel direction.\n * @param radius - Arc radius. If omitted, solver determines it.\n * @param sweepDegOrOpts - Sweep angle in degrees (constrains the arc),\n * or `{ minSweep: degrees }` to seed the geometry without constraining.\n * `minSweep` guides the solver to the correct branch for arcs that\n * sweep more than the default 90° seed.\n */\n arcLeft(radius?: number, sweepDegOrOpts?: number | {\n minSweep: number;\n }): ArcId;\n /**\n * Tangent arc turning right relative to travel direction.\n * @param radius - Arc radius. If omitted, solver determines it.\n * @param sweepDegOrOpts - Sweep angle in degrees (constrains the arc),\n * or `{ minSweep: degrees }` to seed without constraining.\n */\n arcRight(radius?: number, sweepDegOrOpts?: number | {\n minSweep: number;\n }): ArcId;\n /** Close the route with a straight line back to the start point. */\n close(): void;\n /**\n * Close the route back to its start point and register as a profile loop.\n *\n * No extra line segment is added. A coincident constraint connects the\n * last point to the start, and tangency is added for G1 smoothness\n * when arcs are at the junction. The session\'s incremental solver\n * processes these constraints, keeping seed positions accurate for\n * the final solve.\n */\n done(): void;\n /** PointId of the route\'s start point. */\n get start(): PointId;\n /** PointId of the current cursor (route\'s end). */\n get end(): PointId;\n /** Get the start point of a segment. */\n startOf(segId: LineId | ArcId): PointId;\n /** Get the end point of a segment. */\n endOf(segId: LineId | ArcId): PointId;\n /**\n * @param angle - Direction in radians from +X\n * @param length - Optional length\n * @param constrainDirection - Whether to add horizontal/vertical constraint (default true for axis-aligned)\n */\n private _addLine;\n private _addArc;\n private _ensureNotFinished;\n private _registerLoop;\n private _normalizeAngle;\n private _isVertical;\n private _isHorizontal;\n}\ninterface RouteLine {\n /** \'x\' for vertical line at x=offset, \'y\' for horizontal line at y=offset */\n axis: "x" | "y";\n offset: number;\n}\ninterface RouteCircle {\n center: [\n number,\n number\n ];\n radius: number;\n}\ninterface RouteTangent {\n tangent: RouteCircle | CircleId;\n}\ninterface RouteFillet {\n fillet: number;\n /** When \'tangent\', adds a tangent line from the smaller circle before the fillet arc. */\n approach?: "tangent";\n}\ninterface RouteTangentArc {\n tangentArc: number;\n}\ninterface RoutePoint {\n point: [\n number,\n number\n ];\n}\ninterface RouteUntil {\n line: RouteLine | LineId;\n until: number;\n}\ntype RouteStep = RouteLine | RouteCircle | CircleId | RouteTangent | RouteFillet | RouteTangentArc | RoutePoint | RouteUntil;\ninterface ConstrainedSketchBuilder {\n /**\n * Route a profile through a sequence of geometric elements.\n * The solver computes all tangent points and intersections automatically.\n *\n * Steps can include:\n * - `{ point: [x, y] }` — route through a point\n * - `{ axis: \'x\'|\'y\', offset: n }` — follow a construction line\n * - `{ line: {...}, until: n }` — follow a line clipped to a coordinate\n * - `{ tangent: { center, radius } }` — tangent arc onto a construction circle\n * - `{ fillet: radius }` — fillet between adjacent elements\n * - `{ tangentArc: radius }` — free tangent arc (solver finds center)\n *\n * Returns `this` for chaining. Call `.solve()` after to get the Sketch.\n */\n route(steps: RouteStep[]): this;\n /**\n * Start a directional route from coordinates.\n *\n * Returns a `RouteBuilder` - describe the path with up/down/left/right/arcLeft/arcRight.\n * Each method returns the entity ID (`LineId` or `ArcId`) for use in `sk.*` constraints.\n *\n * @example\n * ```ts\n * const r = sk.route(0, 0);\n * const stem = r.up(18);\n * r.arcLeft(8.9);\n * const neck = r.down();\n * r.done();\n * sk.offsetX(stem, neck, 10.8);\n * ```\n */\n route(x: number, y: number): RouteBuilder;\n /**\n * Start a directional route from an existing sketch point.\n */\n route(startPoint: PointId): RouteBuilder;\n}\n/**\n * An immutable 2D point with measurement and construction helpers.\n *\n * Used as construction geometry in sketches, constraints, and analytic\n * measurements. All methods return new instances — `Point2D` is immutable.\n *\n * @category 2D Entities\n */\ndeclare class Point2D {\n readonly x: number;\n readonly y: number;\n constructor(x: number, y: number);\n /**\n * Measure straight-line distance to another point.\n *\n * @param other - The other point.\n * @returns Euclidean distance.\n */\n distanceTo(other: Point2D): number;\n /**\n * Compute the midpoint between this point and another point.\n *\n * @param other - The other point.\n * @returns A new midpoint.\n */\n midpointTo(other: Point2D): Point2D;\n /**\n * Return a point shifted by the given delta.\n *\n * @param dx - Horizontal offset.\n * @param dy - Vertical offset.\n * @returns A translated point.\n */\n translate(dx: number, dy: number): Point2D;\n /**\n * Convert this point to a plain `[x, y]` tuple.\n *\n * @returns A coordinate tuple.\n */\n toTuple(): [\n number,\n number\n ];\n}\n/**\n * Create an analytic 2D point for measurement and construction geometry.\n *\n * @softDeprecated new Point2D(x, y) — or cs.point(x, y) inside constrainedSketch()\n * @deprecated use new Point2D(x, y) — or cs.point(x, y) inside constrainedSketch()\n * @param x - X coordinate\n * @param y - Y coordinate\n * @returns A new `Point2D`\n * @category 2D Entities\n */\ndeclare function point(x: number, y: number): Point2D;\n/**\n * An immutable 2D line segment with length, angle, intersection, and parallel helpers.\n *\n * Provides both segment-only (`intersectSegment`) and infinite-line\n * (`intersect`) intersection queries. All methods return new instances.\n *\n * @category 2D Entities\n */\ndeclare class Line2D {\n readonly start: Point2D;\n readonly end: Point2D;\n constructor(start: Point2D, end: Point2D);\n /** Length of the line segment. */\n get length(): number;\n /** Midpoint of the line segment. */\n get midpoint(): Point2D;\n /** Direction angle in degrees, measured CCW from +X. */\n get angle(): number;\n /** Unit direction vector from start to end. */\n get direction(): [\n number,\n number\n ];\n /**\n * Create a parallel line offset by the given distance.\n *\n * Positive distance shifts to the left of the line direction.\n */\n parallel(distance: number): Line2D;\n /**\n * Intersect this line with another infinite line.\n *\n * @param other - The other line.\n * @returns The intersection point, or `null` if parallel.\n */\n intersect(other: Line2D): Point2D | null;\n /**\n * Intersect this line with another as bounded segments.\n *\n * @param other - The other line.\n * @returns The intersection point, or `null` if the segments do not cross.\n */\n intersectSegment(other: Line2D): Point2D | null;\n /**\n * Create a line from raw coordinates.\n *\n * @param x1 - Start X.\n * @param y1 - Start Y.\n * @param x2 - End X.\n * @param y2 - End Y.\n * @returns A new line segment.\n */\n static fromCoordinates(x1: number, y1: number, x2: number, y2: number): Line2D;\n /**\n * Create a line from a start point, angle, and length.\n *\n * @param origin - Start point.\n * @param angleDeg - Angle in degrees, CCW from +X.\n * @param length - Segment length.\n * @returns A new line segment.\n */\n static fromPointAndAngle(origin: Point2D, angleDeg: number, length: number): Line2D;\n /**\n * Create a line from a start point, direction vector, and length.\n *\n * @param origin - Start point.\n * @param dir - Direction vector `[x, y]`.\n * @param length - Segment length.\n * @returns A new line segment.\n */\n static fromPointAndDirection(origin: Point2D, dir: [\n number,\n number\n ], length: number): Line2D;\n}\n/**\n * Create an analytic 2D line segment between two points.\n *\n * @softDeprecated Line2D.fromCoordinates(x1, y1, x2, y2) — or cs.line(a, b) inside constrainedSketch()\n * @deprecated use Line2D.fromCoordinates(x1, y1, x2, y2) — or cs.line(a, b) inside constrainedSketch()\n * @param x1 - Start X\n * @param y1 - Start Y\n * @param x2 - End X\n * @param y2 - End Y\n * @returns A new `Line2D`\n * @category 2D Entities\n */\ndeclare function line(x1: number, y1: number, x2: number, y2: number): Line2D;\n/**\n * An immutable 2D circle with area, circumference, and extrusion support.\n *\n * Extruding a `Circle2D` produces a cylinder with named `top`, `bottom`,\n * and `side` faces accessible via the topology API.\n *\n * @category 2D Entities\n */\ndeclare class Circle2D {\n readonly center: Point2D;\n readonly radius: number;\n constructor(center: Point2D, radius: number);\n /** Diameter of the circle. */\n get diameter(): number;\n /** Circumference of the circle. */\n get circumference(): number;\n /** Area of the circle. */\n get area(): number;\n /**\n * Return a point on the circle at the given angle.\n *\n * @param angleDeg - Angle in degrees, where 0 is right and CCW is positive.\n * @returns A point on the perimeter.\n */\n pointAtAngle(angleDeg: number): Point2D;\n /**\n * Return a translated circle.\n *\n * @param dx - Horizontal offset.\n * @param dy - Vertical offset.\n * @returns A shifted circle.\n */\n translate(dx: number, dy: number): Circle2D;\n /**\n * Convert this circle to a sketch profile.\n *\n * @param segments - Optional polygon approximation resolution.\n * @returns A sketch representation of the circle.\n */\n toSketch(segments?: number): Sketch;\n /**\n * Extrude the circle into a solid cylinder.\n *\n * @param height - Extrusion height.\n * @param segments - Optional tessellation resolution.\n * @returns A new shape with named cylinder faces.\n */\n extrude(height: number, segments?: number): Shape;\n /**\n * Create a circle from its center and radius.\n *\n * @param center - Circle center.\n * @param radius - Circle radius.\n * @returns A new circle.\n */\n static fromCenterAndRadius(center: Point2D, radius: number): Circle2D;\n /**\n * Create a circle from its center and diameter.\n *\n * @param center - Circle center.\n * @param diameter - Circle diameter.\n * @returns A new circle.\n */\n static fromDiameter(center: Point2D, diameter: number): Circle2D;\n}\n/**\n * Create an analytic 2D circle for measurement, construction, and extrusion.\n *\n * @softDeprecated Circle2D.fromCenterAndRadius(new Point2D(cx, cy), r) — or circle2d(r) for a sketch profile\n * @deprecated use Circle2D.fromCenterAndRadius(new Point2D(cx, cy), r) — or circle2d(r) for a sketch profile\n * @param cx - Center X\n * @param cy - Center Y\n * @param radius - Radius\n * @returns A new `Circle2D`\n * @category 2D Entities\n */\ndeclare function circle(cx: number, cy: number, radius: number): Circle2D;\ntype RectSide = "top" | "bottom" | "left" | "right";\ntype RectVertex = "top-left" | "top-right" | "bottom-left" | "bottom-right";\n/**\n * A rectangle with named sides, vertices, and extrusion support.\n *\n * **Details**\n *\n * Sides are named based on the rectangle\'s local orientation at construction\n * time. Vertices go: bottom-left, bottom-right, top-right, top-left (CCW).\n *\n * Use `rect()` for the normal centered sketch primitive. Use `Rectangle2D`\n * when you need named sides/vertices, or an extrusion with tracked vertical\n * edges such as `vert-br` for `filletTrackedEdge()` / `chamferTrackedEdge()`.\n *\n * Extruding a `Rectangle2D` produces a `Shape` with named faces:\n * `top`, `bottom`, `side-left`, `side-right`, `side-top`, `side-bottom`.\n * These are accessible via the topology API (`.face()`, `.edge()`).\n *\n * **Example**\n *\n * ```ts\n * const r = Rectangle2D.fromDimensions(0, 0, 100, 60);\n * r.side(\'top\'); r.side(\'left\'); // Line2D\n * r.vertex(\'top-left\'); // Point2D\n * r.width; r.height; r.center;\n * const [d1, d2] = r.diagonals(); // [bl-tr, br-tl]\n *\n * r.toSketch(); // Sketch (for 2D operations)\n * r.extrude(20); // Shape with named faces\n *\n * Rectangle2D.fromCenterAndDimensions(new Point2D(50, 30), 100, 60);\n * Rectangle2D.from2Corners(new Point2D(0, 0), new Point2D(100, 60));\n * Rectangle2D.from3Points(p1, p2, p3); // free-angle rectangle\n * ```\n *\n * @category 2D Entities\n */\ndeclare class Rectangle2D {\n /** Vertices in order: bottom-left, bottom-right, top-right, top-left */\n readonly vertices: [\n Point2D,\n Point2D,\n Point2D,\n Point2D\n ];\n constructor(vertices: [\n Point2D,\n Point2D,\n Point2D,\n Point2D\n ]);\n /** Width of the rectangle. */\n get width(): number;\n /** Height of the rectangle. */\n get height(): number;\n /** Geometric center of the rectangle. */\n get center(): Point2D;\n /**\n * Return a named side of the rectangle.\n *\n * @param name - One of `top`, `bottom`, `left`, or `right`.\n * @returns The requested side as a line segment.\n */\n side(name: RectSide): Line2D;\n /**\n * Return a side by index.\n *\n * @param index - Side index: `0=bottom`, `1=right`, `2=top`, `3=left`.\n * @returns The requested side as a line segment.\n */\n sideAt(index: number): Line2D;\n /**\n * Return a named vertex of the rectangle.\n *\n * @param name - One of `bottom-left`, `bottom-right`, `top-right`, or `top-left`.\n * @returns The requested vertex.\n */\n vertex(name: RectVertex): Point2D;\n /**\n * Return the two diagonals of the rectangle.\n *\n * @returns `[bottom-left to top-right, bottom-right to top-left]`.\n */\n diagonals(): [\n Line2D,\n Line2D\n ];\n /**\n * Convert the rectangle to a sketch profile.\n *\n * @returns A sketch representation of the rectangle.\n */\n toSketch(): Sketch;\n /**\n * Return a translated rectangle.\n *\n * @param dx - Horizontal offset.\n * @param dy - Vertical offset.\n * @returns A shifted rectangle.\n */\n translate(dx: number, dy: number): Rectangle2D;\n /**\n * Create an axis-aligned rectangle from origin corner plus width and height.\n *\n * @param x - Bottom-left X.\n * @param y - Bottom-left Y.\n * @param width - Rectangle width.\n * @param height - Rectangle height.\n * @returns A new rectangle.\n */\n static fromDimensions(x: number, y: number, width: number, height: number): Rectangle2D;\n /**\n * Create a rectangle centered on a point.\n *\n * @param center - Rectangle center.\n * @param width - Rectangle width.\n * @param height - Rectangle height.\n * @returns A new rectangle.\n */\n static fromCenterAndDimensions(center: Point2D, width: number, height: number): Rectangle2D;\n /**\n * Create an axis-aligned rectangle from two opposite corners.\n *\n * @param p1 - First corner.\n * @param p2 - Opposite corner.\n * @returns A new rectangle.\n */\n static from2Corners(p1: Point2D, p2: Point2D): Rectangle2D;\n /**\n * Create a free-angle rectangle from three points.\n *\n * `p1` and `p2` define one edge, and `p3` chooses the perpendicular side.\n *\n * @param p1 - First edge point.\n * @param p2 - Second edge point.\n * @param p3 - Point indicating the rectangle height direction.\n * @returns A new rectangle.\n */\n static from3Points(p1: Point2D, p2: Point2D, p3: Point2D): Rectangle2D;\n /**\n * Extrude the rectangle into a solid prism with named topology.\n *\n * @param height - Extrusion height.\n * @param up - When false, extrude downward instead of upward.\n * @returns A new shape with named faces and edges.\n */\n extrude(height: number, up?: boolean): Shape;\n}\ndeclare function polar(length: number, angleDeg: number, from?: [\n number,\n number\n]): [\n number,\n number\n];\ntype LineArg = LineId | Line2D;\ntype PointArg = PointId | Point2D;\n/**\n * Legacy constraint namespace — every member duplicates a\n * `ConstrainedSketchBuilder` method (`sk.parallel(a, b)` etc.), which is the\n * taught spelling. The builder methods auto-import `Point2D`/`Line2D`\n * entities, so nothing here is unique. Kept runtime-alive for existing\n * scripts; each member warns once per run naming its builder replacement.\n */\ndeclare const Constraint: {\n /**\n * Constrain two lines to be parallel.\n *\n * @softDeprecated sk.parallel(a, b)\n * @deprecated use sk.parallel(a, b)\n */\n makeParallel: (builder: ConstrainedSketchBuilder, a: LineArg, b: LineArg) => ConstrainedSketchBuilder;\n /**\n * Removed trap: the name suggested an unsigned angle but it emitted the\n * signed `angle` constraint. Throws with the exact replacements.\n *\n * @internal\n */\n enforceAngle(_builder: ConstrainedSketchBuilder, _a: LineArg, _b: LineArg, _angleDeg: number): never;\n /**\n * Constrain a line to be horizontal.\n *\n * @softDeprecated sk.horizontal(line)\n * @deprecated use sk.horizontal(line)\n */\n horizontal: (builder: ConstrainedSketchBuilder, line: LineArg) => ConstrainedSketchBuilder;\n /**\n * Constrain a line to be vertical.\n *\n * @softDeprecated sk.vertical(line)\n * @deprecated use sk.vertical(line)\n */\n vertical: (builder: ConstrainedSketchBuilder, line: LineArg) => ConstrainedSketchBuilder;\n /**\n * Constrain two lines to have equal length.\n *\n * @softDeprecated sk.equal(a, b)\n * @deprecated use sk.equal(a, b)\n */\n equalLength: (builder: ConstrainedSketchBuilder, a: LineArg, b: LineArg) => ConstrainedSketchBuilder;\n /**\n * Constrain the distance between two points.\n *\n * @softDeprecated sk.distance(a, b, value)\n * @deprecated use sk.distance(a, b, value)\n */\n distance: (builder: ConstrainedSketchBuilder, a: PointArg, b: PointArg, value: number) => ConstrainedSketchBuilder;\n /**\n * Fix a point at a specific coordinate.\n *\n * @softDeprecated sk.fix(point, x, y)\n * @deprecated use sk.fix(point, x, y)\n */\n fix: (builder: ConstrainedSketchBuilder, pt: PointArg, x: number, y: number) => ConstrainedSketchBuilder;\n /**\n * Constrain two points to occupy the same location.\n *\n * @softDeprecated sk.coincident(a, b)\n * @deprecated use sk.coincident(a, b)\n */\n coincident: (builder: ConstrainedSketchBuilder, a: PointArg, b: PointArg) => ConstrainedSketchBuilder;\n /**\n * Constrain two lines to be perpendicular.\n *\n * @softDeprecated sk.perpendicular(a, b)\n * @deprecated use sk.perpendicular(a, b)\n */\n perpendicular: (builder: ConstrainedSketchBuilder, a: LineArg, b: LineArg) => ConstrainedSketchBuilder;\n /**\n * Constrain the length of a line.\n *\n * @softDeprecated sk.length(line, value)\n * @deprecated use sk.length(line, value)\n */\n length: (builder: ConstrainedSketchBuilder, line: LineArg, value: number) => ConstrainedSketchBuilder;\n};\ntype FaceName = string;\ntype EdgeName = string;\ntype NurbsFaceTrimLoop = {\n kind: "nurbsUv";\n role: "outer" | "hole";\n loopIndex: number;\n degree: number;\n rational: boolean;\n controlPoints: [\n number,\n number\n ][];\n weights: number[];\n knots: number[];\n samples: number;\n faceName?: string;\n} | {\n kind: "polylineUv";\n role: "outer" | "hole";\n loopIndex: number;\n points: [\n number,\n number\n ][];\n faceName?: string;\n};\ntype FaceSurface = {\n kind: "plane";\n normal: Vec3;\n} | {\n kind: "cylinder";\n origin: Vec3;\n axis: Vec3;\n radius: number;\n height: number;\n} | {\n kind: "cone";\n origin: Vec3;\n axis: Vec3;\n radiusBottom: number;\n radiusTop: number;\n height: number;\n} | {\n kind: "sphere";\n center: Vec3;\n radius: number;\n} | {\n kind: "torus";\n center: Vec3;\n axis: Vec3;\n majorRadius: number;\n minorRadius: number;\n} | {\n kind: "ruled";\n rails: [\n [\n Vec3,\n Vec3\n ],\n [\n Vec3,\n Vec3\n ]\n ];\n} | {\n kind: "nurbs";\n degreeU: number;\n degreeV: number;\n rational: boolean;\n trimmed: boolean;\n thickness: number;\n trimLoops?: NurbsFaceTrimLoop[];\n};\ntype EdgeCurve = {\n kind: "line";\n start: Vec3;\n end: Vec3;\n faceName?: string;\n} | {\n kind: "circle";\n center: Vec3;\n axis: Vec3;\n radius: number;\n faceName?: string;\n} | {\n kind: "surfaceIso";\n surface: "nurbs";\n fixedParameter: "u" | "v";\n fixedValue: number;\n parameterRange: [\n number,\n number\n ];\n degree: number;\n rational: boolean;\n faceName?: string;\n} | {\n kind: "nurbsUv";\n surface: "nurbs";\n role: "outer" | "hole";\n loopIndex: number;\n degree: number;\n rational: boolean;\n controlPoints: [\n number,\n number\n ][];\n weights: number[];\n knots: number[];\n samples: number;\n faceName?: string;\n} | {\n kind: "polylineUv";\n surface: "nurbs";\n role: "outer" | "hole";\n loopIndex: number;\n points: [\n number,\n number\n ][];\n faceName?: string;\n};\ninterface FaceRef {\n name: FaceName;\n /** Normal direction of the face */\n normal: [\n number,\n number,\n number\n ];\n /** Center point of the face */\n center: [\n number,\n number,\n number\n ];\n /** Compiler-owned face query when available. */\n query?: FaceQueryRef;\n /** True when the face can host a 2D sketch placement frame */\n planar?: boolean;\n /** Face-local horizontal axis for planar faces */\n uAxis?: [\n number,\n number,\n number\n ];\n /** Face-local vertical axis for planar faces */\n vAxis?: [\n number,\n number,\n number\n ];\n /** Analytic surface family when the backend can identify one. */\n surface?: FaceSurface;\n /** Shared descendant-resolution metadata when this face is a semantic region/set. */\n descendant?: FaceDescendantMetadata;\n}\ninterface EdgeRef {\n name: EdgeName;\n /** Start point */\n start: [\n number,\n number,\n number\n ];\n /** End point */\n end: [\n number,\n number,\n number\n ];\n /** Compiler-owned edge query when available. */\n query?: EdgeQueryRef;\n /** Exact or parametric curve family when the backend/source can identify one. */\n curve?: EdgeCurve;\n /** Owning face name when the edge is associated with one face in a larger topology. */\n faceName?: string;\n}\ntype PlaneSpec = {\n origin: Vec3;\n normal: Vec3;\n} | {\n plane: "XY" | "XZ" | "YZ";\n offset?: number;\n} | {\n face: FaceRef;\n};\ntype Vec2 = [\n number,\n number\n];\ntype Vec3$1 = [\n number,\n number,\n number\n];\ntype TpmsThicknessMode = "legacy-field-threshold" | "metric-approx";\ntype SdfFunctionConstant = number | string | boolean | null | SdfFunctionConstant[] | {\n [key: string]: SdfFunctionConstant;\n};\ntype SdfFunctionConstants = Record<string, SdfFunctionConstant>;\ninterface SdfSurfacePatternConstantNode {\n kind: "surfacePattern:constant";\n value: number;\n}\ninterface SdfSurfacePatternSineWaveNode {\n kind: "surfacePattern:sineWave";\n direction: Vec2;\n wavelength: number;\n amplitude: number;\n phase: number;\n bias: number;\n}\ninterface SdfSurfacePatternStripesNode {\n kind: "surfacePattern:stripes";\n direction: Vec2;\n spacing: number;\n width: number;\n depth: number;\n}\ninterface SdfSurfacePatternOverUnderWeaveNode {\n kind: "surfacePattern:overUnderWeave";\n spacing: Vec2;\n threadWidth: Vec2;\n depth: number;\n underScale: number;\n}\ninterface SdfSurfacePatternUnaryNode {\n kind: "surfacePattern:abs" | "surfacePattern:negate";\n child: SdfSurfacePatternNode;\n}\ninterface SdfSurfacePatternNaryNode {\n kind: "surfacePattern:add" | "surfacePattern:multiply" | "surfacePattern:min" | "surfacePattern:max";\n children: SdfSurfacePatternNode[];\n}\ninterface SdfSurfacePatternClampNode {\n kind: "surfacePattern:clamp";\n child: SdfSurfacePatternNode;\n min: number;\n max: number;\n}\ntype SdfSurfacePatternNode = SdfSurfacePatternConstantNode | SdfSurfacePatternSineWaveNode | SdfSurfacePatternStripesNode | SdfSurfacePatternOverUnderWeaveNode | SdfSurfacePatternUnaryNode | SdfSurfacePatternNaryNode | SdfSurfacePatternClampNode;\ninterface SdfSphereNode {\n kind: "sdf:sphere";\n radius: number;\n}\ninterface SdfBoxNode {\n kind: "sdf:box";\n halfExtents: Vec3$1;\n}\ninterface SdfCylinderNode {\n kind: "sdf:cylinder";\n height: number;\n radius: number;\n}\ninterface SdfTorusNode {\n kind: "sdf:torus";\n majorRadius: number;\n minorRadius: number;\n}\ninterface SdfCapsuleNode {\n kind: "sdf:capsule";\n height: number;\n radius: number;\n}\ninterface SdfConeNode {\n kind: "sdf:cone";\n height: number;\n radius: number;\n}\ninterface SdfPolylineSweepNode {\n kind: "sdf:polylineSweep";\n /** Ordered path samples in world/local SDF space. */\n points: Vec3$1[];\n /** Radius at each corresponding point. */\n radii: number[];\n /** Smooth blend radius between adjacent tapered segments. */\n blend: number;\n}\ninterface SdfProfileExtrudeNode {\n kind: "sdf:profileExtrude";\n /** Closed 2D profile loops in XY, CCW outer loops and CW holes. */\n polygons: Vec2[][];\n height: number;\n twist?: number;\n scaleTop?: Vec2;\n}\ninterface SdfProfileRevolveNode {\n kind: "sdf:profileRevolve";\n /** Closed 2D profile loops where x is radius and y becomes world z. */\n polygons: Vec2[][];\n degrees: number;\n}\ninterface SdfUnionNode {\n kind: "sdf:union";\n children: SdfNode[];\n}\ninterface SdfDifferenceNode {\n kind: "sdf:difference";\n /** First child is the base; subsequent children are subtracted. */\n children: SdfNode[];\n}\ninterface SdfIntersectionNode {\n kind: "sdf:intersection";\n children: SdfNode[];\n}\ninterface SdfSmoothUnionNode {\n kind: "sdf:smoothUnion";\n children: SdfNode[];\n radius: number;\n}\ninterface SdfSmoothDifferenceNode {\n kind: "sdf:smoothDifference";\n children: SdfNode[];\n radius: number;\n}\ninterface SdfSmoothIntersectionNode {\n kind: "sdf:smoothIntersection";\n children: SdfNode[];\n radius: number;\n}\ninterface SdfMorphNode {\n kind: "sdf:morph";\n a: SdfNode;\n b: SdfNode;\n /** 0 = fully a, 1 = fully b */\n t: number;\n}\ninterface SdfTranslateNode {\n kind: "sdf:translate";\n child: SdfNode;\n offset: Vec3$1;\n}\ninterface SdfRotateNode {\n kind: "sdf:rotate";\n child: SdfNode;\n /** Euler angles in degrees (X, Y, Z) */\n degrees: Vec3$1;\n}\ninterface SdfScaleNode {\n kind: "sdf:scale";\n child: SdfNode;\n factor: number;\n}\ninterface SdfTwistNode {\n kind: "sdf:twist";\n child: SdfNode;\n /** Total twist angle in degrees over the full height of the shape */\n degreesPerUnit: number;\n}\ninterface SdfBendNode {\n kind: "sdf:bend";\n child: SdfNode;\n /** Bend radius — larger = gentler bend */\n radius: number;\n}\ninterface SdfRepeatNode {\n kind: "sdf:repeat";\n child: SdfNode;\n /** Spacing between repetitions [x, y, z]. 0 = no repetition on that axis. */\n spacing: Vec3$1;\n /** Max repetition count per side. 0 = infinite. */\n count: Vec3$1;\n}\ninterface SdfCircularArrayNode {\n kind: "sdf:circularArray";\n child: SdfNode;\n /** Number of copies arranged around the Z axis. */\n count: number;\n /** Distance to translate the source shape in +X before angular folding. */\n offset: number;\n}\ninterface SdfShellNode {\n kind: "sdf:shell";\n child: SdfNode;\n thickness: number;\n}\ninterface SdfOffsetNode {\n kind: "sdf:offset";\n child: SdfNode;\n /** Field offset in millimeters: positive dilates (rounds convex edges), negative erodes. */\n distance: number;\n}\ninterface SdfDisplaceNode {\n kind: "sdf:displace";\n child: SdfNode;\n /** Serialized as a function body string: receives (x, y, z) and returns a number. */\n functionBody: string;\n /** Named constants injected as additional function parameters (avoids closure serialization issues). */\n constants?: SdfFunctionConstants;\n}\ninterface SdfSurfaceDisplaceNode {\n kind: "sdf:surfaceDisplace";\n child: SdfNode;\n /** Typed built-in pattern, when available. Custom callbacks use patternBody only. */\n pattern?: SdfSurfacePatternNode;\n /**\n * Function body string: receives (u, v) in surface millimeters and returns a height\n * displacement value. Negative = into surface, positive = outward.\n */\n patternBody: string;\n /** Named constants injected as additional function parameters. */\n constants?: SdfFunctionConstants;\n /** Override auto-detected UV mode. Undefined or \'auto\' = auto-detect from child tree. */\n uvMode?: "auto" | "sphere" | "cylinder" | "torus" | "triplanar";\n /** Triplanar blend sharpness (only used when UV mode is triplanar). Default: 4. */\n triplanarSharpness?: number;\n}\ninterface SdfOnionNode {\n kind: "sdf:onion";\n child: SdfNode;\n /** Number of concentric layers */\n layers: number;\n thickness: number;\n}\ninterface SdfGyroidNode {\n kind: "sdf:gyroid";\n cellSize: number;\n thickness: number;\n thicknessMode?: TpmsThicknessMode;\n}\ninterface SdfSchwarzPNode {\n kind: "sdf:schwarzP";\n cellSize: number;\n thickness: number;\n thicknessMode?: TpmsThicknessMode;\n}\ninterface SdfDiamondNode {\n kind: "sdf:diamond";\n cellSize: number;\n thickness: number;\n thicknessMode?: TpmsThicknessMode;\n}\ninterface SdfLidinoidNode {\n kind: "sdf:lidinoid";\n cellSize: number;\n thickness: number;\n thicknessMode?: TpmsThicknessMode;\n}\ninterface SdfSpatialBlendNode {\n kind: "sdf:spatialBlend";\n a: SdfNode;\n b: SdfNode;\n /** Function body returning 0..1. 0 = fully a, 1 = fully b. */\n functionBody: string;\n constants?: SdfFunctionConstants;\n}\ninterface SdfNoiseNode {\n kind: "sdf:noise";\n /** Spatial frequency — smaller = larger features. */\n scale: number;\n /** Peak displacement amplitude. */\n amplitude: number;\n /** Number of octaves for fractal Brownian motion (1 = plain simplex). */\n octaves: number;\n /** Seed for deterministic variation. 0 = default permutation. */\n seed: number;\n}\ninterface SdfVoronoiNode {\n kind: "sdf:voronoi";\n /** Size of each Voronoi cell in world units. */\n cellSize: number;\n /** Wall thickness between cells. */\n wallThickness: number;\n /** Seed for deterministic variation. */\n seed: number;\n /**\n * When set, enables surface-aware mode using IQ two-pass with membrane suppression.\n * The child SDF\'s gradient is used to estimate the surface normal, and walls aligned\n * with that normal are suppressed. This is the child SDF tree whose gradient provides\n * the surface normal for filtering.\n */\n surfaceChild?: SdfNode;\n /**\n * Membrane suppression threshold (0..1). Higher = more aggressive suppression.\n * 0 = no filtering, 1 = suppress all walls. Default: 0.7.\n */\n suppressionThreshold?: number;\n}\ninterface SdfCustomNode {\n kind: "sdf:custom";\n /** Function body string: receives (x, y, z) and returns signed distance. */\n functionBody: string;\n /** GLSL expression generated from the same source expression, when shader-compatible. */\n shaderBody?: string;\n /** Why shaderBody could not be generated. */\n shaderUnsupportedReason?: string;\n /** Conservative maximum shader step for this field. */\n raymarchStepLimit?: number;\n /** Optional divisor applied to shader distance for non-distance fields. */\n raymarchLipschitz?: number;\n bounds: {\n min: Vec3$1;\n max: Vec3$1;\n };\n /** Named constants injected as additional function parameters (avoids closure serialization issues). */\n constants?: SdfFunctionConstants;\n}\ntype SdfNode = SdfSphereNode | SdfBoxNode | SdfCylinderNode | SdfTorusNode | SdfCapsuleNode | SdfConeNode | SdfPolylineSweepNode | SdfProfileExtrudeNode | SdfProfileRevolveNode | SdfUnionNode | SdfDifferenceNode | SdfIntersectionNode | SdfSmoothUnionNode | SdfSmoothDifferenceNode | SdfSmoothIntersectionNode | SdfMorphNode | SdfTranslateNode | SdfRotateNode | SdfScaleNode | SdfTwistNode | SdfBendNode | SdfRepeatNode | SdfCircularArrayNode | SdfShellNode | SdfOffsetNode | SdfDisplaceNode | SdfSurfaceDisplaceNode | SdfOnionNode | SdfGyroidNode | SdfSchwarzPNode | SdfDiamondNode | SdfLidinoidNode | SdfSpatialBlendNode | SdfNoiseNode | SdfVoronoiNode | SdfCustomNode;\ninterface SdfBounds {\n min: Vec3$1;\n max: Vec3$1;\n}\ntype SdfMeshingQuality = "draft" | "preview" | "export";\ninterface SdfToShapeOptions {\n /** Target mesh edge length. Smaller = finer mesh. Overrides quality-derived resolution. */\n edgeLength?: number;\n /** Override auto-computed bounds. Strongly recommended for infinite/repeated fields. */\n bounds?: {\n min: Vec3$1;\n max: Vec3$1;\n };\n /** Coarse quality preset. Default: \'preview\'. */\n quality?: SdfMeshingQuality;\n /** Preferred absolute surface tolerance in millimeters. */\n tolerance?: number;\n /** Smallest feature that should survive meshing, in millimeters. */\n minFeatureSize?: number;\n /** Simplification control. `false` disables, `true` and `\'safe\'` use topology-validated simplification. */\n simplify?: boolean | "safe";\n /** Optional post-extraction triangle budget. Fractional values are floored for compatibility. */\n maxTriangles?: number;\n /** Optional pre-extraction grid-point budget. Default is browser-safe. */\n maxGridPoints?: number;\n /** Lower clamp for resolved edge length. Default: 0.15mm. */\n minEdgeLength?: number;\n /** Log resolved meshing settings and backend extraction timings. */\n diagnostics?: boolean;\n}\ndeclare const SHEET_METAL_EDGES: readonly [\n "top",\n "right",\n "bottom",\n "left"\n];\ntype SheetMetalEdge = (typeof SHEET_METAL_EDGES)[number];\ntype SheetMetalPlanarRegionName = "panel" | `flange-${SheetMetalEdge}`;\ntype SheetMetalRegionName = SheetMetalPlanarRegionName | `bend-${SheetMetalEdge}`;\ninterface SheetMetalBendAllowance {\n kind: "k-factor";\n kFactor: number;\n}\ninterface SheetMetalPanelSpec {\n width: number;\n height: number;\n}\ninterface SheetMetalFlangeSpec {\n edge: SheetMetalEdge;\n length: number;\n angleDeg: number;\n}\ninterface SheetMetalCornerReliefSpec {\n kind: "rect";\n size: number;\n}\ninterface SheetMetalModel {\n panel: SheetMetalPanelSpec;\n thickness: number;\n bendRadius: number;\n bendAllowance: SheetMetalBendAllowance;\n cornerRelief: SheetMetalCornerReliefSpec;\n flanges: SheetMetalFlangeSpec[];\n}\ninterface EdgeSegment {\n /** Stable index within the extraction (deterministic for a given mesh). */\n index: number;\n start: Vec3;\n end: Vec3;\n midpoint: Vec3;\n /** Normalized direction from start → end. */\n direction: Vec3;\n length: number;\n /** Dihedral angle in degrees (0 = coplanar, 180 = knife edge). */\n dihedralAngle: number;\n /** true = outside corner (convex), false = inside corner (concave). */\n convex: boolean;\n /** Normal of first adjacent face. */\n normalA: Vec3;\n /** Normal of second adjacent face (same as normalA for boundary edges). */\n normalB: Vec3;\n /** true if this is a boundary (unmatched) edge — unusual for closed solids. */\n boundary: boolean;\n /** Native kernel topology identity when the active backend can provide one. */\n nativeTopology?: EdgeNativeTopologyRef;\n}\ninterface BoundingRegion {\n xMin?: number;\n xMax?: number;\n yMin?: number;\n yMax?: number;\n zMin?: number;\n zMax?: number;\n}\n/**\n * Filter and sort criteria for edge selection.\n *\n * All filters are combined with AND logic — an edge must pass every\n * specified filter to be included. Omitting a filter means "any".\n *\n * @category Edge Queries\n */\ninterface EdgeQuery {\n /** Sort by proximity to this point (closest first). When used with `selectEdge`, picks the closest match. */\n near?: Vec3;\n /** Filter: edge direction approximately parallel to this vector. */\n parallel?: Vec3;\n /** Filter: edge direction approximately perpendicular to this vector. */\n perpendicular?: Vec3;\n /** Filter: only convex (outside corner) edges. */\n convex?: boolean;\n /** Filter: only concave (inside corner) edges. */\n concave?: boolean;\n /** Filter: minimum dihedral angle in degrees. */\n minAngle?: number;\n /** Filter: maximum dihedral angle in degrees. */\n maxAngle?: number;\n /** Filter: minimum edge length. */\n minLength?: number;\n /** Filter: maximum edge length. */\n maxLength?: number;\n /** Filter: edge midpoint must be within this bounding region. */\n within?: BoundingRegion;\n /** Shorthand: edge midpoint Z is approximately this value within `tolerance`. */\n atZ?: number;\n /** Position tolerance for approximate matches. Used by `atZ` and `near`. Default: `1.0`. */\n tolerance?: number;\n /** Angular tolerance in degrees for `parallel`/`perpendicular` filters. Default: `10`. */\n angleTolerance?: number;\n}\ninterface Route3DPortFrameCompilePlan {\n name: string;\n origin: Vec3;\n axis: Vec3;\n xAxis: Vec3;\n yAxis: Vec3;\n station: number;\n}\ntype Route3DSegmentCompilePlan = {\n kind: "line";\n from: Vec3;\n to: Vec3;\n length: number;\n} | {\n kind: "arc";\n center: Vec3;\n radius: number;\n axis: Vec3;\n start: Vec3;\n end: Vec3;\n sweepDeg: number;\n length: number;\n};\ninterface Route3DCompilePlan {\n kind: "route3d";\n segments: Route3DSegmentCompilePlan[];\n ports: Record<string, Route3DPortFrameCompilePlan>;\n length: number;\n}\ntype SweepPathCompilePlan = {\n kind: "polyline";\n points: [\n number,\n number,\n number\n ][];\n} | {\n kind: "catmull-rom";\n controlPoints: [\n number,\n number,\n number\n ][];\n tension: number;\n closed: boolean;\n} | {\n kind: "hermite";\n p0: [\n number,\n number,\n number\n ];\n p1: [\n number,\n number,\n number\n ];\n t0: [\n number,\n number,\n number\n ];\n t1: [\n number,\n number,\n number\n ];\n chordLength: number;\n} | {\n kind: "quintic-hermite";\n p0: [\n number,\n number,\n number\n ];\n p1: [\n number,\n number,\n number\n ];\n t0: [\n number,\n number,\n number\n ];\n t1: [\n number,\n number,\n number\n ];\n c0: [\n number,\n number,\n number\n ];\n c1: [\n number,\n number,\n number\n ];\n chordLength: number;\n} | {\n kind: "nurbs";\n controlPoints: [\n number,\n number,\n number\n ][];\n weights: number[];\n knots: number[];\n degree: number;\n closed: boolean;\n} | Route3DCompilePlan;\ntype SurfaceContinuity = "G0" | "G1" | "G2";\ntype SurfaceFillStyle = "coons" | "curved" | "stretch";\ninterface SurfaceDomainCompilePlan {\n uMin: number;\n uMax: number;\n vMin: number;\n vMax: number;\n}\ninterface TransformationStep {\n kind: string;\n description: string;\n details?: Record<string, unknown>;\n}\ntype TimelineEntryCategory = "primitive" | "sketch" | "modifier" | "boolean" | "transform";\ninterface TimelineEntry {\n kind: string;\n label: string;\n summary: string;\n category: TimelineEntryCategory;\n}\ninterface FaceTransformationHistory {\n faceName: string;\n origin: {\n operation: string;\n owner?: ShapeQueryOwner;\n };\n transformations: TransformationStep[];\n query?: FaceQueryRef;\n /** Ordered list of operations that built this shape, oldest first. */\n timeline: TimelineEntry[];\n}\n/**\n * FaceQuery — declarative face selector types for geometric face matching.\n *\n * `FaceSelector` is a string label name. `FaceQuery` is a structured\n * object that matches faces by geometry (normal, nearest, area, etc.)\n * and is used internally by compile-plan face resolution.\n */\ninterface FaceQuery {\n /** Filter by face normal direction (cosine similarity > 0.9998) */\n normal?: [\n number,\n number,\n number\n ];\n /** Pick face whose centroid is nearest to this point (XY or XYZ) */\n nearest?: [\n number,\n number\n ] | [\n number,\n number,\n number\n ];\n /** Pick face containing/nearest to this world point */\n at?: [\n number,\n number,\n number\n ];\n /** Disambiguation when multiple faces match */\n pick?: "largest" | "smallest" | "max-x" | "max-y" | "max-z" | "min-x" | "min-y" | "min-z";\n /** Filter by area range (mm²) */\n area?: {\n min?: number;\n max?: number;\n };\n /** Only match planar faces (default: true) */\n planar?: boolean;\n}\n/** A face label name. Pass to `shape.face()` to look up a user-authored label. */\ntype FaceSelector = string;\ntype PlacementReferenceKind = "points" | "edges" | "surfaces" | "objects";\ninterface PlacementEdgeRef {\n start: Vec3;\n end: Vec3;\n}\ninterface PlacementSurfaceRef {\n center: Vec3;\n normal: Vec3;\n}\ninterface PlacementObjectRef {\n min: Vec3;\n max: Vec3;\n}\ninterface PlacementReferences {\n points: Record<string, Vec3>;\n edges: Record<string, PlacementEdgeRef>;\n surfaces: Record<string, PlacementSurfaceRef>;\n objects: Record<string, PlacementObjectRef>;\n}\ntype PlacementObjectInput = PlacementObjectRef | {\n min: [\n number,\n number,\n number\n ];\n max: [\n number,\n number,\n number\n ];\n} | {\n boundingBox(): {\n min: number[];\n max: number[];\n };\n} | {\n _bbox(): {\n min: number[];\n max: number[];\n };\n};\ninterface PlacementReferenceInput {\n points?: Record<string, [\n number,\n number,\n number\n ]>;\n edges?: Record<string, PlacementEdgeRef>;\n surfaces?: Record<string, PlacementSurfaceRef>;\n objects?: Record<string, PlacementObjectInput>;\n}\ntype PlacementAnchorLike = Anchor3D | string;\ntype GroupChild = Shape | Sketch | ShapeGroup;\ninterface NamedGroupChild {\n name: string;\n /** Viewport organization tags for command-palette hide/show/focus workflows. */\n tags?: string | readonly string[];\n shape?: Shape | ShapeGroup;\n sketch?: Sketch;\n group?: GroupInput[] | ShapeGroup;\n}\ntype GroupInput = GroupChild | NamedGroupChild;\ndeclare class ShapeGroup {\n readonly children: GroupChild[];\n readonly childNames: Array<string | undefined>;\n private readonly childTags;\n constructor(children: GroupChild[], childNames?: Array<string | undefined>, childTags?: Array<string | readonly string[] | undefined | null>);\n /** Return the optional name of the child at `index`. */\n childName(index: number): string | undefined;\n /**\n * Return tags attached to the child at `index`.\n * @internal\n */\n tagsForChild(index: number): string[];\n /**\n * Return the named child by name. Throws if not found.\n * Useful when importing a multipart group and working on components individually.\n */\n child(name: string): GroupChild;\n /** Apply fn to all children, producing a new ShapeGroup that also copies placement refs. */\n private mapChildren;\n /** Apply fn to all children and also transform placement refs by the given matrix. */\n private mapChildrenTransform;\n /** Return a deep-cloned ShapeGroup tree (refs copied). */\n clone(): ShapeGroup;\n /** Alias for clone() */\n duplicate(): ShapeGroup;\n /** Move the entire group by (x, y, z). All children move together as a unit. */\n translate(x: number, y: number, z: number): ShapeGroup;\n /** Compute combined bounding box of all 3D children */\n private _bbox;\n private _bboxCenter;\n /** Return the combined 3D bounding box of all children. */\n boundingBox(): {\n min: [\n number,\n number,\n number\n ];\n max: [\n number,\n number,\n number\n ];\n };\n private resolveRotatePoint;\n /** Move the group so its bounding-box min corner lands at the given coordinate. */\n moveTo(x: number, y: number, z: number): ShapeGroup;\n /** Move the group relative to another part\'s bounding-box min corner. */\n moveToLocal(target: Shape | ShapeGroup, x: number, y: number, z: number): ShapeGroup;\n /**\n * Attach this group to a face or anchor on another part.\n *\n * `targetAnchor` can be a built-in anchor name or a custom reference name\n * on the target. `selfAnchor` selects the anchor on this group to align.\n */\n attachTo(target: Shape | ShapeGroup, targetAnchor: Anchor3D | string, selfAnchor?: Anchor3D, offset?: [\n number,\n number,\n number\n ]): ShapeGroup;\n /**\n * Place this group on a face of a parent shape.\n * See Shape.onFace() for full documentation.\n */\n onFace(parent: Shape | ShapeGroup, face: "front" | "back" | "left" | "right" | "top" | "bottom", opts?: {\n u?: number;\n v?: number;\n protrude?: number;\n }): ShapeGroup;\n /** Rotate the group around an arbitrary axis through the origin. Unlike `scale()`/`mirror()` (bounding-box center) and `Sketch.rotate()`, this pivots at the world origin — pass `options.pivot` to rotate in place. */\n rotate(axis: [\n number,\n number,\n number\n ], angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): ShapeGroup;\n /** Rotate the group around the X axis. */\n rotateX(angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): ShapeGroup;\n /** Rotate the group around the Y axis. */\n rotateY(angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): ShapeGroup;\n /** Rotate the group around the Z axis. */\n rotateZ(angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): ShapeGroup;\n /** Rotate around an arbitrary axis, optionally through a pivot point. */\n rotateAroundAxis(axis: [\n number,\n number,\n number\n ], angleDeg: number, pivot?: [\n number,\n number,\n number\n ]): ShapeGroup;\n /**\n * Rotate around an axis until a moving point reaches the target line/plane defined by the axis and target point.\n * ShapeGroup string points use built-in anchors only.\n */\n rotateAroundTo(axis: [\n number,\n number,\n number\n ], pivot: [\n number,\n number,\n number\n ], movingPoint: Anchor3D | [\n number,\n number,\n number\n ], targetPoint: Anchor3D | [\n number,\n number,\n number\n ], options?: RotateAroundToOptions): ShapeGroup;\n /** Reorient the group so its local Z axis points along `direction`. */\n pointAlong(direction: [\n number,\n number,\n number\n ]): ShapeGroup;\n /** Apply a 4x4 transform matrix or `Transform` to all 3D children. */\n transform(m: Mat4 | Transform): ShapeGroup;\n /** Scale uniformly or per-axis from the group\'s bounding-box center. */\n scale(v: number | [\n number,\n number,\n number\n ]): ShapeGroup;\n /** Scale uniformly or per-axis from an explicit pivot point. */\n scaleAround(pivot: [\n number,\n number,\n number\n ], v: number | [\n number,\n number,\n number\n ]): ShapeGroup;\n /** Mirror across a plane through the group\'s bounding-box center. */\n mirror(normal: [\n number,\n number,\n number\n ]): ShapeGroup;\n /** Mirror across a plane through an explicit point. */\n mirrorThrough(point: [\n number,\n number,\n number\n ], normal: [\n number,\n number,\n number\n ]): ShapeGroup;\n /** Return a copy of the group with the given display color applied to each child. */\n color(hex: string): ShapeGroup;\n /**\n * Attach named placement references to this group.\n * References survive normal transforms (translate/rotate/scale/mirror/transform).\n *\n * ```javascript\n * const bracket = group(\n * { name: \'Left\', shape: leftShape },\n * { name: \'Right\', shape: rightShape },\n * ).withReferences({\n * points: { mountCenter: [0, 0, 0] },\n * });\n * ```\n */\n withReferences(refs: PlacementReferenceInput): ShapeGroup;\n /** List named placement references carried by this group. */\n referenceNames(kind?: PlacementReferenceKind): string[];\n /** Backward-compatible alias for `withConnectors()`. @deprecated Use `withConnectors()` instead. */\n withPorts(ports: Record<string, PortInput>): ShapeGroup;\n /** Backward-compatible alias for `connectorNames()`. @deprecated Use `connectorNames()` instead. */\n portNames(): string[];\n /**\n * Resolve a named placement reference or built-in Anchor3D to a 3D point.\n * Named refs take priority over built-in anchors.\n */\n referencePoint(ref: PlacementAnchorLike): [\n number,\n number,\n number\n ];\n /**\n * Translate the group so the given anchor or reference lands on the target coordinate.\n *\n * Accepts any built-in anchor name (`\'bottom\'`, `\'center\'`, `\'top-front-left\'`, etc.)\n * or a custom placement reference attached via `withReferences()`.\n *\n * ```javascript\n * // Ground a group — put its bottom at Z = 0\n * assembly.placeReference(\'bottom\', [0, 0, 0])\n *\n * // Use a custom reference from a multi-file part\n * const placed = require(\'./bracket-assembly.forge.js\').group\n * .placeReference(\'mountCenter\', [0, 0, 50]);\n * ```\n */\n placeReference(ref: PlacementAnchorLike, target: [\n number,\n number,\n number\n ], offset?: [\n number,\n number,\n number\n ]): ShapeGroup;\n /** Attach named connectors — attachment points that survive transforms.\n * Connectors can be bare (position + orientation) or typed (with connectorType/gender for compatibility matching). */\n withConnectors(connectors: Record<string, ConnectorInput>): ShapeGroup;\n /** List all connector names, including "ChildName.connectorName" from named children. */\n connectorNames(): string[];\n /** Get all connectors of a given type, including from named children. */\n connectorsByType(type: string): Array<{\n name: string;\n port: ConnectorDef;\n }>;\n /** Distance between two connector origins on this group (supports dotted child paths). */\n connectorDistance(nameA: string, nameB: string): number;\n /** Get measurements metadata from a connector (supports dotted child paths). */\n connectorMeasurements(name: string): Record<string, number | string>;\n /**\n * Position this group by matching connectors to a target.\n * Connector names support dotted paths into named children: "ChildName.connectorName".\n *\n * Alignment: with a single connector pair, the group translates and rotates so the connector\n * origins coincide and the axes oppose (plug-in model); `up` pins the roll. With multiple pairs,\n * the connector origins define the rigid transform — still author meaningful `axis`/`up` values\n * so the same connectors remain useful for `connect()`, audits, and future matching.\n *\n * Overloads:\n * - Single pair: `matchTo(target, selfConn, targetConn, options?)`\n * - Dictionary (same target): `matchTo(target, { selfConn: targetConn, ... }, options?)`\n * - Multi-target: `matchTo([ [target1, selfConn1, targetConn1], ... ], options?)`\n */\n matchTo(targetOrPairs: Shape | ShapeGroup | Array<[\n Shape | ShapeGroup,\n string,\n string\n ]>, selfConnOrDict?: string | Record<string, string>, targetConnOrOptions?: string | MatchToOptions, maybeOptions?: MatchToOptions): ShapeGroup;\n}\n/**\n * Group multiple shapes/sketches for joint transforms without merging into a single mesh.\n *\n * Unlike union(), child colors and individual identities are preserved.\n * Children can be plain shapes, named descriptors ({ name, shape/sketch/group }),\n * or nested groups. The returned ShapeGroup supports all Shape transforms\n * (translate, rotate, etc.).\n *\n * Named descriptors can include `tags` for viewport organization. Tags do not\n * affect geometry; they let the command palette hide, show only, or focus all\n * objects with the same tag.\n *\n * **Local coordinate pattern:** Build child parts at the origin (local coordinates),\n * then group and translate once to place the whole assembly. This eliminates the\n * error-prone pattern of manually adding parent offsets to every sub-part.\n *\n * @example\n * const body = roundedBox(100, 20, 32, 4);\n * const panel = box(98, 2, 18).translate(0, -12, 4);\n * const louver = box(88, 2, 6).translate(0, -14, -11);\n * const indoorUnit = group(\n * { name: \'Body\', shape: body },\n * { name: \'Panel\', tags: \'cover\', shape: panel },\n * { name: \'Louver\', tags: [\'cover\', \'moving\'], shape: louver },\n * ).translate(0, -18, 70);\n */\ndeclare function group(...items: GroupInput[]): ShapeGroup;\n/** Options for `edgesOfFace()`. */\ninterface EdgesOfOptions {\n /** Exclude edges shared with these named faces. */\n exclude?: string | string[];\n /** Additional geometric filter: only convex edges. */\n convex?: boolean;\n /** Additional geometric filter: only concave edges. */\n concave?: boolean;\n /** Minimum edge length filter. */\n minLength?: number;\n}\ninterface SeatIntoOptions {\n /** Movement axis. Default: inverted face normal (points into target). */\n along?: [\n number,\n number,\n number\n ];\n /** How deep to embed. \'full\' = entire face inside. \'flush\' = nearest point touches. number = mm past flush. Default: \'full\'. */\n depth?: "full" | "flush" | number;\n /** Standoff gap in mm. Positive = gap between face and target. Negative = extra penetration. Default: 0. */\n gap?: number;\n}\ntype SdfFunctionSource = string | ((x: number, y: number, z: number, ...constants: number[]) => number);\ndeclare class SurfacePattern {\n /** Function body: receives (u, v) in surface mm, returns height displacement. */\n readonly body: string;\n /** Named constants injected into the function. */\n readonly constants?: Record<string, number>;\n constructor(body: string, constants?: Record<string, number>);\n}\ntype Pattern2DInput = Pattern2D | number;\ndeclare abstract class Pattern2D extends SurfacePattern {\n protected constructor(body: string);\n /** Add this pattern to one or more patterns or constant height offsets. */\n add(...patterns: Pattern2DInput[]): Pattern2D;\n /** Subtract another pattern or constant height offset from this pattern. */\n subtract(pattern: Pattern2DInput): Pattern2D;\n /** Multiply this pattern by one or more patterns or numeric scale factors. */\n multiply(...patterns: Pattern2DInput[]): Pattern2D;\n /** Keep the lower height between this pattern and one or more other patterns. */\n min(...patterns: Pattern2DInput[]): Pattern2D;\n /** Keep the higher height between this pattern and one or more other patterns. */\n max(...patterns: Pattern2DInput[]): Pattern2D;\n /** Limit pattern height to the inclusive `[min, max]` range in millimeters. */\n clamp(min: number, max: number): Pattern2D;\n /** Convert negative heights to positive heights. */\n abs(): Pattern2D;\n /** Flip the pattern height sign. */\n negate(): Pattern2D;\n}\ninterface Pattern2DSineWaveOptions {\n /** Direction the wave advances in UV space. Default: [1, 0]. */\n direction?: Vec2;\n /** Distance between wave peaks in surface millimeters. */\n wavelength: number;\n /** Height amplitude in millimeters. Default: 1. */\n amplitude?: number;\n /** Phase offset in radians. Default: 0. */\n phase?: number;\n /** Constant height offset in millimeters. Default: 0. */\n bias?: number;\n}\ninterface Pattern2DStripesOptions {\n /** Direction perpendicular to the stripe bands in UV space. Default: [1, 0]. */\n direction?: Vec2;\n /** Center-to-center spacing in surface millimeters. */\n spacing: number;\n /** Stripe width in surface millimeters. */\n width: number;\n /** Stripe groove depth in millimeters. Default: 1. */\n depth?: number;\n}\ninterface Pattern2DOverUnderWeaveOptions {\n /** Thread center-to-center spacing. A number uses the same spacing for U and V. */\n spacing: number | Vec2;\n /** Thread width. A number uses the same width for U and V. */\n threadWidth: number | Vec2;\n /** Thread groove depth in millimeters. Default: 0.8. */\n depth?: number;\n /** Relative height of the under-crossing thread. Default: 0.15. */\n underScale?: number;\n}\ndeclare class Pattern2DBuilder {\n /** Create a constant-height pattern in millimeters. */\n constant(value?: number): Pattern2D;\n /** Create a sinusoidal wave pattern in UV space. */\n sineWave(options: Pattern2DSineWaveOptions): Pattern2D;\n /** Create recessed stripe bands in UV space. */\n stripes(options: Pattern2DStripesOptions): Pattern2D;\n /** Create an over-under woven relief pattern in UV space. */\n overUnderWeave(options: Pattern2DOverUnderWeaveOptions): Pattern2D;\n}\ndeclare function pattern2d(): Pattern2DBuilder;\ninterface SurfaceDisplaceOptions {\n /** Override auto-detected UV mode. Default: \'auto\' (detects from SDF tree). */\n uv?: "auto" | "sphere" | "cylinder" | "torus" | "triplanar";\n /** Triplanar blend sharpness — higher = crisper transitions. Default: 4. Only used in triplanar mode. */\n triplanarSharpness?: number;\n}\ninterface SdfFunctionOptions {\n /** Finite design bounds for this opaque custom field. */\n bounds: SdfBounds | [\n Vec3$1,\n Vec3$1\n ];\n /** Named finite constants referenced from the expression body. */\n constants?: SdfFunctionConstants;\n /** Conservative maximum step for shader-preview tooling. */\n maxStep?: number;\n /** Optional divisor for non-distance fields. Values above 1 slow shader tooling. */\n lipschitz?: number;\n}\ninterface SdfVisualMetadata {\n colorHex?: string;\n materialProps?: ShapeMaterialProps;\n bounds?: SdfBounds;\n}\ntype SculptMaterialPreset = "ceramic" | "porcelain" | "soft-rubber" | "glass" | "mint-glass" | "candy" | "strawberry-gel" | "metal" | "clay";\ntype SculptPolishInput = SculptMaterialPreset | (ShapeMaterialProps & {\n color?: string;\n});\ndeclare function knownSculptMaterialPresets(): SculptMaterialPreset[];\n/**\n * An immutable SDF expression. Supports SDF-specific operations (smooth booleans,\n * domain warps, etc.), can be returned directly for native preview, and converts\n * to a ForgeCAD Shape via `.toShape()` when materialization is needed.\n */\ndeclare class SdfShape {\n /** @internal */\n readonly _node: SdfNode;\n /** @internal */\n readonly _visual: SdfVisualMetadata;\n /** @internal */\n constructor(node: SdfNode, visual?: SdfVisualMetadata);\n /** Display color carried by this implicit leaf. */\n get colorHex(): string | undefined;\n /** Display material carried by this implicit leaf. */\n get materialProps(): ShapeMaterialProps | undefined;\n /** Explicit bounds carried by this implicit leaf, if any. */\n get explicitBounds(): SdfBounds | undefined;\n private withNode;\n private withVisual;\n /** Clone this SDF expression and its visual metadata. */\n clone(): SdfShape;\n /** Alias for clone(). */\n duplicate(): SdfShape;\n /**\n * Mesh this SDF into a ForgeCAD Shape. Typed SDF trees materialize through Rust\n * Manifold Dual Contouring; dynamic trees (custom/noise/voronoi/displace/blend\n * callbacks) mesh through the Surface Nets pipeline.\n * Once converted, the result is a regular Shape — booleans, transforms, export all work.\n */\n toShape(options?: SdfToShapeOptions): Shape;\n /** Set the display color for this implicit leaf. */\n color(value: string | undefined): SdfShape;\n /** Set PBR display material properties for this implicit leaf. */\n material(props: ShapeMaterialProps): SdfShape;\n /** Set explicit preview/meshing bounds for this implicit leaf. */\n bounds(bounds: SdfBounds | [\n Vec3$1,\n Vec3$1\n ]): SdfShape;\n bounds(min: Vec3$1, max: Vec3$1): SdfShape;\n /** Sculpt-style alias for translate(). */\n at(x: number, y: number, z: number): SdfShape;\n /**\n * Sculpt-style alias for translate().\n * @softDeprecated .at(x, y, z)\n * @deprecated use .at(x, y, z)\n */\n move(x: number, y: number, z: number): SdfShape;\n /** Sculpt-style alias for rotateZ(). */\n spin(angleDeg: number): SdfShape;\n /** Sculpt-style tilt around X, Y, Z, or a custom axis. */\n tilt(angleDeg: number, axis?: "x" | "y" | "z" | Vec3$1): SdfShape;\n /**\n * Round all edges of a primitive box while preserving its outer dimensions.\n * Sugar over `offset()`: the box is shrunk by `radius` on every side, then\n * dilated back out with `offset(radius)`, which rounds every edge and corner.\n *\n * For any other shape, use `.offset(radius)` directly (note it grows the part\n * by `radius`), or `Sculpt.box(x, y, z, { radius })` for a rounded box.\n */\n round(radius: number): SdfShape;\n /** Sculpt-style smooth blend with another implicit shape. */\n blend(other: SdfShape, options?: number | {\n radius?: number;\n }): SdfShape;\n /**\n * Sculpt-style alias for blend().\n * @softDeprecated .blend(other, { radius })\n * @deprecated use .blend(other, { radius })\n */\n goop(other: SdfShape, options?: number | {\n radius?: number;\n }): SdfShape;\n /** Sculpt-style smooth carve/subtract. */\n carve(other: SdfShape, options?: number | {\n radius?: number;\n }): SdfShape;\n /**\n * Sculpt-style smooth intersection/keep operation.\n * @softDeprecated .smoothIntersect(other, radius)\n * @deprecated use .smoothIntersect(other, radius)\n */\n keep(other: SdfShape, options?: number | {\n radius?: number;\n }): SdfShape;\n /** Apply a Sculpt material preset or direct material props. */\n polish(input?: SculptPolishInput): SdfShape;\n /**\n * SDF union (sharp).\n *\n * ```js\n * sdf.box(20, 20, 8).union(sdf.cylinder(16, 6), sdf.sphere(7))\n * ```\n */\n union(...others: SdfShape[]): SdfShape;\n /** SDF difference (sharp) — subtracts others from this. */\n subtract(...others: SdfShape[]): SdfShape;\n /**\n * SDF intersection (sharp). Also the canonical way to fill a body with a\n * lattice or clip an infinite field to a design space:\n *\n * ```js\n * // Lattice fill — keep only the gyroid inside the body\n * sdf.sphere(18).intersect(sdf.gyroid({ cellSize: 6, wallThickness: 0.8 }))\n *\n * // Clip an infinite field to a box-shaped design space\n * sdf.gyroid({ cellSize: 6, wallThickness: 0.8 }).intersect(sdf.box(40, 25, 16))\n * ```\n */\n intersect(...others: SdfShape[]): SdfShape;\n /**\n * Clip this SDF to an explicit box-shaped design space.\n * @softDeprecated .intersect(sdf.box(x, y, z))\n * @deprecated use .intersect(sdf.box(x, y, z))\n */\n clipBox(x: number, y: number, z: number): SdfShape;\n /**\n * Keep only the material where this shape overlaps another SDF pattern.\n * @softDeprecated .intersect(pattern)\n * @deprecated use .intersect(pattern)\n */\n fillWith(pattern: SdfShape): SdfShape;\n /**\n * Keep only the gyroid lattice inside this shape.\n * @softDeprecated .intersect(sdf.gyroid({ cellSize, wallThickness }))\n * @deprecated use .intersect(sdf.gyroid({ cellSize, wallThickness }))\n */\n fillWithGyroid(options: TpmsOptions): SdfShape;\n /**\n * Keep only the Schwarz-P lattice inside this shape.\n * @softDeprecated .intersect(sdf.schwarzP({ cellSize, wallThickness }))\n * @deprecated use .intersect(sdf.schwarzP({ cellSize, wallThickness }))\n */\n fillWithSchwarzP(options: TpmsOptions): SdfShape;\n /**\n * Keep only the diamond TPMS lattice inside this shape.\n * @softDeprecated .intersect(sdf.diamond({ cellSize, wallThickness }))\n * @deprecated use .intersect(sdf.diamond({ cellSize, wallThickness }))\n */\n fillWithDiamond(options: TpmsOptions): SdfShape;\n /**\n * Keep only the lidinoid TPMS lattice inside this shape.\n * @softDeprecated .intersect(sdf.lidinoid({ cellSize, wallThickness }))\n * @deprecated use .intersect(sdf.lidinoid({ cellSize, wallThickness }))\n */\n fillWithLidinoid(options: TpmsOptions): SdfShape;\n /** Smooth union — blends shapes together with a smooth radius. */\n smoothUnion(other: SdfShape, radius: number): SdfShape;\n /** Smooth difference — smoothly carves other from this. */\n smoothSubtract(other: SdfShape, radius: number): SdfShape;\n /** Smooth intersection — smoothly intersects. */\n smoothIntersect(other: SdfShape, radius: number): SdfShape;\n /** Morph between this shape and another. t=0 → this, t=1 → other. */\n morph(other: SdfShape, t: number): SdfShape;\n /** Translate this SDF by the given offsets in millimeters. */\n translate(x: number, y: number, z: number): SdfShape;\n /** Rotate around an arbitrary axis through the origin. */\n rotate(axis: [\n number,\n number,\n number\n ], angleDeg: number): SdfShape;\n /** Rotate around the X axis by the given angle in degrees. */\n rotateX(angleDeg: number): SdfShape;\n /** Rotate around the Y axis by the given angle in degrees. */\n rotateY(angleDeg: number): SdfShape;\n /** Rotate around the Z axis by the given angle in degrees. */\n rotateZ(angleDeg: number): SdfShape;\n /** Uniformly scale this SDF around the origin. */\n scale(factor: number): SdfShape;\n /** Twist around the Z axis. */\n twist(degreesPerUnit: number): SdfShape;\n /** Bend around the Z axis with given radius. */\n bend(radius: number): SdfShape;\n /** Repeat in space. Spacing of 0 on an axis means no repetition. Count of 0 = infinite. */\n repeat(spacing: Vec3$1, count?: Vec3$1): SdfShape;\n /**\n * Arrange this SDF in a circular array around the Z axis.\n *\n * The source shape is translated by `offset` in +X before arraying. This uses\n * angular domain folding, so evaluation stays O(1): the source SDF is sampled\n * twice no matter how many copies are requested.\n */\n circularArray(count: number, offset?: number): SdfShape;\n /** Hollow out, keeping only a shell of given thickness. */\n shell(thickness: number): SdfShape;\n /**\n * Offset the distance field by a constant amount in millimeters.\n *\n * Positive `distance` dilates: every surface moves outward by `distance`,\n * which rounds convex edges and corners — and grows the part by `distance`.\n * Negative `distance` erodes: surfaces move inward, shrinking the part and\n * thinning walls. This is the canonical SDF field offset (`d − distance`)\n * and works on ANY implicit shape — sharp booleans, TPMS lattices,\n * `sdf.fromFunction()` fields — not just primitives.\n *\n * Like `shell()`, the result is approximate on fields that are not exact\n * distance fields (for example after `twist()`, `bend()`, or smooth booleans).\n *\n * ```js\n * // Round every edge of a sharp union by 2mm (grows the part by 2mm)\n * sdf.box(20, 20, 8).union(sdf.cylinder(16, 6)).offset(2)\n *\n * // Erode a lattice by 0.2mm to compensate printed over-extrusion\n * sdf.sphere(18).intersect(sdf.gyroid({ cellSize: 6, wallThickness: 1.2 })).offset(-0.2)\n * ```\n */\n offset(distance: number): SdfShape;\n /**\n * Displace the surface by a function of position, or by a pattern SdfShape.\n *\n * ```js\n * // Function displacement\n * shape.displace((x, y, z) => Math.sin(x) * 0.5)\n *\n * // Pattern displacement from a 3D SDF field\n * shape.displace(sdf.knurl({ pitch: 2, depth: 0.3 }))\n * ```\n */\n displace(fn: ((x: number, y: number, z: number) => number) | SdfShape, constants?: Record<string, number>): SdfShape;\n /**\n * Displace the surface using a 2D pattern in surface-local UV coordinates.\n *\n * Automatically detects the shape\'s UV parametrization (sphere, cylinder, torus)\n * from the SDF tree. Falls back to triplanar mapping for arbitrary shapes.\n *\n * UV coordinates are in **surface millimeters** — patterns defined with `spacing: 3`\n * always produce 3mm spacing, regardless of shape size.\n *\n * Prefer `sdf.pattern2d()` or built-in surface patterns when the relief should\n * stay on the native shader and meshing path. Callback functions are supported\n * for experimentation, but they are opaque to the typed pattern optimizer.\n *\n * ```js\n * // Native typed pattern — auto-detects sphere UV\n * const p = sdf.pattern2d()\n * const ribs = p.stripes({ spacing: 3, width: 0.8, depth: 0.35 })\n * .add(p.sineWave({ direction: [0, 1], wavelength: 14, amplitude: 0.08 }))\n *\n * sdf.sphere(27).shell(3)\n * .surfaceDisplace(ribs)\n * .toShape()\n *\n * // Custom 2D pattern via function\n * shape.surfaceDisplace((u, v) => -Math.sin(u * 2) * 0.3)\n * ```\n */\n surfaceDisplace(pattern: SurfacePattern | ((u: number, v: number) => number), options?: SurfaceDisplaceOptions): SdfShape;\n /** Create concentric onion layers. */\n onion(layers: number, thickness: number): SdfShape;\n}\ndeclare function sphere(radius: number): SdfShape;\ndeclare function box(x: number, y: number, z: number): SdfShape;\ndeclare function cylinder(height: number, radius: number): SdfShape;\ndeclare function torus(majorRadius: number, minorRadius: number): SdfShape;\ndeclare function capsule(height: number, radius: number): SdfShape;\ndeclare function cone(height: number, radius: number): SdfShape;\ndeclare function smoothUnion(a: SdfShape, b: SdfShape, options: {\n radius: number;\n}): SdfShape;\ndeclare function smoothDifference(a: SdfShape, b: SdfShape, options: {\n radius: number;\n}): SdfShape;\ndeclare function smoothIntersection(a: SdfShape, b: SdfShape, options: {\n radius: number;\n}): SdfShape;\ndeclare function morph(a: SdfShape, b: SdfShape, t: number): SdfShape;\ninterface BlendOptions {\n /** Optional named constants accessible in the blend function. */\n constants?: Record<string, number>;\n}\ndeclare function blend(a: SdfShape, b: SdfShape, fn: (x: number, y: number, z: number) => number, options?: BlendOptions): SdfShape;\ninterface TpmsOptions {\n cellSize: number;\n /**\n * Dimensionless field threshold kept for compatibility.\n * Prefer `wallThickness` for approximate millimeter units.\n */\n thickness?: number;\n /** Approximate physical wall thickness in millimeters. */\n wallThickness?: number;\n /** Override TPMS thickness interpretation. Defaults to metric when `wallThickness` is used, field-threshold when `thickness` is used. */\n tpmsThicknessMode?: TpmsThicknessMode;\n}\ninterface TpmsBlockOptions extends TpmsOptions {\n type?: "gyroid" | "schwarzP" | "diamond" | "lidinoid";\n size: Vec3$1;\n}\ndeclare function gyroid(options: TpmsOptions): SdfShape;\ndeclare function schwarzP(options: TpmsOptions): SdfShape;\ndeclare function diamond(options: TpmsOptions): SdfShape;\ndeclare function lidinoid(options: TpmsOptions): SdfShape;\ndeclare function withinBox(shape: SdfShape, options: {\n size: Vec3$1;\n}): SdfShape;\ndeclare function tpmsBlock(options: TpmsBlockOptions): SdfShape;\ninterface NoiseOptions {\n /** Spatial frequency — smaller = larger features. Default: 0.1 */\n scale?: number;\n /** Peak displacement amplitude. Default: 1 */\n amplitude?: number;\n /** fBm octaves (1 = plain simplex, higher = more detail). Default: 1 */\n octaves?: number;\n /** Seed for deterministic variation. Default: 0 */\n seed?: number;\n}\ndeclare function noise(options?: NoiseOptions): SdfShape;\ninterface VoronoiOptions {\n /** Size of each Voronoi cell in world units. Default: 10 */\n cellSize?: number;\n /** Wall thickness between cells. Default: 1 */\n wallThickness?: number;\n /** Seed for deterministic variation. Default: 0 */\n seed?: number;\n /**\n * Projection weight for membrane suppression (0..1). Controls how much of\n * the surface-normal distance component is removed from Voronoi cell distances.\n * 0 = no projection (classic 3D voronoi with membranes).\n * 1 = full tangent-plane projection (pure 2D pattern on surface).\n * Default: 0.85. Only active when voronoi is intersected with another shape.\n */\n suppressionThreshold?: number;\n}\ndeclare function voronoi(options?: VoronoiOptions): SdfShape;\ninterface HoneycombOptions {\n /** Size of each hex cell. Default: 8 */\n cellSize?: number;\n /** Wall thickness. Default: 1 */\n wallThickness?: number;\n}\ndeclare function honeycomb(options?: HoneycombOptions): SdfShape;\ninterface WavesOptions {\n /** Distance between wave peaks. Default: 10 */\n wavelength?: number;\n /** Height of waves. Default: 1 */\n amplitude?: number;\n /** Axis along which waves propagate: \'x\', \'y\', or \'z\'. Default: \'x\' */\n axis?: "x" | "y" | "z";\n}\ndeclare function waves(options?: WavesOptions): SdfShape;\ninterface KnurlOptions {\n /** Distance between knurl ridges. Default: 3 */\n pitch?: number;\n /** Depth of knurl grooves. Default: 0.5 */\n depth?: number;\n /** Helix angle in degrees. Default: 30 */\n angle?: number;\n}\ndeclare function knurl(options?: KnurlOptions): SdfShape;\ninterface PerforatedOptions {\n /** Hole radius. Default: 3 */\n radius?: number;\n /** Center-to-center spacing. Default: 8 */\n spacing?: number;\n}\ndeclare function perforated(options?: PerforatedOptions): SdfShape;\ninterface ScalesOptions {\n /** Scale diameter. Default: 5 */\n size?: number;\n /** How much scales protrude. Default: 0.8 */\n depth?: number;\n}\ndeclare function scales(options?: ScalesOptions): SdfShape;\ninterface BrickOptions {\n /** Brick width. Default: 10 */\n width?: number;\n /** Brick height. Default: 5 */\n height?: number;\n /** Mortar groove depth. Default: 0.5 */\n depth?: number;\n /** Mortar gap width. Default: 1 */\n mortar?: number;\n}\ndeclare function brick(options?: BrickOptions): SdfShape;\ninterface WeaveOptions {\n /** Thread center-to-center spacing (for intersection patterns). Default: 5 */\n spacing?: number;\n /** Thread half-width. Default: 1 */\n threadRadius?: number;\n}\ndeclare function weave(options?: WeaveOptions): SdfShape;\ninterface BasketWeaveOptions {\n /** Spacing between threads in mm (both directions). Default: 3 */\n spacing?: number;\n /** Thread width in mm. Default: 1.5 */\n threadWidth?: number;\n /** Thread protrusion depth in mm. Default: 0.8 */\n depth?: number;\n}\ndeclare function basketWeave(options?: BasketWeaveOptions): SurfacePattern;\ndeclare function fromFunction(fn: SdfFunctionSource, options: SdfFunctionOptions): SdfShape;\ndeclare function twist(shape: SdfShape, degreesPerUnit: number): SdfShape;\ndeclare function bend(shape: SdfShape, radius: number): SdfShape;\ndeclare function repeat(shape: SdfShape, spacing: Vec3$1, count?: Vec3$1): SdfShape;\ndeclare function circularArray(shape: SdfShape, count: number, offset?: number): SdfShape;\ntype ToShapeTreeResult = Shape | ShapeGroup;\ninterface CombineOptions {\n op?: "union" | "intersection";\n}\n/**\n * Materialize one SDF leaf or all SDF leaves in a renderable tree.\n *\n * Raw `SdfShape` values become mesh-backed `Shape`s. Plain objects and arrays\n * preserve their renderable children as a `ShapeGroup` when more than one leaf\n * is found. Non-renderable metadata is ignored for materialization and remains\n * available to callers through normal `require()` return values.\n */\ndeclare function toShape(value: unknown, options?: SdfToShapeOptions): ToShapeTreeResult;\n/**\n * Collapse a tree of SDF leaves into one continuous SDF field.\n *\n * This intentionally discards per-leaf color/material identity because the\n * result is one scalar field. Use plain object returns for multi-material SDF\n * preview, and use `sdf.combine(...)` only when you want one implicit body.\n *\n * The canonical spelling is the namespaced `sdf.combine(value, { op })`; the\n * bare `combine` runtime global is kept for compatibility only.\n *\n * @softDeprecated sdf.combine(value, { op })\n * @deprecated use sdf.combine(value, { op })\n */\ndeclare function combine(value: unknown, options?: CombineOptions): SdfShape;\ninterface JointOverlayViewConfigOptions {\n enabled?: boolean;\n axisColor?: string;\n axisCoreColor?: string;\n arcColor?: string;\n zeroColor?: string;\n arcVisualLimitDeg?: number;\n axisLengthScale?: number;\n axisLengthMin?: number;\n axisLineRadiusScale?: number;\n axisLineRadiusMin?: number;\n axisLineRadiusMax?: number;\n spokeLineRadiusScale?: number;\n spokeLineRadiusMin?: number;\n spokeLineRadiusMax?: number;\n arcLineRadiusScale?: number;\n arcLineRadiusMin?: number;\n arcLineRadiusMax?: number;\n axisDotRadiusScale?: number;\n axisDotRadiusMin?: number;\n axisArrowRadiusScale?: number;\n axisArrowRadiusMin?: number;\n axisArrowLengthScale?: number;\n axisArrowLengthMin?: number;\n axisArrowOffsetFactor?: number;\n arcRadiusScale?: number;\n arcRadiusMin?: number;\n arcDotRadiusScale?: number;\n arcDotRadiusMin?: number;\n arcArrowRadiusScale?: number;\n arcArrowRadiusMin?: number;\n arcArrowLengthScale?: number;\n arcArrowLengthMin?: number;\n arcArrowOffsetFactor?: number;\n arcStepDeg?: number;\n arcMinSteps?: number;\n arcTubeSegmentsMin?: number;\n arcTubeSegmentsFactor?: number;\n arcTubeRadialSegments?: number;\n}\ninterface ViewConfigOptions {\n jointOverlay?: JointOverlayViewConfigOptions;\n}\n/**\n * Configure viewport helper visuals for the current script execution.\n *\n * **Details**\n *\n * Controls renderer-only overlays that appear in the viewport but are not part of the geometry.\n * Currently supports the joint overlay that renders axis arrows and arc indicators when\n * joint controls are active. Multiple calls merge — later values override earlier ones per key.\n *\n * This does **not** trigger a geometry recompute; it only affects the visual helpers drawn on\n * top of the 3D scene.\n *\n * **Example**\n *\n * ```js\n * viewConfig({\n * jointOverlay: {\n * axisColor: \'#13dfff\',\n * arcColor: \'#ff7a1a\',\n * axisLineRadiusScale: 0.03,\n * arcLineRadiusScale: 0.022,\n * },\n * });\n * ```\n *\n * @param options.jointOverlay - Joint overlay appearance. Supports `enabled`, `axisColor`, `axisCoreColor`, `arcColor`, `zeroColor`, `axisLengthScale`, `arcVisualLimitDeg`, `arcStepDeg`, and detailed radius/arrow sizing properties\n * @returns void\n * @softDeprecated scene({ jointOverlay: { ... } }) — same keys, on the one renderer-config global\n * @deprecated use scene({ jointOverlay: { ... } }) — same keys, on the one renderer-config global\n * @category Scene Configuration\n */\ndeclare function viewConfig(options?: ViewConfigOptions): void;\ninterface SceneCameraConfig {\n position?: [\n number,\n number,\n number\n ];\n target?: [\n number,\n number,\n number\n ];\n up?: [\n number,\n number,\n number\n ];\n fov?: number;\n type?: "perspective" | "orthographic";\n}\ntype SceneViewCameraConfig = SceneCameraConfig & {\n position: [\n number,\n number,\n number\n ];\n target: [\n number,\n number,\n number\n ];\n};\ninterface SceneViewConfig {\n /** Explicit camera state for this named render view. */\n camera: SceneViewCameraConfig;\n}\ntype SceneViewInputConfig = SceneViewConfig | SceneViewCameraConfig;\ntype SceneViewMap = Record<string, SceneViewConfig>;\ntype SceneJourneyDiagnosticLevel = "error" | "warning";\ninterface SceneJourneyDiagnostic {\n level: SceneJourneyDiagnosticLevel;\n message: string;\n stepId?: string;\n suggestions?: string[];\n}\ninterface SceneJourneyStepConfig {\n /** Stable step id used by viewer links and Next/Back state. */\n id: string;\n /** Viewer-facing title. Defaults to the step id. */\n title?: string;\n /** Object name or slash-separated tree path to focus. */\n focus?: string;\n /** Short optional viewer caption. */\n caption?: string;\n /** Optional explicit camera for this step. When omitted, the viewer fits `focus`. */\n camera?: SceneViewCameraConfig;\n /** Resolved object id after script execution, when `focus` matched exactly one object. */\n resolvedFocusId?: string | null;\n /** Resolved object tree path or name after script execution. */\n resolvedFocusPath?: string | null;\n /** Resolution diagnostics for this step. */\n diagnostics?: SceneJourneyDiagnostic[];\n}\ninterface SceneJourneyConfig {\n /** Viewer-facing journey title. Defaults to the journey id. */\n title?: string;\n /** Optional starting step id. Defaults to the first step. */\n startsAt?: string;\n /** Whether the viewer should offer or auto-open the journey. First slice supports opt-in. */\n behavior?: "opt-in" | "auto";\n /** Ordered journey spine. Branches can be added later without changing this core contract. */\n steps: SceneJourneyStepConfig[];\n /** True unless any journey or step diagnostic has level "error". */\n valid?: boolean;\n /** Whole-journey diagnostics, including unresolved startsAt and step target diagnostics. */\n diagnostics?: SceneJourneyDiagnostic[];\n}\ntype SceneJourneyMap = Record<string, SceneJourneyConfig>;\ntype SceneLightType = "ambient" | "directional" | "point" | "spot" | "hemisphere";\ninterface SceneLightConfig {\n type: SceneLightType;\n color?: string;\n intensity?: number;\n position?: [\n number,\n number,\n number\n ];\n /** Target for directional/spot lights */\n target?: [\n number,\n number,\n number\n ];\n /** Ground color for hemisphere lights */\n groundColor?: string;\n /** Sky color alias for hemisphere lights (same as color) */\n skyColor?: string;\n /** Spot light cone angle in radians */\n angle?: number;\n /** Spot light penumbra (0–1) */\n penumbra?: number;\n /** Point/spot light decay */\n decay?: number;\n /** Point/spot light distance (0 = infinite) */\n distance?: number;\n /** Whether this light casts shadows */\n castShadow?: boolean;\n}\ninterface SceneEnvironmentConfig {\n /** Built-in preset name or \'none\' to disable */\n preset?: "studio" | "sunset" | "dawn" | "warehouse" | "forest" | "apartment" | "lobby" | "city" | "park" | "night" | "none";\n /** Environment map intensity */\n intensity?: number;\n /** Use environment map as scene background */\n background?: boolean;\n}\ninterface SceneBackgroundGradient {\n top: string;\n bottom: string;\n}\ninterface SceneFogConfig {\n color?: string;\n /** Linear fog near distance */\n near?: number;\n /** Linear fog far distance */\n far?: number;\n /** Exponential fog density (if set, uses FogExp2 instead of linear Fog) */\n density?: number;\n}\ninterface SceneBloomConfig {\n intensity?: number;\n threshold?: number;\n radius?: number;\n}\ninterface SceneVignetteConfig {\n darkness?: number;\n offset?: number;\n}\ninterface SceneGrainConfig {\n intensity?: number;\n}\ninterface ScenePostProcessingConfig {\n bloom?: SceneBloomConfig;\n vignette?: SceneVignetteConfig;\n grain?: SceneGrainConfig;\n toneMappingExposure?: number;\n}\ninterface SceneGroundConfig {\n /** Show a ground plane */\n visible?: boolean;\n /** Ground color */\n color?: string;\n /** Offset below the model\'s bounding box minimum Z. Default 0 (flush with model bottom). */\n offset?: number;\n /** Receive shadows on the ground */\n receiveShadow?: boolean;\n}\ninterface SceneCaptureConfig {\n /** Frames for one full orbit rotation (default: 72) */\n framesPerTurn?: number;\n /** Frozen frames before motion starts (default: 6) */\n holdFrames?: number;\n /** Orbit pitch angle in degrees (default: auto from camera) */\n pitchDeg?: number;\n /** Output frame rate (default: 24) */\n fps?: number;\n /** Output frame size in pixels (default: 960) */\n size?: number;\n /** Canvas background color for capture (default: \'#252526\') */\n background?: string;\n}\ninterface SceneOptions {\n background?: string | SceneBackgroundGradient;\n camera?: SceneCameraConfig;\n views?: Record<string, SceneViewInputConfig>;\n journeys?: Record<string, SceneJourneyConfig>;\n lights?: SceneLightConfig[];\n environment?: SceneEnvironmentConfig;\n fog?: SceneFogConfig;\n postProcessing?: ScenePostProcessingConfig;\n ground?: SceneGroundConfig;\n /** Default capture parameters for `forgecad capture` — CLI flags override these. */\n capture?: SceneCaptureConfig;\n /**\n * Joint-overlay helper visuals (axis arrows and arc indicators shown when\n * joint controls are active). Renderer-only — no geometry recompute.\n */\n jointOverlay?: JointOverlayViewConfigOptions;\n}\n/**\n * Configure the scene environment for the current script execution.\n *\n * **Details**\n *\n * Controls camera, named render views, guided journeys, lighting, background, fog, environment\n * maps, post-processing, capture defaults, and the joint-control helper overlay\n * (`jointOverlay` — axis arrows and arc indicators, renderer-only). Multiple `scene()` calls\n * merge per-key — later values win — so configuration can be split across calls.\n *\n * Two behavioral cliffs:\n * - Specifying `lights` removes **all** default lights. Include your own ambient light or the\n * scene is fully dark.\n * - Setting `camera.position` disables auto-framing — the viewport no longer auto-fits the\n * geometry on script reload.\n *\n * Named views are repeatable cameras checked in with the model code. Canonical shape is\n * `{ camera: { position, target } }`; a direct `{ position, target }` shorthand is also\n * accepted. Render one with `--view <name>` (see CLI docs).\n *\n * Journeys are ordered `steps`, each focusing a returned object by name/tree path with an\n * optional caption and camera. In the viewer they are opt-in (an Explore control; the camera\n * does not move until started). Inspect resolved targets with `forgecad run --journeys`.\n *\n * Post-processing (`bloom`, `vignette`, `grain`) is browser-only; the CLI applies camera,\n * lights, background, fog, and `toneMappingExposure` but skips shader effects.\n *\n * All numeric values accept `param()` expressions.\n *\n * **Example**\n *\n * ```js\n * scene({\n * background: { top: \'#000814\', bottom: \'#001d3d\' },\n * camera: { position: [160, -120, 100], target: [0, 0, 50], fov: 52 },\n * views: {\n * hero: { camera: { position: [180, -140, 90], target: [0, 0, 25], fov: 38 } },\n * },\n * journeys: {\n * tour: { steps: [{ id: \'earth\', focus: \'Earth\', caption: \'Fit and inspect Earth.\' }] },\n * },\n * lights: [\n * { type: \'ambient\', color: \'#001233\', intensity: 0.08 },\n * { type: \'directional\', position: [50, -30, 200], color: \'#ffd60a\', intensity: 1.2 },\n * ],\n * fog: { color: \'#000814\', near: 100, far: 450 },\n * postProcessing: { bloom: { intensity: param(\'bloom\', 1.5, 0, 4) } },\n * jointOverlay: { axisColor: \'#13dfff\', arcColor: \'#ff7a1a\' },\n * });\n * ```\n *\n * @param options.background - Solid color hex string or `{ top, bottom }` vertical gradient\n * @param options.camera - Camera override: `position` `[x,y,z]`, `target` `[x,y,z]`, `fov` (degrees 1–179), `up` `[x,y,z]`, `type` (`\'perspective\' | \'orthographic\'`)\n * @param options.views - Named render cameras for `forgecad render 3d --view <name>`. Each view is either `{ camera }` or a direct camera shorthand with required `position` and `target`, optional `up`, `fov`, and `type`\n * @param options.journeys - Guided viewer journeys keyed by id. Each journey has ordered `steps`; each step can use `focus` to target a returned object by name/tree path, plus optional `title`, `caption`, and explicit `camera`\n * @param options.lights - Full light array (replaces all defaults). Each entry: `type` (`\'ambient\' | \'directional\' | \'point\' | \'spot\' | \'hemisphere\'`), `color`, `intensity`, `position`, `target`, `castShadow`. Hemisphere accepts `skyColor`/`groundColor`. Spot/point accept `angle`, `penumbra`, `decay`, `distance`\n * @param options.environment - Built-in HDRI preset: `preset` (`\'studio\' | \'sunset\' | \'dawn\' | \'warehouse\' | \'forest\' | \'apartment\' | \'lobby\' | \'city\' | \'park\' | \'night\' | \'none\'`), `intensity`, `background`\n * @param options.fog - Atmospheric fog: `color`, `near`/`far` (linear) or `density` (exponential FogExp2)\n * @param options.postProcessing - Shader effects: `bloom` `{ intensity?, threshold?, radius? }`, `vignette` `{ darkness?, offset? }`, `grain` `{ intensity? }`, `toneMappingExposure`\n * @param options.ground - Ground plane: `visible`, `color`, `offset` (below model bottom), `receiveShadow`\n * @param options.capture - Capture defaults for `forgecad capture`: `framesPerTurn` (12–720, default 72), `holdFrames` (0–300, default 6), `pitchDeg` (-80–80), `fps` (1–60, default 24), `size` (pixels, default 960), `background`\n * @param options.jointOverlay - Joint-control helper overlay (renderer-only): `enabled`, `axisColor`, `axisCoreColor`, `arcColor`, `zeroColor`, `axisLengthScale`, `arcVisualLimitDeg`, `arcStepDeg`, and detailed radius/arrow sizing properties\n * @returns void\n * @category Scene Configuration\n */\ndeclare function scene(options: SceneOptions): void;\ntype SketchFaceTarget = SketchFace3D | string | FaceRef;\ninterface ShapeFeatureExtentSideOptions {\n depth?: number;\n upToFace?: SketchFaceTarget | FaceRef;\n through?: boolean;\n}\ninterface ShapeFeatureExtentOptions {\n forward: ShapeFeatureExtentSideOptions;\n reverse?: ShapeFeatureExtentSideOptions;\n}\ninterface ShapeHoleThreadOptions {\n designation?: string;\n pitch?: number;\n class?: string;\n handedness?: "right" | "left";\n depth?: number;\n modeled?: boolean;\n}\ninterface ShapeHoleOptions {\n diameter: number;\n depth?: number;\n upToFace?: SketchFaceTarget | FaceRef;\n extent?: ShapeFeatureExtentOptions;\n u?: number;\n v?: number;\n counterbore?: {\n diameter: number;\n depth: number;\n };\n countersink?: {\n diameter: number;\n angleDeg?: number;\n };\n thread?: ShapeHoleThreadOptions;\n}\ninterface ShapeCutoutOptions {\n depth?: number;\n upToFace?: SketchFaceTarget | FaceRef;\n extent?: ShapeFeatureExtentOptions;\n taperScale?: number | [\n number,\n number\n ];\n}\ninterface Shape {\n /**\n * Drill a hole into this solid at a face.\n *\n * @param faceOrRef Target face — a face selector string (e.g. `\'top\'`), a placed sketch, or a `FaceRef`.\n * @param opts Hole parameters: `diameter` (required), optional `depth`, `counterbore`, `countersink`, `thread`, `u`/`v` offset, `upToFace`, `extent`.\n *\n * @example\n * box(50, 50, 20).hole(\'top\', { diameter: 8, depth: 10 })\n * box(50, 50, 20).hole(\'top\', { diameter: 6, counterbore: { diameter: 12, depth: 3 } })\n */\n hole(faceOrRef: SketchFaceTarget | FaceRef, opts: ShapeHoleOptions): Shape;\n /**\n * Cut a profile-shaped pocket through a face using a placed sketch.\n *\n * The sketch must be placed on a face with `Sketch.onFace(...)`. The cut follows the sketch\'s 2D profile.\n *\n * @param sketch Sketch placed on a face via `.onFace()`.\n * @param opts Optional `depth`, `upToFace`, `extent`, `taperScale`.\n *\n * @example\n * const profile = circle2d(10).onFace(body, \'top\');\n * body.cutout(profile, { depth: 5 })\n */\n cutout(sketch: Sketch, opts?: ShapeCutoutOptions): Shape;\n}\ninterface PocketOptions {\n /**\n * Shrink the face boundary inward by this many mm before extruding.\n * Produces angled walls when combined with depth. Default: 0 (full face).\n */\n inset?: number;\n /**\n * Scale the face profile uniformly (e.g. 0.8 = 80% of the face area).\n * Mutually exclusive with `inset`; `inset` takes precedence if both are set.\n */\n scale?: number;\n /** Corner join style when using `inset`. Default: \'Round\'. */\n join?: "Square" | "Round" | "Miter";\n}\ntype BossOptions = PocketOptions;\ninterface Shape {\n /**\n * Cut a pocket (cavity) into this solid through the named face.\n *\n * @param face Which face to cut into — e.g. `\'top\'`, `\'front\'`, or a face query.\n * @param depth How deep the pocket goes into the solid (mm).\n * @param opts Optional `inset` (shrink boundary, mm), `scale` (uniform scale, e.g. 0.8), `join` (`\'Round\'`/`\'Square\'`/`\'Miter\'`).\n *\n * @example\n * box(100, 100, 20).pocket(\'top\', 8)\n * box(100, 100, 20).pocket(\'top\', 8, { inset: 5 })\n * box(100, 100, 20).pocket(\'top\', 8, { scale: 0.8 })\n */\n pocket(face: FaceSelector, depth: number, opts?: PocketOptions): Shape;\n /**\n * Add a boss (protrusion) from the named face.\n *\n * @param face Which face to protrude from — e.g. `\'top\'`, `\'front\'`, or a face query.\n * @param height Height of the protrusion above the face (mm).\n * @param opts Optional `inset` (shrink boundary, mm), `scale` (uniform scale, e.g. 0.8), `join` (`\'Round\'`/`\'Square\'`/`\'Miter\'`).\n *\n * @example\n * box(100, 100, 20).boss(\'top\', 5)\n * box(100, 100, 20).boss(\'top\', 10, { scale: 0.6 })\n */\n boss(face: FaceSelector, height: number, opts?: BossOptions): Shape;\n}\ntype Vec3$2 = [\n number,\n number,\n number\n];\ntype SlicePlane = "xy" | "xz" | "yz" | Vec3$2;\ninterface FromSlicesSlice {\n /** Plane normal: axis name or arbitrary unit vector. */\n on: SlicePlane;\n /** Signed offset along the normal from the origin. */\n at: number;\n /** 2D cross-section profile on that plane. */\n profile: Sketch;\n}\ninterface FromSlicesOptions {\n /** Marching-grid edge length for level-set meshing (Manifold only). */\n edgeLength?: number;\n /** Extra bounding-box padding (Manifold only). */\n boundsPadding?: number;\n}\nexport namespace Shape {\n /**\n * Construct a 3D shape from cross-section slices on one or more planes.\n *\n * Slices with the same normal direction are lofted together. Slices with\n * different normals are combined via smooth radial blending — each\n * silhouette constrains the shape\'s extent, producing smooth ellipsoidal\n * cross-sections.\n *\n * @example\n * ```js\n * // Egg from two orthogonal silhouettes\n * const eggProfile = ellipse(15, 25);\n * return Shape.fromSlices([\n * { on: \'xz\', at: 0, profile: eggProfile },\n * { on: \'yz\', at: 0, profile: eggProfile },\n * ]);\n * ```\n *\n * @example\n * ```js\n * // Vase with cross-section transitions\n * return Shape.fromSlices([\n * { on: \'xy\', at: 0, profile: circle2d(20) },\n * { on: \'xy\', at: 40, profile: rect(25, 25) },\n * { on: \'xy\', at: 80, profile: circle2d(8) },\n * { on: \'xz\', at: 0, profile: vaseOutline },\n * ]);\n * ```\n */\n function fromSlices(slices: FromSlicesSlice[], options?: FromSlicesOptions): Shape;\n}\ntype ExplodeAxis = "x" | "y" | "z";\ntype ExplodeDirection = "radial" | ExplodeAxis | [\n number,\n number,\n number\n];\ninterface ExplodeDirective {\n /** Multiplier applied to `amount` for this node */\n stage?: number;\n /** Direction mode for this node */\n direction?: ExplodeDirection;\n /** Optional axis lock after direction is resolved */\n axisLock?: ExplodeAxis;\n}\ninterface ExplodeConfigOptions {\n amount?: number;\n stages?: number[];\n mode?: ExplodeDirection;\n axisLock?: ExplodeAxis;\n byName?: Record<string, ExplodeDirective>;\n byPath?: Record<string, ExplodeDirective>;\n}\ntype ExplodeViewDirection = ExplodeDirection;\ninterface ExplodeViewDirective extends ExplodeDirective {\n}\ninterface ExplodeViewOptions {\n /** Set false to disable viewport explode offsets for this script output. */\n enabled?: boolean;\n /** Scales the UI explode amount. Default: 1 */\n amountScale?: number;\n /**\n * Per-depth stage multipliers (depth 1 = first level).\n * If depth exceeds this array, the last value is reused.\n * Default when omitted: reciprocal depth (1, 1/2, 1/3, ...)\n */\n stages?: number[];\n /** Global direction mode fallback. Default: \'radial\' */\n mode?: ExplodeViewDirection;\n /** Global axis lock fallback. */\n axisLock?: ExplodeAxis;\n /** Per-object overrides by final object name. */\n byName?: Record<string, ExplodeViewDirective>;\n /** Per-tree-path overrides using slash-separated object tree segments. */\n byPath?: Record<string, ExplodeViewDirective>;\n}\n/**\n * Configure how the viewport explode slider offsets returned objects.\n *\n * **Details**\n *\n * Offsets are resolved from the returned object tree, not a flat list. In `radial` mode each\n * node follows its parent branch direction, then fans locally from the immediate parent\n * center — nested assemblies peel apart level by level. In fixed-axis or fixed-vector modes,\n * the branch follows that axis/vector but nested descendants fan out perpendicular by default.\n *\n * Multiple calls merge — later values override earlier ones on a per-key basis. `byName` and\n * `byPath` maps are merged entry-by-entry.\n *\n * For programmatic explode applied before returning (without the slider), use `lib.explode()`\n * instead.\n *\n * **Example**\n *\n * ```js\n * explodeView({\n * amountScale: 1.2,\n * stages: [0.35, 0.8],\n * mode: \'radial\',\n * byPath: { \'Drive/Shaft\': { direction: [1, 0, 0], stage: 1.6 } },\n * });\n * ```\n *\n * @param options.enabled - Set `false` to disable viewport explode offsets for this script\n * @param options.amountScale - Multiplier applied to the UI slider amount. Default: 1\n * @param options.stages - Per-depth stage multipliers (depth 1 = first level). If depth exceeds the array, the last value is reused. Default: reciprocal depth (`1, 1/2, 1/3, ...`)\n * @param options.mode - Global direction mode: `\'radial\'` | `\'x\'` | `\'y\'` | `\'z\'` | `[x, y, z]`. Default: `\'radial\'`\n * @param options.axisLock - Constrain motion to a single world axis: `\'x\'` | `\'y\'` | `\'z\'`\n * @param options.byName - Per-object overrides keyed by final returned object name: `{ stage?, direction?, axisLock? }`\n * @param options.byPath - Per-tree-path overrides using slash-separated path segments (e.g. `\'Drive/Shaft\'`): `{ stage?, direction?, axisLock? }`\n * @returns void\n * @see {@link explode} for applying explode offsets programmatically before returning geometry\n * @category Explode View\n */\ndeclare function explodeView(options?: ExplodeViewOptions): void;\ntype JointViewType = "revolute" | "prismatic";\ntype JointViewAxis = [\n number,\n number,\n number\n];\ninterface JointViewInput {\n name: string;\n child: string;\n parent?: string;\n type?: JointViewType;\n axis?: JointViewAxis;\n pivot?: [\n number,\n number,\n number\n ];\n min?: number;\n max?: number;\n default?: number;\n unit?: string;\n hidden?: boolean;\n}\ninterface JointViewAnimationKeyframeInput {\n /** Timeline position [0, 1]. If omitted from ALL keyframes, positions are auto-computed from tick weights. */\n at?: number;\n /** Relative weight of the segment from this keyframe to the next (default 1). Only used in tick-based mode (when `at` is omitted). Last keyframe\'s ticks value is ignored. */\n ticks?: number;\n values: Record<string, number>;\n}\ninterface JointViewAnimationInput {\n name: string;\n duration?: number;\n loop?: boolean;\n continuous?: boolean;\n keyframes: JointViewAnimationKeyframeInput[];\n}\ninterface JointViewCouplingTermInput {\n joint: string;\n ratio?: number;\n}\ninterface JointViewCouplingInput {\n joint: string;\n terms: JointViewCouplingTermInput[];\n offset?: number;\n}\ninterface JointViewDef {\n name: string;\n child: string;\n parent?: string;\n type: JointViewType;\n axis: JointViewAxis;\n pivot: [\n number,\n number,\n number\n ];\n min?: number;\n max?: number;\n defaultValue: number;\n unit?: string;\n hidden?: boolean;\n}\ninterface JointViewAnimationKeyframeDef {\n at: number;\n values: Record<string, number>;\n}\ninterface JointViewAnimationDef {\n name: string;\n duration: number;\n loop: boolean;\n continuous: boolean;\n keyframes: JointViewAnimationKeyframeDef[];\n}\ninterface JointViewCouplingTermDef {\n joint: string;\n ratio: number;\n}\ninterface JointViewCouplingDef {\n joint: string;\n terms: JointViewCouplingTermDef[];\n offset: number;\n}\ninterface JointsViewOptions {\n enabled?: boolean;\n joints?: JointViewInput[];\n couplings?: JointViewCouplingInput[];\n animations?: JointViewAnimationInput[];\n defaultAnimation?: string;\n}\ninterface CollectedJointsView {\n enabled?: boolean;\n motionSource?: "viewport" | "assembly";\n joints: JointViewDef[];\n couplings: JointViewCouplingDef[];\n animations: JointViewAnimationDef[];\n defaultAnimation?: string;\n}\n/**\n * Register legacy viewport-only mechanism controls that animate returned objects without re-running the script.\n *\n * **Details**\n *\n * Deprecated: return an `Assembly` instead — returned assemblies expose\n * solver-backed controls automatically. Legacy pitfalls if you must stay here:\n * solve at rest pose (all animated joints = 0) or transforms double-rotate;\n * `pivot` is the world-space joint origin at rest; keyframes interpolate\n * linearly and never auto-wrap across `-180/180` (use accumulating angles with\n * `continuous: true`); mirrored revolute axes need negated tracks (see\n * `Assembly.connect()`); `min`/`max` bound the slider only — keyframes are not\n * clamped; coupled joints cannot be keyframe targets.\n *\n * @param options - `enabled`, `joints`, `couplings`, `animations`, `defaultAnimation`\n * @softDeprecated return the Assembly directly (return mech) — solver-backed controls appear automatically; use mech.addAnimation(name, { keyframes }) for animation clips\n * @deprecated use return the Assembly directly (return mech) — solver-backed controls appear automatically; use mech.addAnimation(name, { keyframes }) for animation clips\n * @skillSuppress Compatibility-only viewport FK API. Prefer returning `Assembly` directly so controls move through the solver-backed link/edge kinematics model.\n * @category Animation\n */\ndeclare function jointsView(options?: JointsViewOptions): void;\ninterface BomOpts {\n /**\n * Quantity unit label, e.g. "mm", "pieces", "kg".\n * Default: "pieces"\n */\n unit?: string;\n /**\n * Optional explicit grouping key used during report aggregation.\n */\n key?: string;\n /** Material name, e.g. "steel", "birch plywood", "nylon" */\n material?: string;\n /** Overall dimensions `[width, height]` or `[width, height, thickness]` in the entry\'s unit */\n dimensions?: number[];\n /** Cross-section dimensions `[w, h]` for tubes and profiles */\n section?: number[];\n /** Wall thickness for hollow sections (mm) */\n wall?: number;\n /** Diameter for round stock, bolts, dowels (mm) */\n diameter?: number;\n /** Length for fasteners (mm) */\n length?: number;\n /** Manufacturing process, e.g. "laser cut", "CNC", "welded" */\n process?: string;\n /** Free-form notes */\n notes?: string;\n /** Wood grain direction, e.g. "long", "cross" */\n grain?: string;\n /** Extensible for domain-specific fields */\n [key: string]: unknown;\n}\n/**\n * Register a Bill of Materials entry for report export.\n *\n * **Details**\n *\n * BOM entries are accumulated during script execution and exported alongside\n * the model in report views. Rows are grouped by normalized\n * `description + unit`. Pass an explicit `key` to force multiple descriptions\n * to collapse into a single line item.\n *\n * - `quantity` must be a finite number `>= 0`. A quantity of `0` is silently\n * ignored (useful for conditional scripting with `param()`-driven counts).\n * - `unit` defaults to `"pieces"` when omitted or empty.\n * - The assembly `solved.bom()` / `solved.bomCsv()` API is separate and\n * covers per-part assembly metadata; this function is for free-form\n * purchased-item annotation.\n * - `bom()` is injected into every `.forge.js` script. Call it directly; do\n * not write `const { bom } = require(...)`, because top-level declarations\n * named `bom` collide with the built-in runtime name.\n *\n * **Example**\n *\n * ```ts\n * const tubeLen = param("Tube Length", 1200, { min: 300, max: 4000, unit: "mm" });\n * const boltCount = param("Bolt Count", 16, { min: 0, max: 200, integer: true });\n *\n * bom(tubeLen, "iron tube 30 x 20", { unit: "mm" });\n * bom(boltCount, "M4 bolt, 16 mm length");\n * bom(4, "rubber foot", { key: "foot-rubber" }); // explicit aggregation key\n *\n * // Structured metadata for richer reports:\n * bom(tubeLen, "rectangular steel tube", {\n * unit: "mm",\n * material: "steel",\n * section: [30, 20],\n * wall: 3,\n * });\n * ```\n *\n * @param quantity - Quantity of the item; must be finite and `>= 0`\n * @param description - Human-readable item text, e.g. `"M4 bolt, 16 mm length"`\n * @param opts - Optional `unit` label (default `"pieces"`), aggregation `key`, and structured metadata (`material`, `section`, `wall`, `diameter`, `length`, `process`, `notes`, `grain`, etc.)\n * @category Bill of Materials\n */\ndeclare function bom(quantity: number, description: string, opts?: BomOpts): void;\ntype CompareAlignMode = "none" | "center" | "center-scale";\ninterface CompareWithOptions {\n /** Candidate alignment before scoring. Defaults to no automatic alignment. */\n align?: CompareAlignMode;\n /** Distance tolerance in model units for coverage scoring. Defaults to the comparison scorer\'s auto tolerance. */\n toleranceMm?: number;\n /** Surface samples per direction for numeric scoring. Defaults to the comparison scorer\'s standard sample count. */\n samples?: number;\n /** Human label for the reference model in inspection manifests. */\n label?: string;\n}\n/**\n * Declare a reference model for comparison inspection.\n *\n * **Details**\n *\n * `compareWith()` lets a model carry its own comparison target for inspection\n * workflows. `forgecad inspect compare overlay model.forge.js`\n * uses this reference to render the same Difference Only comparison overlay as\n * the live viewport. Amber marks candidate mismatch evidence, cyan marks\n * reference mismatch evidence, and faint model context keeps the overlay\n * readable. When the CLI can resolve the referenced file, the manifest also\n * includes the same geometric score produced by `forgecad compare 3d`.\n *\n * The path is resolved relative to the file that calls `compareWith()`. It may\n * point to another `.forge.js` file or an imported CAD asset such as `.stl`,\n * `.obj`, `.3mf`, `.step`, or `.stp`.\n *\n * **Example**\n *\n * ```js\n * compareWith(\'./reference.3mf\', { align: \'center\', toleranceMm: 0.25, samples: 3000 });\n * return rebuiltBearing;\n * ```\n *\n * @param path - Reference model path, relative to the current ForgeCAD file\n * @param options - Comparison scoring and manifest options\n * @returns void\n * @category Inspection\n */\ndeclare function compareWith(path: string, options?: CompareWithOptions): void;\ntype CutPlaneExcludeInput = string | string[];\ninterface CutPlaneOptions {\n /** Optional offset along the plane normal (primarily for object-form overload). */\n offset?: number;\n /** Object names to keep uncut for this plane. */\n exclude?: CutPlaneExcludeInput;\n}\n/**\n * Define a named section plane for inspecting internal geometry.\n *\n * **Details**\n *\n * Registers a cut plane that appears as a toggle in the viewport View Panel. When enabled,\n * geometry on the positive side of the plane (the side the normal points toward) is clipped away,\n * revealing the internal cross-section. The newly exposed section faces render with a hatched\n * overlay; pre-existing coplanar boundary faces are left unhatched.\n *\n * Planes are registered once per script run. The viewport toggle state (on/off) persists across\n * parameter changes without re-running the script. The `exclude` option only works correctly when\n * the excluded object names are stable across parameter changes.\n *\n * Accepts two overloads: `cutPlane(name, normal, offset?, options?)` or\n * `cutPlane(name, normal, options?)` where options may include `offset`.\n *\n * **Example**\n *\n * ```js\n * const cutZ = param(\'Cut Height\', 10, { min: -50, max: 50, unit: \'mm\' });\n * cutPlane(\'Inspection\', [0, 0, 1], cutZ, { exclude: [\'Probe\', \'Fasteners\'] });\n * ```\n *\n * @param name - Display label shown in the viewport View Panel\n * @param normal - Plane normal `[x, y, z]` — geometry on this side is clipped away\n * @param offset - Distance from origin along the normal. Default: 0\n * @param options.exclude - Object name(s) to keep uncut for this plane\n * @returns void\n * @category Cut Plane\n */\ndeclare function cutPlane(name: string, normal: [\n number,\n number,\n number\n], offset?: number, options?: CutPlaneOptions): void;\ndeclare function cutPlane(name: string, normal: [\n number,\n number,\n number\n], options?: CutPlaneOptions): void;\ntype RenderLabelAnchor = "center" | "top" | "bottom" | "left" | "right" | "top-left" | "top-right" | "bottom-left" | "bottom-right";\ninterface RenderLabelOptions {\n /** Text color as any CSS color string. */\n color?: string;\n /** Background color as any CSS color string. Use `\'transparent\'` for no pill background. */\n background?: string;\n /** Font size in CSS pixels. Defaults to 12. */\n size?: number;\n /** Additional world-space offset from `at`. */\n offset?: [\n number,\n number,\n number\n ];\n /** Which point of the label box is anchored to `at`. Defaults to `\'center\'`. */\n anchor?: RenderLabelAnchor;\n /** When false, the label is hidden when occluded by scene geometry. Defaults to true. */\n alwaysOnTop?: boolean;\n}\ninterface RenderLabelDef extends RenderLabelOptions {\n id: string;\n text: string;\n at: [\n number,\n number,\n number\n ];\n offset: [\n number,\n number,\n number\n ];\n anchor: RenderLabelAnchor;\n alwaysOnTop: boolean;\n}\n/**\n * Viewport-only presentation helpers.\n *\n * Use `Viewport.label()` for temporary review, debug, tutorial, or explicitly\n * requested presentation annotations that should appear in the viewer but should\n * not become CAD geometry. This keeps annotations separate from semantic face\n * labels and from `text2d()` geometry.\n *\n * @category Viewport Labels\n */\ndeclare const Viewport: {\n /**\n * Add a render-only viewport label at a world-space point.\n *\n * **Details**\n *\n * `Viewport.label()` is for temporary review, debug, tutorial, or explicitly\n * requested presentation overlays. It does not create sketches, meshes,\n * B-rep topology, exported text, or face labels, so it stays off the OCCT\n * path. Default production models should be understandable from physical\n * geometry, materials, part boundaries, and named objects, not viewport\n * annotations.\n *\n * Use `text2d()` only when the letters should become manufactured geometry,\n * such as raised lettering, engraved serial numbers, or exported nameplates.\n *\n * Labels are collected during script execution and rendered by the viewport\n * as lightweight overlay annotations. They are ignored by exports and do not\n * appear in `objects`.\n *\n * **Example**\n *\n * ```js\n * Viewport.label(\'Bearing bore\', [0, 0, 18], {\n * color: \'#f8fafc\',\n * background: \'#0f172acc\',\n * offset: [0, 0, 8],\n * anchor: \'bottom\',\n * });\n *\n * return box(40, 30, 12);\n * ```\n *\n * @param text - Annotation text to display in the viewport\n * @param at - World-space anchor point `[x, y, z]`\n * @param options - Visual label options\n * @returns void\n * @category Viewport Labels\n */\n label(text: string, at: [\n number,\n number,\n number\n ], options?: RenderLabelOptions): void;\n /**\n * Highlight any geometry for visual debugging in the viewport.\n *\n * **Details**\n *\n * `Viewport.highlight()` draws render-only debug overlays — distinctive\n * colored markers that appear in the viewport but never become geometry,\n * never export, and never appear in `objects`.\n *\n * Supported inputs:\n * - `[x, y, z]` — 3D point\n * - `[[x1,y1,z1], [x2,y2,z2]]` — edge (line segment)\n * - `{ normal: [x,y,z], offset: number }` — plane by normal + distance from origin\n * - `{ normal: [x,y,z], point: [x,y,z] }` — plane by normal + point on plane\n * - `Shape` — highlight an entire 3D shape\n * - `FaceRef` (from `shape.face(\'top\')`) — highlight as plane at face center\n * - `EdgeRef` (from `shape.edge(\'left\')`) — highlight as edge segment\n * - `string` — 2D sketch entity ID (e.g. `\'L0\'`, `\'P0\'`)\n *\n * Pass `{ labels: true }` with a `Shape` to annotate every user-authored\n * labeled face with its name (the face-label debugging view).\n *\n * **Example**\n *\n * ```js\n * const b = box(30, 20, 15);\n * Viewport.highlight([0, 0, 0], { color: \'cyan\', label: \'origin\' });\n * Viewport.highlight(b.face(\'top\'), { color: \'red\' });\n * Viewport.highlight(b, { labels: true }); // annotate all user-labeled faces\n * return b;\n * ```\n *\n * @param target - Point, edge, plane, Shape, FaceRef, EdgeRef, or sketch entity ID\n * @param options - `color`, `label`, `pulse`, `size` (points/planes), `labels` (Shape only)\n * @returns void\n * @category Viewport Labels\n */\n highlight(target: unknown, options?: HighlightOptions): void;\n};\ntype Vec3$3 = [\n number,\n number,\n number\n];\ninterface HermiteCurveEndpoint {\n /** Position */\n point: Vec3$3;\n /** Tangent direction (will be normalized internally) */\n tangent: Vec3$3;\n /** Weight: scales tangent magnitude relative to chord length. Default 1.0. */\n weight?: number;\n}\ndeclare class HermiteCurve3D {\n /** Start position */\n readonly p0: Vec3$3;\n /** End position */\n readonly p1: Vec3$3;\n /** Scaled tangent at start (direction * weight * chordLength) */\n readonly t0: Vec3$3;\n /** Scaled tangent at end (direction * weight * chordLength) */\n readonly t1: Vec3$3;\n /** Chord length (straight-line distance between endpoints) */\n readonly chordLength: number;\n constructor(start: HermiteCurveEndpoint, end: HermiteCurveEndpoint);\n /** Evaluate position at parameter t ∈ [0, 1] */\n pointAt(t: number): Vec3$3;\n /** Evaluate tangent (first derivative) at parameter t ∈ [0, 1] */\n tangentAt(t: number): Vec3$3;\n /** Evaluate curvature vector (second derivative) at parameter t ∈ [0, 1] */\n curvatureAt(t: number): Vec3$3;\n /** Sample the curve as a polyline of evenly-spaced parameter values. */\n sample(count?: number): Vec3$3[];\n /** Approximate arc length by sampling. */\n length(samples?: number): number;\n /**\n * Sample with adaptive density — more points where curvature is higher.\n * Returns at least `minCount` points, up to `maxCount`.\n */\n sampleAdaptive(minCount?: number, maxCount?: number): Vec3$3[];\n /** Convert to a format compatible with sweep() path input. */\n toPolyline(samples?: number): Vec3$3[];\n}\ninterface QuinticHermiteCurveEndpoint {\n /** Position */\n point: Vec3$3;\n /** Tangent direction (will be normalized internally) */\n tangent: Vec3$3;\n /** Second derivative / curvature vector. Default [0, 0, 0]. */\n curvature?: Vec3$3;\n /** Weight: scales tangent magnitude relative to chord length. Default 1.0. */\n weight?: number;\n}\ndeclare class QuinticHermiteCurve3D {\n /** Start position */\n readonly p0: Vec3$3;\n /** End position */\n readonly p1: Vec3$3;\n /** Scaled tangent at start (direction * weight * chordLength) */\n readonly t0: Vec3$3;\n /** Scaled tangent at end (direction * weight * chordLength) */\n readonly t1: Vec3$3;\n /** Scaled second derivative at start (curvature * weight² * chordLength²) */\n readonly c0: Vec3$3;\n /** Scaled second derivative at end (curvature * weight² * chordLength²) */\n readonly c1: Vec3$3;\n /** Chord length (straight-line distance between endpoints) */\n readonly chordLength: number;\n constructor(start: QuinticHermiteCurveEndpoint, end: QuinticHermiteCurveEndpoint);\n /** Evaluate position at parameter t ∈ [0, 1] */\n pointAt(t: number): Vec3$3;\n /** Evaluate tangent (first derivative, normalized) at parameter t ∈ [0, 1] */\n tangentAt(t: number): Vec3$3;\n /** Evaluate curvature vector (second derivative) at parameter t ∈ [0, 1] */\n curvatureAt(t: number): Vec3$3;\n /** Sample the curve as a polyline of evenly-spaced parameter values. */\n sample(count?: number): Vec3$3[];\n /** Approximate arc length by sampling. */\n length(samples?: number): number;\n /**\n * Sample with adaptive density — more points where curvature is higher.\n * Returns at least `minCount` points, up to `maxCount`.\n */\n sampleAdaptive(minCount?: number, maxCount?: number): Vec3$3[];\n /** Convert to a format compatible with sweep() path input. */\n toPolyline(samples?: number): Vec3$3[];\n}\ntype Vec3$4 = [\n number,\n number,\n number\n];\ninterface NurbsCurve3DOptions {\n /** Polynomial degree (default 3 = cubic). Must be ≥ 1. */\n degree?: number;\n /** Rational weights, one per control point (default: all 1.0 = non-rational). */\n weights?: number[];\n /** Knot vector (default: uniform clamped). Must have length = controlPoints.length + degree + 1. */\n knots?: number[];\n /** Whether the curve is closed/periodic (default false). */\n closed?: boolean;\n}\ndeclare class NurbsCurve3D {\n readonly controlPoints: Vec3$4[];\n readonly weights: number[];\n readonly knots: number[];\n readonly degree: number;\n readonly closed: boolean;\n constructor(points: Vec3$4[], options?: NurbsCurve3DOptions);\n /**\n * Evaluate the curve at parameter t ∈ [0, 1].\n * Uses De Boor\'s algorithm — exact, O(degree²).\n */\n pointAt(t: number): Vec3$4;\n /**\n * Evaluate the unit tangent vector at parameter t ∈ [0, 1].\n */\n tangentAt(t: number): Vec3$4;\n /**\n * Sample the curve uniformly at `count` points.\n */\n sample(count?: number): Vec3$4[];\n /**\n * Sample with adaptive density — more points in high-curvature regions.\n */\n sampleAdaptive(minCount?: number, maxCount?: number): Vec3$4[];\n /**\n * Approximate arc length by summing polyline segment lengths.\n */\n length(samples?: number): number;\n /** Convert to a format compatible with sweep() path input. */\n toPolyline(samples?: number): Vec3$4[];\n private estimateCurvature;\n}\ninterface Route3DFromPolylineOptions {\n /** Bend radius applied to every virtual interior corner. Default 0 keeps sharp polyline corners. */\n cornerRadius?: number;\n /** Name for the start port. Default "start". */\n startPort?: string;\n /** Name for the end port. Default "end". */\n endPort?: string;\n /** Preferred up vector for deterministic port frames. Default [0, 0, 1]. */\n up?: Vec3;\n}\ninterface Route3DToPolylineOptions {\n /** Approximate target point count for the full route. */\n samples?: number;\n /** Maximum angular spacing on arc segments. Default 6 degrees. */\n maxAngleDeg?: number;\n}\ntype Route3DVec3 = Vec3;\ninterface RoutePortFrame {\n name: string;\n origin: Route3DVec3;\n axis: Route3DVec3;\n xAxis: Route3DVec3;\n yAxis: Route3DVec3;\n station: number;\n}\ntype Route3DSegment = {\n kind: "line";\n from: Route3DVec3;\n to: Route3DVec3;\n length: number;\n} | {\n kind: "arc";\n center: Route3DVec3;\n radius: number;\n axis: Route3DVec3;\n start: Route3DVec3;\n end: Route3DVec3;\n sweepDeg: number;\n length: number;\n};\n/**\n * Metadata-bearing analytic 3D route made from line and arc segments.\n *\n * Use `Curve.Route.fromPolyline()` when you know the virtual design skeleton points\n * and bend radius. ForgeCAD computes tangent trim points, bend arcs, total\n * length, and named start/end port frames. Pass the route directly to `sweep()`.\n *\n * ```js\n * const route = Curve.Route.fromPolyline(\n * [[0, 0, 0], [0, 0, 80], [60, 0, 80]],\n * { cornerRadius: 24, startPort: "inlet", endPort: "outlet" },\n * );\n * const pipe = sweep(difference2d(circle2d(8), circle2d(6)), route);\n * const outlet = route.port("outlet");\n * ```\n */\ndeclare class Route3D {\n private readonly plan;\n private constructor();\n /** Build a line/arc route from virtual polyline corner points. */\n static fromPolyline(points: Route3DVec3[], options?: Route3DFromPolylineOptions): Route3D;\n /** Total centerline length, including line and bend arc segments. */\n get length(): number;\n /** Exact line and arc segments that make up this route. */\n get segments(): Route3DSegment[];\n /** Named port frames, keyed by port name. */\n get ports(): Record<string, RoutePortFrame>;\n /** Return one named route port frame. */\n port(name: string): RoutePortFrame;\n /** Convert this route to the compile plan consumed by sweep(). */\n toSweepPathPlan(): SweepPathCompilePlan;\n /** Sample this analytic route as a polyline for inspection or backend lowering. */\n toPolyline(options?: number | Route3DToPolylineOptions): Route3DVec3[];\n}\ntype Vec2$1 = [\n number,\n number\n];\ntype Vec3$5 = [\n number,\n number,\n number\n];\ninterface Spline2DOptions {\n /** Closed loop (default true). */\n closed?: boolean;\n /** Catmull-Rom tension in [0, 1]. 0 = very round, 1 = linear-ish. Default 0.5. */\n tension?: number;\n /** Samples per segment (minimum 3). Default 16. */\n samplesPerSegment?: number;\n /**\n * For open splines, provide stroke width to return a solid Sketch.\n * If omitted for open splines, an error is thrown.\n */\n strokeWidth?: number;\n /** Stroke join for open splines. Default \'Round\'. */\n join?: "Round" | "Square";\n}\ninterface Spline3DOptions {\n /** Closed loop (default false). */\n closed?: boolean;\n /** Catmull-Rom tension in [0, 1]. 0 = very round, 1 = linear-ish. Default 0.5. */\n tension?: number;\n}\ninterface LoftOptions {\n /** Marching-grid edge length for level-set meshing. Smaller = finer. */\n edgeLength?: number;\n /** Optional extra bounds padding. */\n boundsPadding?: number;\n}\ninterface FieldLoftOptions extends LoftOptions {\n /** Simplification control after field extraction. Default is topology-safe simplification. */\n simplify?: boolean | "safe";\n /** Hard post-extraction triangle budget. Must be a positive integer. If safe simplification cannot reach it, the build fails. */\n maxTriangles?: number;\n}\ninterface SweepOptions {\n /** Number of samples when path is a Curve3D. Default 48. */\n samples?: number;\n /** Marching-grid edge length for level-set meshing. Smaller = finer. */\n edgeLength?: number;\n /** Optional extra bounds padding. */\n boundsPadding?: number;\n /**\n * Preferred "up" vector for local profile frame.\n * Auto fallback is used near parallel segments.\n */\n up?: Vec3$5;\n}\ninterface VariableSweepSection {\n /** Parameter along the spine (0 = start, 1 = end). */\n t: number;\n /** Cross-section profile at this station. */\n profile: Sketch;\n}\ninterface VariableSweepOptions {\n /** Number of samples when spine is a Curve3D. Default 48. */\n samples?: number;\n /** Marching-grid edge length for level-set meshing. Smaller = finer. */\n edgeLength?: number;\n /** Optional extra bounds padding. */\n boundsPadding?: number;\n /**\n * Preferred "up" vector for local profile frame.\n * Auto fallback is used near parallel segments.\n */\n up?: Vec3$5;\n}\ndeclare class Curve3D {\n readonly points: Vec3$5[];\n readonly closed: boolean;\n readonly tension: number;\n constructor(points: Vec3$5[], options?: Spline3DOptions);\n /** Sample the curve with a fixed number of points per segment. */\n sampleBySegment(samplesPerSegment?: number): Vec3$5[];\n /** Sample the curve to an approximate total point count. */\n sample(count?: number): Vec3$5[];\n /** Map normalized t ∈ [0,1] to segment index + local parameter. */\n private segmentAt;\n /** Return the position on the curve at normalized parameter `t` in `[0, 1]`. O(1), no allocations. */\n pointAt(t: number): Vec3$5;\n /** Return a unit tangent vector at normalized parameter `t` in `[0, 1]`. O(1), analytical derivative. */\n tangentAt(t: number): Vec3$5;\n /** Approximate the curve length by polyline sampling. */\n length(samples?: number): number;\n}\n/**\n * Build a smooth Catmull-Rom spline sketch from 2D control points.\n *\n * A closed spline (default) returns a filled profile. An open spline requires\n * a strokeWidth option to produce a solid sketch. Use tension (0..1, default 0.5)\n * to control curve tightness.\n */\ndeclare function spline2d(points: Vec2$1[], options?: Spline2DOptions): Sketch;\n/**\n * Loft between multiple sketches along Z stations.\n *\n * Profiles can differ in topology and vertex count: interpolation is done on\n * signed-distance fields and meshed with level-set extraction. Heights must be\n * strictly increasing. Compatible loft stacks can also stay on the maintained\n * export-backend path.\n *\n * The surface is smooth through 3+ stations (C1 spanwise interpolation, like\n * CAD lofts), so it can bow slightly past the straight ruling between\n * stations; sections are matched exactly at their stations. Two-station lofts\n * are ruled. `edgeLength` caps the sample spacing in curved or twisted\n * regions (quality presets scale it); straight regions keep input density.\n *\n * Performance note: loft is significantly heavier than primitive/extrude/revolve.\n * If the part is axis-symmetric (bottles, vases, knobs), prefer revolve().\n */\ndeclare function loft(profiles: Sketch[], heights: number[], options?: LoftOptions): Shape;\ntype SweepPathInput = Curve3D | HermiteCurve3D | QuinticHermiteCurve3D | NurbsCurve3D | Route3D | Vec3$5[];\ndeclare function sweep(profile: Sketch, path: SweepPathInput, options?: SweepOptions): Shape;\n/**\n * Sweep a variable cross-section along a 3D spine curve.\n *\n * Unlike sweep(), which uses a single constant profile, variableSweep()\n * interpolates between multiple profiles at different stations along the spine.\n * This enables organic shapes like tapering tubes, bone-like structures, and\n * sculptural forms.\n *\n * Each section specifies a t parameter (0 = start, 1 = end of spine) and a\n * 2D profile sketch. The SDF-based level-set mesher smoothly blends between\n * profiles at intermediate positions.\n *\n * Performance note: like sweep(), this uses level-set meshing internally.\n */\ndeclare function variableSweep(spine: SweepPathInput, sections: VariableSweepSection[], options?: VariableSweepOptions): Shape;\ntype SimColliderMode = "convex" | "box" | "visual" | "none";\ninterface SimMaterialOptions {\n densityKgM3?: number;\n staticFriction?: number;\n dynamicFriction?: number;\n restitution?: number;\n}\ninterface SimMaterialDef extends SimMaterialOptions {\n kind: "material";\n name: string;\n}\ninterface SimColliderDef {\n kind: "collider";\n mode: SimColliderMode;\n reason?: string;\n}\ninterface SimContactDef {\n kind: "wheelSurface" | "gripperSurface";\n connectorName: string;\n}\ninterface SimBodyOptions {\n massKg?: number;\n densityKgM3?: number;\n material?: SimMaterialDef;\n collider?: SimColliderDef;\n contacts?: Record<string, SimContactDef>;\n}\ninterface SimBodyDef extends SimBodyOptions {\n kind: "body";\n}\ninterface SimPassiveDriveOptions {\n damping?: number;\n friction?: number;\n}\ninterface SimVelocityDriveOptions extends SimPassiveDriveOptions {\n maxTorqueNm: number;\n maxSpeedRpm: number;\n}\ntype SimDriveDef = ({\n kind: "passive";\n} & SimPassiveDriveOptions) | ({\n kind: "velocity";\n} & SimVelocityDriveOptions);\ntype SimProfileName = "Robot-Body-Runnable" | "Robot-Body-Isaac" | "Prop-Robotics-Physx";\ninterface SimProfileDef {\n kind: "profile";\n name: SimProfileName;\n}\ninterface SimDiffDriveControllerOptions {\n leftJoints: string[];\n rightJoints: string[];\n wheelSeparationMm: number;\n wheelRadiusMm: number;\n topic?: string;\n odomTopic?: string;\n tfTopic?: string;\n frameId?: string;\n odomFrameId?: string;\n maxLinearVelocity?: number;\n maxAngularVelocity?: number;\n linearAcceleration?: number;\n angularAcceleration?: number;\n}\ninterface SimDiffDriveControllerDef extends SimDiffDriveControllerOptions {\n kind: "diffDrive";\n}\ntype SimControllerDef = SimDiffDriveControllerDef;\ninterface SimAssemblySimulationOptions {\n profile: SimProfileDef;\n rootPart?: string;\n controllers?: SimControllerDef[];\n}\ninterface SimAssemblySimulationDef extends SimAssemblySimulationOptions {\n kind: "simulation";\n}\ndeclare function material(name: string, options?: SimMaterialOptions): SimMaterialDef;\ndeclare function body(options: SimBodyOptions): SimBodyDef;\ndeclare function passiveDrive(options?: SimPassiveDriveOptions): SimDriveDef;\ndeclare function velocityDrive(options: SimVelocityDriveOptions): SimDriveDef;\ndeclare function diffDrive(options: SimDiffDriveControllerOptions): SimDiffDriveControllerDef;\n/**\n * Simulation-readiness metadata constructors for robot and physics-aware assets.\n *\n * Use `Sim.body(...)` on `assembly().addPart(...)`, `Sim.drive.*(...)` on\n * `assembly().connect(...)`, and finish the root assembly with\n * `assembly.withSimulation(...)`.\n *\n * @category Assembly\n */\ndeclare const Sim: {\n /** Create a named physical material with density and contact coefficients for simulation export and checks. */\n readonly material: typeof material;\n /** Describe one assembly part as a physical body with mass/density, material, collider intent, and optional contact surfaces. */\n readonly body: typeof body;\n /** Collision-geometry intent constructors for physical parts. */\n readonly collider: {\n /** Use a generated collision mesh for the part. This is the default fast rigid-body collider for irregular parts. */\n readonly convexHull: () => SimColliderDef;\n /** Use the part bounding box as the collision geometry. This is fastest and works well for chassis and simple blocks. */\n readonly boundingBox: () => SimColliderDef;\n /** Use the visual mesh as collision geometry. This is exact but usually slower in physics engines. */\n readonly visualMesh: () => SimColliderDef;\n /** Disable collision for a part with an explicit reason, such as a sensor-only or decorative object. */\n readonly none: (reason: string) => SimColliderDef;\n };\n /** Joint-drive intent constructors for passive or powered assembly joints. */\n readonly drive: {\n /** Mark a joint as passive while preserving damping and friction metadata for simulation export. */\n readonly passive: typeof passiveDrive;\n /** Mark a revolute joint as velocity-driven with torque and speed limits. Speed is authored in rpm and exported as deg/s or rad/s as needed. */\n readonly velocity: typeof velocityDrive;\n };\n /** Contact-surface metadata over existing part connectors. */\n readonly contact: {\n /** Mark a connector as the wheel tread contact surface for offline checks and downstream simulation metadata. */\n readonly wheelSurface: (connectorName: string) => SimContactDef;\n /** Mark a connector as a gripper pad/contact surface for offline checks and downstream grasp-readiness metadata. */\n readonly gripperSurface: (connectorName: string) => SimContactDef;\n };\n /** Named validation/export profile constructors. */\n readonly profile: {\n /** SimReady-style profile for a robot body that should be runnable in a physics simulator. */\n readonly robotBodyRunnable: () => SimProfileDef;\n /** SimReady-style profile for robot bodies targeting Isaac Sim readiness. */\n readonly robotBodyIsaac: () => SimProfileDef;\n /** SimReady-style profile for robotics assets with PhysX-ready rigid bodies and colliders. */\n readonly roboticsAssetPhysx: () => SimProfileDef;\n };\n /** Standard controller metadata constructors for simulator package generation. */\n readonly controller: {\n /** Describe a differential-drive controller from left/right wheel joints and wheel dimensions. */\n readonly diffDrive: typeof diffDrive;\n };\n};\ninterface RobotLinkExportOptions {\n massKg?: number;\n densityKgM3?: number;\n collision?: "visual" | "convex" | "box" | "none";\n}\ninterface RobotJointExportOptions {\n effort?: number;\n velocity?: number;\n damping?: number;\n friction?: number;\n}\ninterface RobotDiffDrivePluginOptions {\n leftJoints: string[];\n rightJoints: string[];\n wheelSeparationMm: number;\n wheelRadiusMm: number;\n topic?: string;\n odomTopic?: string;\n tfTopic?: string;\n frameId?: string;\n odomFrameId?: string;\n maxLinearVelocity?: number;\n maxAngularVelocity?: number;\n linearAcceleration?: number;\n angularAcceleration?: number;\n}\ninterface RobotJointStatePublisherOptions {\n enabled?: boolean;\n joints?: string[];\n topic?: string;\n updateRate?: number;\n}\ntype RobotPose6 = [\n number,\n number,\n number,\n number,\n number,\n number\n];\ninterface RobotWorldKeyboardTeleopOptions {\n enabled?: boolean;\n linearStep?: number;\n angularStep?: number;\n}\ninterface RobotWorldOptions {\n name?: string;\n generateDemoWorld?: boolean;\n spawnPose?: RobotPose6;\n keyboardTeleop?: RobotWorldKeyboardTeleopOptions;\n}\ninterface RobotExportOptions {\n assembly: Assembly;\n modelName?: string;\n state?: JointState;\n static?: boolean;\n selfCollide?: boolean;\n allowAutoDisable?: boolean;\n links?: Record<string, RobotLinkExportOptions>;\n joints?: Record<string, RobotJointExportOptions>;\n plugins?: {\n diffDrive?: RobotDiffDrivePluginOptions;\n jointStatePublisher?: RobotJointStatePublisherOptions;\n };\n world?: RobotWorldOptions;\n}\ntype SimulationModelSource = "assembly" | "robotExport";\ninterface SimulationModel {\n modelName: string;\n assembly: AssemblyDefinition;\n simulation: SimAssemblySimulationDef | null;\n source: SimulationModelSource;\n state: JointState;\n static: boolean;\n selfCollide: boolean;\n allowAutoDisable: boolean;\n links: Record<string, RobotLinkExportOptions>;\n joints: Record<string, RobotJointExportOptions>;\n plugins: {\n diffDrive?: RobotDiffDrivePluginOptions;\n jointStatePublisher?: RobotJointStatePublisherOptions;\n };\n world: RobotWorldOptions | null;\n}\ntype CollectedRobotExport = SimulationModel;\n/**\n * Compatibility shim for SDF/URDF robot package metadata.\n *\n * **Details**\n *\n * Prefer returning `assembly(...).withSimulation(...)` with `Sim.body(...)`,\n * `Sim.drive.*(...)`, and `Sim.controller.*(...)` metadata. The CLI commands\n * `forgecad export sdf` and `forgecad export urdf` now read that assembly\n * simulation contract directly.\n *\n * `robotExport()` remains available for one compatibility window. It converts\n * the legacy descriptor into the same internal simulation model used by\n * source-authored `Sim` metadata and produces a robot package with:\n * - Mesh-based inertia tensors (full 6-component, not bounding-box approximations)\n * - Separate collision meshes\n * - Joint limits, effort/velocity/damping/friction metadata from assembly joints\n *\n * **Collision mesh modes** (set per-link via `links["PartName"].collision`):\n *\n * | Mode | Description | Default |\n * |------|-------------|---------|\n * | `\'convex\'` | Convex hull (separate `_collision.stl`) | Yes |\n * | `\'box\'` | AABB primitive — fastest physics | |\n * | `\'visual\'` | Same mesh as visual — exact but slow | |\n * | `\'none\'` | No collision geometry | |\n *\n * **Unit conventions:**\n * - Revolute `velocity` is in degrees/second in Forge; exporters convert to rad/s.\n * - Prismatic distances are in mm in Forge; exported in meters.\n * - `massKg` is preferred; `densityKgM3` is used when mass is unknown.\n * - Compatibility coupling metadata, when present, maps only the primary term\n * (largest ratio) to `<mimic>` — SDF/URDF support single-leader mimic only.\n * Dropped terms emit a warning.\n *\n * **Legacy example**\n *\n * ```ts\n * robotExport({\n * assembly: rover, // assembly() with parts + revolute wheel joints\n * modelName: "Scout",\n * links: { Chassis: { massKg: 10 }, "Left Wheel": { massKg: 0.8 } },\n * plugins: {\n * diffDrive: {\n * leftJoints: ["leftWheel"], rightJoints: ["rightWheel"],\n * wheelSeparationMm: 280, wheelRadiusMm: 60,\n * },\n * },\n * world: { generateDemoWorld: true },\n * });\n * ```\n *\n * **Preferred CLI usage**\n *\n * ```bash\n * forgecad export sdf model.forge.js # SDF package (Gazebo/Ignition)\n * forgecad export urdf model.forge.js # URDF package (ROS/PyBullet/MuJoCo)\n * ```\n *\n * @param options - Assembly, per-link mass/collision, per-joint overrides, plugins, world config\n * @returns The collected export descriptor (also stored globally for the CLI)\n * @deprecated Return `assembly(...).withSimulation(...)` and attach physics with `Sim.*` metadata instead.\n * @category Robot Export\n */\ndeclare function robotExport(options: RobotExportOptions): CollectedRobotExport;\n/**\n * Combine 2D sketches into a single profile using an additive boolean union.\n *\n * **Details**\n *\n * Accepts individual sketches or arrays: `union2d(a, b, c)` or `union2d([a, b, c])`.\n * Uses Manifold\'s batch operation — faster than chaining `.add()` one by one when\n * combining many sketches.\n *\n * **Example**\n *\n * ```ts\n * const cross = union2d(rect(60, 10), rect(10, 60));\n * ```\n *\n * @returns A new sketch containing the union of all inputs\n * @see {@link Sketch.add} for the chainable method equivalent\n * @category Sketch Booleans\n */\ndeclare function union2d(...inputs: SketchOperandInput[]): Sketch;\n/**\n * Subtract one or more 2D sketches from a base sketch.\n *\n * **Details**\n *\n * The first sketch is the base; all subsequent sketches are subtracted from it.\n * Accepts individual sketches or arrays: `difference2d(base, c1, c2)` or\n * `difference2d([base, c1, c2])`.\n * Uses Manifold\'s batch operation — faster than chaining `.subtract()` one by one.\n *\n * **Example**\n *\n * ```ts\n * const donut = difference2d(circle2d(50), circle2d(30));\n * ```\n *\n * @returns A new sketch with all subsequent inputs subtracted from the first\n * @see {@link Sketch.subtract} for the chainable method equivalent\n * @category Sketch Booleans\n */\ndeclare function difference2d(...inputs: SketchOperandInput[]): Sketch;\n/**\n * Keep only the area where all input sketches overlap (intersection boolean).\n *\n * **Details**\n *\n * Accepts individual sketches or arrays: `intersection2d(a, b)` or `intersection2d([a, b, c])`.\n * Uses Manifold\'s batch operation — faster than chaining `.intersect()` one by one.\n *\n * **Example**\n *\n * ```ts\n * const lens = intersection2d(circle2d(30).translate(-10, 0), circle2d(30).translate(10, 0));\n * ```\n *\n * @returns A new sketch containing only the shared area of all inputs\n * @see {@link Sketch.intersect} for the chainable method equivalent\n * @category Sketch Booleans\n */\ndeclare function intersection2d(...inputs: SketchOperandInput[]): Sketch;\ntype RectVertexName = "bottomLeft" | "bottomRight" | "topRight" | "topLeft";\ntype RectSideName = "bottom" | "right" | "top" | "left";\ninterface RectOptions {\n /** Bottom-left x coordinate. Default: 0. */\n x?: number;\n /** Bottom-left y coordinate. Default: 0. */\n y?: number;\n /** Width (along x). Default: 10. */\n width?: number;\n /** Height (along y). Default: 10. */\n height?: number;\n /** Prevent 180° rotation (ensures bottom edge points rightward). Default: false. */\n blockRotation?: boolean;\n}\n/**\n * Typed handle for a constrained axis-aligned rectangle in the solver.\n *\n * Structural constraints pre-applied:\n * `horizontal(bottom)`, `horizontal(top)`, `vertical(left)`, `vertical(right)`,\n * `ccw(bl, br, tr, tl)`.\n *\n * This leaves **4 DOF** (position x/y, width, height). Use `sk.fix()`,\n * `sk.length()`, `sk.shapeWidth()`, etc. to pin them.\n */\ninterface ConstrainedRect {\n readonly bottomLeft: PointId;\n readonly bottomRight: PointId;\n readonly topRight: PointId;\n readonly topLeft: PointId;\n /** bottom-left → bottom-right */\n readonly bottom: LineId;\n /** bottom-right → top-right */\n readonly right: LineId;\n /** top-right → top-left */\n readonly top: LineId;\n /** top-left → bottom-left */\n readonly left: LineId;\n /**\n * Center point constrained to the geometric center via `midpoint` on the diagonal.\n * Can be used in further constraints: `sk.fix(rect.center, 0, 0)`,\n * `sk.coincident(rect.center, other)`.\n */\n readonly center: PointId;\n /** ShapeId for `shapeWidth`, `shapeHeight`, `shapeArea`, `shapeCentroidX/Y`. */\n readonly shape: ShapeId;\n /** CCW-ordered vertex array: [bottomLeft, bottomRight, topRight, topLeft]. */\n readonly vertices: [\n PointId,\n PointId,\n PointId,\n PointId\n ];\n /** CCW-ordered side array: [bottom, right, top, left]. */\n readonly sides: [\n LineId,\n LineId,\n LineId,\n LineId\n ];\n /** Named vertex lookup. */\n vertex(name: RectVertexName): PointId;\n /** Named side lookup. */\n side(name: RectSideName): LineId;\n}\n/**\n * Add an axis-aligned rectangle concept to the builder.\n *\n * Creates 4 vertices (CCW: bl→br→tr→tl), 4 sides, 4 structural constraints\n * (`horizontal`/`vertical` on each side), CCW winding, a center point, a loop,\n * and a shape. Returns a `ConstrainedRect` handle with 4 DOF (x, y, width, height).\n *\n * Use `sk.rect()` as the shorthand builder method.\n *\n * **Example**\n *\n * ```ts\n * const sk = constrainedSketch();\n * const r = sk.rect({ x: 0, y: 0, width: 100, height: 50 });\n * sk.fix(r.bottomLeft, 0, 0);\n * sk.length(r.bottom, 120); // override initial width\n * return sk.solve().extrude(10);\n * ```\n *\n * @param sk - The builder to add the rectangle to.\n * @param options - Initial position and size.\n * @returns A `ConstrainedRect` handle with named vertices, sides, and center.\n * @softDeprecated sk.rect({ x: 0, y: 0, width: 100, height: 50 }) — same options, called on the constrainedSketch() builder\n * @deprecated use sk.rect({ x: 0, y: 0, width: 100, height: 50 }) — same options, called on the constrainedSketch() builder\n * @category Constrained Sketches\n */\ndeclare function addRect(sk: ConstrainedSketchBuilder, options?: RectOptions): ConstrainedRect;\ninterface ConstrainedSketchBuilder {\n /**\n * Add an axis-aligned rectangle concept.\n * Returns a `ConstrainedRect` handle with named vertices, sides, and center.\n */\n rect(options?: RectOptions): ConstrainedRect;\n}\ninterface PolygonOptions {\n /** Initial vertex coordinates. Minimum 3 points. */\n points: ReadonlyArray<readonly [\n number,\n number\n ]>;\n /**\n * Whether to register a closed loop for sketch generation.\n * Default: true.\n */\n addLoop?: boolean;\n /** Prevent 180° rotation (ensures first edge maintains its initial direction). Default: false. */\n blockRotation?: boolean;\n}\n/**\n * Typed handle for a general constrained polygon in the solver.\n *\n * Structural constraints pre-applied: `ccw(vertices)` for winding enforcement.\n *\n * `sides[i]` goes from `vertices[i]` → `vertices[(i+1) % n]`.\n */\ninterface ConstrainedPolygon {\n /** CCW-ordered PointIds. */\n readonly vertices: PointId[];\n /**\n * CCW-ordered LineIds.\n * `sides[i]` runs from `vertices[i]` → `vertices[(i+1) % n]`.\n */\n readonly sides: LineId[];\n /** ShapeId for `shapeWidth`, `shapeHeight`, `shapeArea`, `shapeCentroidX/Y`. */\n readonly shape: ShapeId;\n /** Get vertex by index. */\n vertex(index: number): PointId;\n /** Get side by index (side `i` goes vertex `i` → vertex `(i+1) % n`). */\n side(index: number): LineId;\n}\n/**\n * Add a general polygon concept to the builder.\n *\n * Creates n vertices and n sides (CCW: `sides[i]` from `vertices[i]` →\n * `vertices[(i+1) % n]`). Applies a `ccw` constraint to enforce winding.\n * All dimensional constraints (lengths, angles, position) are left to the caller.\n *\n * Use `sk.addPolygon()` as the shorthand builder method.\n *\n * **Example**\n *\n * ```ts\n * const sk = constrainedSketch();\n * const tri = sk.addPolygon({ points: [[0,0],[100,0],[50,80]] });\n * sk.fix(tri.vertex(0), 0, 0);\n * sk.length(tri.side(0), 100);\n * return sk.solve().extrude(5);\n * ```\n *\n * @param sk - The builder to add the polygon to.\n * @param options - Vertex coordinates and loop registration options.\n * @returns A `ConstrainedPolygon` handle with indexed vertex and side access.\n * @softDeprecated sk.addPolygon({ points: [[0,0],[100,0],[50,80]] }) — same options, called on the constrainedSketch() builder\n * @deprecated use sk.addPolygon({ points: [[0,0],[100,0],[50,80]] }) — same options, called on the constrainedSketch() builder\n * @category Constrained Sketches\n */\ndeclare function addPolygon(sk: ConstrainedSketchBuilder, options: PolygonOptions): ConstrainedPolygon;\ninterface ConstrainedSketchBuilder {\n /**\n * Add a general polygon concept (CCW winding enforced).\n * Returns a `ConstrainedPolygon` handle.\n */\n addPolygon(options: PolygonOptions): ConstrainedPolygon;\n}\ninterface RegularPolygonOptions {\n /** Number of sides (minimum 3). */\n sides: number;\n /** Circumradius — distance from center to vertex. Default: 10. */\n radius?: number;\n /** Center x coordinate. Default: 0. */\n cx?: number;\n /** Center y coordinate. Default: 0. */\n cy?: number;\n /**\n * Angle (in degrees) of vertex[0] measured from the +X axis (CCW positive).\n * Default: 0 (rightmost vertex).\n */\n startAngle?: number;\n /** Prevent 180° rotation (ensures first edge maintains its initial direction). Default: false. */\n blockRotation?: boolean;\n}\n/**\n * Typed handle for a constrained regular polygon in the solver.\n *\n * Structural constraints pre-applied:\n * - `equal(sides[0], sides[1])`, ..., `equal(sides[n-2], sides[n-1])` — equal sides\n * - `ccw(vertices)` — CCW winding\n *\n * Leaves **4 DOF** (center x/y, radius/scale, rotation). The center point is\n * tracked by the solver and exposed for further constraints.\n *\n * Note: Equal sides + CCW + regular initial placement makes this\n * "practically regular" for the solver. If you need geometrically exact\n * regularity (equal angles too), add `angleBetween` constraints on each pair\n * of adjacent sides.\n */\ninterface ConstrainedRegularPolygon extends ConstrainedPolygon {\n /**\n * Center point. Use `sk.fix(poly.center, x, y)` to pin location,\n * or `sk.coincident(poly.center, other)` to align with other geometry.\n */\n readonly center: PointId;\n}\n/**\n * Add a regular n-gon concept to the builder.\n *\n * Vertices are placed at `(cx + r·cos(startAngle + i·2π/n), cy + r·sin(...))`.\n * Equal-radius and equal-side constraints enforce regularity (4 DOF: center x/y, radius, rotation).\n * The center point is tracked by the solver and exposed via the returned handle.\n *\n * Use `sk.regularPolygon()` as the shorthand builder method.\n *\n * **Example**\n *\n * ```ts\n * const sk = constrainedSketch();\n * const hex = sk.regularPolygon({ sides: 6, radius: 25 });\n * sk.fix(hex.center, 0, 0);\n * sk.length(hex.side(0), 30); // all sides change (equal constraint)\n * return sk.solve().extrude(5);\n * ```\n *\n * @param sk - The builder to add the polygon to.\n * @param options - Number of sides, circumradius, center position, and rotation.\n * @returns A `ConstrainedRegularPolygon` handle extending `ConstrainedPolygon` with a `center` point.\n * @softDeprecated sk.regularPolygon({ sides: 6, radius: 25 }) — same options, called on the constrainedSketch() builder\n * @deprecated use sk.regularPolygon({ sides: 6, radius: 25 }) — same options, called on the constrainedSketch() builder\n * @category Constrained Sketches\n */\ndeclare function addRegularPolygon(sk: ConstrainedSketchBuilder, options: RegularPolygonOptions): ConstrainedRegularPolygon;\ninterface ConstrainedSketchBuilder {\n /**\n * Add a regular n-gon concept (equal sides, CCW winding).\n * Returns a `ConstrainedRegularPolygon` handle with a center point.\n */\n regularPolygon(options: RegularPolygonOptions): ConstrainedRegularPolygon;\n}\ninterface GroupRectOptions {\n /** Bottom-left x coordinate (world). Default: 0. */\n x?: number;\n /** Bottom-left y coordinate (world). Default: 0. */\n y?: number;\n /** Width (along x in local coords). Required. */\n width: number;\n /** Height (along y in local coords). Required. */\n height: number;\n /** Allow the solver to rotate this rectangle. Default: false. */\n allowRotation?: boolean;\n}\ninterface ConstrainedGroupRect extends SketchGroupHandle {\n readonly bottomLeft: PointId;\n readonly bottomRight: PointId;\n readonly topRight: PointId;\n readonly topLeft: PointId;\n readonly bottom: LineId;\n readonly right: LineId;\n readonly top: LineId;\n readonly left: LineId;\n readonly shape: ShapeId;\n}\ninterface ConstrainedSketchBuilder {\n /**\n * Add a rigid rectangle as a group concept.\n * Returns a `ConstrainedGroupRect` handle with named vertices and sides.\n * The rectangle is fixed in shape — only position (and optionally rotation) varies.\n */\n groupRect(options: GroupRectOptions): ConstrainedGroupRect;\n}\ntype PointArg$1 = [\n number,\n number\n] | [\n number,\n number,\n number\n] | Point2D;\ninterface DimOpts {\n offset?: number;\n label?: string;\n color?: string;\n component?: string | string[];\n currentComponent?: boolean;\n}\n/**\n * Add a dimension annotation between two points, or along an entity.\n *\n * **Details**\n *\n * Dimension annotations are purely visual callouts rendered in the viewport\n * and report export. They do not affect geometry or constrain the model.\n *\n * Point arguments accept 2D tuples `[x, y]`, 3D tuples `[x, y, z]`, or\n * `Point2D` objects (Z is treated as 0 for 2D inputs).\n *\n * Entity arguments: pass a single `Line2D` (from a constrained sketch) or an\n * `EdgeRef` (from `shape.edge(\'left\')`) as the first argument to dimension\n * along that entity directly — no manual endpoint extraction needed.\n *\n * **Ownership Rules (Report Pages)**\n *\n * - `currentComponent: true` — deterministic ownership by the calling import\n * instance. Use when authoring reusable imported parts.\n * - `component: "Part Name"` — route dimension to another named returned object.\n * - Multiple owners: dimension is shared and appears on the assembly overview page.\n * - No ownership set: report export infers ownership via endpoint-in-bbox.\n *\n * **Example**\n *\n * ```ts\n * dim([-w / 2, 0, 0], [w / 2, 0, 0], { label: "Width" });\n * dim([0, 0, -h / 2], [0, 0, h / 2], { label: "Height", offset: 14 });\n * dim([0, 0, 0], [100, 0, 0], { component: "Base", color: "#00AAFF" });\n * dim(sk.line(a, b), { label: "Span", offset: -8 }); // Line2D entity\n * dim(myBox.edge("top-right"), { label: "Depth" }); // EdgeRef entity\n * ```\n *\n * @param from - Start point as `[x, y]`, `[x, y, z]`, or `Point2D` — or a\n * `Line2D` / `EdgeRef` entity (then pass `opts` as the second argument)\n * @param to - End point as `[x, y]`, `[x, y, z]`, or `Point2D`\n * @param opts - Optional: `offset` (default 10), `label`, `color` (hex),\n * `component` (string or string[] — report ownership), `currentComponent` (boolean)\n * @category Dimensions\n */\ndeclare function dim(line: Line2D, opts?: DimOpts): void;\ndeclare function dim(edge: EdgeRef, opts?: DimOpts): void;\ndeclare function dim(from: PointArg$1, to: PointArg$1, opts?: DimOpts): void;\n/**\n * Add a dimension annotation along a `Line2D`.\n *\n * **Details**\n *\n * Convenience wrapper around {@link dim} that extracts the start and end\n * points from a constrained-sketch `Line2D` entity. All `opts` are forwarded\n * unchanged.\n *\n * **Example**\n *\n * ```ts\n * const a = point(0, 0);\n * const b = point(100, 0);\n * dimLine(line(a, b), { label: "Span", offset: -8 });\n * ```\n *\n * @param l - A `Line2D` entity from the constrained sketch builder\n * @param opts - Optional: `offset`, `label`, `color`, `component`, `currentComponent`\n * @see {@link dim} for point-to-point annotations\n * @softDeprecated dim(line, opts) — dim() accepts a Line2D (or EdgeRef) directly\n * @deprecated use dim(line, opts) — dim() accepts a Line2D (or EdgeRef) directly\n * @category Dimensions\n */\ndeclare function dimLine(l: Line2D, opts?: DimOpts): void;\ntype RegionSelection = "all" | "largest";\ninterface DxfImportOptions {\n /** Keep all disconnected regions, or only the largest. */\n regionSelection?: RegionSelection;\n /** Keep at most this many regions (largest-first). */\n maxRegions?: number;\n /** Drop regions below this absolute area threshold. */\n minRegionArea?: number;\n /** Drop regions below this ratio of largest-region area. */\n minRegionAreaRatio?: number;\n /** Curve flattening tolerance in DXF drawing units. */\n flattenTolerance?: number;\n /** Minimum segment count for a full circle or 360° of arcs. */\n arcSegments?: number;\n /** Endpoint tolerance used to chain LINE/ARC entities into closed loops. */\n joinTolerance?: number;\n /** Global scale applied after DXF parsing. */\n scale?: number;\n /** Maximum imported sketch width. If exceeded, geometry is uniformly downscaled. */\n maxWidth?: number;\n /** Maximum imported sketch height. If exceeded, geometry is uniformly downscaled. */\n maxHeight?: number;\n /** Recenter imported geometry so its 2D bounds center is at CAD origin. */\n centerOnOrigin?: boolean;\n /** Simplification tolerance for final sketch cleanup. */\n simplify?: number;\n}\ninterface SketchDxfOptions {\n /** DXF layer name. Default: "0" */\n layer?: string;\n /** DXF color index (1–255, AutoCAD ACI). Default: 7 (white/black) */\n colorIndex?: number;\n}\n/**\n * Export a 2D sketch as a DXF string (R12/AC1009 — maximally compatible).\n *\n * **Details**\n *\n * For regular sketches, each polygon loop becomes a closed `LWPOLYLINE`.\n * For constrained sketches, exports raw `LINE`, `CIRCLE`, and `ARC` entities\n * from the constraint edge geometry, which preserves internal/shared edges\n * that `toPolygons()` would merge away.\n *\n * The R12 format is chosen for maximum compatibility with CAM tools,\n * laser-cutter software, and older CAD readers.\n *\n * **Example**\n *\n * ```ts\n * const s = rect(100, 60);\n * const dxf = sketchToDxf(s, { layer: \'cut\' });\n * ```\n *\n * @param sketch - A 2D `Sketch` to export\n * @param options - Optional DXF `layer` name and AutoCAD `colorIndex` (ACI 1–255)\n * @returns DXF markup string in R12/AC1009 format\n * @see {@link sketchToSvg} for SVG export\n * @category Export\n */\ndeclare function sketchToDxf(sketch: Sketch, options?: SketchDxfOptions): string;\ninterface SketchSvgOptions {\n /** Stroke color. Default: "black" */\n stroke?: string;\n /** Stroke width in sketch units. Default: 0.5 */\n strokeWidth?: number;\n /** Fill color. Default: "none" */\n fill?: string;\n /** Padding around the sketch bounding box in sketch units. Default: 2 */\n padding?: number;\n /** If set, scale so 1 sketch-unit = this many px. Otherwise auto-fit. */\n pixelsPerUnit?: number;\n}\n/**\n * Export a 2D sketch as an SVG string.\n *\n * **Details**\n *\n * For regular sketches, exports filled polygon regions. For constrained\n * sketches, exports raw edge geometry (LINE, ARC, CIRCLE) which preserves\n * internal/shared edges that `toPolygons()` would merge away.\n *\n * The SVG uses the sketch\'s native coordinate system (Y-up) with a CSS\n * transform that flips Y so the output renders correctly in SVG\'s Y-down\n * space. Coordinates are in sketch units (typically mm).\n *\n * **Example**\n *\n * ```ts\n * const s = rect(100, 60);\n * const svg = sketchToSvg(s, { stroke: \'#333\', strokeWidth: 0.8 });\n * ```\n *\n * @param sketch - A 2D `Sketch` to export\n * @param options - Optional stroke, fill, padding, and pixel-per-unit scale\n * @returns SVG markup string\n * @see {@link sketchToDxf} for DXF export\n * @category Export\n */\ndeclare function sketchToSvg(sketch: Sketch, options?: SketchSvgOptions): string;\n/**\n * Round a tracked vertical solid edge with a circular fillet.\n *\n * **Details**\n *\n * Compiler-owned fillet for a narrow tracked-edge subset on solids.\n *\n * This is **not** a general 2D sketch-corner fillet. It currently works only\n * on tracked vertical edges from `box()` or `Rectangle2D` extrusions (plus\n * rigid transforms and supported preserved descendants of those). Generic\n * sketch extrudes, including `rect(...).extrude(...)`, are outside the\n * supported subset right now.\n *\n * **Supported edges:**\n * - Tracked vertical edges from `box()` or `Rectangle2D.extrude()`\n * - Rigid transforms between tracked source and target\n * - Untouched sibling tracked vertical edges after earlier `filletTrackedEdge`/`chamferTrackedEdge`\n *\n * **Not supported:** edges after shell, hole, cut, trim, difference,\n * intersection, generic sketch extrudes, or tapered extrudes.\n *\n * Canonical quadrants: `vert-bl → [1,-1]`, `vert-br → [-1,-1]`,\n * `vert-tr → [-1,1]`, `vert-tl → [1,1]`\n *\n * **Example**\n *\n * ```ts\n * const base = Rectangle2D.fromDimensions(0, 0, 50, 50).extrude(20);\n * const filleted = filletTrackedEdge(base, base.edge(\'vert-br\'), 5, [-1, -1]);\n * ```\n *\n * @param shape - Compile-covered solid to modify\n * @param edge - `EdgeRef` from the same tracked target body (e.g. `base.edge(\'vert-br\')`)\n * @param radius - Fillet radius (must be positive and finite)\n * @param quadrant - Corner direction sign pair (default: `[-1, -1]`)\n * @param segments - Arc resolution (default: `16`)\n * @returns A new Shape with the fillet applied\n * @see {@link fillet} for the general-purpose variant — it takes the same exact compiler-owned path for tracked edges, without the quadrant parameter\n * @see {@link chamferTrackedEdge} for beveled tracked edges\n * @softDeprecated fillet(shape, radius, shape.edge(\'vert-br\')) — same exact compiler-owned path, no quadrant tuple needed\n * @deprecated use fillet(shape, radius, shape.edge(\'vert-br\')) — same exact compiler-owned path, no quadrant tuple needed\n * @category Edge Features\n */\ndeclare function filletTrackedEdge(shape: Shape, edge: EdgeRef, radius: number, quadrant?: [\n number,\n number\n], segments?: number): Shape;\n/**\n * Bevel a tracked vertical solid edge with a 45° chamfer.\n *\n * **Details**\n *\n * Compiler-owned chamfer for tracked vertical edges. Requires a\n * compile-plan-covered target. This is not a general 2D sketch-corner tool;\n * supported subset and quadrant semantics are the same as `filletTrackedEdge()` - see\n * that function for details.\n *\n * **Example**\n *\n * ```ts\n * const base = Rectangle2D.fromDimensions(0, 0, 50, 50).extrude(20);\n * const chamfered = chamferTrackedEdge(base, base.edge(\'vert-br\'), 3, [-1, -1]);\n * ```\n *\n * @param shape - Compile-covered solid to modify\n * @param edge - `EdgeRef` from the same tracked target body (e.g. `base.edge(\'vert-br\')`)\n * @param size - Chamfer size (must be positive and finite)\n * @param quadrant - Corner direction sign pair (default: `[-1, -1]`)\n * @returns A new Shape with the chamfer applied\n * @see {@link chamfer} for the general-purpose variant — it takes the same exact compiler-owned path for tracked edges, without the quadrant parameter\n * @see {@link filletTrackedEdge} for rounded tracked edges\n * @softDeprecated chamfer(shape, size, shape.edge(\'vert-br\')) — same exact compiler-owned path, no quadrant tuple needed\n * @deprecated use chamfer(shape, size, shape.edge(\'vert-br\')) — same exact compiler-owned path, no quadrant tuple needed\n * @category Edge Features\n */\ndeclare function chamferTrackedEdge(shape: Shape, edge: EdgeRef, size: number, quadrant?: [\n number,\n number\n]): Shape;\ninterface FilletCornerSpec {\n index: number;\n radius: number;\n segments?: number;\n}\ntype PointInput = [\n number,\n number\n] | Point2D;\n/**\n * Create a polygon from points with specific corners rounded to arc fillets.\n *\n * **Details**\n *\n * Each corner spec identifies a vertex by its index in the `points` array and\n * the desired fillet `radius`. Both convex and concave corners are supported.\n *\n * Constraints:\n * - Collinear corners cannot be filleted (throws an error)\n * - Two neighboring fillets whose tangent lengths overlap the same edge will throw\n * - Radius must be positive and small enough to fit within the adjacent edge lengths\n *\n * Prefer the Sketch methods: they work on any composed sketch (after booleans,\n * `attachTo`, text), select corners by position instead of brittle indices, and\n * have chamfer twins — `sketch.filletCorners(radius)`, `sketch.filletCorner([x, y], radius)`,\n * `sketch.chamferCorners(size)`, `sketch.chamferCorner([x, y], size)`.\n *\n * **Example**\n *\n * ```ts\n * const roof = filletCorners(roofPoints, [\n * { index: 3, radius: 19 },\n * { index: 4, radius: 19 },\n * { index: 5, radius: 19 },\n * ]);\n * ```\n *\n * @param points - Closed polygon vertices as `[x, y][]` or `Point2D[]`\n * @param corners - Fillet specs — each has `index` (vertex index), `radius`, and optional `segments`\n * @returns A new polygon sketch with the specified corners filleted\n * @see {@link Sketch.filletCorner} for seed-point corner selection on any sketch\n * @softDeprecated polygon(points).filletCorner([x, y], radius) — seed-point corner selection; or .filletCorners(radius) for all corners\n * @deprecated use polygon(points).filletCorner([x, y], radius) — seed-point corner selection; or .filletCorners(radius) for all corners\n * @category Sketch Operations\n */\ndeclare function filletCorners(points: PointInput[], corners: FilletCornerSpec[]): Sketch;\n/**\n * Pre-load and cache a font for use with `text2d()`.\n *\n * **Details**\n *\n * Fonts are cached by their source string (or `cacheKey` for `ArrayBuffer` sources),\n * so repeated calls with the same path are free. Pre-loading is useful when you call\n * `text2d()` many times with the same font — it avoids repeated disk reads.\n *\n * Built-in font names that work everywhere (browser + CLI):\n * - `\'sans-serif\'` or `\'inter\'` — bundled Inter Regular\n *\n * **Example**\n *\n * ```ts\n * const font = loadFont(\'/path/to/Arial Bold.ttf\');\n * text2d(\'Title\', { size: 12, font }).extrude(1.5);\n * text2d(\'Subtitle\', { size: 8, font }).extrude(1);\n * ```\n *\n * @param source - Built-in name (`\'sans-serif\'`/`\'inter\'`), file path (CLI only), or `ArrayBuffer`\n * @param cacheKey - Optional cache key when passing an `ArrayBuffer`\n * @returns Parsed opentype.js Font object\n * @see {@link text2d} to use the loaded font\n * @category Sketch Text\n */\ndeclare function loadFont(source: string | ArrayBuffer, cacheKey?: string): opentype$1.Font;\ninterface HelixOptions {\n /** Radius from the central Z axis to the helix centerline. */\n radius: number;\n /** Axial distance per full turn. Provide any two of `pitch`, `turns`, and `height`. */\n pitch?: number;\n /** Number of full rotations around the axis. Provide any two of `pitch`, `turns`, and `height`. */\n turns?: number;\n /** Total height along +Z. Provide any two of `pitch`, `turns`, and `height`. */\n height?: number;\n /** Start angle in degrees. Default 0 starts on +X. */\n startAngle?: number;\n /** Reverse winding direction when viewed from +Z. */\n clockwise?: boolean;\n /** Point samples per turn for the metadata path. Default 32. */\n samplesPerTurn?: number;\n}\ninterface HelixCoilOptions extends HelixOptions {\n /** Radius of the circular wire profile. Required unless a custom profile is passed. */\n wireRadius?: number;\n /** Segment count for the default circular wire profile. Default 24. */\n profileSegments?: number;\n /** Sweep path samples per turn. Default 32. */\n divisionsPerTurn?: number;\n}\ntype Vec2$2 = [\n number,\n number\n];\ntype Vec3$6 = [\n number,\n number,\n number\n];\ninterface SurfaceTessellationOptions {\n /** `uniform` uses resolution directly; `adaptive` lets the Truck kernel refine open sheets from chord error. */\n mode?: "uniform" | "adaptive";\n /** Target chord-error tolerance in model units for adaptive Truck tessellation. */\n tolerance?: number;\n /** Minimum adaptive samples per direction. */\n minResolution?: number;\n /** Maximum adaptive samples per direction. Defaults to `resolution` when omitted. */\n maxResolution?: number;\n}\ninterface SurfaceDomainOptions {\n /** Lower U parameter bound in normalized surface space (default 0). */\n uMin?: number;\n /** Upper U parameter bound in normalized surface space (default 1). */\n uMax?: number;\n /** Lower V parameter bound in normalized surface space (default 0). */\n vMin?: number;\n /** Upper V parameter bound in normalized surface space (default 1). */\n vMax?: number;\n}\ninterface SurfaceTrimNurbsCurveOptions {\n /** Curve trim loop kind. */\n kind: "nurbs";\n /** NURBS UV control points in normalized post-domain space. */\n controlPoints: Vec2$2[];\n /** Optional rational weights. Defaults to 1.0 for each control point. */\n weights?: number[];\n /** Optional knot vector. Defaults to uniform clamped knots. */\n knots?: number[];\n /** Curve degree. Defaults to cubic where possible. */\n degree?: number;\n /** Number of UV samples used by the Truck kernel for trim triangulation. */\n samples?: number;\n}\ntype SurfaceTrimLoopInput = Vec2$2[] | SurfaceTrimNurbsCurveOptions;\ninterface SurfaceTrimOptions {\n /** Outer trim loop in normalized post-domain UV space. */\n outer: SurfaceTrimLoopInput;\n /** Optional hole loops in normalized post-domain UV space. */\n holes?: SurfaceTrimLoopInput[];\n}\ninterface NurbsSurfaceOptions {\n /** Degree in U direction (default 3). */\n degreeU?: number;\n /** Degree in V direction (default 3). */\n degreeV?: number;\n /** Weights grid — same dimensions as controlGrid (default: all 1.0). */\n weights?: number[][];\n /** Knot vector in U direction (default: uniform clamped). */\n knotsU?: number[];\n /** Knot vector in V direction (default: uniform clamped). */\n knotsV?: number[];\n /** Sheet thickness — if > 0, thickens the surface into a solid (default 0 = surface only). */\n thickness?: number;\n /** Tessellation resolution — points per direction (default 32). */\n resolution?: number;\n /** Optional rectangular parameter domain in normalized [0, 1] U/V space. */\n domain?: SurfaceDomainOptions;\n /** Optional polygonal or NURBS-curve UV trim loops. SDF, Truck, and OCCT support open trimmed surfaces; Manifold supports sampled thickened trimmed solids. */\n trim?: SurfaceTrimOptions;\n /** Optional Truck kernel tessellation controls for render mesh generation. */\n tessellation?: SurfaceTessellationOptions;\n /** Explicit opt-in for sampled approximation paths on non-exact backends. */\n approximate?: boolean;\n}\ndeclare class NurbsSurface {\n readonly controlGrid: Vec3$6[][];\n readonly weightsGrid: number[][];\n readonly knotsU: number[];\n readonly knotsV: number[];\n readonly degreeU: number;\n readonly degreeV: number;\n readonly nU: number;\n readonly nV: number;\n readonly domain: SurfaceDomainCompilePlan;\n constructor(controlGrid: Vec3$6[][], options?: NurbsSurfaceOptions);\n /**\n * Evaluate the surface at parameters (u, v) ∈ [0, 1]².\n * Uses tensor product evaluation: evaluate basis functions in U and V independently.\n */\n pointAt(u: number, v: number): Vec3$6;\n /**\n * Evaluate the surface normal at (u, v) via cross product of partial derivatives.\n */\n normalAt(u: number, v: number): Vec3$6;\n /**\n * Tessellate the surface into a triangle mesh.\n * Returns positions, normals, and triangle indices.\n */\n tessellate(resU?: number, resV?: number): {\n positions: Vec3$6[];\n normals: Vec3$6[];\n indices: number[];\n };\n private remapU;\n private remapV;\n}\n/**\n * Create a NURBS surface from a grid of control points.\n *\n * The control grid is indexed as `controlGrid[u][v]` — each row is a curve\n * in the V direction, and columns trace curves in the U direction.\n *\n * With default options, creates a bicubic non-rational B-spline surface\n * with uniform clamped knots.\n *\n * @example\n * // Simple 4×4 control grid — a gently curved surface\n * const grid = [\n * [[0,0,0], [10,0,2], [20,0,2], [30,0,0]],\n * [[0,10,1], [10,10,5], [20,10,5], [30,10,1]],\n * [[0,20,1], [10,20,5], [20,20,5], [30,20,1]],\n * [[0,30,0], [10,30,2], [20,30,2], [30,30,0]],\n * ];\n * const surface = nurbsSurface(grid, { thickness: 2 });\n *\n * @softDeprecated Surface.Nurbs(controlGrid, options) — same arguments and options, including thickness\n * @deprecated use Surface.Nurbs(controlGrid, options) — same arguments and options, including thickness\n */\ndeclare function nurbsSurface(controlGrid: Vec3$6[][], options?: NurbsSurfaceOptions): Shape;\n/**\n * Layout helpers — eliminate manual trigonometry for common positioning patterns.\n *\n * These functions return arrays of {x, y} positions that can be used with\n * translate(), circularPattern seed placement, or any other positioning API.\n */\ninterface LayoutPoint {\n x: number;\n y: number;\n}\ninterface CircularLayoutOptions {\n /** Angle of the first element in degrees (default: 0 = +X axis). */\n startDeg?: number;\n /** Center X coordinate (default: 0). */\n centerX?: number;\n /** Center Y coordinate (default: 0). */\n centerY?: number;\n}\n/**\n * Compute evenly-spaced positions around a circle.\n *\n * Eliminates the most common trig pattern in CAD scripts:\n * ```js\n * // Before — manual trig\n * for (let i = 0; i < 12; i++) {\n * const angle = i * 30 * Math.PI / 180;\n * markers.push(marker.translate(r * Math.cos(angle), r * Math.sin(angle), 0));\n * }\n *\n * // After — declarative\n * for (const {x, y} of circularLayout(12, r)) {\n * markers.push(marker.translate(x, y, 0));\n * }\n * ```\n */\ndeclare function circularLayout(count: number, radius: number, options?: CircularLayoutOptions): LayoutPoint[];\ninterface PolygonVerticesOptions {\n /** Angle of the first vertex in degrees (default: 90 = top). */\n startDeg?: number;\n /** Center X coordinate (default: 0). */\n centerX?: number;\n /** Center Y coordinate (default: 0). */\n centerY?: number;\n}\n/**\n * Compute the vertex positions of a regular polygon.\n *\n * Default orientation places the first vertex at the top (90 degrees),\n * matching the convention used by `ngon()`.\n *\n * Eliminates manual Math.sqrt(3) for triangles, pentagon vertex math, etc:\n * ```js\n * // Before — manual equilateral triangle\n * const v1 = [center.x - r/2, center.y + r * Math.sqrt(3)/2];\n * const v2 = [center.x - r/2, center.y - r * Math.sqrt(3)/2];\n * const v3 = [center.x + r, center.y];\n *\n * // After — declarative\n * const [v1, v2, v3] = polygonVertices(3, r);\n * ```\n */\ndeclare function polygonVertices(sides: number, radius: number, options?: PolygonVerticesOptions): LayoutPoint[];\ntype LoftAxis = "X" | "Y" | "Z";\ntype LoftGuideRailSide = "left" | "right" | "front" | "back" | "center";\ninterface LoftPath2DLike {\n toPolyline(samples?: number): Array<[\n number,\n number\n ]>;\n}\ntype LoftPath2D = Array<[\n number,\n number\n]> | LoftPath2DLike;\ntype LoftGuideRailPath = Vec3[] | Curve3D | HermiteCurve3D | QuinticHermiteCurve3D | NurbsCurve3D;\ninterface LoftStation {\n profile: Sketch;\n position: number;\n}\ninterface LoftGuideRail {\n side: LoftGuideRailSide;\n path: LoftGuideRailPath;\n}\ninterface LoftWithGuideRailsOptions extends LoftOptions {\n /** Primary station axis. Default Z. */\n axis?: LoftAxis;\n /** Number of generated loft stations including ends. Default scales with station count. */\n samples?: number;\n /** Number of points sampled from curve-backed rails before axis interpolation. Default 64. */\n railSamples?: number;\n}\n/**\n * Namespaced loft helpers for guided station lofts.\n *\n * `Loft.withGuideRails(...)` keeps the public API out of the global namespace\n * while letting guide rails control the side silhouette of the generated loft.\n * Use left/right/front/back rails to constrain envelope sides, and a center rail\n * to move the section centers along a path.\n */\ndeclare const Loft: {\n /** Create a loft station from a 2D profile and an axis position. */\n station(profile: Sketch, position: number): LoftStation;\n /**\n * Loft by interpolating signed-distance fields instead of matching vertices.\n *\n * Use this path when profiles change character, such as round shafts blending\n * into flat, cruciform, or lobed tips. It is Manifold-only, mesh-based, and\n * slower than stitched lofting, but it avoids profile-point correspondence\n * artifacts because it blends profile fields instead of boundary vertices.\n */\n field(profiles: Sketch[], heights: number[], options?: FieldLoftOptions): Shape;\n /** Create a guide rail that constrains the section-local negative-X side. */\n leftRail(path: LoftGuideRailPath): LoftGuideRail;\n /** Create a guide rail that constrains the section-local positive-X side. */\n rightRail(path: LoftGuideRailPath): LoftGuideRail;\n /** Create a guide rail that constrains the section-local positive-Y side. */\n frontRail(path: LoftGuideRailPath): LoftGuideRail;\n /** Create a guide rail that constrains the section-local negative-Y side. */\n backRail(path: LoftGuideRailPath): LoftGuideRail;\n /** Create a guide rail that moves section centers along the loft. */\n centerRail(path: LoftGuideRailPath): LoftGuideRail;\n /**\n * Place a 2D guide path onto the XZ plane.\n *\n * The path\'s first coordinate becomes X and its second coordinate becomes Z.\n * Use this for left/right silhouette rails authored with `path()` or `constrainedSketch()`.\n */\n pathOnXz(path: LoftPath2D, y?: number): Vec3[];\n /**\n * Place a 2D guide path onto the YZ plane.\n *\n * The path\'s first coordinate becomes Y and its second coordinate becomes Z.\n * Use this for front/back crown rails authored with `path()` or `constrainedSketch()`.\n */\n pathOnYz(path: LoftPath2D, x?: number): Vec3[];\n /**\n * Place a 2D guide path onto the XY plane.\n *\n * The path\'s first coordinate becomes X and its second coordinate becomes Y.\n * Use this when lofting along X or Y and a rail lives in a horizontal sketch plane.\n */\n pathOnXy(path: LoftPath2D, z?: number): Vec3[];\n /**\n * Loft through profile stations while forcing generated sections to follow guide rails.\n *\n * Stations define the cross-section family. Guide rails define the side or center\n * paths the loft must pass through. With opposite side rails, the section is scaled\n * to touch both rails. With one side rail, the section keeps its interpolated size\n * unless a center rail is also present.\n */\n withGuideRails(stations: LoftStation[], rails: LoftGuideRail[], options?: LoftWithGuideRailsOptions): Shape;\n};\ndeclare class PathBuilder {\n private segs;\n private x;\n private y;\n /** Current departure tangent unit vector. */\n private dirX;\n private dirY;\n /**\n * Current cursor X position.\n *\n * @returns The current X coordinate.\n */\n getX(): number;\n /**\n * Current cursor Y position.\n *\n * @returns The current Y coordinate.\n */\n getY(): number;\n /**\n * Move the cursor to an absolute position without drawing a segment.\n *\n * When called after the initial `path()`, this establishes the start of the outline.\n * Calling `moveTo` again mid-path starts a new sub-path (hole in `close()`, separate\n * segment for `stroke()`).\n *\n * @param x - Absolute X coordinate.\n * @param y - Absolute Y coordinate.\n * @category Path Builder\n */\n moveTo(x: number, y: number): this;\n /**\n * Draw a straight line from the current cursor to an absolute position.\n *\n * @param x - Absolute X coordinate of the endpoint.\n * @param y - Absolute Y coordinate of the endpoint.\n * @category Path Builder\n */\n lineTo(x: number, y: number): this;\n /**\n * Draw a horizontal line segment by `dx` units from the current cursor.\n *\n * Positive `dx` moves right; negative moves left.\n *\n * @param dx - Horizontal displacement.\n * @category Path Builder\n */\n lineH(dx: number): this;\n /**\n * Draw a vertical line segment by `dy` units from the current cursor.\n *\n * Positive `dy` moves up; negative moves down.\n *\n * @param dy - Vertical displacement.\n * @category Path Builder\n */\n lineV(dy: number): this;\n /**\n * Draw a line at the given angle and length from the current cursor.\n *\n * Angle convention: `0°` points right (+X), `90°` points up (+Y).\n *\n * **Example**\n *\n * ```ts\n * // L-bracket with angled return\n * path().moveTo(0, 0).lineH(50).lineV(-70).lineAngled(20, 235).stroke(4);\n * ```\n *\n * @param length - Length of the line segment.\n * @param degrees - Angle in degrees (0 = right, 90 = up).\n * @category Path Builder\n */\n lineAngled(length: number, degrees: number): this;\n /**\n * Draw a line by a relative `(dx, dy)` displacement from the current cursor.\n *\n * @param dx - Horizontal displacement.\n * @param dy - Vertical displacement.\n * @category Path Builder\n */\n lineBy(dx: number, dy: number): this;\n /**\n * Draw an arc to a point offset from the current cursor.\n *\n * @param dx - Relative X displacement to the end point.\n * @param dy - Relative Y displacement to the end point.\n * @param radius - Arc radius.\n * @param clockwise - Sweep direction. Default: `false` (CCW).\n * @category Path Builder\n */\n arcBy(dx: number, dy: number, radius: number, clockwise?: boolean): this;\n /**\n * Draw a cubic Bezier using control points relative to the current cursor.\n *\n * @param dcp1x - Relative X of the first control point.\n * @param dcp1y - Relative Y of the first control point.\n * @param dcp2x - Relative X of the second control point.\n * @param dcp2y - Relative Y of the second control point.\n * @param dx - Relative X of the end point.\n * @param dy - Relative Y of the end point.\n * @category Path Builder\n */\n bezierBy(dcp1x: number, dcp1y: number, dcp2x: number, dcp2y: number, dx: number, dy: number): this;\n /**\n * Draw a circular arc from the current position to (x, y) with the given radius.\n * `clockwise=true` → arc curves to the right of the start→end direction.\n * `clockwise=false` → arc curves to the left of the start→end direction.\n */\n arcTo(x: number, y: number, radius: number, clockwise?: boolean): this;\n /**\n * G1-continuous arc — radius derived from current tangent + endpoint.\n * Throws if endpoint is collinear with current direction.\n */\n tangentArcTo(x: number, y: number): this;\n /**\n * Draw an arc defined by center, radius, and angle range (no trig needed).\n * If the path has no segments yet, automatically moves to the arc start.\n * Positive sweep (startDeg < endDeg) = CCW, negative = CW.\n *\n * ```js\n * // Arc centered at (10, 0), radius 50, from -30° to +30°\n * path().arc(10, 0, 50, -30, 30).stroke(8, \'Round\')\n * ```\n */\n arc(cx: number, cy: number, radius: number, startDeg: number, endDeg: number): this;\n /**\n * Arc around a known center point, sweeping by the given angle.\n * Radius is derived from the distance between the current position and the center.\n * Positive sweep = CCW (math convention), negative = CW.\n *\n * ```js\n * // Arc 90° CCW around (50, 50)\n * path().moveTo(70, 50).arcAround(50, 50, 90)\n * // Arc 45° CW around the origin\n * path().moveTo(10, 0).arcAround(0, 0, -45)\n * ```\n */\n arcAround(cx: number, cy: number, sweepDeg: number): this;\n /**\n * Arc around a center point given as an offset from the current position.\n * `(dx, dy)` is the vector from the current point to the center.\n * Positive sweep = CCW (math convention), negative = CW.\n *\n * ```js\n * // Arc 90° CCW around a center 20 units to the right\n * path().moveTo(50, 50).arcAroundRelative(20, 0, 90)\n * // Equivalent to: path().moveTo(50, 50).arcAround(70, 50, 90)\n * ```\n */\n arcAroundRelative(dx: number, dy: number, sweepDeg: number): this;\n /**\n * Smooth three-arc end cap from the current position to (endX, endY).\n * Inserts: small corner arc → large cap arc → small corner arc, all G1-continuous.\n */\n smoothCapTo(endX: number, endY: number, cornerRadius: number, capRadius: number): this;\n /**\n * Cubic bezier from current position to (x, y) via two control points.\n */\n bezierTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): this;\n /**\n * G1-continuous cubic bezier — first control point is auto-derived from\n * the current tangent direction. `weight` controls how far the auto-placed\n * control point extends along the tangent (default: 1/3 of the chord).\n *\n * The second control point `(cp2x, cp2y)` must be provided — it controls\n * the arrival curvature. For a fully automatic smooth curve, see `smoothThrough`.\n */\n tangentBezierTo(cp2x: number, cp2y: number, x: number, y: number, weight?: number): this;\n /**\n * Catmull-Rom spline through a list of waypoints from the current position.\n * The current position is included as the first point. The last waypoint\n * becomes the new cursor position.\n *\n * @param waypoints — intermediate + final points (at least 1)\n * @param tension — 0 = very round, 1 = linear (default 0.5)\n */\n smoothThrough(waypoints: [\n number,\n number\n ][], tension?: number): this;\n /**\n * Rational B-spline edge to (x, y) with explicit control points and weights.\n *\n * The control points define the B-spline shape between the current position\n * and (x, y). The current position is NOT included in `controlPoints` — it is\n * automatically prepended. The endpoint (x, y) is the last control point.\n *\n * @param controlPoints — interior + endpoint control points (endpoint = last)\n * @param opts.weights — rational weights (default: all 1.0)\n * @param opts.degree — B-spline degree (default: control point count - 1, capped at 3)\n */\n nurbsTo(controlPoints: [\n number,\n number\n ][], opts?: {\n weights?: number[];\n degree?: number;\n }): this;\n /**\n * Exact circular arc to (x, y) using a rational quadratic NURBS.\n *\n * Unlike `arcTo()` which tessellates to a polyline, this preserves the\n * exact arc definition. When extruded through the OCCT backend, it produces\n * a true cylindrical face — not a faceted approximation.\n *\n * @param x — endpoint X\n * @param y — endpoint Y\n * @param opts.radius — arc radius (default: auto-computed from chord)\n * @param opts.clockwise — winding direction (default: false = CCW)\n */\n exactArcTo(x: number, y: number, opts?: {\n radius?: number;\n clockwise?: boolean;\n }): this;\n /**\n * Round the last corner (the junction between the previous two segments)\n * with a tangent arc of the given radius.\n *\n * Must be called after at least two line/arc segments that form a corner.\n * The fillet trims back both segments and inserts a tangent arc.\n *\n * ```js\n * path().moveTo(0,0).lineTo(10,0).lineTo(10,10).fillet(2).lineTo(0,10).close()\n * ```\n */\n fillet(radius: number): this;\n /**\n * Chamfer the last corner with a straight cut of the given distance.\n *\n * ```js\n * path().moveTo(0,0).lineTo(10,0).lineTo(10,10).chamfer(2).lineTo(0,10).close()\n * ```\n */\n chamfer(distance: number): this;\n private computeFilletGeom;\n private computeChamferGeom;\n private getSegEnd;\n private getSegDirAt;\n private trimLastSegEnd;\n /**\n * Mirror all existing segments across an axis and append the mirrored copy\n * in reverse order, creating a symmetric path. The axis passes through the\n * current cursor position.\n *\n * @param axis — \'x\' mirrors across the local X-axis (flips Y),\n * \'y\' mirrors across the local Y-axis (flips X),\n * or `[nx, ny]` for an arbitrary axis direction.\n *\n * ```js\n * // Build right half, mirror to get full symmetric profile\n * path().moveTo(0,0).lineTo(10,0).lineTo(10,5).mirror(\'x\').close()\n * ```\n */\n mirror(axis: "x" | "y" | [\n number,\n number\n ]): this;\n /**\n * Label the most recently added segment. Labels are born here and grow\n * into face names when the sketch is extruded, lofted, swept, or revolved.\n *\n * Labels must be unique within a path. Each segment can have at most one label.\n */\n label(name: string): this;\n /**\n * Label the closing segment and close the path. Shorthand for labeling the\n * implicit line from the last point back to the start, then closing.\n */\n closeLabel(name: string): Sketch;\n /** Label for the implicit closing segment (set by closeLabel). */\n private _closingLabel;\n /** Expand all segments into a flat tessellated polyline. */\n private tessellate;\n /**\n * Return the open path as a sampled 2D polyline.\n *\n * This is for construction geometry such as guide rails, measured centerlines,\n * and curve-driven helpers where the authored path should stay open instead of\n * becoming a filled sketch or stroked profile.\n *\n * **Example**\n *\n * ```ts\n * const rail = path()\n * .moveTo(24, 0)\n * .bezierTo(32, 44, 28, 92, 18, 120)\n * .toPolyline();\n * ```\n *\n * @returns A sampled open polyline.\n * @category Path Builder\n */\n toPolyline(): [\n number,\n number\n ][];\n /**\n * Close the path and return a filled `Sketch`.\n *\n * **Details**\n *\n * The winding of the polygon is automatically corrected to CCW (the expected\n * orientation for ForgeCAD sketches). If the path contains multiple sub-paths\n * (started with subsequent `moveTo` calls), the first sub-path is the outer\n * contour and subsequent sub-paths become holes subtracted from it.\n *\n * Edge labels (assigned with `.label(\'name\')`) are transferred to the resulting\n * sketch and propagate through `extrude()`, `revolve()`, `loft()`, and `sweep()`\n * into named faces on the resulting `Shape`.\n *\n * **Example**\n *\n * ```ts\n * const triangle = path().moveTo(0, 0).lineH(50).lineV(30).close();\n *\n * // With a hole (second sub-path)\n * const frame = path()\n * .moveTo(0, 0).lineH(40).lineV(30).lineH(-40).close(); // outer\n * // (hole would be added with another moveTo and line sequence before close)\n * ```\n *\n * @returns A filled `Sketch` with correct CCW winding.\n * @category Path Builder\n */\n close(): Sketch;\n /**\n * Close the path and return an offset version of the filled Sketch.\n * Positive delta expands outward, negative shrinks inward.\n */\n closeOffset(delta: number, join?: "Round" | "Square" | "Miter"): Sketch;\n /**\n * Thicken an open polyline (centerline) into a solid filled profile with uniform width.\n *\n * **Details**\n *\n * Expands the path into a closed profile `width` units wide (half-width on each side\n * of the centerline). Use `\'Round\'` for ribs, wire traces, and organic profiles —\n * it adds semicircular endcaps and rounds joins. Use `\'Square\'` (default) for sharp\n * miter joins without endcaps.\n *\n * Not the same as rounding corners of a closed polygon — for mixed sharp-and-rounded\n * outlines, build the polygon first and apply `.filletCorner([x, y], radius)` per corner.\n *\n * **Example**\n *\n * ```ts\n * // Square-join L-bracket\n * const bracket = path().moveTo(0, 0).lineH(50).lineV(-70).lineAngled(20, 235).stroke(4);\n *\n * // Round-join rib\n * const rib = path().moveTo(0, 0).lineH(60).stroke(6, \'Round\');\n *\n * // Equivalent standalone form\n * const wire = stroke([[0, 0], [50, 0], [50, -70]], 4);\n * ```\n *\n * @param width - Total stroke width (half on each side of the centerline).\n * @param join - `\'Square\'` for miter joins (default) or `\'Round\'` for rounded joins\n * and semicircular endcaps.\n * @returns A filled `Sketch` representing the thickened profile.\n * @category Path Builder\n */\n stroke(width: number, join?: "Round" | "Square"): Sketch;\n /** Split segments into sub-paths at each moveTo. */\n private splitSubPaths;\n /** Build semantic ProfileEdge array from path segments (for pathProfile compile plan). */\n private buildProfileEdges;\n private profileEdgeCountForSeg;\n /** Tessellate a sub-path (sequence of segments). */\n private tessellateSegs;\n}\n/**\n * Create a new `PathBuilder` for tracing a 2D outline point by point.\n *\n * **Details**\n *\n * `PathBuilder` is a fluent API for constructing 2D profiles using a mix of line\n * segments, arcs, bezier curves, and splines. Always start with `.moveTo(x, y)` to\n * set the starting point. Call `.close()` to get a filled `Sketch`, or `.stroke(width)`\n * to thicken an open polyline into a solid profile.\n *\n * Edge labels can be assigned with `.label(\'name\')` after any segment — they propagate\n * through extrusion, revolve, loft, and sweep into named faces on the resulting `Shape`.\n *\n * **Example**\n *\n * ```ts\n * // Closed triangle\n * const triangle = path().moveTo(0, 0).lineH(50).lineV(30).close();\n *\n * // L-shaped bracket as a stroke\n * const bracket = path().moveTo(0, 0).lineH(50).lineV(-70).lineAngled(20, 235).stroke(4);\n *\n * // Labeled edges for downstream face references\n * const slot = path()\n * .moveTo(0, 0)\n * .lineTo(30, 0).label(\'bottom\')\n * .lineTo(30, 10)\n * .lineTo(0, 10).label(\'top\')\n * .close();\n * ```\n *\n * @returns A new `PathBuilder` instance with the cursor at the origin.\n * @category Path Builder\n */\ndeclare function path(): PathBuilder;\n/**\n * Thicken a 2D polyline (centerline) into a solid filled profile of uniform width.\n *\n * **Details**\n *\n * Standalone equivalent of `path()...stroke(width, join)`. Use for centerline-based\n * geometry — ribs, wire traces, brackets. For rounding corners of a *closed* outline\n * use `.filletCorners(radius)` (all corners) or `.filletCorner([x, y], radius)`\n * (one corner) instead.\n *\n * @param points - Centerline vertices as `[x, y]` pairs (at least 2)\n * @param width - Total stroke width (half on each side of the centerline)\n * @param join - `\'Square\'` for miter joins (default) or `\'Round\'` for rounded joins and semicircular endcaps\n * @returns A filled `Sketch` representing the thickened profile\n * @category Sketch Primitives\n */\ndeclare function stroke(points: [\n number,\n number\n][], width: number, join?: "Round" | "Square"): Sketch;\n/**\n * Repeat a shape in a linear pattern along a direction vector and union the copies.\n *\n * **Details**\n *\n * Creates `count` copies of `shape`, each offset by `(dx*i, dy*i, dz*i)`\n * from the original. All copies are unioned into a single `Shape`. Distinct\n * compiler ownership is assigned to each copy so face identity via\n * owner-scoped canonical queries still works post-merge.\n *\n * **Example**\n *\n * ```ts\n * // 5 cylinders, 20mm apart along X\n * linearPattern(cylinder(10, 3), 5, 20, 0)\n * ```\n *\n * @param shape - The shape to repeat\n * @param count - Total number of copies (including the original at index 0)\n * @param dx - X step per copy\n * @param dy - Y step per copy\n * @param dz - Z step per copy (default: `0`)\n * @returns Union of all copies\n * @see {@link circularPattern} for rotational patterns\n * @see {@link mirrorCopy} for mirror patterns\n * @category Tracked Shapes\n */\ndeclare function linearPattern(shape: Shape, count: number, dx: number, dy: number, dz?: number): Shape;\ninterface CircularPatternOptions {\n /** Center X of the rotation (default: 0). Used when the rotation axis is Z. */\n centerX?: number;\n /** Center Y of the rotation (default: 0). Used when the rotation axis is Z. */\n centerY?: number;\n /** Rotation axis direction (default: [0, 0, 1] = Z axis). */\n axis?: [\n number,\n number,\n number\n ];\n /** Pivot point for the rotation (default: [0, 0, 0]). Overrides centerX/centerY when set. */\n origin?: [\n number,\n number,\n number\n ];\n}\n/**\n * Repeat a shape in a circular pattern around an axis and union the copies.\n *\n * **Details**\n *\n * Distributes `count` copies evenly around the rotation axis (360° / count\n * per step). All copies are unioned into a single `Shape`. Distinct compiler\n * ownership is assigned to each copy — post-merge face identity via\n * owner-scoped canonical queries still works for pattern descendants.\n *\n * Two calling conventions:\n * - **Simple** (Z axis): `circularPattern(shape, 6)` or\n * `circularPattern(shape, 6, centerX, centerY)`\n * - **Advanced** (arbitrary axis): `circularPattern(shape, 6, { axis, origin })`\n *\n * **Example**\n *\n * ```ts\n * // 8 holes evenly spaced around origin\n * circularPattern(cylinder(12, 4).translate(30, 0, -1), 8)\n *\n * // Circular pattern around X axis\n * circularPattern(myFeature, 4, { axis: [1, 0, 0], origin: [0, 0, 50] })\n * ```\n *\n * @param shape - The shape to repeat\n * @param count - Total number of copies (including the original at index 0)\n * @param centerXOrOpts - Center X offset, or options object with `axis`/`origin`/`centerX`/`centerY`\n * @param centerY - Center Y offset (only when using the simple two-number form)\n * @returns Union of all copies\n * @see {@link linearPattern} for linear patterns\n * @see {@link mirrorCopy} for mirror patterns\n * @category Tracked Shapes\n */\ndeclare function circularPattern(shape: Shape, count: number, centerXOrOpts?: number | CircularPatternOptions, centerY?: number): Shape;\n/**\n * Repeat a 2D sketch in a linear pattern and union the copies.\n *\n * @param sketch - The sketch to repeat\n * @param count - Total number of copies\n * @param dx - X step per copy\n * @param dy - Y step per copy (default: `0`)\n * @returns Union of all copies as a single Sketch\n * @category Tracked Shapes\n */\ndeclare function linearPattern2d(sketch: Sketch, count: number, dx: number, dy?: number): Sketch;\n/**\n * Repeat a 2D sketch in a circular pattern around a center point and union the copies.\n *\n * @param sketch - The sketch to repeat\n * @param count - Total number of copies\n * @param centerXOrOpts - Center X, or options object with `centerX`/`centerY`/`startDeg`\n * @param centerY - Center Y (only when using the two-number form)\n * @returns Union of all copies as a single Sketch\n * @category Tracked Shapes\n */\ndeclare function circularPattern2d(sketch: Sketch, count: number, centerXOrOpts?: number | {\n centerX?: number;\n centerY?: number;\n startDeg?: number;\n}, centerY?: number): Sketch;\n/**\n * Mirror a shape across a plane and union the mirror with the original.\n *\n * **Details**\n *\n * The mirror plane passes through the origin and is defined by its normal\n * vector. The mirrored copy is unioned with the original to produce a single\n * symmetric Shape.\n *\n * **Example**\n *\n * ```ts\n * // Mirror across the YZ plane (X=0)\n * mirrorCopy(box(50, 30, 10), [1, 0, 0])\n * ```\n *\n * @param shape - The shape to mirror\n * @param normal - Normal of the mirror plane (e.g. `[1, 0, 0]` for YZ plane)\n * @returns Union of original and its mirror\n * @see {@link linearPattern} for linear repetition\n * @see {@link circularPattern} for circular repetition\n * @category Tracked Shapes\n */\ndeclare function mirrorCopy(shape: Shape, normal: [\n number,\n number,\n number\n]): Shape;\n/**\n * Create a 2D rectangle centered at the origin.\n *\n * **Example**\n *\n * ```ts\n * rect(40, 20).extrude(5);\n * ```\n *\n * @param width - Width along the X axis\n * @param height - Height along the Y axis\n * @returns A centered rectangle sketch\n * @category Sketch Primitives\n */\ndeclare function rect(width: number, height: number): Sketch;\n/**\n * Create a 2D circle centered at the origin.\n *\n * **Details**\n *\n * Omit `segments` for a smooth (auto-tessellated) circle. Pass an integer to get a\n * regular polygon approximation — e.g. `6` for a hexagon, `8` for an octagon.\n *\n * **Example**\n *\n * ```ts\n * circle2d(25).extrude(10); // smooth cylinder\n * circle2d(25, 6).extrude(10); // hexagonal prism\n * ```\n *\n * @param radius - Radius of the circle\n * @param segments - Number of polygon sides (omit for smooth)\n * @returns A circle sketch\n * @category Sketch Primitives\n */\ndeclare function circle2d(radius: number, segments?: number): Sketch;\n/**\n * Create a 2D rectangle with rounded corners, centered at the origin.\n *\n * **Details**\n *\n * The corner radius is automatically clamped to `min(width/2, height/2)` so it\n * can never exceed the shape dimensions.\n *\n * **Example**\n *\n * ```ts\n * roundedRect(60, 30, 5).extrude(3);\n * ```\n *\n * @param width - Width along the X axis\n * @param height - Height along the Y axis\n * @param radius - Corner fillet radius (clamped to fit)\n * @returns A rounded rectangle sketch\n * @category Sketch Primitives\n */\ndeclare function roundedRect(width: number, height: number, radius: number): Sketch;\n/**\n * Create a 2D polygon from an array of `[x, y]` points or `Point2D` objects.\n *\n * **Details**\n *\n * Winding order is normalized automatically — clockwise (CW) input is silently\n * reversed to CCW before being passed to the geometry kernel.\n *\n * **Example**\n *\n * ```ts\n * polygon([[0, 0], [50, 0], [25, 40]]).extrude(5); // triangle\n * ```\n *\n * @param points - Closed polygon vertices as `[x, y]` pairs or `Point2D` objects\n * @returns A polygon sketch\n * @category Sketch Primitives\n */\ndeclare function polygon(points: ([\n number,\n number\n] | Point2D)[]): Sketch;\n/**\n * Create a regular polygon inscribed in a circle of the given radius.\n *\n * **Details**\n *\n * `radius` is the center-to-vertex (circumradius) distance. Use `sides` of `3` for a\n * triangle, `6` for a hexagon, etc. The first vertex is at the top (−90° from +X).\n *\n * **Example**\n *\n * ```ts\n * ngon(6, 20).extrude(10); // hexagonal prism, circumradius 20\n * ```\n *\n * @param sides - Number of polygon sides\n * @param radius - Circumradius (center to vertex)\n * @returns A regular polygon sketch\n * @category Sketch Primitives\n */\ndeclare function ngon(sides: number, radius: number): Sketch;\n/**\n * Create a 2D ellipse centered at the origin.\n *\n * **Example**\n *\n * ```ts\n * ellipse(30, 15).extrude(5);\n * ellipse(30, 15, 32).extrude(5); // lower-resolution approximation\n * ```\n *\n * @param rx - Radius along the X axis\n * @param ry - Radius along the Y axis\n * @param segments - Number of polygon sides (default 64)\n * @returns An ellipse sketch\n * @category Sketch Primitives\n */\ndeclare function ellipse(rx: number, ry: number, segments?: number): Sketch;\n/**\n * Create a slot (oblong / stadium shape) — a rectangle with semicircular ends, centered at the origin.\n *\n * **Example**\n *\n * ```ts\n * slot(40, 10).extrude(3); // 40mm long, 10mm wide slot\n * ```\n *\n * @param length - Overall length (tip to tip)\n * @param width - Overall width (= diameter of the semicircular ends)\n * @returns A slot sketch\n * @category Sketch Primitives\n */\ndeclare function slot(length: number, width: number): Sketch;\n/**\n * Create an arc-shaped slot (banana / annular sector) centered at the origin.\n *\n * **Details**\n *\n * The slot is symmetric about the +X axis. The two ends are closed with\n * semicircular caps. `pitchRadius` is the distance from the origin to the\n * centerline of the slot, and `thickness` is the radial width of the slot.\n *\n * **Example**\n *\n * ```ts\n * arcSlot(135, 74, 40).extrude(5); // pitch R135, 74° sweep, 40mm wide\n * ```\n *\n * @param pitchRadius - Distance from the center to the middle of the slot\n * @param sweepDeg - Angular extent of the slot in degrees\n * @param thickness - Radial width of the slot\n * @returns An arc slot sketch\n * @category Sketch Primitives\n */\ndeclare function arcSlot(pitchRadius: number, sweepDeg: number, thickness: number): Sketch;\n/**\n * Create a star shape with alternating outer and inner radii.\n *\n * **Details**\n *\n * The first tip points down (−90°). The same shape — and the whole family of\n * alternating-radius rosettes — is a two-line recipe over the layout\n * primitives:\n *\n * ```ts\n * const tips = polygonVertices(5, 30, { startDeg: -90 });\n * const valleys = polygonVertices(5, 12, { startDeg: -90 + 180 / 5 });\n * polygon(tips.flatMap((tip, i) => [tip, valleys[i]])).extrude(4);\n * ```\n *\n * **Example**\n *\n * ```ts\n * star(5, 30, 12).extrude(4); // five-pointed star\n * ```\n *\n * @param points - Number of star points\n * @param outerR - Radius to the tips\n * @param innerR - Radius to the valleys\n * @returns A star sketch\n * @softDeprecated polygon(tips.flatMap((tip, i) => [tip, valleys[i]])) with tips = polygonVertices(n, outerR, { startDeg: -90 }) and valleys = polygonVertices(n, innerR, { startDeg: -90 + 180 / n })\n * @deprecated use polygon(tips.flatMap((tip, i) => [tip, valleys[i]])) with tips = polygonVertices(n, outerR, { startDeg: -90 }) and valleys = polygonVertices(n, innerR, { startDeg: -90 + 180 / n })\n * @category Sketch Primitives\n */\ndeclare function star(points: number, outerR: number, innerR: number): Sketch;\ntype Vec3$7 = [\n number,\n number,\n number\n];\ninterface SurfacePatchOptions {\n /** Number of samples along each direction. Default 24. */\n resolution?: number;\n /** Thickness of the generated solid. Default 0 for an open exact sheet. */\n thickness?: number;\n /** Allow explicit approximation for non-exact curve inputs such as Curve3D samples. */\n approximate?: boolean;\n}\n/**\n * Create a smooth surface patch from 4 boundary curves (Coons patch).\n *\n * The four curves form the boundary of a quadrilateral patch:\n * - bottom: u=0..1 at v=0 (from corner00 to corner10)\n * - top: u=0..1 at v=1 (from corner01 to corner11)\n * - left: v=0..1 at u=0 (from corner00 to corner01)\n * - right: v=0..1 at u=1 (from corner10 to corner11)\n *\n * The interior is filled using bilinear Coons patch interpolation:\n * P(u,v) = Lc(u,v) + Ld(u,v) - B(u,v)\n *\n * The result is a thin solid created by offsetting the surface mesh\n * along its normals by the specified thickness.\n *\n * Note: curves should meet at corners. Small gaps are tolerated.\n *\n * @softDeprecated Surface.Patch(curves, { approximate: true }) — Surface.Patch defaults approximate: false (pass { approximate: true } for sampled Curve3D/Vec3[] boundaries) and returns an open sheet; use .thicken(t) instead of the thickness option\n * @deprecated use Surface.Patch(curves, { approximate: true }) — Surface.Patch defaults approximate: false (pass { approximate: true } for sampled Curve3D/Vec3[] boundaries) and returns an open sheet; use .thicken(t) instead of the thickness option\n */\ndeclare function surfacePatch(curves: {\n bottom: Curve3D | Vec3$7[];\n top: Curve3D | Vec3$7[];\n left: Curve3D | Vec3$7[];\n right: Curve3D | Vec3$7[];\n}, options?: SurfacePatchOptions): Shape;\ninterface SvgImportOptions {\n /**\n * Which geometry channels to include:\n * - `auto`: prefer fills; if no fill geometry exists, fall back to strokes\n * - `fill`: import only filled regions\n * - `stroke`: import only stroke geometry\n * - `fill-and-stroke`: include both\n */\n include?: "auto" | "fill" | "stroke" | "fill-and-stroke";\n /** Keep all disconnected regions, or only the largest. */\n regionSelection?: "all" | "largest";\n /** Keep at most this many regions (largest-first). */\n maxRegions?: number;\n /** Drop regions below this absolute area threshold. */\n minRegionArea?: number;\n /** Drop regions below this ratio of largest-region area. */\n minRegionAreaRatio?: number;\n /**\n * Curve flattening tolerance in SVG user units.\n * Smaller = more segments, higher fidelity.\n */\n flattenTolerance?: number;\n /** Minimum segment count for arc discretization. */\n arcSegments?: number;\n /** Global scale applied after SVG parsing. */\n scale?: number;\n /**\n * Maximum imported sketch width.\n * If exceeded, geometry is uniformly downscaled to fit.\n */\n maxWidth?: number;\n /**\n * Maximum imported sketch height.\n * If exceeded, geometry is uniformly downscaled to fit.\n */\n maxHeight?: number;\n /**\n * Recenter imported geometry so its 2D bounds center is at CAD origin.\n */\n centerOnOrigin?: boolean;\n /** Simplification tolerance for final sketch cleanup. */\n simplify?: number;\n /**\n * Flip SVG Y-down coordinates to CAD Y-up.\n * Enabled by default.\n */\n invertY?: boolean;\n}\ninterface TextOptions {\n /**\n * Cap height of the text in model units.\n * All other dimensions (stroke weight, spacing) scale proportionally.\n * @default 10\n */\n size?: number;\n /**\n * Extra space between characters in model units.\n * Negative values tighten the tracking.\n * @default 0\n */\n letterSpacing?: number;\n /**\n * Horizontal alignment relative to x = 0.\n * - `\'left\'` — left edge at x = 0 (default)\n * - `\'center\'` — centred on x = 0\n * - `\'right\'` — right edge at x = 0\n * @default \'left\'\n */\n align?: "left" | "center" | "right";\n /**\n * Vertical alignment relative to y = 0.\n * - `\'baseline\'` — y = 0 is the text baseline (bottom of capital letters)\n * - `\'center\'` — y = 0 is the vertical midpoint of the cap height\n * - `\'top\'` — y = 0 is the top of capital letters\n * @default \'baseline\'\n */\n baseline?: "baseline" | "center" | "top";\n /**\n * Font to use for text rendering.\n *\n * - `\'sans-serif\'` or `\'inter\'` — bundled Inter font (works everywhere, including browser)\n * - **file path** — path to a TTF, OTF, or WOFF font file (CLI/Node only)\n * - **Font object** — a previously loaded opentype.js Font (from `loadFont()`)\n * - **omitted** — uses the bundled Inter font (same as `\'sans-serif\'`)\n *\n * @example\n * text2d(\'Hello World\', { size: 10 }) // default Inter\n * text2d(\'Custom Font\', { size: 10, font: \'/path/to/font.ttf\' })\n */\n font?: string | opentype$1.Font;\n /**\n * Bezier flattening tolerance in model units.\n * Smaller = more polygon segments = smoother curves.\n * @default auto (0.5% of size)\n */\n flattenTolerance?: number;\n}\n/**\n * Build a filled 2D Sketch from a text string.\n *\n * **Details**\n *\n * The Sketch origin is at the left end of the text baseline by default. Use `align`\n * and `baseline` options to adjust placement. Text is rendered using the bundled\n * Inter font by default, or any TTF/OTF/WOFF font you provide.\n *\n * `text2d()` creates real geometry. For temporary viewport annotations, prefer\n * `Viewport.label()` so the text stays off the geometry and OCCT compile paths.\n * Do not use either form of text to make unclear production geometry readable;\n * model the physical artifact clearly instead.\n *\n * Alignment reference table:\n *\n * | `align` | `baseline` | Origin |\n * |------------|--------------|-------------------------------------|\n * | `\'left\'` | `\'baseline\'` | Bottom-left of first char (default) |\n * | `\'center\'` | `\'center\'` | Dead center of text block |\n * | `\'right\'` | `\'top\'` | Top-right corner |\n *\n * **Example**\n *\n * ```ts\n * // Extruded nameplate\n * text2d(\'FORGE CAD\', { size: 8 }).extrude(1.2);\n *\n * // Centered label on the XY plane\n * text2d(\'V 2.0\', { size: 6, align: \'center\', baseline: \'center\' });\n *\n * // Engraved text cut into the top face of a box\n * const label = text2d(\'REV A\', { size: 5, align: \'center\', baseline: \'center\' });\n * plate.subtract(label.onFace(plate, \'top\', { protrude: -0.5 }).extrude(1));\n *\n * // Custom TTF font\n * text2d(\'Hello\', { size: 10, font: \'/path/to/Arial.ttf\' }).extrude(1);\n *\n * // Pre-loaded font for reuse\n * const font = loadFont(\'/path/to/Arial Bold.ttf\');\n * text2d(\'Title\', { size: 12, font }).extrude(1.5);\n * ```\n *\n * @param content - The text string to render\n * @param options - Rendering options (size, letterSpacing, align, baseline, font, flattenTolerance)\n * @returns A filled Sketch of the rendered letterforms\n * @see {@link textWidth} to measure text width without creating geometry\n * @see {@link loadFont} to pre-load a font for reuse\n * @see {@link Viewport.label} for temporary viewport annotations\n * @category Sketch Text\n */\ndeclare function text2d(content: string, options?: TextOptions): Sketch;\n/**\n * Measure the rendered advance width of a string without creating any geometry.\n *\n * **Details**\n *\n * Uses the same font metrics as `text2d()`. Useful for computing layout dimensions\n * before building the actual sketch — e.g. sizing a plate to fit a label.\n *\n * **Example**\n *\n * ```ts\n * const w = textWidth(\'SERIAL: 001\', { size: 6 });\n * const plate = box(w + 10, 12, 2);\n * ```\n *\n * @param content - The text string to measure\n * @param options - Accepts `size`, `letterSpacing`, and `font` (same as `text2d`)\n * @returns Rendered advance width in model units\n * @see {@link text2d} to create the actual geometry\n * @category Sketch Text\n */\ndeclare function textWidth(content: string, options?: Pick<TextOptions, "size" | "letterSpacing" | "font">): number;\ninterface ListParamFieldDef {\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n integer?: boolean;\n boolean?: boolean;\n choices?: string[];\n}\n/**\n * Shorthand alias for `Param.number()` — declare a numeric slider parameter.\n *\n * See `Param.number()` for full documentation, examples, and default range rules.\n *\n * @param name - Display label and override key\n * @param defaultValue - Initial value when no override is set\n * @param opts - Range, step, unit, and display options\n * @returns The current value (default or overridden)\n * @see {@link Param} for the full parameter namespace\n * @category Parameters\n * @internal\n */\ndeclare function param(name: string, defaultValue: number, opts?: {\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n integer?: boolean;\n reverse?: boolean;\n}): number;\n/**\n * Parameter namespace — the primary way to declare user-facing parameters.\n *\n * **Details**\n *\n * All parameter types live under `Param.*` for discoverability and clean scripts.\n * The standalone function `param()` is kept as the shorthand alias for\n * `Param.number()`.\n *\n * **Example**\n *\n * ```ts\n * const width = Param.number("Width", 50, { min: 10, max: 100, unit: "mm" });\n * const label = Param.string("Label", "Hello World");\n * const show = Param.bool("Show Holes", true);\n * const style = Param.choice("Style", "round", ["round", "square"]);\n * ```\n *\n * @concept\n * @category Parameters\n */\ndeclare const Param: {\n /**\n * Declare a numeric parameter that renders as a slider in the UI.\n *\n * **Details**\n *\n * Each call registers a slider control. When the user moves the\n * slider the entire script re-executes with the new value. Parameter values\n * are also overridable from `require()` imports or the CLI `--param` flag —\n * the `name` string is the key used in both cases.\n *\n * Default range rules when options are omitted:\n * - `min` defaults to `0`\n * - `max` defaults to `defaultValue * 4`\n * - `step` is auto-calculated: `1` for integer params, `0.1` for ranges ≤ 100,\n * `1` for larger ranges\n *\n * The `unit` option is cosmetic only — no conversion is performed.\n * Use `integer: true` for counts, sides, quantities (rounds to whole numbers;\n * step defaults to `1`).\n *\n * **Example**\n *\n * ```ts\n * const width = Param.number("Width", 50);\n * const angle = Param.number("Angle", 45, { min: 0, max: 180, unit: "°" });\n * const sides = Param.number("Sides", 6, { min: 3, max: 12, integer: true });\n * ```\n *\n * **Parameter overrides** — key must match `name` exactly:\n *\n * ```ts\n * // Via require()\n * const bracket = require("./bracket.forge.js", { Width: 80 });\n *\n * // Via CLI\n * // forgecad run model.forge.js --param "Wall Thickness=3"\n * ```\n *\n * Also available as the shorthand alias `param()`.\n */\n number(name: string, defaultValue: number, opts?: {\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n integer?: boolean;\n reverse?: boolean;\n }): number;\n /**\n * Declare a string parameter that renders as a text input in the UI.\n *\n * **Details**\n *\n * String parameters let users type free-form text — labels, names, inscriptions,\n * file paths, etc. The `name` string is the override key.\n *\n * **Example**\n *\n * ```ts\n * const label = Param.string("Label", "Hello World");\n * const name = Param.string("Name", "Part-001", { maxLength: 20 });\n * ```\n *\n * Override via import:\n * ```ts\n * const tag = require("./tag.forge.js", { Label: "Custom Text" });\n * ```\n *\n * Only available as `Param.string()` — no standalone alias.\n */\n string(name: string, defaultValue: string, opts?: {\n maxLength?: number;\n }): string;\n /**\n * Declare a boolean parameter that renders as a checkbox in the UI.\n *\n * **Details**\n *\n * Internally stored as `0`/`1`. When overriding from CLI or `require()`, pass\n * `1` for true and `0` for false. The `name` string is the override key.\n *\n * **Example**\n *\n * ```ts\n * const showHoles = Param.bool("Show Holes", true);\n * if (showHoles) return difference(plate, cylinder(10, 5).translate(50, 30, 0));\n * return plate;\n * ```\n *\n * Override via import:\n * ```ts\n * const pan = require("./pan.forge.js", { "Show Lid": 0 });\n * ```\n *\n */\n bool(name: string, defaultValue: boolean): boolean;\n /**\n * Declare a choice parameter that renders as a dropdown in the UI.\n *\n * **Details**\n *\n * `defaultValue` must exactly match one entry in `choices`. Returns the\n * selected string label. Prefer `Param.choice` over `Param.number` when a slider\n * would hide intent — named choices like `"wok"` are self-describing.\n *\n * Overrides may be passed as the choice label string (preferred) or as a\n * numeric index. The `name` string is the override key.\n *\n * **Example**\n *\n * ```ts\n * const panStyle = Param.choice("Pan Style", "frying-pan", ["frying-pan", "saute-pan", "wok"]);\n * if (panStyle === "wok") return buildWok();\n * ```\n *\n * Override via import:\n * ```ts\n * const pan = require("./pan.forge.js", { "Pan Style": "wok" });\n * ```\n *\n * Override via CLI:\n * ```bash\n * forgecad run model.forge.js --param "Pan Style=wok"\n * ```\n *\n */\n choice(name: string, defaultValue: string, choices: string[]): string;\n /**\n * Declare a list parameter — an array of struct items with per-field UI controls.\n *\n * **Details**\n *\n * Each item in the list is a struct whose fields each render as their own\n * control (slider, checkbox, or dropdown). The user can add/remove rows up to\n * `minItems`/`maxItems` bounds.\n *\n * Field types:\n * - Boolean fields (`boolean: true` in field defs) return as `boolean`\n * - Choice fields (`choices: [...]` in field defs) return as `string`\n * - All other fields return as `number`\n */\n list<T extends Record<string, number | boolean | string>>(name: string, defaultItems: T[], opts: {\n fields: Partial<Record<keyof T & string, ListParamFieldDef>>;\n minItems?: number;\n maxItems?: number;\n }): T[];\n};\ntype ExactCurveInput = EdgeRef | NurbsCurve3D | Curve3D | HermiteCurve3D | QuinticHermiteCurve3D | Vec3[];\ninterface SurfaceMatchConstraintInput {\n target: EdgeRef;\n continuity?: SurfaceContinuity;\n}\ninterface SurfaceCommonOptions {\n resolution?: number;\n approximate?: boolean;\n}\ninterface SurfaceAnalyticCommonOptions {\n /** Tessellation/sample hint for non-OCCT previews and diagnostics. */\n resolution?: number;\n /** Optional normalized UV trims in this surface\'s bounded parameter domain. */\n trim?: SurfaceTrimOptions;\n}\ninterface SurfacePlaneOptions extends SurfaceAnalyticCommonOptions {\n /** Center of the finite plane patch. Defaults to the origin. */\n origin?: Vec3;\n /** Plane normal. Defaults to +Z. */\n normal?: Vec3;\n /** Plane-local +U direction. Defaults to a stable perpendicular axis. */\n xAxis?: Vec3;\n /** Physical width along the +U axis. */\n width: number;\n /** Physical height along the +V axis. */\n height: number;\n}\ninterface SurfaceCylinderOptions extends SurfaceAnalyticCommonOptions {\n /** Center of the cylinder base. Defaults to the origin. */\n origin?: Vec3;\n /** Cylinder axis from base toward top. Defaults to +Z. */\n axis?: Vec3;\n /** Direction for angle 0 around the axis. Defaults to a stable perpendicular axis. */\n xAxis?: Vec3;\n radius: number;\n height: number;\n startDeg?: number;\n endDeg?: number;\n}\ninterface SurfaceConeOptions extends SurfaceAnalyticCommonOptions {\n /** Center of the cone/frustum base. Defaults to the origin. */\n origin?: Vec3;\n /** Cone axis from base toward top. Defaults to +Z. */\n axis?: Vec3;\n /** Direction for angle 0 around the axis. Defaults to a stable perpendicular axis. */\n xAxis?: Vec3;\n radiusBottom: number;\n radiusTop: number;\n height: number;\n startDeg?: number;\n endDeg?: number;\n}\ninterface SurfaceSphereOptions extends SurfaceAnalyticCommonOptions {\n center?: Vec3;\n /** Polar axis. Defaults to +Z. */\n axis?: Vec3;\n /** Direction for longitude 0 around the axis. Defaults to a stable perpendicular axis. */\n xAxis?: Vec3;\n radius: number;\n startDeg?: number;\n endDeg?: number;\n minLatitudeDeg?: number;\n maxLatitudeDeg?: number;\n}\ninterface SurfaceTorusOptions extends SurfaceAnalyticCommonOptions {\n center?: Vec3;\n /** Axis through the torus hole. Defaults to +Z. */\n axis?: Vec3;\n /** Direction for major angle 0 around the axis. Defaults to a stable perpendicular axis. */\n xAxis?: Vec3;\n majorRadius: number;\n minorRadius: number;\n startDeg?: number;\n endDeg?: number;\n tubeStartDeg?: number;\n tubeEndDeg?: number;\n}\ninterface SurfaceSolidOptions {\n tolerance?: number;\n validate?: boolean;\n}\ninterface SurfacePatchOptions$1 extends SurfaceCommonOptions {\n match?: Partial<Record<"u0" | "u1" | "v0" | "v1", SurfaceMatchConstraintInput>>;\n style?: SurfaceFillStyle;\n}\ninterface SurfaceFillInput extends SurfaceCommonOptions {\n boundaries: Array<{\n name: string;\n curve: ExactCurveInput;\n }>;\n match?: Record<string, SurfaceMatchConstraintInput>;\n style?: SurfaceFillStyle;\n}\ninterface SurfaceBoundaryInput extends SurfaceCommonOptions {\n u: [\n ExactCurveInput,\n ExactCurveInput\n ];\n v: [\n ExactCurveInput,\n ExactCurveInput\n ];\n match?: Partial<Record<"u0" | "u1" | "v0" | "v1", SurfaceMatchConstraintInput>>;\n style?: SurfaceFillStyle;\n}\ninterface SurfaceExtendOptions {\n edge: "u0" | "u1" | "v0" | "v1";\n length: number;\n continuity?: SurfaceContinuity;\n}\ninterface SurfacePlaneOp {\n normal: Vec3;\n offset?: number;\n}\ninterface BlendEdgeOptions {\n shape?: Shape;\n edges: EdgeRef[];\n radius: number;\n continuity?: SurfaceContinuity;\n segments?: number;\n}\ninterface BlendCornerYOptions extends Omit<BlendEdgeOptions, "edges"> {\n edges: EdgeRef[];\n cornerTolerance?: number;\n minBranchAngleDeg?: number;\n}\ninterface BlendSurfaceOptions extends SurfaceFillInput {\n}\ninterface EdgeContinuityThresholds {\n continuity?: SurfaceContinuity;\n samples?: number;\n positionTolerance?: number;\n tangentToleranceDeg?: number;\n curvatureTolerance?: number;\n}\ninterface EdgeContinuityEdgeReport {\n edgeIndex: number;\n continuity: SurfaceContinuity;\n maxPositionalGap: number;\n maxTangentAngleDeg: number;\n maxCurvatureGap: number;\n}\ninterface EdgeContinuityReport {\n requested: SurfaceContinuity;\n passed: boolean;\n sampledEdges: number;\n edges: EdgeContinuityEdgeReport[];\n worst: {\n continuity: SurfaceContinuity;\n maxPositionalGap: number;\n maxTangentAngleDeg: number;\n maxCurvatureGap: number;\n };\n}\ninterface CurvatureSample {\n t: number;\n curvature: number;\n point: Vec3;\n}\ninterface SurfaceHealthReport {\n tinyEdges: Array<{\n index: number;\n length: number;\n }>;\n sliverFaces: Array<{\n index: number;\n area: number;\n sliverScore: number;\n }>;\n valid: boolean;\n}\ninterface BRepValidityOptions {\n tolerance?: number;\n requireClosed?: boolean;\n requireManifold?: boolean;\n requireSolid?: boolean;\n minVolume?: number;\n}\ntype BRepValidityIssueKind = "brep-check-invalid" | "open-edge" | "non-manifold-edge" | "degenerate-face" | "zero-volume" | "inverted-orientation";\ninterface BRepValidityIssue {\n kind: BRepValidityIssueKind;\n message: string;\n index?: number;\n faceCount?: number;\n length?: number;\n volume?: number;\n area?: number;\n}\ninterface BRepValidityReport {\n ok: boolean;\n valid: boolean;\n closed: boolean;\n manifold: boolean;\n solid: boolean;\n oriented: boolean;\n volume: number;\n edgeCount: number;\n faceCount: number;\n openEdges: number;\n nonManifoldEdges: number;\n degenerateFaces: number;\n errors: BRepValidityIssue[];\n}\ndeclare const Surface: {\n /** Create a finite analytic plane sheet that can be trimmed, sewn, thickened, or used as a low-level face. */\n Plane(options: SurfacePlaneOptions): Shape;\n /** Create a finite analytic cylindrical sheet, optionally bounded by start/end angles. */\n Cylinder(options: SurfaceCylinderOptions): Shape;\n /** Create a finite analytic conical or frustum sheet, optionally bounded by start/end angles. */\n Cone(options: SurfaceConeOptions): Shape;\n /** Create a finite analytic spherical sheet bounded by longitude and latitude ranges. */\n Sphere(options: SurfaceSphereOptions): Shape;\n /** Create a finite analytic torus sheet bounded by major and tube angle ranges. */\n Torus(options: SurfaceTorusOptions): Shape;\n /**\n * Create an exact NURBS surface from a grid of control points.\n *\n * The control grid is indexed as `controlGrid[u][v]` — each row is a curve in\n * the V direction, and columns trace curves in the U direction. With default\n * options this builds a bicubic non-rational B-spline sheet with uniform\n * clamped knots; `NurbsSurfaceOptions` controls degrees, weights, knots,\n * trim loops, tessellation, domain, and an optional `thickness` to return a\n * thin solid instead of an open sheet.\n *\n * @example\n * // Simple 4×4 control grid — a gently curved surface\n * const grid = [\n * [[0,0,0], [10,0,2], [20,0,2], [30,0,0]],\n * [[0,10,1], [10,10,5], [20,10,5], [30,10,1]],\n * [[0,20,1], [10,20,5], [20,20,5], [30,20,1]],\n * [[0,30,0], [10,30,2], [20,30,2], [30,30,0]],\n * ];\n * const sheet = Surface.Nurbs(grid);\n * const panel = Surface.Nurbs(grid, { thickness: 2 });\n */\n Nurbs(controlGrid: Vec3[][], options?: NurbsSurfaceOptions): Shape;\n Ruled(curveA: ExactCurveInput, curveB: ExactCurveInput, options?: SurfaceCommonOptions): Shape;\n /**\n * Create a smooth open surface sheet from 4 boundary curves (Coons patch).\n *\n * The four curves form the boundary of a quadrilateral patch and should meet\n * at corners (small gaps are tolerated). Boundaries are exact by default:\n * pass `NurbsCurve3D` values or `Shape.edge()` refs, or set\n * `{ approximate: true }` to accept sampled `Curve3D`/`Vec3[]` boundaries.\n * The result is an open sheet — call `.thicken(t)` for a thin solid.\n *\n * @example\n * const sheet = Surface.Patch({ bottom, top, left, right });\n * const panel = Surface.Patch({ bottom, top, left, right }).thicken(1.5);\n */\n Patch(curves: {\n bottom: ExactCurveInput;\n top: ExactCurveInput;\n left: ExactCurveInput;\n right: ExactCurveInput;\n }, options?: SurfacePatchOptions$1): Shape;\n Boundary(input: SurfaceBoundaryInput): Shape;\n Fill(input: SurfaceFillInput): Shape;\n Sew(shapes: Shape[], options?: {\n tolerance?: number;\n }): Shape;\n /** Sew surface faces or consume an existing sewn shell and make a solid B-rep. */\n Solid(input: Shape | Shape[], options?: SurfaceSolidOptions): Shape;\n Extend(shape: Shape, options: SurfaceExtendOptions): Shape;\n Trim(shape: Shape, tool: Shape | SurfacePlaneOp): Shape;\n Split(shape: Shape, tool: Shape | SurfacePlaneOp): [\n Shape,\n Shape\n ];\n Match(shape: Shape, options: {\n edge: "u0" | "u1" | "v0" | "v1";\n target: EdgeRef;\n continuity?: SurfaceContinuity;\n }): Shape;\n /**\n * Renamed alias of `Surface.Match()` — kept runtime-alive for existing scripts.\n *\n * @softDeprecated Surface.Match()\n * @deprecated use Surface.Match()\n */\n MatchEdge: (shape: Shape, options: {\n edge: "u0" | "u1" | "v0" | "v1";\n target: EdgeRef;\n continuity?: SurfaceContinuity;\n }) => Shape;\n};\ndeclare const Blend: {\n Edge(options: BlendEdgeOptions): Shape;\n Surface(options: BlendSurfaceOptions): Shape;\n /**\n * @alpha\n * Current implementation uses continuity-controlled edge fillets on solid edges.\n * It does not yet provide a dedicated open-sheet or multi-patch Y-corner solver.\n * Follow progress: https://github.com/KoStard/forgecad-private/issues/162\n */\n CornerY(options: BlendCornerYOptions): Shape;\n};\ndeclare const Analysis: {\n EdgeContinuity(shape: Shape, options?: EdgeContinuityThresholds): EdgeContinuityReport;\n SurfaceContinuity(shape: Shape, options?: EdgeContinuityThresholds): EdgeContinuityReport;\n CurvatureComb(input: NurbsCurve3D | EdgeRef, options?: {\n samples?: number;\n }): CurvatureSample[];\n SurfaceHealth(shape: Shape, options?: {\n tinyEdgeThreshold?: number;\n sliverThreshold?: number;\n }): SurfaceHealthReport;\n /** Validate B-rep/shell/solid structure and return closedness, manifoldness, orientation, and issue diagnostics. */\n BRepValidity(shape: Shape, options?: BRepValidityOptions): BRepValidityReport;\n};\ntype VerificationStatus = "pass" | "fail";\ninterface VerificationResult {\n id: string;\n label: string;\n status: VerificationStatus;\n message: string;\n /** Verification category for quality gates and UI filtering. */\n kind?: "interface";\n /** 1-based source line number, if captured from the call stack */\n line?: number;\n /** Human-readable expected value for display */\n expected?: string;\n /** Human-readable actual value for display */\n actual?: string;\n /** Spec name — when set, the UI groups this result under a collapsible section */\n group?: string;\n}\ninterface SpecResult {\n /** Spec name */\n name: string;\n /** Number of checks that passed */\n passed: number;\n /** Total number of checks */\n total: number;\n /** The individual verification results added by this spec */\n results: VerificationResult[];\n}\n/**\n * A named, reusable bundle of verification checks.\n *\n * Create one with `spec(name, checkFn)`, then call `.check(...shapes)` to\n * run the checks. Results appear grouped under the spec name in the Checks\n * panel. Can be reused across multiple shapes and imported via `require()`.\n *\n * @category Parameters\n */\ninterface Spec {\n /** The display name of this spec */\n readonly name: string;\n /**\n * Run this spec\'s checks against one or more shapes.\n * All `verify.*` calls inside the check function are automatically\n * grouped under this spec\'s name in the Checks panel.\n */\n check(...args: unknown[]): SpecResult;\n}\n/**\n * Create a named, reusable bundle of verification checks.\n *\n * **Details**\n *\n * A spec groups related `verify.*` calls under a collapsible header in the\n * Checks panel. This makes large check suites scannable. Specs can be applied\n * to multiple shapes and can check relationships between parts.\n *\n * Specs can be defined in separate `.forge.js` files and imported via\n * `require()` to share them across models.\n *\n * `spec.check()` returns a `SpecResult` — you can inspect it programmatically\n * or ignore the return value and let the Checks panel show results.\n *\n * **Example**\n *\n * ```ts\n * const printable = spec("Fits printer bed", (shape) => {\n * verify.notEmpty("Has geometry", shape);\n * const bb = shape.boundingBox();\n * verify.lessThan("Width < 220mm", bb.max[0] - bb.min[0], 220);\n * verify.lessThan("Depth < 220mm", bb.max[1] - bb.min[1], 220);\n * verify.lessThan("Height < 250mm", bb.max[2] - bb.min[2], 250);\n * });\n *\n * // Reuse on multiple shapes\n * printable.check(bracket);\n * printable.check(standoff);\n *\n * // Check relationships between parts\n * const fitSpec = spec("Assembly fit", (partA, partB) => {\n * verify.notColliding("No interference", partA, partB, 10);\n * });\n * fitSpec.check(bracket, standoff);\n * ```\n *\n * **Spec-first workflow:** Write specs before building geometry. Checks go\n * from red to green as you build — effectively TDD for CAD.\n *\n * @param name - Display name in the Checks panel (e.g. `"Fits printer bed"`)\n * @param checkFn - Function that calls `verify.*` methods; receives args from `.check()`\n * @returns A `Spec` object with a `.check(...args)` method\n * @category Parameters\n */\ndeclare function spec(name: string, checkFn: (...args: any[]) => void): Spec;\ninterface ShapeLike {\n boundingBox(): {\n min: number[];\n max: number[];\n };\n isEmpty(): boolean;\n intersect?(other: ShapeLike): ShapeLike;\n volume(): number;\n surfaceArea(): number;\n}\ninterface ConnectorDistanceLike {\n connectorDistance(nameA: string, nameB: string): number;\n}\ninterface FaceRefLike {\n normal: [\n number,\n number,\n number\n ];\n center: [\n number,\n number,\n number\n ];\n}\ndeclare const verify: {\n /**\n * Custom predicate check.\n *\n * @param label Short name shown in the panel ("gear clearance")\n * @param check Function that returns true (pass) or false (fail)\n * @param message Optional extra context shown on failure\n */\n that(label: string, check: () => boolean, message?: string): void;\n /**\n * Check that two numbers are approximately equal (within tolerance).\n */\n equal(label: string, actual: number, expected: number, tolerance?: number, message?: string): void;\n /**\n * Check that two numbers are NOT equal (differ by more than tolerance).\n */\n notEqual(label: string, actual: number, unexpected: number, tolerance?: number, message?: string): void;\n /** Check that actual > min. */\n greaterThan(label: string, actual: number, min: number, message?: string): void;\n /** Check that actual < max. */\n lessThan(label: string, actual: number, max: number, message?: string): void;\n /** Check that min <= actual <= max. */\n inRange(label: string, actual: number, min: number, max: number, message?: string): void;\n /**\n * Check that the bounding-box centers of two shapes coincide within tolerance (mm).\n */\n centersCoincide(label: string, a: ShapeLike, b: ShapeLike, tolerance?: number): void;\n /**\n * Check the distance between two named connectors on a shape or group.\n *\n * Use this when connectors + `matchTo()` define a static assembly interface.\n * It proves the mate at runtime, unlike a plain source-level connector\n * declaration. The common case is `expected = 0`, meaning the two connector\n * origins should coincide after placement.\n *\n * **Example**\n *\n * ```ts\n * verify.connectorDistance("leg is seated", bench, "Rail.leg_0", "Leg0.head", 0, 0.01);\n * ```\n */\n connectorDistance(label: string, target: ConnectorDistanceLike, connectorA: string, connectorB: string, expected?: number, tolerance?: number): void;\n /**\n * Declare the expected physical connectivity component count for the returned visible model.\n *\n * **Details**\n *\n * Use this for generated mechanical models that should have a clear component graph:\n * one connected fixture, a purchased part plus a removable cartridge, a root assembly plus\n * named intentional ghosts, and so on. `forgecad inspect mechanical-integrity` resolves the returned\n * visible objects with the same physical-connectivity analysis used in the quality gate and\n * fails if the actual component count differs.\n *\n * This catches the common generated-CAD failure where a script returns a visually plausible\n * artifact but the handle, screw, washer, cover, or terminal block is actually a separate island.\n *\n * **Example**\n *\n * ```ts\n * verify.physicalComponentCount("vise is one connected installed assembly", 1);\n * ```\n */\n physicalComponentCount(label: string, expected: number): void;\n /**\n * Declare that two visible objects intentionally overlap because the overlap is real manufacturing intent.\n *\n * **Details**\n *\n * Use this only for overlaps that a mechanical reviewer would accept as actual matter sharing volume:\n * welded/fused regions, overmolded inserts, potted electronics, cast-in hardware, or deliberately\n * bonded laminations. This is not a shortcut for screws without holes, shafts without bores, covers\n * without pockets, or parts placed with collision as a positioning hack.\n *\n * `forgecad inspect mechanical-integrity --collisions` only honors this declaration when both shapes are\n * returned as visible objects and the exact collision report finds that same object pair. Unused or\n * non-visible declarations fail the quality gate so annotations cannot hide unrelated collisions.\n *\n * **Example**\n *\n * ```ts\n * verify.intentionalOverlap("rubber grip is overmolded on handle", rubberGrip, handleCore, "overmolded insert");\n * ```\n */\n intentionalOverlap(label: string, a: ShapeLike, b: ShapeLike, reason: string): void;\n /**\n * Check that two shapes do not share positive volume.\n *\n * Face-to-face contact is allowed; use `verify.minClearance()` when an actual\n * running gap is required.\n *\n * @param searchLength Search radius for minGap diagnostics (mm, default 1.0)\n */\n notColliding(label: string, a: ShapeLike, b: ShapeLike, searchLength?: number): void;\n /**\n * Check that a minimum clearance gap exists between two shapes.\n */\n minClearance(label: string, a: ShapeLike, b: ShapeLike, minGap: number, searchLength?: number): void;\n /**\n * Check that the clearance gap between two shapes is inside an allowed range.\n *\n * **Details**\n *\n * Use this for seated and retained interfaces where a part must be close\n * enough to be mechanically accountable, but must not collide beyond the\n * allowed minimum. It catches both failure modes that make generated CAD look\n * fake: parts floating away from their receiver, and parts intersecting their\n * receiver because the pocket, bore, or running clearance was not modeled.\n *\n * For contact, use a narrow range such as `[-0.01, 0.05]` to tolerate tiny\n * numerical noise. For a running fit, use the intended clearance band.\n *\n * Manifold-backed shapes use exact min-gap distance. Other backends use a\n * mesh-derived min-gap check and say so in the verification message; keep\n * `forgecad inspect mechanical-integrity --collisions` in the acceptance gate for\n * positive-volume interference.\n *\n * **Example**\n *\n * ```ts\n * verify.clearanceBetween("cover is seated on gasket", cover, gasket, -0.01, 0.05);\n * verify.clearanceBetween("carriage runs inside rail", carriage, rail, 0.2, 0.5);\n * ```\n */\n clearanceBetween(label: string, a: ShapeLike, b: ShapeLike, minGap: number, maxGap: number, searchLength?: number): void;\n /**\n * Check that two face normals are parallel (within toleranceDeg degrees).\n */\n parallel(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void;\n /**\n * Check that two face normals are perpendicular (within toleranceDeg degrees).\n */\n perpendicular(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void;\n /**\n * Check that a face is coplanar with (same plane as) another face,\n * meaning they are parallel AND their centers lie on the same plane.\n */\n coplanar(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number, toleranceMm?: number): void;\n /**\n * Check that a face center lies at a specific position (within toleranceMm).\n */\n faceAt(label: string, face: FaceRefLike, expectedPos: [\n number,\n number,\n number\n ], toleranceMm?: number): void;\n /**\n * Check that two face normals point in the same direction (not antiparallel).\n * Stricter than parallel — both |angle| AND sign must match.\n */\n sameDirection(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void;\n /**\n * Check that a shape is empty.\n */\n isEmpty(label: string, shape: ShapeLike, message?: string): void;\n /**\n * Check that a shape is NOT empty.\n */\n notEmpty(label: string, shape: ShapeLike, message?: string): void;\n /**\n * Check that a shape\'s volume is approximately equal to expected (mm³).\n *\n * @param expected Expected volume in mm³\n * @param tolerance Absolute tolerance in mm³ (default 1.0)\n */\n volumeApprox(label: string, shape: ShapeLike, expected: number, tolerance?: number): void;\n /**\n * Check that a shape\'s surface area is approximately equal to expected (mm²).\n *\n * @param expected Expected surface area in mm²\n * @param tolerance Absolute tolerance in mm² (default 1.0)\n */\n areaApprox(label: string, shape: ShapeLike, expected: number, tolerance?: number): void;\n /**\n * Check that a shape\'s bounding box has approximately the given size.\n *\n * @param expectedSize [sizeX, sizeY, sizeZ] in mm\n * @param tolerance Per-axis tolerance in mm (default 0.1)\n */\n boundingBoxSize(label: string, shape: ShapeLike, expectedSize: [\n number,\n number,\n number\n ], tolerance?: number): void;\n /**\n * Check that every sampled seam on a shape meets a requested continuity threshold.\n */\n edgeContinuity(label: string, shape: ShapeLike, options?: EdgeContinuityThresholds): void;\n /**\n * Check that a shape has no tiny edges below the requested threshold.\n */\n noTinyEdges(label: string, shape: ShapeLike, threshold?: number): void;\n /**\n * Check that a shape has no sliver faces below the requested score threshold.\n */\n noSliverFaces(label: string, shape: ShapeLike, threshold?: number): void;\n /**\n * Best-effort exact-shape validity guard for self-intersections or broken B-Rep topology.\n */\n noSelfIntersection(label: string, shape: ShapeLike): void;\n};\n/**\n * Register a mock (context) object for visualization and inspection.\n *\n * **Details**\n *\n * Mock objects appear in the viewport and inspection analysis when you run a\n * file directly, but are excluded when the file is imported via `require()`.\n * This lets you model the surrounding context — walls, bolts, mating parts —\n * without polluting the module\'s exports.\n *\n * The shape is returned unchanged, so you can reference it for alignment,\n * dimensioning, and `verify` checks.\n *\n * Mock objects participate in focused inspection commands such as\n * `forgecad inspect fit interference` and `forgecad inspect physical gaps`.\n * Their names appear with a `(mock)` suffix in reports.\n *\n * In the viewport, mock objects render at reduced opacity so they are\n * visually distinct from real geometry.\n *\n * **Example**\n *\n * ```ts\n * // bracket.forge.js\n * const wall = mock(box(100, 200, 10).translate(0, 0, -5), "wall");\n * const bolt = mock(cylinder(3, 15).translate(10, 15, 0), "bolt");\n *\n * const bracket = box(20, 30, 5);\n * verify.notColliding("bracket vs wall", bracket, wall);\n *\n * return bracket;\n * // When imported: only bracket is exported\n * // When run directly: bracket + wall + bolt all visible\n * ```\n *\n * @param shape - The context shape to register as a mock\n * @param name - Optional display name (defaults to "Mock N")\n * @returns The same shape, unchanged — use it for alignment and verify checks\n * @category Context\n */\ndeclare function mock<T extends Shape>(shape: T, name?: string): T;\ntype SourceFrameUpAxis = "+X" | "-X" | "+Y" | "-Y" | "+Z" | "-Z";\ninterface ImportSourceFrame {\n /** Source-file axis that should become ForgeCAD +Z after import. */\n up: SourceFrameUpAxis;\n}\ninterface ImportSourceFrameOptions {\n /**\n * Coordinate frame declared by the imported file.\n *\n * ForgeCAD remains Z-up. `sourceFrame.up` tells ForgeCAD which source-file\n * axis represents up so the import can be rotated into ForgeCAD\'s Z-up world.\n */\n sourceFrame?: ImportSourceFrame;\n}\ninterface MeshImportOptions extends ImportSourceFrameOptions {\n /** Uniform scale factor applied to the imported mesh (e.g. 25.4 for inch→mm). */\n scale?: number;\n /** Center the mesh at the origin based on its bounding box. */\n center?: boolean;\n /** For 3MF files, import one build item/resource object by stable ref or name. */\n object?: string;\n /** For 3MF files, import build items/resource objects as named ShapeGroup children. */\n separateObjects?: boolean;\n}\ninterface StepImportOptions extends ImportSourceFrameOptions {\n}\ntype SculptBlendInput = SdfShape | readonly SdfShape[];\ninterface SculptBlendOptions {\n /** Smooth blend radius in model units. Default: 4. */\n radius?: number;\n}\ninterface SculptBoxOptions {\n /** Rounded-box radius. Omit for a sharp SDF box. */\n radius?: number;\n}\ninterface SculptTubeOptions {\n /** Tube radius in model units. Used as the default when points do not carry their own radius. Default: 3. */\n radius?: number;\n /** Smooth blend radius between tube segments and joints. Default: smallest point radius. */\n blend?: number;\n /** Smooth the control polyline into a Catmull-Rom curve before sweeping. Default: false for tube/path, true for curve. */\n curve?: boolean | "linear" | "catmull-rom";\n /** Curve samples per control-point interval when curve smoothing is enabled. Default: 8, capped at 8 for realtime preview. */\n segments?: number;\n /** Catmull-Rom tangent scale when curve smoothing is enabled. Default: 0.5. */\n tension?: number;\n /** Material/color preset applied to the final tube. */\n polish?: SculptPolishInput;\n}\ntype SculptVec3 = Vec3$1 | readonly [\n number,\n number,\n number\n];\ntype SculptPathPoint = SculptVec3 | readonly [\n number,\n number,\n number,\n number\n] | {\n point: SculptVec3;\n radius?: number;\n} | {\n position: SculptVec3;\n radius?: number;\n} | {\n at: SculptVec3;\n radius?: number;\n};\ntype SculptPointList = readonly SculptPathPoint[];\ntype SculptLookPreset = "gallery" | "soft-studio" | "candy-shop" | "midnight" | "workbench";\ntype SculptBlendArg = SculptBlendInput | SculptBlendOptions;\ndeclare function sculptSphere(radius: number): SdfShape;\ndeclare function sculptBox(x: number, y: number, z: number, options?: SculptBoxOptions): SdfShape;\ndeclare function sculptCylinder(height: number, radius: number): SdfShape;\ndeclare function sculptDisk(radius: number, thickness?: number): SdfShape;\ndeclare function sculptCapsule(height: number, radius: number): SdfShape;\ndeclare function sculptTorus(majorRadius: number, minorRadius: number): SdfShape;\ndeclare function sculptCone(height: number, radius: number): SdfShape;\ndeclare function sculptTubePublic(points: SculptPointList, options?: SculptTubeOptions): SdfShape;\ndeclare function sculptCurve(points: SculptPointList, options?: SculptTubeOptions): SdfShape;\ndeclare function sculptBlend(first?: SculptBlendArg, optionsOrShape?: SculptBlendArg, ...rest: SculptBlendArg[]): SdfShape;\ndeclare function sculptUnion(first?: SculptBlendInput, ...rest: SculptBlendInput[]): SdfShape;\ndeclare function sculptCarve(base: SdfShape, cutters: SculptBlendInput, options?: SculptBlendOptions): SdfShape;\ndeclare function sculptKeep(first?: SculptBlendArg, optionsOrShape?: SculptBlendArg, ...rest: SculptBlendArg[]): SdfShape;\ndeclare function sculptPolish(shape: SdfShape, input?: SculptPolishInput): SdfShape;\ndeclare function sculptMaterial(input?: SculptPolishInput): ShapeMaterialProps & {\n color?: string;\n};\ndeclare function sculptLook(preset?: SculptLookPreset): SceneOptions;\ndeclare const Sculpt: {\n /** Create a liquid SDF sphere centered at the origin. */\n sphere: typeof sculptSphere;\n /** Create a liquid SDF box; pass `{ radius }` for a rounded box. */\n box: typeof sculptBox;\n /** Create a liquid SDF cylinder centered at the origin, axis along Z. */\n cylinder: typeof sculptCylinder;\n /** Create a thin circular disk centered at the origin, axis along Z. Useful as a circular cutter or insert. */\n disk: typeof sculptDisk;\n /**\n * Alias for `Sculpt.disk()`.\n * @softDeprecated Sculpt.disk(radius, thickness)\n * @deprecated use Sculpt.disk(radius, thickness)\n */\n circle: typeof sculptDisk;\n /** Create a liquid SDF capsule centered at the origin, axis along Z. */\n capsule: typeof sculptCapsule;\n /** Create a liquid SDF torus lying in the XY plane. */\n torus: typeof sculptTorus;\n /** Create a liquid SDF cone. */\n cone: typeof sculptCone;\n /** Create a smooth tube through a list of 3D points. */\n tube: typeof sculptTubePublic;\n /** Create a smooth variable-thickness sweep through 3D control points. */\n curve: typeof sculptCurve;\n /**\n * Alias for `Sculpt.tube()`; points may use [x, y, z, radius] for variable thickness.\n * @softDeprecated Sculpt.tube(points, options)\n * @deprecated use Sculpt.tube(points, options)\n */\n path: typeof sculptTubePublic;\n /** Smoothly blend one or more SDF shapes into a continuous body. */\n blend: typeof sculptBlend;\n /** Sharply union one or more SDF shapes. */\n union: typeof sculptUnion;\n /** Smoothly subtract one or more cutter shapes from a base shape. */\n carve: typeof sculptCarve;\n /** Smoothly intersect one or more SDF shapes. */\n keep: typeof sculptKeep;\n /** Apply a Sculpt material preset or direct material properties. */\n polish: typeof sculptPolish;\n /** Resolve a Sculpt material preset to ForgeCAD material properties. */\n material: typeof sculptMaterial;\n /** Return a polished scene preset tuned for liquid SDF preview. */\n look: typeof sculptLook;\n /** List the built-in Sculpt material preset names. */\n knownMaterials: typeof knownSculptMaterialPresets;\n};\n/**\n * SDF modeling — signed distance field primitives, smooth booleans, TPMS lattices,\n * domain warps, and surface patterns.\n *\n * Return `SdfShape` values directly from a script for native raymarch preview; plain\n * objects and arrays of SDF leaves render too (object keys become named preview parts).\n * Shapes live as a lazy expression tree — call `.toShape()` / `toShape(...)` only at the\n * materialization boundary: export, mesh booleans, or mixed SDF/manifold projects.\n *\n * SDF geometry is implicit and sampled, not B-rep/exact. Use with caution when precision,\n * tolerances, or exact export matter.\n *\n * @example\n * ```js\n * return {\n * shell: sdf.smoothUnion(sdf.sphere(10), sdf.box(15, 15, 15), { radius: 3 }).shell(2),\n * core: sdf.gyroid({ cellSize: 6, wallThickness: 0.8 })\n * .intersect(sdf.sphere(18))\n * .color(\'#ffcf5a\'),\n * };\n * ```\n */\ndeclare const sdf: {\n /** Create an SDF sphere centered at the origin. */\n sphere: typeof sphere;\n /** Create an SDF box centered at the origin with given full dimensions (not half-extents). */\n box: typeof box;\n /** Create an SDF cylinder centered at the origin, axis along Z. */\n cylinder: typeof cylinder;\n /** Create an SDF torus centered at the origin, lying in the XY plane. */\n torus: typeof torus;\n /** Create an SDF capsule centered at the origin, axis along Z. */\n capsule: typeof capsule;\n /** Create an SDF cone with base at z=0 and tip at z=height. */\n cone: typeof cone;\n /** Smooth union — blends shapes together with a smooth transition radius. */\n smoothUnion: typeof smoothUnion;\n /** Smooth difference — smoothly subtracts b from a. */\n smoothDifference: typeof smoothDifference;\n /** Smooth intersection — smoothly intersects a and b. */\n smoothIntersection: typeof smoothIntersection;\n /**\n * Morph between two SDF shapes. t=0 → a, t=1 → b.\n * @softDeprecated a.morph(b, t)\n * @deprecated use a.morph(b, t)\n */\n morph: typeof morph;\n /**\n * Spatially blend between two SDF patterns.\n * The blend function receives (x, y, z) and returns 0..1:\n * 0 = fully pattern `a`, 1 = fully pattern `b`.\n */\n blend: typeof blend;\n /** Gyroid TPMS lattice — the most common lattice for additive manufacturing. */\n gyroid: typeof gyroid;\n /** Schwarz-P TPMS lattice — isotropic pore structure. */\n schwarzP: typeof schwarzP;\n /** Diamond TPMS lattice — stiffest TPMS structure. */\n diamond: typeof diamond;\n /** Lidinoid TPMS lattice — visually distinct from gyroid, popular in research and art. */\n lidinoid: typeof lidinoid;\n /**\n * TPMS block preset clipped to an explicit design space.\n * @softDeprecated sdf.gyroid({ cellSize, wallThickness }).intersect(sdf.box(x, y, z))\n * @deprecated use sdf.gyroid({ cellSize, wallThickness }).intersect(sdf.box(x, y, z))\n */\n tpmsBlock: typeof tpmsBlock;\n /**\n * Clip an SDF shape to a box-shaped design space.\n * @softDeprecated shape.intersect(sdf.box(x, y, z))\n * @deprecated use shape.intersect(sdf.box(x, y, z))\n */\n withinBox: typeof withinBox;\n /** 3D Simplex noise field — produces organic, natural-looking displacements. */\n noise: typeof noise;\n /** 3D Voronoi pattern — organic cellular structures like bone, coral, or soap bubbles. */\n voronoi: typeof voronoi;\n /** Honeycomb (hexagonal) lattice pattern. Intersect with your shape to apply. */\n honeycomb: typeof honeycomb;\n /** Sinusoidal wave ridges — parallel ridges along an axis. */\n waves: typeof waves;\n /** Knurl pattern — crossed helical grooves for grips and handles. */\n knurl: typeof knurl;\n /** Perforated plate pattern — regular array of cylindrical holes. */\n perforated: typeof perforated;\n /** Fish/dragon scale pattern — overlapping circular scales in hex-packed rows. */\n scales: typeof scales;\n /** Brick/stone wall pattern — running bond with mortar grooves. */\n brick: typeof brick;\n /** Grid lattice pattern — two families of infinite slabs crossing at 90°. */\n weave: typeof weave;\n /** Basket weave surface pattern — threads with over-under crossings in UV space. Returns a SurfacePattern for use with `.surfaceDisplace()`. */\n basketWeave: typeof basketWeave;\n /** Create typed, composable 2D surface patterns for `.surfaceDisplace()`. */\n pattern2d: typeof pattern2d;\n /**\n * Twist an SDF shape around the Z axis.\n * @softDeprecated shape.twist(degreesPerUnit)\n * @deprecated use shape.twist(degreesPerUnit)\n */\n twist: typeof twist;\n /**\n * Bend an SDF shape around the Z axis.\n * @softDeprecated shape.bend(radius)\n * @deprecated use shape.bend(radius)\n */\n bend: typeof bend;\n /**\n * Repeat an SDF shape in space.\n * @softDeprecated shape.repeat(spacing, count)\n * @deprecated use shape.repeat(spacing, count)\n */\n repeat: typeof repeat;\n /**\n * Arrange an SDF shape in a circular array around the Z axis.\n * @softDeprecated shape.circularArray(count, offset)\n * @deprecated use shape.circularArray(count, offset)\n */\n circularArray: typeof circularArray;\n /** A 2D surface pattern — a heightmap function for use with `.surfaceDisplace()`. */\n SurfacePattern: typeof SurfacePattern;\n /** Create a custom SDF from one expression; shader-safe expressions keep shader metadata for tooling. */\n fromFunction: typeof fromFunction;\n /**\n * Collapse a plain object/array tree of SDF leaves into one continuous implicit field.\n * Per-leaf color/material identity is intentionally discarded — the result is one\n * scalar field. Use plain object returns for multi-material SDF preview, and\n * `sdf.combine(...)` only when you want one implicit body. Explicit shape lists\n * are covered by `a.union(b, c)`; pass `{ op: \'intersection\' }` to intersect instead.\n *\n * ```js\n * // Built a part as a named tree for preview, now need one field to shell:\n * const parts = { body: sdf.box(40, 30, 12), boss: sdf.cylinder(10, 8) };\n * return sdf.combine(parts).shell(1.2);\n * ```\n */\n combine: typeof combine;\n /** Sculpt-like facade: friendly liquid-modeling verbs backed by the same SDF kernel. */\n Sculpt: {\n sphere: (radius: number) => SdfShape;\n box: (x: number, y: number, z: number, options?: SculptBoxOptions) => SdfShape;\n cylinder: (height: number, radius: number) => SdfShape;\n disk: (radius: number, thickness?: number) => SdfShape;\n circle: (radius: number, thickness?: number) => SdfShape;\n capsule: (height: number, radius: number) => SdfShape;\n torus: (majorRadius: number, minorRadius: number) => SdfShape;\n cone: (height: number, radius: number) => SdfShape;\n tube: (points: SculptPointList, options?: SculptTubeOptions) => SdfShape;\n curve: (points: SculptPointList, options?: SculptTubeOptions) => SdfShape;\n path: (points: SculptPointList, options?: SculptTubeOptions) => SdfShape;\n blend: (first?: SculptBlendInput | SculptBlendOptions, optionsOrShape?: SculptBlendInput | SculptBlendOptions, ...rest: (SculptBlendInput | SculptBlendOptions)[]) => SdfShape;\n union: (first?: SculptBlendInput, ...rest: SculptBlendInput[]) => SdfShape;\n carve: (base: SdfShape, cutters: SculptBlendInput, options?: SculptBlendOptions) => SdfShape;\n keep: (first?: SculptBlendInput | SculptBlendOptions, optionsOrShape?: SculptBlendInput | SculptBlendOptions, ...rest: (SculptBlendInput | SculptBlendOptions)[]) => SdfShape;\n polish: (shape: SdfShape, input?: SculptPolishInput) => SdfShape;\n material: (input?: SculptPolishInput) => ShapeMaterialProps & {\n color?: string;\n };\n look: (preset?: SculptLookPreset) => SceneOptions;\n knownMaterials: typeof knownSculptMaterialPresets;\n };\n};\ntype GeometryBackend = "manifold" | "occt" | "truck" | "sdf" | "hybrid" | "unknown";\ntype GeometryRepresentation = "mesh-solid" | "brep-solid" | "surface" | "mixed";\ntype GeometryFidelity = "kernel-native" | "exact" | "sampled" | "deformed" | "mixed" | "unknown";\ntype GeometryTopology = "none" | "synthetic" | "kernel";\ntype GeometrySource = "primitive" | "extrude" | "revolve" | "boolean" | "sheet-metal" | "shell" | "fillet" | "chamfer" | "level-set" | "loft" | "sweep" | "surface-patch" | "deform" | "draft" | "offset-solid" | "imported" | "sdf" | "fromSlices" | "unknown";\ninterface GeometryInfo {\n backend: GeometryBackend;\n representation: GeometryRepresentation;\n fidelity: GeometryFidelity;\n topology: GeometryTopology;\n sources: GeometrySource[];\n}\ninterface ShapeLike$1 {\n toShape(): Shape;\n}\ntype ShapeOperandInput = Shape | ShapeLike$1 | readonly (Shape | ShapeLike$1)[];\ntype ShapeRuntimePayload = ShapeBackend | Manifold;\ntype ShapeAnchorTarget = Shape | {\n referencePoint(ref: string): [\n number,\n number,\n number\n ];\n} | {\n _bbox(): {\n min: number[];\n max: number[];\n };\n};\ntype RotationPointLike = PlacementAnchorLike | Vec3;\ninterface ShapeMaterialProps {\n /** Metalness factor (0 = dielectric, 1 = metal). Default: 0.05 */\n metalness?: number;\n /** Roughness factor (0 = mirror, 1 = fully diffuse). Default: 0.35 */\n roughness?: number;\n /** Emissive glow color (hex string, e.g. "#ff6b35"). */\n emissive?: string;\n /** Emissive intensity multiplier. Default: 1 */\n emissiveIntensity?: number;\n /** Opacity (0 = fully transparent, 1 = fully opaque). Default: 1 */\n opacity?: number;\n /** Render as wireframe. Default: false */\n wireframe?: boolean;\n /** Clearcoat intensity (0–1). Default: 0.1 */\n clearcoat?: number;\n /** Clearcoat roughness (0–1). Default: 0.4 */\n clearcoatRoughness?: number;\n /** Glass/translucency transmission factor (0–1). Renderer support depends on target. */\n transmission?: number;\n /** Index of refraction for transmissive materials. Typical glass is ~1.45. */\n ior?: number;\n /** Approximate transmissive volume thickness in model units. */\n thickness?: number;\n /** Specular highlight intensity (0–1). */\n specularIntensity?: number;\n /** Specular highlight tint. */\n specularColor?: string;\n /** Reflection strength for supported renderers (0–1). */\n reflectivity?: number;\n}\ntype ShapeReferenceKind = "face" | "face-set" | "edge" | "edge-set" | "point" | "point-set";\ntype ShapeReferenceStatus = "alive" | "deleted" | "ambiguous";\ntype ShapeReferenceCardinality = "zero" | "one" | "many";\ninterface ShapeReferenceResolution {\n path: string;\n sourcePath: string;\n shapeName?: string;\n kind: ShapeReferenceKind;\n status: ShapeReferenceStatus;\n cardinality: ShapeReferenceCardinality;\n rule: string;\n faces: FaceRef[];\n edges: EdgeSegment[];\n points: Vec3[];\n}\n/**\n * A first-class reference path over a shape\'s semantic faces and face relationships.\n *\n * Created with `shape.ref("lid/back")`, then refined through methods such as\n * `.point()` or `.edges()`. The reference stores intent as a readable path and\n * resolves lazily against the current shape metadata.\n */\ndeclare class ShapeRef {\n private readonly shape;\n readonly path: string;\n private readonly optional;\n constructor(shape: Shape, path: string, optional?: boolean);\n /** Resolve this reference into its current faces, edges, or points. */\n resolve(): ShapeReferenceResolution;\n /** The resolved reference kind, such as `face`, `edge-set`, or `point`. */\n get kind(): ShapeReferenceKind;\n /** Whether the reference currently resolves to zero, one, or many matches. */\n get cardinality(): ShapeReferenceCardinality;\n /** Return the reference lifecycle status for the current shape state. */\n status(): ShapeReferenceStatus;\n /** Return a human-readable explanation of how this reference resolved. */\n explain(): string;\n /** Name this derived reference so the same shape can resolve it by `shape.ref(name)`. */\n as(name: string): ShapeRef;\n /** Return an optional reference that resolves to zero matches instead of throwing when missing. */\n maybe(): ShapeRef;\n /** Mark that a multi-match reference is intentionally being used as a set. */\n all(): ShapeRef;\n /** Require this reference to resolve to exactly one match. */\n one(): ShapeRef;\n /** Resolve this reference as one or more faces. */\n faces(): FaceRef[];\n /** Resolve this reference as exactly one face. */\n face(): FaceRef;\n /** Resolve this reference as one or more edges. Face references return boundary edges. */\n edges(): EdgeSegment[];\n /** Resolve this reference as exactly one edge. */\n edge(): EdgeSegment;\n /** Resolve this reference as one or more points. Faces use centers and edges use midpoints. */\n points(): Vec3[];\n /** Resolve this reference as exactly one point. */\n point(): Vec3;\n /** Return the structured JSON-friendly reference resolution. */\n toJSON(): ShapeReferenceResolution;\n /** Return a compact display form for this reference path. */\n toString(): string;\n}\n/**\n * Core 3D solid shape. All operations are immutable and return new shapes.\n *\n * Supports transforms (translate, rotate, scale, mirror, transform, rotateAround, pointAlong),\n * booleans (add, subtract, intersect), cutting (split, splitByPlane, trimByPlane),\n * shelling, anchor positioning (attachTo, onFace), placement references, and queries\n * (volume, surfaceArea, boundingBox, isEmpty, numTri, geometryInfo).\n */\ndeclare class Shape {\n colorHex: string | undefined;\n materialProps: ShapeMaterialProps | undefined;\n constructor(payload: ShapeRuntimePayload, color?: string, geometryInfo?: Partial<GeometryInfo>);\n /** @internal Use .color() instead. */\n setColor(value: string | undefined): Shape;\n /** Set the color of this shape (hex string, e.g. "#ff0000"). Returns a new Shape with the color applied. */\n color(value: string | undefined): Shape;\n /**\n * Set PBR material properties for this shape\'s visual appearance.\n *\n * **Details**\n *\n * Returns a new Shape with the specified material properties merged on top of any previously\n * set properties. All properties are optional — omitted keys retain their current value.\n * Material properties survive transforms and boolean operations.\n *\n * Use `.color()` to set the base diffuse color; `.material()` controls how that color behaves\n * under light (metalness, roughness, clearcoat) and can add emissive glow independent of\n * lighting. Emissive glow pairs naturally with the `postProcessing.bloom` effect in `scene()`.\n *\n * **Example**\n *\n * ```js\n * box(50, 50, 50).material({ metalness: 0.9, roughness: 0.1 }); // polished metal\n * sphere(30).material({ emissive: \'#ff6b35\', emissiveIntensity: 2 }); // glowing\n * cylinder(40, 20).material({ opacity: 0.4, clearcoat: 1.0, clearcoatRoughness: 0.02 }); // ice\n *\n * // Chainable with other shape methods\n * box(100, 100, 10).color(\'#gold\').material({ metalness: 0.95, roughness: 0.05 }).translate(0, 0, 50);\n * ```\n *\n * @param props.metalness - Metallic vs. dielectric (0–1). Default: 0.05\n * @param props.roughness - Surface roughness; 0 = mirror, 1 = matte. Default: 0.35\n * @param props.emissive - Glow color hex string (e.g. `\'#ff6b35\'`). Glows independently of lighting\n * @param props.emissiveIntensity - Glow multiplier. Default: 1\n * @param props.opacity - Transparency; 0 = fully transparent, 1 = fully opaque. Default: 1\n * @param props.wireframe - Render as wireframe. Default: false\n * @param props.clearcoat - Clearcoat layer intensity (0–1). Default: 0.1\n * @param props.clearcoatRoughness - Clearcoat layer roughness (0–1). Default: 0.4\n * @returns A new Shape with the merged material properties applied\n * @category Materials\n */\n material(props: ShapeMaterialProps): Shape;\n /** Return a new Shape wrapper for explicit duplication in scripts. */\n clone(): Shape;\n /** Alias for clone() */\n duplicate(): Shape;\n /** Inspect which backend/representation produced this solid. */\n geometryInfo(): GeometryInfo;\n /** Name this shape as a reference namespace for diagnostics and future published refs. */\n as(name: string): Shape;\n /** Resolve a semantic reference path like `lid`, `lid/back`, or a midpoint selector on `lid/back`. */\n ref(path: string): ShapeRef;\n /** Attach named placement references that survive normal transforms and imports. */\n withReferences(refs: PlacementReferenceInput): Shape;\n /** List named placement references carried by this shape. */\n referenceNames(kind?: PlacementReferenceKind): string[];\n /**\n * Deprecated alias for `withConnectors()`.\n * @deprecated Use `withConnectors()` instead.\n */\n withPorts(_ports: Record<string, PortInput>): Shape;\n /**\n * Deprecated alias for `connectorNames()`.\n * @deprecated Use `connectorNames()` instead.\n */\n portNames(): string[];\n /** Resolve a named placement reference or built-in anchor to a 3D point. */\n referencePoint(ref: PlacementAnchorLike): [\n number,\n number,\n number\n ];\n /**\n * Resolve a face by user-authored label or compiler-owned name. Returns a `FaceRef`\n * that can be passed to `.onFace()`, `projectToPlane()`, or used directly in placement.\n *\n * **Details**\n *\n * `.face(name)` is a pure label lookup — it finds faces by user-authored labels, not by\n * geometric queries. Labels are born in sketches via `.label()` / `.labelEdges()` and\n * grow into face names through extrude, loft, revolve, and sweep. They are stable\n * references that travel with the geometry.\n *\n * Labels must be unique within a shape. Use `.prefixLabels()` before combining shapes\n * with `union()` / `difference()` to avoid collisions. Collision detection throws a\n * clear error with a fix suggestion.\n *\n * Boolean survival: `union()` and `intersection()` carry labels from every operand;\n * `difference()` carries only the base (first) operand\'s labels — cutter labels are\n * dropped. A surviving label addresses whatever portion of its face survives the\n * boolean; cutters may split or erase it, and a lineage shared by multiple union\n * operands resolves as a face set rather than a single face.\n *\n * For compile-covered shapes (extrude, loft, etc.) the lookup resolves via the shape\'s\n * compile plan. As a fallback, planar-faced mesh shapes (e.g. results of boolean ops)\n * are resolved via coplanar triangle clustering.\n *\n * **Example**\n *\n * ```ts\n * // Edge labels become side face names after extrude\n * const profile = path()\n * .moveTo(0, 0)\n * .lineTo(100, 0).label(\'floor\')\n * .lineTo(100, 50).label(\'wall\')\n * .lineTo(0, 50).label(\'ceiling\')\n * .closeLabel(\'left-wall\');\n * const room = profile.extrude(30, { labels: { start: \'base\', end: \'top\' } });\n * room.face(\'floor\'); // side face from the labeled edge\n * room.face(\'base\'); // base cap (user-specified)\n *\n * // .labelEdges() shorthand for sequential edge labeling\n * const plate = rect(100, 50).labelEdges(\'south\', \'east\', \'north\', \'west\');\n * const solid = plate.extrude(20, { labels: { start: \'bottom\', end: \'top\' } });\n * solid.face(\'south\'); // side face\n *\n * // Prefix before combining to avoid collisions\n * const left = wing.prefixLabels(\'l/\');\n * const right = wing.mirror([1, 0, 0]).prefixLabels(\'r/\');\n * const full = union(left, right);\n * full.face(\'l/upper\'); // left wing upper surface\n * ```\n *\n * @param selector - A user-authored label string, compiler-owned face name, or FaceQuery.\n * @returns A `FaceRef` carrying the face\'s center, normal, and local axes.\n * @see {@link Shape.faceNames} — list available face names on this shape\n * @see {@link Shape.prefixLabels} — prefix labels before combining shapes\n * @category Face References\n */\n face(selector: FaceSelector): FaceRef;\n /**\n * Return faces matching a query, or label semantic faces when passed a mapping.\n *\n * Mapping form returns a new shape:\n * `shape.faces({ lid: \'top\', walls: [\'front\', \'back\', \'left\', \'right\'] })`.\n */\n faces(): FaceRef[];\n faces(query: FaceQuery): FaceRef[];\n faces(mapping: Record<string, string | string[]>): Shape;\n /** List defined semantic face names currently available on this shape. */\n faceNames(): string[];\n /** Prefix all user-authored face labels, including semantic labels from `faces(mapping)`. Returns a new shape with modified labels. */\n prefixLabels(prefix: string): Shape;\n /** Rename a single face label. Returns a new shape. */\n renameLabel(from: string, to: string): Shape;\n /** Remove specific face labels. Returns a new shape. */\n dropLabels(...names: string[]): Shape;\n /** Remove all face labels. Returns a new shape. */\n dropAllLabels(): Shape;\n /** Get a named topology edge. Only available on shapes with tracked topology (from box/cylinder/extrude). */\n edge(name: string): EdgeRef;\n /** List named topology edge names. Returns empty array if shape has no tracked topology. */\n edgeNames(): string[];\n /**\n * Return all boundary edges of a named face.\n *\n * **Details**\n *\n * Finds edges where one adjacent mesh face belongs to the target face and the\n * other belongs to a different face. The result is coalesced (tessellation\n * fragments merged) and can be passed directly to `fillet()` or `chamfer()`.\n *\n * This is a topological query — no coordinates, no tolerances, no minimum-length\n * hacks. It works because an edge is the boundary between two faces.\n *\n * **Example**\n *\n * ```js\n * // Fillet all top edges of a mounting plate\n * let plate = box(120, 80, 6).faces({ workSurface: \'top\' })\n * plate = fillet(plate, 3, plate.edgesOf(\'workSurface\'))\n *\n * // Shelled enclosure — fillet the outer lip\n * let body = box(80, 50, 35).faces({ opening: \'top\' })\n * body = body.shell(2, { openFaces: [\'top\'] })\n * body = fillet(body, 1.5, body.edgesOf(\'opening\'))\n *\n * // Filter: only concave edges (after a boolean subtraction)\n * body.edgesOf(\'top\', { concave: true })\n * ```\n *\n * @param faceLabel - Name of the face whose edges to return.\n * @param options - Optional: exclude faces, convexity filter, minLength.\n * @returns EdgeSegment[] ready for fillet/chamfer. Throws if face not found.\n * @see {@link Shape.edgesBetween} — edges shared between two faces\n * @see {@link Shape.faces} — assign labels to faces\n * @see {@link Shape.ref} — resolve semantic face and edge paths\n * @category Edge Queries\n */\n edgesOf(faceLabel: string, options?: EdgesOfOptions): EdgeSegment[];\n /**\n * Return edges shared between two named faces.\n *\n * **Details**\n *\n * An edge is "between" faces A and B when one of its adjacent mesh triangles\n * belongs to A and the other belongs to B. This is the most precise topological\n * edge selection — "fillet the edges where the top meets the wall."\n *\n * The second argument can be a single face name or an array (edges between A\n * and any of B1, B2, ...).\n *\n * **Example**\n *\n * ```js\n * // Fillet the edge where lid meets one wall\n * let body = box(100, 60, 30).faces({ lid: \'top\', wall: \'side-left\' })\n * body = fillet(body, 2, body.edgesBetween(\'lid\', \'wall\'))\n *\n * // Fillet a cylinder rim — where the flat cap meets the curved barrel\n * let tube = cylinder(30, 10).faces({ cap: \'top\', barrel: \'side\' })\n * tube = fillet(tube, 1, tube.edgesBetween(\'cap\', \'barrel\'))\n *\n * // Multiple target faces at once\n * body.edgesBetween(\'lid\', [\'left-wall\', \'right-wall\', \'front-wall\', \'back-wall\'])\n * ```\n *\n * @param faceA - First face name.\n * @param faceB - Second face name, or array of face names.\n * @returns EdgeSegment[] ready for fillet/chamfer. Throws if faces don\'t share edges.\n * @see {@link Shape.edgesOf} — all edges of a single face\n * @see {@link Shape.faces} — assign labels to faces\n * @see {@link Shape.ref} — resolve semantic face and edge paths\n * @category Edge Queries\n */\n edgesBetween(faceA: string, faceB: string | string[]): EdgeSegment[];\n /** @internal Resolve a user face label or canonical name to a FaceRef. */\n private _resolveFaceLabel;\n /** @internal Resolve a user face label, face set, or canonical name to one or more FaceRefs. */\n private _resolveFaceLabels;\n /** Get the transformation history for a specific face. */\n faceHistory(name: string): FaceTransformationHistory;\n /**\n * Translate the shape so the given anchor or reference lands on the target coordinate.\n *\n * Accepts any built-in anchor name (`\'bottom\'`, `\'center\'`, `\'top-front-left\'`, etc.)\n * or a custom placement reference attached via `withReferences()`.\n *\n * ```javascript\n * // Ground a shape — put its bottom face center at Z = 0\n * shape.placeReference(\'bottom\', [0, 0, 0])\n *\n * // Center at the world origin\n * shape.placeReference(\'center\', [0, 0, 0])\n *\n * // Align left edge to X = 10\n * shape.placeReference(\'left\', [10, 0, 0])\n * ```\n */\n placeReference(ref: PlacementAnchorLike, target: [\n number,\n number,\n number\n ], offset?: [\n number,\n number,\n number\n ]): Shape;\n /**\n * Translate using polar coordinates (radius + angle in degrees).\n * Eliminates manual `r * Math.cos(angle * PI/180)` calculations.\n *\n * Example: `shape.translatePolar(50, 30)` moves 50mm at 30 degrees from +X.\n */\n translatePolar(radius: number, angleDeg: number, z?: number): Shape;\n /** Move the shape relative to its current position. All transforms are immutable and return new shapes. */\n translate(x: number, y: number, z: number): Shape;\n /** Position the shape so its bounding box min corner is at the given global coordinate. */\n moveTo(x: number, y: number, z: number): Shape;\n /** Position the shape relative to another shape\'s local coordinate system (bounding box min corner). */\n moveToLocal(target: Shape | {\n toShape(): Shape;\n }, x: number, y: number, z: number): Shape;\n /** Rotate around an arbitrary axis through the origin. Unlike `Sketch.rotate()` (bounding-box center), this pivots at the world origin — pass `options.pivot` to rotate in place. */\n rotate(axis: [\n number,\n number,\n number\n ], angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): Shape;\n /** Rotate around the X axis by the given angle in degrees. */\n rotateX(angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): Shape;\n /** Rotate around the Y axis by the given angle in degrees. */\n rotateY(angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): Shape;\n /** Rotate around the Z axis by the given angle in degrees. */\n rotateZ(angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): Shape;\n /** Apply a 4x4 affine transform matrix (column-major) or a Transform object. */\n transform(m: Mat4 | Transform): Shape;\n /** Scale the shape uniformly or per-axis from the shape\'s bounding box center. Accepts a single number or [x, y, z] array. */\n scale(v: number | [\n number,\n number,\n number\n ]): Shape;\n /** Scale the shape uniformly or per-axis from an explicit pivot point. */\n scaleAround(pivot: [\n number,\n number,\n number\n ], v: number | [\n number,\n number,\n number\n ]): Shape;\n /** Mirror across a plane through the shape\'s bounding box center, defined by its normal vector. */\n mirror(normal: [\n number,\n number,\n number\n ]): Shape;\n /** Mirror across a plane through an explicit point, defined by its normal vector. */\n mirrorThrough(point: [\n number,\n number,\n number\n ], normal: [\n number,\n number,\n number\n ]): Shape;\n /**\n * Reorient a shape so its primary axis (Z) points along the given direction.\n * Useful for laying cylinders/extrusions along X or Y without thinking about Euler angles.\n * The shape\'s origin stays at [0,0,0] — translate after pointAlong to position it.\n *\n * Example: cylinder(40, 5).pointAlong([1, 0, 0]) — lays cylinder along X, starting at origin\n */\n pointAlong(direction: [\n number,\n number,\n number\n ]): Shape;\n /**\n * Rotate around an arbitrary axis through a pivot point.\n * Equivalent to: translate(-pivot) → rotate around axis → translate(+pivot)\n * @internal Prefer rotate(), rotateX(), rotateY(), rotateZ() for public use.\n */\n rotateAroundAxis(axis: [\n number,\n number,\n number\n ], angleDeg: number, pivot?: [\n number,\n number,\n number\n ]): Shape;\n /**\n * Rotate around an axis until a moving point reaches the target line/plane defined by the axis and target point.\n * `movingPoint` / `targetPoint` may be raw world points or this shape\'s anchors/references.\n */\n rotateAroundTo(axis: [\n number,\n number,\n number\n ], pivot: [\n number,\n number,\n number\n ], movingPoint: RotationPointLike, targetPoint: RotationPointLike, options?: RotateAroundToOptions): Shape;\n /** Unwrap any object with toShape() without circular import. */\n private static _unwrap;\n /**\n * Warn if a boolean operation had no geometric effect.\n * Compares volumes before and after; if they match within tolerance, the operation was a no-op.\n */\n private static _checkBooleanNoOp;\n /** Union this shape with others (additive boolean). Method form of union(). */\n add(...others: ShapeOperandInput[]): Shape;\n /** Subtract other shapes from this one. Method form of difference(). */\n subtract(...others: ShapeOperandInput[]): Shape;\n /** Keep only the overlap with other shapes. Method form of intersection(). */\n intersect(...others: ShapeOperandInput[]): Shape;\n /** Alias for add() — matches the free-function union() naming. */\n union(...others: ShapeOperandInput[]): Shape;\n /** Alias for subtract() — matches the free-function difference() naming. */\n difference(...others: ShapeOperandInput[]): Shape;\n /** Alias for intersect() — matches the free-function intersection() naming. */\n intersection(...others: ShapeOperandInput[]): Shape;\n /** Split into [inside, outside] by another shape. */\n split(cutter: Shape | {\n toShape(): Shape;\n }): [\n Shape,\n Shape\n ];\n /** Split by infinite plane. Returns [positive-side, negative-side]. */\n splitByPlane(normal: [\n number,\n number,\n number\n ], originOffset?: number): [\n Shape,\n Shape\n ];\n /** Keep the positive side of the plane and discard the opposite side. */\n trimByPlane(normal: [\n number,\n number,\n number\n ], originOffset?: number): Shape;\n /** Hollow out compile-covered boxes, cylinders, and straight extrudes.\n * `openFaces` names any subset of the base shape\'s labeled faces to leave open (no wall).\n */\n shell(thickness: number, opts?: {\n openFaces?: string[];\n }): Shape;\n /** Offset-thicken an exact open surface or shell into a solid. */\n thicken(thickness: number): Shape;\n /** Get the axis-aligned bounding box as { min: [x,y,z], max: [x,y,z] }. */\n boundingBox(): ShapeRuntimeBounds;\n /** Volume in mm cubed. */\n volume(): number;\n /** Surface area in mm squared. */\n surfaceArea(): number;\n /** True if the shape contains no geometry. */\n isEmpty(): boolean;\n /** Number of disconnected solid bodies in this shape. */\n numBodies(): number;\n /** Triangle count of the mesh representation. */\n numTri(): number;\n /** Extract triangle mesh for Three.js rendering */\n getMesh(): ShapeRuntimeMesh;\n /** Slice the runtime solid by a plane normal to local Z at the given offset. */\n slice(offset?: number): any;\n /** Orthographically project the runtime solid onto the local XY plane. */\n project(): any;\n /**\n * Position this shape relative to another using named 3D anchor points.\n *\n * Anchors are bounding-box-relative: \'center\', face centers (\'top\', \'front\', ...),\n * edge midpoints (\'top-front\', \'back-left\', ...), and corners (\'top-front-left\', ...).\n * Anchor word order is flexible: \'front-left\' and \'left-front\' are equivalent.\n * Named placement references (from withReferences) can also be used as anchors.\n */\n attachTo(target: ShapeAnchorTarget, targetAnchor: PlacementAnchorLike, selfAnchor?: PlacementAnchorLike, offset?: [\n number,\n number,\n number\n ]): Shape;\n /**\n * Place this shape on a face of a parent shape.\n *\n * Think of it like sticking a label on a box surface:\n * - `face` picks which surface (\'front\', \'back\', \'top\', etc.)\n * - `u, v` position within that face\'s 2D plane (from center)\n * - front/back: u = left/right (X), v = up/down (Z)\n * - left/right: u = forward/back (Y), v = up/down (Z)\n * - top/bottom: u = left/right (X), v = forward/back (Y)\n * - `protrude` = how far the child sticks out (positive = outward from face)\n */\n onFace(parent: ShapeAnchorTarget, face: "front" | "back" | "left" | "right" | "top" | "bottom", opts?: {\n u?: number;\n v?: number;\n protrude?: number;\n }): Shape;\n /**\n * Slide this shape along an axis until a labeled face is embedded in the target body.\n *\n * Position the shape roughly first (translate/rotate), then call seatInto to\n * auto-adjust the penetration depth. No manual coordinate math needed.\n *\n * ```js\n * // Wing root embeds into fuselage — adapts to any fuselage shape\n * wing.translate(0, wingY, 0).seatInto(fuselage, \'root\');\n *\n * // Sensor pod sits flush on fuselage surface\n * pod.translate(0, station, radius + 20).seatInto(fuselage, \'base\', { depth: \'flush\' });\n *\n * // Antenna with 3mm gasket standoff\n * mast.translate(0, station, radius + 50).seatInto(fuselage, \'mount\', { depth: \'flush\', gap: 3 });\n * ```\n *\n * @param target - The body to embed into (must be a watertight solid).\n * @param surface - Label of the face on this shape that connects (from `.faces(mapping)`).\n * @param options - Movement axis (defaults to face normal direction), depth mode, and gap control.\n * @returns A new Shape translated along the movement axis.\n */\n seatInto(target: Shape, surface: string, options?: SeatIntoOptions): Shape;\n /**\n * Slide this shape until a target\'s labeled face is fully covered (inside this shape).\n *\n * The inverse of `seatInto`: instead of embedding *your* face into the target,\n * you move until the *target\'s* face is embedded inside you.\n *\n * ```js\n * // Nacelle moves up until pylon\'s bottom face is inside the nacelle\n * nacelle.translate(rough).seatOver(pylon, \'bottom\');\n *\n * // Cap slides down over a post until post\'s top face is covered\n * cap.translate(rough).seatOver(post, \'top\');\n * ```\n *\n * @param target - The shape whose face you want to cover.\n * @param targetSurface - Label of the face on the TARGET shape to cover.\n * @param options - Movement axis (defaults to negative of target face normal), depth mode, and gap control.\n * @returns A new Shape translated along the movement axis.\n */\n seatOver(target: Shape, targetSurface: string, options?: SeatIntoOptions): Shape;\n /** Attach named connectors — attachment points that survive transforms and imports.\n * Connectors can be bare (position + orientation) or typed (with connectorType/gender for compatibility matching). */\n withConnectors(connectors: Record<string, ConnectorInput>): Shape;\n /** List all connector names on this shape. */\n connectorNames(): string[];\n /** Get all connectors of a given type. */\n connectorsByType(type: string): Array<{\n name: string;\n port: ConnectorDef;\n }>;\n /** Distance between two connector origins on this shape. */\n connectorDistance(nameA: string, nameB: string): number;\n /** Get measurements metadata from a connector. */\n connectorMeasurements(name: string): Record<string, number | string>;\n /**\n * Position this shape by matching connectors to a target.\n *\n * Alignment: with a single connector pair, the shape translates and rotates so the connector\n * origins coincide and the axes oppose (plug-in model); `up` pins the roll. With multiple pairs,\n * the connector origins define the rigid transform — still author meaningful `axis`/`up` values\n * so the same connectors remain useful for `connect()`, audits, and future matching.\n *\n * Overloads:\n * - Single pair: `matchTo(target, selfConn, targetConn, options?)`\n * - Dictionary (same target): `matchTo(target, { selfConn: targetConn, ... }, options?)`\n * - Multi-target: `matchTo([ [target1, selfConn1, targetConn1], ... ], options?)`\n */\n matchTo(targetOrPairs: Shape | MatchTarget | Array<[\n Shape | MatchTarget,\n string,\n string\n ]>, selfConnOrDict?: string | Record<string, string>, targetConnOrOptions?: string | MatchToOptions, maybeOptions?: MatchToOptions): Shape;\n}\ntype MatchTarget = Shape | {\n portNames?(): string[];\n};\n/**\n * Create a rectangular box. Centered on XY, base at Z=0.\n *\n * All ForgeCAD dimensions are millimeters; all angles are degrees (applies to every API, not just `box`).\n *\n * Extents:\n * - X: `[-width/2, width/2]`\n * - Y: `[-depth/2, depth/2]`\n * - Z: `[0, height]`\n *\n * This origin convention (centered on XY, base at Z=0) applies to all volumetric\n * primitives that have a base. There is no `center: true` option — recenter with\n * `.translate(0, 0, -height/2)` or `.placeReference(\'center\', [0, 0, 0])`.\n *\n * For named faces, build from a labeled sketch:\n * `rect(width, depth).labelEdges(\'s\', \'e\', \'n\', \'w\').extrude(height, { labels: { start: \'bottom\', end: \'top\' } })`.\n */\ndeclare function box$1(width: number, depth: number, height: number): Shape;\n/**\n * Create a cylinder or cone with named faces and edges.\n * Centered on XY, base at Z=0.\n *\n * Extents:\n * - X/Y: centered at the origin\n * - Z: `[0, height]`\n *\n * `radiusTop` defaults to `radius`. Set `radiusTop` smaller to taper the side,\n * or `0` for a pointy cone. Use `segments` to create regular prisms\n * (for example `6` for a hexagonal prism).\n *\n * Named faces: `top`, `bottom`, `side`\n * Named edges: `top-rim`, `bottom-rim`\n */\ndeclare function cylinder$1(height: number, radius: number, radiusTop?: number, segments?: number): Shape;\n/**\n * Create a sphere centered at the origin.\n *\n * Extents:\n * - X: `[-radius, radius]`\n * - Y: `[-radius, radius]`\n * - Z: `[-radius, radius]`\n *\n * Use `segments` for lower-poly approximations.\n */\ndeclare function sphere$1(radius: number, segments?: number): Shape;\n/**\n * Create a torus (donut shape) lying in the XY plane. Centered on all axes.\n *\n * Extents:\n * - X: `[-(majorRadius + minorRadius), +(majorRadius + minorRadius)]`\n * - Y: `[-(majorRadius + minorRadius), +(majorRadius + minorRadius)]`\n * - Z: `[-minorRadius, minorRadius]`\n *\n * The origin is the center of the ring.\n */\ndeclare function torus$1(majorRadius: number, minorRadius: number, segments?: number): Shape;\n/**\n * Combine shapes into a single solid (additive boolean).\n *\n * Accepts individual shapes, or an array of shapes. `union()` returns one\n * solid, so only the first operand\'s color is preserved in the result. Use\n * `group()` when you want separate child colors or identities.\n */\ndeclare function union(...inputs: ShapeOperandInput[]): Shape;\n/**\n * Subtract shapes from a base shape (subtractive boolean).\n *\n * The first shape is the base; all subsequent shapes are subtracted from it.\n * Accepts individual shapes, or an array of shapes.\n */\ndeclare function difference(...inputs: ShapeOperandInput[]): Shape;\n/**\n * Keep only the overlapping volume of the input shapes (intersection boolean).\n *\n * Requires at least two shapes. Accepts individual shapes, or an array.\n */\ndeclare function intersection(...inputs: ShapeOperandInput[]): Shape;\ntype Constraint3DType = "flush" | "align" | "parallel" | "faceDistance" | "concentric" | "axisParallel" | "pointCoincident" | "pointOnFace" | "pointOnAxis" | "angle" | "fixed";\ninterface BodyFeatureRef {\n bodyId: string;\n featureName: string;\n}\ninterface Constraint3D {\n id: string;\n type: Constraint3DType;\n refA: BodyFeatureRef;\n refB: BodyFeatureRef;\n value?: number;\n}\ndeclare class MateBuilder {\n readonly constraints: Constraint3D[];\n private nextId;\n private add;\n /** Constrain two faces so they stay flush. */\n flush(faceA: string, faceB: string): string;\n /** Constrain two faces so their normals align. */\n align(faceA: string, faceB: string): string;\n /** Constrain two faces so they remain parallel. */\n parallel(faceA: string, faceB: string): string;\n /** Constrain the distance between two faces. */\n faceDistance(faceA: string, faceB: string, distance: number): string;\n /** Constrain two axes to share the same center line. */\n concentric(axisA: string, axisB: string): string;\n /** Constrain two axes to remain parallel. */\n axisParallel(axisA: string, axisB: string): string;\n /** Constrain two points to coincide. */\n pointCoincident(pointA: string, pointB: string): string;\n /** Constrain a point to lie on a face. */\n pointOnFace(point: string, face: string): string;\n /** Constrain a point to lie on an axis. */\n pointOnAxis(point: string, axis: string): string;\n /** Constrain the angle between two faces. */\n angle(faceA: string, faceB: string, degrees: number): string;\n /** Total constraint equations. */\n get totalEquations(): number;\n}\ninterface AssemblyFrameOptions {\n origin: [\n number,\n number,\n number\n ];\n axis: [\n number,\n number,\n number\n ];\n up: [\n number,\n number,\n number\n ];\n fixed?: boolean;\n metadata?: Record<string, unknown>;\n}\ninterface AssemblyFrameDef {\n name: string;\n origin: Vec3;\n axis: Vec3;\n up: Vec3;\n fixed: boolean;\n metadata?: Record<string, unknown>;\n}\ntype AssemblyFrameJointType = "fixed" | "revolute" | "prismatic";\ninterface AssemblyFixedFrameJointOptions {\n parent: string;\n child: string;\n metadata?: Record<string, unknown>;\n}\ninterface AssemblyMovingFrameJointOptions {\n parent: string;\n child: string;\n min?: number;\n max?: number;\n default?: number;\n unit?: string;\n control?: boolean;\n metadata?: Record<string, unknown>;\n}\ninterface AssemblyFrameJointDef {\n name: string;\n type: AssemblyFrameJointType;\n parent: string;\n child: string;\n rest: Transform;\n min?: number;\n max?: number;\n defaultValue: number;\n unit?: string;\n control: boolean;\n metadata?: Record<string, unknown>;\n}\ninterface AssemblyFrameEdgeOptions {\n name?: string;\n metadata?: Record<string, unknown>;\n}\ninterface AssemblyFrameEdgeDef {\n name: string;\n a: string;\n b: string;\n metadata?: Record<string, unknown>;\n}\ninterface SolvedAssemblyFrameEdgeDef extends AssemblyFrameEdgeDef {\n start: Vec3;\n end: Vec3;\n solvedLength: number;\n}\ninterface SolvedAssemblyFrameDef extends AssemblyFrameDef {\n transform: Transform;\n}\ntype AssemblyPart = Shape | ShapeGroup;\ntype JointType = "fixed" | "revolute" | "prismatic";\ntype JointState = Record<string, number | undefined>;\ntype AssemblyControlState = Record<string, number | undefined>;\ninterface PartMetadata {\n material?: string;\n process?: string;\n tolerance?: string;\n qty?: number;\n notes?: string;\n densityKgM3?: number;\n massKg?: number;\n /** Viewport organization tags applied to scene objects produced from this part. */\n tags?: string | readonly string[];\n [key: string]: unknown;\n}\ninterface PartOptions {\n transform?: TransformInput;\n metadata?: PartMetadata;\n sim?: SimBodyDef;\n mate?: AssemblyPartMateInput | AssemblyPartMateInput[];\n bindToFrame?: string;\n}\ninterface AssemblyLinkOptions {\n /** Initial world-space position of this link before kinematic constraints solve it. */\n at?: [\n number,\n number,\n number\n ];\n /** Keep the link locked at its authored `at` position during solves. */\n fixed?: boolean;\n /** User metadata carried through the kinematic graph for inspection and tooling. */\n metadata?: Record<string, unknown>;\n}\ninterface AssemblyKinematicControlOptions {\n min?: number;\n max?: number;\n default?: number;\n unit?: string;\n}\ninterface AssemblyKinematicLimitOptions {\n min?: number;\n max?: number;\n}\ninterface AssemblyEdgeBetweenLinksOptions {\n name?: string;\n length?: number | "lockCurrent" | "free";\n min?: number;\n max?: number;\n visualOnly?: boolean;\n control?: AssemblyKinematicControlOptions;\n metadata?: Record<string, unknown>;\n}\ninterface AssemblyAngleBetweenLinksOptions {\n name?: string;\n value?: number;\n min?: number;\n max?: number;\n control?: boolean | AssemblyKinematicControlOptions;\n limit?: AssemblyKinematicLimitOptions;\n metadata?: Record<string, unknown>;\n}\ninterface AssemblyAngleReferenceDef {\n kind: "worldDirection";\n direction: Vec3;\n}\ninterface AssemblyPartMateInput {\n /** Name of a connector declared on the part (via `withConnectors()`). */\n connector: string;\n /** Name of the link this connector\'s origin is pinned to. */\n toLink: string;\n /**\n * Optional second link to orient toward. When set, the part is rotated so the\n * connector\'s **axis** aims from `toLink` toward `aimLink`, posing an oriented\n * bone instead of only translating it. For full pose without relying on a\n * connector axis, declare a second mate (two connectors → two links).\n */\n aimLink?: string;\n}\ninterface JointOptions {\n frame?: TransformInput;\n origin?: [\n number,\n number,\n number\n ];\n axis?: Vec3;\n min?: number;\n max?: number;\n default?: number;\n unit?: string;\n effort?: number;\n velocity?: number;\n damping?: number;\n friction?: number;\n drive?: SimDriveDef;\n /** Connector refs that define this joint contract. Usually set by `connect()` / `match()`. */\n connectorRefs?: JointConnectorRefs;\n /**\n * Slave this joint to another joint: `value = ratio × source + offset`.\n *\n * Use for mechanisms with one physical DOF expressed through several joints —\n * a mirrored gripper jaw (`ratio: -1`), a gear pair, a drive crank turning\n * with its servo. A followed joint stops being an independent control: the\n * Motion view drives it from its source, `solve()` derives its value (a\n * direct state override is ignored with a warning), and limits still clamp\n * the derived value.\n */\n follows?: JointFollowOptions;\n}\ninterface JointFollowOptions {\n /** Name of the source joint that drives this one. */\n joint: string;\n /** Multiplier applied to the source joint value (default 1). */\n ratio?: number;\n /** Constant added after the ratio (default 0). */\n offset?: number;\n}\ninterface JointConnectorRefs {\n parent: string;\n child: string;\n parentAlign?: PortAlign;\n childAlign?: PortAlign;\n}\ninterface AssemblyAnimationOptions {\n /** Animation length in seconds (default chosen by the viewer). */\n duration?: number;\n /** Loop the animation (default false). */\n loop?: boolean;\n /** Interpolate continuously through keyframes instead of pausing on each. */\n continuous?: boolean;\n /** Keyframes of control values by joint/control name. `at` (0..1) or `ticks` control timing. */\n keyframes: JointViewAnimationInput["keyframes"];\n /** Make this the animation that plays when the model loads. */\n default?: boolean;\n}\ninterface JointCouplingTerm {\n joint: string;\n ratio?: number;\n}\ninterface JointCouplingOptions {\n terms: JointCouplingTerm[];\n offset?: number;\n}\ninterface GearRatioLike {\n jointRatio: number;\n /** Phase offset in degrees for legacy coupling export metadata. */\n phaseDeg?: number;\n}\ninterface GearCouplingOptions {\n ratio?: number;\n pair?: GearRatioLike;\n driverTeeth?: number;\n drivenTeeth?: number;\n mesh?: "external" | "internal" | "bevel" | "face";\n offset?: number;\n driverOrigin?: [\n number,\n number,\n number\n ];\n drivenOrigin?: [\n number,\n number,\n number\n ];\n}\ninterface PartRecord {\n name: string;\n part: AssemblyPart;\n base: Transform;\n metadata?: PartMetadata;\n sim?: SimBodyDef;\n mates: AssemblyPartMateInput[];\n bindToFrame?: string;\n}\ninterface JointCouplingTermRecord {\n joint: string;\n ratio: number;\n}\ninterface AssemblyPartDef {\n name: string;\n part: AssemblyPart;\n base: Transform;\n metadata?: PartMetadata;\n sim?: SimBodyDef;\n mates: AssemblyPartMateInput[];\n bindToFrame?: string;\n}\ninterface AssemblyJointSimulationDef {\n drive?: SimDriveDef;\n}\ninterface AssemblyLinkDef {\n name: string;\n at: Vec3;\n fixed: boolean;\n metadata?: Record<string, unknown>;\n}\ninterface AssemblyEdgeBetweenLinksDef {\n name: string;\n a: string;\n b: string;\n length: number | null;\n min?: number;\n max?: number;\n visualOnly: boolean;\n control?: AssemblyKinematicControlOptions;\n metadata?: Record<string, unknown>;\n}\ninterface AssemblyAngleBetweenLinksDef {\n name: string;\n a?: string;\n b: string;\n c: string;\n reference?: AssemblyAngleReferenceDef;\n target?: number;\n min?: number;\n max?: number;\n control?: AssemblyKinematicControlOptions;\n metadata?: Record<string, unknown>;\n}\ninterface AssemblyDerivedLinkDef {\n name: string;\n fromLink: string;\n towardLink: string;\n /** Signed: positive moves from `fromLink` toward `towardLink`, negative moves away. */\n distance: number;\n}\ninterface AssemblyKinematicGraphDef {\n links: AssemblyLinkDef[];\n edges: AssemblyEdgeBetweenLinksDef[];\n angles: AssemblyAngleBetweenLinksDef[];\n derivedLinks: AssemblyDerivedLinkDef[];\n}\ninterface SolvedAssemblyLinkDef {\n name: string;\n position: Vec3;\n fixed: boolean;\n gaugeFixed?: boolean;\n}\ninterface SolvedAssemblyEdgeDef {\n name: string;\n a: string;\n b: string;\n length: number | null;\n solvedLength: number;\n residual: number;\n visualOnly: boolean;\n}\ninterface SolvedAssemblyAngleDef {\n name: string;\n a?: string;\n b: string;\n c: string;\n reference?: AssemblyAngleReferenceDef;\n target?: number;\n solvedValue: number;\n residual: number;\n control?: AssemblyKinematicControlOptions;\n}\ninterface SolvedAssemblyDerivedLinkDef extends AssemblyDerivedLinkDef {\n position: Vec3;\n}\ninterface AssemblyFloatingComponentDef {\n links: string[];\n gaugeLink: string;\n}\ninterface SolvedAssemblyKinematics {\n links: SolvedAssemblyLinkDef[];\n edges: SolvedAssemblyEdgeDef[];\n angles: SolvedAssemblyAngleDef[];\n derivedLinks: SolvedAssemblyDerivedLinkDef[];\n frameEdges?: SolvedAssemblyFrameEdgeDef[];\n controls: Record<string, number>;\n floatingComponents: AssemblyFloatingComponentDef[];\n diagnostics: string[];\n maxResidual: number;\n converged: boolean;\n}\ninterface AssemblyJointDef {\n name: string;\n type: JointType;\n parent: string;\n child: string;\n frame: Transform;\n axis: Vec3;\n min?: number;\n max?: number;\n defaultValue: number;\n unit?: string;\n effort?: number;\n velocity?: number;\n damping?: number;\n friction?: number;\n sim?: AssemblyJointSimulationDef;\n connectorRefs?: JointConnectorRefs;\n}\ninterface AssemblyJointCouplingDef {\n joint: string;\n terms: JointCouplingTermRecord[];\n offset: number;\n}\ninterface AssemblyDefinition {\n name: string;\n sim?: SimAssemblySimulationDef;\n parts: AssemblyPartDef[];\n joints: AssemblyJointDef[];\n jointCouplings: AssemblyJointCouplingDef[];\n kinematics: AssemblyKinematicGraphDef;\n frames: AssemblyFrameDef[];\n frameJoints: AssemblyFrameJointDef[];\n frameEdges: AssemblyFrameEdgeDef[];\n}\ninterface BomRow {\n part: string;\n qty: number;\n material?: string;\n process?: string;\n tolerance?: string;\n notes?: string;\n metadata?: PartMetadata;\n}\ninterface CollisionOptions {\n parts?: string[];\n ignorePairs?: Array<[\n string,\n string\n ]>;\n minOverlapVolume?: number;\n}\ninterface CollisionFinding {\n partA: string;\n partB: string;\n overlapVolume: number;\n}\ninterface JointSweepFrame {\n value: number;\n collisions: CollisionFinding[];\n warnings: string[];\n}\n/**\n * Convert an array of BOM rows into a CSV string.\n *\n * **Details**\n *\n * Produces a CSV with columns: `part`, `qty`, `material`, `process`, `tolerance`, `notes`.\n * String values are quoted and internal double-quotes are escaped. Prefer calling\n * `solvedAssembly.bomCsv()` directly — this function is exposed for custom BOM processing.\n *\n * @param rows - BOM rows from `SolvedAssembly.bom()`\n * @returns CSV string with header row\n * @see {@link SolvedAssembly.bomCsv} for the all-in-one method\n * @softDeprecated solvedAssembly.bomCsv() — the method form covers BOM CSV export\n * @deprecated use solvedAssembly.bomCsv() — the method form covers BOM CSV export\n * @category Assembly\n */\ndeclare function bomToCsv(rows: BomRow[]): string;\ninterface MateMetadata {\n explodeHints: Record<string, {\n direction: Vec3;\n }>;\n dof: number;\n converged: boolean;\n}\n/**\n * The result of solving an assembly at a specific joint state.\n *\n * **Details**\n *\n * `SolvedAssembly` holds world-space transforms for every part at a given pose.\n * Top-level scripts can return a `SolvedAssembly` directly for display. Use\n * `toGroup()` when you specifically need a `ShapeGroup` for composition,\n * group-style transforms, or named-child lookup. Do not call `toGroup()` just\n * to make a solved assembly render. Use `getPart()` / `getTransform()` to\n * inspect individual parts programmatically.\n *\n * **Validation**\n *\n * Call `collisionReport()` to detect overlapping parts at this solved pose.\n *\n * **Example**\n *\n * ```ts\n * const solved = mech.solve({ shoulder: 45, elbow: -20 });\n * console.log("Collisions", solved.collisionReport());\n * return solved;\n * ```\n *\n * @category Assembly\n */\ndeclare class SolvedAssembly {\n readonly name: string;\n private readonly parts;\n private readonly transforms;\n private readonly jointValues;\n private readonly solveWarnings;\n private readonly _mateMetadata;\n private readonly _kinematics;\n private readonly _frames;\n private readonly _usedPortRefs;\n constructor(name: string, parts: Map<string, PartRecord>, transforms: Map<string, Transform>, jointValues: JointState, solveWarnings: string[], _mateMetadata?: MateMetadata | null, _kinematics?: SolvedAssemblyKinematics | null, _frames?: Map<string, SolvedAssemblyFrameDef>, usedPortRefs?: ReadonlySet<string>);\n /** Return any warnings generated during solve (clamped joints, unconverged mates, etc.). */\n warnings(): string[];\n /** Return a snapshot of resolved joint values (after clamping and coupling). */\n getJointState(): JointState;\n /**\n * Explode direction hints derived from mate constraints, or null if no mates.\n *\n * @softDeprecated explode hints derive automatically from connect()/match() joints — use explodeView()\n * @deprecated use explode hints derive automatically from connect()/match() joints — use explodeView()\n */\n get mateExplodeHints(): Record<string, {\n direction: Vec3;\n }> | null;\n /**\n * Remaining degrees of freedom after mate constraints, or null if no mates.\n *\n * @softDeprecated diagnostics for the legacy mate() solver — position parts with connect()/match() and inspect kinematics instead\n * @deprecated use diagnostics for the legacy mate() solver — position parts with connect()/match() and inspect kinematics instead\n */\n get mateDof(): number | null;\n /**\n * Whether the mate constraint solver converged, or null if no mates.\n *\n * @softDeprecated diagnostics for the legacy mate() solver — position parts with connect()/match() and inspect kinematics instead\n * @deprecated use diagnostics for the legacy mate() solver — position parts with connect()/match() and inspect kinematics instead\n */\n get mateConverged(): boolean | null;\n /** Solved assembly-native kinematic or frame-edge overlay data, or null when no rig overlay data was declared. */\n get kinematics(): SolvedAssemblyKinematics | null;\n /** Return the solved world position of a kinematic link. */\n getLinkPosition(linkName: string): Vec3;\n /** Return the solved world transform for a named rig frame. */\n getFrame(frameName: string): Transform;\n /** Return solved rig frames, including origin, axis, up, and transform. */\n get frames(): SolvedAssemblyFrameDef[];\n /**\n * Return the world-space `Transform` for the named part at the solved pose.\n *\n * @param partName - Name as registered with `addPart()`\n * @returns The world transform (position + orientation) of the part\n * @category Assembly\n */\n getTransform(partName: string): Transform;\n /**\n * Return the named part already positioned at its solved world transform.\n *\n * @param partName - Name as registered with `addPart()`\n * @returns The part (`Shape` or `ShapeGroup`) at its solved world position\n * @category Assembly\n */\n getPart(partName: string): AssemblyPart;\n /** @internal */\n _toSceneObjects(options?: {\n bakeTransforms?: boolean;\n }): Array<{\n name: string;\n shape?: Shape;\n group?: Array<{\n name: string;\n shape: Shape;\n tags?: string[];\n assemblyTransform?: Mat4;\n }>;\n metadata?: PartMetadata;\n assemblyTransform?: Mat4;\n }>;\n /**\n * Convert all solved parts into a `ShapeGroup` with named children.\n *\n * **Details**\n *\n * Each part becomes a named child in the group, already positioned at its\n * solved world transform. Use this only when you specifically need a\n * `ShapeGroup` for composition, `ShapeGroup` transforms, or named-child\n * access. Top-level scripts can return the `SolvedAssembly` directly; do not\n * call `toGroup()` just to make a solved assembly render.\n *\n * **Example**\n *\n * ```ts\n * const armGroup = mech.solve({ shoulder: 60 }).toGroup(); // only because we need rotateZ()\n * return armGroup.rotateZ(90);\n * ```\n *\n * @returns `ShapeGroup` with one child per part, each at its solved world position\n * @category Assembly\n */\n toGroup(): ShapeGroup;\n /**\n * Return an array of named scene objects for the viewport renderer.\n *\n * **Details**\n *\n * Each part becomes `{ name, shape }` or `{ name, group: [...] }` if the\n * part is a `ShapeGroup`. Top-level scripts should normally return the\n * `SolvedAssembly` directly. Use `toGroup()` when you need `ShapeGroup`\n * behavior; use this method only for advanced scene-graph control where you\n * need access to the flat per-part array with metadata.\n *\n * @returns Array of `{ name, shape?, group?, metadata? }` for each part\n * @category Assembly\n */\n toSceneObjects(): Array<{\n name: string;\n shape?: Shape;\n group?: Array<{\n name: string;\n shape: Shape;\n tags?: string[];\n }>;\n metadata?: PartMetadata;\n }>;\n /**\n * Backward-compatible alias for `toSceneObjects()`.\n *\n * @softDeprecated toSceneObjects() — same return shape; or return the SolvedAssembly directly\n * @deprecated use toSceneObjects() — same return shape; or return the SolvedAssembly directly\n */\n toScene(): Array<{\n name: string;\n shape?: Shape;\n group?: Array<{\n name: string;\n shape: Shape;\n tags?: string[];\n }>;\n metadata?: PartMetadata;\n }>;\n /**\n * Generate a bill of materials for all parts in the solved assembly.\n *\n * @returns Array of `BomRow` with part name, qty, and any metadata (material, process, etc.)\n * @see {@link bomCsv} for CSV-formatted output\n * @category Assembly\n */\n bom(): BomRow[];\n /**\n * Generate a bill of materials as a CSV string.\n *\n * @returns CSV-formatted BOM with columns: part, qty, material, process, tolerance, notes\n * @see {@link bom} for the structured row array\n * @category Assembly\n */\n bomCsv(): string;\n /**\n * Detect overlapping (colliding) part pairs in this solved pose.\n *\n * **Details**\n *\n * Computes boolean intersections between all part pairs and returns findings\n * where the overlap volume exceeds `minOverlapVolume` (default 0.1 mm³).\n *\n * **Example**\n *\n * ```ts\n * const solved = mech.solve({ shoulder: 35, elbow: 60 });\n * console.log("Collisions", solved.collisionReport());\n * ```\n *\n * @param options - Filter by `parts`, ignore specific `ignorePairs`, or set `minOverlapVolume`\n * @returns Array of `{ partA, partB, overlapVolume }` for each interference found\n * @category Assembly\n */\n collisionReport(options?: CollisionOptions): CollisionFinding[];\n /**\n * Compute the minimum gap (clearance) between two parts in this solved pose.\n *\n * **Details**\n *\n * Returns `0` if the parts are touching or overlapping. Manifold-backed parts use\n * the exact Manifold gap query. SDF-backed parts use a mesh-derived sampled gap.\n * `searchLength` bounds the Manifold search radius in mm — increase it for widely\n * separated Manifold parts.\n *\n * @param partA - Name of the first part\n * @param partB - Name of the second part\n * @param searchLength - Maximum search radius in mm (default 10)\n * @returns Minimum gap in mm; `0` means touching or interfering\n * @category Assembly\n */\n minClearance(partA: string, partB: string, searchLength?: number): number;\n}\ninterface ConnectOptions {\n as?: string;\n type?: JointType;\n /** Lower joint-slider limit; solve clamps to it with a warning. Not a physical stop — enforce real travel limits with stop geometry. */\n min?: number;\n /** Upper joint-slider limit; same semantics as `min`. */\n max?: number;\n default?: number;\n unit?: string;\n /**\n * @deprecated Connectors now always meet face-to-face (anti-parallel axes).\n * This parameter is ignored. If your connectors produce wrong orientation,\n * fix the connector axis directions instead of using flip.\n */\n flip?: boolean;\n /** Which point on the parent connector to align: \'start\', \'middle\' (default), or \'end\'. */\n parentAlign?: PortAlign;\n /** Which point on the child connector to align: \'start\', \'middle\' (default), or \'end\'. */\n childAlign?: PortAlign;\n /** Shorthand: set both parentAlign and childAlign at once. */\n align?: PortAlign;\n /** Slave this joint to another joint: `value = ratio × source + offset` (e.g. a mirrored jaw with `ratio: -1`). */\n follows?: JointFollowOptions;\n effort?: number;\n velocity?: number;\n damping?: number;\n friction?: number;\n drive?: SimDriveDef;\n}\ninterface ToJointsViewOptions {\n defaults?: Record<string, number>;\n overrides?: Record<string, Partial<JointViewInput>>;\n animations?: JointViewAnimationInput[];\n couplings?: JointViewCouplingInput[];\n defaultAnimation?: string;\n enabled?: boolean;\n}\n/**\n * Container for a kinematic mechanism made up of links, relationships, and parts.\n * See {@link assembly} for the link-graph vs connector-frame decision rules.\n *\n * **Details**\n *\n * Returning an unsolved `Assembly` keeps the graph available to the runtime;\n * return `mech.solve({ theta: 60 })` for a fixed pose instead.\n *\n * **Return types**\n *\n * | Return value | Standalone | `require()` result type |\n * |---|---|---|\n * | `Assembly` (unsolved) | yes | `ImportedAssembly` |\n * | `SolvedAssembly` | yes | `SolvedAssembly` |\n *\n * @category Assembly\n */\ndeclare class Assembly {\n readonly name: string;\n private readonly parts;\n private readonly joints;\n private readonly jointCouplings;\n private readonly frames;\n private readonly frameJoints;\n private readonly frameEdges;\n private readonly links;\n private readonly linkEdges;\n private readonly linkAngles;\n private readonly derivedLinks;\n private readonly _mateFns;\n private readonly viewAnimations;\n private _defaultViewAnimation;\n private _refs;\n private _simulation?;\n private readonly _portsByPart;\n private readonly _usedPortRefs;\n private _connectCounter;\n private _frameEdgeCounter;\n private _linkEdgeCounter;\n private _linkAngleCounter;\n constructor(name?: string);\n private _fork;\n /** Connector refs (e.g. "PartName.connectorName") consumed by connect/match calls. */\n get usedConnectorRefs(): ReadonlySet<string>;\n /**\n * Compatibility alias for `usedConnectorRefs`.\n *\n * @softDeprecated usedConnectorRefs — same ReadonlySet\n * @deprecated use usedConnectorRefs — same ReadonlySet\n */\n get usedPortRefs(): ReadonlySet<string>;\n /**\n * Register mate constraints between parts.\n * Constraints are solved during `solve()` to derive part positions and explode hints.\n * Part references use "partName:featureName" format.\n *\n * @softDeprecated connect("Parent.connectorA", "Child.connectorB") / match("Child.connectorB", "Parent.connectorA") — connector-frame joints replace string-ref mates\n * @deprecated use connect("Parent.connectorA", "Child.connectorB") / match("Child.connectorB", "Parent.connectorA") — connector-frame joints replace string-ref mates\n */\n mate(fn: (m: MateBuilder) => void): Assembly;\n /**\n * Attach named placement reference points to this assembly.\n * These are surfaced automatically on the ImportedAssembly when this file is\n * imported via require(), so consumers can use placeReference() without\n * re-declaring them.\n * Returns a new Assembly — does not mutate.\n */\n withReferences(refs: Pick<PlacementReferenceInput, "points">): Assembly;\n /** @internal — used by require() to seed ImportedAssembly refs. */\n getReferences(): PlacementReferences;\n /**\n * Attach the root simulation contract for this assembly.\n *\n * Use this after adding physical parts and joints. Robot-body profiles require\n * `rootPart`; asset profiles can describe one-part or multi-part physical assets.\n * URDF/SDF exporters and `forgecad check simready` read this contract directly,\n * so model files no longer need a separate `robotExport(...)` side effect.\n *\n * @category Assembly\n */\n withSimulation(options: SimAssemblySimulationOptions): Assembly;\n /**\n * Attach named connectors to a specific part or the assembly as a whole.\n *\n * **Details**\n *\n * Connectors declared this way are in the part\'s local coordinate system.\n * They are captured automatically if the incoming `Shape` already has\n * connectors via `shape.withConnectors(...)`, but you can also add or\n * override connectors after the fact with this method.\n *\n * Use the single-argument overload to attach assembly-level connectors —\n * these are exposed when this assembly is imported as a sub-assembly.\n *\n * @param partName - Part name (must already be added via `addPart`)\n * @param connectors - Map of connector name → `ConnectorInput`\n * @returns `this` for chaining\n * @see {@link connect} for using connectors to create joints\n * @category Connectors\n */\n withConnectors(partName: string, connectors: Record<string, ConnectorInput>): Assembly;\n withConnectors(connectors: Record<string, ConnectorInput>): Assembly;\n /**\n * Backward-compatible alias for `withConnectors()`.\n *\n * @deprecated Use `withConnectors()` instead.\n */\n withPorts(partNameOrPorts: string | Record<string, PortInput>, maybePorts?: Record<string, PortInput>): Assembly;\n /** Get connectors declared on a part in part-local space. */\n getConnectors(partName: string): ConnectorMap;\n /**\n * Backward-compatible alias for `getConnectors()`.\n *\n * @deprecated Use `getConnectors()` instead.\n */\n getPorts(partName: string): PortMap;\n /** @internal — set already-normalized connectors directly (used by mergeInto). */\n _setPortsDirect(partName: string, ports: PortMap): void;\n /** @internal — get all connector maps, keyed by part name. */\n getAllPorts(): Map<string, PortMap>;\n /**\n * Parse a "PartName.connectorName" reference and return the resolved connector.\n * Throws descriptive errors if the part or connector doesn\'t exist.\n */\n getConnector(ref: string): {\n partName: string;\n connectorName: string;\n connector: ConnectorDef;\n };\n /**\n * Compatibility alias for `getConnector()`.\n *\n * @softDeprecated getConnector(ref) — same "PartName.connectorName" ref; fields are partName/connectorName/connector\n * @deprecated use getConnector(ref) — same "PartName.connectorName" ref; fields are partName/connectorName/connector\n */\n getPort(ref: string): {\n partName: string;\n portName: string;\n port: PortDef;\n };\n /**\n * Add a named rig frame to the assembly.\n *\n * A frame is a solved pose: `origin` plus orientation. `axis` is the frame\'s\n * primary direction and `up` fixes roll around that axis. Use frames for\n * robot links, joint axes, and parts that must carry orientation. Use\n * `link()` for solved points in distance/angle graphs.\n *\n * @category Assembly\n */\n frame(name: string, options: AssemblyFrameOptions): Assembly;\n private resolveFrameJointEndpoint;\n private addFrameJoint;\n /**\n * Rigidly attach a child rig frame to a parent rig frame.\n *\n * Fixed joints carry frame hierarchy but do not expose a Motion control.\n *\n * @category Assembly\n */\n fixedJoint(name: string, options: AssemblyFixedFrameJointOptions): Assembly;\n /**\n * Add a revolute rig-frame joint.\n *\n * The child frame rotates around the parent frame\'s `axis` direction. Moving\n * frame joints appear in Motion by default; pass `control: false` to keep the\n * joint solved at its default value without showing a Motion control.\n *\n * @category Assembly\n */\n revoluteJoint(name: string, options: AssemblyMovingFrameJointOptions): Assembly;\n /**\n * Add a prismatic rig-frame joint.\n *\n * The child frame translates along the parent frame\'s `axis` direction. Moving\n * frame joints appear in Motion by default; pass `control: false` to keep the\n * joint solved at its default value without showing a Motion control.\n *\n * @category Assembly\n */\n prismaticJoint(name: string, options: AssemblyMovingFrameJointOptions): Assembly;\n /**\n * Add a visual skeleton edge between two rig frame origins.\n *\n * Frame edges follow the solved frame poses produced by `fixedJoint()`,\n * `revoluteJoint()`, and `prismaticJoint()`. They do not add constraints,\n * degrees of freedom, parts, or geometry; use them to make a frame-only rig\n * readable in the Motion/rig inspection overlay.\n *\n * @category Assembly\n */\n edgeBetweenFrames(a: string, b: string, options?: AssemblyFrameEdgeOptions): Assembly;\n /**\n * Add a named kinematic link to the assembly graph.\n *\n * Links are assembly-native solved points. They can exist before any geometry\n * is attached, can be displayed by the viewport, and are solved by\n * link/edge/angle constraints.\n *\n * A link is not a rigid-body frame. It has a world position but no orientation\n * basis. Use `connect()` when a physical part must inherit a connector frame\n * and rotate about a real hinge/slider axis.\n *\n * @category Assembly\n */\n link(name: string, options?: AssemblyLinkOptions): Assembly;\n /**\n * Add a relationship edge between two kinematic links.\n *\n * By default the edge captures the authored distance between links as a\n * structural length. Pass `{ length: \'free\' }` or `{ visualOnly: true }` for\n * a non-structural overlay edge.\n *\n * @category Assembly\n */\n edgeBetweenLinks(a: string, b: string, options?: AssemblyEdgeBetweenLinksOptions): Assembly;\n /**\n * Add an angle relationship among three kinematic links.\n *\n * The middle link is the vertex. When `control` is set, `solve(state)` reads\n * the control value from `state[name]` and solves dependent links from that\n * driven angle.\n *\n * @category Assembly\n */\n addAngleBetweenLinks(a: string, b: string, c: string, options?: AssemblyAngleBetweenLinksOptions): Assembly;\n /**\n * Add an absolute angle relationship from a world direction to a link segment.\n *\n * The first link is the vertex/pivot and the second link is the moving point.\n * A value of `0` places `fromLink -> toLink` along `direction` in the\n * mechanism plane; positive angles rotate counter-clockwise in that plane.\n *\n * Use `Points.polar(1, angleDeg)` when the reference direction is planar and\n * angle-based instead of axis-aligned.\n *\n * @category Assembly\n */\n addAngleBetweenLinkSegmentAndWorldDirection(fromLink: string, toLink: string, direction: Vec3, options?: AssemblyAngleBetweenLinksOptions): Assembly;\n /**\n * @deprecated Use `addAngleBetweenLinkSegmentAndWorldDirection(fromLink, toLink, [1, 0, 0], options)`.\n * @skillSuppress Compatibility-only renamed API. Use `addAngleBetweenLinkSegmentAndWorldDirection()` instead.\n */\n addAngleOfLinkSegmentFromXAxis(fromLink: string, toLink: string, options?: AssemblyAngleBetweenLinksOptions): Assembly;\n /**\n * @deprecated Use `addAngleBetweenLinkSegmentAndWorldDirection(fromLink, toLink, [0, 1, 0], options)`.\n * @skillSuppress Compatibility-only renamed API. Use `addAngleBetweenLinkSegmentAndWorldDirection()` instead.\n */\n addAngleOfLinkSegmentFromYAxis(fromLink: string, toLink: string, options?: AssemblyAngleBetweenLinksOptions): Assembly;\n private assertNotDerivedStructuralLink;\n private assertCanBecomeDerivedLink;\n /**\n * Create a derived link on the line through `fromLink` and `towardLink`, at a\n * **signed** distance from `fromLink`.\n *\n * **Sign convention** (read this first):\n * - `distance > 0` — the point moves from `fromLink` **toward** `towardLink`.\n * - `distance < 0` — the point moves from `fromLink` **away from** `towardLink`\n * (the coupler-extension case, e.g. the Chebyshev lambda linkage\'s trace\n * point beyond the rocker joint).\n * - `distance` greater than the solved edge length places the point **beyond**\n * `towardLink`, still on the same line.\n *\n * Derived links are trace/reference points. They are recomputed after the\n * primary link solve and cannot participate in structural edges or angle\n * constraints. Because the distance is one signed parameter, a `param()`-driven\n * value can sweep continuously from extension (negative) through `fromLink`\n * (zero) to beyond `towardLink` (large positive).\n *\n * **Example**\n *\n * ```ts\n * // Chebyshev lambda linkage: trace point C3 extends beyond C2, away from C1.\n * mech.linkAlong(\'C3\', \'C2\', \'C1\', -2.5 * a);\n * // Midpoint-style reference 30 mm from A toward B:\n * mech.linkAlong(\'probe\', \'A\', \'B\', 30);\n * ```\n *\n * @param name - Name for the derived link (created if it does not exist)\n * @param fromLink - Link the distance is measured from\n * @param towardLink - Link that defines the positive direction\n * @param distance - Signed distance in mm (negative extends away from `towardLink`)\n * @returns `this` for chaining\n * @category Assembly\n */\n linkAlong(name: string, fromLink: string, towardLink: string, distance: number): Assembly;\n /**\n * Create a derived link at a fixed distance from `fromLink` toward `towardLink`.\n *\n * Derived links are trace/reference points. They are recomputed after the\n * primary link solve and cannot participate in structural edges or angle\n * constraints.\n *\n * @softDeprecated linkAlong(name, fromLink, towardLink, distance) — same arguments; positive distance moves toward towardLink, negative moves away\n * @deprecated use linkAlong(name, fromLink, towardLink, distance) — same arguments; positive distance moves toward towardLink, negative moves away\n * @category Assembly\n */\n linkToward(name: string, fromLink: string, towardLink: string, distance: number): Assembly;\n /**\n * Create a derived link at a fixed distance from `fromLink` away from `awayFromLink`.\n *\n * Use this for coupler trace/extension points such as the Chebyshev lambda\n * linkage\'s point beyond the rocker joint.\n *\n * @softDeprecated linkAlong(name, fromLink, awayFromLink, -distance) — negate the distance: negative values extend away from the reference link\n * @deprecated use linkAlong(name, fromLink, awayFromLink, -distance) — negate the distance: negative values extend away from the reference link\n * @category Assembly\n */\n linkAwayFrom(name: string, fromLink: string, awayFromLink: string, distance: number): Assembly;\n private normalizeAngleOptions;\n /** Return the assembly-native kinematic graph definition. */\n describeKinematics(): AssemblyKinematicGraphDef;\n /**\n * @deprecated `addFrame()` has been removed. Use `frame()` for rig frames, or\n * `addPart(name, group())` for an empty placeholder part.\n * @internal\n */\n addFrame(_name: string, _options?: PartOptions): Assembly;\n /**\n * Add a named part to the assembly.\n *\n * **Details**\n *\n * Connectors declared on the part (via `withConnectors()`) are captured automatically.\n * Parts are positioned at world origin by default unless a `transform` is\n * provided in `options`. For root parts (no incoming joint), `transform` is\n * their final world position.\n *\n * `options.mate` is for point-link attachments. During `solve()`, ForgeCAD\n * translates the part so the named connector origin lands on the solved link\n * position. The part keeps its existing orientation; connector `axis` and `up`\n * are not used for link mating. Use this for markers, sensors, labels, and\n * other geometry that should ride on a solved point. Use `connect()` for\n * oriented physical parts such as limbs, levers, hinges, and wheels.\n *\n * When a part is a `ShapeGroup`, name the group children explicitly to get\n * readable viewport labels (e.g. `"Base Assembly.Body"` instead of\n * `"Base Assembly.1"`):\n *\n * ```ts\n * const housing = group(\n * { name: "Body", shape: body },\n * { name: "Lid", shape: lid },\n * );\n * assembly.addPart("Base Assembly", housing);\n * ```\n *\n * @param name - Unique part name (must not already exist)\n * @param part - The `Shape` or `ShapeGroup` geometry\n * @param options - Optional `{ transform, metadata }` (material, process, qty, tags, etc.)\n * @returns `this` for chaining\n * @category Assembly\n */\n addPart(name: string, part: AssemblyPart, options?: PartOptions): Assembly;\n /**\n * Add a kinematic joint between a parent and child part.\n *\n * **Details**\n *\n * `frame` is a transform from the **parent part frame** to the **joint frame\n * at zero state**. The child\'s world position is computed as:\n *\n * ```\n * childWorld = parentWorld × frame × motion(value) × childBase\n * ```\n *\n * For revolute joints `value` is in degrees; for prismatic joints `value` is\n * in mm.\n *\n * @param name - Unique joint name\n * @param type - `\'revolute\'`, `\'prismatic\'`, or `\'fixed\'`\n * @param parent - Name of the parent part (already added via `addPart`)\n * @param child - Name of the child part (already added via `addPart`)\n * @param options - Frame, axis, limits, effort, velocity, damping, friction\n * @returns `this` for chaining\n * @category Joints\n * @internal\n */\n addJoint(name: string, type: JointType, parent: string, child: string, options?: JointOptions): Assembly;\n /**\n * Shorthand for `addJoint(name, \'revolute\', parent, child, options)`.\n *\n * `min`/`max` bound the editor\'s joint slider and clamp solve state (with a\n * warning) — they are not physical stops. Enforce real travel limits with\n * stop geometry.\n *\n * @param name - Unique joint name\n * @param parent - Parent part name\n * @param child - Child part name\n * @param options - Frame, axis, min/max degrees, effort, velocity, damping, friction\n * @returns `this` for chaining\n * @category Joints\n * @internal\n */\n addRevolute(name: string, parent: string, child: string, options?: JointOptions): Assembly;\n /**\n * Shorthand for `addJoint(name, \'prismatic\', parent, child, options)`.\n *\n * @param name - Unique joint name\n * @param parent - Parent part name\n * @param child - Child part name\n * @param options - Frame, axis, min/max mm, effort, velocity, damping, friction\n * @returns `this` for chaining\n * @category Joints\n * @internal\n */\n addPrismatic(name: string, parent: string, child: string, options?: JointOptions): Assembly;\n /**\n * Shorthand for `addJoint(name, \'fixed\', parent, child, options)`.\n *\n * Fixed joints rigidly attach a child part to its parent at `frame` with no motion.\n * Before calling `mergeInto()`, use `addFixed()` to collapse multiple root parts\n * into a single root.\n *\n * @param name - Unique joint name\n * @param parent - Parent part name\n * @param child - Child part name\n * @param options - Optional frame transform for placement offset\n * @returns `this` for chaining\n * @category Joints\n * @internal\n */\n addFixed(name: string, parent: string, child: string, options?: JointOptions): Assembly;\n /**\n * Connect two parts by aligning their declared connectors, automatically computing frame and axis.\n *\n * **Details**\n *\n * Connector refs use `"PartName.connectorName"`. The child connector origin\n * lands exactly on the parent connector origin; joint frame and axis are\n * derived from the connector geometry — no manual `frame`/`axis` math.\n *\n * Frame semantics: `origin` is the pivot/contact point, `axis` the hinge or\n * slide direction, `up` locks the part\'s zero-state twist. Omitted `up` gets a\n * deterministic perpendicular — provide `up` whenever rest orientation matters.\n * (`addPart(..., { mate })` translates only; see `addPart`.)\n *\n * **Face-to-face:** each connector\'s axis points outward from its part;\n * mating makes the axes anti-parallel, like a plug meeting a socket (same\n * convention as `matchTo()`).\n *\n * **Revolute sign:** a positive joint value follows the right-hand rule about\n * the **child** connector\'s placed axis. Because face-to-face mating makes\n * the axes anti-parallel, that is the *left*-hand rule about the parent\n * connector\'s outward axis — if `+30` swings the opposite way you expected,\n * you predicted from the parent\'s axis. `forgecad debug assembly` prints each\n * joint\'s resolved world axis.\n *\n * **Mirrored revolute axes:** because of the right-hand rule, a mirrored\n * hinge axis (`[1, 0, 0]` vs `[-1, 0, 0]`) rotates oppositely for the\n * same `+theta`: negate the mirrored side\'s value and mirror limits as\n * `[min, max] -> [-max, -min]`. Prismatic joints have no handedness flip. Use\n * an explicit per-side sign mapping (or side-neutral link controls) for\n * bilateral mechanisms.\n *\n * Joint type defaults to the connector\'s `kind`. For `start`/`end` connectors,\n * `align` / `parentAlign` / `childAlign` (`\'start\' | \'middle\' | \'end\'`) choose\n * which point meets.\n *\n * **Example**\n *\n * ```ts\n * const frame = box(100, 10, 80).withConnectors({\n * hinge: connector("hinge", { origin: [0, 0, 40], axis: [0, 0, 1], up: [1, 0, 0] }),\n * });\n * const door = box(60, 4, 80).withConnectors({\n * hinge: connector("hinge", { origin: [0, 0, 40], axis: [0, 0, -1], up: [1, 0, 0] }),\n * });\n * assembly("Door").addPart("Frame", frame).addPart("Door", door)\n * .connect("Frame.hinge", "Door.hinge", { as: "swing", min: 0, max: 110 });\n * ```\n *\n * @param parentConnectorRef - `"PartName.connectorName"` on the parent side\n * @param childConnectorRef - `"PartName.connectorName"` on the child side\n * @param options - `as` (joint name), `type`, limits, alignment — see `ConnectOptions`\n * @returns `this` for chaining\n * @see {@link match} for typed connector matching with gender/type validation\n * @category Connectors\n */\n connect(parentConnectorRef: string, childConnectorRef: string, options?: ConnectOptions): Assembly;\n /**\n * Auto-create a joint by matching typed connectors between two parts.\n *\n * **Details**\n *\n * Connectors can carry a `connectorType` string and a `gender`\n * (`\'male\'`, `\'female\'`, or `\'neutral\'`). `match()` validates type and gender\n * compatibility (use `{ force: true }` to skip validation) and creates the\n * joint automatically from the connector\'s `kind` metadata.\n *\n * The `pairs` map is `{ childConnector: parentConnector }`. The first pair\n * drives joint creation; additional pairs are validated but do not create\n * additional joints (they constrain the same rigid connection).\n *\n * Define connectors on shapes with `shape.withConnectors(...)`:\n *\n * ```ts\n * const door = doorShape.withConnectors({\n * hinge_top: connector.male("hinge", { origin: [0, 0, 90], axis: [0, 0, 1] }),\n * hinge_bottom: connector.male("hinge", { origin: [0, 0, 10], axis: [0, 0, 1] }),\n * });\n * ```\n *\n * Then match in the assembly:\n *\n * ```ts\n * const mech = assembly("Door")\n * .addPart("Frame", frame)\n * .addPart("Door", door)\n * .match("Door", "Frame", { hinge_top: "hinge_top", hinge_bottom: "hinge_bottom" });\n * // Matching connectors computes the placement relationship automatically.\n * ```\n *\n * @param childPartName - Part name of the child\n * @param parentPartName - Part name of the parent\n * @param pairs - Map of `{ childConnectorName: parentConnectorName }`\n * @param options - `force` (skip gender/type validation), `as` (joint name override)\n * @returns `this` for chaining\n * @see {@link connect} for connector-based connections without type/gender validation\n * @category Connectors\n */\n match(childPartName: string, parentPartName: string, pairs: Record<string, string>, options?: MatchToOptions & {\n as?: string;\n }): Assembly;\n /**\n * @deprecated `addJointCoupling()` has been removed from the modeling API.\n * Model mechanism behavior with assembly links, `edgeBetweenLinks()`, and\n * controlled `addAngleBetweenLinks()` constraints instead.\n * @internal\n */\n addJointCoupling(_jointName: string, _options: JointCouplingOptions): Assembly;\n /**\n * @deprecated `addGearCoupling()` has been removed from the modeling API.\n * Gear behavior should be expressed by the assembly kinematic graph, not by\n * separate joint-mimic sugar.\n * @internal\n */\n addGearCoupling(_drivenJointName: string, _driverJointName: string, _options?: GearCouplingOptions): Assembly;\n private solveFrames;\n private solveFrameEdges;\n private attachFrameEdgesToKinematics;\n private solveKinematicLinks;\n private buildKinematicWorkplane;\n private solveKinematicLinksPlanar;\n private evaluateDerivedLinks;\n private solveKinematicLinksLegacy;\n /**\n * Solve the assembly at the given control state and return positioned parts.\n *\n * **Details**\n *\n * Solves assembly-native kinematic links first. Controlled\n * `addAngleBetweenLinks()` relationships read values from `state` by name,\n * clamp to their declared limits, and expose the solved graph on\n * `SolvedAssembly.kinematics`. Angles solve in the plane of their three\n * authored link positions, so a limb that swings out of the `z = 0` plane\n * poses correctly; structural edges hold their bone lengths so a fully\n * angle-driven serial chain follows forward kinematics.\n *\n * Connector mates declared on `addPart(..., { mate })` attach geometry to\n * solved links while preserving part and connector identity:\n * - one mate **positions** the connector origin on its link;\n * - a mate with `aimLink` (or a second mate to another link) also\n * **orients** the part, rotating an oriented bone to span its links rather\n * than only translating it;\n * - a third mate **pins the roll** about the bone axis (full frame), e.g. a\n * bore or clevis that must face a specific way.\n *\n * Connector-frame joints created by `connect()` / `match()` are also\n * evaluated; their values are read from `state` by joint name and clamped to\n * joint limits.\n *\n * **Example**\n *\n * ```ts\n * return mech.solve({ theta: 45 });\n * ```\n *\n * @param state - Map of control or legacy joint name → value\n * @returns `SolvedAssembly` with all parts at their computed world positions\n * @category Assembly\n */\n solve(state?: JointState): SolvedAssembly;\n private descendantPartNames;\n private jointDrivesCoupling;\n private sweepCollisionOptions;\n /**\n * @deprecated `sweepJoint()` has been removed from the modeling API. Motion\n * sweeps are diagnostics and will be exposed from CLI/debug tooling instead.\n * @internal\n */\n sweepJoint(_jointName: string, _from: number, _to: number, _steps: number, _baseState?: JointState, _collisionOptions?: CollisionOptions): JointSweepFrame[];\n /**\n * Internal diagnostic sweep used by assembly audits until CLI sweep tooling\n * owns this workflow.\n *\n * @internal\n */\n _sweepJointForDiagnostics(jointName: string, from: number, to: number, steps: number, baseState?: JointState, collisionOptions?: CollisionOptions): JointSweepFrame[];\n /**\n * Build the control view used by returned assemblies.\n *\n * @internal\n */\n _collectAssemblyControlsView(options?: ToJointsViewOptions, state?: JointState): CollectedJointsView;\n private buildJointsViewOptions;\n /**\n * Register a named keyframe animation for this assembly\'s Motion view.\n *\n * **Details**\n *\n * Works with the returned-assembly controls path: return the unsolved\n * `Assembly` and the animation appears in the Motion tab alongside the\n * solver-backed joint controls. Keyframes hold control values by joint\n * name; joints declared with `follows` are derived automatically and must\n * not appear in keyframes.\n *\n * **Example**\n *\n * ```ts\n * robot.addAnimation("Pick and place", {\n * duration: 12,\n * loop: true,\n * keyframes: [\n * { values: { J1: 0, J2: -90 } },\n * { values: { J1: 45, J2: -30 } },\n * { values: { J1: 0, J2: -90 } },\n * ],\n * });\n * return robot;\n * ```\n *\n * @param name - Animation name shown in the Motion tab\n * @param options - `duration` (seconds), `loop`, `continuous`, `keyframes`, `default` (make this the animation that plays on load)\n * @returns `this` for chaining\n * @category Assembly\n */\n addAnimation(name: string, options: AssemblyAnimationOptions): Assembly;\n /**\n * Deprecated adapter that derives viewport-only FK controls from the assembly graph.\n *\n * **Details**\n *\n * Prefer `return mech;` — the runtime exposes returned-assembly controls\n * automatically and re-runs `solve(state)` on every change, which handles\n * closed loops and avoids stacking a viewport transform on solved geometry.\n * This adapter remains only for legacy scripts that want the old\n * viewport-only FK behavior; see {@link jointsView} for its caveats.\n *\n * @softDeprecated return the Assembly directly (return mech) — solver-backed controls appear automatically; use addAnimation(name, { keyframes }) for animation clips\n * @deprecated use return the Assembly directly (return mech) — solver-backed controls appear automatically; use addAnimation(name, { keyframes }) for animation clips\n * @skillSuppress Compatibility-only viewport FK adapter. Prefer returning `Assembly` directly so controls move through the solver-backed link/edge kinematics model.\n * @param options - `defaults`, `animations`, `couplings`, `overrides`, `enabled`\n * @category Assembly\n */\n toJointsView(options?: ToJointsViewOptions): void;\n /** Return the serializable assembly definition used by solve/inspect pipelines. */\n describe(): AssemblyDefinition;\n}\n/**\n * Create an assembly container with named parts, connectors, and kinematic links.\n *\n * **Use this from iteration 1 for any model with moving parts.** Do not build one\n * static pose and retrofit motion later.\n *\n * **Details**\n *\n * Two motion tools:\n *\n * - **Link-graph kinematics** (`link()`, `edgeBetweenLinks()`,\n * `addAngleBetweenLinks()`) solve named point positions — a link is a point,\n * not a rigid-body frame. Use when the hard part is solving positions,\n * especially closed loops.\n * - **Connector-frame joints** (`connect()` / `match()`) align full connector\n * frames (`origin`, `axis`, `up`) and derive joint frame + axis. Use for\n * serial articulated parts whose orientation matters: hips, hinges, drums,\n * sliders, wheels.\n *\n * `addPart(..., { mate })` places geometry on the solved link graph by\n * **translation only**: one mate pins a connector origin to a link, two mates\n * orient a part to span two solved links, a third pins roll. Right for markers\n * and point-following geometry; use `connect()`/`match()` when the part needs\n * a deterministic rest orientation.\n *\n * Return the `Assembly` itself to expose its joints and driven link controls in\n * the editor; moving a control re-runs `solve(state)`, so closed loops move\n * through the real solver instead of a viewport-only FK approximation.\n *\n * If no link in a connected kinematic component is fixed, ForgeCAD chooses a\n * deterministic gauge link for solving and reports a floating-component warning.\n *\n * A file that returns an `Assembly` is importable via `require()` and yields an\n * `ImportedAssembly`; use `mergeInto()` to flatten it into a parent assembly.\n *\n * **Point-link example** (mates a marker to the solved `tip` point; does not\n * orient a bar along `ground -> tip`):\n *\n * ```ts\n * const marker = box(8, 8, 4).withConnectors({\n * center: connector({ origin: [0, 0, 0], axis: [0, 0, 1] }),\n * });\n *\n * const mech = assembly("Linkage")\n * .link("ground", { at: [0, 0, 0], fixed: true })\n * .link("worldX", { at: [10, 0, 0], fixed: true })\n * .link("tip", { at: [40, 0, 0] })\n * .edgeBetweenLinks("ground", "tip", { name: "bar" })\n * .addAngleBetweenLinks("worldX", "ground", "tip", {\n * name: "theta",\n * control: { min: 0, max: 120, default: 30 },\n * })\n * .addPart("Tip marker", marker, { mate: { connector: "center", toLink: "tip" } });\n *\n * return mech;\n * ```\n *\n * @param name - Display name for the assembly (used in BOM, robot export, etc.)\n * @returns A new `Assembly` builder\n * @category Assembly\n */\ndeclare function assembly(name?: string): Assembly;\ninterface MergeIntoOptions {\n /**\n * Prefix applied to every part name and joint name from the sub-assembly.\n * E.g. prefix "Left Arm" turns part "Base" into "Left Arm.Base".\n * Strongly recommended to avoid name collisions when merging multiple instances.\n */\n prefix?: string;\n /** Part name in the parent assembly to attach the sub-assembly root to. */\n mountParent: string;\n /** Name for the new mount joint in the parent graph. */\n mountJoint: string;\n /** Joint type for the mount connection (default: \'fixed\'). */\n mountType?: JointType;\n /** Frame, axis, limits, and other options for the mount joint. */\n mountOptions?: JointOptions;\n}\n/**\n * A wrapper around an imported `Assembly` that provides kinematic access and\n * convenient transform helpers.\n *\n * **Details**\n *\n * When a `.forge.js` file returns an unsolved `Assembly`, `require()` wraps it\n * in an `ImportedAssembly`. This preserves the kinematic structure — you can\n * call `solve()` and `mergeInto()` — and converts to a static `ShapeGroup` via\n * the explicit `toGroup(state?)` boundary when group-style transforms are needed.\n *\n * **Kinematic access**\n *\n * ```ts\n * const arm = require("./arm.forge.js");\n *\n * const solved = arm.solve({ shoulder: 45 }); // full kinematic solve\n * const link = arm.getPart("Link", { shoulder: 60 }); // single part at state\n * const group = arm.toGroup({ shoulder: 45 }); // only when ShapeGroup behavior is needed\n * ```\n *\n * **Static positioning** — convert explicitly, then transform the group\n * (`toGroup()` solves at default joint values and discards kinematics):\n *\n * ```ts\n * const positioned = arm.toGroup().rotateZ(-90).translate(0, -20, 50);\n * ```\n *\n * **Merging into a parent**\n *\n * ```ts\n * require("./arm.forge.js").mergeInto(robot, {\n * prefix: "Left Arm",\n * mountParent: "Chassis",\n * mountJoint: "leftMount",\n * mountOptions: { frame: Transform.identity().translate(-70, 0, 10) },\n * });\n * ```\n *\n * @category Assembly\n */\ndeclare class ImportedAssembly {\n private readonly _assembly;\n private readonly _refs;\n private readonly _offset;\n constructor(_assembly: Assembly, _refs?: PlacementReferences, _offset?: readonly [\n number,\n number,\n number\n ]);\n /** The underlying Assembly, for advanced composition and inspection. */\n get assembly(): Assembly;\n /** Solve the assembly at the given joint state (defaults to each joint\'s default value). */\n solve(state?: JointState): SolvedAssembly;\n /**\n * Return a specific named part positioned at the given joint state, with any\n * stored placement offset applied.\n *\n * @softDeprecated getPart(name, state?) — same offset-aware accessor; note that solve(state).getPart(name) does NOT apply the placeReference() offset stored on the imported assembly\n * @deprecated use getPart(name, state?) — same offset-aware accessor; note that solve(state).getPart(name) does NOT apply the placeReference() offset stored on the imported assembly\n */\n part(name: string, state?: JointState): AssemblyPart;\n /**\n * Return a specific named part positioned at the solved pose, with any stored\n * placement offset applied.\n *\n * **Details**\n *\n * This mirrors `SolvedAssembly.getPart()` for imported assemblies, with one\n * addition: any offset stored by `placeReference()` is applied, so the part\n * lands where the imported assembly was placed. (`solve(state).getPart(name)`\n * returns the part in the assembly\'s own coordinates, without that offset.)\n *\n * @param partName - Name as registered with `addPart()`\n * @param state - Optional joint state; defaults to each joint\'s default value\n * @returns The part (`Shape` or `ShapeGroup`) at its solved, placed position\n * @category Assembly\n */\n getPart(partName: string, state?: JointState): AssemblyPart;\n /**\n * Convert all assembly parts to a ShapeGroup with named children.\n * Use this for composition, transforms, or child lookup — not as a required\n * render step for assemblies.\n * Child names match the part names used in the assembly.\n * Any stored placement offset and placement references are forwarded to the group.\n */\n toGroup(state?: JointState): ShapeGroup;\n /**\n * Attach named placement reference points to this assembly.\n * Points are simple 3D coordinates (relative to the assembly\'s own origin).\n * Returns a new ImportedAssembly — does not mutate.\n */\n withReferences(refs: Pick<PlacementReferenceInput, "points">): ImportedAssembly;\n /** List all attached placement reference names. */\n referenceNames(kind?: PlacementReferenceKind): string[];\n /**\n * Translate the assembly so the named reference point lands on `target`.\n * Returns a new ImportedAssembly — does not mutate.\n * All point refs are translated by the same delta.\n */\n placeReference(ref: string, target: [\n number,\n number,\n number\n ], offset?: [\n number,\n number,\n number\n ]): ImportedAssembly;\n /**\n * Solve at defaults and return a translated ShapeGroup.\n *\n * @softDeprecated toGroup().translate(x, y, z) — the explicit toGroup(state?) call shows where kinematics become a static group; or placeReference() to position by a named point\n * @deprecated use toGroup().translate(x, y, z) — the explicit toGroup(state?) call shows where kinematics become a static group; or placeReference() to position by a named point\n */\n translate(x: number, y: number, z: number): ShapeGroup;\n /**\n * Solve at defaults and return a rotated ShapeGroup.\n *\n * @softDeprecated toGroup().rotate(axis, angleDeg, options) — the explicit toGroup(state?) call shows where kinematics become a static group\n * @deprecated use toGroup().rotate(axis, angleDeg, options) — the explicit toGroup(state?) call shows where kinematics become a static group\n */\n rotate(axis: [\n number,\n number,\n number\n ], angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): ShapeGroup;\n /**\n * Solve at defaults and return a ShapeGroup rotated around X.\n *\n * @softDeprecated toGroup().rotateX(angleDeg, options) — the explicit toGroup(state?) call shows where kinematics become a static group\n * @deprecated use toGroup().rotateX(angleDeg, options) — the explicit toGroup(state?) call shows where kinematics become a static group\n */\n rotateX(angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): ShapeGroup;\n /**\n * Solve at defaults and return a ShapeGroup rotated around Y.\n *\n * @softDeprecated toGroup().rotateY(angleDeg, options) — the explicit toGroup(state?) call shows where kinematics become a static group\n * @deprecated use toGroup().rotateY(angleDeg, options) — the explicit toGroup(state?) call shows where kinematics become a static group\n */\n rotateY(angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): ShapeGroup;\n /**\n * Solve at defaults and return a ShapeGroup rotated around Z.\n *\n * @softDeprecated toGroup().rotateZ(angleDeg, options) — the explicit toGroup(state?) call shows where kinematics become a static group\n * @deprecated use toGroup().rotateZ(angleDeg, options) — the explicit toGroup(state?) call shows where kinematics become a static group\n */\n rotateZ(angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): ShapeGroup;\n /**\n * Solve at defaults and return a scaled ShapeGroup.\n *\n * @softDeprecated toGroup().scale(v) — the explicit toGroup(state?) call shows where kinematics become a static group\n * @deprecated use toGroup().scale(v) — the explicit toGroup(state?) call shows where kinematics become a static group\n */\n scale(v: number | [\n number,\n number,\n number\n ]): ShapeGroup;\n /**\n * Solve at defaults and return a mirrored ShapeGroup.\n *\n * @softDeprecated toGroup().mirror(normal) — the explicit toGroup(state?) call shows where kinematics become a static group\n * @deprecated use toGroup().mirror(normal) — the explicit toGroup(state?) call shows where kinematics become a static group\n */\n mirror(normal: [\n number,\n number,\n number\n ]): ShapeGroup;\n /**\n * Solve at defaults and return a colored ShapeGroup.\n *\n * @softDeprecated toGroup().color(hex) — the explicit toGroup(state?) call shows where kinematics become a static group\n * @deprecated use toGroup().color(hex) — the explicit toGroup(state?) call shows where kinematics become a static group\n */\n color(hex: string): ShapeGroup;\n /** Solve at defaults, get a named child part from the resulting group. */\n child(name: string): Shape | Sketch | ShapeGroup;\n /**\n * Detect overlapping part pairs at the default solved pose.\n *\n * **Details**\n *\n * This mirrors `SolvedAssembly.collisionReport()` for imported assemblies.\n * Use `solve(state).collisionReport(options)` when inspecting a non-default\n * joint state.\n *\n * @param options - Filter by `parts`, ignore specific `ignorePairs`, or set `minOverlapVolume`\n * @returns Array of `{ partA, partB, overlapVolume }` for each interference found\n * @category Assembly\n */\n collisionReport(options?: CollisionOptions): CollisionFinding[];\n /**\n * Compute the minimum gap between two parts at the default solved pose.\n *\n * **Details**\n *\n * This mirrors `SolvedAssembly.minClearance()` for imported assemblies. Use\n * `solve(state).minClearance(partA, partB, searchLength)` when inspecting a\n * non-default joint state.\n *\n * @param partA - Name of the first part\n * @param partB - Name of the second part\n * @param searchLength - Maximum search radius in mm (default 10)\n * @returns Minimum gap in mm; `0` means touching or interfering\n * @category Assembly\n */\n minClearance(partA: string, partB: string, searchLength?: number): number;\n /**\n * Flatten this sub-assembly\'s parts and relationships into `parent` and wire a mount relationship.\n *\n * **Details**\n *\n * All part, link, and legacy joint names from the sub-assembly are prefixed\n * with `"${options.prefix}."` to avoid collisions; connectors are forwarded\n * with the same prefix. After the merge, drive controls from the parent using\n * the prefixed names:\n *\n * ```ts\n * parent.solve({ "Left Arm.theta": 45, "Right Arm.theta": -20 })\n * ```\n *\n * The sub-assembly must have exactly one root part before it can be merged\n * (collapse multiple roots with `addFixed()` first). See the\n * {@link ImportedAssembly} class docs for a full merge example.\n *\n * @param parent - The target assembly to merge into\n * @param options - `prefix`, `mountParent`, `mountJoint`, `mountType`, `mountOptions`\n * @returns `parent` for chaining\n * @category Assembly\n */\n mergeInto(parent: Assembly, options: MergeIntoOptions): Assembly;\n}\ninterface RevoluteJointOpts {\n axis?: [\n number,\n number,\n number\n ];\n min?: number;\n max?: number;\n default?: number;\n unit?: string;\n reverse?: boolean;\n}\n/**\n * @deprecated `joint()` has been removed from the modeling API. Use\n * `assembly().link(...).edgeBetweenLinks(...).addAngleBetweenLinks(...)`\n * with controls for mechanisms.\n * @internal\n */\ndeclare function joint(_name: string, _shape: Shape, _pivot: [\n number,\n number,\n number\n], _opts?: RevoluteJointOpts): Shape;\ndeclare const DEFERRED_EDGE_SELECTION_MARKER: unique symbol;\ntype DeferredEdgeSelectionMode = "all" | "first";\ninterface DeferredEdgeSelection {\n readonly [DEFERRED_EDGE_SELECTION_MARKER]: true;\n readonly query: EdgeQuery;\n readonly mode: DeferredEdgeSelectionMode;\n}\n/**\n * Select all edges from a shape that match the given query.\n *\n * **Details**\n *\n * Uses the active kernel\'s native topology query when available (Truck),\n * otherwise extracts sharp edges from the mesh (dihedral angle > 1°), applies\n * all filters in the query, and returns the matching `EdgeSegment[]`. When\n * `near` is specified the results are sorted closest-first.\n *\n * Works on any shape — primitives, booleans, shells, and imported meshes.\n * Use this when tracked topology is unavailable (e.g. after a difference or\n * on imported geometry). For simpler cases, pass an `EdgeQuery` directly to\n * `fillet()` or `chamfer()` instead of calling `selectEdges` separately.\n *\n * **Example**\n *\n * ```ts\n * // Fillet all top edges of a box\n * const topEdges = selectEdges(part, { atZ: 20, perpendicular: [0, 0, 1] });\n * let result = part;\n * for (const edge of coalesceEdges(topEdges)) {\n * result = fillet(result, 2, edge);\n * }\n * ```\n *\n * @param shape - The solid to extract edges from\n * @param query - Optional filter/sort criteria\n * @returns Array of matching edge segments (may be empty)\n * @see {@link selectEdge} for the single-match variant\n * @see {@link coalesceEdges} to merge split tessellation segments\n * @category Edge Queries\n */\ndeclare function selectEdges(shape: Shape, query?: EdgeQuery): EdgeSegment[];\n/**\n * Select the single best-matching edge from a shape.\n *\n * **Details**\n *\n * When `near` is specified, returns the edge whose midpoint is closest to\n * that point. Otherwise returns the first matching edge in mesh order.\n * Throws if no edges match the query — useful as a guard when you expect\n * exactly one result.\n *\n * **Example**\n *\n * ```ts\n * // Chamfer one specific edge near a known point\n * const bottomEdge = selectEdge(part, { near: [25, 0, 0], atZ: 0 });\n * result = chamfer(result, 1.5, bottomEdge);\n * ```\n *\n * @param shape - The solid to extract edges from\n * @param query - Filter/sort criteria; use `near` to pick the closest match\n * @returns The single best-matching edge segment\n * @throws If no edges match the query\n * @see {@link selectEdges} for multi-match variant\n * @category Edge Queries\n */\ndeclare function selectEdge(shape: Shape, query?: EdgeQuery): EdgeSegment;\n/**\n * Merge collinear edge segments into longer logical edges.\n *\n * **Details**\n *\n * Tessellation often splits one geometric edge into multiple short segments.\n * `coalesceEdges` groups adjacent collinear segments and merges each group\n * into a single `EdgeSegment` spanning the full extent. This is usually\n * needed before passing edges to `fillet()` or `chamfer()` on non-primitive\n * shapes.\n *\n * The `tolerance` controls the maximum perpendicular distance from\n * collinearity before two segments are considered non-collinear. Default: `0.01`.\n *\n * **Example**\n *\n * ```ts\n * const topEdges = selectEdges(part, { atZ: 20 });\n * for (const edge of coalesceEdges(topEdges)) {\n * result = fillet(result, 2, edge);\n * }\n * ```\n *\n * @param segments - Edge segments from `selectEdges()`\n * @param tolerance - Max perpendicular deviation for collinearity (default: `0.01`)\n * @returns Merged edge segments\n * @see {@link selectEdges} to obtain the input segments\n * @category Edge Queries\n */\ndeclare function coalesceEdges(segments: EdgeSegment[], tolerance?: number): EdgeSegment[];\ntype EdgeReferenceLike = {\n edges(): EdgeSegment[];\n};\ntype EdgeSelector = EdgeSegment | EdgeSegment[] | EdgeQuery | EdgeRef | EdgeReferenceLike | DeferredEdgeSelection;\n/**\n * Apply experimental fillets (rounded edges) to one or more edges of a shape.\n *\n * **Details**\n *\n * **Experimental**: edge finishes (fillet and chamfer) are backend-sensitive.\n * The Manifold backend is known to produce incorrect results for some\n * edge-finish cases, and the OCCT backend can be very slow, especially with\n * broad edge selections. Prefer profile-level rounding where the design allows\n * (`sketch.filletCorners(radius)` before extruding — exact and fast); otherwise\n * use targeted edge selectors and inspect the result before treating it as\n * production-ready geometry.\n *\n * Edge selections compile into backend operations; unsupported selections fail\n * as explicit kernel gaps instead of using TypeScript geometry fallbacks.\n *\n * The `edges` parameter is flexible:\n * - Omit to fillet **all** sharp edges\n * - Pass an `EdgeQuery` for an inline filter (most common)\n * - Pass an `EdgeSegment` or `EdgeSegment[]` from `selectEdges()` for\n * pre-selected edges\n * - Pass a tracked `EdgeRef` from `shape.edge(\'vert-br\')` (vertical edges of\n * `box()` / `Rectangle2D` extrusions) — this takes the **exact**\n * compiler-owned path, not the mesh-approximate one\n *\n * Throws if no edges match the selection, or if `radius` is not a positive\n * finite number.\n *\n * Selectorless (all-edges) calls draw from a per-run broad edge-feature\n * budget. Exceeding it throws — except in live preview, which skips the\n * finish with a warning for responsiveness. Explicit edge selectors are never\n * budgeted; `FORGECAD_BROAD_EDGE_FEATURE_BUDGET` / `FORGECAD_ALLOW_BROAD_EDGE_FEATURES=1`\n * raise or lift the budget.\n *\n * **Example**\n *\n * ```ts\n * // Fillet all edges\n * fillet(myShape, 2)\n *\n * // Fillet only top convex edges\n * fillet(myShape, 1.5, { atZ: 20, convex: true })\n *\n * // Fillet vertical edges selected beforehand\n * const edges = selectEdges(myShape, { parallel: [0, 0, 1] })\n * fillet(myShape, 3, edges)\n *\n * // Exact compiler-owned fillet on a tracked box edge\n * const base = box(50, 50, 20)\n * fillet(base, 5, base.edge(\'vert-br\'))\n * ```\n *\n * @param shape - The solid to modify\n * @param radius - Fillet radius (must be positive and finite)\n * @param edges - Which edges to fillet: `EdgeSegment`, `EdgeSegment[]`, `EdgeQuery`, tracked `EdgeRef`, or `undefined` (all)\n * @param segments - Arc resolution hint for kernels that tessellate generated blends (default: `16`)\n * @returns A new Shape with the fillets applied\n * @see {@link chamfer} for beveled edges\n * @see {@link selectEdges} to pre-select edges\n * @experimental\n * @category Edge Features\n */\ndeclare function fillet(shape: Shape, radius: number, edges?: EdgeSelector, segments?: number): Shape;\n/**\n * Apply experimental chamfers (beveled edges) to one or more edges of a shape.\n *\n * **Details**\n *\n * **Experimental**: same backend caveats as {@link fillet} — Manifold may be\n * incorrect for some edge-finish cases, OCCT can be very slow on broad\n * selections; prefer profile-level rounding or targeted selectors and inspect\n * the result.\n *\n * Produces a 45° bevel at the specified `size` (distance from edge). Edge\n * selections compile into backend operations; unsupported selections fail as\n * explicit kernel gaps instead of using TypeScript geometry fallbacks.\n *\n * Selectorless (all-edges) calls draw from a per-run broad edge-feature\n * budget. Exceeding it throws — except in live preview, which skips the\n * finish with a warning for responsiveness. Explicit edge selectors are never\n * budgeted; `FORGECAD_BROAD_EDGE_FEATURE_BUDGET` / `FORGECAD_ALLOW_BROAD_EDGE_FEATURES=1`\n * raise or lift the budget.\n *\n * The `edges` parameter accepts the same options as `fillet()`: inline\n * `EdgeQuery`, pre-selected `EdgeSegment`/`EdgeSegment[]`, a tracked `EdgeRef`\n * from `shape.edge(\'vert-br\')` (exact compiler-owned path), or `undefined`\n * (all sharp edges).\n *\n * **Example**\n *\n * ```ts\n * // Chamfer all edges\n * chamfer(myShape, 1)\n *\n * // Chamfer only vertical edges\n * chamfer(myShape, 2, { parallel: [0, 0, 1] })\n *\n * // Exact compiler-owned chamfer on a tracked box edge\n * const base = box(50, 50, 20)\n * chamfer(base, 3, base.edge(\'vert-br\'))\n * ```\n *\n * @param shape - The solid to modify\n * @param size - Chamfer size — distance from the edge (must be positive and finite)\n * @param edges - Which edges to chamfer: `EdgeSegment`, `EdgeSegment[]`, `EdgeQuery`, tracked `EdgeRef`, or `undefined` (all)\n * @returns A new Shape with the chamfers applied\n * @see {@link fillet} for rounded edges\n * @experimental\n * @category Edge Features\n */\ndeclare function chamfer(shape: Shape, size: number, edges?: EdgeSelector): Shape;\n/**\n * Apply a draft angle (taper) to vertical faces for mold extraction.\n *\n * **Details**\n *\n * Adds a taper angle to the vertical faces of a solid so that it can be\n * extracted from a mold. The neutral plane is the Z position where the draft\n * angle is zero — faces above and below are tapered symmetrically. Typical\n * values for injection molding are 1–5°.\n *\n * SDF, Manifold, and Truck lower supported vertical-prism solids with Z-axis\n * pull directions to a tapered loft. OCCT uses its native draft operation when\n * available.\n *\n * **Example**\n *\n * ```ts\n * // Add 3° draft to a box for injection molding\n * draft(myBox, 3)\n *\n * // Draft with custom pull direction and neutral plane\n * draft(myShape, 2, [0, 0, 1], 10)\n * ```\n *\n * @param shape - The solid to modify\n * @param angleDeg - Draft angle in degrees (typically 1–5 for injection molding)\n * @param pullDirection - Mold pull direction (default: `[0, 0, 1]` = Z-up)\n * @param neutralPlaneOffset - Z-offset of the neutral plane (default: `0`)\n * @returns A new Shape with draft applied\n * @category Edge Features\n */\ndeclare function draft(shape: Shape, angleDeg: number, pullDirection?: [\n number,\n number,\n number\n], neutralPlaneOffset?: number): Shape;\n/**\n * Uniformly offset all surfaces of a solid inward or outward.\n *\n * **Details**\n *\n * Unlike `shell()`, which hollows a solid by removing one face, `offsetSolid()`\n * produces a new solid whose every surface is shifted by `thickness`. Positive\n * values grow the shape outward; negative values shrink it inward.\n *\n * Requires the OCCT backend. Throws on Manifold.\n *\n * **Example**\n *\n * ```ts\n * // Grow a box outward by 1mm on all sides\n * offsetSolid(myBox, 1)\n *\n * // Shrink a shape inward by 0.5mm\n * offsetSolid(myShape, -0.5)\n * ```\n *\n * @param shape - The solid to offset\n * @param thickness - Offset distance (positive = outward, negative = inward; must be non-zero and finite)\n * @returns A new Shape with offset surfaces\n * @category Edge Features\n */\ndeclare function offsetSolid(shape: Shape, thickness: number): Shape;\ntype MetricSize = "M2" | "M2.5" | "M3" | "M4" | "M5" | "M6" | "M8" | "M10";\ntype FastenerFit = "close" | "normal" | "loose" | "tap";\ninterface FastenerHoleOptions {\n /** Thread standard — only `\'iso-metric\'` is supported. */\n standard?: "iso-metric";\n /** ISO metric thread size, e.g. `\'M5\'`. Supported: M2–M10. */\n size: MetricSize;\n /**\n * Clearance fit class (default: `\'normal\'`).\n *\n * | Fit | Hole dia | Use when |\n * |-----|----------|----------|\n * | `\'close\'` | nominal + ~0.2 mm | Press-location or close-tolerance |\n * | `\'normal\'` | nominal + ~0.5 mm | Standard through-bolt clearance |\n * | `\'loose\'` | nominal + 1–2 mm | Adjustment slots or misaligned patterns |\n * | `\'tap\'` | ISO tap drill | Tapped hole in mating part |\n */\n fit?: FastenerFit;\n /** Hole depth in mm. */\n depth: number;\n /**\n * Optional counterbore at the top of the hole (wider recess for a socket-head cap).\n * `diameter` defaults to the ISO head-clearance diameter for the chosen size.\n */\n counterbore?: {\n depth: number;\n diameter?: number;\n };\n /**\n * Optional countersink at the top of the hole (conical recess for a flat-head screw).\n * `angleDeg` defaults to `90`.\n */\n countersink?: {\n diameter: number;\n angleDeg?: number;\n };\n /**\n * When `true` (default), the hole is centered on Z=0 (extends from −depth/2 to +depth/2).\n * When `false`, the top face is at Z=0 and the hole extends downward.\n */\n center?: boolean;\n /** Polygon segment count for the cylinder approximation (default: 48). */\n segments?: number;\n}\ninterface TSlotProfileOptions {\n /** Outer profile size (square). */\n size?: number;\n /** Slot mouth width (the narrow opening at each side). */\n slotWidth?: number;\n /** Wider interior slot cavity width. Must be >= slotWidth. */\n slotInnerWidth?: number;\n /** Total slot depth from outer face inward. */\n slotDepth?: number;\n /** Depth of the narrow mouth before it widens into slotInnerWidth. */\n slotNeckDepth?: number;\n /** Outer shell wall thickness. */\n wall?: number;\n /** Central cross-web thickness. */\n web?: number;\n /** Center boss diameter (solid material around center bore). */\n centerBossDia?: number;\n /** Center bore diameter (for tapping/through-hole). Set 0 to disable. */\n centerBoreDia?: number;\n /** Outer corner radius. */\n outerCornerRadius?: number;\n /** Segment count used for circular features in 2D. */\n segments?: number;\n}\ninterface TSlotExtrusionOptions extends TSlotProfileOptions {\n}\ninterface Profile2020BSlot6ProfileOptions {\n /** Slot mouth width. */\n slotWidth?: number;\n /** Wider inner slot width. */\n slotInnerWidth?: number;\n /** Slot depth from outer face. */\n slotDepth?: number;\n /** Depth of the narrow neck before widening into slotInnerWidth. */\n slotNeckDepth?: number;\n /** Center core bore diameter (set 0 to disable). */\n centerBoreDia?: number;\n /** Solid boss diameter around center bore (must exceed centerBoreDia when bore is enabled). */\n centerBossDia?: number;\n /** Width of diagonal ribs connecting center boss to corner regions. */\n diagonalWebWidth?: number;\n /** Outside corner radius. */\n outerCornerRadius?: number;\n /** Circle segment count. */\n segments?: number;\n}\ninterface Profile2020BSlot6Options extends Profile2020BSlot6ProfileOptions {\n}\ninterface ExplodeNamedItem {\n name: string;\n shape?: Shape | ShapeGroup;\n sketch?: Sketch;\n color?: string;\n group?: ExplodeItem[];\n explode?: ExplodeDirective;\n}\ntype ExplodeItem = Shape | Sketch | ShapeGroup | ExplodeNamedItem;\ninterface ExplodeOptions extends ExplodeConfigOptions {\n}\ntype BeltVec2 = [\n number,\n number\n];\ntype BeltMode = "open" | "crossed";\ninterface TangentCircle2D {\n name?: string;\n center: BeltVec2;\n radius: number;\n}\ninterface BeltPulley2D {\n name?: string;\n center: BeltVec2;\n pitchRadius: number;\n}\ninterface TangentLoop2DOptions {\n /** `open` uses external tangents; `crossed` uses internal tangents. */\n mode?: BeltMode;\n}\ninterface BeltDriveOptions {\n pulleys: BeltPulley2D[] | Record<string, Omit<BeltPulley2D, "name"> & {\n name?: string;\n }>;\n /** Belt width along +Z. */\n beltWidth: number;\n /** Belt thickness in the pulley plane. Default 2mm. */\n beltThickness?: number;\n /**\n * Reserved for multi-pulley route intent. The first implementation supports\n * two-pulley routes and rejects multi-pulley calls with explicit guidance.\n */\n route?: "outer" | BeltRouteContact[];\n /** Visual stroke width for the returned pitch path sketch. Default 0.25mm. */\n pitchPathWidth?: number;\n}\ninterface BeltRouteContact {\n pulley: string;\n wrap?: "cw" | "ccw" | "short" | "long";\n tangentIn?: "left" | "right" | "internal" | "external";\n tangentOut?: "left" | "right" | "internal" | "external";\n}\ninterface BeltLineSpan {\n kind: "line";\n fromPulley: string;\n toPulley: string;\n from: BeltVec2;\n to: BeltVec2;\n length: number;\n}\ninterface BeltWrapArc {\n kind: "arc";\n pulley: string;\n center: BeltVec2;\n pitchRadius: number;\n from: BeltVec2;\n to: BeltVec2;\n sweepDeg: number;\n wrapDeg: number;\n length: number;\n tangentIn: BeltVec2;\n tangentOut: BeltVec2;\n}\ntype BeltPathSegment = BeltLineSpan | BeltWrapArc;\ninterface BeltDriveResult {\n belt: Shape;\n beltProfile: Sketch;\n pitchPath: Sketch;\n route: TangentLoop2D;\n length: number;\n wraps: BeltWrapArc[];\n wrapByPulley: Record<string, BeltWrapArc>;\n straightSpans: BeltLineSpan[];\n skippedPulleys: string[];\n}\ndeclare class TangentLoop2D {\n readonly circles: TangentCircle2D[];\n readonly mode: BeltMode;\n readonly segments: BeltPathSegment[];\n readonly straightSpans: BeltLineSpan[];\n readonly wraps: BeltWrapArc[];\n readonly wrapByPulley: Record<string, BeltWrapArc>;\n readonly length: number;\n constructor(circles: TangentCircle2D[], options?: TangentLoop2DOptions);\n /** Convert the loop centerline into a thin visual sketch. */\n toSketch(width?: number): Sketch;\n /** Convert the loop into a filled profile using the pitch path itself as the boundary. */\n toProfile(): Sketch;\n /** Build a belt band sketch by offsetting the route to inner and outer pulley radii. */\n offsetBand(thickness: number): Sketch;\n}\ninterface SpurGearOptions {\n module: number;\n teeth: number;\n pressureAngleDeg?: number;\n faceWidth: number;\n backlash?: number;\n clearance?: number;\n addendum?: number;\n dedendum?: number;\n boreDiameter?: number;\n segmentsPerTooth?: number;\n}\ninterface SectorGearOptions extends Omit<SpurGearOptions, "teeth"> {\n teethOnFullCircle: number;\n toothCount: number;\n firstTooth?: number;\n body?: Shape;\n}\ninterface GearBodyDiskOptions {\n outerRadius: number;\n faceWidth: number;\n boreDiameter?: number;\n segments?: number;\n}\ninterface GearBodyDiskWithHubOptions extends GearBodyDiskOptions {\n hubDiameter: number;\n hubFaceWidth?: number;\n}\ninterface GearBodySpokedOptions extends GearBodyDiskOptions {\n rimWidth: number;\n hubDiameter: number;\n spokeCount: number;\n spokeWidth: number;\n}\ninterface GearBodyFromProfileOptions {\n faceWidth: number;\n boreDiameter?: number;\n}\ndeclare function gearBodyDisk(options: GearBodyDiskOptions): Shape;\ndeclare function gearBodyDiskWithHub(options: GearBodyDiskWithHubOptions): Shape;\ndeclare function gearBodySpoked(options: GearBodySpokedOptions): Shape;\ndeclare function gearBodyFromProfile(profile: Sketch, options: GearBodyFromProfileOptions): Shape;\ntype DriveWheelRegionKind = "body" | "spurTeeth" | "solidArc" | "custom";\ninterface DriveWheelRegionMeta {\n name: string;\n kind: DriveWheelRegionKind;\n fromAngleDeg?: number;\n toAngleDeg?: number;\n innerRadius?: number;\n outerRadius?: number;\n module?: number;\n teethOnFullCircle?: number;\n toothCount?: number;\n pitchRadius?: number;\n rootRadius?: number;\n faceWidth?: number;\n}\ninterface DriveWheelMeta {\n kind: "driveWheel";\n faceWidth: number;\n boreDiameter: number;\n regions: DriveWheelRegionMeta[];\n}\ninterface DriveWheelOptions {\n body?: Shape;\n faceWidth?: number;\n boreDiameter?: number;\n}\ninterface DriveWheelSpurTeethRegionOptions extends Omit<SpurGearOptions, "teeth" | "faceWidth" | "boreDiameter"> {\n name?: string;\n teethOnFullCircle: number;\n toothCount: number;\n firstTooth?: number;\n faceWidth?: number;\n}\ninterface DriveWheelSolidArcRegionOptions {\n name?: string;\n fromAngleDeg: number;\n toAngleDeg: number;\n innerRadius?: number;\n outerRadius: number;\n faceWidth?: number;\n segments?: number;\n}\ninterface DriveWheelShapeRegionOptions {\n fromAngleDeg?: number;\n toAngleDeg?: number;\n innerRadius?: number;\n outerRadius?: number;\n}\ndeclare class DriveWheelBuilder {\n private readonly body?;\n private readonly faceWidth?;\n private readonly boreDiameter;\n private readonly regions;\n constructor(options?: DriveWheelOptions);\n /**\n * Add an involute spur-tooth window on part of the pitch circle.\n */\n addSpurTeethBetween(options: DriveWheelSpurTeethRegionOptions): this;\n /**\n * Add a constant-radius solid arc region such as a dwell, stop, or pusher.\n */\n addSolidArcBetween(options: DriveWheelSolidArcRegionOptions): this;\n /**\n * Add a fully custom region shape while preserving region metadata.\n */\n addShapeRegion(name: string, shape: Shape, options?: DriveWheelShapeRegionOptions): this;\n /**\n * Build the final wheel shape with a bore connector and region metadata.\n */\n build(): Shape;\n private measurements;\n private regionMetadata;\n private resolveFaceWidth;\n private resolveBuildFaceWidth;\n private defaultBodyRadius;\n private resolveName;\n}\ninterface FaceGearOptions extends SpurGearOptions {\n /** Which face carries the teeth (default: `\'top\'`). */\n side?: "top" | "bottom";\n /** Axial tooth height in mm (default: module). */\n toothHeight?: number;\n}\ninterface SideGearOptions extends FaceGearOptions {\n}\ninterface RingGearOptions {\n module: number;\n teeth: number;\n pressureAngleDeg?: number;\n faceWidth: number;\n backlash?: number;\n clearance?: number;\n addendum?: number;\n dedendum?: number;\n rimWidth?: number;\n outerDiameter?: number;\n segmentsPerTooth?: number;\n}\ninterface RackGearOptions {\n module: number;\n teeth: number;\n pressureAngleDeg?: number;\n faceWidth: number;\n backlash?: number;\n clearance?: number;\n addendum?: number;\n dedendum?: number;\n baseHeight?: number;\n}\ninterface BevelGearOptions {\n module: number;\n teeth: number;\n pressureAngleDeg?: number;\n faceWidth: number;\n backlash?: number;\n clearance?: number;\n addendum?: number;\n dedendum?: number;\n boreDiameter?: number;\n pitchAngleDeg?: number;\n mateTeeth?: number;\n shaftAngleDeg?: number;\n segmentsPerTooth?: number;\n}\ninterface GearPairSpec {\n module: number;\n teeth: number;\n pressureAngleDeg?: number;\n faceWidth?: number;\n backlash?: number;\n clearance?: number;\n addendum?: number;\n dedendum?: number;\n boreDiameter?: number;\n segmentsPerTooth?: number;\n}\ninterface GearPairOptions {\n pinion: Shape | GearPairSpec;\n gear: Shape | GearPairSpec;\n backlash?: number;\n centerDistance?: number;\n place?: boolean;\n phaseDeg?: number;\n}\ninterface GearPairDiagnostic {\n level: "info" | "warn" | "error";\n code: string;\n message: string;\n}\ninterface GearPairResult {\n pinion: Shape;\n gear: Shape;\n centerDistance: number;\n centerDistanceNominal: number;\n backlash: number;\n pressureAngleDeg: number;\n workingPressureAngleDeg: number;\n contactRatio: number;\n jointRatio: number;\n speedReduction: number;\n /** Phase rotation (degrees) for the gear around its shaft axis for correct tooth\n * mesh alignment. When `place: true` this is already baked into `gear`.\n * When `place: false`, rotate the gear by this amount before positioning. */\n phaseDeg: number;\n diagnostics: GearPairDiagnostic[];\n status: "ok" | "warn" | "error";\n}\ninterface GearMeshPlacement {\n pinionAxis: [\n number,\n number,\n number\n ];\n gearAxis: [\n number,\n number,\n number\n ];\n pinionCenter: [\n number,\n number,\n number\n ];\n gearCenter: [\n number,\n number,\n number\n ];\n}\ninterface BevelGearPairSpec extends GearPairSpec {\n}\ninterface BevelGearPairOptions {\n pinion: Shape | BevelGearPairSpec;\n gear: Shape | BevelGearPairSpec;\n shaftAngleDeg?: number;\n backlash?: number;\n place?: boolean;\n phaseDeg?: number;\n}\ninterface FaceGearSpec extends GearPairSpec {\n side?: "top" | "bottom";\n toothHeight?: number;\n}\ninterface SideGearSpec extends FaceGearSpec {\n}\ninterface SideGearPairOptions {\n side: Shape | SideGearSpec;\n vertical: Shape | GearPairSpec;\n backlash?: number;\n centerDistance?: number;\n meshPlaneZ?: number;\n place?: boolean;\n phaseDeg?: number;\n}\ninterface BevelGearPairResult extends GearMeshPlacement {\n pinion: Shape;\n gear: Shape;\n shaftAngleDeg: number;\n pinionPitchAngleDeg: number;\n gearPitchAngleDeg: number;\n coneDistance: number;\n backlash: number;\n jointRatio: number;\n speedReduction: number;\n /** Phase rotation (degrees) for gear tooth mesh alignment. See GearPairResult.phaseDeg. */\n phaseDeg: number;\n diagnostics: GearPairDiagnostic[];\n status: "ok" | "warn" | "error";\n}\ninterface SideGearPairResult {\n side: Shape;\n vertical: Shape;\n centerDistance: number;\n centerDistanceNominal: number;\n backlash: number;\n pressureAngleDeg: number;\n meshPlaneZ: number;\n radialOverlap: number;\n jointRatio: number;\n speedReduction: number;\n /** Phase rotation (degrees) for the vertical gear. See GearPairResult.phaseDeg. */\n phaseDeg: number;\n diagnostics: GearPairDiagnostic[];\n status: "ok" | "warn" | "error";\n}\ninterface FaceGearPairOptions {\n face: Shape | FaceGearSpec;\n vertical: Shape | GearPairSpec;\n backlash?: number;\n centerDistance?: number;\n meshPlaneZ?: number;\n place?: boolean;\n phaseDeg?: number;\n}\ninterface FaceGearPairResult {\n face: Shape;\n vertical: Shape;\n centerDistance: number;\n centerDistanceNominal: number;\n backlash: number;\n pressureAngleDeg: number;\n meshPlaneZ: number;\n radialOverlap: number;\n jointRatio: number;\n speedReduction: number;\n /** Phase rotation (degrees) for the vertical gear. See GearPairResult.phaseDeg. */\n phaseDeg: number;\n diagnostics: GearPairDiagnostic[];\n status: "ok" | "warn" | "error";\n}\ntype Vec2$3 = [\n number,\n number\n];\ninterface BoltPatternOptions {\n /** Metric bolt size (\'M2\'–\'M10\'). */\n size: MetricSize;\n /** Clearance class. Default: \'normal\'. */\n fit?: FastenerFit;\n /** List of [x, y] positions in the pattern\'s 2D coordinate frame. */\n positions: Vec2$3[];\n /** Number of circle segments per hole. Default: 48. */\n segments?: number;\n}\ninterface BoltPatternCutOptions {\n /**\n * Z position of the top of the cutters (before punching through). For a cut\n * from z=0 downward through a 20mm plate, pass `from: -1` and `depth: 22` to\n * ensure the cutter fully punches through with some margin.\n */\n from?: number;\n /** Optional counterbore. */\n counterbore?: {\n depth: number;\n diameter?: number;\n };\n /** Optional countersink. */\n countersink?: {\n diameter: number;\n angleDeg?: number;\n };\n}\ninterface BoltPattern {\n /** Bolt size (\'M5\' etc.). */\n readonly size: MetricSize;\n /** Clearance diameter in mm, derived from size + fit. */\n readonly dia: number;\n /** Positions in 2D (XY of the plane being cut). */\n readonly positions: ReadonlyArray<Vec2$3>;\n /** Min X across all positions — useful for housing sizing. */\n readonly minX: number;\n /** Max X across all positions. */\n readonly maxX: number;\n /** Min Y across all positions. */\n readonly minY: number;\n /** Max Y across all positions. */\n readonly maxY: number;\n /**\n * Subtract the pattern from a shape. Cuts one ISO-clearance hole at each position.\n *\n * @param shape - Shape to cut.\n * @param depth - Depth of the cut along Z.\n * @param options - Optional counterbore/countersink and Z offset.\n */\n cut(shape: Shape, depth: number, options?: BoltPatternCutOptions): Shape;\n}\ntype WasherStandard = "din-125-a";\ninterface FastenerSetDimensions {\n size: MetricSize;\n nominalDiameter: number;\n boltLength: number;\n clearanceDia: number;\n tapDia: number;\n nutAcrossFlats: number;\n nutHeight: number;\n washerOuterDia: number;\n washerInnerDia: number;\n washerThickness: number;\n}\ninterface FastenerSetOptions {\n /** Include a DIN 125-A washer under the bolt head (default: `true`). */\n washerUnderHead?: boolean;\n /** Include a DIN 125-A washer under the nut (default: `true`). */\n washerUnderNut?: boolean;\n /**\n * Clearance fit class for the through-hole cutter (default: `\'normal\'`).\n *\n * | Fit | Hole dia | Use when |\n * |-----|----------|----------|\n * | `\'close\'` | nominal + ~0.2 mm | Press-location or close-tolerance |\n * | `\'normal\'` | nominal + ~0.5 mm | Standard through-bolt clearance |\n * | `\'loose\'` | nominal + 1–2 mm | Adjustment slots or misaligned patterns |\n * | `\'tap\'` | ISO tap drill | Tapped hole in mating part |\n */\n fit?: FastenerFit;\n /** Polygon segment count for all circular geometry (default: 36). */\n segments?: number;\n}\ninterface FastenerSetResult {\n /** Hex bolt: head top at z=0, threaded shaft extends toward −Z by `boltLength`. */\n bolt: Shape;\n /** Hex nut centered at z=0. */\n nut: Shape;\n /** Flat washer centered at z=0. Null when washerUnderHead is false. */\n washerUnderHead: Shape | null;\n /** Flat washer centered at z=0. Null when washerUnderNut is false. */\n washerUnderNut: Shape | null;\n /** Clearance-hole cutter (cylinder) centered at z=0, for subtracting from a through-plate. */\n clearanceHole: Shape;\n /** Tap-drill cutter (cylinder) centered at z=0, for subtracting from a tapped plate. */\n tappedHole: Shape;\n /** Reference dimensions for BOM, placement calculations, and documentation. */\n dims: FastenerSetDimensions;\n}\n/**\n * Pre-built parametric parts available in user scripts as `lib.*`.\n *\n * **Details**\n *\n * Every key in this object becomes a method or namespace on the `lib` object\n * exposed to `.forge.js` scripts — see the member list below for the catalog.\n * Sizes outside the supported ranges throw at runtime with a descriptive error.\n *\n * @category Part Library\n */\ndeclare const partLibrary: {\n /**\n * Plain cylindrical through-hole cutter centered on Z=0. Legacy helper —\n * `Shape.hole()` places, validates, and recesses holes on a face directly.\n * @softDeprecated shape.hole(face, { diameter, depth }) — face-relative blind/through holes with counterbore/countersink/thread support\n * @deprecated use shape.hole(face, { diameter, depth }) — face-relative blind/through holes with counterbore/countersink/thread support\n */\n boltHole: (diameter: number, depth: number) => Shape;\n/**\n * ISO metric fastener hole cutter with optional counterbore or countersink.\n *\n * **Details**\n *\n * Returns a cutter shape (subtract from a solid to produce the hole). Sizes\n * outside M2–M10 will throw.\n *\n * **Example**\n *\n * ```ts\n * const plate = box(60, 40, 8)\n * .subtract(lib.fastenerHole({ size: \'M5\', fit: \'normal\', depth: 8 })\n * .translate(15, 10, 4));\n * ```\n *\n * @param opts - Hole configuration including size, fit class, depth, and optional recesses.\n * @returns A cutter shape centered on Z=0 (or positioned at Z=0 top when `center: false`).\n * @category Fasteners\n */\n\n fastenerHole(opts: FastenerHoleOptions): Shape;\n /**\n * Counterbore hole cutter — through-hole with a wider cylindrical recess at\n * the top, built from four positional numbers. Legacy helper — `Shape.hole()`\n * takes named options and places the hole on a face directly.\n * @softDeprecated shape.hole(face, { diameter: holeDia, counterbore: { diameter: boreDia, depth: boreDepth } }) — through by default; pass depth for blind holes\n * @deprecated use shape.hole(face, { diameter: holeDia, counterbore: { diameter: boreDia, depth: boreDepth } }) — through by default; pass depth for blind holes\n */\n counterbore: (holeDia: number, boreDia: number, boreDepth: number, totalDepth: number) => Shape;\n/**\n * Rectangular hollow tube (thin-wall box section).\n *\n * Both the outer and inner boxes are centered on the XY plane with their base at Z=0.\n *\n * @param outerX - Outer width in mm.\n * @param outerY - Outer depth in mm.\n * @param outerZ - Length (height) of the tube in mm.\n * @param wall - Wall thickness in mm (removed uniformly from all four sides).\n * @returns A hollow rectangular tube solid.\n * @category Fasteners\n */\n\n tube(outerX: number, outerY: number, outerZ: number, wall: number): Shape;\n/**\n * Hollow cylindrical pipe.\n *\n * Centered on the XY plane, extending upward along +Z from z=0 to z=height.\n * For complex routed pipe geometry, see `lib.pipeRoute`.\n *\n * @param height - Pipe length in mm.\n * @param outerRadius - Outer radius in mm.\n * @param wall - Wall thickness in mm.\n * @param segments - Polygon approximation segments (default: 32).\n * @returns A hollow cylinder solid.\n * @category Fasteners\n */\n\n pipe(height: number, outerRadius: number, wall: number, segments?: number): Shape;\n/**\n * Apply deterministic exploded-view offsets to an assembly tree.\n *\n * **Details**\n *\n * Traverses arrays, nested `{ name, group: [...] }` structures, and `ShapeGroup` outputs,\n * translating each node while preserving names, colors, and nesting; returns the same structure\n * type as the input. `radial` mode is branch-aware and parent-relative — nested assemblies peel\n * apart level by level. Named items accept an inline `explode: { stage?, direction?, axisLock? }`\n * override. Bakes the offset into geometry (e.g. driven by a `param()` slider); for a\n * viewport-only slider use `explodeView()` instead.\n *\n * **Example**\n *\n * ```js\n * const explodeAmt = param(\'Explode\', 0, { min: 0, max: 40, unit: \'mm\' });\n *\n * return lib.explode(assembly, {\n * amount: explodeAmt,\n * stages: [0.4, 0.8],\n * mode: \'radial\',\n * byName: { Shaft: { direction: [1, 0, 0], stage: 1.4 } },\n * });\n * ```\n *\n * @param items - Array of `Shape | Sketch | ShapeGroup | { name, shape?, sketch?, group?, explode? }`, or a `ShapeGroup`\n * @param options.amount - Total explode distance in model units\n * @param options.stages - Per-depth stage multipliers (depth 1 = first level). Default: reciprocal depth (`1, 1/2, 1/3, ...`)\n * @param options.mode - Direction mode: `\'radial\'` | `\'x\'` | `\'y\'` | `\'z\'` | `[x, y, z]`. Default: `\'radial\'`\n * @param options.axisLock - Constrain motion to a world axis: `\'x\'` | `\'y\'` | `\'z\'`\n * @param options.byName - Per-object overrides keyed by name: `{ stage?, direction?, axisLock? }`\n * @param options.byPath - Per-tree-path overrides using slash-separated path segments\n * @returns Same structure type as input, with translated geometry\n * @see {@link explodeView} for viewport-slider-driven explode without rerunning the script\n * @category Explode View\n */\n\n explode<T extends ExplodeItem[] | ShapeGroup>(items: T, options?: ExplodeOptions): T;\n /**\n * Generic hex nut with a cylindrical bore. Legacy twin of `lib.nut()`.\n * @softDeprecated lib.nut(holeDia, { acrossFlats, height, boreDiameter: holeDia }) — exact same geometry\n * @deprecated use lib.nut(holeDia, { acrossFlats, height, boreDiameter: holeDia }) — exact same geometry\n */\n hexNut: (acrossFlats: number, height: number, holeDia: number) => Shape;\n/**\n * L-shaped mounting bracket with optional through-holes.\n *\n * Produces a right-angle bracket: a horizontal base plate and a vertical wall.\n * Both legs share `width`. Optional holes are drilled through the base (along Z)\n * and the wall (along Y).\n *\n * @param width - Width of the bracket (both legs share this dimension) in mm.\n * @param height - Wall leg height in mm.\n * @param depth - Base leg depth in mm.\n * @param thick - Material thickness in mm (both legs).\n * @param holeDia - Hole diameter in mm. Pass `0` (default) to omit holes.\n * @returns An L-bracket solid with base at Z=0.\n * @category Fasteners\n */\n\n bracket(width: number, height: number, depth: number, thick: number, holeDia?: number): Shape;\n/**\n * Rectangular grid of cylindrical hole cutters.\n *\n * Returns the union of `rows × cols` cylinders laid out on a regular grid.\n * Subtract from a solid to produce the full pattern.\n *\n * **Example**\n *\n * ```ts\n * const pattern = lib.holePattern(3, 4, 20, 20, 4, 10);\n * const panel = box(80, 70, 10).subtract(pattern.translate(-30, -20, 0));\n * ```\n *\n * @param rows - Number of rows in the grid.\n * @param cols - Number of columns in the grid.\n * @param spacingX - Column pitch in mm.\n * @param spacingY - Row pitch in mm.\n * @param holeDia - Hole diameter in mm.\n * @param depth - Hole depth in mm.\n * @returns Union of cutter cylinders, origin at the first hole center.\n * @category Fasteners\n */\n\n holePattern(rows: number, cols: number, spacingX: number, spacingY: number, holeDia: number, depth: number): Shape;\n/**\n * External helical thread — clean mesh, no SDF grid artifacts.\n *\n * **Details**\n *\n * Builds a cross-section with a single trapezoidal tooth from the root radius\n * out to the crest radius, then twist-extrudes it so the tooth traces a helix.\n * Manifold\'s extrude+twist produces structured quad-based geometry that follows\n * the thread profile cleanly.\n *\n * Returns a threaded cylinder along +Z from z=0 to z=length.\n *\n * @param diameter - Nominal (crest) diameter in mm.\n * @param pitch - Thread pitch in mm (axial distance between crests).\n * @param length - Thread length in mm.\n * @param options - Optional overrides: `depth` (tooth height, default 0.35×pitch), `segments` (default 36).\n * @returns A threaded cylinder solid.\n * @category Fasteners\n */\n\n thread(diameter: number, pitch: number, length: number, options?: {\n depth?: number;\n segments?: number;\n}): Shape;\n/**\n * ISO-style hex bolt with real helical threads.\n *\n * **Details**\n *\n * The hex head sits from z=0 up to z=headHeight. The shaft extends downward\n * along −Z by `length` mm. An unthreaded shank section is included when\n * `threadLength < length`.\n *\n * Default proportions follow ISO 4762 loosely: pitch ≈ 0.15×diameter,\n * head height ≈ 0.65×diameter, across-flats ≈ 1.6×diameter.\n *\n * For standard M-size bolts pre-configured for a complete joint, use\n * {@link fastenerSet} instead.\n *\n * @param diameter - Nominal shaft diameter in mm.\n * @param length - Shaft length in mm (head height not included).\n * @param options - Optional overrides for pitch, head height, across-flats, thread length, and segments.\n * @returns A hex bolt solid with head top at z=0.\n * @category Fasteners\n */\n\n bolt(diameter: number, length: number, options?: {\n pitch?: number;\n headHeight?: number;\n headAcrossFlats?: number;\n threadLength?: number;\n segments?: number;\n}): Shape;\n/**\n * ISO-style hex nut with a clearance bore.\n *\n * **Details**\n *\n * Constructed from the intersection of three rotated slabs with a cylindrical\n * bore subtracted. The nut is centered at the origin, height along Z\n * (−height/2 to +height/2).\n *\n * Default proportions follow ISO 4032 loosely: height ≈ 0.8×diameter,\n * across-flats ≈ 1.6×diameter. The bore is a clearance bore — `diameter`\n * + 0.2 mm by default, override with `boreDiameter` for exact bore control —\n * not modelled with helical threads, for rendering efficiency.\n *\n * For standard M-size nuts pre-configured for a complete joint, use\n * {@link fastenerSet} instead.\n *\n * @param diameter - Nominal thread diameter in mm.\n * @param options - Optional overrides for height, across-flats, exact bore diameter, and segments.\n * @returns A hex nut solid centered at origin.\n * @category Fasteners\n */\n\n nut(diameter: number, options?: {\n height?: number;\n acrossFlats?: number;\n /** Exact bore diameter in mm. Default: `diameter + 0.2` (clearance bore). */\n boreDiameter?: number;\n segments?: number;\n}): Shape;\n/**\n * ISO metric flat washer (DIN 125-A).\n *\n * **Details**\n *\n * Returns a flat ring centered at the origin, thickness along Z. Dimensions\n * are taken from {@link WASHER_TABLE}. Sizes outside M2–M10 will throw.\n *\n * @param size - ISO metric thread size, e.g. `\'M5\'`. Supported: M2–M10.\n * @param options - Optional `standard` (only `\'din-125-a\'` supported) and `segments` (default 48).\n * @returns A flat washer ring solid centered at origin.\n * @category Fasteners\n */\n\n washer(size: MetricSize, options?: {\n standard?: WasherStandard;\n segments?: number;\n}): Shape;\n/**\n * Complete ISO metric fastener set — bolt, nut, optional washers, and matching hole cutters.\n *\n * **Details**\n *\n * Returns all geometry for one bolted joint: the bolt, nut, up to two washers,\n * a clearance-hole cutter, and a tap-drill cutter. All shapes are returned\n * **un-positioned** (each on the Z-axis). Place them with `.translate()`.\n *\n * Sizes outside M4–M10 are supported for the washer (M2–M10); unsupported\n * combinations will throw.\n *\n * **Example**\n *\n * ```ts\n * const hw = lib.fastenerSet(\'M5\', 20);\n *\n * const topPlate = box(60, 40, 8).translate(0, 0, 12)\n * .subtract(hw.clearanceHole.translate(15, 10, 12));\n * const botPlate = box(60, 40, 8)\n * .subtract(hw.clearanceHole.translate(15, 10, 0));\n *\n * return [\n * { name: \'Top Plate\', shape: topPlate },\n * { name: \'Bot Plate\', shape: botPlate },\n * { name: \'Bolt\', shape: hw.bolt.translate(15, 10, 20) },\n * { name: \'Nut\', shape: hw.nut.translate(15, 10, -4) },\n * ];\n * ```\n *\n * @param size - ISO metric thread size, e.g. `\'M5\'`. Supported: M4–M10 (bolt/nut), M2–M10 (washer).\n * @param boltLength - Nominal shaft length in mm (head height not included). Must be > 0.\n * @param options - Washer inclusion, fit class, and segment count.\n * @returns A {@link FastenerSetResult} with all joint shapes and reference dimensions.\n * @category Fasteners\n */\n\n fastenerSet(size: MetricSize, boltLength: number, options?: FastenerSetOptions): FastenerSetResult;\n/**\n * Route a pipe (solid or hollow) through 3D waypoints with smooth bends.\n *\n * This is a convenience recipe over `Curve.Route.fromPolyline()` and `sweep()`.\n * Use `Curve.Route` directly when you need route metadata, named ports, or\n * custom swept profiles.\n */\n\n pipeRoute(points: [\n number,\n number,\n number\n][], radius: number, options?: {\n bendRadius?: number;\n wall?: number;\n segments?: number;\n}): Shape;\n/**\n * Pipe elbow — a curved pipe section for connecting two pipe directions.\n *\n * This is a convenience recipe over `Curve.Arc()` and `sweep()`. Use\n * `Curve.Route.fromPolyline()` directly when you need named route ports,\n * segment metadata, or multi-bend pipe runs.\n *\n * @param pipeRadius - Pipe outer radius\n * @param bendRadius - Centerline bend radius (distance from arc center to pipe center)\n * @param angle - Bend angle in degrees (e.g. 90 for a right-angle bend)\n * @param options.wall - Wall thickness for hollow pipe\n * @param options.segments - Circumferential segments (default 32)\n * @param options.from - Incoming direction vector (default [0,0,1])\n * @param options.to - Outgoing direction vector (overrides angle if both from/to given)\n */\n\n elbow(pipeRadius: number, bendRadius: number, angle?: number | {\n from?: [\n number,\n number,\n number\n ];\n to?: [\n number,\n number,\n number\n ];\n wall?: number;\n segments?: number;\n}, options?: {\n wall?: number;\n segments?: number;\n from?: [\n number,\n number,\n number\n ];\n to?: [\n number,\n number,\n number\n ];\n}): Shape;\n/**\n * Create a flat open-belt body around two pulley pitch circles.\n *\n * The belt is generated as a tangent loop in the XY plane and extruded along\n * +Z by `beltWidth`. The result includes the solid belt, the 2D belt profile,\n * a thin pitch-path sketch for visualization, total belt length, tangent spans,\n * and wrap metadata for each pulley.\n *\n * For more than two pulleys, the API intentionally asks for route intent before\n * geometry is created. Use `route: "outer"` for the future outside-envelope\n * mode, or an ordered route for future serpentine/idler layouts.\n *\n * ```ts\n * const drive = lib.beltDrive({\n * pulleys: [\n * { name: "motor", center: [0, 0], pitchRadius: 12 },\n * { name: "output", center: [80, 0], pitchRadius: 28 },\n * ],\n * beltWidth: 8,\n * beltThickness: 2,\n * });\n * return drive.belt;\n * ```\n *\n * @category Belt Drives\n */\n\n beltDrive(options: BeltDriveOptions): BeltDriveResult;\n/**\n * Build a closed 2D route made from common tangent spans and pulley wrap arcs.\n *\n * Use this when you need reusable belt/chain route geometry before creating a\n * solid body. The first implementation supports two circles. `mode: "open"`\n * uses external tangents; `mode: "crossed"` uses internal tangents.\n *\n * ```ts\n * const route = lib.tangentLoop2d([\n * { center: [0, 0], radius: 12 },\n * { center: [80, 0], radius: 28 },\n * ]);\n * const belt = route.offsetBand(2).extrude(8);\n * ```\n *\n * @category Belt Drives\n */\n\n tangentLoop2d(circles: TangentCircle2D[], options?: TangentLoop2DOptions): TangentLoop2D;\n/**\n * Build a 2D T-slot cross-section sketch.\n *\n * Default parameters describe a 20x20 B-type profile with slot 6.\n * Use this when you want a drawing-ready profile sketch before extrusion.\n */\n\n tSlotProfile(options?: TSlotProfileOptions): Sketch;\n/**\n * Build a T-slot extrusion from the generated 2D profile.\n * Extrudes along +Z by default.\n */\n\n tSlotExtrusion(length: number, options?: TSlotExtrusionOptions): Shape;\n/**\n * Accurate-ish 2D profile for 20x20 B-type slot 6.\n *\n * Returns a drawing-ready Sketch centered at origin.\n */\n\n profile2020BSlot6Profile(options?: Profile2020BSlot6ProfileOptions): Sketch;\n/**\n * 20x20 B-type slot 6 extrusion with profile-accurate defaults.\n *\n * Pass option overrides if your supplier\'s profile differs slightly.\n */\n\n profile2020BSlot6(length: number, options?: Profile2020BSlot6Options): Shape;\n/**\n * Involute external spur gear with optional center bore.\n *\n * Specify module, teeth, faceWidth as required parameters. Optional tuning includes\n * pressureAngleDeg (default 20), backlash, clearance, addendum, dedendum, boreDiameter,\n * and segmentsPerTooth (default 10).\n *\n * **Connectors (for assembly-based positioning):**\n * - `bore`: revolute connector at the bore center, axis along +Z. Carries\n * measurements: `{ module, teeth, pitchRadius, outerRadius, faceWidth }`.\n *\n * Use `.connect("Housing.seat", "Gear.bore")` to mount a gear on a shaft seat.\n */\n\n spurGear(options: SpurGearOptions): Shape;\n/**\n * Conical bevel gear generated from a tapered involute extrusion. Specify pitchAngleDeg directly or derive it from mateTeeth + shaftAngleDeg.\n *\n * **Connectors (for assembly-based positioning):**\n * - `bore`: revolute connector at the large-end bore center (Z=0), axis along -Z (away from teeth).\n * - `apex`: connector at the cone apex above the gear (the point where the pitch cone converges),\n * axis along +Z. Useful for meshing two bevel gears — their apices should coincide.\n *\n * Carries measurements: `{ module, teeth, pitchRadius, pitchAngleDeg, coneDistance, faceWidth }`.\n */\n\n bevelGear(options: BevelGearOptions): Shape;\n/**\n * Face gear (crown style) where the teeth project axially from one face\n * (top or bottom) of the disk instead of the outer cylindrical rim.\n *\n * Uses the same involute tooth sizing as spurGear, then projects the tooth\n * band axially from the chosen side (`side`, default `\'top\'`) with axial\n * tooth height `toothHeight` (default: one module). To mesh it with a\n * perpendicular spur pinion, prefer `lib.faceGearPair()` — it rotates and\n * positions the pinion at the face tooth band for you.\n *\n * **Connectors (for assembly-based positioning):**\n *\n * - `bore`: revolute connector at the bore center on the body side (the face\n * opposite the teeth), axis pointing away from the teeth. Carries\n * measurements: `{ module, teeth, pitchRadius, outerRadius, faceWidth, toothSide }`.\n *\n * ```js\n * const crown = lib.faceGear({ module: 1.5, teeth: 30, faceWidth: 5, boreDiameter: 6 });\n * assembly().connect("Housing.crown_seat", "Crown.bore");\n * ```\n */\n\n faceGear(options: FaceGearOptions): Shape;\n /**\n * Legacy name for `lib.faceGear()` — identical arguments and geometry.\n * @softDeprecated lib.faceGear(options) — identical arguments\n * @deprecated use lib.faceGear(options) — identical arguments\n */\n sideGear: (options: SideGearOptions) => Shape;\n/**\n * Internal ring gear with involute-derived tooth spaces. Specify rimWidth or outerDiameter for the annular body.\n *\n * **Connectors (for assembly-based positioning):**\n * - `bore`: connector at the ring center, axis along +Z. For planetary gearboxes, this is\n * where the ring mounts to the housing. Carries measurements:\n * `{ module, teeth, pitchRadius, innerRadius, outerRadius, faceWidth }`.\n */\n\n ringGear(options: RingGearOptions): Shape;\n/**\n * Linear rack gear with pressure-angle flanks. Use with spurGear for rack-and-pinion mechanisms.\n *\n * **Orientation:** teeth run along the X axis with tooth tips pointing +Y (pitch line at Y=0).\n * The rack is extruded +Z by `faceWidth`. Rotate the rack to align with a different slide axis.\n *\n * **Connectors (for assembly-based positioning):**\n * - `teeth`: prismatic connector at the pitch line center, axis along +X (slide direction).\n * Carries measurements: `{ module, teeth, faceWidth, length }`.\n *\n * Connect to a housing\'s rack channel:\n * ```js\n * housing.withConnectors({\n * rack_channel: connector("rack-channel", {\n * origin: [pitchR, 0, channelZ], axis: [1, 0, 0], kind: "prismatic",\n * }),\n * });\n * assembly.connect("Housing.rack_channel", "Rack.teeth", { as: "slide" });\n * ```\n */\n\n rackGear(options: RackGearOptions): Shape;\n/**\n * Build or validate a spur-gear pair and return ratio, backlash, and mesh diagnostics.\n *\n * Accepts either shapes from spurGear() or analytical specs for each member.\n * When place is true (default), the gear is auto-positioned at the correct center distance.\n */\n\n gearPair(options: GearPairOptions): GearPairResult;\n/** Build or validate a bevel-gear pair and return ratio diagnostics plus recommended joint placement vectors. */\n\n bevelGearPair(options: BevelGearPairOptions): BevelGearPairResult;\n/**\n * Pair helper for a face (crown) gear + perpendicular "vertical" spur gear.\n * Auto-placement rotates the spur around +Y and positions it to mesh at the face tooth band.\n */\n\n faceGearPair(options: FaceGearPairOptions): FaceGearPairResult;\n /**\n * Legacy name for `lib.faceGearPair()` — same options with the side member renamed to face.\n * @softDeprecated lib.faceGearPair({ face, vertical, ... }) — same options with the side member renamed to face; diagnostics use facegear.* codes\n * @deprecated use lib.faceGearPair({ face, vertical, ... }) — same options with the side member renamed to face; diagnostics use facegear.* codes\n */\n sideGearPair: (options: SideGearPairOptions) => SideGearPairResult;\n/**\n * Coupling ratio between two meshed spur gears.\n *\n * When gear A turns 1°, gear B turns `-teethA / teethB` degrees (negative because\n * meshed external gears rotate in opposite directions).\n *\n * @example\n * ```js\n * const drivenPerDriver = lib.gearRatio(12, 24); // -0.5\n * verify.equal("external spur ratio", drivenPerDriver, -0.5);\n * ```\n *\n * Pass `{ internal: true }` for internal gear pairs (ring gear + spur/planet),\n * where the two rotate in the same direction.\n */\n\n gearRatio(teethA: number, teethB: number, options?: {\n internal?: boolean;\n}): number;\n/**\n * Coupling ratio between a pinion and a rack.\n *\n * When the pinion rotates by `θ` degrees, the rack slides by `θ × (π × module × teeth / 360)` mm.\n * Equivalently, 1mm of rack travel = `180 / (π × pitchRadius)` degrees of pinion rotation.\n *\n * @example\n * ```js\n * const pinionDegPerMm = lib.rackRatio(1.5, 12); // ~6.37 deg/mm\n * ```\n */\n\n rackRatio(module: number, pinionTeeth: number): number;\n/**\n * Planetary gear reduction ratio when the ring is held fixed.\n *\n * Input: sun. Output: carrier. Ratio: `1 + ringTeeth / sunTeeth`.\n * One turn of the sun produces `1 / ratio` turns of the carrier.\n */\n\n planetaryRatio(sunTeeth: number, ringTeeth: number): number;\n/**\n * Define a bolt pattern once and cut it from multiple parts.\n *\n * @example\n * ```js\n * const bolts = lib.boltPattern({\n * size: \'M5\',\n * positions: [[20, 15], [-20, 15], [20, -15], [-20, -15]],\n * });\n *\n * const base = bolts.cut(box(60, 50, 10), 12, { from: -1 });\n * const cover = bolts.cut(box(60, 50, 3), 5, { from: -1 });\n * // Same positions in both parts — guaranteed aligned.\n * ```\n *\n * @category Part Library\n */\n\n boltPattern(options: BoltPatternOptions): BoltPattern;\n /** Start a composable exceptional gear or drive wheel. */\n/**\n * Start a composable exceptional gear or drive wheel.\n */\n\n driveWheel(options?: DriveWheelOptions): DriveWheelBuilder;\n /** Read functional-region metadata from a drive wheel shape. */\n/**\n * Read the functional-region metadata attached by `driveWheel().build()`.\n */\n\n readDriveWheelMeta(shape: Shape): DriveWheelMeta | null;\n /** Involute sector gear with teeth on only part of the pitch circle. */\n/**\n * Involute sector gear with teeth on only part of the pitch circle.\n *\n * Specify the full-circle pitch as `teethOnFullCircle`, then choose the active\n * tooth window with `firstTooth` and `toothCount`. The body is separate from\n * the tooth region: pass a `gearBody...` shape for spokes, hubs, and product\n * styling, or omit it for a simple root-radius disk.\n *\n * **Example**\n *\n * ```ts\n * const body = lib.gearBodies.spoked({\n * outerRadius: 22, rimWidth: 3, hubDiameter: 10,\n * spokeCount: 5, spokeWidth: 2.5, faceWidth: 8, boreDiameter: 5,\n * });\n * const sector = lib.sectorGear({\n * module: 1.25, teethOnFullCircle: 36, toothCount: 10,\n * faceWidth: 8, body,\n * });\n * ```\n */\n\n sectorGear(options: SectorGearOptions): Shape;\n /** Gear body preset namespace: disk, diskWithHub, spoked, and fromProfile. */\n gearBodies: {\n readonly disk: typeof gearBodyDisk;\n readonly diskWithHub: typeof gearBodyDiskWithHub;\n readonly spoked: typeof gearBodySpoked;\n readonly fromProfile: typeof gearBodyFromProfile;\n };\n /**\n * Flat alias for `lib.gearBodies.disk()`.\n * @softDeprecated lib.gearBodies.disk(options) — identical arguments\n * @deprecated use lib.gearBodies.disk(options) — identical arguments\n */\n gearBodyDisk: (options: GearBodyDiskOptions) => Shape;\n /**\n * Flat alias for `lib.gearBodies.diskWithHub()`.\n * @softDeprecated lib.gearBodies.diskWithHub(options) — identical arguments\n * @deprecated use lib.gearBodies.diskWithHub(options) — identical arguments\n */\n gearBodyDiskWithHub: (options: GearBodyDiskWithHubOptions) => Shape;\n /**\n * Flat alias for `lib.gearBodies.spoked()`.\n * @softDeprecated lib.gearBodies.spoked(options) — identical arguments\n * @deprecated use lib.gearBodies.spoked(options) — identical arguments\n */\n gearBodySpoked: (options: GearBodySpokedOptions) => Shape;\n /**\n * Flat alias for `lib.gearBodies.fromProfile()`.\n * @softDeprecated lib.gearBodies.fromProfile(profile, options) — identical arguments\n * @deprecated use lib.gearBodies.fromProfile(profile, options) — identical arguments\n */\n gearBodyFromProfile: (profile: Sketch, options: GearBodyFromProfileOptions) => Shape;\n};\ninterface ProductMaterial {\n color?: string;\n material?: ShapeMaterialProps;\n}\ninterface ClearMaterialOptions {\n tint?: string;\n opacity?: number;\n}\ninterface ColorMaterialOptions {\n color?: string;\n}\ntype ProductProfileKind = "oval" | "roundedRect" | "circle" | "superEllipse" | "custom";\ninterface ProductProfileDescriptor {\n sketch: Sketch;\n width: number;\n depth: number;\n kind: ProductProfileKind;\n radius?: number;\n}\ninterface ProductProfileOptions {\n segments?: number;\n}\ninterface ProductSuperEllipseOptions extends ProductProfileOptions {\n /** Higher values produce squarer product surfaces; 2 is an ellipse. */\n exponent?: number;\n}\ntype ProductRailKind = "bezier" | "nurbs" | "polyline";\ninterface ProductRailSpec {\n kind: ProductRailKind;\n points: Vec3[];\n degree?: number;\n name?: string;\n}\ntype ProductScenePreset = "product" | "service" | "mechanical";\ninterface ProductStationProfile {\n sketch: Sketch;\n width: number;\n depth: number;\n kind: ProductProfileKind;\n radius?: number;\n exponent?: number;\n}\ninterface ProductStationSpec {\n name: string;\n center: Vec3;\n profile: ProductStationProfile;\n crown?: number;\n}\ninterface ProductStationSuperEllipseOptions {\n segments?: number;\n exponent?: number;\n}\ndeclare class ProductStationBuilder {\n readonly name: string;\n private centerValue;\n private profileValue?;\n private crownValue;\n constructor(name: string);\n /** Position this station in world coordinates. */\n at(point: Vec3): this;\n /** Convenience for traditional Z-up section stacks. */\n z(z: number): this;\n /** Convenience for product bodies running front-to-back along Y. */\n y(y: number): this;\n /** Convenience for product bodies running left-to-right along X. */\n x(x: number): this;\n /** Use an oval cross-section with full width and depth dimensions. */\n oval(width: number, depth: number, options?: {\n segments?: number;\n }): this;\n /** Use a superellipse cross-section for soft-square product surfaces. */\n superEllipse(width: number, depth: number, options?: ProductStationSuperEllipseOptions): this;\n /** Use a rounded-rectangle cross-section with the given corner radius. */\n roundedRect(width: number, depth: number, radius: number): this;\n /** Use a circular cross-section from a full diameter. */\n circle(diameter: number, options?: {\n segments?: number;\n }): this;\n /** Use a custom 2D sketch as the station cross-section. */\n custom(sketch: Sketch, width: number, depth: number): this;\n /** Set the station crown amount for soft product-section intent. */\n crown(amount: number): this;\n /** Return the immutable station spec consumed by Product.skin(). */\n toSpec(): ProductStationSpec;\n}\n/** Primary world axis used to order ProductSkin loft stations. */\ntype ProductSkinAxis = "X" | "Y" | "Z";\n/** Semantic side of a ProductSkin. `back` is accepted as an alias for `rear`. */\ntype ProductSkinSide = "left" | "right" | "top" | "bottom" | "front" | "rear" | "back";\n/** Reported lowering mode for ProductSkin and conformal feature diagnostics. */\ntype ProductSkinRepresentation = "exact" | "sampled" | "mixed" | "fallback";\ninterface ProductSkinRefQuery {\n /** Side of the product skin. `front` is the minimum axis cap, `rear`/`back` is the maximum axis cap. */\n side: ProductSkinSide;\n /** Across-side parameter for side refs. Defaults to 0.5. */\n u?: number;\n /** Along-axis parameter, 0 at the first cap and 1 at the rear/back cap. Defaults to 0.5. */\n v?: number;\n /** Positive distance away from the surface along the resolved normal. */\n offset?: number;\n}\ninterface ProductSurfaceFrame {\n point: Vec3;\n normal: Vec3;\n tangentU: Vec3;\n tangentV: Vec3;\n matrix: Mat4;\n skin: string;\n representation: ProductSkinRepresentation;\n}\ninterface ProductSkinDiagnostics {\n representation: ProductSkinRepresentation;\n lowering: string[];\n warnings: string[];\n stationNames: string[];\n railNames: string[];\n}\ninterface ProductAttachOptions {\n offset?: number;\n inset?: number;\n}\ninterface ProductPanelAttachOptions extends ProductAttachOptions {\n at?: Partial<ProductSkinRefQuery>;\n thickness?: number;\n material?: ProductMaterial;\n color?: string;\n}\ntype ProductRefInput = ProductSurfaceRef;\n/** Options shared by Product.ribbon() builders and Product.surface(...).ribbon(...). */\ninterface ProductRibbonBuildOptions {\n /** Width across the surface in millimeters. */\n width?: number;\n /** Solid thickness outward from the source surface in millimeters. */\n thickness?: number;\n /** Positive clearance between the source surface and the ribbon\'s inner face. */\n offset?: number;\n /** Samples along the ribbon path. Higher values bend more smoothly. */\n samples?: number;\n /** Samples across the ribbon width. Use 3+ to visibly wrap over curved cross-sections. */\n widthSamples?: number;\n /** Tessellation resolution passed to the lowered NURBS surface. */\n resolution?: number;\n /** Apply a product material preset to the ribbon. */\n material?: ProductMaterial;\n /** Apply a simple color override. */\n color?: string;\n}\ntype ProductSurfaceRibbonOptions = ProductRibbonBuildOptions;\n/** Path point for Product.ribbon().on(...): either a side/u/v query or a resolved surface ref. */\ntype ProductRibbonPathPoint = ProductSkinRefQuery | ProductSurfaceRef;\n/** Side-local path point for Product.surface(side).ribbon(...); the surface helper supplies `side`. */\ninterface ProductSurfacePathPoint {\n /** Across-side parameter on the bound side. Defaults to 0.5. */\n u?: number;\n /** Along-axis parameter, 0 at the first cap and 1 at the rear/back cap. Defaults to 0.5. */\n v?: number;\n /** Positive distance away from the surface along the resolved normal. */\n offset?: number;\n}\n/** Diagnostics describing how a conformal ribbon was sampled and lowered. */\ninterface ProductRibbonDiagnostics {\n /** Ribbon shape name. */\n name: string;\n /** Source skin name when the ribbon follows a ProductSkin directly. */\n skin?: string;\n /** Source skin side when all path points are on one semantic side. */\n side?: ProductSkinSide;\n /** Number of control path points supplied before interpolation. */\n pathPointCount: number;\n /** Final ribbon width in millimeters. */\n width: number;\n /** Final ribbon solid thickness in millimeters. */\n thickness: number;\n /** Final normal offset from the source surface in millimeters. */\n offset: number;\n /** Final sample count along the ribbon path. */\n samples: number;\n /** Final sample count across the ribbon width. */\n widthSamples: number;\n /** NURBS tessellation resolution used for the lowered surface. */\n resolution: number;\n /** Lowering primitive used for the ribbon shape. */\n lowering: "nurbsSurface";\n /** Expected fidelity inherited from the source skin/ref sampling mode. */\n expectedFidelity: ProductSkinRepresentation;\n /** Number of generated width samples clamped to the valid side span. */\n clampedUCount: number;\n /** Largest absolute u-distance lost to side-span clamping. */\n maxUClampDistance: number;\n /** Non-fatal sampling and lowering warnings. */\n warnings: string[];\n}\n/** Shape plus diagnostics returned by ProductRibbonBuilder.buildWithDiagnostics(). */\ninterface ProductRibbonResult {\n /** Lowered conformal ribbon shape. */\n shape: Shape;\n /** Sampling and lowering diagnostics for the returned shape. */\n diagnostics: ProductRibbonDiagnostics;\n}\ndeclare class ProductSurfaceRef {\n private readonly skin;\n private readonly query;\n readonly name?: string | undefined;\n constructor(skin: ProductSkin, query: ProductSkinRefQuery, name?: string | undefined);\n /** Resolve this semantic surface ref into a point, normal, tangents, and placement matrix. */\n frame(overrides?: Partial<ProductSkinRefQuery>): ProductSurfaceFrame;\n /** Return a copy of this ref with side/u/v/offset overrides. */\n with(overrides: Partial<ProductSkinRefQuery>): ProductSurfaceRef;\n /** Place a detail shape or group on this ref\'s local surface frame. */\n attach(detail: Shape | ShapeGroup, options?: ProductAttachOptions): Shape | ShapeGroup;\n /** Return the serializable side/u/v query behind this ref. */\n querySpec(): ProductSkinRefQuery;\n}\ndeclare class ProductSkin {\n readonly name: string;\n readonly shape: Shape;\n readonly axis: ProductSkinAxis;\n readonly stations: ProductStationSpec[];\n readonly rails: Record<string, ProductRailSpec>;\n private readonly refQueries;\n private readonly axisMin;\n private readonly axisMax;\n private readonly diagnosticsValue;\n constructor(name: string, shape: Shape, axis: ProductSkinAxis, stations: ProductStationSpec[], rails: Record<string, ProductRailSpec>, refs: Record<string, ProductSkinRefQuery>, diagnostics: Omit<ProductSkinDiagnostics, "stationNames" | "railNames">);\n /** Return the renderable shape generated for this product skin. */\n toShape(): Shape;\n /** Create a group containing this skin plus named child details. */\n with(...children: GroupInput[]): ShapeGroup;\n /** Boolean-union structural details into the skin body. */\n integrate(...details: Shape[]): Shape;\n /** Return lowering representation, station names, rail names, and warnings. */\n diagnostics(): ProductSkinDiagnostics;\n /** Create a side/u/v surface-ref query on this skin. */\n uv(side: ProductSkinSide, u?: number, v?: number): ProductSkinRefQuery;\n /** Resolve a named ref published with Product.skin().refs(...). */\n ref(name: string): ProductSurfaceRef;\n /** Create a sampled curve as a sequence of surface refs on this skin. */\n curveOnSurface(name: string, points: Array<Partial<ProductSkinRefQuery> & {\n side: ProductSkinSide;\n }>): ProductSurfaceRef[];\n /**\n * Create a fluent surface helper for refs and conformal features on one side of this skin.\n *\n * Use this when several refs or ribbons share the same skin side; side-local helpers keep\n * path points concise and make it harder to mix sides accidentally.\n */\n surface(side: ProductSkinSide): ProductSurfaceBuilder;\n /** Interpolate center, width, and depth at a normalized v or absolute axis value. */\n stationAt(vOrAxis: number): {\n center: Vec3;\n width: number;\n depth: number;\n dWidth: number;\n dDepth: number;\n axisValue: number;\n exponent: number;\n kind: ProductProfileKind;\n };\n /** Build a local surface frame from a side/u/v query. */\n frame(query: ProductSkinRefQuery): ProductSurfaceFrame;\n}\ndeclare class ProductSkinBuilder {\n readonly name: string;\n private axisValue;\n private stationsValue;\n private railsValue;\n private refsValue;\n private materialValue;\n private colorValue;\n private edgeLengthValue;\n private wallValue;\n constructor(name: string);\n /** Choose the primary station axis for the skin loft. */\n axis(axis: ProductSkinAxis): this;\n /** Set named cross-section stations for the product skin. */\n stations(stations: Array<ProductStationBuilder | ProductStationSpec>): this;\n /** Attach named guide rails for product-skin construction and downstream surface references. */\n rails(rails: Record<string, ProductRailSpec>): this;\n /** Publish a named semantic surface ref on the skin. */\n ref(name: string, query: ProductSkinRefQuery): this;\n /** Publish multiple named semantic surface refs on the skin. */\n refs(refs: Record<string, ProductSkinRefQuery>): this;\n /** Create a side/u/v surface-ref query for use in refs(...) or Product.ref(...). */\n uv(side: ProductSkinSide, u?: number, v?: number): ProductSkinRefQuery;\n /** Apply a product material preset to the lowered skin. */\n material(material: ProductMaterial): this;\n /** Apply a simple color override to the lowered skin. */\n color(color: string): this;\n /** Set the sampled loft target edge length. */\n edgeLength(value: number): this;\n /** Record intended wall thickness for product design metadata. Use explicit shelling when the model needs real inner-wall geometry. */\n wall(thickness: number): this;\n /** Lower stations and refs into a ProductSkin body. */\n build(): ProductSkin;\n}\ndeclare class ProductPanelBuilder {\n readonly name: string;\n private profileValue;\n private thicknessValue;\n private materialValue;\n private colorValue;\n constructor(name: string);\n /** Use a rounded rectangle panel profile. */\n rounded(width: number, height: number, radius?: number): this;\n /** Use an oval panel profile. */\n oval(width: number, height: number): this;\n /** Use a custom 2D panel profile. */\n profile(profile: Sketch): this;\n /** Set panel extrusion thickness. */\n thickness(thickness: number): this;\n /** Apply a product material preset to the panel. */\n material(material: ProductMaterial): this;\n /** Apply a simple color override to the panel. */\n color(color: string): this;\n /** Build the panel in local coordinates. */\n build(): Shape;\n /** Build and attach this panel to a ProductSurfaceRef. */\n attachTo(ref: ProductRefInput, options?: ProductPanelAttachOptions): Shape;\n}\n/**\n * Fluent builder behind the legacy `Product.spout()` feature.\n *\n * @softDeprecated Product.place(loft(sections, heights), ref) — loft local sections at origin, then place on the ProductSurfaceRef frame\n * @deprecated use Product.place(loft(sections, heights), ref) — loft local sections at origin, then place on the ProductSurfaceRef frame\n */\ndeclare class ProductSpoutBuilder {\n readonly name: string;\n private fromValue?;\n private sectionsValue;\n private projectionValue;\n private materialValue;\n private colorValue;\n private edgeLengthValue;\n constructor(name: string);\n /** Set the skin ref this spout projects from. */\n from(ref: ProductSurfaceRef): this;\n /** Set local spout section profiles from root to mouth. */\n sections(sections: Array<Sketch | ProductStationBuilder | ProductStationSpec>): this;\n /** Set the projection length along the source ref normal. */\n projection(length: number): this;\n /** Set the sampled loft target edge length for the spout. */\n edgeLength(value: number): this;\n /** Apply a product material preset to the spout. */\n material(material: ProductMaterial): this;\n /** Apply a simple color override to the spout. */\n color(color: string): this;\n /** Build the spout in local coordinates. */\n build(): Shape;\n /** Build and place the spout on its source ref. */\n attach(options?: ProductAttachOptions): Shape;\n}\n/**\n * Result bundle of the legacy `Product.handle()` builder.\n *\n * @softDeprecated build handles with sweep(gripProfile, spinePoints) plus Product.place(pad, ref) landing pads and group(...) the parts\n * @deprecated use build handles with sweep(gripProfile, spinePoints) plus Product.place(pad, ref) landing pads and group(...) the parts\n */\ndeclare class ProductHandleFeature {\n readonly grip: Shape;\n readonly upperPad: Shape;\n readonly lowerPad: Shape;\n constructor(grip: Shape, upperPad: Shape, lowerPad: Shape);\n /** Return the physical shapes that make up this handle feature. */\n structural(): Shape[];\n /** Boolean-union the handle feature into a single shape. */\n toShape(): Shape;\n /** Return the handle as a named ShapeGroup preserving child colors. */\n toGroup(): ShapeGroup;\n}\n/**\n * Fluent builder behind the legacy `Product.handle()` feature.\n *\n * @softDeprecated sweep(gripProfile, spinePoints) with spine points derived from ref.frame().point, plus Product.place(pad, ref) for landing pads\n * @deprecated use sweep(gripProfile, spinePoints) with spine points derived from ref.frame().point, plus Product.place(pad, ref) for landing pads\n */\ndeclare class ProductHandleBuilder {\n readonly name: string;\n private upperRef?;\n private lowerPoint?;\n private spineValue?;\n private gripProfile;\n private materialValue;\n private padMaterialValue;\n private edgeLengthValue;\n constructor(name: string);\n /** Set the upper body ref and lower world anchor for the handle. */\n between(upper: ProductSurfaceRef, lower: Vec3): this;\n /** Set an explicit handle centerline from points or a rail spec. */\n spine(points: Vec3[] | ProductRailSpec): this;\n /** Set the grip cross-section profile. */\n grip(profile: Sketch): this;\n /** Apply a product material preset to the grip. */\n material(material: ProductMaterial): this;\n /** Apply a product material preset to handle landing pads. */\n padMaterial(material: ProductMaterial): this;\n /** Set the sampled loft target edge length for the grip. */\n edgeLength(value: number): this;\n /** Build the handle grip and landing pads. */\n build(): ProductHandleFeature;\n}\n/** Fluent helper bound to one ProductSkin side for refs and side-local conformal features. */\ndeclare class ProductSurfaceBuilder {\n private readonly skin;\n readonly side: ProductSkinSide;\n constructor(skin: ProductSkin, side: ProductSkinSide);\n /** Create a ref on this skin side. */\n ref(u?: number, v?: number, offset?: number): ProductSurfaceRef;\n /** Create a side/u/v query on this skin side. */\n uv(u?: number, v?: number, offset?: number): ProductSkinRefQuery;\n /** Resolve a point/frame on this surface using the builder\'s side. */\n frame(query?: Partial<ProductSkinRefQuery>): ProductSurfaceFrame;\n /**\n * Start a conformal ribbon on this skin side.\n *\n * Path points use side-local `u`/`v` coordinates; this builder supplies the side.\n * The returned ProductRibbonBuilder is already bound to the source skin and can be further\n * configured before build(). Use `widthSamples` >= 3 when the ribbon must visibly wrap over\n * curved product sections instead of behaving like a flat strip.\n */\n ribbon(name: string, points: ProductSurfacePathPoint[], options?: ProductRibbonBuildOptions): ProductRibbonBuilder;\n}\n/** Builder for thin trim, label, grip, and split-line features that bend with a ProductSkin surface. */\ndeclare class ProductRibbonBuilder {\n readonly name: string;\n private skinValue?;\n private queryPath;\n private refPath;\n private widthValue;\n private thicknessValue;\n private offsetValue;\n private samplesValue;\n private widthSamplesValue;\n private resolutionValue;\n private materialValue;\n private colorValue;\n private lastDiagnosticsValue?;\n constructor(name: string);\n /**\n * Follow a ProductSkin with side/u/v path queries or refs.\n *\n * This is the highest-fidelity mode because every interpolated sample is resolved through\n * ProductSkin.frame(), so the ribbon bends along the selected side as station width/depth changes.\n * All query path points must stay on one side; split side transitions into separate ribbons.\n */\n on(skin: ProductSkin, points: ProductRibbonPathPoint[], options?: ProductRibbonBuildOptions): this;\n /**\n * Follow explicit surface refs.\n *\n * Useful for named refs or paths assembled elsewhere. The builder resolves each ref frame and\n * interpolates between those frames; use on(skin, points) when you need full skin-side sampling\n * between sparse control points.\n */\n fromRefs(points: ProductSurfaceRef[], options?: ProductRibbonBuildOptions): this;\n /** Set ribbon width in millimeters. */\n width(width: number): this;\n /** Set solid thickness outward from the source surface in millimeters. */\n thickness(thickness: number): this;\n /** Set positive clearance between the source surface and the ribbon\'s inner face. */\n offset(offset: number): this;\n /** Set samples along the path. */\n samples(samples: number): this;\n /** Set samples across the width. Use 3+ to bend over curved cross-sections. */\n widthSamples(samples: number): this;\n /** Set NURBS tessellation resolution. */\n resolution(resolution: number): this;\n /** Apply a product material preset. */\n material(material: ProductMaterial): this;\n /** Apply a simple color override. */\n color(color: string): this;\n /** Build a conformal ribbon as a thin NURBS surface solid. */\n build(options?: ProductRibbonBuildOptions): Shape;\n /**\n * Build a conformal ribbon and return surface-feature diagnostics.\n *\n * Use this while validating API usage or model fidelity; diagnostics report sampling counts,\n * side-span clamping, lowering mode, and warnings that should be visible in reviews.\n */\n buildWithDiagnostics(options?: ProductRibbonBuildOptions): ProductRibbonResult;\n /** Return diagnostics from the most recent build, if this builder has been built. */\n diagnostics(): ProductRibbonDiagnostics | undefined;\n private applyOptions;\n private centerDesiredNormal;\n private samplePathQuery;\n private buildSkinGrid;\n private buildRefGrid;\n private makeDiagnostics;\n private cloneDiagnostics;\n}\ndeclare const Product: {\n /** Start a named product skin builder. */\n skin(name: string): ProductSkinBuilder;\n /** Start a named cross-section station for Product.skin(...).stations(...). */\n station(name: string): ProductStationBuilder;\n /** Namespaced rail builders for product skin guide rails and handle spines. */\n rail: {\n bezier(points: Vec3[], options?: {\n name?: string;\n }): ProductRailSpec;\n nurbs(points: Vec3[], options?: {\n degree?: number;\n name?: string;\n }): ProductRailSpec;\n polyline(points: Vec3[], options?: {\n name?: string;\n }): ProductRailSpec;\n };\n /** Product profile helper namespace: oval, superEllipse, roundedRect, and circle — for stations, panels, trims, and openings. */\n profiles: {\n /** Create a centered oval profile using full product width and depth dimensions. */\n oval(width: number, depth: number, options?: ProductProfileOptions): Sketch;\n /** Create a centered superellipse profile for soft square-to-oval product sections. */\n superEllipse(width: number, depth: number, options?: ProductSuperEllipseOptions): Sketch;\n /** Create a centered rounded-rectangle product profile. */\n roundedRect(width: number, depth: number, radius: number): Sketch;\n /** Create a centered circular product profile from its diameter. */\n circle(diameter: number, options?: ProductProfileOptions): Sketch;\n };\n /** Namespaced product material presets for molded plastic, rubber, metal, and transparent parts. */\n materials: {\n mattePlastic(color?: string): ProductMaterial;\n softRubber(options?: ColorMaterialOptions): ProductMaterial;\n clearPolycarbonate(options?: ClearMaterialOptions): ProductMaterial;\n brushedSteel(options?: ColorMaterialOptions): ProductMaterial;\n };\n /** Apply a product material preset to a Shape. */\n applyMaterial(shape: Shape, preset: ProductMaterial | undefined): Shape;\n /** Apply an opinionated scene preset for product review renders. */\n scenePreset(name: ProductScenePreset): void;\n /**\n * Create a centered oval profile from full width/depth dimensions.\n *\n * @softDeprecated Product.profiles.oval(width, depth, options) — identical arguments\n * @deprecated use Product.profiles.oval(width, depth, options) — identical arguments\n */\n ovalProfile: (width: number, depth: number, options?: ProductProfileOptions) => Sketch;\n /**\n * Create a centered rounded-rectangle profile.\n *\n * @softDeprecated Product.profiles.roundedRect(width, depth, radius) — identical arguments\n * @deprecated use Product.profiles.roundedRect(width, depth, radius) — identical arguments\n */\n roundedRectProfile: (width: number, depth: number, radius: number) => Sketch;\n /**\n * Create a centered circular profile from full diameter.\n *\n * @softDeprecated Product.profiles.circle(diameter, options) — identical arguments\n * @deprecated use Product.profiles.circle(diameter, options) — identical arguments\n */\n circleProfile: (diameter: number, options?: ProductProfileOptions) => Sketch;\n /**\n * Create a centered superellipse profile for soft-square product sections.\n *\n * @softDeprecated Product.profiles.superEllipse(width, depth, options) — identical arguments\n * @deprecated use Product.profiles.superEllipse(width, depth, options) — identical arguments\n */\n superEllipseProfile: (width: number, depth: number, options?: ProductSuperEllipseOptions) => Sketch;\n /** Measure the width and depth of a 2D profile sketch. */\n profileSize(sketch: Sketch): {\n width: number;\n depth: number;\n };\n /** Describe a custom sketch as a product profile. */\n describeProfile(sketch: Sketch, kind?: ProductProfileKind, radius?: number): ProductProfileDescriptor;\n /** Scale an existing profile sketch to a target width/depth. */\n scaleProfileTo(sketch: Sketch, width: number, depth: number): Sketch;\n /** Create an ad-hoc ProductSurfaceRef from a skin and side/u/v query. */\n ref(skin: ProductSkin, query: ProductSkinRefQuery): ProductSurfaceRef;\n /**\n * Create a fluent surface helper for refs and conformal features on one side of a skin.\n *\n * Equivalent to skin.surface(side), useful when writing in Product.* namespace style.\n */\n surface(skin: ProductSkin, side: ProductSkinSide): ProductSurfaceBuilder;\n /** Start a panel feature builder. */\n panel(name: string): ProductPanelBuilder;\n /**\n * Start a conformal ribbon/trim builder for details that should bend with a ProductSkin.\n *\n * Call .on(skin, points) for side/u/v sampling or .fromRefs(points) for explicit surface refs,\n * then configure width, thickness, offset, sampling, material, and color before build().\n */\n ribbon(name: string): ProductRibbonBuilder;\n /**\n * Start a spout/nozzle feature builder.\n *\n * @softDeprecated Product.place(loft(sectionSketches, heights).as(\'spout\'), ref) — loft the local spout sections at origin, then place the result on the ProductSurfaceRef frame (or ref.attach(...) with offset/inset options)\n * @deprecated use Product.place(loft(sectionSketches, heights).as(\'spout\'), ref) — loft the local spout sections at origin, then place the result on the ProductSurfaceRef frame (or ref.attach(...) with offset/inset options)\n */\n spout: (name: string) => ProductSpoutBuilder;\n /**\n * Start a handle feature builder.\n *\n * @softDeprecated sweep(gripProfile, spinePoints) with spine points derived from ref.frame().point, plus Product.place(padShape, ref) for landing pads — composes loft/sweep with connector frames instead of a fixed handle archetype\n * @deprecated use sweep(gripProfile, spinePoints) with spine points derived from ref.frame().point, plus Product.place(padShape, ref) for landing pads — composes loft/sweep with connector frames instead of a fixed handle archetype\n */\n handle: (name: string) => ProductHandleBuilder;\n /** Place a shape or group on a ProductSurfaceRef. */\n place(detail: Shape | ShapeGroup, ref: ProductRefInput, options?: ProductAttachOptions): Shape | ShapeGroup;\n /** Small blended landing volume for manual structural bridges and connection proofs. */\n landing(name: string, radius?: number, material?: ProductMaterial): Shape;\n};\ntype SurfaceCarrierKind = "cylinder" | "plane" | "productSkin";\ninterface CylinderSurfaceCoordinate {\n kind?: "cylinder";\n angle: number;\n z: number;\n offset?: number;\n}\ninterface PlaneSurfaceCoordinate {\n kind?: "plane";\n x: number;\n y: number;\n offset?: number;\n}\ninterface ProductSkinSurfaceCoordinate {\n kind?: "productSkin";\n side?: ProductSkinSide;\n u?: number;\n v?: number;\n offset?: number;\n}\ntype SurfaceCoordinate = CylinderSurfaceCoordinate | PlaneSurfaceCoordinate | ProductSkinSurfaceCoordinate;\ninterface SurfaceFrame {\n point: Vec3;\n normal: Vec3;\n tangentAlong: Vec3;\n tangentAcross: Vec3;\n matrix: Mat4;\n carrier: string;\n representation: SurfaceCarrierKind | string;\n coordinate: SurfaceCoordinate;\n}\ntype SurfaceDiagnosticCode = "carrier.cylinder" | "carrier.plane" | "carrier.productSkinWarning" | "region.centerOutOfBounds" | "region.productSkinBoundaryOutOfBounds" | "region.zBoundaryOutOfBounds" | "region.boundarySelfIntersection" | "region.holeIntentRecorded" | "member.extendedToJunctionCapDeferred" | "feature.attached" | "feature.edgeRadiusNotRounded" | "feature.plateOpeningLowered" | "feature.plateCounterboreLowered" | "feature.bandOpeningLowered" | "feature.bandRibPatternLowered" | "feature.bandProfileRailsLowered" | "feature.bandCupWebLowered" | "feature.notLowered" | "join.anchorMissing" | "join.anchorAmbiguous" | "join.selectedAnchorIncomplete" | "join.insufficientOverlap" | "join.anchorsTooClose" | "join.radiusExceedsSpan" | "join.opposedNormals" | "join.multiTargetExplicit" | "join.memberUnjoined" | "join.explicitRecorded" | "join.endpointDistance" | "join.landingDistance" | "join.selectedAnchorDistance" | "join.noEndpointAnchors" | "join.autoAmbiguousSharedEndpoint" | "join.autoSharedEndpointLowered" | "join.autoNoSharedEndpoints" | "join.junctionNodeCompiled" | "join.junctionLowered" | "compiler.bandFeatureOpeningsLowered" | "compiler.bandSweepLowered" | "compiler.plateFeatureOpeningsLowered" | "compiler.platePrismLowered" | "compiler.mirroredMemberLowered" | "compiler.debugBuild";\ninterface SurfaceDiagnostic {\n category: "carrier" | "path" | "region" | "member" | "feature" | "join" | "compiler";\n level: "info" | "warning" | "error";\n message: string;\n subject?: string;\n /** Stable machine-readable repair hook. Messages may change; codes should not. */\n code?: SurfaceDiagnosticCode;\n}\ninterface SurfaceBounds {\n u?: [\n number,\n number\n ];\n v?: [\n number,\n number\n ];\n angle?: [\n number,\n number\n ];\n z?: [\n number,\n number\n ];\n x?: [\n number,\n number\n ];\n y?: [\n number,\n number\n ];\n}\ninterface SurfaceAnchor<C extends SurfaceCoordinate = SurfaceCoordinate> {\n carrier: CarrierSurface<C>;\n coordinate: C;\n frame(): SurfaceFrame;\n}\ninterface CarrierSurface<C extends SurfaceCoordinate = SurfaceCoordinate> {\n readonly name: string;\n readonly kind: SurfaceCarrierKind;\n pointAt(coordinate: C): Vec3;\n mirrorPoint?(point: Vec3): Vec3;\n frameAt(coordinate: C, tangentHint?: Vec3): SurfaceFrame;\n normalAt(coordinate: C): Vec3;\n tangentAt(coordinate: C, tangentHint?: Vec3): Vec3;\n bounds(): SurfaceBounds;\n offset(distance: number): CarrierSurface<C>;\n diagnostics(): SurfaceDiagnostic[];\n mirrorCoordinate(coordinate: C): C;\n}\ninterface SurfacePathSample<C extends SurfaceCoordinate = SurfaceCoordinate> {\n t: number;\n coordinate: C;\n point: Vec3;\n frame: SurfaceFrame;\n}\ntype AngleMode = "shortest" | "explicit";\ndeclare class SurfacePath<C extends SurfaceCoordinate = SurfaceCoordinate> {\n readonly carrier: CarrierSurface<C>;\n readonly points: C[];\n readonly closedValue: boolean;\n private readonly angleMode;\n constructor(carrier: CarrierSurface<C>, points: C[], closedValue?: boolean, angleMode?: AngleMode);\n closed(): SurfacePath<C>;\n mirror(): SurfacePath<C>;\n coordinateAt(t: number): C;\n sample(count?: number): SurfacePathSample<C>[];\n length(samples?: number): number;\n}\ndeclare class SurfacePathBuilder<C extends SurfaceCoordinate = SurfaceCoordinate> {\n readonly carrier: CarrierSurface<C>;\n private pointsValue;\n private closedValue;\n private angleMode;\n constructor(carrier: CarrierSurface<C>);\n from(coordinate: C): this;\n through(coordinate: C): this;\n to(coordinate: C): this;\n around(input: {\n z: number;\n fromAngle: number;\n toAngle: number;\n offset?: number;\n }): this;\n closed(): this;\n mirror(): SurfacePath<C>;\n build(): SurfacePath<C>;\n sample(count?: number): SurfacePathSample<C>[];\n}\ndeclare class CylinderCarrier implements CarrierSurface<CylinderSurfaceCoordinate> {\n readonly name: string;\n readonly kind: "cylinder";\n private radiusValue;\n private heightValue;\n private clearanceValue;\n private centerValue;\n constructor(name: string);\n diameter(value: number): this;\n radius(value: number): this;\n height(value: number): this;\n clearance(value: number): this;\n center(point: Vec3): this;\n path(): SurfacePathBuilder<CylinderSurfaceCoordinate>;\n anchor(angle: number, z?: number, options?: {\n offset?: number;\n }): SurfaceAnchor<CylinderSurfaceCoordinate>;\n front(options?: {\n z?: number;\n offset?: number;\n }): SurfaceAnchor<CylinderSurfaceCoordinate>;\n back(options?: {\n z?: number;\n offset?: number;\n }): SurfaceAnchor<CylinderSurfaceCoordinate>;\n left(options?: {\n z?: number;\n offset?: number;\n }): SurfaceAnchor<CylinderSurfaceCoordinate>;\n right(options?: {\n z?: number;\n offset?: number;\n }): SurfaceAnchor<CylinderSurfaceCoordinate>;\n top(options?: {\n angle?: number;\n offset?: number;\n }): SurfaceAnchor<CylinderSurfaceCoordinate>;\n bottom(options?: {\n angle?: number;\n offset?: number;\n }): SurfaceAnchor<CylinderSurfaceCoordinate>;\n pointAt(coordinate: CylinderSurfaceCoordinate): Vec3;\n mirrorPoint(point: Vec3): Vec3;\n normalAt(coordinate: CylinderSurfaceCoordinate): Vec3;\n tangentAt(coordinate: CylinderSurfaceCoordinate, tangentHint?: Vec3): Vec3;\n frameAt(coordinate: CylinderSurfaceCoordinate, tangentHint?: Vec3): SurfaceFrame;\n bounds(): SurfaceBounds;\n offset(distance: number): CylinderCarrier;\n diagnostics(): SurfaceDiagnostic[];\n mirrorCoordinate(coordinate: CylinderSurfaceCoordinate): CylinderSurfaceCoordinate;\n radiusValueWithClearance(): number;\n}\ndeclare class PlaneCarrier implements CarrierSurface<PlaneSurfaceCoordinate> {\n readonly name: string;\n readonly kind: "plane";\n private widthValue;\n private heightValue;\n private originValue;\n private normalValue;\n private xAxisValue;\n private offsetValue;\n constructor(name: string);\n size(width: number, height: number): this;\n origin(point: Vec3): this;\n normal(normal: Vec3): this;\n path(): SurfacePathBuilder<PlaneSurfaceCoordinate>;\n anchor(x?: number, y?: number, options?: {\n offset?: number;\n }): SurfaceAnchor<PlaneSurfaceCoordinate>;\n left(options?: {\n y?: number;\n offset?: number;\n }): SurfaceAnchor<PlaneSurfaceCoordinate>;\n right(options?: {\n y?: number;\n offset?: number;\n }): SurfaceAnchor<PlaneSurfaceCoordinate>;\n top(options?: {\n x?: number;\n offset?: number;\n }): SurfaceAnchor<PlaneSurfaceCoordinate>;\n bottom(options?: {\n x?: number;\n offset?: number;\n }): SurfaceAnchor<PlaneSurfaceCoordinate>;\n pointAt(coordinate: PlaneSurfaceCoordinate): Vec3;\n mirrorPoint(point: Vec3): Vec3;\n normalAt(): Vec3;\n tangentAt(coordinate: PlaneSurfaceCoordinate, tangentHint?: Vec3): Vec3;\n frameAt(coordinate: PlaneSurfaceCoordinate, tangentHint?: Vec3): SurfaceFrame;\n bounds(): SurfaceBounds;\n offset(distance: number): PlaneCarrier;\n diagnostics(): SurfaceDiagnostic[];\n mirrorCoordinate(coordinate: PlaneSurfaceCoordinate): PlaneSurfaceCoordinate;\n}\ninterface ProductSkinSideTransitionInput {\n name?: string;\n v?: number;\n offset?: number;\n}\ninterface ProductSkinSideTransition {\n name?: string;\n from: ProductSkinSurfaceCoordinate;\n to: ProductSkinSurfaceCoordinate;\n}\ninterface ProductSkinSideRouteInput {\n name?: string;\n sides: ProductSkinSide[];\n from: ProductSkinSurfaceCoordinate;\n to: ProductSkinSurfaceCoordinate;\n v?: number;\n offset?: number;\n}\ninterface ProductSkinSideRouteSegment {\n name: string;\n side: ProductSkinSide;\n from: ProductSkinSurfaceCoordinate;\n to: ProductSkinSurfaceCoordinate;\n startAnchorName?: string;\n endAnchorName?: string;\n}\ninterface ProductSkinSideRoute {\n name?: string;\n transitions: ProductSkinSideTransition[];\n segments: ProductSkinSideRouteSegment[];\n}\ndeclare class ProductSkinCarrier implements CarrierSurface<ProductSkinSurfaceCoordinate> {\n readonly skin: ProductSkin;\n readonly name: string;\n private readonly sideValue?;\n private readonly offsetValue;\n readonly kind: "productSkin";\n constructor(skin: ProductSkin, name?: string, sideValue?: ProductSkinSide | undefined, offsetValue?: number);\n surface(side: ProductSkinSide): ProductSkinCarrier;\n path(): SurfacePathBuilder<ProductSkinSurfaceCoordinate>;\n /**\n * Return matching side-local coordinates for an explicit split-member transition.\n *\n * Each SurfacePath still stays on one ProductSkin side. Use this helper to create\n * one member ending on `from`, another starting on `to`, then join named anchors.\n *\n * Rules: only adjacent `left`/`top`/`right`/`bottom` sides are supported — for\n * front/rear caps use `Product.panel()`. `v` is normalized 0–1 along the shared\n * boundary (default 0.5); `name` must be non-empty when provided; `offset` lifts\n * both coordinates off the surface. Throws if the returned boundary coordinates\n * are not physically coincident — check side order, `v`, and `offset`.\n */\n sideTransition(fromSide: ProductSkinSide, toSide: ProductSkinSide, input?: ProductSkinSideTransitionInput): ProductSkinSideTransition;\n /**\n * Return a sequence of matching side-local coordinates for an explicit multi-side split-member route.\n *\n * Each adjacent side pair becomes one named transition. Build one member per side\n * segment, add transition anchors at each returned pair, then join the anchors.\n * The same validation as `sideTransition()` applies to every adjacent pair.\n */\n sideTransitionChain(sides: ProductSkinSide[], input?: ProductSkinSideTransitionInput): ProductSkinSideTransition[];\n /**\n * Return side-local member segments for a generated multi-side split-member route.\n *\n * The route still compiles as explicit members plus named-anchor joins. This\n * helper only generates the per-side segment endpoints and transition names.\n */\n sideRoute(input: ProductSkinSideRouteInput): ProductSkinSideRoute;\n pointAt(coordinate: ProductSkinSurfaceCoordinate): Vec3;\n mirrorPoint(point: Vec3): Vec3;\n normalAt(coordinate: ProductSkinSurfaceCoordinate): Vec3;\n tangentAt(coordinate: ProductSkinSurfaceCoordinate, tangentHint?: Vec3): Vec3;\n frameAt(coordinate: ProductSkinSurfaceCoordinate, tangentHint?: Vec3): SurfaceFrame;\n bounds(): SurfaceBounds;\n offset(distance: number): ProductSkinCarrier;\n diagnostics(): SurfaceDiagnostic[];\n mirrorCoordinate(coordinate: ProductSkinSurfaceCoordinate): ProductSkinSurfaceCoordinate;\n}\ntype MemberFeatureType = "roundedSlot" | "roundedCutout" | "counterbore" | "lip" | "cup" | "ribPattern";\ninterface MemberFeature {\n type: MemberFeatureType;\n name?: string;\n length?: number;\n width?: number;\n diameter?: number;\n counterboreDiameter?: number;\n clearanceDiameter?: number;\n height?: number;\n depth?: number;\n count?: number;\n along?: number;\n across?: number;\n verticalTravel?: number;\n}\ndeclare class RoundedSlotBuilder {\n private readonly lengthValue;\n private readonly widthValue;\n private alongValue?;\n private acrossValue?;\n private verticalTravelValue;\n constructor(lengthValue: number, widthValue: number);\n verticalTravel(value: number): this;\n at(input: {\n along?: number;\n across?: number;\n z?: number;\n }): this;\n named(name: string): MemberFeature;\n toFeature(name?: string): MemberFeature;\n}\ndeclare class CounterboreBuilder {\n private readonly counterboreDiameterValue;\n private readonly clearanceDiameterValue;\n private readonly depthValue;\n private alongValue?;\n private acrossValue?;\n constructor(counterboreDiameterValue: number, clearanceDiameterValue: number, depthValue: number);\n at(input: {\n along?: number;\n across?: number;\n z?: number;\n }): this;\n named(name: string): MemberFeature;\n toFeature(name?: string): MemberFeature;\n}\n/** Legacy single-function namespace — kept runtime-alive for existing scripts. */\ndeclare const Slot: {\n /**\n * Create a rounded member-local slot feature.\n *\n * @softDeprecated SurfaceMembers.roundedSlot({ length, width }) — same builder, same fluent chain\n * @deprecated use SurfaceMembers.roundedSlot({ length, width }) — same builder, same fluent chain\n */\n rounded: (input: {\n length: number;\n width: number;\n }) => RoundedSlotBuilder;\n};\n/** Legacy single-function namespace — kept runtime-alive for existing scripts. */\ndeclare const Counterbore: {\n /**\n * Create a cylindrical member-local counterbore feature.\n *\n * @softDeprecated SurfaceMembers.counterbore({ diameter, clearanceDiameter, depth }) — same builder, same fluent chain\n * @deprecated use SurfaceMembers.counterbore({ diameter, clearanceDiameter, depth }) — same builder, same fluent chain\n */\n cylindrical: (input: {\n diameter: number;\n clearanceDiameter: number;\n depth: number;\n }) => CounterboreBuilder;\n};\n/** Legacy single-function namespace — kept runtime-alive for existing scripts. */\ndeclare const Ribs: {\n /**\n * Create repeated ribs that belong to a surface member before lowering.\n *\n * @softDeprecated SurfaceMembers.ribs({ count, height }) — same feature object\n * @deprecated use SurfaceMembers.ribs({ count, height }) — same feature object\n */\n repeated: (input: {\n count: number;\n height: number;\n }) => MemberFeature;\n};\ntype MemberOutwardDirection = "outside" | "inside";\ninterface MemberSectionStation {\n t: number;\n width?: number;\n thickness?: number;\n}\ninterface MemberSectionInput {\n width?: number;\n thickness: number;\n edgeRadius?: number;\n direction?: MemberOutwardDirection;\n material?: ProductMaterial;\n stations?: MemberSectionStation[];\n}\ninterface MemberSection {\n width?: number;\n thickness: number;\n edgeRadius: number;\n direction: MemberOutwardDirection;\n material?: ProductMaterial;\n stations: MemberSectionStation[];\n}\ntype SurfaceMemberAnchorRole = "start" | "end" | "center" | "landing" | "feature" | "boundary" | "explicit";\ninterface SurfaceMemberAnchorIR {\n role: SurfaceMemberAnchorRole;\n kind: "member-end" | "member-center" | "member-landing" | "member-boundary" | "plate-edge" | "plate-center" | "plate-landing" | "feature-center" | "surface-coordinate";\n name?: string;\n point: Vec3;\n normal?: Vec3;\n}\ninterface SurfaceMemberIR {\n name: string;\n kind: SurfaceMemberKind;\n section: MemberSection;\n features: MemberFeature[];\n mirrorOf?: string;\n anchors?: SurfaceMemberAnchorIR[];\n}\ninterface SurfaceJoinIR {\n from: string;\n to: string[];\n fromAnchor?: string;\n toAnchor?: string;\n radius?: number;\n style?: string;\n priority?: number;\n continuity?: string;\n name?: string;\n}\ninterface SurfaceBodyIR {\n name: string;\n carrier?: string;\n members: SurfaceMemberIR[];\n joins: SurfaceJoinIR[];\n diagnostics: SurfaceDiagnostic[];\n}\ninterface MemberMesh {\n vertices: Vec3[];\n triangles: Array<[\n number,\n number,\n number\n ]>;\n}\ntype SurfaceBandCap = "square" | "round" | "tapered" | "extended-to-junction";\ntype WidthEasing = "linear" | "easeInOut";\ntype WidthProfile = number | ((t: number) => number) | {\n stations: Array<{\n t: number;\n width: number;\n }>;\n easing?: WidthEasing;\n} | {\n from: number;\n to: number;\n easing?: WidthEasing;\n};\ninterface SurfaceBandBoundarySample {\n t: number;\n left: Vec3;\n right: Vec3;\n center: Vec3;\n width: number;\n}\ninterface SurfaceBandHoleInput {\n length: number;\n width: number;\n along?: number;\n across?: number;\n}\ninterface SurfaceBandHoleRegion {\n name: string;\n kind: "rounded-slot";\n centerAcross: number;\n centerAlong: number;\n length: number;\n width: number;\n loop: Array<[\n number,\n number\n ]>;\n}\ninterface SurfaceRegion {\n readonly kind: string;\n boundaries(samples?: number): SurfaceBandBoundarySample[];\n /** Member-local holes or cut regions recorded on this surface region, when supported by the region type. */\n holes?(): SurfaceBandHoleRegion[];\n diagnostics?(samples?: number): SurfaceDiagnostic[];\n}\ndeclare class SurfaceBand<C extends SurfaceCoordinate = SurfaceCoordinate> implements SurfaceRegion {\n readonly centerPath: SurfacePath<C>;\n readonly widthProfile: WidthProfile;\n readonly capStyle: SurfaceBandCap;\n readonly kind = "band";\n private holeInputs;\n constructor(centerPath: SurfacePath<C>, widthProfile: WidthProfile, capStyle?: SurfaceBandCap);\n widthAt(t: number): number;\n boundaries(samples?: number): SurfaceBandBoundarySample[];\n /** Return a new band with a named member-local rounded-slot hole region recorded as inspectable intent. */\n withHole(name: string, input: SurfaceBandHoleInput): SurfaceBand<C>;\n /** Resolve recorded hole regions into member-local across/along loops. */\n holes(): SurfaceBandHoleRegion[];\n diagnostics(samples?: number): SurfaceDiagnostic[];\n}\ndeclare function surfaceBand<C extends SurfaceCoordinate>(path: SurfacePath<C> | SurfacePathBuilder<C>, width: WidthProfile, cap?: SurfaceBandCap): SurfaceBand<C>;\ntype SurfaceMemberKind = "band" | "plate";\ninterface SurfaceMemberExplicitAnchor<C extends SurfaceCoordinate = SurfaceCoordinate> {\n name: string;\n coordinate: C;\n carrierName?: string;\n}\ninterface SurfaceMemberSpec<C extends SurfaceCoordinate = SurfaceCoordinate> {\n name: string;\n kind: SurfaceMemberKind;\n path?: SurfacePath<C> | SurfacePathBuilder<C>;\n anchor?: SurfaceAnchor<C>;\n size?: {\n width: number;\n height: number;\n };\n section: MemberSection;\n features: MemberFeature[];\n capStyle?: SurfaceBandCap;\n explicitAnchors?: SurfaceMemberExplicitAnchor<C>[];\n}\ninterface CompiledSurfaceMember {\n name: string;\n shape: Shape;\n mesh: MemberMesh;\n diagnostics: SurfaceDiagnostic[];\n anchors: {\n start?: CompiledSurfaceMemberAnchor;\n end?: CompiledSurfaceMemberAnchor;\n center: CompiledSurfaceMemberAnchor;\n landings?: CompiledSurfaceMemberAnchor[];\n boundaries?: CompiledSurfaceMemberAnchor[];\n features?: CompiledSurfaceMemberAnchor[];\n explicit?: CompiledSurfaceMemberAnchor[];\n };\n}\ninterface CompiledSurfaceMemberAnchor extends SurfaceMemberAnchorIR {\n frame?: SurfaceFrame;\n}\ndeclare function compileSurfaceMember<C extends SurfaceCoordinate>(input: Omit<SurfaceMemberSpec<C>, "section"> & {\n section: MemberSection | MemberSectionInput;\n}): CompiledSurfaceMember;\ninterface SurfaceBodyDiagnostics {\n name: string;\n memberCount: number;\n joinCount: number;\n lowering: "directMemberMeshes";\n messages: SurfaceDiagnostic[];\n}\ninterface SurfaceBodyBuildResult {\n shape: Shape | ShapeGroup;\n diagnostics: SurfaceBodyDiagnostics;\n ir: SurfaceBodyIR;\n}\ninterface MemberRecord<C extends SurfaceCoordinate = SurfaceCoordinate> {\n spec: Partial<SurfaceMemberSpec<C>> & {\n name: string;\n };\n features: MemberFeature[];\n mirrorOf?: string;\n}\ndeclare class SurfaceJoinBuilder {\n private readonly body;\n private readonly join;\n constructor(body: SurfaceBodyBuilder, join: SurfaceJoinIR);\n /** Select named anchors on the source and target members before lowering this join. */\n betweenAnchors(fromAnchor: string, toAnchor: string): this;\n blend(input?: {\n radius?: number;\n style?: string;\n priority?: number;\n continuity?: string;\n }): SurfaceBodyBuilder;\n}\ndeclare class SurfaceMemberBuilder<C extends SurfaceCoordinate = SurfaceCoordinate> {\n private readonly body;\n private readonly record;\n constructor(body: SurfaceBodyBuilder, record: MemberRecord<C>);\n plate(): this;\n band(): this;\n at(anchor: SurfaceAnchor<C>): this;\n size(width: number, height: number): this;\n path(path: SurfacePath<C> | SurfacePathBuilder<C>): this;\n section(section: MemberSectionInput): this;\n cap(style: SurfaceBandCap): this;\n slot(name: string, feature: MemberFeature | RoundedSlotBuilder): this;\n cutout(name: string, feature: MemberFeature | RoundedSlotBuilder): this;\n counterbore(name: string, feature: MemberFeature | CounterboreBuilder): this;\n /** Add a named anchor at a carrier surface coordinate for explicit member joins. */\n anchorAt(name: string, coordinate: C | SurfaceAnchor<C>): this;\n features(features: MemberFeature | MemberFeature[]): this;\n profile(name: string, options?: {\n depth?: number;\n height?: number;\n }): this;\n mirrorOf(memberName: string): SurfaceBodyBuilder;\n member(name: string): SurfaceMemberBuilder;\n join(from: string, to: string | string[]): SurfaceJoinBuilder;\n autoJoinAtSharedAnchors(): SurfaceBodyBuilder;\n build(): Shape | ShapeGroup;\n buildWithDiagnostics(): SurfaceBodyBuildResult;\n buildDebug(): SurfaceBodyBuildResult;\n}\n/**\n * Builder for a named surface-member body. Owns named members — `band()` or `plate()` —\n * and the joins between them. Features (slots, cutouts, lips, cups, ribs) attach to a\n * member\'s local coordinate system before lowering; this is not a global boolean recipe.\n */\ndeclare class SurfaceBodyBuilder {\n readonly name: string;\n private carrierValue?;\n private records;\n private joinsValue;\n constructor(name: string);\n carrier(carrier: CarrierSurface): this;\n member(name: string): SurfaceMemberBuilder;\n /**\n * Declare a join between named members. Only a limited join set lowers to real\n * geometry: close endpoint pairs, selected named-anchor pairs (`.betweenAnchors()`),\n * and sampled band/plate landing pads. Farther, missing-anchor, or ambiguous joins\n * remain diagnostic-only intent — decompose the design into supported joins instead\n * of expecting a fallback.\n */\n join(from: string, to: string | string[]): SurfaceJoinBuilder;\n /**\n * Lower only unambiguous shared-endpoint pairs (exactly two members sharing a point)\n * into junction geometry. Shared points with more than two members produce a warning\n * diagnostic — declare explicit `.join(...)` relationships instead.\n */\n autoJoinAtSharedAnchors(): this;\n /** Build and return only the member + junction geometry. Use `buildWithDiagnostics()` for the member graph and diagnostic codes. */\n build(): Shape | ShapeGroup;\n /**\n * Build geometry plus a serializable member graph (IR) and diagnostics. Every\n * diagnostic carries a stable `code` field (e.g. `region.centerOutOfBounds`) —\n * repair loops must match on `code`, never on message prose. Clipped or crossing\n * regions and invalid joins are reported as diagnostics, never silently accepted\n * as valid geometry.\n */\n buildWithDiagnostics(): SurfaceBodyBuildResult;\n /** `buildWithDiagnostics()` plus visible debug markers: anchors, join connections/radii, band centerlines and boundary rails, frame axes, and feature outlines. */\n buildDebug(): SurfaceBodyBuildResult;\n private buildJoinDebugShapes;\n private buildPathDebugShapes;\n private buildFeatureDebugShapes;\n private debugFeatureOutlineMesh;\n private debugPlateFeatureOutlineMesh;\n private debugBandFeatureOutlineMesh;\n private debugBandRibPatternMesh;\n private debugBandProfileFeatureMesh;\n private compileMemberSpecs;\n private describeJoinAnchors;\n private lowerEndpointJunctions;\n private lowerAutoSharedEndpointJunctions;\n private resolveMemberSpecs;\n private completeRecord;\n private resolveMirror;\n private toIR;\n}\n/**\n * Start a surface-member body builder for straps, inlays, guards, braces, cuffs, and similar\n * physical members that live on a carrier surface.\n *\n * @example\n * const carrier = Carrier.cylinder(\'guard-envelope\').diameter(84).height(36).clearance(2);\n * const guard = SurfaceBody(\'simple-guard\')\n * .carrier(carrier)\n * .member(\'left-strut\')\n * .band()\n * .path(carrier.path().from({ angle: -132, z: 6 }).to({ angle: -58, z: 18 }))\n * .section({ width: 5.5, thickness: 2.8, edgeRadius: 0.6 })\n * .member(\'right-strut\')\n * .mirrorOf(\'left-strut\')\n * .member(\'front-hoop\')\n * .band()\n * .path(carrier.path().around({ z: 18, fromAngle: -58, toAngle: 58 }))\n * .section({ width: 6.2, thickness: 3, edgeRadius: 0.7 })\n * .join(\'left-strut\', \'front-hoop\').blend({ radius: 3.2 })\n * .join(\'right-strut\', \'front-hoop\').blend({ radius: 3.2 })\n * .build();\n */\ndeclare function SurfaceBody(name: string): SurfaceBodyBuilder;\n/**\n * Factory for carrier surfaces — the coordinate-and-frame owners that surface members\n * (`SurfaceBody`) live on.\n *\n * **Details**\n *\n * A carrier owns surface-local coordinates and 3D frames; members and paths are authored\n * in carrier coordinates, never in raw Cartesian math. Cylinder coordinates are\n * `{ angle, z }` with `angle` in degrees — paths handle seam wrapping, so never compute\n * positions with trig. `clearance()`/`offset()` lift geometry off the nominal surface.\n * A ProductSkin carrier path stays on one side (`left`/`right`/`top`/`bottom`); for\n * multi-side detail, split into one member per side and join them at the matching\n * side-local coordinates from `sideTransition()` / `sideTransitionChain()` / `sideRoute()`.\n *\n * **Example**\n *\n * ```ts\n * // Bottle-cage arm: a curved band on a cylinder, authored in degrees + mm\n * const bottle = Carrier.cylinder(\'bottle\').diameter(74).height(170).clearance(1.5);\n * const arm = bottle.path()\n * .from({ angle: -145, z: 18 })\n * .through({ angle: -80, z: 72 })\n * .to({ angle: -34, z: 112 });\n * ```\n */\ndeclare const Carrier: {\n /** Create an analytic cylinder carrier for bottles, limbs, tubes, guards, and cuffs. */\n cylinder(name: string): CylinderCarrier;\n /** Create an analytic plane carrier for plates and local flat construction surfaces. */\n plane(name: string): PlaneCarrier;\n /** Adapt an existing ProductSkin into the general surface-member carrier protocol. */\n productSkin(skin: ProductSkin): ProductSkinCarrier;\n /** Reserved stub for future parameterized mesh carriers; arbitrary mesh parameterization is not implemented yet. */\n mesh(name: string): never;\n /** Reserved stub for future scan/body-fit carriers; arbitrary scan parameterization is not implemented yet. */\n scan(name: string): never;\n};\ndeclare const SurfaceMembers: {\n Body: typeof SurfaceBody;\n Band: typeof SurfaceBand;\n band: typeof surfaceBand;\n compileMember: typeof compileSurfaceMember;\n /**\n * Create a rounded member-local slot feature for `SurfaceMemberBuilder.slot()`/`.cutout()`.\n *\n * Returns a fluent `RoundedSlotBuilder`: chain `.verticalTravel(mm)` to extend\n * the slot for vertical bottle-drop style insertion (travel is summed into the\n * slot length) and `.at({ along, across })` (or `{ z }`) to position it in\n * member-local coordinates.\n *\n * @example\n * const arm = body.member(\'arm\', armPath)\n * .slot(\'upper-mount-slot\', SurfaceMembers.roundedSlot({ length: 12, width: 5.7 }).verticalTravel(6).at({ z: 82 }));\n */\n roundedSlot(input: {\n length: number;\n width: number;\n }): RoundedSlotBuilder;\n /**\n * Create a cylindrical member-local counterbore feature for `SurfaceMemberBuilder.counterbore()`.\n *\n * `diameter` is the counterbore pocket diameter and must be larger than\n * `clearanceDiameter`, the through-hole for the fastener shank. Chain\n * `.at({ along, across })` (or `{ z }`) to position it in member-local\n * coordinates.\n *\n * @example\n * const strap = body.member(\'strap\', strapPath)\n * .counterbore(\'head-pocket\', SurfaceMembers.counterbore({ diameter: 9.8, clearanceDiameter: 5.7, depth: 3 }).at({ z: 58 }));\n */\n counterbore(input: {\n diameter: number;\n clearanceDiameter: number;\n depth: number;\n }): CounterboreBuilder;\n /**\n * Create a repeated-rib stiffening feature for `SurfaceMemberBuilder.features()`.\n *\n * Ribs belong to the surface member and follow its carrier-surface lowering;\n * `count` ribs of the given `height` are distributed along the member.\n *\n * @example\n * const grip = body.member(\'grip\', gripPath)\n * .features(SurfaceMembers.ribs({ count: 18, height: 0.35 }));\n */\n ribs(input: {\n count: number;\n height: number;\n }): MemberFeature;\n};\ndeclare function distance(a: Vec3, b: Vec3): number;\ndeclare function midpoint(a: Vec3, b: Vec3): Vec3;\ndeclare function lerp(a: Vec3, b: Vec3, t: number): Vec3;\ndeclare function direction(a: Vec3, b: Vec3): Vec3;\ndeclare function offset(point: Vec3, dir: Vec3, amount: number): Vec3;\ndeclare const Points: {\n /** Euclidean distance between two 3D points. */\n readonly distance: typeof distance;\n /** Center point between two 3D points. */\n readonly midpoint: typeof midpoint;\n /** Linearly interpolate between two 3D points. t=0 returns a, t=1 returns b. */\n readonly lerp: typeof lerp;\n /** Unit direction vector from a to b. Throws if a and b are the same point. */\n readonly direction: typeof direction;\n /** Move a point along a direction vector by a given amount. */\n readonly offset: typeof offset;\n /** Compute a 2D point at distance and angle (degrees) from an optional origin. */\n readonly polar: typeof polar;\n};\ninterface WoodBoardOptions {\n /** Wood species, e.g. "birch", "oak", "plywood". Default: "wood" */\n species?: string;\n /** Material description for BOM, e.g. "birch plywood". Default: species value */\n material?: string;\n /** Grain direction: "long" (along width) or "cross" (along height). Default: "long" */\n grain?: string;\n /** Color hex string for visualization. Default: "#d2b48c" (tan/wood) */\n color?: string;\n /** If false, skip automatic BOM registration. Default: true */\n autoBom?: boolean;\n}\n/**\n * A board of wood with metadata for manufacturing: grain direction, species,\n * and dimensions. The underlying geometry is a simple box.\n *\n * WoodBoard operations are immutable. Joint operations return new boards instead\n * of carving the original in-place, and transform methods preserve all metadata.\n *\n * @category Woodworking\n */\ndeclare class WoodBoard {\n /** The underlying 3D shape. */\n shape: Shape;\n /** Board width (mm) — the longer flat dimension */\n readonly width: number;\n /** Board height (mm) — the shorter flat dimension */\n readonly height: number;\n /** Board thickness (mm) */\n readonly thickness: number;\n /** Grain direction: "long" or "cross" */\n readonly grain: string;\n /** Wood species, e.g. "birch", "oak" */\n readonly species: string;\n /** Material label for BOM */\n readonly material: string;\n constructor(width: number, height: number, thickness: number, opts?: WoodBoardOptions);\n /**\n * Subtract a cutter from this board, returning a new board.\n * Used by joint functions (dado, rabbet, mortiseAndTenon).\n */\n cut(cutter: Shape): WoodBoard;\n /** Create a new WoodBoard with a pre-built shape, preserving metadata. No BOM re-registration. */\n private _withShape;\n /** Translate the board in 3D space. */\n translate(x: number, y: number, z: number): WoodBoard;\n /** Rotate the board around an axis by a given angle in degrees. */\n rotate(axis: [\n number,\n number,\n number\n ], angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): WoodBoard;\n /** Rotate the board around the X axis by a given angle in degrees. */\n rotateX(angleDeg: number): WoodBoard;\n /** Rotate the board around the Y axis by a given angle in degrees. */\n rotateY(angleDeg: number): WoodBoard;\n /** Rotate the board around the Z axis by a given angle in degrees. */\n rotateZ(angleDeg: number): WoodBoard;\n /** Mirror the board across a plane defined by its normal. */\n mirror(normal: [\n number,\n number,\n number\n ]): WoodBoard;\n /** Set the board\'s display color. */\n color(value: string): WoodBoard;\n /** Clone the board (creates an independent copy of the underlying shape). */\n clone(): WoodBoard;\n}\n/**\n * Options for a dado joint — a channel cut across the face of a board.\n * @category Woodworking\n */\ninterface DadoOptions {\n /** Distance from the bottom edge of the host to the bottom of the channel (mm). */\n fromBottom?: number;\n /** Distance from the top edge of the host to the top of the channel (mm). Alternative to fromBottom. */\n fromTop?: number;\n /** Channel depth into the board thickness (mm). Default: 1/3 of host thickness. */\n depth?: number;\n /** Fit tolerance. Default: \'snug\'. */\n fit?: "friction" | "snug" | "slip";\n /** If set, the dado stops this far from the front edge (mm), creating a stopped dado. */\n stopped?: number;\n}\ndeclare function dado(host: WoodBoard, guest: WoodBoard, opts: DadoOptions): WoodBoard;\n/**\n * Options for a rabbet joint — an L-shaped step along an edge.\n * @category Woodworking\n */\ninterface RabbetOptions {\n /** Which edge to cut the rabbet on. */\n edge: "top" | "bottom" | "left" | "right" | "back";\n /** How far into the board face the rabbet extends (mm). */\n width: number;\n /** How deep the step is cut into the thickness (mm). */\n depth: number;\n}\ndeclare function rabbet(board: WoodBoard, opts: RabbetOptions): WoodBoard;\n/**\n * Options for a mortise-and-tenon joint.\n * @category Woodworking\n */\ninterface MortiseAndTenonOptions {\n /** \'blind\' (default): mortise doesn\'t go through. \'through\': mortise goes all the way. */\n style?: "blind" | "through";\n /** Position of the mortise center along the mortise board height. */\n position?: {\n fromTop?: number;\n fromBottom?: number;\n };\n /** Tenon thickness (mm). Default: 1/3 of tenon board thickness. */\n tenonThickness?: number;\n /** Tenon width (mm). Default: 60% of tenon board height. */\n tenonWidth?: number;\n /** Tenon length (mm). Default: 2/3 of mortise board thickness for blind, full for through. */\n tenonLength?: number;\n /** Corner radius for mortise (mm). Default: 0 (square corners, hand tools). */\n cornerRadius?: number;\n /** Fit tolerance. Default: \'snug\'. */\n fit?: "friction" | "snug" | "slip" | "knockdown";\n}\ninterface MortiseAndTenonResult {\n mortiseBoard: WoodBoard;\n tenonBoard: WoodBoard;\n}\ndeclare function mortiseAndTenon(mortiseBoard: WoodBoard, tenonBoard: WoodBoard, opts?: MortiseAndTenonOptions): MortiseAndTenonResult;\n/**\n * Woodworking namespace — create boards and cut joints.\n *\n * **Boards:** `Wood.board()` creates a WoodBoard with grain, species, and BOM metadata.\n *\n * **Joints:** `Wood.dado()`, `Wood.rabbet()`, and `Wood.mortiseAndTenon()` are\n * immutable — they return new board value(s) with the joint cut applied.\n *\n * @category Woodworking\n */\ndeclare const Wood: {\n /**\n * Create a wood board with metadata for manufacturing.\n *\n * The board is a box(width, height, thickness) centered on XY, base at Z=0.\n * Width along X, height along Y, thickness along Z (0 to thickness).\n *\n * @param width - Board width in mm (X-axis).\n * @param height - Board height in mm (Y-axis).\n * @param thickness - Board thickness in mm (Z-axis).\n * @param opts - Species, grain, color, and BOM options.\n * @returns A new WoodBoard instance.\n * @category Woodworking\n */\n readonly board: (width: number, height: number, thickness: number, opts?: WoodBoardOptions) => WoodBoard;\n /**\n * Cut a dado (channel) across the face of a host board for a guest board to sit in.\n *\n * Returns a new host board with the dado cut applied.\n *\n * @category Woodworking\n */\n readonly dado: typeof dado;\n /**\n * Cut a rabbet (L-shaped step) along an edge of a board.\n *\n * Returns a new board with the rabbet cut applied.\n *\n * @category Woodworking\n */\n readonly rabbet: typeof rabbet;\n /**\n * Cut a mortise in one board and shape a tenon on another.\n *\n * Returns new boards with the mortise pocket and tenon cuts applied.\n *\n * @category Woodworking\n */\n readonly mortiseAndTenon: typeof mortiseAndTenon;\n};\n/**\n * Highlight all user-labeled faces on a shape for visual debugging.\n *\n * Shows each user-authored label name in the viewport for visual debugging.\n * Returns the shape unchanged for chaining: `return showLabels(myShape)`.\n *\n * @softDeprecated Viewport.highlight(shape, { labels: true }) — then return the shape; highlight returns void\n * @deprecated use Viewport.highlight(shape, { labels: true }) — then return the shape; highlight returns void\n */\ndeclare function showLabels(shape: Shape): Shape;\n/** Cross-section: slice a 3D shape with a plane and return the intersection as a 2D Sketch. */\ndeclare function intersectWithPlane(shape: Shape, plane: PlaneSpec): Sketch;\n/**\n * Extract the boundary profile of a named face as a 2D sketch.\n *\n * The result is returned in the face\'s local 2D coordinate system, making it convenient\n * for offsets, pocket profiles, or follow-up sketch operations driven by an existing face.\n *\n * @param shape - Source shape containing the face.\n * @param face - Named face selector to extract.\n * @returns A sketch of the face boundary in face-local coordinates.\n */\ndeclare function faceProfile(shape: Shape, face: FaceSelector): Sketch;\n/** Orthographically project a 3D shape onto a plane and return the silhouette as a 2D Sketch. */\ndeclare function projectToPlane(shape: Shape, plane: PlaneSpec): Sketch;\ninterface SheetMetalOptions {\n /**\n * Base panel dimensions. This is the flat blank before flanges are applied.\n */\n panel: {\n /** Width of the panel along the X axis. */\n width: number;\n /** Height of the panel along the Y axis. */\n height: number;\n };\n /** Sheet thickness in mm. Applied uniformly across the panel and all flanges. */\n thickness: number;\n /** Inside bend radius in mm. Must be ≥ 0. Typically 0.5–2× the sheet thickness. */\n bendRadius: number;\n /**\n * Bend allowance model used when computing the flat-pattern developed length.\n *\n * Currently only K-factor is supported. The K-factor (0–1) describes how far\n * the neutral axis sits from the inner bend surface. Typical values:\n * - Soft materials / large radius: 0.50\n * - General sheet steel: 0.42–0.44\n * - Hard materials / tight radius: 0.30–0.38\n */\n bendAllowance: {\n /** K-factor (neutral-axis offset, 0–1). */\n kFactor: number;\n };\n /**\n * Corner relief cut at each bend intersection.\n *\n * Prevents material overlap when two flanges meet at a corner. Defaults to a\n * rectangular relief sized to `bendRadius + thickness` if omitted.\n */\n cornerRelief?: {\n /** Relief shape — only `\'rect\'` is supported in v1. */\n kind?: "rect";\n /** Side length of the square relief cut in mm. */\n size: number;\n };\n}\ninterface SheetMetalFlangeOptions {\n /** Flange leg length in mm, measured from the outside of the bend to the tip. */\n length: number;\n /**\n * Bend angle in degrees (default: `90`).\n *\n * Only `90°` is supported in v1. Values other than 90 will be rejected at\n * build time.\n */\n angleDeg?: number;\n}\ninterface SheetMetalCutoutOptions {\n /** Horizontal offset within the region, measured from the region centre (mm). Default: `0`. */\n u?: number;\n /** Vertical offset within the region, measured from the region centre (mm). Default: `0`. */\n v?: number;\n /**\n * Anchor point on the sketch that aligns to `(u, v)`.\n *\n * Use `\'center\'` for most cases. For asymmetric profiles, verify orientation\n * by placing one test cutout before committing to the final position.\n *\n * Default: `\'center\'`.\n */\n selfAnchor?: Anchor;\n}\ninterface SheetMetalCutoutOp {\n region: SheetMetalPlanarRegionName;\n sketch: Sketch;\n u: number;\n v: number;\n selfAnchor: Anchor;\n}\n/**\n * An immutable sheet metal part that accumulates flanges and cutouts.\n *\n * Each mutating method returns a **new** `SheetMetalPart`; the original is\n * unchanged. The part does not produce geometry until you call `.folded()` or\n * `.flatPattern()`.\n *\n * @category Sheet Metal\n */\ndeclare class SheetMetalPart {\n private readonly model;\n private readonly cutouts;\n constructor(model: SheetMetalModel, cutouts?: readonly SheetMetalCutoutOp[]);\n /**\n * Add a 90° flange along one edge of the base panel.\n *\n * **Details**\n *\n * Each of the four edges (`\'top\'`, `\'right\'`, `\'bottom\'`, `\'left\'`) may carry\n * at most one flange. Calling `.flange()` twice for the same edge throws.\n *\n * Corner reliefs are automatically inserted at the intersections of adjacent\n * flanges. Build flanges before cutouts — validate with `.folded()` and\n * `.flatPattern()` after each addition.\n *\n * **Example**\n *\n * ```ts\n * const part = sheetMetal({ panel: { width: 100, height: 60 }, thickness: 1.5, bendRadius: 2, bendAllowance: { kFactor: 0.42 } })\n * .flange(\'top\', { length: 15 })\n * .flange(\'bottom\', { length: 15 });\n * ```\n *\n * @param edge - Which panel edge gets the flange: `\'top\' | \'right\' | \'bottom\' | \'left\'`.\n * @param options - Flange leg length and optional bend angle (only 90° in v1).\n * @returns A new `SheetMetalPart` with the flange added.\n * @category Sheet Metal\n */\n flange(edge: SheetMetalEdge, options: SheetMetalFlangeOptions): SheetMetalPart;\n /**\n * Subtract a 2D sketch cutout from a planar region of the sheet metal part.\n *\n * **Details**\n *\n * `region` must be `\'panel\'` or one of `\'flange-top\'`, `\'flange-right\'`,\n * `\'flange-bottom\'`, `\'flange-left\'` (only available once the corresponding\n * flange has been added). Cutouts inside bend regions are **not** supported\n * in v1.\n *\n * `sketch` must be an **unplaced** compile-covered 2D profile (e.g. the result\n * of `circle2d()`, `rect()`, `roundedRect()`). Passing an already-placed\n * sketch (one that has had `.onFace(...)` called on it) will throw.\n *\n * **Authoring order:** Add all flanges before adding cutouts. Add panel\n * cutouts before flange cutouts. Add one region at a time and validate with\n * `.folded()` / `.flatPattern()` after each step.\n *\n * **Example**\n *\n * ```ts\n * const part = sheetMetal({ panel: { width: 180, height: 110 }, thickness: 1.5, bendRadius: 2, bendAllowance: { kFactor: 0.42 } })\n * .flange(\'top\', { length: 18 })\n * .cutout(\'panel\', rect(72, 36), { selfAnchor: \'center\' })\n * .cutout(\'flange-top\', roundedRect(26, 10, 5), { selfAnchor: \'center\' });\n * ```\n *\n * @param region - Target planar region: `\'panel\'` or `\'flange-<edge>\'`.\n * @param sketch - Unplaced 2D sketch profile to cut through the region.\n * @param options - Optional placement offsets and self-anchor point.\n * @returns A new `SheetMetalPart` with the cutout added.\n * @category Sheet Metal\n */\n cutout(region: SheetMetalPlanarRegionName, sketch: Sketch, options?: SheetMetalCutoutOptions): SheetMetalPart;\n /**\n * Return all semantic region names currently available on this part.\n *\n * **Details**\n *\n * The returned list always includes `\'panel\'`. For every flange that has been\n * added, the list also includes the corresponding `\'flange-<edge>\'` and\n * `\'bend-<edge>\'` entries.\n *\n * Use this to discover valid targets for `.cutout()` or for querying faces\n * by region after materializing with `.folded()`.\n *\n * Defended region names:\n * `panel` | `flange-top` | `flange-right` | `flange-bottom` | `flange-left` |\n * `bend-top` | `bend-right` | `bend-bottom` | `bend-left`\n *\n * @returns Array of semantic region name strings for this part\'s current configuration.\n * @category Sheet Metal\n */\n regionNames(): SheetMetalRegionName[];\n /**\n * Materialize the 3D folded solid.\n *\n * **Details**\n *\n * Applies all flanges (bent up at their configured angles) and all registered\n * cutouts, then returns the resulting `Shape`. The shape is compiler-owned\n * and exact-exportable (STEP, IGES, etc.).\n *\n * Prefer calling `.folded()` to validate each build step before proceeding\n * to the final model.\n *\n * @returns The fully folded 3D sheet metal solid.\n * @see {@link SheetMetalPart.flatPattern} for the unfolded blank.\n * @category Sheet Metal\n */\n folded(): Shape;\n /**\n * Materialize the flat-pattern (unfolded blank) for fabrication.\n *\n * **Details**\n *\n * Unfolds all flanges using the K-factor bend allowance and lays the result\n * flat in the XY plane. Cutouts are projected into the flat geometry.\n * The returned shape is exact-exportable and ready for laser / waterjet / CNC\n * nesting workflows.\n *\n * The developed length of each bend zone is:\n * `BA = (bendRadius + kFactor × thickness) × angleDeg × π / 180`\n *\n * @returns The flat 2D blank as a 3D solid of uniform thickness.\n * @see {@link SheetMetalPart.folded} for the 3D folded shape.\n * @category Sheet Metal\n */\n flatPattern(): Shape;\n private buildOutput;\n}\n/**\n * Create a parametric sheet metal part with flanges, bend allowances, and flat-pattern unfolding.\n *\n * **Details**\n *\n * `sheetMetal()` keeps one semantic model and derives both a folded 3D solid\n * and an accurate flat pattern from it. The K-factor bend allowance is applied\n * during unfolding. This is a strict v1 subset — it does not infer sheet metal\n * from arbitrary solids.\n *\n * **Recommended authoring order:**\n * 1. Define the base panel + thickness + bend parameters.\n * 2. Chain `.flange()` calls for each edge. Validate with `.folded()` and\n * `.flatPattern()` before adding cutouts.\n * 3. Add panel cutouts, then flange cutouts one region at a time.\n * 4. Validate after each new cutout region.\n *\n * **v1 limitations:** one base panel, up to four 90° edge flanges, constant\n * thickness, explicit K-factor, rectangular corner reliefs, planar cutouts\n * only. No hems, jogs, lofted bends, non-90° flanges, or bend-region cutouts.\n *\n * **Example**\n *\n * ```ts\n * const cover = sheetMetal({\n * panel: { width: 180, height: 110 },\n * thickness: 1.5,\n * bendRadius: 2,\n * bendAllowance: { kFactor: 0.42 },\n * cornerRelief: { size: 4 },\n * })\n * .flange(\'top\', { length: 18 })\n * .flange(\'right\', { length: 18 })\n * .flange(\'bottom\', { length: 18 })\n * .flange(\'left\', { length: 18 })\n * .cutout(\'panel\', rect(72, 36), { selfAnchor: \'center\' })\n * .cutout(\'flange-right\', roundedRect(26, 10, 5), { selfAnchor: \'center\' });\n *\n * const folded = cover.folded();\n * const flat = cover.flatPattern();\n * ```\n *\n * @param options - Panel geometry, thickness, bend radius, K-factor, and optional corner relief.\n * @returns A `SheetMetalPart` builder. Call `.folded()` or `.flatPattern()` to produce geometry.\n * @category Sheet Metal\n */\ndeclare function sheetMetal(options: SheetMetalOptions): SheetMetalPart;\ninterface FingerJointOptions {\n /** Explicit finger count (must be odd, >= 3). Default: auto from length/thickness. */\n fingers?: number;\n /** Explicit finger width. Default: auto. */\n fingerWidth?: number;\n /** Extra clearance per side (mm). Default: 0. */\n clearance?: number;\n /** Laser kerf (mm). Default: 0. */\n kerf?: number;\n /** Whether edge starts with full finger or half. Default: \'full\'. */\n endStyle?: "full" | "half";\n}\ninterface FingerJointResult {\n /** Even-position finger rects (tabs for side A, slots for side B). */\n tabProfile: Sketch;\n /** Odd-position finger rects (tabs for side B, slots for side A). */\n matingProfile: Sketch;\n /** Full rectangle minus odd slot cuts. */\n slotProfile: Sketch;\n}\ninterface TabSlotOptions {\n /** Number of tabs. Default: auto (length / (4 * thickness)). */\n tabCount?: number;\n /** Tab width. Default: 2 * thickness. */\n tabWidth?: number;\n /** Extra clearance per side (mm). Default: 0. */\n clearance?: number;\n /** Laser kerf (mm). Default: 0. */\n kerf?: number;\n /** Distance from panel edges to first/last tab center. Default: thickness. */\n inset?: number;\n}\ninterface TabSlotResult {\n tabs: Sketch;\n slots: Sketch;\n}\ninterface LivingHingeOptions {\n /** Slit pattern style. Default: \'straight\'. */\n pattern?: "straight" | "serpentine";\n /** Explicit slit width (beyond kerf). Default: 0. */\n slitWidth?: number;\n /** Distance between slit rows. Default: 2 * thickness. */\n rowSpacing?: number;\n /** Length of each slit. Default: 0.7 * (length - 2 * landWidth). */\n slitLength?: number;\n /** Uncut material between slit ends and row edges. Default: 2 * thickness. */\n landWidth?: number;\n /** Target bend radius - auto-computes row spacing. Overrides rowSpacing. */\n bendRadius?: number;\n /** Material thickness (needed for bend radius calc). Default: 3. */\n thickness?: number;\n}\ninterface SnapFitOptions {\n /** Tab beam length. Default: 4 * thickness. */\n tabLength?: number;\n /** Tab beam width. Default: thickness. */\n tabWidth?: number;\n /** How much the barb protrudes beyond the beam. Default: 0.3 * thickness. */\n overhang?: number;\n /** Slot clearance per side. Default: 0.1. */\n clearance?: number;\n /** Laser kerf. Default: 0. */\n kerf?: number;\n /** Barb style. Default: \'barb\'. */\n style?: "arrow" | "barb";\n}\ninterface SnapFitResult {\n tab: Sketch;\n slot: Sketch;\n}\n/**\n * Apply kerf compensation to a complete part outline (outer boundary + holes).\n *\n * Offsets inward by half-kerf: the outer boundary shrinks and inner holes grow.\n * This is correct because the laser beam removes material on both sides of the\n * cut line.\n *\n * @softDeprecated sketch.offset(-kerf / 2) — or let FlatPart.profile(kerf) / Laser.kit({ kerf }) apply kerf compensation automatically\n * @deprecated use sketch.offset(-kerf / 2) — or let FlatPart.profile(kerf) / Laser.kit({ kerf }) apply kerf compensation automatically\n */\ndeclare function kerfCompensateOutline(sketch: Sketch, kerf: number): Sketch;\n/**\n * Apply kerf compensation to joint protrusions (tabs, fingers).\n *\n * These grow by half-kerf so they are slightly oversized and fit tightly in\n * their mating slots after the laser removes material.\n *\n * @softDeprecated sketch.offset(kerf / 2) — or let FlatPart.profile(kerf) / Laser.kit({ kerf }) apply kerf compensation automatically\n * @deprecated use sketch.offset(kerf / 2) — or let FlatPart.profile(kerf) / Laser.kit({ kerf }) apply kerf compensation automatically\n */\ndeclare function kerfCompensateTabs(sketch: Sketch, kerf: number): Sketch;\n/**\n * Apply kerf compensation to joint cutouts (slots, holes that receive tabs).\n *\n * These grow by half-kerf so tabs can fit into them after the laser removes\n * material from both sides of the slot walls.\n *\n * @softDeprecated sketch.offset(kerf / 2) — or let FlatPart.profile(kerf) / Laser.kit({ kerf }) apply kerf compensation automatically\n * @deprecated use sketch.offset(kerf / 2) — or let FlatPart.profile(kerf) / Laser.kit({ kerf }) apply kerf compensation automatically\n */\ndeclare function kerfCompensateSlots(sketch: Sketch, kerf: number): Sketch;\ninterface PartJoints {\n /** Geometry to ADD to the base profile (tabs, fingers protruding from edges). */\n additions?: Sketch[];\n /** Geometry to SUBTRACT from the base profile (slots, holes for mating tabs). */\n subtractions?: Sketch[];\n}\n/**\n * Build a kerf-compensated part profile.\n *\n * 1. Start with the base profile.\n * 2. Kerf-compensate each tab addition (grow by kerf/2), then union with base.\n * 3. Kerf-compensate each slot subtraction (grow by kerf/2), then subtract from base.\n * 4. Kerf-compensate the resulting outline (shrink by kerf/2).\n *\n * Order matters: joints modify geometry BEFORE outline compensation so the\n * final inward offset applies uniformly to the assembled profile.\n *\n * Sign conventions: the outline shrinks by kerf/2 while tab additions and slot\n * subtractions grow by kerf/2 (the beam removes material on both sides of the\n * cut line).\n *\n * @softDeprecated FlatPart.profile(kerf) / FlatPart.solid(kerf) — or Laser.kit({ kerf }), which compensates every part automatically\n * @deprecated use FlatPart.profile(kerf) / FlatPart.solid(kerf) — or Laser.kit({ kerf }), which compensates every part automatically\n */\ndeclare function kerfCompensatePart(baseProfile: Sketch, joints: PartJoints, kerf: number): Sketch;\ninterface MaterialKerfEntry {\n material: string;\n thickness: number;\n kerf: number;\n laserType: string;\n notes?: string;\n}\n/**\n * Common kerf values. Users should always test-cut to verify for their specific setup.\n *\n * @softDeprecated Laser.COMMON_KERFS\n * @deprecated use Laser.COMMON_KERFS\n */\ndeclare const COMMON_KERFS: MaterialKerfEntry[];\n/**\n * Look up kerf for a material + thickness + laser combo.\n *\n * If `laserType` is omitted, returns the first matching material + thickness\n * entry. Returns `undefined` when no match is found.\n *\n * @softDeprecated Laser.lookupKerf(material, thickness, laserType)\n * @deprecated use Laser.lookupKerf(material, thickness, laserType)\n */\ndeclare function lookupKerf(material: string, thickness: number, laserType?: string): number | undefined;\ninterface EdgeInfo {\n name: string;\n start: [\n number,\n number\n ];\n end: [\n number,\n number\n ];\n length: number;\n /** Outward-facing normal. */\n normal: [\n number,\n number\n ];\n}\ninterface FlatPartOptions {\n material?: string;\n qty?: number;\n color?: string;\n}\n/** Tracks a joint connection for assembly preview. */\ninterface JointRecord {\n type: "finger" | "tabSlot" | "snapFit";\n partA: string;\n partB: string;\n edgeA: string;\n edgeB: string;\n /** Fold angle in degrees. Default: 90. */\n foldAngle: number;\n}\ndeclare class FlatPart {\n readonly name: string;\n readonly thickness: number;\n readonly options: FlatPartOptions;\n private _baseProfile;\n private _edges;\n private _additions;\n private _subtractions;\n private _joints;\n private _partNumber;\n constructor(name: string, baseProfile: Sketch, thickness: number, edges: Map<string, EdgeInfo>, options?: FlatPartOptions);\n /** All edges as a read-only map. */\n get edges(): ReadonlyMap<string, EdgeInfo>;\n /** Look up a named edge. Throws if the edge does not exist. */\n edge(name: string): EdgeInfo;\n /** All edge names on this part. */\n edgeNames(): string[];\n /** BOM part number assigned to this flat part. */\n get partNumber(): number;\n /** Update the BOM part number for this flat part. */\n set partNumber(n: number);\n /** Joint records that attach this part to other parts in the kit. */\n get joints(): readonly JointRecord[];\n /** Requested quantity of this part in the kit. Defaults to `1`. */\n get quantity(): number;\n /** Add geometry (e.g. protruding tabs) to the part profile. */\n addGeometry(sketch: Sketch): void;\n /** Subtract geometry (e.g. slot cuts) from the part profile. */\n subtractGeometry(sketch: Sketch): void;\n /** Record a joint connection for assembly preview. */\n addJoint(record: JointRecord): void;\n /** Final 2D profile with joints and optional kerf compensation. */\n profile(kerf?: number): Sketch;\n /** 3D solid — extrude the profile by material thickness. */\n solid(kerf?: number): Shape;\n}\n/**\n * Create a rectangular flat panel with 4 named edges.\n *\n * Profile origin at bottom-left corner.\n * Edges: bottom (y=0), right (x=width), top (y=height), left (x=0).\n * Edge traversal follows CCW winding order.\n *\n * @softDeprecated Laser.panel(name, width, height, thickness, options) — identical arguments\n * @deprecated use Laser.panel(name, width, height, thickness, options) — identical arguments\n */\ndeclare function flatPanel(name: string, width: number, height: number, thickness: number, options?: FlatPartOptions): FlatPart;\n/**\n * Create a flat part from an arbitrary profile with user-named edges.\n *\n * Edge normals are computed automatically (perpendicular to direction, rotated 90deg CW).\n *\n * @softDeprecated Laser.part(name, profile, thickness, edges, options) — identical arguments\n * @deprecated use Laser.part(name, profile, thickness, edges, options) — identical arguments\n */\ndeclare function flatPart(name: string, profile: Sketch, thickness: number, edges?: Record<string, {\n start: [\n number,\n number\n ];\n end: [\n number,\n number\n ];\n}>, options?: FlatPartOptions): FlatPart;\n/**\n * Connect two parts with finger joints along specified edges.\n *\n * Adds finger geometry to partA\'s edge, cuts matching slots from partB\'s edge.\n * The joint profiles are positioned along each edge using rotation + translation.\n *\n * @softDeprecated Laser.fingerJoint(partA, edgeA, partB, edgeB, options) — identical arguments\n * @deprecated use Laser.fingerJoint(partA, edgeA, partB, edgeB, options) — identical arguments\n */\ndeclare function fingerJoint(partA: FlatPart, edgeNameA: string, partB: FlatPart, edgeNameB: string, options?: FingerJointOptions & {\n foldAngle?: number;\n}): void;\n/**\n * Connect two parts with tab-and-slot joints along specified edges.\n *\n * Adds tab geometry to partA\'s edge, cuts matching slots from partB\'s edge.\n *\n * @softDeprecated Laser.tabSlot(partA, edgeA, partB, edgeB, options) — identical arguments\n * @deprecated use Laser.tabSlot(partA, edgeA, partB, edgeB, options) — identical arguments\n */\ndeclare function tabSlot(partA: FlatPart, edgeNameA: string, partB: FlatPart, edgeNameB: string, options?: TabSlotOptions & {\n foldAngle?: number;\n}): void;\ninterface PackedPiece {\n description: string;\n material: string;\n /** Width as placed (may differ from original if rotated). */\n width: number;\n /** Height as placed. */\n height: number;\n /** Original width before placement. */\n origWidth: number;\n /** Original height before placement. */\n origHeight: number;\n x: number;\n y: number;\n rotated: boolean;\n}\ninterface PackedSheet {\n sheetIndex: number;\n material: string;\n pieces: PackedPiece[];\n sheetWidth: number;\n sheetHeight: number;\n usedArea: number;\n /** Ordered guillotine cuts to separate all pieces on this sheet. */\n cuts: GuillotineCut[];\n}\ninterface GuillotineCut {\n /** 1-based sequence number. */\n step: number;\n /** \'V\' = vertical cut (splits left/right), \'H\' = horizontal cut (splits top/bottom). */\n direction: "V" | "H";\n /** Cut line start X (mm from sheet origin). */\n x1: number;\n /** Cut line start Y (mm from sheet origin). */\n y1: number;\n /** Cut line end X (mm from sheet origin). */\n x2: number;\n /** Cut line end Y (mm from sheet origin). */\n y2: number;\n /** Cut length in mm. */\n lengthMm: number;\n}\ninterface CuttingLayoutResult {\n sheets: PackedSheet[];\n totalPieces: number;\n totalSheets: number;\n totalUsedArea: number;\n totalSheetArea: number;\n wastePercent: number;\n kerf: number;\n /** Total length of all cuts across all sheets (mm). */\n totalCutLength: number;\n}\ninterface AssemblyPreviewOptions {\n /** Kerf compensation passed to each part\'s solid(). Default: 0 */\n kerf?: number;\n /** Fold amount: 0 = flat layout, 1 = fully assembled. Default: 1 */\n fold?: number;\n /** Explode distance: 0 = assembled, >0 = parts spread outward. Default: 0 */\n explode?: number;\n}\ninterface AssemblyPreviewResult {\n /** All part shapes grouped for display. */\n shapes: ShapeGroup;\n /** Individual transformed shapes keyed by part name. */\n partShapes: Map<string, Shape>;\n}\n/**\n * Generate a 3D assembly preview from flat parts and their joint records.\n *\n * The preview can fold joints partially or fully and optionally apply exploded spacing\n * so part relationships are easier to inspect visually.\n *\n * @param parts - Flat parts to assemble.\n * @param joints - Joint definitions connecting the parts.\n * @param options - Fold, explode, and kerf preview options.\n * @returns Preview shapes plus a per-part shape map.\n * @softDeprecated Laser.kit(...).assemblyPreview(options) — the kit collects joints and applies its kerf (default 0.2 mm; this standalone form defaulted kerf to 0, so use Laser.kit({ kerf: 0 }) for byte-identical preview geometry); Laser.assemblyPreview(parts, joints, options) keeps the standalone form\n * @deprecated use Laser.kit(...).assemblyPreview(options) — the kit collects joints and applies its kerf (default 0.2 mm; this standalone form defaulted kerf to 0, so use Laser.kit({ kerf: 0 }) for byte-identical preview geometry); Laser.assemblyPreview(parts, joints, options) keeps the standalone form\n */\ndeclare function assemblyPreview(parts: FlatPart[], joints: JointRecord[], options?: AssemblyPreviewOptions): AssemblyPreviewResult;\ninterface AssemblyStep {\n /** 1-based step number. */\n stepNumber: number;\n /** Human-readable instruction. */\n description: string;\n /** The part being added in this step. */\n partName: string;\n /** Part number (for cross-ref with cut sheets). */\n partNumber: number;\n /** Which existing part it connects to. */\n connectsTo: string;\n /** Joint type used. */\n jointType: "finger" | "tabSlot" | "snapFit";\n /** The edge on the new part. */\n newPartEdge: string;\n /** The edge on the existing part. */\n existingPartEdge: string;\n /** Fold angle in degrees. */\n foldAngle: number;\n /** Part names in the assembly so far (after this step). */\n assembledParts: string[];\n}\ninterface AssemblyInstructionsOptions {\n /** Part to start from. Default: part with most joint connections. */\n rootPart?: string;\n}\ninterface AssemblyInstructionsResult {\n steps: AssemblyStep[];\n /** Total number of parts in the assembly. */\n totalParts: number;\n /** Parts not connected to the joint graph (orphans). */\n orphanParts: string[];\n}\n/**\n * Generate step-by-step assembly instructions from flat parts and joints.\n *\n * Algorithm:\n * 1. Build adjacency graph from joints\n * 2. Pick root part (most connections, or user-specified)\n * 3. BFS from root, creating one step per part addition\n * 4. Each step describes: which part to add, where it connects, how to orient it\n *\n * Heuristics for step ordering:\n * - Start with the part that has the most connections (the base)\n * - Add parts that connect to already-assembled parts first (BFS order)\n * - Among candidates at the same BFS depth, prefer parts with more\n * connections to already-assembled parts (structurally stable)\n *\n * @softDeprecated Laser.kit(...).assemblyInstructions(options) — the kit collects joints for you; Laser.instructions(parts, joints, options) keeps the standalone form\n * @deprecated use Laser.kit(...).assemblyInstructions(options) — the kit collects joints for you; Laser.instructions(parts, joints, options) keeps the standalone form\n */\ndeclare function assemblyInstructions(parts: FlatPart[], joints: JointRecord[], options?: AssemblyInstructionsOptions): AssemblyInstructionsResult;\n/**\n * Format assembly instructions as a human-readable text document.\n *\n * Includes a "Step 0" preamble identifying the base part, followed by\n * numbered steps, and a note about any orphan parts.\n *\n * @softDeprecated Laser.kit(...).formatInstructions(options) — or Laser.formatInstructions(result) for the standalone form\n * @deprecated use Laser.kit(...).formatInstructions(options) — or Laser.formatInstructions(result) for the standalone form\n */\ndeclare function formatInstructions(result: AssemblyInstructionsResult): string;\ninterface LaserKitOptions {\n /** Default material label for parts that don\'t specify one. */\n material?: string;\n /** Stock sheet width in mm (default 600). */\n sheetWidth?: number;\n /** Stock sheet height in mm (default 400). */\n sheetHeight?: number;\n /** Laser kerf in mm (default 0.2). */\n kerf?: number;\n}\ninterface LaserKitBomEntry {\n partNumber: number;\n name: string;\n quantity: number;\n material: string;\n widthMm: number;\n heightMm: number;\n}\ndeclare class LaserKit {\n private _parts;\n private _nextPartNumber;\n private _options;\n constructor(options?: LaserKitOptions);\n /** Laser kerf in mm. */\n get kerf(): number;\n /** All registered parts (flat, in insertion order). */\n get parts(): readonly FlatPart[];\n /** Default material label. */\n get material(): string;\n /** Stock sheet width in mm. */\n get sheetWidth(): number;\n /** Stock sheet height in mm. */\n get sheetHeight(): number;\n /**\n * Register a flat part with this kit.\n * Assigns a sequential part number and records the quantity.\n */\n addPart(part: FlatPart, overrides?: {\n qty?: number;\n }): this;\n /** Generate nested cut sheets using guillotine bin-packing. */\n cutSheets(): CuttingLayoutResult;\n /** Bill of materials listing every part with dimensions. */\n bom(): LaserKitBomEntry[];\n /** Individual SVG string for each part profile, keyed by part name. */\n partSvgs(): Map<string, string>;\n /** Combined inventory SVG showing all parts in a labeled grid. */\n inventorySvg(): string;\n /** Collect deduplicated joints from all registered parts. */\n private _collectJoints;\n /** 3D fold-up preview of the assembled kit. */\n assemblyPreview(options?: Omit<AssemblyPreviewOptions, "kerf">): AssemblyPreviewResult;\n /** Step-by-step assembly instructions. */\n assemblyInstructions(options?: AssemblyInstructionsOptions): AssemblyInstructionsResult;\n /** Human-readable assembly instructions text. */\n formatInstructions(options?: AssemblyInstructionsOptions): string;\n}\n/**\n * Top-level factory for creating a LaserKit container.\n *\n * @softDeprecated Laser.kit(options) — identical arguments\n * @deprecated use Laser.kit(options) — identical arguments\n */\ndeclare function laserKit(options?: LaserKitOptions): LaserKit;\n/**\n * Laser-cutting namespace — flat parts, joints, kits, kerf data, and assembly previews.\n *\n * **Workflow:** create parts with `Laser.panel()` / `Laser.part()`, connect them\n * with `Laser.fingerJoint()` / `Laser.tabSlot()`, then collect them in a\n * `Laser.kit()` for BOM, sheet nesting, SVG export, and assembly previews.\n * The kit applies kerf compensation automatically from its `kerf` option.\n *\n * @category Laser Cutting\n */\ndeclare const Laser: {\n /**\n * Create a rectangular flat panel with 4 named edges.\n *\n * Profile origin at the bottom-left corner.\n * Edges: `bottom` (y=0), `right` (x=width), `top` (y=height), `left` (x=0).\n * Edge traversal follows CCW winding order.\n *\n * @param name - Unique part name used in BOM, joints, and instructions.\n * @param width - Panel width in mm (X-axis).\n * @param height - Panel height in mm (Y-axis).\n * @param thickness - Material thickness in mm.\n * @param options - Material, quantity, and color options.\n * @returns A new FlatPart with the four named edges.\n * @category Laser Cutting\n */\n readonly panel: (name: string, width: number, height: number, thickness: number, options?: FlatPartOptions) => FlatPart;\n /**\n * Create a flat part from an arbitrary 2D profile with user-named edges.\n *\n * Edge normals are computed automatically (perpendicular to the edge\n * direction, rotated 90 degrees clockwise).\n *\n * @param name - Unique part name used in BOM, joints, and instructions.\n * @param profile - 2D outline of the part.\n * @param thickness - Material thickness in mm.\n * @param edges - Named edges as `{ start: [x, y], end: [x, y] }` segments along the outline.\n * @param options - Material, quantity, and color options.\n * @returns A new FlatPart with the given named edges.\n * @category Laser Cutting\n */\n readonly part: (name: string, profile: Sketch, thickness: number, edges?: Record<string, {\n start: [\n number,\n number\n ];\n end: [\n number,\n number\n ];\n }>, options?: FlatPartOptions) => FlatPart;\n /**\n * Connect two parts with finger joints along the named edges.\n *\n * Adds finger geometry to partA\'s edge and cuts matching slots from partB\'s\n * edge; the joint is also recorded on both parts for assembly previews and\n * instructions.\n *\n * @param partA - Part that receives the protruding fingers.\n * @param edgeNameA - Edge of partA to joint along.\n * @param partB - Part that receives the mating slots.\n * @param edgeNameB - Edge of partB to joint along.\n * @param options - Finger sizing plus `foldAngle` in degrees (default: 90).\n * @category Laser Cutting\n */\n readonly fingerJoint: (partA: FlatPart, edgeNameA: string, partB: FlatPart, edgeNameB: string, options?: FingerJointOptions & {\n foldAngle?: number;\n }) => void;\n /**\n * Connect two parts with tab-and-slot joints along the named edges.\n *\n * Adds tab geometry to partA\'s edge and cuts matching slots from partB\'s\n * edge; the joint is also recorded on both parts for assembly previews and\n * instructions.\n *\n * @param partA - Part that receives the protruding tabs.\n * @param edgeNameA - Edge of partA to joint along.\n * @param partB - Part that receives the mating slots.\n * @param edgeNameB - Edge of partB to joint along.\n * @param options - Tab sizing plus `foldAngle` in degrees (default: 90).\n * @category Laser Cutting\n */\n readonly tabSlot: (partA: FlatPart, edgeNameA: string, partB: FlatPart, edgeNameB: string, options?: TabSlotOptions & {\n foldAngle?: number;\n }) => void;\n /**\n * Create a LaserKit container for a flat-pack project.\n *\n * The kit collects FlatPart instances, assigns sequential part numbers,\n * generates a bill of materials, nests parts onto cut sheets, exports SVG\n * views, and produces kerf-compensated assembly previews and step-by-step\n * instructions. Kerf compensation uses the kit\'s `kerf` option (default 0.2 mm).\n *\n * @param options - Default material, sheet size, and kerf.\n * @returns A new LaserKit.\n * @category Laser Cutting\n */\n readonly kit: (options?: LaserKitOptions) => LaserKit;\n /**\n * Generate a 3D assembly preview from flat parts and their joint records.\n *\n * Prefer `Laser.kit(...).assemblyPreview(options)` — the kit collects the\n * joint records and applies its kerf automatically. This standalone form\n * defaults `kerf` to 0.\n *\n * @param parts - Flat parts to assemble.\n * @param joints - Joint definitions connecting the parts.\n * @param options - Fold, explode, and kerf preview options.\n * @returns Preview shapes plus a per-part shape map.\n * @category Laser Cutting\n */\n readonly assemblyPreview: (parts: FlatPart[], joints: JointRecord[], options?: AssemblyPreviewOptions) => AssemblyPreviewResult;\n /**\n * Generate step-by-step assembly instructions from flat parts and joints.\n *\n * Prefer `Laser.kit(...).assemblyInstructions(options)` — the kit collects\n * the joint records for you. Steps are ordered BFS from the most-connected\n * (base) part so each new part attaches to already-assembled parts.\n *\n * @param parts - Flat parts in the kit.\n * @param joints - Joint definitions connecting the parts.\n * @param options - Root-part override.\n * @returns Ordered steps plus orphan-part diagnostics.\n * @category Laser Cutting\n */\n readonly instructions: (parts: FlatPart[], joints: JointRecord[], options?: AssemblyInstructionsOptions) => AssemblyInstructionsResult;\n /**\n * Format assembly instructions as a human-readable text document.\n *\n * Includes a "Step 0" preamble identifying the base part, followed by\n * numbered steps, and a note about any orphan parts.\n *\n * @param result - Result of `Laser.instructions()` or `kit.assemblyInstructions()`.\n * @returns Formatted plain-text instructions.\n * @category Laser Cutting\n */\n readonly formatInstructions: (result: AssemblyInstructionsResult) => string;\n /**\n * Look up kerf for a material + thickness + laser combo in `Laser.COMMON_KERFS`.\n *\n * If `laserType` is omitted, returns the first matching material + thickness\n * entry. Returns `undefined` when no match is found. Always test-cut to\n * verify kerf for a specific machine.\n *\n * @param material - Material key, e.g. `\'birch-plywood\'`, `\'mdf\'`, `\'acrylic\'`.\n * @param thickness - Material thickness in mm.\n * @param laserType - Optional laser type, e.g. `\'CO2-40W\'`.\n * @returns Full kerf width in mm, or `undefined`.\n * @category Laser Cutting\n */\n readonly lookupKerf: (material: string, thickness: number, laserType?: string) => number | undefined;\n /**\n * Common full-kerf values by material, thickness, and laser type.\n *\n * Reference data only — kerf varies per machine, lens, and focus; always\n * test-cut to verify before committing a sheet.\n *\n * @category Laser Cutting\n */\n readonly COMMON_KERFS: MaterialKerfEntry[];\n};\ninterface CurveArcOptions {\n /** Arc start point. */\n start: Vec3;\n /** Arc end point. */\n end: Vec3;\n /** Tangent direction at the start point. Magnitude is ignored. */\n tangent: Vec3;\n}\ninterface CurveBlendEndpoint {\n /** Endpoint position. */\n point: Vec3;\n /** Tangent direction at this endpoint. Magnitude is ignored. */\n tangent: Vec3;\n /** Tangent reach relative to the endpoint chord length. Default 1. */\n weight?: number;\n}\ninterface CurveBlendG2Endpoint extends CurveBlendEndpoint {\n /** Optional endpoint curvature/second-derivative vector. Default is zero. */\n curvature?: Vec3;\n}\ninterface CurveFitOptions {\n /** Polynomial degree. Default is cubic, reduced automatically for short point lists. */\n degree?: number;\n /** Maximum allowed interpolation residual in model units. Default 1e-7. */\n tolerance?: number;\n /**\n * Interpolate a closed periodic loop through the points. The loop closes from\n * the last point back to the first automatically — do not repeat the first\n * point at the end.\n */\n closed?: boolean;\n}\ntype CurveTrimInput = NurbsCurve3D | Vec3[];\ntype CurveTrimOutput<T extends CurveTrimInput> = T extends NurbsCurve3D ? NurbsCurve3D : Vec3[];\ninterface CurveHelixPath extends Curve3D {\n readonly radius: number;\n readonly pitch: number;\n readonly turns: number;\n readonly height: number;\n readonly startAngle: number;\n readonly clockwise: boolean;\n}\ninterface CurveHelixCoil {\n (options: HelixCoilOptions): Shape;\n (profile: Sketch, options: HelixCoilOptions): Shape;\n}\n/**\n * Canonical exact/smooth 3D curve constructors.\n *\n * `Curve.*` is the public home for reference curves and route centerlines that\n * feed `sweep`, `variableSweep`, route visualization, and future path consumers.\n * Standalone 3D curve constructors have been collapsed into this namespace.\n */\ndeclare const Curve: {\n /**\n * Create an exact G1 blend curve between two directed endpoints.\n *\n * The returned curve is a cubic non-rational `NurbsCurve3D`: ForgeCAD converts\n * the endpoint positions and tangents into Bezier control points, so the curve\n * can feed `sweep` and exact surface boundaries through the existing `nurbs`\n * IR rather than a sampled polyline.\n *\n * @example\n * const rail = Curve.Blend(\n * { point: [0, 0, 0], tangent: [1, 0, 0], weight: 0.8 },\n * { point: [40, 20, 8], tangent: [0, 1, 0], weight: 0.8 },\n * );\n * const tube = sweep(circle2d(2), rail);\n */\n Blend(start: CurveBlendEndpoint, end: CurveBlendEndpoint): NurbsCurve3D;\n /**\n * Create an exact G2 blend curve between two directed endpoints.\n *\n * This is the curvature-aware companion to `Curve.Blend()`. It returns a\n * degree-5 non-rational `NurbsCurve3D` that matches endpoint position,\n * tangent direction, and optional curvature/second-derivative vectors.\n *\n * @example\n * const rail = Curve.BlendG2(\n * { point: [0, 0, 0], tangent: [1, 0, 0], curvature: [0, 0.02, 0] },\n * { point: [50, 20, 0], tangent: [0, 1, 0], curvature: [-0.02, 0, 0] },\n * );\n */\n BlendG2(start: CurveBlendG2Endpoint, end: CurveBlendG2Endpoint): NurbsCurve3D;\n /**\n * Create an exact circular 3D arc from start, end, and start tangent.\n *\n * The returned curve is a rational quadratic `NurbsCurve3D`, split into\n * stable spans when needed, so it can feed `sweep` without sampling the\n * authoring intent away.\n *\n * @example\n * const rail = Curve.Arc({\n * start: [40, 0, 0],\n * end: [0, 40, 0],\n * tangent: [0, 1, 0],\n * });\n * const tube = sweep(circle2d(2), rail);\n */\n Arc(options: CurveArcOptions): NurbsCurve3D;\n /**\n * Create an exact straight 3D NURBS line segment.\n *\n * @example\n * const rail = Curve.Line([0, 0, 0], [80, 0, 15]);\n * const rib = sweep(circle2d(2), rail);\n */\n Line(start: Vec3, end: Vec3): NurbsCurve3D;\n /**\n * Create a polyline path as cloned 3D points.\n *\n * Identity helper — every path consumer accepts the raw `Vec3[]` array\n * directly, so this wrapper adds nothing over a plain point-array literal.\n *\n * @softDeprecated pass the Vec3[] points array directly — sweep(profile, points) and Curve.Trim/Reverse accept raw point arrays; use Curve.Route.fromPolyline(points) when the centerline needs bend/port metadata\n * @deprecated use pass the Vec3[] points array directly — sweep(profile, points) and Curve.Trim/Reverse accept raw point arrays; use Curve.Route.fromPolyline(points) when the centerline needs bend/port metadata\n */\n Polyline: (points: Vec3[]) => Vec3[];\n /**\n * Create a smooth Catmull-Rom spline path.\n *\n * Returns a sampled `Curve3D` approximation, which exact consumers reject\n * (`Curve.Trim`/`Curve.Reverse`, exact surface boundaries).\n *\n * @softDeprecated Curve.Fit(points) — exact NURBS interpolation through the same points (add { closed: true } for loops); unlike the sampled Catmull-Rom spline it works with Curve.Trim/Reverse, sweeps, and exact surface boundaries\n * @deprecated use Curve.Fit(points) — exact NURBS interpolation through the same points (add { closed: true } for loops); unlike the sampled Catmull-Rom spline it works with Curve.Trim/Reverse, sweeps, and exact surface boundaries\n */\n Spline: (points: Vec3[], options?: Spline3DOptions) => Curve3D;\n /**\n * Create an exact NURBS 3D curve from control points, weights, knots, and degree.\n *\n * @example\n * const rail = Curve.Nurbs([[0, 0, 0], [30, 4, 12], [60, -4, 12], [90, 0, 0]]);\n * const tube = sweep(circle2d(2), rail);\n */\n Nurbs(points: Vec3[], options?: NurbsCurve3DOptions): NurbsCurve3D;\n /**\n * Fit a non-rational NURBS curve that interpolates every input point.\n *\n * This is global B-spline interpolation, not approximate curve reduction:\n * ForgeCAD computes chord-length parameters, averaged clamped knots, solves\n * the control points, then verifies the interpolation residual against\n * `tolerance`. With `{ closed: true }` the fit is standard periodic B-spline\n * interpolation: the curve loops smoothly from the last point back to the\n * first (do not repeat the first point at the end).\n *\n * @example\n * const rail = Curve.Fit(\n * [[0, 0, 0], [20, 8, 12], [50, -4, 18], [80, 0, 0]],\n * { degree: 3, tolerance: 0.001 },\n * );\n * const tube = sweep(circle2d(2), rail);\n *\n * // Closed loop through four points — no duplicated closing point\n * const loop = Curve.Fit(\n * [[30, 0, 0], [0, 30, 0], [-30, 0, 0], [0, -30, 0]],\n * { closed: true },\n * );\n */\n Fit(points: Vec3[], options?: CurveFitOptions): NurbsCurve3D;\n /**\n * Extract an exact curve segment from normalized parameter `start` to `end`.\n *\n * `NurbsCurve3D` inputs are trimmed with exact knot insertion/subdomain\n * extraction. Polyline point arrays are trimmed by arclength over their exact\n * line segments. Sampled `Curve3D` splines are rejected until ForgeCAD has a\n * tolerance-controlled rebuild path.\n */\n Trim<T extends CurveTrimInput>(curve: T, start: number, end: number): CurveTrimOutput<T>;\n /**\n * Reverse an exact curve without changing its geometry.\n *\n * `NurbsCurve3D` inputs reverse control points, weights, and knots. Polyline\n * point arrays are cloned and reversed. Sampled `Curve3D` splines are rejected\n * until ForgeCAD has a tolerance-controlled rebuild path.\n */\n Reverse<T extends CurveTrimInput>(curve: T): CurveTrimOutput<T>;\n /**\n * Build analytic 3D line/arc routes for sweeps.\n *\n * `Curve.Route.fromPolyline()` is the canonical route API. It returns a\n * `Route3D` value object, preserving exact route segments, named port\n * frames, and the lowerable `route3d` sweep compile plan.\n *\n * @example\n * const route = Curve.Route.fromPolyline(\n * [[0, 0, 0], [0, 0, 50], [40, 0, 50]],\n * { cornerRadius: 12, startPort: \'inlet\', endPort: \'outlet\' },\n * );\n * const tube = sweep(circle2d(4), route);\n */\n Route: typeof Route3D;\n /**\n * Build helical paths and swept coils.\n *\n * `Curve.Helix` is the canonical namespace for helical paths and coils.\n * It uses the same sweep-based lowering as other curve paths.\n *\n * @example\n * const guide = Curve.Helix.path({ radius: 20, pitch: 6, turns: 4 });\n * const spring = Curve.Helix.coil({ radius: 20, pitch: 6, turns: 4, wireRadius: 1 });\n */\n Helix: {\n path(options: HelixOptions): CurveHelixPath;\n coil: CurveHelixCoil;\n };\n};\n/**\n * Import a module with optional ForgeCAD parameter overrides. Returns the module\'s exports.\n *\n * When importing a `.forge.js` file, most return values are passed through exactly as the script\n * returns them. Assembly returns have one extra composition rule: an unsolved `Assembly` is wrapped\n * as an `ImportedAssembly`, preserving `solve(state)` and `mergeInto()` across file boundaries,\n * while a returned `SolvedAssembly` stays a `SolvedAssembly`. If the script returns a metadata\n * object (e.g. `{ shape: myShape, bolts: {...} }`), the caller receives the full object —\n * renderable values and metadata together.\n *\n * **Script return contract:** a `.forge.js` script returns one of three shapes: a single\n * renderable (Shape, ShapeGroup, Sketch, SdfShape, Assembly), an array of renderables or\n * named descriptors (`{ name, shape|sketch|group }`), or a metadata object mixing renderable\n * values with plain data. When a script runs directly, renderable entries of a metadata\n * object are rendered under their key names and non-renderable entries are silently\n * skipped — both halves of the metadata contract: one return value serves the viewport\n * and `require()` callers.\n *\n * **Assembly return contract**\n *\n * | `.forge.js` return value | `require()` result |\n * |---|---|\n * | `Assembly` | `ImportedAssembly` |\n * | `SolvedAssembly` | `SolvedAssembly` |\n *\n * `ImportedAssembly` exposes default-pose helpers such as `getPart()`, `collisionReport()`, and\n * `minClearance()`. Use `solve(state)` first when inspecting a non-default pose.\n *\n * **Path rule:** Always include the file extension in relative imports: use\n * `require("./part.forge.js")` for model files and `require("./helpers.js")` for plain helper\n * modules. ForgeCAD does not apply Node-style extension inference, so `require("./part")` will\n * not find `part.forge.js` or `part.js`.\n *\n * **Parameter scoping:** Parameters declared in required files are automatically namespaced with\n * a `"filename#N / "` prefix (e.g. `"bracket.forge.js#1 / Width"`). This prevents collisions\n * when multiple files declare same-named params. Each file\'s params appear as separate sliders.\n *\n * **Parameter overrides:** When passing overrides, use the bare param name (not the scoped name).\n * Overrides are type-checked — unrecognized keys throw an error with typo suggestions.\n *\n * **Multi-file assembly pattern** — pass cross-cutting design values from the assembly to parts:\n *\n * ```js\n * // assembly.forge.js — owns cross-cutting params, passes to parts\n * const wall = param("Wall", 3);\n * const baseH = param("Base Height", 20);\n *\n * const mount = require(\'./motor-mount.forge.js\', { Wall: wall });\n * const base = require(\'./base-body.forge.js\', { Wall: wall, Height: baseH });\n * ```\n *\n * **Metadata pattern** — parts publish interface data alongside geometry:\n *\n * ```js\n * // motor-mount.forge.js\n * return { shape: mount, bolts: { dia: 5.3, pos: holePositions } };\n *\n * // base-body.forge.js\n * const mount = require(\'./motor-mount.forge.js\');\n * mount.bolts.pos // access the metadata\n * mount.shape // access the geometry\n * ```\n *\n * **Forge-aware builder module pattern** — use `.forge.js` modules for reusable\n * sketch, profile, shape, or assembly builders that need ForgeCAD runtime APIs:\n *\n * ```js\n * // profiles.forge.js — inspectable on its own, reusable through require()\n * function wheelProfile() {\n * return circle2d(40).subtract(circle2d(18));\n * }\n *\n * return {\n * preview: [{ name: \'Wheel profile\', sketch: wheelProfile() }],\n * make: { wheelProfile },\n * };\n *\n * // main.forge.js\n * const profiles = require(\'./profiles.forge.js\');\n * const wheel = profiles.make.wheelProfile().extrude(8);\n * ```\n *\n * Keep exported builders pure over top-level constants, top-level `param()` values,\n * or explicit function arguments. Do not declare new `param()` values inside an\n * exported builder if callers need `require(\'./profiles.forge.js\', { Width: 80 })`\n * overrides: import overrides are validated while the module loads, before any\n * exported builder is called. Use plain `.js` modules only for pure constants,\n * tables, math helpers, and formatting code that does not construct ForgeCAD geometry.\n *\n * **Entry detection (Node semantics):** `require.main` is the entry script\'s module\n * object, so `require.main === module` is true only in the file being run directly.\n * Part files use it to build standalone preview geometry only when opened directly —\n * importers then skip that work entirely:\n *\n * ```js\n * // part.forge.js\n * function bracket() { ... }\n * if (require.main === module) {\n * return { preview: [{ name: \'Bracket\', shape: bracket() }] }; // direct run: render it\n * }\n * return { make: { bracket } }; // imported: builders only\n * ```\n *\n * @concept import\n */\ndeclare function require$1(path: string, paramOverrides?: Record<string, number | string>): any;\n/** Namespaced file-format import helpers — the single vocabulary for bringing external geometry files into a model. @concept import */\ndeclare const Import: {\n /** Parse a DXF file and return closed 2D profile geometry as a Sketch. The result can be extruded directly. */\n dxfSketch(fileName: string, options?: DxfImportOptions): Sketch;\n /** Parse an SVG file and return it as a Sketch with options for region filtering, scaling, and simplification. */\n svgSketch(fileName: string, options?: SvgImportOptions): Sketch;\n /**\n * Import an external mesh file (STL, OBJ, 3MF).\n *\n * By default, 3MF build items are flattened into one Shape for compatibility.\n * Use `separateObjects: true` to import 3MF build items/resource objects as a\n * named ShapeGroup whose children are targetable by `forgecad ls`. Use `object`\n * to import one item by the stable ref/name reported by `forgecad run`.\n *\n * For 3MF sources, `forgecad run` prints a source-structure table with one line\n * per build item: `[3mf:build:NNN:object:N] name type=... verts=... tris=...\n * bbox=[min] → [max]`. Build items are numbered from `001`; files with no build\n * items list resource objects as `3mf:object:N` instead. Per-item bboxes reveal\n * multi-part structure — account for every substantial item before flattening.\n * Pass any listed stable ref or name as `object` to import that item alone.\n *\n * Use `sourceFrame: { up: "+Y" }` when the file was authored in a non-Z-up\n * coordinate system. ForgeCAD remains Z-up; the import is rotated so the named\n * source axis becomes ForgeCAD +Z. Supported values: `"+X"`, `"-X"`, `"+Y"`,\n * `"-Y"`, `"+Z"`, `"-Z"`.\n *\n * ```js\n * const all = Import.mesh("./assembly.3mf", { separateObjects: true });\n * const pin = all.child("Pin #001");\n * const plate = Import.mesh("./assembly.3mf", { object: "3mf:build:001:object:7" });\n * const yUpPart = Import.mesh("./part.obj", { sourceFrame: { up: "+Y" } });\n * ```\n */\n mesh(fileName: string, options?: MeshImportOptions): Shape | ShapeGroup;\n /** Import a STEP file (.step, .stp) as an exact OCCT-backed Shape. Preserves NURBS curves, B-spline surfaces, and exact topology. Requires running with the OCCT backend. Use `sourceFrame: { up: "+Y" }` to rotate Y-up source files into ForgeCAD\'s Z-up world. */\n step(fileName: string, options?: StepImportOptions): Shape;\n};\n/**\n * Parse an SVG file and return it as a Sketch.\n *\n * @softDeprecated Import.svgSketch(file, options)\n * @deprecated use Import.svgSketch(file, options)\n * @concept import\n */\ndeclare function importSvgSketch(fileName: string, options?: SvgImportOptions): Sketch;\n/**\n * Import an external mesh file (STL, OBJ, 3MF).\n *\n * Use `sourceFrame: { up: "+Y" }` to rotate a non-Z-up source file into\n * ForgeCAD\'s Z-up world.\n *\n * @softDeprecated Import.mesh(file, options)\n * @deprecated use Import.mesh(file, options)\n * @concept import\n */\ndeclare function importMesh(fileName: string, options?: MeshImportOptions): Shape | ShapeGroup;\n/**\n * Import a STEP file (.step, .stp) as an exact OCCT-backed Shape.\n *\n * Use `sourceFrame: { up: "+Y" }` to rotate a non-Z-up source file into\n * ForgeCAD\'s Z-up world.\n *\n * @softDeprecated Import.step(file)\n * @deprecated use Import.step(file)\n * @concept import\n */\ndeclare function importStep(fileName: string, options?: StepImportOptions): Shape;\n/**\n * Highlight any geometry for visual debugging in the viewport.\n *\n * Supported inputs:\n * - `string` — sketch entity ID (e.g. `\'L0\'`, `\'P0\'`, `\'C0\'`)\n * - `[x, y, z]` — 3D point\n * - `[[x1,y1,z1], [x2,y2,z2]]` — edge (line segment)\n * - `{ normal: [x,y,z], offset: number }` — plane by normal + distance from origin\n * - `{ normal: [x,y,z], point: [x,y,z] }` — plane by normal + point on plane\n * - `Shape` — highlight entire 3D shape\n * - `FaceRef` (from `shape.face(\'top\')`) — highlight as plane at face center\n * - `EdgeRef` (from `shape.edge(\'left\')`) — highlight as edge segment\n *\n * @softDeprecated Viewport.highlight(target, options) — same arguments, namespaced with the other render-only overlays\n * @deprecated use Viewport.highlight(target, options) — same arguments, namespaced with the other render-only overlays\n * @concept visual\n */\ndeclare function highlight(entityId: string, opts?: HighlightOptions): void;\ndeclare function highlight(point: [\n number,\n number,\n number\n], opts?: HighlightOptions): void;\ndeclare function highlight(edge: [\n [\n number,\n number,\n number\n ],\n [\n number,\n number,\n number\n ]\n], opts?: HighlightOptions): void;\ndeclare function highlight(plane: {\n normal: [\n number,\n number,\n number\n ];\n offset: number;\n}, opts?: HighlightOptions): void;\ndeclare function highlight(plane: {\n normal: [\n number,\n number,\n number\n ];\n point: [\n number,\n number,\n number\n ];\n}, opts?: HighlightOptions): void;\ndeclare function highlight(shape: Shape, opts?: HighlightOptions): void;\ndeclare function highlight(face: FaceRef, opts?: HighlightOptions): void;\ndeclare function highlight(edge: EdgeRef, opts?: HighlightOptions): void;\n/** All library parts. Access via `lib.xxx()` in scripts. */\ndeclare const lib: typeof partLibrary;\n\n/** Route step factories. Access via `route.line()`, `route.fillet()`, etc. */\ndeclare const route: typeof routeStepFactories;\n\n/** Connector factory. Create attachment points: `connector({...})`, `connector.male(type, {...})`, etc. */\ndeclare const connector: typeof connectorFactory;\n';
1184
+ const FORGE_TYPES = '// AUTO-GENERATED — do not edit by hand.\n// Regenerate: npm run gen:types (source: src/forge/forge-public-api.ts)\n// External type stubs (opaque — not user-facing)\ntype opentype$1 = unknown;\ntype Manifold = unknown;\n\n// Generated by dts-bundle-generator v9.5.1\n\ntype Vec3 = [\n number,\n number,\n number\n];\ntype Mat4 = [\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number\n];\ntype TransformInput = Transform | Mat4;\ntype RotateAroundToMode = "plane" | "line";\ninterface RotateAroundToOptions {\n mode?: RotateAroundToMode;\n}\ndeclare class Transform {\n private readonly m;\n private constructor();\n /** Return the identity transform. */\n static identity(): Transform;\n /** Wrap an existing `Transform` or raw 4x4 matrix as a `Transform`. */\n static from(input: TransformInput): Transform;\n /**\n * Compose transforms in chain order: `Transform.compose(a, b, c)` applies\n * `a`, then `b`, then `c` — the same left-to-right order as\n * `Transform.from(a).mul(b).mul(c)`.\n *\n * Prefer this over manual `.mul()` chains when composing 3+ transforms\n * (e.g. kinematics: `local -> childBase -> jointMotion -> jointFrame ->\n * parentWorld`); the variadic form makes the application order explicit and\n * prevents order mistakes.\n *\n * **Example**\n *\n * ```ts\n * const world = Transform.compose(childBase, jointMotion, jointFrame, parentWorld);\n * ```\n *\n * @param steps Transforms (or raw 4x4 matrices) applied left to right.\n * @returns The composed transform. With no arguments, the identity.\n */\n static compose(...steps: TransformInput[]): Transform;\n /** Create a translation transform. */\n static translation(x: number, y: number, z: number): Transform;\n /** Create a uniform or per-axis scale transform. */\n static scale(v: number | Vec3): Transform;\n /** Create a rotation around an arbitrary axis, optionally about a pivot. */\n static rotationAxis(axis: Vec3, angleDeg: number, pivot?: Vec3): Transform;\n /** Solve the rotation needed to move one point onto a target line or plane. */\n static rotateAroundTo(axis: Vec3, pivot: Vec3, movingPoint: Vec3, targetPoint: Vec3, options?: RotateAroundToOptions): Transform;\n /** Compose transforms in chain order: `a.mul(b)` applies `a`, then `b`. */\n mul(other: TransformInput): Transform;\n /** Translate after the current transform. */\n translate(x: number, y: number, z: number): Transform;\n /** Rotate after the current transform. */\n rotateAxis(axis: Vec3, angleDeg: number, pivot?: Vec3): Transform;\n /** Rotate about the X axis after the current transform (parity with `Shape.rotateX`). */\n rotateX(angleDeg: number, pivot?: Vec3): Transform;\n /** Rotate about the Y axis after the current transform (parity with `Shape.rotateY`). */\n rotateY(angleDeg: number, pivot?: Vec3): Transform;\n /** Rotate about the Z axis after the current transform (parity with `Shape.rotateZ`). */\n rotateZ(angleDeg: number, pivot?: Vec3): Transform;\n /** Scale after the current transform. */\n scale(v: number | Vec3): Transform;\n /** Return the inverse transform. */\n inverse(): Transform;\n /** Transform a point using homogeneous coordinates. */\n point(p: Vec3): Vec3;\n /** Transform a direction vector without translation. */\n vector(v: Vec3): Vec3;\n /** Return the transform as a raw 4x4 matrix array. */\n toArray(): Mat4;\n}\n/**\n * Compose transforms in chain order.\n * Equivalent to Transform.identity().mul(a).mul(b).mul(c)...\n *\n * @softDeprecated Transform.compose(a, b, ...)\n * @deprecated use Transform.compose(a, b, ...)\n */\ndeclare function composeChain(...steps: TransformInput[]): Transform;\ntype ConnectorGender = "male" | "female" | "neutral";\ninterface PortDef {\n origin: Vec3;\n axis: Vec3;\n up: Vec3;\n extent?: number;\n kind?: JointType;\n min?: number;\n max?: number;\n connectorType?: string;\n gender?: ConnectorGender;\n measurements?: Record<string, number | string>;\n}\ntype PortAlign = "middle" | "start" | "end";\ninterface PortInput {\n origin?: [\n number,\n number,\n number\n ];\n axis?: [\n number,\n number,\n number\n ];\n start?: [\n number,\n number,\n number\n ];\n end?: [\n number,\n number,\n number\n ];\n up?: [\n number,\n number,\n number\n ];\n kind?: JointType;\n min?: number;\n max?: number;\n}\ntype PortMap = Record<string, PortDef>;\ntype ConnectorGender$1 = "male" | "female" | "neutral";\ninterface ConnectorInput extends PortInput {\n connectorType?: string;\n gender?: ConnectorGender$1;\n measurements?: Record<string, number | string>;\n}\ntype ConnectorDef = PortDef;\ntype ConnectorMap = PortMap;\ninterface MatchToOptions {\n force?: boolean;\n angle?: number;\n distance?: number;\n}\n/**\n * Create a connector — a named attachment point on a shape.\n *\n * Overloads:\n * - `connector(geometry)` — bare connector (position + orientation only)\n * - `connector(type, geometry)` — typed connector for compatibility matching\n * - `connector(type, geometry, measurements)` — typed with measurement metadata\n */\ndeclare function connectorFactory(typeOrInput: string | PortInput, inputOrMeasurements?: PortInput | Record<string, number | string>, measurements?: Record<string, number | string>): ConnectorInput;\ndeclare namespace connectorFactory {\n var male: (typeOrInput: string | PortInput, inputOrMeasurements?: PortInput | Record<string, number | string>, measurements?: Record<string, number | string>) => ConnectorInput;\n var female: (typeOrInput: string | PortInput, inputOrMeasurements?: PortInput | Record<string, number | string>, measurements?: Record<string, number | string>) => ConnectorInput;\n var neutral: (typeOrInput: string | PortInput, inputOrMeasurements?: PortInput | Record<string, number | string>, measurements?: Record<string, number | string>) => ConnectorInput;\n}\ndeclare const ANCHOR3D_NAMES: readonly [\n "center",\n "front",\n "back",\n "left",\n "right",\n "top",\n "bottom",\n "front-left",\n "front-right",\n "back-left",\n "back-right",\n "top-front",\n "top-back",\n "top-left",\n "top-right",\n "bottom-front",\n "bottom-back",\n "bottom-left",\n "bottom-right",\n "top-front-left",\n "top-front-right",\n "top-back-left",\n "top-back-right",\n "bottom-front-left",\n "bottom-front-right",\n "bottom-back-left",\n "bottom-back-right"\n];\ntype Anchor3D = (typeof ANCHOR3D_NAMES)[number];\ntype SketchFace3D = "front" | "back" | "left" | "right" | "top" | "bottom";\ninterface ShapeQueryOwner {\n id: string;\n operation: string;\n}\ntype TopologyRewriteQueryOutcome = "preserved" | "split" | "merged";\ninterface CanonicalFaceQueryRef {\n kind: "canonical-face";\n face: SketchFace3D;\n owner?: ShapeQueryOwner;\n}\ninterface TrackedFaceQueryRef {\n kind: "tracked-face";\n faceName: string;\n owner?: ShapeQueryOwner;\n}\ninterface DirectFaceQueryRef {\n kind: "face-ref";\n faceName?: string;\n owner?: ShapeQueryOwner;\n}\ninterface PropagatedFaceQueryRef {\n kind: "propagated-face";\n rewriteId: string;\n outcome: TopologyRewriteQueryOutcome;\n source: FaceQueryRef;\n owner?: ShapeQueryOwner;\n}\ninterface CreatedFaceQueryRef {\n kind: "created-face";\n rewriteId: string;\n operation: string;\n slot: string;\n owner?: ShapeQueryOwner;\n}\ntype FaceQueryRef = CanonicalFaceQueryRef | TrackedFaceQueryRef | DirectFaceQueryRef | PropagatedFaceQueryRef | CreatedFaceQueryRef;\ntype EdgeQuerySelector = "edge" | "start" | "end" | "midpoint";\ninterface TrackedEdgeQueryRef {\n kind: "tracked-edge";\n edgeName: string;\n selector: EdgeQuerySelector;\n owner?: ShapeQueryOwner;\n}\ninterface DirectEdgeQueryRef {\n kind: "edge-ref";\n edgeName?: string;\n selector: EdgeQuerySelector;\n owner?: ShapeQueryOwner;\n}\ninterface PropagatedEdgeQueryRef {\n kind: "propagated-edge";\n rewriteId: string;\n outcome: TopologyRewriteQueryOutcome;\n source: EdgeQueryRef;\n selector: EdgeQuerySelector;\n owner?: ShapeQueryOwner;\n}\ninterface CreatedEdgeQueryRef {\n kind: "created-edge";\n rewriteId: string;\n operation: string;\n slot: string;\n selector: EdgeQuerySelector;\n owner?: ShapeQueryOwner;\n}\ntype EdgeQueryRef = TrackedEdgeQueryRef | DirectEdgeQueryRef | PropagatedEdgeQueryRef | CreatedEdgeQueryRef;\ntype FaceDescendantSemantic = "face" | "region" | "set";\ninterface FaceDescendantMetadata {\n kind: "single" | "face-set";\n semantic: FaceDescendantSemantic;\n memberCount: number;\n memberNames: string[];\n coplanar: boolean;\n}\ninterface ConstraintTypeMap {\n /**\n * Forces two points to occupy the same position.\n *\n * This is the most fundamental connectivity constraint — use it to join\n * line endpoints, close a polygon, or snap a point to another point.\n * Contributes **2 equations** (one per axis).\n */\n coincident: {\n a: PointId;\n b: PointId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces a line to be horizontal (parallel to the X axis).\n *\n * Both endpoints are moved to their average Y coordinate so the line\n * remains centered in place. Contributes **1 equation**: `b.y − a.y = 0`.\n */\n horizontal: {\n line: LineId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces a line to be vertical (parallel to the Y axis).\n *\n * Both endpoints are moved to their average X coordinate so the line\n * remains centered in place. Contributes **1 equation**: `b.x − a.x = 0`.\n */\n vertical: {\n line: LineId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces lines `a` and `b` to be parallel.\n *\n * The direction of `b` is rotated to match `a`\'s direction (either\n * co-directional or anti-parallel — whichever is closer to the current\n * orientation). Line `a` is treated as the reference; only `b` is moved.\n * Contributes **1 equation**: `cross(unit_a, unit_b) = 0`.\n */\n parallel: {\n a: LineId;\n b: LineId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces lines `a` and `b` to be perpendicular (90° apart).\n *\n * The direction of `b` is rotated to be ±90° from `a`, choosing the\n * sign closest to the current orientation. Line `a` is the reference;\n * only `b` is moved. Contributes **1 equation**: `dot(unit_a, unit_b) = 0`.\n */\n perpendicular: {\n a: LineId;\n b: LineId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Constrains tangency between a line and a circle, or between two circles.\n *\n * **Line–circle** (`line` + `circle`): the perpendicular distance from the\n * circle\'s center to the infinite line equals the circle\'s radius.\n *\n * **Circle–circle** (`a` + `b`): the two circles are externally tangent —\n * the distance between centers equals the sum of their radii.\n *\n * Exactly one mode must be active (provide either `line`+`circle` or `a`+`b`).\n * Contributes **1 equation**.\n */\n tangent: {\n line?: LineId;\n circle?: CircleId;\n a?: CircleId;\n b?: CircleId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces lines `a` and `b` to have the same length.\n *\n * Line `a`\'s length is used as the target; `b`\'s endpoints are scaled\n * symmetrically along `b`\'s current direction to match it.\n * Contributes **1 equation**: `|b| − |a| = 0`.\n */\n equal: {\n a: LineId;\n b: LineId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces points `a` and `b` to be mirror images of each other across\n * the infinite line through `axis`.\n *\n * When neither point is fixed, `b` is moved to the reflection of `a`.\n * When `b` is fixed, `a` is moved instead. Contributes **2 equations**\n * (one per axis): `b − reflect(a, axis) = [0, 0]`.\n */\n symmetric: {\n a: PointId;\n b: PointId;\n axis: LineId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces two circles to share the same center point.\n *\n * The centers are merged to their average position (or snapped to the fixed\n * one if either is fixed). Contributes **2 equations**\n * (one per axis): `center_b − center_a = [0, 0]`.\n */\n concentric: {\n a: CircleId;\n b: CircleId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces a point to lie on the infinite line passing through a line segment.\n *\n * The point is projected onto the line\'s infinite extension (not clamped to\n * the segment). Contributes **1 equation**: signed distance from the point\n * to the line = 0.\n */\n collinear: {\n point: PointId;\n line: LineId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Pins a point to an absolute position `(x, y)` in sketch space.\n *\n * Rust treats the point as externally pinned geometry. `equations: 0`\n * is intentional because the point\'s mobility is removed through the\n * `fixed` flag rather than by adding a residual row.\n */\n fixed: {\n point: PointId;\n x: number;\n y: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces a point to sit at the exact midpoint of a line segment.\n *\n * Rust enforces this as two scalar equations, one per axis:\n * `point - (a + b) / 2 = [0, 0]`.\n */\n midpoint: {\n point: PointId;\n line: LineId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces a point to lie exactly on the circumference of a circle.\n *\n * The point is moved radially so its distance from the center equals the\n * radius. Contributes **1 equation**: `|point − center| − radius = 0`.\n */\n pointOnCircle: {\n point: PointId;\n circle: CircleId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces a point to lie on a **bounded** line segment — not merely the\n * infinite extension of the line.\n *\n * The point is projected onto the segment and the parameter `t` is clamped\n * to `[0, 1]`. When `t` is already inside that range the behaviour is\n * identical to `collinear`. When the point would project outside the\n * segment it is snapped to the nearest endpoint instead.\n *\n * Contributes **1 equation** (like `collinear`): the point can still slide\n * along the segment, giving it one remaining degree of freedom.\n */\n pointOnLine: {\n point: PointId;\n line: LineId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the Euclidean distance between two points to `value`.\n *\n * Points are moved symmetrically along the current direction vector so the\n * center of the pair stays fixed. Contributes **1 equation**:\n * `|b − a| − value = 0`.\n */\n distance: {\n a: PointId;\n b: PointId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the length of a line segment to `value`.\n *\n * Endpoints are scaled symmetrically about the line\'s midpoint while\n * preserving its direction. Contributes **1 equation**:\n * `|b − a| − value = 0`.\n */\n length: {\n line: LineId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the angle from line `a` to line `b` to `value` degrees (CCW).\n * Line `a` is the reference; only `b` is rotated. Contributes **1 equation**.\n */\n angle: {\n a: LineId;\n b: LineId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the radius of a circle to `value`.\n *\n * Has no effect if the circle\'s `fixedRadius` flag is set.\n * Contributes **1 equation**: `radius − value = 0`.\n */\n radius: {\n circle: CircleId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the diameter of a circle to `value` (i.e. `radius = value / 2`).\n *\n * Has no effect if the circle\'s `fixedRadius` flag is set.\n * Contributes **1 equation**: `radius − value / 2 = 0`.\n */\n diameter: {\n circle: CircleId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the signed horizontal distance from point `a` to point `b` to `value`.\n *\n * The constraint is directional: `b.x − a.x = value`. A positive value places\n * `b` to the right of `a`; negative places it to the left.\n * Contributes **1 equation**.\n */\n hDistance: {\n a: PointId;\n b: PointId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the signed vertical distance from point `a` to point `b` to `value`.\n *\n * The constraint is directional: `b.y − a.y = value`. A positive value places\n * `b` above `a`; negative places it below.\n * Contributes **1 equation**.\n */\n vDistance: {\n a: PointId;\n b: PointId;\n value: number;\n };\n}\ninterface HighlightDef {\n /** Entity ID to highlight (edge, point, surface, circle, arc). */\n entityId: string;\n /** Override color (CSS color string). Default: \'#ff00ff\' (magenta). */\n color?: string;\n /** Optional label to display near the entity. */\n label?: string;\n /** When true, animate opacity between 0.5 and 1.0 for attention. */\n pulse?: boolean;\n}\ninterface HighlightOptions {\n color?: string;\n label?: string;\n pulse?: boolean;\n /** Size hint for points (radius in mm) or planes (disc radius in mm). */\n size?: number;\n /**\n * Shape inputs only: when true, annotate every user-authored labeled face\n * with its label name (one plane highlight per labeled face). The whole-shape\n * tint is added only when other visual options (`color`, `label`, `pulse`)\n * are also passed.\n */\n labels?: boolean;\n}\ndeclare const __brand: unique symbol;\ntype Brand<T, B extends string> = T & {\n readonly [__brand]: B;\n};\ntype PointId = Brand<string, "PointId">;\ntype LineId = Brand<string, "LineId">;\ntype CircleId = Brand<string, "CircleId">;\ntype ArcId = Brand<string, "ArcId">;\ntype BezierId = Brand<string, "BezierId">;\ntype ShapeId = Brand<string, "ShapeId">;\ntype GroupId = Brand<string, "GroupId">;\ninterface SketchArc {\n id: ArcId;\n /** Center point of the arc\'s circle. */\n center: PointId;\n /** Point on the arc where it begins. Must lie on the circle. */\n start: PointId;\n /** Point on the arc where it ends. Must lie on the circle. */\n end: PointId;\n /** Current solved radius — kept consistent with |center–start| and |center–end| by the solver. */\n radius: number;\n /** True → arc sweeps clockwise from start to end; false → counter-clockwise. */\n clockwise: boolean;\n /** When true, the solver treats radius as a constant (no variable, no perturbation). */\n fixedRadius: boolean;\n construction: boolean;\n /** Optional human-readable name for display. */\n name?: string;\n}\ninterface SketchShape {\n id: ShapeId;\n /** Ordered list of line IDs forming a closed polygon. */\n lines: LineId[];\n}\ninterface SketchGroupLocalPoint {\n id: PointId;\n lx: number;\n ly: number;\n}\ninterface SketchGroup {\n id: GroupId;\n /** World position of the group\'s local origin. */\n x: number;\n y: number;\n /** Rotation angle (radians) of the group frame. */\n theta: number;\n /** When true, all 3 DOF are frozen. */\n fixed: boolean;\n /** When true, θ is frozen — only translation DOF remain. */\n fixedRotation: boolean;\n /** Points in local coordinates. */\n points: SketchGroupLocalPoint[];\n /** Lines connecting local points (using their global IDs). */\n lines: {\n id: LineId;\n a: PointId;\n b: PointId;\n }[];\n}\ninterface SketchPoint {\n id: PointId;\n x: number;\n y: number;\n fixed: boolean;\n}\ninterface SketchLine {\n id: LineId;\n a: PointId;\n b: PointId;\n construction: boolean;\n /** Optional human-readable name for display (e.g. "top", "bottom"). */\n name?: string;\n}\ninterface SketchCircle {\n id: CircleId;\n center: PointId;\n radius: number;\n construction: boolean;\n fixedRadius: boolean;\n segments: number;\n /** Optional human-readable name for display. */\n name?: string;\n}\ninterface SketchBezier {\n id: BezierId;\n /** First control point (start of curve). */\n p0: PointId;\n /** Second control point (controls tangent at start). */\n p1: PointId;\n /** Third control point (controls tangent at end). */\n p2: PointId;\n /** Fourth control point (end of curve). */\n p3: PointId;\n construction: boolean;\n /** Optional human-readable name for display. */\n name?: string;\n}\ntype ProfileSegment = {\n kind: "line";\n line: LineId;\n label?: string;\n} | {\n kind: "arc";\n arc: ArcId;\n label?: string;\n} | {\n kind: "bezier";\n bezier: BezierId;\n label?: string;\n};\ntype SketchLoop = {\n type: "poly";\n points: PointId[];\n} | {\n type: "circle";\n circle: CircleId;\n}\n/** Mixed profile of line and arc segments forming a closed loop. */\n | {\n type: "profile";\n segments: ProfileSegment[];\n};\ntype AnnotationElement = \n/** Symbol placed at a specific position (geometric constraints like parallel, equal, fixed). */\n{\n kind: "symbol";\n position: [\n number,\n number\n ];\n symbol: ConstraintSymbol;\n rotation?: number;\n}\n/** Dimension line with extension lines (length, distance). */\n | {\n kind: "dimension";\n from: [\n number,\n number\n ];\n to: [\n number,\n number\n ];\n offset: number;\n value: string;\n}\n/** Angle arc between two directions (angle, absoluteAngle). */\n | {\n kind: "angle-arc";\n center: [\n number,\n number\n ];\n startAngle: number;\n endAngle: number;\n radius: number;\n value: string;\n}\n/** Fallback text label for constraints not yet migrated to annotations. */\n | {\n kind: "text";\n position: [\n number,\n number\n ];\n text: string;\n};\ntype ConstraintSymbol = "parallel" | "equal" | "perpendicular" | "horizontal" | "vertical" | "fixed" | "midpoint" | "coincident" | "collinear" | "tangent" | "concentric" | "ccw" | "symmetric";\ninterface ConstraintDisplay {\n id: string;\n type: string;\n label: string;\n /** Text position used as fallback when annotations are not defined. */\n position: [\n number,\n number\n ];\n value?: number;\n isDimension: boolean;\n /** True when the solver failed to satisfy this constraint (genuinely conflicting geometry). */\n isConflicting: boolean;\n /** True when this constraint is mathematically redundant — it duplicates an equation already\n * provided by another constraint, making the DOF count negative even though the solver converges. */\n isRedundant: boolean;\n /** For rejected constraints: why the builder rejected it (maxError, constraint params, blame). */\n rejectionReason?: string;\n /** Entity IDs referenced by this constraint (points, lines, circles, etc.). */\n entityIds: string[];\n /** Per-equation residual error for this constraint (how far off it is). */\n residual: number;\n /** Annotation elements for this constraint. Empty array → use text fallback. */\n annotations: AnnotationElement[];\n}\ninterface SurfaceDisplay {\n /** Zero-based index, largest-first by area. */\n index: number;\n /** Region area in mm². */\n area: number;\n /** Centroid of the region polygon. */\n centroid: [\n number,\n number\n ];\n /** Axis-aligned bounding box. */\n bounds: {\n min: [\n number,\n number\n ];\n max: [\n number,\n number\n ];\n };\n /** A point guaranteed to be inside the region — usable as seed for detectArrangementRegion(). */\n seed: [\n number,\n number\n ];\n /** Polygon vertices (CCW winding) for rendering the region fill. */\n polygon: [\n number,\n number\n ][];\n}\ninterface SketchConstraintMeta {\n status: "under" | "fully" | "over" | "over-redundant";\n /** Net degrees of freedom: positive = under-constrained, 0 = fully, negative = over-constrained. */\n dof: number;\n maxError: number;\n constraints: ConstraintDisplay[];\n rejected: ConstraintDisplay[];\n /** Detected surfaces from line arrangement (DCEL face detection). Empty if no closed regions. */\n surfaces: SurfaceDisplay[];\n construction: {\n lines: {\n id: string;\n a: [\n number,\n number\n ];\n b: [\n number,\n number\n ];\n }[];\n circles: {\n id: string;\n center: [\n number,\n number\n ];\n radius: number;\n }[];\n arcs: {\n id: string;\n center: [\n number,\n number\n ];\n start: [\n number,\n number\n ];\n end: [\n number,\n number\n ];\n radius: number;\n clockwise: boolean;\n }[];\n };\n /** Non-construction geometry edges rendered as solid wireframe overlay. */\n edges: {\n lines: {\n id: string;\n name?: string;\n a: [\n number,\n number\n ];\n b: [\n number,\n number\n ];\n }[];\n circles: {\n id: string;\n name?: string;\n center: [\n number,\n number\n ];\n radius: number;\n }[];\n arcs: {\n id: string;\n name?: string;\n center: [\n number,\n number\n ];\n start: [\n number,\n number\n ];\n end: [\n number,\n number\n ];\n radius: number;\n clockwise: boolean;\n }[];\n beziers: {\n id: string;\n name?: string;\n points: [\n number,\n number\n ][];\n }[];\n points: {\n id: string;\n pos: [\n number,\n number\n ];\n }[];\n };\n /** True when the solver hit its time budget before fully converging. */\n timedOut?: boolean;\n /** Opt-in solver debug artifacts returned by the Rust planner. */\n debug?: SolverDebugArtifacts;\n /** Programmatic debug highlights from user code. */\n highlights?: HighlightDef[];\n}\ninterface ConstraintDefinition {\n points: SketchPoint[];\n lines: SketchLine[];\n circles: SketchCircle[];\n arcs: SketchArc[];\n beziers: SketchBezier[];\n shapes: SketchShape[];\n /** Rigid-body groups — the solver sees 3 DOF per group instead of 2N per point. */\n groups: SketchGroup[];\n loops: SketchLoop[];\n constraints: SketchConstraint[];\n rejectedConstraints: SketchConstraint[];\n /** Maps rejected constraint ID → human-readable reason. Populated by the builder. */\n rejectionReasons?: Map<string, string>;\n}\ninterface SolveOptions {\n /** Maximum number of LM outer iterations per restart. */\n iterations?: number;\n /** Infinity-norm residual tolerance for declaring convergence. */\n tolerance?: number;\n /** Number of deterministic restart seeds used by the global solver. */\n restarts?: number;\n /** Optional projector iterations used only for initialisation, not as the main solver. */\n warmStartIterations?: number;\n /** Maximum LM step length in scaled variable space. Larger = bolder, smaller = safer. */\n maxScaledStep?: number;\n /** Skip redundancy detection (safe when topology is unchanged and previous DOF >= 0). */\n skipRedundancyCheck?: boolean;\n /** Run the targeted presolve hook for this constraint before the main solve. */\n presolveConstraintId?: string;\n /** When set and the first solve exceeds tolerance*5, retry with this many restarts. */\n fallbackRestarts?: number;\n /** Add constraints progressively with short LM solves, all in one WASM call. */\n progressive?: boolean;\n /** Wall-clock time budget in ms for the entire solve. 0 = no limit. */\n timeBudgetMs?: number;\n /** Capture a readable constructive transcript in `constraintMeta.debug`. */\n debugConstructiveTranscript?: boolean;\n /** Capture SVG snapshots for constructive steps in `constraintMeta.debug`. */\n debugSvgSnapshots?: boolean;\n}\ninterface SolverConstraintResidual {\n id: string;\n residual: number;\n}\ninterface ConstructiveTranscriptStep {\n index: number;\n label: string;\n kind: string;\n summary: string;\n outcome: string;\n localError: number;\n topResiduals: SolverConstraintResidual[];\n branch?: string;\n snapshotLabel?: string;\n}\ninterface SolverSvgSnapshot {\n label: string;\n error: number;\n svg: string;\n}\ninterface SolverDebugArtifacts {\n constructiveTranscript: ConstructiveTranscriptStep[];\n svgSnapshots: SolverSvgSnapshot[];\n}\ninterface ConstraintTypeMap {\n}\ninterface ConstraintBuilderMethods {\n moveTo(x: number, y: number): this;\n lineTo(x: number, y: number): this;\n lineH(dx: number): this;\n lineV(dy: number): this;\n lineAngled(length: number, degrees: number): this;\n arcTo(x: number, y: number, radius: number, clockwise?: boolean): this;\n arcByCenter(centerId: PointId, startId: PointId, endId: PointId, clockwise?: boolean, name?: string): ArcId;\n bezier(p0: any, p1: any, p2: any, p3: any, name?: string): BezierId;\n bezierTo(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number): this;\n blendTo(x: number, y: number, weight?: number): this;\n close(): this;\n addLoopCircle(center: PointId, radius: number, segments?: number): this;\n addLoop(points: any[]): this;\n addProfileLoop(segments: Array<{\n kind: "line";\n line: any;\n } | {\n kind: "arc";\n arc: any;\n } | {\n kind: "bezier";\n bezier: any;\n }>): this;\n horizontal(line: any): this;\n vertical(line: any): this;\n parallel(a: any, b: any): this;\n sameDirection(a: any, b: any): this;\n oppositeDirection(a: any, b: any): this;\n blockRotation(points: any[], axis?: "x" | "y"): this;\n perpendicular(a: any, b: any): this;\n tangent(a: any, b: any): this;\n equal(a: any, b: any): this;\n coincident(a: any, b: any): this;\n concentric(a: any, b: any): this;\n collinear(point: any, line: any): this;\n symmetric(a: any, b: any, axis: any): this;\n fix(point: any, x?: number, y?: number): this;\n midpoint(point: any, line: any): this;\n pointOnCircle(point: any, circle: any): this;\n pointOnLine(point: any, line: any): this;\n distance(a: any, b: any, value: number): this;\n length(line: any, value: number): this;\n angle(a: any, b: any, value: number): this;\n radius(circle: any, value: number): this;\n diameter(circle: any, value: number): this;\n hDistance(a: any, b: any, value: number): this;\n vDistance(a: any, b: any, value: number): this;\n pointLineDistance(point: any, line: any, value: number): this;\n lineDistance(a: any, b: any, value: number): this;\n absoluteAngle(line: any, value: number): this;\n equalRadius(a: any, b: any): this;\n arcLength(arc: any, value: number): this;\n lineTangentArc(line: any, arc: any, atStart: boolean): this;\n arcTangentArc(arcA: any, arcB: any, aAtStart?: boolean, bAtStart?: boolean): this;\n bezierTangentArc(bezier: any, arc: any, atBezierStart: boolean, atArcStart: boolean): this;\n smoothBlend(arc1: any, arc2: any, options?: {\n weight?: number;\n arc1End?: "start" | "end";\n arc2End?: "start" | "end";\n }): BezierId;\n shapeWidth(shape: any, value: number): this;\n shapeHeight(shape: any, value: number): this;\n shapeCentroidX(shape: any, value: number): this;\n shapeCentroidY(shape: any, value: number): this;\n shapeArea(shape: any, value: number): this;\n shapeEqualCentroid(a: any, b: any): this;\n angleBetween(a: any, b: any, value: number): this;\n ccw(...points: any[]): this;\n importPoint(pt: {\n x: number;\n y: number;\n }, fixed?: boolean): PointId;\n importLine(l: {\n start: {\n x: number;\n y: number;\n };\n end: {\n x: number;\n y: number;\n };\n }, fixed?: boolean): LineId;\n importRectangle(r: {\n vertices: [\n {\n x: number;\n y: number;\n },\n {\n x: number;\n y: number;\n },\n {\n x: number;\n y: number;\n },\n {\n x: number;\n y: number;\n }\n ];\n }, fixed?: boolean): {\n bottom: LineId;\n right: LineId;\n top: LineId;\n left: LineId;\n points: [\n PointId,\n PointId,\n PointId,\n PointId\n ];\n };\n referencePoint(x: number, y: number): PointId;\n referenceLine(x1: number, y1: number, x2: number, y2: number): LineId;\n referenceFrom(source: any, entityId: string): PointId | LineId | null;\n referenceAllFrom(source: any): {\n points: Map<string, PointId>;\n lines: Map<string, LineId>;\n };\n}\ntype SketchConstraint = {\n [K in keyof ConstraintTypeMap]: {\n id: string;\n type: K;\n } & ConstraintTypeMap[K];\n}[keyof ConstraintTypeMap];\ninterface ConstraintTypeMap {\n /**\n * Forces lines `a` and `b` to be parallel **and** separated by a signed\n * perpendicular distance of `value`.\n *\n * The distance is measured from the midpoint of `a` to the midpoint of `b`\n * along `a`\'s left-normal direction. Positive values place `b` to the left\n * of `a` (when facing `a`\'s direction).\n *\n * This constraint combines two equations:\n * 1. `cross(unit_a, unit_b) = 0` — parallelism\n * 2. `perpDist(mid_b, line_a) − value = 0` — offset distance\n *\n * Contributes **2 equations**.\n */\n lineDistance: {\n a: LineId;\n b: LineId;\n value: number;\n };\n}\n/** Exported for backward-compatibility with forge-public-api.ts */\ntype LineDistanceConstraint = {\n id: string;\n type: "lineDistance";\n} & {\n a: LineId;\n b: LineId;\n value: number;\n};\ninterface ConstraintTypeMap {\n /**\n * Sets the angle of a line from the positive X axis to exactly `value` degrees.\n * The direction is enforced as-is (a→b). Contributes **1 equation**:\n * `normalizeAngle(angle − target) = 0`.\n */\n absoluteAngle: {\n line: LineId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces two circles to have the same radius.\n *\n * Rust enforces one scalar equality: `radius_b - radius_a = 0`.\n */\n equalRadius: {\n a: CircleId;\n b: CircleId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the arc length of an arc to `value`.\n *\n * Arc length is defined as `radius × sweep`, where `sweep` is the angle\n * (in radians) from the start point to the end point in the arc\'s direction.\n * A zero-length sweep is treated as a full circle (2π).\n *\n * Rust enforces this as one scalar equation: `radius × sweep - value = 0`.\n */\n arcLength: {\n arc: ArcId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Constrains a line to be tangent to an arc at the arc\'s start or end point.\n *\n * Tangency requires the line\'s direction to be perpendicular to the arc\'s\n * radius at the contact point. Set `atStart: true` to use the arc\'s start\n * point as the tangency contact; `false` uses the end point.\n *\n * Rust enforces tangency as one scalar orthogonality equation between the\n * line direction and the chosen radius direction.\n */\n lineTangentArc: {\n line: LineId;\n arc: ArcId;\n atStart: boolean;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the X coordinate of a polygon\'s arithmetic centroid to `value`.\n *\n * All non-fixed vertices are translated horizontally by the same amount so\n * that `mean(vertices.x) = value`. The shape\'s size, proportions, and Y\n * position are unaffected. Contributes **1 equation**.\n */\n shapeCentroidX: {\n shape: ShapeId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the Y coordinate of a polygon\'s arithmetic centroid to `value`.\n *\n * All non-fixed vertices are translated vertically by the same amount so\n * that `mean(vertices.y) = value`. The shape\'s size, proportions, and X\n * position are unaffected. Contributes **1 equation**.\n */\n shapeCentroidY: {\n shape: ShapeId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the axis-aligned bounding-box width of a polygon shape to `value`.\n *\n * All non-fixed vertices are scaled horizontally (`x` only) from the\n * bounding-box center: `pt.x = cx + (pt.x − cx) × (value / width)`.\n * The shape\'s height and Y position are unaffected.\n * Contributes **1 equation**.\n */\n shapeWidth: {\n shape: ShapeId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the axis-aligned bounding-box height of a polygon shape to `value`.\n *\n * All non-fixed vertices are scaled vertically (`y` only) from the\n * bounding-box center: `pt.y = cy + (pt.y − cy) × (value / height)`.\n * The shape\'s width and X position are unaffected.\n * Contributes **1 equation**.\n */\n shapeHeight: {\n shape: ShapeId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the enclosed area of a polygon shape to `value`.\n *\n * Area is computed via the shoelace formula on the ordered vertex list.\n * All non-fixed vertices are scaled uniformly from the polygon\'s arithmetic\n * centroid: `scale = sqrt(target / current)`, so the shape\'s proportions\n * and position are preserved. Contributes **1 equation**.\n */\n shapeArea: {\n shape: ShapeId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces two shapes to share the same centroid.\n *\n * Translates the non-fixed vertices of each shape so that their arithmetic\n * centroids coincide at the mean of the two current centroids.\n * Contributes **2 equations**: `cx(a) − cx(b) = 0` and `cy(a) − cy(b) = 0`.\n */\n shapeEqualCentroid: {\n a: ShapeId;\n b: ShapeId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Constrains the signed perpendicular distance from a point to an infinite line.\n *\n * Positive `value` places the point to the **left** of the line\n * (when facing the line\'s direction from `a` to `b`). Negative places it\n * to the right. Zero is equivalent to `collinear`.\n * Contributes **1 equation**: `perpDist(point, line) − value = 0`.\n */\n pointLineDistance: {\n point: PointId;\n line: LineId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Enforces counter-clockwise winding order on a polygon defined by `points`.\n *\n * This resolves the discrete orientation ambiguity that arises when a polygon\'s\n * shape is fully determined but its mirror image also satisfies all constraints\n * (e.g. an equilateral triangle with a fixed vertex and side angle).\n *\n * Rust owns the actual branch handling and one-sided residual logic.\n * `equations: 0` is intentional: this constraint removes the mirror branch\n * without claiming an extra continuous degree of freedom.\n */\n ccw: {\n points: PointId[];\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Sets the unsigned angle between lines `a` and `b` to `value` degrees.\n *\n * Unlike `angle` (which is directional — 90 ≠ −90), this constraint\n * accepts both orientations of `b`: whichever of `+value` or `+value+180°`\n * is closer to the current direction is chosen. Use this when you care\n * about the magnitude of the angle but not the sign (e.g. "these two lines\n * are 60° apart" without specifying which side).\n *\n * Contributes **1 equation**: `sin(angleB − angleA − target) = 0`.\n */\n angleBetween: {\n a: LineId;\n b: LineId;\n value: number;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces lines `a` and `b` to point in the **same direction** (co-directional).\n *\n * Unlike `parallel` (which allows either co-directional or anti-parallel),\n * this constraint pins the relative direction: `dot(unit_a, unit_b) > 0`\n * AND `cross(unit_a, unit_b) = 0`.\n *\n * Use this with `lineDistance` when the sign of the distance matters —\n * `sameDirection` guarantees the normals point the same way, so positive\n * distance means the same physical side for both lines.\n *\n * Rust uses one continuous parallelism equation plus orientation-aware\n * branch handling to keep `b` facing the same way as `a`.\n */\n sameDirection: {\n a: LineId;\n b: LineId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Forces lines `a` and `b` to point in **opposite directions** (anti-parallel).\n *\n * Unlike `parallel` (which allows either co-directional or anti-parallel),\n * this constraint pins the relative direction: `dot(unit_a, unit_b) < 0`\n * AND `cross(unit_a, unit_b) = 0`.\n *\n * Use this with `lineDistance` when you need two lines facing each other —\n * e.g. the top of one rect facing the bottom of another.\n *\n * Rust uses one continuous parallelism equation plus orientation-aware\n * branch handling to keep `b` facing opposite to `a`.\n */\n oppositeDirection: {\n a: LineId;\n b: LineId;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Prevents 180° rotation of a polygon by ensuring the first edge\n * (p0 → p1) maintains its initial direction sign.\n *\n * For an axis-aligned rectangle [bl, br, tr, tl], this guarantees\n * `br.x > bl.x` — i.e. the "bottom" edge points rightward (+x).\n * Without this, a rect can satisfy CCW winding while being inside-out\n * (negative width AND negative height → positive area → CCW blind spot).\n *\n * Rust treats this as an orientation guard with `equations: 0`, so it\n * preserves the intended branch without claiming an extra continuous DOF.\n *\n * The constraint stores `axis: \'x\' | \'y\'` — which coordinate of the\n * first edge must increase. For rects, this is `\'x\'` (bottom goes right).\n */\n blockRotation: {\n points: PointId[];\n axis: "x" | "y";\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Constrains two arcs to be tangent (G1 smooth) at a shared junction point.\n *\n * The radius vectors of both arcs at the junction must be collinear\n * (cross product of unit radii = 0), ensuring the tangent directions match.\n *\n * Use `coincident` separately to enforce the shared endpoint.\n * Contributes **1 equation**.\n */\n arcTangentArc: {\n arcA: ArcId;\n arcB: ArcId;\n /** Use arc A\'s start (true) or end (false) as the junction. */\n aAtStart: boolean;\n /** Use arc B\'s start (true) or end (false) as the junction. */\n bAtStart: boolean;\n };\n}\ninterface ConstraintTypeMap {\n /**\n * Constrains a cubic Bezier curve to be tangent to an arc at one of their endpoints.\n *\n * The Bezier\'s tangent direction (tangent_control − tangent_base) must be\n * perpendicular to the arc\'s radius at the contact point.\n *\n * The builder resolves the Bezier entity to two point IDs:\n * - at bezier start: tangentBase=P0, tangentControl=P1\n * - at bezier end: tangentBase=P3, tangentControl=P2\n *\n * Contributes **1 equation**.\n */\n bezierTangentArc: {\n /** Point on the Bezier at the tangent end (P0 for start, P3 for end). */\n tangentBase: PointId;\n /** Control point defining tangent direction (P1 for start, P2 for end). */\n tangentControl: PointId;\n arc: ArcId;\n /** Use arc start (true) or end (false) as the contact point. */\n atArcStart: boolean;\n };\n}\ninterface ConstrainedSketchBuilder {\n /** Move the cursor to `(x, y)` and start a new profile loop. */\n moveTo(x: number, y: number): this;\n /** Draw a line from the current cursor to `(x, y)`. */\n lineTo(x: number, y: number): this;\n /** Draw a horizontal line of length `dx` from the current cursor. */\n lineH(dx: number): this;\n /** Draw a vertical line of length `dy` from the current cursor. */\n lineV(dy: number): this;\n /** Draw a line of the given `length` at `degrees` from +X. */\n lineAngled(length: number, degrees: number): this;\n /** Draw a circular arc from the current cursor to `(x, y)` with the given radius. */\n arcTo(x: number, y: number, radius: number, clockwise?: boolean): this;\n /** Create an arc from an explicit center point and endpoint IDs. */\n arcByCenter(centerId: PointId, startId: PointId, endId: PointId, clockwise?: boolean, name?: string, fixedRadius?: boolean): ArcId;\n /** Create a cubic Bezier curve from four control points. */\n bezier(p0: any, p1: any, p2: any, p3: any, name?: string): BezierId;\n /** Draw a cubic Bezier from the current cursor to `(x3, y3)`. */\n bezierTo(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number): this;\n /** Draw a smooth Bezier tangent to the previous arc. */\n blendTo(x: number, y: number, weight?: number): this;\n /** Label the current path segment. */\n label(name: string): this;\n /** Close the current path and register the loop. */\n close(): this;\n /** Add a circle loop to the path. */\n addLoopCircle(center: PointId, radius: number, segments?: number): this;\n /** Add a closed polygon loop from point IDs. */\n addLoop(points: any[]): this;\n /** Add a profile loop from prebuilt line/arc/bezier segments. */\n addProfileLoop(segments: Array<{\n kind: "line";\n line: any;\n } | {\n kind: "arc";\n arc: any;\n } | {\n kind: "bezier";\n bezier: any;\n }>): this;\n}\ninterface ConstrainedSketchBuilder {\n /** Constrain a line to be horizontal (parallel to the X axis). */\n horizontal(line: any): this;\n /** Constrain a line to be vertical (parallel to the Y axis). */\n vertical(line: any): this;\n /** Constrain two lines to be parallel. */\n parallel(a: any, b: any): this;\n /** Constrain two lines to point in the same direction. */\n sameDirection(a: any, b: any): this;\n /** Constrain two lines to point in opposite directions. */\n oppositeDirection(a: any, b: any): this;\n /** Prevent 180° rotation of a polygon by anchoring its first edge. */\n blockRotation(points: any[], axis?: "x" | "y"): this;\n /** Constrain two lines to be perpendicular. */\n perpendicular(a: any, b: any): this;\n /** Constrain a line/circle or circle/circle tangency relationship. */\n tangent(a: any, b: any): this;\n /** Constrain two lines to have equal length. */\n equal(a: any, b: any): this;\n /** Constrain two points to coincide. */\n coincident(a: any, b: any): this;\n /** Constrain two circles to share a center. */\n concentric(a: any, b: any): this;\n /** Constrain a point to lie on the infinite extension of a line. */\n collinear(point: any, line: any): this;\n /** Constrain two points to be symmetric about an axis line. */\n symmetric(a: any, b: any, axis: any): this;\n /** Pin a point at a specific world location. */\n fix(point: any, x?: number, y?: number): this;\n /** Constrain a point to lie at the midpoint of a line. */\n midpoint(point: any, line: any): this;\n /** Constrain a point to lie on the perimeter of a circle. */\n pointOnCircle(point: any, circle: any): this;\n /** Constrain a point to lie on the bounded segment of a line. */\n pointOnLine(point: any, line: any): this;\n}\ninterface ConstrainedSketchBuilder {\n /** Constrain the Euclidean distance between two points. */\n distance(a: any, b: any, value: number): this;\n /** Constrain the length of a line segment. */\n length(line: any, value: number): this;\n /** Constrain the signed angle from line `a` to line `b`. */\n angle(a: any, b: any, value: number): this;\n /** Constrain the radius of a circle. */\n radius(circle: any, value: number): this;\n /** Constrain the diameter of a circle. */\n diameter(circle: any, value: number): this;\n /** Constrain the horizontal distance between two points. */\n hDistance(a: any, b: any, value: number): this;\n /** Constrain the vertical distance between two points. */\n vDistance(a: any, b: any, value: number): this;\n /** Constrain the signed perpendicular distance from a point to a line. */\n pointLineDistance(point: any, line: any, value: number): this;\n /** Constrain the perpendicular offset distance between two lines. */\n lineDistance(a: any, b: any, value: number): this;\n /** Constrain the absolute angle of a line measured from +X. */\n absoluteAngle(line: any, value: number): this;\n /** Constrain two circles to have equal radii. */\n equalRadius(a: any, b: any): this;\n /** Constrain the arc length of an arc. */\n arcLength(arc: any, value: number): this;\n /** Constrain a line to be tangent to an arc at its start or end point. */\n lineTangentArc(line: any, arc: any, atStart: boolean): this;\n /** Constrain two arcs to be tangent at their shared junction point. */\n arcTangentArc(arcA: any, arcB: any, aAtStart?: boolean, bAtStart?: boolean): this;\n /** Constrain a Bezier to be tangent to an arc at one endpoint. */\n bezierTangentArc(bezier: any, arc: any, atBezierStart: boolean, atArcStart: boolean): this;\n /** Create a Bezier blend between two arcs. */\n smoothBlend(arc1: any, arc2: any, options?: {\n weight?: number;\n arc1End?: "start" | "end";\n arc2End?: "start" | "end";\n }): BezierId;\n /** Constrain a shape\'s width. */\n shapeWidth(shape: any, value: number): this;\n /** Constrain a shape\'s height. */\n shapeHeight(shape: any, value: number): this;\n /** Constrain a shape\'s centroid X position. */\n shapeCentroidX(shape: any, value: number): this;\n /** Constrain a shape\'s centroid Y position. */\n shapeCentroidY(shape: any, value: number): this;\n /** Constrain a shape\'s area. */\n shapeArea(shape: any, value: number): this;\n /** Constrain two shapes to have the same centroid. */\n shapeEqualCentroid(a: any, b: any): this;\n /** Constrain the unsigned angle between two lines. */\n angleBetween(a: any, b: any, value: number): this;\n /** Constrain all given points to be in counter-clockwise order. */\n ccw(...points: any[]): this;\n /**\n * Constrain the horizontal (X-axis) offset between two lines.\n * Uses the start-point of each line to measure horizontal distance.\n * `value` is the signed distance: b.startPt.x − a.startPt.x = value.\n */\n offsetX(a: any, b: any, value: number): this;\n /**\n * Constrain the vertical (Y-axis) offset between two lines.\n * Uses the start-point of each line to measure vertical distance.\n * `value` is the signed distance: b.startPt.y − a.startPt.y = value.\n */\n offsetY(a: any, b: any, value: number): this;\n}\ndeclare const SHAPE_BACKEND_MARKER: unique symbol;\ninterface ShapeRuntimeBounds {\n readonly min: [\n number,\n number,\n number\n ];\n readonly max: [\n number,\n number,\n number\n ];\n}\ninterface ShapeRuntimeMesh {\n readonly numProp: number;\n readonly numTri: number;\n readonly triVerts: Uint32Array;\n readonly vertProperties: Float32Array;\n readonly numVert?: number;\n readonly mergeFromVert?: Uint32Array;\n readonly mergeToVert?: Uint32Array;\n readonly runIndex?: Uint32Array;\n readonly runOriginalID?: Uint32Array;\n readonly runTransform?: Float32Array;\n readonly faceID?: Uint32Array | Int32Array;\n /** Face ID to semantic face name map. Required when faceID values are used outside the backend. */\n readonly faceIdNames?: string[];\n readonly halfedgeTangent?: Float32Array;\n /**\n * Precomputed per-triangle-corner analytic surface normals: 9 floats per\n * triangle (3 corners x xyz), in the same order as `triVerts`. When present,\n * the renderer uses these directly (analytic shading) instead of facet\n * auto-smoothing — a flat cap stays flat, a fillet shades with true curvature,\n * and a tangent cap/fillet seam has matching normals (no crease). Supplied by\n * the native geometry-crate backend; absent for backends that don\'t compute it.\n */\n readonly cornerNormals?: Float32Array;\n}\ntype ShapeRuntimeCrossSection = any;\ninterface EdgeNativeTopologyRef {\n backend: "truck";\n edge: number;\n}\ninterface ShapeBackend {\n readonly [SHAPE_BACKEND_MARKER]: true;\n clone(): ShapeBackend;\n translate(x: number, y: number, z: number): ShapeBackend;\n rotate(x: number, y: number, z: number): ShapeBackend;\n transform(m: Mat4): ShapeBackend;\n scale(v: number | [\n number,\n number,\n number\n ]): ShapeBackend;\n mirror(normal: [\n number,\n number,\n number\n ]): ShapeBackend;\n split(other: ShapeBackend): [\n ShapeBackend,\n ShapeBackend\n ];\n splitByPlane(normal: [\n number,\n number,\n number\n ], originOffset: number): [\n ShapeBackend,\n ShapeBackend\n ];\n trimByPlane(normal: [\n number,\n number,\n number\n ], originOffset: number): ShapeBackend;\n boundingBox(): ShapeRuntimeBounds;\n volume(): number;\n surfaceArea(): number;\n isEmpty(): boolean;\n /** Number of disconnected solid bodies in this shape. */\n numBodies(): number;\n numTri(): number;\n getMesh(): ShapeRuntimeMesh;\n slice(offset: number): ShapeRuntimeCrossSection;\n project(): ShapeRuntimeCrossSection;\n dispose?(): void;\n}\ndeclare const PROFILE_BACKEND_MARKER: unique symbol;\ninterface ProfileBounds {\n min: [\n number,\n number\n ];\n max: [\n number,\n number\n ];\n}\ninterface ProfileBackend {\n readonly [PROFILE_BACKEND_MARKER]: true;\n area(): number;\n bounds(): ProfileBounds;\n isEmpty(): boolean;\n numVert(): number;\n toPolygons(): number[][][];\n translate(x: number, y: number): ProfileBackend;\n rotate(degrees: number): ProfileBackend;\n scale(v: number | [\n number,\n number\n ]): ProfileBackend;\n mirror(ax: [\n number,\n number\n ]): ProfileBackend;\n offset(delta: number, join: "Square" | "Round" | "Miter"): ProfileBackend;\n simplify(epsilon: number): ProfileBackend;\n subtract(other: ProfileBackend): ProfileBackend;\n extrude(height: number, divisions: number, twist: number, scaleTop?: [\n number,\n number\n ]): ShapeBackend;\n revolve(segments: number, degrees: number): ShapeBackend;\n}\ntype Anchor = "center" | "top-left" | "top-right" | "bottom-left" | "bottom-right" | "top" | "bottom" | "left" | "right";\ntype SketchOperandInput = Sketch | readonly Sketch[];\n/**\n * Immutable 2D profile for extrusion, revolve, and other operations.\n *\n * **Details**\n *\n * `Sketch` wraps Manifold\'s `CrossSection` with a chainable 2D API. Every method\n * returns a new `Sketch` — the original is never mutated. Colors, edge labels, and\n * placement data are preserved through all transforms and boolean operations.\n *\n * Supported operations:\n * - **Transforms** — `translate`, `rotate`, `rotateAround`, `scale`, `mirror`\n * - **Booleans** — `add` (union), `subtract` (difference), `intersect`\n * - **Operations** — `offset`, `simplify`, `filletCorners`, `filletCorner`, `chamferCorners`, `chamferCorner`\n * - **Queries** — `area`, `bounds`, `isEmpty`, `numVert`\n * - **3D operations** — `extrude`, `revolve`, `onFace`\n * - **Regions** — `regions`, `region`\n * - **Placement** — `attachTo`\n *\n * Named anchor positions used by `attachTo()`:\n * `\'center\'` | `\'top-left\'` | `\'top-right\'` | `\'bottom-left\'` | `\'bottom-right\'`\n * | `\'top\'` | `\'bottom\'` | `\'left\'` | `\'right\'`\n */\ndeclare class Sketch {\n readonly cross: ProfileBackend;\n colorHex: string | undefined;\n constructor(cross: ProfileBackend, color?: string);\n /**\n * Set the display color of this sketch.\n *\n * **Details**\n *\n * Color is preserved through all transforms and boolean operations. Pass\n * `undefined` to clear the color.\n *\n * **Example**\n *\n * ```ts\n * circle2d(20).color(\'#ff0000\').extrude(5);\n * ```\n *\n * @param value - Hex color string (e.g. `\'#ff0000\'`) or `undefined` to clear\n * @returns A new sketch with the color set\n * @category Sketch Core\n */\n color(value: string | undefined): Sketch;\n /**\n * Create an explicit copy of this sketch for branching variants.\n *\n * **Details**\n *\n * Because all Sketch operations are immutable, `clone()` is rarely needed.\n * Use it when you want to assign the same sketch to multiple names and\n * continue modifying each independently without confusion.\n *\n * @returns A new Sketch with the same geometry and metadata\n * @see {@link duplicate} for an alias\n * @category Sketch Core\n */\n clone(): Sketch;\n /**\n * Alias for `clone()` — create an explicit copy handle for branching variants.\n *\n * @returns A new Sketch with the same geometry and metadata\n * @see {@link clone}\n * @category Sketch Core\n */\n duplicate(): Sketch;\n /**\n * Return the total filled area of the sketch.\n *\n * @returns Area in square model units\n * @category Sketch Core\n */\n area(): number;\n /**\n * Return the axis-aligned bounding box of the sketch.\n *\n * @returns `{ min: [x, y], max: [x, y] }`\n * @category Sketch Core\n */\n bounds(): ProfileBounds;\n /**\n * Return `true` if the sketch contains no filled area.\n *\n * @returns `true` when the sketch has no geometry\n * @category Sketch Core\n */\n isEmpty(): boolean;\n /**\n * Return the number of vertices in the polygon representation of the sketch contours.\n *\n * @returns Vertex count\n * @category Sketch Core\n */\n numVert(): number;\n /**\n * Return the sketch as a list of polygons matching its contour topology.\n *\n * Useful when you need raw polygon data for inspection or custom export.\n * @category Sketch Core\n */\n toPolygons(): number[][][];\n /**\n * Move the sketch by the given X and Y offset.\n *\n * @param x - Offset along X\n * @param y - Offset along Y (default 0)\n * @returns A translated sketch\n * @category Sketch Transforms\n */\n translate(x: number, y?: number): Sketch;\n /**\n * Rotate the sketch around its bounding-box center.\n *\n * @param degrees - Rotation angle in degrees (CCW positive)\n * @returns A rotated sketch\n * @see {@link rotateAround} to rotate around an arbitrary point\n * @category Sketch Transforms\n */\n rotate(degrees: number): Sketch;\n /**\n * Rotate the sketch around a specific pivot point.\n *\n * **Example**\n *\n * ```ts\n * rect(20, 20).rotateAround(45, [0, 0]);\n * ```\n *\n * @param degrees - Rotation angle in degrees (CCW positive)\n * @param pivot - 2D pivot point `[x, y]`\n * @returns A rotated sketch\n * @category Sketch Transforms\n */\n rotateAround(degrees: number, pivot: [\n number,\n number\n ]): Sketch;\n /**\n * Scale the sketch relative to its bounding-box center.\n *\n * **Details**\n *\n * Pass a single number for uniform scaling, or `[sx, sy]` for per-axis scaling.\n *\n * @param v - Scale factor — uniform `number` or per-axis `[sx, sy]`\n * @returns A scaled sketch\n * @see {@link scaleAround} to scale relative to an arbitrary point\n * @category Sketch Transforms\n */\n scale(v: number | [\n number,\n number\n ]): Sketch;\n /**\n * Mirror the sketch across a line through its bounding-box center.\n *\n * **Details**\n *\n * `normal` is the normal vector of the mirror line (not the line direction).\n * For example, `[1, 0]` mirrors across a vertical line (Y axis direction),\n * and `[0, 1]` mirrors across a horizontal line.\n *\n * @param normal - Normal of the mirror line as `[x, y]`\n * @returns A mirrored sketch\n * @see {@link mirrorThrough} to mirror across a line through an arbitrary point\n * @category Sketch Transforms\n */\n mirror(normal: [\n number,\n number\n ]): Sketch;\n /**\n * Scale the sketch relative to an arbitrary pivot point.\n *\n * @param pivot - 2D pivot point `[x, y]`\n * @param v - Scale factor — uniform `number` or per-axis `[sx, sy]`\n * @returns A scaled sketch\n * @category Sketch Transforms\n */\n scaleAround(pivot: [\n number,\n number\n ], v: number | [\n number,\n number\n ]): Sketch;\n /**\n * Mirror the sketch across a line defined by a point and a normal direction.\n *\n * @param point - A point on the mirror line\n * @param normal - Normal of the mirror line as `[x, y]`\n * @returns A mirrored sketch\n * @category Sketch Transforms\n */\n mirrorThrough(point: [\n number,\n number\n ], normal: [\n number,\n number\n ]): Sketch;\n /**\n * Add (union) one or more sketches to this sketch.\n *\n * **Details**\n *\n * Accepts individual sketches or arrays: `sketch.add(a, b)` or `sketch.add([a, b])`.\n * For combining many sketches at once, prefer the free function `union2d()` which\n * uses Manifold\'s batch operation and is faster than chaining.\n *\n * **Example**\n *\n * ```ts\n * circle2d(20).add(rect(10, 30)).extrude(5);\n * ```\n *\n * @returns A new sketch with all inputs unioned together\n * @see {@link union2d} for the batch free-function alternative\n * @category Sketch Booleans\n */\n add(...others: SketchOperandInput[]): Sketch;\n /**\n * Subtract one or more sketches from this sketch.\n *\n * **Details**\n *\n * Accepts individual sketches or arrays: `sketch.subtract(a, b)` or `sketch.subtract([a, b])`.\n * For subtracting many cutters at once, prefer the free function `difference2d()`.\n *\n * **Example**\n *\n * ```ts\n * rect(40, 40).subtract(circle2d(10)).extrude(5);\n * ```\n *\n * @returns A new sketch with all inputs subtracted\n * @see {@link difference2d} for the batch free-function alternative\n * @category Sketch Booleans\n */\n subtract(...others: SketchOperandInput[]): Sketch;\n /**\n * Intersect this sketch with one or more others (keep overlapping area only).\n *\n * **Details**\n *\n * Accepts individual sketches or arrays: `sketch.intersect(a, b)` or `sketch.intersect([a, b])`.\n * For intersecting many sketches, prefer the free function `intersection2d()`.\n *\n * @returns A new sketch containing only the area shared by all inputs\n * @see {@link intersection2d} for the batch free-function alternative\n * @category Sketch Booleans\n */\n intersect(...others: SketchOperandInput[]): Sketch;\n /**\n * Alias for `add()` — matches the free-function `union2d()` naming.\n *\n * @see {@link add}\n * @category Sketch Booleans\n */\n union(...others: SketchOperandInput[]): Sketch;\n /**\n * Alias for `subtract()` — matches the free-function `difference2d()` naming.\n *\n * @see {@link subtract}\n * @category Sketch Booleans\n */\n difference(...others: SketchOperandInput[]): Sketch;\n /**\n * Alias for `intersect()` — matches the free-function `intersection2d()` naming.\n *\n * @see {@link intersect}\n * @category Sketch Booleans\n */\n intersection(...others: SketchOperandInput[]): Sketch;\n /**\n * Inflate (positive delta) or deflate (negative delta) the sketch contour.\n *\n * **Details**\n *\n * For rounding corners, prefer `filletCorners(radius)` (all corners) or\n * `filletCorner([x, y], radius)` (one corner) — they round only true corners\n * and keep concave geometry exact.\n *\n * - `\'Round\'` — smooth arc at each corner (default)\n * - `\'Square\'` — flat mitered extension\n * - `\'Miter\'` — sharp pointed extension\n *\n * **Example**\n *\n * ```ts\n * rect(40, 20).offset(3); // expand by 3\n * ```\n *\n * @param delta - Offset distance (positive = expand, negative = shrink)\n * @param join - Corner join style: `\'Round\'` | `\'Square\'` | `\'Miter\'` (default `\'Round\'`)\n * @returns A new offset sketch\n * @category Sketch Operations\n */\n offset(delta: number, join?: "Square" | "Round" | "Miter"): Sketch;\n /**\n * Round every significant corner of this sketch with the same fillet radius.\n *\n * **Details**\n *\n * Works on any sketch — primitives, boolean results, attached or composed\n * profiles. A vertex counts as a corner when its turn angle is at least 30°,\n * so vertices that belong to tessellated circles or earlier fillets are left\n * untouched. Both convex and concave corners are rounded. Holes are\n * preserved, and their corners are rounded too.\n *\n * Throws if the sketch has no significant corners, or if the radius does not\n * fit a corner (the error names the corner and the maximum radius).\n *\n * **Example**\n *\n * ```ts\n * rect(60, 30).filletCorners(5).extrude(4); // rounded plate\n * polygon(bracketPts).filletCorners(3); // every corner of an outline\n * ```\n *\n * @param radius - Fillet radius applied to every significant corner\n * @returns A new sketch with all significant corners rounded\n * @see {@link filletCorner} to round a single corner picked by a seed point\n * @see {@link chamferCorners} for straight bevels instead of arcs\n * @category Sketch Operations\n */\n filletCorners(radius: number): Sketch;\n /**\n * Round the single corner of this sketch nearest to a seed point.\n *\n * **Details**\n *\n * Selects the significant corner (turn angle ≥ 30°) closest to `at` — the\n * seed does not need to be exact, just nearer to the intended corner than to\n * any other (like `region([x, y])` seed selection). Throws if two corners\n * are equidistant from the seed, naming both so you can move the seed.\n *\n * Chain calls to round several corners with different radii.\n *\n * **Example**\n *\n * ```ts\n * polygon(roofPts)\n * .filletCorner([45, 86], 14) // peak\n * .filletCorner([24, 74], 8); // left shoulder\n * ```\n *\n * @param at - Seed point `[x, y]` near the corner to round\n * @param radius - Fillet radius\n * @returns A new sketch with that corner rounded\n * @see {@link filletCorners} to round all significant corners at once\n * @see {@link chamferCorner} for a straight bevel instead of an arc\n * @category Sketch Operations\n */\n filletCorner(at: [\n number,\n number\n ], radius: number): Sketch;\n /**\n * Bevel every significant corner of this sketch with a straight chamfer.\n *\n * **Details**\n *\n * Replaces each significant corner (turn angle ≥ 30°) with a straight cut\n * set back `size` along both adjacent edges. Tessellated arcs are left\n * untouched; holes are preserved. Throws if the sketch has no significant\n * corners or the size does not fit a corner.\n *\n * **Example**\n *\n * ```ts\n * rect(60, 30).chamferCorners(4).extrude(4); // beveled plate outline\n * ```\n *\n * @param size - Setback distance along each adjacent edge\n * @returns A new sketch with all significant corners beveled\n * @see {@link chamferCorner} to bevel a single corner picked by a seed point\n * @see {@link filletCorners} for rounded corners instead of bevels\n * @category Sketch Operations\n */\n chamferCorners(size: number): Sketch;\n /**\n * Bevel the single corner of this sketch nearest to a seed point.\n *\n * **Details**\n *\n * Selects the significant corner (turn angle ≥ 30°) closest to `at` and\n * replaces it with a straight cut set back `size` along both adjacent edges.\n * Throws if two corners are equidistant from the seed. Chain calls to bevel\n * several corners with different sizes.\n *\n * **Example**\n *\n * ```ts\n * rect(60, 30).chamferCorner([30, 15], 6); // bevel only the top-right corner\n * ```\n *\n * @param at - Seed point `[x, y]` near the corner to bevel\n * @param size - Setback distance along each adjacent edge\n * @returns A new sketch with that corner beveled\n * @see {@link chamferCorners} to bevel all significant corners at once\n * @see {@link filletCorner} for a rounded corner instead of a bevel\n * @category Sketch Operations\n */\n chamferCorner(at: [\n number,\n number\n ], size: number): Sketch;\n /**\n * Decompose this sketch into its distinct filled regions, sorted largest-first by area.\n *\n * **Details**\n *\n * A single sketch can contain several disconnected filled areas (e.g., two separate\n * rectangles, or a ring shape with a hole). This method enumerates all top-level\n * connected regions as independent `Sketch` objects, each with its own outer boundary\n * and associated holes.\n *\n * **Example**\n *\n * ```ts\n * const pair = union2d(rect(40, 40), rect(40, 40).translate(60, 0));\n * const [left, right] = pair.regions(); // largest first\n * left.extrude(5);\n * ```\n *\n * @returns Array of region sketches, sorted by area descending\n * @see {@link region} to pick one region by seed point\n * @category Sketch Regions\n */\n regions(): Sketch[];\n /**\n * Select the single filled region that contains the given 2D seed point.\n *\n * **Details**\n *\n * The seed must lie strictly inside the filled area — not on a boundary edge and\n * not inside a hole. Throws a descriptive error if the seed is outside all regions.\n * If unsure where regions are, use `.regions()` first — each result has `.bounds()`.\n *\n * **Example**\n *\n * ```ts\n * const donut = circle2d(50).subtract(circle2d(30));\n * donut.region([40, 0]).extrude(10); // seed at radius 40, inside the ring\n * ```\n *\n * @param seed - A 2D point `[x, y]` strictly inside the desired region\n * @returns The sketch region containing the seed point\n * @see {@link regions} to enumerate all regions\n * @category Sketch Regions\n */\n region(seed: [\n number,\n number\n ]): Sketch;\n /** Extrude this 2D sketch along Z to create a 3D solid. Supports twist and scale tapering. */\n extrude(height: number, opts?: {\n twist?: number;\n divisions?: number;\n scaleTop?: number | [\n number,\n number\n ];\n }): Shape;\n /** Revolve this 2D sketch around the world Z axis. Sketch X is radius; sketch Y becomes world Z height. Keep the profile at X > 0 unless it intentionally touches the axis. */\n revolve(degrees?: number, segments?: number): Shape;\n /**\n * Position this sketch relative to another using named anchor points.\n *\n * **Details**\n *\n * Computes the translation needed to align `selfAnchor` on this sketch with\n * `targetAnchor` on the target sketch, then applies an optional pixel-exact offset.\n *\n * Anchor positions: `\'center\'` | `\'top-left\'` | `\'top-right\'` | `\'bottom-left\'`\n * | `\'bottom-right\'` | `\'top\'` | `\'bottom\'` | `\'left\'` | `\'right\'`\n *\n * **Example**\n *\n * ```ts\n * const arm = rect(4, 70).attachTo(plate, \'bottom-left\', \'top-left\');\n * const shifted = rect(4, 70).attachTo(plate, \'bottom-left\', \'top-left\', [5, 0]);\n * ```\n *\n * @param target - The sketch to attach to\n * @param targetAnchor - Named anchor point on the target sketch\n * @param selfAnchor - Named anchor point on this sketch to align (default `\'center\'`)\n * @param offset - Additional `[dx, dy]` offset applied after alignment\n * @returns A translated sketch\n * @category Sketch Transforms\n */\n attachTo(target: Sketch, targetAnchor: Anchor, selfAnchor?: Anchor, offset?: [\n number,\n number\n ]): Sketch;\n /**\n * Place this sketch on a face or planar target in 3D space.\n *\n * Use this when a 2D profile should be oriented onto a 3D face before\n * extrusion or other downstream operations.\n *\n * @param parentOrFace - Parent shape, face reference, or shape-like target.\n * @param faceOrOpts - Face selector or placement options.\n * @param opts - Additional placement options when a face selector is provided separately.\n * @returns A sketch positioned on the target face.\n * @category Sketch Transforms\n */\n onFace(parentOrFace: Shape | {\n toShape(): Shape;\n } | {\n _bbox(): {\n min: number[];\n max: number[];\n };\n } | FaceRef, faceOrOpts?: "front" | "back" | "left" | "right" | "top" | "bottom" | string | FaceRef | {\n u?: number;\n v?: number;\n protrude?: number;\n selfAnchor?: Anchor;\n }, opts?: {\n u?: number;\n v?: number;\n protrude?: number;\n selfAnchor?: Anchor;\n }): Sketch;\n /** Label the single boundary edge (for circles, single-loop profiles). Returns a new sketch. */\n labelEdge(name: string): Sketch;\n /**\n * Label edges in winding order, or by named map for rect.\n *\n * Positional: `labelEdges(\'bottom\', \'right\', \'top\', \'left\')` — one per edge, `null` to skip.\n * Named (rect only): `labelEdges({ bottom: \'floor\', top: \'ceiling\' })`.\n * Returns a new sketch.\n */\n labelEdges(...args: (string | null)[] | [\n Record<string, string>\n ]): Sketch;\n /** List current edge label names. */\n edgeLabels(): string[];\n /** Prefix all edge labels. Returns a new sketch with prefixed labels. */\n prefixLabels(prefix: string): Sketch;\n /** Rename a single edge label. Returns a new sketch. */\n renameLabel(from: string, to: string): Sketch;\n /** Remove specific labels. Returns a new sketch. */\n dropLabels(...names: string[]): Sketch;\n /** Remove all labels. Returns a new sketch. */\n dropAllLabels(): Sketch;\n}\ndeclare class ConstraintSketch extends Sketch {\n readonly constraintMeta: SketchConstraintMeta;\n readonly definition: ConstraintDefinition;\n constructor(cross: Sketch["cross"], constraintMeta: SketchConstraintMeta, definition: ConstraintDefinition);\n /**\n * Enumerate all bounded regions formed by the line arrangement of this sketch.\n * Construction lines are excluded. Regions are returned largest-first by area.\n */\n detectArrangement(): Sketch[];\n /**\n * Select the single arrangement region that contains the given seed point.\n * Throws if no region contains the seed.\n */\n detectArrangementRegion(_seed: [\n number,\n number\n ]): Sketch;\n /**\n * Return the solved constrained path as a sampled 2D polyline.\n *\n * Use this when a construction rail was authored with `constrainedSketch()`\n * and should feed another operation such as `Loft.pathOnXz(...)`.\n * The sketch must contain exactly one profile path.\n *\n * @param samples - Samples per curved segment. Default 32.\n * @returns The solved path as an open polyline.\n */\n toPolyline(samples?: number): [\n number,\n number\n ][];\n /**\n * Re-solve the sketch after changing the value of one existing constraint.\n *\n * Use this for interactive dimension edits without rebuilding the whole sketch graph.\n * It attempts a warm-started solve first, then falls back to a full solve if needed.\n *\n * @param constraintId - ID of the existing constraint to update.\n * @param value - New numeric value for the constraint.\n * @returns A new solved `ConstraintSketch`.\n */\n withUpdatedConstraint(constraintId: string, value: number): ConstraintSketch;\n /**\n * Return a human-readable diagnostic string of the solved state.\n */\n inspect(): string;\n}\ninterface ConstrainedSketchBuilder {\n /**\n * Import a `Point2D` object into the sketch.\n * @softDeprecated sk.point(pt.x, pt.y, fixed) — constraint methods auto-import Point2D values directly\n * @deprecated use sk.point(pt.x, pt.y, fixed) — constraint methods auto-import Point2D values directly\n */\n importPoint(pt: {\n x: number;\n y: number;\n }, fixed?: boolean): PointId;\n /**\n * Import a `Line2D` object into the sketch.\n * @softDeprecated sk.line(sk.point(l.start.x, l.start.y, fixed), sk.point(l.end.x, l.end.y, fixed)) — constraint methods auto-import Line2D values directly\n * @deprecated use sk.line(sk.point(l.start.x, l.start.y, fixed), sk.point(l.end.x, l.end.y, fixed)) — constraint methods auto-import Line2D values directly\n */\n importLine(l: {\n start: {\n x: number;\n y: number;\n };\n end: {\n x: number;\n y: number;\n };\n }, fixed?: boolean): LineId;\n /**\n * Import a `Rectangle2D` as four points and four lines.\n * @softDeprecated sk.rect({ x, y, width, height }) — returns richer named sides (bottom/right/top/left) and vertices\n * @deprecated use sk.rect({ x, y, width, height }) — returns richer named sides (bottom/right/top/left) and vertices\n */\n importRectangle(r: {\n vertices: [\n {\n x: number;\n y: number;\n },\n {\n x: number;\n y: number;\n },\n {\n x: number;\n y: number;\n },\n {\n x: number;\n y: number;\n }\n ];\n }, fixed?: boolean): {\n bottom: LineId;\n right: LineId;\n top: LineId;\n left: LineId;\n points: [\n PointId,\n PointId,\n PointId,\n PointId\n ];\n };\n /** Add a fixed reference point at `(x, y)`. */\n referencePoint(x: number, y: number): PointId;\n /** Add a fixed reference line from `(x1, y1)` to `(x2, y2)`. */\n referenceLine(x1: number, y1: number, x2: number, y2: number): LineId;\n /** Import a single named entity from a solved sketch as fixed reference geometry. */\n referenceFrom(source: ConstraintSketch, entityId: string): PointId | LineId | null;\n /** Import all non-construction entities from a solved sketch as fixed references. */\n referenceAllFrom(source: ConstraintSketch): {\n points: Map<string, PointId>;\n lines: Map<string, LineId>;\n };\n}\ninterface ConstrainedSketchOptions {\n /** When true, adding a constraint that cannot be satisfied throws instead of silently discarding it. */\n strict?: boolean;\n}\ninterface ConstrainedSketchBuilder extends ConstraintBuilderMethods {\n}\ndeclare class ConstrainedSketchBuilder {\n private points;\n private lines;\n private circles;\n private arcs;\n private beziers;\n private shapes;\n private _groups;\n /** Point IDs owned by groups — excluded from the serialized points array. */\n private groupOwnedPointIds;\n /** Line IDs owned by groups — excluded from the serialized lines array. */\n private groupOwnedLineIds;\n private constraints;\n private loops;\n private rejectedConstraints;\n /** Maps rejected constraint ID → human-readable reason string. */\n private rejectionReasons;\n protected cursor: PointId | null;\n protected loopStart: PointId | null;\n /** Last arc created by the path API (arcTo), used by blendTo. */\n protected lastPathArc: ArcId | null;\n private nextId;\n private strict;\n /** Cumulative time spent in seedIncrementalGeometry calls (ms). */\n private seedTimeMs;\n /** Max cumulative time for all seed calls (ms). After this, seeding is skipped. */\n private static readonly SEED_BUDGET_MS;\n /** WASM solver session handle — persists state across seed steps. */\n private _sessionHandle;\n private _sessionApi;\n private _sessionFailed;\n constructor(options?: ConstrainedSketchOptions);\n /** Try to create a WASM solver session. Returns true if session is active. */\n private ensureSession;\n private destroySession;\n /**\n * Add a free point to the sketch at `(x, y)`.\n *\n * If `x` or `y` are omitted, the point is placed at the bounding-box center\n * of existing geometry so it starts near other entities rather than at the origin.\n * Throws if either coordinate is `NaN` or `Infinity`.\n *\n * @param x - X coordinate. Defaults to existing geometry center.\n * @param y - Y coordinate. Defaults to existing geometry center.\n * @param fixed - When true the point is pinned at (x, y) and acts like a `fix` constraint.\n * @returns A `PointId` that can be passed to `line()`, `circle()`, and constraints.\n * @category Constrained Sketches\n */\n point(x?: number, y?: number, fixed?: boolean): PointId;\n /**\n * Return the `PointId` of the point created at the given insertion index (0-based).\n *\n * Useful for referencing points by order when the original `PointId` was not stored.\n *\n * @param index - 0-based creation index.\n * @returns The `PointId` at that index.\n * @category Constrained Sketches\n */\n /** Return the `PointId` of the point created at the given insertion index. */\n pointAt(index: number): PointId;\n /**\n * Connect two existing points with a line segment.\n *\n * Pass `construction = true` for a helper line that participates in constraints\n * but is excluded from the solved sketch output (not part of any profile loop).\n *\n * **Example** (construction axis for symmetry)\n *\n * ```ts\n * const axis = sk.line(sk.point(0, -50), sk.point(0, 50), true);\n * sk.symmetric(p1, p2, axis);\n * ```\n *\n * @param a - Start `PointId`.\n * @param b - End `PointId`.\n * @param construction - When true this line is excluded from the solved output. Default: false.\n * @param name - Optional label for debugging.\n * @returns A `LineId` for use in constraints and path loops.\n * @category Constrained Sketches\n */\n line(a: PointId, b: PointId, construction?: boolean, name?: string): LineId;\n /**\n * Return the `LineId` of the line created at the given insertion index (0-based).\n *\n * Useful in path-drawing workflows where lines are created implicitly via\n * `lineTo()` and you need to reference one by position.\n *\n * ```ts\n * sk.moveTo(0, 0).lineTo(50, 0).lineTo(50, 30).close();\n * sk.length(sk.lineAt(0), 50);\n * ```\n *\n * @param index - 0-based creation index.\n * @returns The `LineId` at that index.\n * @category Constrained Sketches\n */\n /** Return the `LineId` of the line created at the given insertion index. */\n lineAt(index: number): LineId;\n /**\n * Add a circle to the sketch with the given center point and initial radius.\n *\n * The radius is a starting value — if you add a `radius()` or `diameter()` constraint,\n * the solver will adjust it. Non-construction circles automatically register a loop.\n *\n * @param center - Center `PointId`.\n * @param radius - Initial radius (must be finite and positive).\n * @param construction - When true the circle is excluded from the solved output. Default: false.\n * @param segments - Tessellation resolution for display. Default: 48.\n * @param name - Optional label for debugging.\n * @returns A `CircleId` for use in constraints.\n * @category Constrained Sketches\n */\n circle(center: PointId, radius: number, construction?: boolean, segments?: number, name?: string): CircleId;\n /**\n * Return the `CircleId` of the circle created at the given insertion index (0-based).\n *\n * @param index - 0-based creation index.\n * @returns The `CircleId` at that index.\n * @category Constrained Sketches\n */\n /** Return the `CircleId` of the circle created at the given insertion index. */\n circleAt(index: number): CircleId;\n /**\n * Register a named shape (closed polygon) from an ordered list of line IDs.\n *\n * The `ShapeId` can be passed to `shapeWidth()`, `shapeHeight()`, `shapeArea()`,\n * `shapeCentroidX()`, `shapeCentroidY()`, and `shapeEqualCentroid()` constraints.\n * Shape registration is done automatically by concept factories like `rect()` and `addPolygon()`.\n *\n * @param lines - Ordered line IDs forming the closed polygon boundary.\n * @returns A `ShapeId` for use in shape constraints.\n * @category Constrained Sketches\n */\n shape(lines: LineId[]): ShapeId;\n /**\n * Create a rigid-body group with a local coordinate frame.\n *\n * Points and lines added to the group move together as a unit — the solver\n * sees 3 DOF (x, y, θ) instead of 2N per point. After configuring the group,\n * call `.done()` to register it and receive a `SketchGroupHandle`.\n *\n * Group points are addressable by their `PointId` in all sketch constraints\n * (e.g. `sk.coincident`, `sk.distance`) just like any other points.\n *\n * **Example**\n *\n * ```ts\n * const g = sk.group({ x: 50, y: 30 });\n * const p0 = g.point(0, 0); // local origin → world (50, 30)\n * const p1 = g.point(100, 0); // local (100,0) → world (150, 30)\n * const l = g.line(p0, p1);\n * g.fixRotation();\n * const handle = g.done();\n * // p0, p1 work in constraints like any other PointId:\n * sk.coincident(p0, someExternalPoint);\n * ```\n *\n * @param opts - Initial frame position (x, y) in degrees theta, and optional id.\n * @returns A `SketchGroupBuilder` — call `.done()` to finalize.\n * @category Constrained Sketches\n */\n group(opts?: {\n x?: number;\n y?: number;\n theta?: number;\n id?: string;\n }): SketchGroupBuilder;\n /**\n * Register a group directly (called by SketchGroupBuilder).\n * @internal\n */\n _registerGroup(group: SketchGroup): void;\n /** Add a raw constraint object to the builder. */\n constrain(constraint: Omit<SketchConstraint, "id">): this;\n /**\n * Keep the live builder geometry near a solved state as constraints are added.\n * This restores the progressive seeding path used by large sketches in the browser\n * without rejecting constraints or changing the final solve API.\n */\n private seedIncrementalGeometry;\n /** Serialize a constraint for the session API (matches Rust serde format). */\n private serializeConstraintForSession;\n protected resolvePointId(p: any): PointId;\n protected resolveLineId(l: any): LineId;\n protected resolveCircleId(c: any): CircleId;\n protected resolveArcId(a: any): ArcId;\n protected resolveBezierId(b: any): BezierId;\n protected resolveShapeId(s: any): ShapeId;\n protected requireFinite(value: number, constraintName: string): void;\n /** Sync point positions from session state back to builder entities. */\n private syncPointsFromSession;\n /**\n * Run the constraint solver and return a solved sketch.\n *\n * The returned `ConstraintSketch` extends `Sketch` and can be used directly\n * in all 3D operations (`extrude`, `revolve`, etc.). It also exposes\n * `constraintMeta` with the solver status:\n *\n * ```ts\n * const result = sk.solve();\n * result.constraintMeta.status; // \'fully\' | \'under\' | \'over\' | \'over-redundant\'\n * result.constraintMeta.dof; // 0 = fully constrained\n * result.constraintMeta.maxError; // residual — should be < 1e-6\n * result.inspect(); // human-readable summary\n * result.withUpdatedConstraint(\'cst-5\', 120); // update a dimension without rebuilding\n * ```\n *\n * **Troubleshooting**\n *\n * - **Under-constrained (dof > 0)** — add `fix()`, `length()`, or other dimensional constraints.\n * - **Over-constrained** — conflicting constraints are auto-rejected. Check `result.constraintMeta.constraints` and `result.inspect()`.\n * - **maxError > 1e-6** — solver did not converge; check for contradictory constraints.\n *\n * @param options - Advanced solver options (iterations, restarts, tolerance, etc.).\n * @returns A `ConstraintSketch` (also a valid `Sketch`) with solved geometry.\n * @category Constrained Sketches\n */\n solve(options?: SolveOptions): ConstraintSketch | Sketch;\n /**\n * Run the solver without building a full `ConstraintSketch`.\n *\n * Lighter than `solve()` — skips profile and DOF analysis. Useful for\n * lightweight constraint validation or progress monitoring mid-construction.\n *\n * @param options - Advanced solver options (iterations, restarts, tolerance, etc.).\n * @returns Object with `maxError`, `rejectedCount`, and the solved `ConstraintDefinition`.\n * @category Constrained Sketches\n */\n solveConstraintsOnly(options?: SolveOptions): {\n maxError: number;\n rejectedCount: number;\n definition: ConstraintDefinition;\n };\n private buildDefinition;\n /** Sync solved positions from a definition back to the builder\'s live entities. */\n private syncFromDefinition;\n private getPoint;\n /** Compute arc center from start/end points, radius, and clockwise flag; create the arc entity. */\n protected addArc(startId: PointId, endId: PointId, radius: number, clockwise: boolean): ArcId;\n /**\n * Bounding box of all existing non-construction points.\n * Returns null when no points exist yet.\n * Used by concept factories to auto-offset initial geometry.\n */\n _pointBounds(): {\n minX: number;\n maxX: number;\n minY: number;\n maxY: number;\n } | null;\n}\n/**\n * Create a parametric 2D sketch driven by geometric constraints and a nonlinear solver.\n *\n * **Workflow**\n *\n * 1. Create a builder with `constrainedSketch()`.\n * 2. Add geometry — points, lines, circles, arcs — using the builder methods.\n * 3. Add constraints (`horizontal`, `length`, `fix`, etc.) to drive the geometry.\n * 4. Call `.solve()` to run the solver and get a `ConstraintSketch` (which extends `Sketch`).\n *\n * **Example**\n *\n * ```ts\n * const sk = constrainedSketch();\n * const p1 = sk.point(0, 0);\n * const p2 = sk.point(50, 0);\n * const l1 = sk.line(p1, p2);\n * sk.fix(p1, 0, 0);\n * sk.horizontal(l1);\n * sk.length(l1, 50);\n * return sk.solve().extrude(10);\n * ```\n *\n * **Solver status**\n *\n * ```ts\n * const result = sk.solve();\n * result.constraintMeta.status; // \'fully\' | \'under\' | \'over\' | \'over-redundant\'\n * result.constraintMeta.dof; // 0 = fully constrained\n * result.constraintMeta.maxError; // residual — should be < 1e-6\n * result.inspect(); // human-readable summary\n * result.withUpdatedConstraint(\'cst-5\', 120); // update a dimension without rebuilding\n * ```\n *\n * @param options - Optional builder configuration.\n * @returns A `ConstrainedSketchBuilder` ready to accept geometry and constraints.\n * @category Constrained Sketches\n */\ndeclare function constrainedSketch(options?: ConstrainedSketchOptions): ConstrainedSketchBuilder;\ninterface SketchGroupHandle {\n readonly id: GroupId;\n /** Get a group vertex PointId by its index (order of `.point()` calls). */\n point(index: number): PointId;\n /** Get a group line LineId by its index (order of `.line()` calls). */\n line(index: number): LineId;\n /** All group vertex PointIds in creation order. */\n readonly vertices: PointId[];\n /** All group line LineIds in creation order. */\n readonly sides: LineId[];\n}\ndeclare class SketchGroupBuilder {\n private sk;\n private groupId;\n private gx;\n private gy;\n private gtheta;\n private localPoints;\n private localLines;\n private isFixed;\n private isFixedRotation;\n private registered;\n constructor(sk: ConstrainedSketchBuilder, opts: {\n x?: number;\n y?: number;\n theta?: number;\n id?: string;\n });\n /** Add a point in local coordinates. Returns its globally-addressable PointId. */\n /**\n * Add a point in local coordinates. Returns its globally-addressable PointId.\n */\n point(lx: number, ly: number): PointId;\n /** Connect two group points with a line. Both must be PointIds from this group. */\n /**\n * Connect two group points with a line. Both must be PointIds from this group.\n */\n line(a: PointId, b: PointId, name?: string): LineId;\n /** Freeze rotation (θ). Group can still translate - 2 DOF remain. */\n /** Freeze rotation (theta). Group can still translate - 2 DOF remain. */\n fixRotation(): this;\n /** Freeze all 3 DOF - group is completely fixed. */\n /** Freeze all 3 DOF - group is completely fixed. */\n fix(): this;\n /**\n * Finalize and register the group with the builder.\n * Returns a handle for referencing group points/lines in constraints.\n */\n /** Finalize and register the group with the builder. */\n done(): SketchGroupHandle;\n}\ndeclare class RouteBuilder {\n private readonly sk;\n private readonly _startPt;\n private cursorPt;\n private cursorX;\n private cursorY;\n /**\n * Current travel direction in radians from +X.\n * null = direction not yet established (first segment will set it).\n */\n private direction;\n private segments;\n private lastLineId;\n private lastArcId;\n private finished;\n constructor(sk: ConstrainedSketchBuilder, startPt: PointId, x: number, y: number);\n /** Vertical line going +Y. Length is optional (solver determines it from constraints). */\n up(length?: number): LineId;\n /** Vertical line going -Y. Length is optional. */\n down(length?: number): LineId;\n /** Horizontal line going +X. Length is optional. */\n right(length?: number): LineId;\n /** Horizontal line going -X. Length is optional. */\n left(length?: number): LineId;\n /** Line at an arbitrary angle (degrees from +X). Length is optional. */\n lineAt(angleDeg: number, length?: number): LineId;\n /**\n * Line with solver-determined direction. Length is optional.\n * Direction comes from tangency to previous arc or from constraints.\n */\n line(length?: number): LineId;\n /**\n * Line toward a specific point.\n * Length defaults to the distance to that point.\n */\n toward(x: number, y: number): LineId;\n /**\n * Tangent arc turning left relative to travel direction.\n * @param radius - Arc radius. If omitted, solver determines it.\n * @param sweepDegOrOpts - Sweep angle in degrees (constrains the arc),\n * or `{ minSweep: degrees }` to seed the geometry without constraining.\n * `minSweep` guides the solver to the correct branch for arcs that\n * sweep more than the default 90° seed.\n */\n arcLeft(radius?: number, sweepDegOrOpts?: number | {\n minSweep: number;\n }): ArcId;\n /**\n * Tangent arc turning right relative to travel direction.\n * @param radius - Arc radius. If omitted, solver determines it.\n * @param sweepDegOrOpts - Sweep angle in degrees (constrains the arc),\n * or `{ minSweep: degrees }` to seed without constraining.\n */\n arcRight(radius?: number, sweepDegOrOpts?: number | {\n minSweep: number;\n }): ArcId;\n /** Close the route with a straight line back to the start point. */\n close(): void;\n /**\n * Close the route back to its start point and register as a profile loop.\n *\n * No extra line segment is added. A coincident constraint connects the\n * last point to the start, and tangency is added for G1 smoothness\n * when arcs are at the junction. The session\'s incremental solver\n * processes these constraints, keeping seed positions accurate for\n * the final solve.\n */\n done(): void;\n /** PointId of the route\'s start point. */\n get start(): PointId;\n /** PointId of the current cursor (route\'s end). */\n get end(): PointId;\n /** Get the start point of a segment. */\n startOf(segId: LineId | ArcId): PointId;\n /** Get the end point of a segment. */\n endOf(segId: LineId | ArcId): PointId;\n /**\n * @param angle - Direction in radians from +X\n * @param length - Optional length\n * @param constrainDirection - Whether to add horizontal/vertical constraint (default true for axis-aligned)\n */\n private _addLine;\n private _addArc;\n private _ensureNotFinished;\n private _registerLoop;\n private _normalizeAngle;\n private _isVertical;\n private _isHorizontal;\n}\ninterface RouteLine {\n /** \'x\' for vertical line at x=offset, \'y\' for horizontal line at y=offset */\n axis: "x" | "y";\n offset: number;\n}\ninterface RouteCircle {\n center: [\n number,\n number\n ];\n radius: number;\n}\ninterface RouteTangent {\n tangent: RouteCircle | CircleId;\n}\ninterface RouteFillet {\n fillet: number;\n /** When \'tangent\', adds a tangent line from the smaller circle before the fillet arc. */\n approach?: "tangent";\n}\ninterface RouteTangentArc {\n tangentArc: number;\n}\ninterface RoutePoint {\n point: [\n number,\n number\n ];\n}\ninterface RouteUntil {\n line: RouteLine | LineId;\n until: number;\n}\ntype RouteStep = RouteLine | RouteCircle | CircleId | RouteTangent | RouteFillet | RouteTangentArc | RoutePoint | RouteUntil;\ninterface ConstrainedSketchBuilder {\n /**\n * Route a profile through a sequence of geometric elements.\n * The solver computes all tangent points and intersections automatically.\n *\n * Steps can include:\n * - `{ point: [x, y] }` — route through a point\n * - `{ axis: \'x\'|\'y\', offset: n }` — follow a construction line\n * - `{ line: {...}, until: n }` — follow a line clipped to a coordinate\n * - `{ tangent: { center, radius } }` — tangent arc onto a construction circle\n * - `{ fillet: radius }` — fillet between adjacent elements\n * - `{ tangentArc: radius }` — free tangent arc (solver finds center)\n *\n * Returns `this` for chaining. Call `.solve()` after to get the Sketch.\n */\n route(steps: RouteStep[]): this;\n /**\n * Start a directional route from coordinates.\n *\n * Returns a `RouteBuilder` - describe the path with up/down/left/right/arcLeft/arcRight.\n * Each method returns the entity ID (`LineId` or `ArcId`) for use in `sk.*` constraints.\n *\n * @example\n * ```ts\n * const r = sk.route(0, 0);\n * const stem = r.up(18);\n * r.arcLeft(8.9);\n * const neck = r.down();\n * r.done();\n * sk.offsetX(stem, neck, 10.8);\n * ```\n */\n route(x: number, y: number): RouteBuilder;\n /**\n * Start a directional route from an existing sketch point.\n */\n route(startPoint: PointId): RouteBuilder;\n}\n/**\n * An immutable 2D point with measurement and construction helpers.\n *\n * Used as construction geometry in sketches, constraints, and analytic\n * measurements. All methods return new instances — `Point2D` is immutable.\n *\n * @category 2D Entities\n */\ndeclare class Point2D {\n readonly x: number;\n readonly y: number;\n constructor(x: number, y: number);\n /**\n * Measure straight-line distance to another point.\n *\n * @param other - The other point.\n * @returns Euclidean distance.\n */\n distanceTo(other: Point2D): number;\n /**\n * Compute the midpoint between this point and another point.\n *\n * @param other - The other point.\n * @returns A new midpoint.\n */\n midpointTo(other: Point2D): Point2D;\n /**\n * Return a point shifted by the given delta.\n *\n * @param dx - Horizontal offset.\n * @param dy - Vertical offset.\n * @returns A translated point.\n */\n translate(dx: number, dy: number): Point2D;\n /**\n * Convert this point to a plain `[x, y]` tuple.\n *\n * @returns A coordinate tuple.\n */\n toTuple(): [\n number,\n number\n ];\n}\n/**\n * Create an analytic 2D point for measurement and construction geometry.\n *\n * @softDeprecated new Point2D(x, y) — or cs.point(x, y) inside constrainedSketch()\n * @deprecated use new Point2D(x, y) — or cs.point(x, y) inside constrainedSketch()\n * @param x - X coordinate\n * @param y - Y coordinate\n * @returns A new `Point2D`\n * @category 2D Entities\n */\ndeclare function point(x: number, y: number): Point2D;\n/**\n * An immutable 2D line segment with length, angle, intersection, and parallel helpers.\n *\n * Provides both segment-only (`intersectSegment`) and infinite-line\n * (`intersect`) intersection queries. All methods return new instances.\n *\n * @category 2D Entities\n */\ndeclare class Line2D {\n readonly start: Point2D;\n readonly end: Point2D;\n constructor(start: Point2D, end: Point2D);\n /** Length of the line segment. */\n get length(): number;\n /** Midpoint of the line segment. */\n get midpoint(): Point2D;\n /** Direction angle in degrees, measured CCW from +X. */\n get angle(): number;\n /** Unit direction vector from start to end. */\n get direction(): [\n number,\n number\n ];\n /**\n * Create a parallel line offset by the given distance.\n *\n * Positive distance shifts to the left of the line direction.\n */\n parallel(distance: number): Line2D;\n /**\n * Intersect this line with another infinite line.\n *\n * @param other - The other line.\n * @returns The intersection point, or `null` if parallel.\n */\n intersect(other: Line2D): Point2D | null;\n /**\n * Intersect this line with another as bounded segments.\n *\n * @param other - The other line.\n * @returns The intersection point, or `null` if the segments do not cross.\n */\n intersectSegment(other: Line2D): Point2D | null;\n /**\n * Create a line from raw coordinates.\n *\n * @param x1 - Start X.\n * @param y1 - Start Y.\n * @param x2 - End X.\n * @param y2 - End Y.\n * @returns A new line segment.\n */\n static fromCoordinates(x1: number, y1: number, x2: number, y2: number): Line2D;\n /**\n * Create a line from a start point, angle, and length.\n *\n * @param origin - Start point.\n * @param angleDeg - Angle in degrees, CCW from +X.\n * @param length - Segment length.\n * @returns A new line segment.\n */\n static fromPointAndAngle(origin: Point2D, angleDeg: number, length: number): Line2D;\n /**\n * Create a line from a start point, direction vector, and length.\n *\n * @param origin - Start point.\n * @param dir - Direction vector `[x, y]`.\n * @param length - Segment length.\n * @returns A new line segment.\n */\n static fromPointAndDirection(origin: Point2D, dir: [\n number,\n number\n ], length: number): Line2D;\n}\n/**\n * Create an analytic 2D line segment between two points.\n *\n * @softDeprecated Line2D.fromCoordinates(x1, y1, x2, y2) — or cs.line(a, b) inside constrainedSketch()\n * @deprecated use Line2D.fromCoordinates(x1, y1, x2, y2) — or cs.line(a, b) inside constrainedSketch()\n * @param x1 - Start X\n * @param y1 - Start Y\n * @param x2 - End X\n * @param y2 - End Y\n * @returns A new `Line2D`\n * @category 2D Entities\n */\ndeclare function line(x1: number, y1: number, x2: number, y2: number): Line2D;\n/**\n * An immutable 2D circle with area, circumference, and extrusion support.\n *\n * Extruding a `Circle2D` produces a cylinder with named `top`, `bottom`,\n * and `side` faces accessible via the topology API.\n *\n * @category 2D Entities\n */\ndeclare class Circle2D {\n readonly center: Point2D;\n readonly radius: number;\n constructor(center: Point2D, radius: number);\n /** Diameter of the circle. */\n get diameter(): number;\n /** Circumference of the circle. */\n get circumference(): number;\n /** Area of the circle. */\n get area(): number;\n /**\n * Return a point on the circle at the given angle.\n *\n * @param angleDeg - Angle in degrees, where 0 is right and CCW is positive.\n * @returns A point on the perimeter.\n */\n pointAtAngle(angleDeg: number): Point2D;\n /**\n * Return a translated circle.\n *\n * @param dx - Horizontal offset.\n * @param dy - Vertical offset.\n * @returns A shifted circle.\n */\n translate(dx: number, dy: number): Circle2D;\n /**\n * Convert this circle to a sketch profile.\n *\n * @param segments - Optional polygon approximation resolution.\n * @returns A sketch representation of the circle.\n */\n toSketch(segments?: number): Sketch;\n /**\n * Extrude the circle into a solid cylinder.\n *\n * @param height - Extrusion height.\n * @param segments - Optional tessellation resolution.\n * @returns A new shape with named cylinder faces.\n */\n extrude(height: number, segments?: number): Shape;\n /**\n * Create a circle from its center and radius.\n *\n * @param center - Circle center.\n * @param radius - Circle radius.\n * @returns A new circle.\n */\n static fromCenterAndRadius(center: Point2D, radius: number): Circle2D;\n /**\n * Create a circle from its center and diameter.\n *\n * @param center - Circle center.\n * @param diameter - Circle diameter.\n * @returns A new circle.\n */\n static fromDiameter(center: Point2D, diameter: number): Circle2D;\n}\n/**\n * Create an analytic 2D circle for measurement, construction, and extrusion.\n *\n * @softDeprecated Circle2D.fromCenterAndRadius(new Point2D(cx, cy), r) — or circle2d(r) for a sketch profile\n * @deprecated use Circle2D.fromCenterAndRadius(new Point2D(cx, cy), r) — or circle2d(r) for a sketch profile\n * @param cx - Center X\n * @param cy - Center Y\n * @param radius - Radius\n * @returns A new `Circle2D`\n * @category 2D Entities\n */\ndeclare function circle(cx: number, cy: number, radius: number): Circle2D;\ntype RectSide = "top" | "bottom" | "left" | "right";\ntype RectVertex = "top-left" | "top-right" | "bottom-left" | "bottom-right";\n/**\n * A rectangle with named sides, vertices, and extrusion support.\n *\n * **Details**\n *\n * Sides are named based on the rectangle\'s local orientation at construction\n * time. Vertices go: bottom-left, bottom-right, top-right, top-left (CCW).\n *\n * Use `rect()` for the normal centered sketch primitive. Use `Rectangle2D`\n * when you need named sides/vertices, or an extrusion with tracked vertical\n * edges such as `vert-br` for `filletTrackedEdge()` / `chamferTrackedEdge()`.\n *\n * Extruding a `Rectangle2D` produces a `Shape` with named faces:\n * `top`, `bottom`, `side-left`, `side-right`, `side-top`, `side-bottom`.\n * These are accessible via the topology API (`.face()`, `.edge()`).\n *\n * **Example**\n *\n * ```ts\n * const r = Rectangle2D.fromDimensions(0, 0, 100, 60);\n * r.side(\'top\'); r.side(\'left\'); // Line2D\n * r.vertex(\'top-left\'); // Point2D\n * r.width; r.height; r.center;\n * const [d1, d2] = r.diagonals(); // [bl-tr, br-tl]\n *\n * r.toSketch(); // Sketch (for 2D operations)\n * r.extrude(20); // Shape with named faces\n *\n * Rectangle2D.fromCenterAndDimensions(new Point2D(50, 30), 100, 60);\n * Rectangle2D.from2Corners(new Point2D(0, 0), new Point2D(100, 60));\n * Rectangle2D.from3Points(p1, p2, p3); // free-angle rectangle\n * ```\n *\n * @category 2D Entities\n */\ndeclare class Rectangle2D {\n /** Vertices in order: bottom-left, bottom-right, top-right, top-left */\n readonly vertices: [\n Point2D,\n Point2D,\n Point2D,\n Point2D\n ];\n constructor(vertices: [\n Point2D,\n Point2D,\n Point2D,\n Point2D\n ]);\n /** Width of the rectangle. */\n get width(): number;\n /** Height of the rectangle. */\n get height(): number;\n /** Geometric center of the rectangle. */\n get center(): Point2D;\n /**\n * Return a named side of the rectangle.\n *\n * @param name - One of `top`, `bottom`, `left`, or `right`.\n * @returns The requested side as a line segment.\n */\n side(name: RectSide): Line2D;\n /**\n * Return a side by index.\n *\n * @param index - Side index: `0=bottom`, `1=right`, `2=top`, `3=left`.\n * @returns The requested side as a line segment.\n */\n sideAt(index: number): Line2D;\n /**\n * Return a named vertex of the rectangle.\n *\n * @param name - One of `bottom-left`, `bottom-right`, `top-right`, or `top-left`.\n * @returns The requested vertex.\n */\n vertex(name: RectVertex): Point2D;\n /**\n * Return the two diagonals of the rectangle.\n *\n * @returns `[bottom-left to top-right, bottom-right to top-left]`.\n */\n diagonals(): [\n Line2D,\n Line2D\n ];\n /**\n * Convert the rectangle to a sketch profile.\n *\n * @returns A sketch representation of the rectangle.\n */\n toSketch(): Sketch;\n /**\n * Return a translated rectangle.\n *\n * @param dx - Horizontal offset.\n * @param dy - Vertical offset.\n * @returns A shifted rectangle.\n */\n translate(dx: number, dy: number): Rectangle2D;\n /**\n * Create an axis-aligned rectangle from origin corner plus width and height.\n *\n * @param x - Bottom-left X.\n * @param y - Bottom-left Y.\n * @param width - Rectangle width.\n * @param height - Rectangle height.\n * @returns A new rectangle.\n */\n static fromDimensions(x: number, y: number, width: number, height: number): Rectangle2D;\n /**\n * Create a rectangle centered on a point.\n *\n * @param center - Rectangle center.\n * @param width - Rectangle width.\n * @param height - Rectangle height.\n * @returns A new rectangle.\n */\n static fromCenterAndDimensions(center: Point2D, width: number, height: number): Rectangle2D;\n /**\n * Create an axis-aligned rectangle from two opposite corners.\n *\n * @param p1 - First corner.\n * @param p2 - Opposite corner.\n * @returns A new rectangle.\n */\n static from2Corners(p1: Point2D, p2: Point2D): Rectangle2D;\n /**\n * Create a free-angle rectangle from three points.\n *\n * `p1` and `p2` define one edge, and `p3` chooses the perpendicular side.\n *\n * @param p1 - First edge point.\n * @param p2 - Second edge point.\n * @param p3 - Point indicating the rectangle height direction.\n * @returns A new rectangle.\n */\n static from3Points(p1: Point2D, p2: Point2D, p3: Point2D): Rectangle2D;\n /**\n * Extrude the rectangle into a solid prism with named topology.\n *\n * @param height - Extrusion height.\n * @param up - When false, extrude downward instead of upward.\n * @returns A new shape with named faces and edges.\n */\n extrude(height: number, up?: boolean): Shape;\n}\ndeclare function polar(length: number, angleDeg: number, from?: [\n number,\n number\n]): [\n number,\n number\n];\ntype LineArg = LineId | Line2D;\ntype PointArg = PointId | Point2D;\n/**\n * Legacy constraint namespace — every member duplicates a\n * `ConstrainedSketchBuilder` method (`sk.parallel(a, b)` etc.), which is the\n * taught spelling. The builder methods auto-import `Point2D`/`Line2D`\n * entities, so nothing here is unique. Kept runtime-alive for existing\n * scripts; each member warns once per run naming its builder replacement.\n */\ndeclare const Constraint: {\n /**\n * Constrain two lines to be parallel.\n *\n * @softDeprecated sk.parallel(a, b)\n * @deprecated use sk.parallel(a, b)\n */\n makeParallel: (builder: ConstrainedSketchBuilder, a: LineArg, b: LineArg) => ConstrainedSketchBuilder;\n /**\n * Removed trap: the name suggested an unsigned angle but it emitted the\n * signed `angle` constraint. Throws with the exact replacements.\n *\n * @internal\n */\n enforceAngle(_builder: ConstrainedSketchBuilder, _a: LineArg, _b: LineArg, _angleDeg: number): never;\n /**\n * Constrain a line to be horizontal.\n *\n * @softDeprecated sk.horizontal(line)\n * @deprecated use sk.horizontal(line)\n */\n horizontal: (builder: ConstrainedSketchBuilder, line: LineArg) => ConstrainedSketchBuilder;\n /**\n * Constrain a line to be vertical.\n *\n * @softDeprecated sk.vertical(line)\n * @deprecated use sk.vertical(line)\n */\n vertical: (builder: ConstrainedSketchBuilder, line: LineArg) => ConstrainedSketchBuilder;\n /**\n * Constrain two lines to have equal length.\n *\n * @softDeprecated sk.equal(a, b)\n * @deprecated use sk.equal(a, b)\n */\n equalLength: (builder: ConstrainedSketchBuilder, a: LineArg, b: LineArg) => ConstrainedSketchBuilder;\n /**\n * Constrain the distance between two points.\n *\n * @softDeprecated sk.distance(a, b, value)\n * @deprecated use sk.distance(a, b, value)\n */\n distance: (builder: ConstrainedSketchBuilder, a: PointArg, b: PointArg, value: number) => ConstrainedSketchBuilder;\n /**\n * Fix a point at a specific coordinate.\n *\n * @softDeprecated sk.fix(point, x, y)\n * @deprecated use sk.fix(point, x, y)\n */\n fix: (builder: ConstrainedSketchBuilder, pt: PointArg, x: number, y: number) => ConstrainedSketchBuilder;\n /**\n * Constrain two points to occupy the same location.\n *\n * @softDeprecated sk.coincident(a, b)\n * @deprecated use sk.coincident(a, b)\n */\n coincident: (builder: ConstrainedSketchBuilder, a: PointArg, b: PointArg) => ConstrainedSketchBuilder;\n /**\n * Constrain two lines to be perpendicular.\n *\n * @softDeprecated sk.perpendicular(a, b)\n * @deprecated use sk.perpendicular(a, b)\n */\n perpendicular: (builder: ConstrainedSketchBuilder, a: LineArg, b: LineArg) => ConstrainedSketchBuilder;\n /**\n * Constrain the length of a line.\n *\n * @softDeprecated sk.length(line, value)\n * @deprecated use sk.length(line, value)\n */\n length: (builder: ConstrainedSketchBuilder, line: LineArg, value: number) => ConstrainedSketchBuilder;\n};\ntype FaceName = string;\ntype EdgeName = string;\ntype NurbsFaceTrimLoop = {\n kind: "nurbsUv";\n role: "outer" | "hole";\n loopIndex: number;\n degree: number;\n rational: boolean;\n controlPoints: [\n number,\n number\n ][];\n weights: number[];\n knots: number[];\n samples: number;\n faceName?: string;\n} | {\n kind: "polylineUv";\n role: "outer" | "hole";\n loopIndex: number;\n points: [\n number,\n number\n ][];\n faceName?: string;\n};\ntype FaceSurface = {\n kind: "plane";\n normal: Vec3;\n} | {\n kind: "cylinder";\n origin: Vec3;\n axis: Vec3;\n radius: number;\n height: number;\n} | {\n kind: "cone";\n origin: Vec3;\n axis: Vec3;\n radiusBottom: number;\n radiusTop: number;\n height: number;\n} | {\n kind: "sphere";\n center: Vec3;\n radius: number;\n} | {\n kind: "torus";\n center: Vec3;\n axis: Vec3;\n majorRadius: number;\n minorRadius: number;\n} | {\n kind: "ruled";\n rails: [\n [\n Vec3,\n Vec3\n ],\n [\n Vec3,\n Vec3\n ]\n ];\n} | {\n kind: "nurbs";\n degreeU: number;\n degreeV: number;\n rational: boolean;\n trimmed: boolean;\n thickness: number;\n trimLoops?: NurbsFaceTrimLoop[];\n};\ntype EdgeCurve = {\n kind: "line";\n start: Vec3;\n end: Vec3;\n faceName?: string;\n} | {\n kind: "circle";\n center: Vec3;\n axis: Vec3;\n radius: number;\n faceName?: string;\n} | {\n kind: "surfaceIso";\n surface: "nurbs";\n fixedParameter: "u" | "v";\n fixedValue: number;\n parameterRange: [\n number,\n number\n ];\n degree: number;\n rational: boolean;\n faceName?: string;\n} | {\n kind: "nurbsUv";\n surface: "nurbs";\n role: "outer" | "hole";\n loopIndex: number;\n degree: number;\n rational: boolean;\n controlPoints: [\n number,\n number\n ][];\n weights: number[];\n knots: number[];\n samples: number;\n faceName?: string;\n} | {\n kind: "polylineUv";\n surface: "nurbs";\n role: "outer" | "hole";\n loopIndex: number;\n points: [\n number,\n number\n ][];\n faceName?: string;\n};\ninterface FaceRef {\n name: FaceName;\n /** Normal direction of the face */\n normal: [\n number,\n number,\n number\n ];\n /** Center point of the face */\n center: [\n number,\n number,\n number\n ];\n /** Compiler-owned face query when available. */\n query?: FaceQueryRef;\n /** True when the face can host a 2D sketch placement frame */\n planar?: boolean;\n /** Face-local horizontal axis for planar faces */\n uAxis?: [\n number,\n number,\n number\n ];\n /** Face-local vertical axis for planar faces */\n vAxis?: [\n number,\n number,\n number\n ];\n /** Analytic surface family when the backend can identify one. */\n surface?: FaceSurface;\n /** Shared descendant-resolution metadata when this face is a semantic region/set. */\n descendant?: FaceDescendantMetadata;\n}\ninterface EdgeRef {\n name: EdgeName;\n /** Start point */\n start: [\n number,\n number,\n number\n ];\n /** End point */\n end: [\n number,\n number,\n number\n ];\n /** Compiler-owned edge query when available. */\n query?: EdgeQueryRef;\n /** Exact or parametric curve family when the backend/source can identify one. */\n curve?: EdgeCurve;\n /** Owning face name when the edge is associated with one face in a larger topology. */\n faceName?: string;\n}\ntype PlaneSpec = {\n origin: Vec3;\n normal: Vec3;\n} | {\n plane: "XY" | "XZ" | "YZ";\n offset?: number;\n} | {\n face: FaceRef;\n};\ntype Vec2 = [\n number,\n number\n];\ntype Vec3$1 = [\n number,\n number,\n number\n];\ntype TpmsThicknessMode = "legacy-field-threshold" | "metric-approx";\ntype SdfFunctionConstant = number | string | boolean | null | SdfFunctionConstant[] | {\n [key: string]: SdfFunctionConstant;\n};\ntype SdfFunctionConstants = Record<string, SdfFunctionConstant>;\ninterface SdfSurfacePatternConstantNode {\n kind: "surfacePattern:constant";\n value: number;\n}\ninterface SdfSurfacePatternSineWaveNode {\n kind: "surfacePattern:sineWave";\n direction: Vec2;\n wavelength: number;\n amplitude: number;\n phase: number;\n bias: number;\n}\ninterface SdfSurfacePatternStripesNode {\n kind: "surfacePattern:stripes";\n direction: Vec2;\n spacing: number;\n width: number;\n depth: number;\n}\ninterface SdfSurfacePatternOverUnderWeaveNode {\n kind: "surfacePattern:overUnderWeave";\n spacing: Vec2;\n threadWidth: Vec2;\n depth: number;\n underScale: number;\n}\ninterface SdfSurfacePatternUnaryNode {\n kind: "surfacePattern:abs" | "surfacePattern:negate";\n child: SdfSurfacePatternNode;\n}\ninterface SdfSurfacePatternNaryNode {\n kind: "surfacePattern:add" | "surfacePattern:multiply" | "surfacePattern:min" | "surfacePattern:max";\n children: SdfSurfacePatternNode[];\n}\ninterface SdfSurfacePatternClampNode {\n kind: "surfacePattern:clamp";\n child: SdfSurfacePatternNode;\n min: number;\n max: number;\n}\ntype SdfSurfacePatternNode = SdfSurfacePatternConstantNode | SdfSurfacePatternSineWaveNode | SdfSurfacePatternStripesNode | SdfSurfacePatternOverUnderWeaveNode | SdfSurfacePatternUnaryNode | SdfSurfacePatternNaryNode | SdfSurfacePatternClampNode;\ninterface SdfSphereNode {\n kind: "sdf:sphere";\n radius: number;\n}\ninterface SdfBoxNode {\n kind: "sdf:box";\n halfExtents: Vec3$1;\n}\ninterface SdfCylinderNode {\n kind: "sdf:cylinder";\n height: number;\n radius: number;\n}\ninterface SdfTorusNode {\n kind: "sdf:torus";\n majorRadius: number;\n minorRadius: number;\n}\ninterface SdfCapsuleNode {\n kind: "sdf:capsule";\n height: number;\n radius: number;\n}\ninterface SdfConeNode {\n kind: "sdf:cone";\n height: number;\n radius: number;\n}\ninterface SdfPolylineSweepNode {\n kind: "sdf:polylineSweep";\n /** Ordered path samples in world/local SDF space. */\n points: Vec3$1[];\n /** Radius at each corresponding point. */\n radii: number[];\n /** Smooth blend radius between adjacent tapered segments. */\n blend: number;\n}\ninterface SdfProfileExtrudeNode {\n kind: "sdf:profileExtrude";\n /** Closed 2D profile loops in XY, CCW outer loops and CW holes. */\n polygons: Vec2[][];\n height: number;\n twist?: number;\n scaleTop?: Vec2;\n}\ninterface SdfProfileRevolveNode {\n kind: "sdf:profileRevolve";\n /** Closed 2D profile loops where x is radius and y becomes world z. */\n polygons: Vec2[][];\n degrees: number;\n}\ninterface SdfUnionNode {\n kind: "sdf:union";\n children: SdfNode[];\n}\ninterface SdfDifferenceNode {\n kind: "sdf:difference";\n /** First child is the base; subsequent children are subtracted. */\n children: SdfNode[];\n}\ninterface SdfIntersectionNode {\n kind: "sdf:intersection";\n children: SdfNode[];\n}\ninterface SdfSmoothUnionNode {\n kind: "sdf:smoothUnion";\n children: SdfNode[];\n radius: number;\n}\ninterface SdfSmoothDifferenceNode {\n kind: "sdf:smoothDifference";\n children: SdfNode[];\n radius: number;\n}\ninterface SdfSmoothIntersectionNode {\n kind: "sdf:smoothIntersection";\n children: SdfNode[];\n radius: number;\n}\ninterface SdfMorphNode {\n kind: "sdf:morph";\n a: SdfNode;\n b: SdfNode;\n /** 0 = fully a, 1 = fully b */\n t: number;\n}\ninterface SdfTranslateNode {\n kind: "sdf:translate";\n child: SdfNode;\n offset: Vec3$1;\n}\ninterface SdfRotateNode {\n kind: "sdf:rotate";\n child: SdfNode;\n /** Euler angles in degrees (X, Y, Z) */\n degrees: Vec3$1;\n}\ninterface SdfScaleNode {\n kind: "sdf:scale";\n child: SdfNode;\n factor: number;\n}\ninterface SdfTwistNode {\n kind: "sdf:twist";\n child: SdfNode;\n /** Total twist angle in degrees over the full height of the shape */\n degreesPerUnit: number;\n}\ninterface SdfBendNode {\n kind: "sdf:bend";\n child: SdfNode;\n /** Bend radius — larger = gentler bend */\n radius: number;\n}\ninterface SdfRepeatNode {\n kind: "sdf:repeat";\n child: SdfNode;\n /** Spacing between repetitions [x, y, z]. 0 = no repetition on that axis. */\n spacing: Vec3$1;\n /** Max repetition count per side. 0 = infinite. */\n count: Vec3$1;\n}\ninterface SdfCircularArrayNode {\n kind: "sdf:circularArray";\n child: SdfNode;\n /** Number of copies arranged around the Z axis. */\n count: number;\n /** Distance to translate the source shape in +X before angular folding. */\n offset: number;\n}\ninterface SdfShellNode {\n kind: "sdf:shell";\n child: SdfNode;\n thickness: number;\n}\ninterface SdfOffsetNode {\n kind: "sdf:offset";\n child: SdfNode;\n /** Field offset in millimeters: positive dilates (rounds convex edges), negative erodes. */\n distance: number;\n}\ninterface SdfDisplaceNode {\n kind: "sdf:displace";\n child: SdfNode;\n /** Serialized as a function body string: receives (x, y, z) and returns a number. */\n functionBody: string;\n /** Named constants injected as additional function parameters (avoids closure serialization issues). */\n constants?: SdfFunctionConstants;\n}\ninterface SdfSurfaceDisplaceNode {\n kind: "sdf:surfaceDisplace";\n child: SdfNode;\n /** Typed built-in pattern, when available. Custom callbacks use patternBody only. */\n pattern?: SdfSurfacePatternNode;\n /**\n * Function body string: receives (u, v) in surface millimeters and returns a height\n * displacement value. Negative = into surface, positive = outward.\n */\n patternBody: string;\n /** Named constants injected as additional function parameters. */\n constants?: SdfFunctionConstants;\n /** Override auto-detected UV mode. Undefined or \'auto\' = auto-detect from child tree. */\n uvMode?: "auto" | "sphere" | "cylinder" | "torus" | "triplanar";\n /** Triplanar blend sharpness (only used when UV mode is triplanar). Default: 4. */\n triplanarSharpness?: number;\n}\ninterface SdfOnionNode {\n kind: "sdf:onion";\n child: SdfNode;\n /** Number of concentric layers */\n layers: number;\n thickness: number;\n}\ninterface SdfGyroidNode {\n kind: "sdf:gyroid";\n cellSize: number;\n thickness: number;\n thicknessMode?: TpmsThicknessMode;\n}\ninterface SdfSchwarzPNode {\n kind: "sdf:schwarzP";\n cellSize: number;\n thickness: number;\n thicknessMode?: TpmsThicknessMode;\n}\ninterface SdfDiamondNode {\n kind: "sdf:diamond";\n cellSize: number;\n thickness: number;\n thicknessMode?: TpmsThicknessMode;\n}\ninterface SdfLidinoidNode {\n kind: "sdf:lidinoid";\n cellSize: number;\n thickness: number;\n thicknessMode?: TpmsThicknessMode;\n}\ninterface SdfSpatialBlendNode {\n kind: "sdf:spatialBlend";\n a: SdfNode;\n b: SdfNode;\n /** Function body returning 0..1. 0 = fully a, 1 = fully b. */\n functionBody: string;\n constants?: SdfFunctionConstants;\n}\ninterface SdfNoiseNode {\n kind: "sdf:noise";\n /** Spatial frequency — smaller = larger features. */\n scale: number;\n /** Peak displacement amplitude. */\n amplitude: number;\n /** Number of octaves for fractal Brownian motion (1 = plain simplex). */\n octaves: number;\n /** Seed for deterministic variation. 0 = default permutation. */\n seed: number;\n}\ninterface SdfVoronoiNode {\n kind: "sdf:voronoi";\n /** Size of each Voronoi cell in world units. */\n cellSize: number;\n /** Wall thickness between cells. */\n wallThickness: number;\n /** Seed for deterministic variation. */\n seed: number;\n /**\n * When set, enables surface-aware mode using IQ two-pass with membrane suppression.\n * The child SDF\'s gradient is used to estimate the surface normal, and walls aligned\n * with that normal are suppressed. This is the child SDF tree whose gradient provides\n * the surface normal for filtering.\n */\n surfaceChild?: SdfNode;\n /**\n * Membrane suppression threshold (0..1). Higher = more aggressive suppression.\n * 0 = no filtering, 1 = suppress all walls. Default: 0.7.\n */\n suppressionThreshold?: number;\n}\ninterface SdfCustomNode {\n kind: "sdf:custom";\n /** Function body string: receives (x, y, z) and returns signed distance. */\n functionBody: string;\n /** GLSL expression generated from the same source expression, when shader-compatible. */\n shaderBody?: string;\n /** Why shaderBody could not be generated. */\n shaderUnsupportedReason?: string;\n /** Conservative maximum shader step for this field. */\n raymarchStepLimit?: number;\n /** Optional divisor applied to shader distance for non-distance fields. */\n raymarchLipschitz?: number;\n bounds: {\n min: Vec3$1;\n max: Vec3$1;\n };\n /** Named constants injected as additional function parameters (avoids closure serialization issues). */\n constants?: SdfFunctionConstants;\n}\ntype SdfNode = SdfSphereNode | SdfBoxNode | SdfCylinderNode | SdfTorusNode | SdfCapsuleNode | SdfConeNode | SdfPolylineSweepNode | SdfProfileExtrudeNode | SdfProfileRevolveNode | SdfUnionNode | SdfDifferenceNode | SdfIntersectionNode | SdfSmoothUnionNode | SdfSmoothDifferenceNode | SdfSmoothIntersectionNode | SdfMorphNode | SdfTranslateNode | SdfRotateNode | SdfScaleNode | SdfTwistNode | SdfBendNode | SdfRepeatNode | SdfCircularArrayNode | SdfShellNode | SdfOffsetNode | SdfDisplaceNode | SdfSurfaceDisplaceNode | SdfOnionNode | SdfGyroidNode | SdfSchwarzPNode | SdfDiamondNode | SdfLidinoidNode | SdfSpatialBlendNode | SdfNoiseNode | SdfVoronoiNode | SdfCustomNode;\ninterface SdfBounds {\n min: Vec3$1;\n max: Vec3$1;\n}\ntype SdfMeshingQuality = "draft" | "preview" | "export";\ninterface SdfToShapeOptions {\n /** Target mesh edge length. Smaller = finer mesh. Overrides quality-derived resolution. */\n edgeLength?: number;\n /** Override auto-computed bounds. Strongly recommended for infinite/repeated fields. */\n bounds?: {\n min: Vec3$1;\n max: Vec3$1;\n };\n /** Coarse quality preset. Default: \'preview\'. */\n quality?: SdfMeshingQuality;\n /** Preferred absolute surface tolerance in millimeters. */\n tolerance?: number;\n /** Smallest feature that should survive meshing, in millimeters. */\n minFeatureSize?: number;\n /** Simplification control. `false` disables, `true` and `\'safe\'` use topology-validated simplification. */\n simplify?: boolean | "safe";\n /** Optional post-extraction triangle budget. Fractional values are floored for compatibility. */\n maxTriangles?: number;\n /** Optional pre-extraction grid-point budget. Default is browser-safe. */\n maxGridPoints?: number;\n /** Lower clamp for resolved edge length. Default: 0.15mm. */\n minEdgeLength?: number;\n /** Log resolved meshing settings and backend extraction timings. */\n diagnostics?: boolean;\n}\ndeclare const SHEET_METAL_EDGES: readonly [\n "top",\n "right",\n "bottom",\n "left"\n];\ntype SheetMetalEdge = (typeof SHEET_METAL_EDGES)[number];\ntype SheetMetalPlanarRegionName = "panel" | `flange-${SheetMetalEdge}`;\ntype SheetMetalRegionName = SheetMetalPlanarRegionName | `bend-${SheetMetalEdge}`;\ninterface SheetMetalBendAllowance {\n kind: "k-factor";\n kFactor: number;\n}\ninterface SheetMetalPanelSpec {\n width: number;\n height: number;\n}\ninterface SheetMetalFlangeSpec {\n edge: SheetMetalEdge;\n length: number;\n angleDeg: number;\n}\ninterface SheetMetalCornerReliefSpec {\n kind: "rect";\n size: number;\n}\ninterface SheetMetalModel {\n panel: SheetMetalPanelSpec;\n thickness: number;\n bendRadius: number;\n bendAllowance: SheetMetalBendAllowance;\n cornerRelief: SheetMetalCornerReliefSpec;\n flanges: SheetMetalFlangeSpec[];\n}\ninterface EdgeSegment {\n /** Stable index within the extraction (deterministic for a given mesh). */\n index: number;\n start: Vec3;\n end: Vec3;\n midpoint: Vec3;\n /** Normalized direction from start → end. */\n direction: Vec3;\n length: number;\n /** Dihedral angle in degrees (0 = coplanar, 180 = knife edge). */\n dihedralAngle: number;\n /** true = outside corner (convex), false = inside corner (concave). */\n convex: boolean;\n /** Normal of first adjacent face. */\n normalA: Vec3;\n /** Normal of second adjacent face (same as normalA for boundary edges). */\n normalB: Vec3;\n /** true if this is a boundary (unmatched) edge — unusual for closed solids. */\n boundary: boolean;\n /** Native kernel topology identity when the active backend can provide one. */\n nativeTopology?: EdgeNativeTopologyRef;\n}\ninterface BoundingRegion {\n xMin?: number;\n xMax?: number;\n yMin?: number;\n yMax?: number;\n zMin?: number;\n zMax?: number;\n}\n/**\n * Filter and sort criteria for edge selection.\n *\n * All filters are combined with AND logic — an edge must pass every\n * specified filter to be included. Omitting a filter means "any".\n *\n * @category Edge Queries\n */\ninterface EdgeQuery {\n /** Sort by proximity to this point (closest first). When used with `selectEdge`, picks the closest match. */\n near?: Vec3;\n /** Filter: edge direction approximately parallel to this vector. */\n parallel?: Vec3;\n /** Filter: edge direction approximately perpendicular to this vector. */\n perpendicular?: Vec3;\n /** Filter: only convex (outside corner) edges. */\n convex?: boolean;\n /** Filter: only concave (inside corner) edges. */\n concave?: boolean;\n /** Filter: minimum dihedral angle in degrees. */\n minAngle?: number;\n /** Filter: maximum dihedral angle in degrees. */\n maxAngle?: number;\n /** Filter: minimum edge length. */\n minLength?: number;\n /** Filter: maximum edge length. */\n maxLength?: number;\n /** Filter: edge midpoint must be within this bounding region. */\n within?: BoundingRegion;\n /** Shorthand: edge midpoint Z is approximately this value within `tolerance`. */\n atZ?: number;\n /** Position tolerance for approximate matches. Used by `atZ` and `near`. Default: `1.0`. */\n tolerance?: number;\n /** Angular tolerance in degrees for `parallel`/`perpendicular` filters. Default: `10`. */\n angleTolerance?: number;\n}\ninterface Route3DPortFrameCompilePlan {\n name: string;\n origin: Vec3;\n axis: Vec3;\n xAxis: Vec3;\n yAxis: Vec3;\n station: number;\n}\ntype Route3DSegmentCompilePlan = {\n kind: "line";\n from: Vec3;\n to: Vec3;\n length: number;\n} | {\n kind: "arc";\n center: Vec3;\n radius: number;\n axis: Vec3;\n start: Vec3;\n end: Vec3;\n sweepDeg: number;\n length: number;\n};\ninterface Route3DCompilePlan {\n kind: "route3d";\n segments: Route3DSegmentCompilePlan[];\n ports: Record<string, Route3DPortFrameCompilePlan>;\n length: number;\n}\ntype SweepPathCompilePlan = {\n kind: "polyline";\n points: [\n number,\n number,\n number\n ][];\n} | {\n kind: "catmull-rom";\n controlPoints: [\n number,\n number,\n number\n ][];\n tension: number;\n closed: boolean;\n} | {\n kind: "hermite";\n p0: [\n number,\n number,\n number\n ];\n p1: [\n number,\n number,\n number\n ];\n t0: [\n number,\n number,\n number\n ];\n t1: [\n number,\n number,\n number\n ];\n chordLength: number;\n} | {\n kind: "quintic-hermite";\n p0: [\n number,\n number,\n number\n ];\n p1: [\n number,\n number,\n number\n ];\n t0: [\n number,\n number,\n number\n ];\n t1: [\n number,\n number,\n number\n ];\n c0: [\n number,\n number,\n number\n ];\n c1: [\n number,\n number,\n number\n ];\n chordLength: number;\n} | {\n kind: "nurbs";\n controlPoints: [\n number,\n number,\n number\n ][];\n weights: number[];\n knots: number[];\n degree: number;\n closed: boolean;\n} | Route3DCompilePlan;\ntype SurfaceContinuity = "G0" | "G1" | "G2";\ntype SurfaceFillStyle = "coons" | "curved" | "stretch";\ninterface SurfaceDomainCompilePlan {\n uMin: number;\n uMax: number;\n vMin: number;\n vMax: number;\n}\ninterface TransformationStep {\n kind: string;\n description: string;\n details?: Record<string, unknown>;\n}\ntype TimelineEntryCategory = "primitive" | "sketch" | "modifier" | "boolean" | "transform";\ninterface TimelineEntry {\n kind: string;\n label: string;\n summary: string;\n category: TimelineEntryCategory;\n}\ninterface FaceTransformationHistory {\n faceName: string;\n origin: {\n operation: string;\n owner?: ShapeQueryOwner;\n };\n transformations: TransformationStep[];\n query?: FaceQueryRef;\n /** Ordered list of operations that built this shape, oldest first. */\n timeline: TimelineEntry[];\n}\n/**\n * FaceQuery — declarative face selector types for geometric face matching.\n *\n * `FaceSelector` is a string label name. `FaceQuery` is a structured\n * object that matches faces by geometry (normal, nearest, area, etc.)\n * and is used internally by compile-plan face resolution.\n */\ninterface FaceQuery {\n /** Filter by face normal direction (cosine similarity > 0.9998) */\n normal?: [\n number,\n number,\n number\n ];\n /** Pick face whose centroid is nearest to this point (XY or XYZ) */\n nearest?: [\n number,\n number\n ] | [\n number,\n number,\n number\n ];\n /** Pick face containing/nearest to this world point */\n at?: [\n number,\n number,\n number\n ];\n /** Disambiguation when multiple faces match */\n pick?: "largest" | "smallest" | "max-x" | "max-y" | "max-z" | "min-x" | "min-y" | "min-z";\n /** Filter by area range (mm²) */\n area?: {\n min?: number;\n max?: number;\n };\n /** Only match planar faces (default: true) */\n planar?: boolean;\n}\n/** A face label name. Pass to `shape.face()` to look up a user-authored label. */\ntype FaceSelector = string;\ntype PlacementReferenceKind = "points" | "edges" | "surfaces" | "objects";\ninterface PlacementEdgeRef {\n start: Vec3;\n end: Vec3;\n}\ninterface PlacementSurfaceRef {\n center: Vec3;\n normal: Vec3;\n}\ninterface PlacementObjectRef {\n min: Vec3;\n max: Vec3;\n}\ninterface PlacementReferences {\n points: Record<string, Vec3>;\n edges: Record<string, PlacementEdgeRef>;\n surfaces: Record<string, PlacementSurfaceRef>;\n objects: Record<string, PlacementObjectRef>;\n}\ntype PlacementObjectInput = PlacementObjectRef | {\n min: [\n number,\n number,\n number\n ];\n max: [\n number,\n number,\n number\n ];\n} | {\n boundingBox(): {\n min: number[];\n max: number[];\n };\n} | {\n _bbox(): {\n min: number[];\n max: number[];\n };\n};\ninterface PlacementReferenceInput {\n points?: Record<string, [\n number,\n number,\n number\n ]>;\n edges?: Record<string, PlacementEdgeRef>;\n surfaces?: Record<string, PlacementSurfaceRef>;\n objects?: Record<string, PlacementObjectInput>;\n}\ntype PlacementAnchorLike = Anchor3D | string;\ntype GroupChild = Shape | Sketch | ShapeGroup;\ninterface NamedGroupChild {\n name: string;\n /** Viewport organization tags for command-palette hide/show/focus workflows. */\n tags?: string | readonly string[];\n shape?: Shape | ShapeGroup;\n sketch?: Sketch;\n group?: GroupInput[] | ShapeGroup;\n}\ntype GroupInput = GroupChild | NamedGroupChild;\ndeclare class ShapeGroup {\n readonly children: GroupChild[];\n readonly childNames: Array<string | undefined>;\n private readonly childTags;\n constructor(children: GroupChild[], childNames?: Array<string | undefined>, childTags?: Array<string | readonly string[] | undefined | null>);\n /** Return the optional name of the child at `index`. */\n childName(index: number): string | undefined;\n /**\n * Return tags attached to the child at `index`.\n * @internal\n */\n tagsForChild(index: number): string[];\n /**\n * Return the named child by name. Throws if not found.\n * Useful when importing a multipart group and working on components individually.\n */\n child(name: string): GroupChild;\n /** Apply fn to all children, producing a new ShapeGroup that also copies placement refs. */\n private mapChildren;\n /** Apply fn to all children and also transform placement refs by the given matrix. */\n private mapChildrenTransform;\n /** Return a deep-cloned ShapeGroup tree (refs copied). */\n clone(): ShapeGroup;\n /** Alias for clone() */\n duplicate(): ShapeGroup;\n /** Move the entire group by (x, y, z). All children move together as a unit. */\n translate(x: number, y: number, z: number): ShapeGroup;\n /** Compute combined bounding box of all 3D children */\n private _bbox;\n private _bboxCenter;\n /** Return the combined 3D bounding box of all children. */\n boundingBox(): {\n min: [\n number,\n number,\n number\n ];\n max: [\n number,\n number,\n number\n ];\n };\n private resolveRotatePoint;\n /** Move the group so its bounding-box min corner lands at the given coordinate. */\n moveTo(x: number, y: number, z: number): ShapeGroup;\n /** Move the group relative to another part\'s bounding-box min corner. */\n moveToLocal(target: Shape | ShapeGroup, x: number, y: number, z: number): ShapeGroup;\n /**\n * Attach this group to a face or anchor on another part.\n *\n * `targetAnchor` can be a built-in anchor name or a custom reference name\n * on the target. `selfAnchor` selects the anchor on this group to align.\n */\n attachTo(target: Shape | ShapeGroup, targetAnchor: Anchor3D | string, selfAnchor?: Anchor3D, offset?: [\n number,\n number,\n number\n ]): ShapeGroup;\n /**\n * Place this group on a face of a parent shape.\n * See Shape.onFace() for full documentation.\n */\n onFace(parent: Shape | ShapeGroup, face: "front" | "back" | "left" | "right" | "top" | "bottom", opts?: {\n u?: number;\n v?: number;\n protrude?: number;\n }): ShapeGroup;\n /** Rotate the group around an arbitrary axis through the origin. Unlike `scale()`/`mirror()` (bounding-box center) and `Sketch.rotate()`, this pivots at the world origin — pass `options.pivot` to rotate in place. */\n rotate(axis: [\n number,\n number,\n number\n ], angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): ShapeGroup;\n /** Rotate the group around the X axis. */\n rotateX(angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): ShapeGroup;\n /** Rotate the group around the Y axis. */\n rotateY(angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): ShapeGroup;\n /** Rotate the group around the Z axis. */\n rotateZ(angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): ShapeGroup;\n /** Rotate around an arbitrary axis, optionally through a pivot point. */\n rotateAroundAxis(axis: [\n number,\n number,\n number\n ], angleDeg: number, pivot?: [\n number,\n number,\n number\n ]): ShapeGroup;\n /**\n * Rotate around an axis until a moving point reaches the target line/plane defined by the axis and target point.\n * ShapeGroup string points use built-in anchors only.\n */\n rotateAroundTo(axis: [\n number,\n number,\n number\n ], pivot: [\n number,\n number,\n number\n ], movingPoint: Anchor3D | [\n number,\n number,\n number\n ], targetPoint: Anchor3D | [\n number,\n number,\n number\n ], options?: RotateAroundToOptions): ShapeGroup;\n /** Reorient the group so its local Z axis points along `direction`. */\n pointAlong(direction: [\n number,\n number,\n number\n ]): ShapeGroup;\n /** Apply a 4x4 transform matrix or `Transform` to all 3D children. */\n transform(m: Mat4 | Transform): ShapeGroup;\n /** Scale uniformly or per-axis from the group\'s bounding-box center. */\n scale(v: number | [\n number,\n number,\n number\n ]): ShapeGroup;\n /** Scale uniformly or per-axis from an explicit pivot point. */\n scaleAround(pivot: [\n number,\n number,\n number\n ], v: number | [\n number,\n number,\n number\n ]): ShapeGroup;\n /** Mirror across a plane through the group\'s bounding-box center. */\n mirror(normal: [\n number,\n number,\n number\n ]): ShapeGroup;\n /** Mirror across a plane through an explicit point. */\n mirrorThrough(point: [\n number,\n number,\n number\n ], normal: [\n number,\n number,\n number\n ]): ShapeGroup;\n /** Return a copy of the group with the given display color applied to each child. */\n color(hex: string): ShapeGroup;\n /**\n * Attach named placement references to this group.\n * References survive normal transforms (translate/rotate/scale/mirror/transform).\n *\n * ```javascript\n * const bracket = group(\n * { name: \'Left\', shape: leftShape },\n * { name: \'Right\', shape: rightShape },\n * ).withReferences({\n * points: { mountCenter: [0, 0, 0] },\n * });\n * ```\n */\n withReferences(refs: PlacementReferenceInput): ShapeGroup;\n /** List named placement references carried by this group. */\n referenceNames(kind?: PlacementReferenceKind): string[];\n /** Backward-compatible alias for `withConnectors()`. @deprecated Use `withConnectors()` instead. */\n withPorts(ports: Record<string, PortInput>): ShapeGroup;\n /** Backward-compatible alias for `connectorNames()`. @deprecated Use `connectorNames()` instead. */\n portNames(): string[];\n /**\n * Resolve a named placement reference or built-in Anchor3D to a 3D point.\n * Named refs take priority over built-in anchors.\n */\n referencePoint(ref: PlacementAnchorLike): [\n number,\n number,\n number\n ];\n /**\n * Translate the group so the given anchor or reference lands on the target coordinate.\n *\n * Accepts any built-in anchor name (`\'bottom\'`, `\'center\'`, `\'top-front-left\'`, etc.)\n * or a custom placement reference attached via `withReferences()`.\n *\n * ```javascript\n * // Ground a group — put its bottom at Z = 0\n * assembly.placeReference(\'bottom\', [0, 0, 0])\n *\n * // Use a custom reference from a multi-file part\n * const placed = require(\'./bracket-assembly.forge.js\').group\n * .placeReference(\'mountCenter\', [0, 0, 50]);\n * ```\n */\n placeReference(ref: PlacementAnchorLike, target: [\n number,\n number,\n number\n ], offset?: [\n number,\n number,\n number\n ]): ShapeGroup;\n /** Attach named connectors — attachment points that survive transforms.\n * Connectors can be bare (position + orientation) or typed (with connectorType/gender for compatibility matching). */\n withConnectors(connectors: Record<string, ConnectorInput>): ShapeGroup;\n /** List all connector names, including "ChildName.connectorName" from named children. */\n connectorNames(): string[];\n /** Get all connectors of a given type, including from named children. */\n connectorsByType(type: string): Array<{\n name: string;\n port: ConnectorDef;\n }>;\n /** Distance between two connector origins on this group (supports dotted child paths). */\n connectorDistance(nameA: string, nameB: string): number;\n /** Get measurements metadata from a connector (supports dotted child paths). */\n connectorMeasurements(name: string): Record<string, number | string>;\n /**\n * Position this group by matching connectors to a target.\n * Connector names support dotted paths into named children: "ChildName.connectorName".\n *\n * Alignment: with a single connector pair, the group translates and rotates so the connector\n * origins coincide and the axes oppose (plug-in model); `up` pins the roll. With multiple pairs,\n * the connector origins define the rigid transform — still author meaningful `axis`/`up` values\n * so the same connectors remain useful for `connect()`, audits, and future matching.\n *\n * Overloads:\n * - Single pair: `matchTo(target, selfConn, targetConn, options?)`\n * - Dictionary (same target): `matchTo(target, { selfConn: targetConn, ... }, options?)`\n * - Multi-target: `matchTo([ [target1, selfConn1, targetConn1], ... ], options?)`\n */\n matchTo(targetOrPairs: Shape | ShapeGroup | Array<[\n Shape | ShapeGroup,\n string,\n string\n ]>, selfConnOrDict?: string | Record<string, string>, targetConnOrOptions?: string | MatchToOptions, maybeOptions?: MatchToOptions): ShapeGroup;\n}\n/**\n * Group multiple shapes/sketches for joint transforms without merging into a single mesh.\n *\n * Unlike union(), child colors and individual identities are preserved.\n * Children can be plain shapes, named descriptors ({ name, shape/sketch/group }),\n * or nested groups. The returned ShapeGroup supports all Shape transforms\n * (translate, rotate, etc.).\n *\n * Named descriptors can include `tags` for viewport organization. Tags do not\n * affect geometry; they let the command palette hide, show only, or focus all\n * objects with the same tag.\n *\n * **Local coordinate pattern:** Build child parts at the origin (local coordinates),\n * then group and translate once to place the whole assembly. This eliminates the\n * error-prone pattern of manually adding parent offsets to every sub-part.\n *\n * @example\n * const body = roundedBox(100, 20, 32, 4);\n * const panel = box(98, 2, 18).translate(0, -12, 4);\n * const louver = box(88, 2, 6).translate(0, -14, -11);\n * const indoorUnit = group(\n * { name: \'Body\', shape: body },\n * { name: \'Panel\', tags: \'cover\', shape: panel },\n * { name: \'Louver\', tags: [\'cover\', \'moving\'], shape: louver },\n * ).translate(0, -18, 70);\n */\ndeclare function group(...items: GroupInput[]): ShapeGroup;\n/** Options for `edgesOfFace()`. */\ninterface EdgesOfOptions {\n /** Exclude edges shared with these named faces. */\n exclude?: string | string[];\n /** Additional geometric filter: only convex edges. */\n convex?: boolean;\n /** Additional geometric filter: only concave edges. */\n concave?: boolean;\n /** Minimum edge length filter. */\n minLength?: number;\n}\ninterface SeatIntoOptions {\n /** Movement axis. Default: inverted face normal (points into target). */\n along?: [\n number,\n number,\n number\n ];\n /** How deep to embed. \'full\' = entire face inside. \'flush\' = nearest point touches. number = mm past flush. Default: \'full\'. */\n depth?: "full" | "flush" | number;\n /** Standoff gap in mm. Positive = gap between face and target. Negative = extra penetration. Default: 0. */\n gap?: number;\n}\ntype SdfFunctionSource = string | ((x: number, y: number, z: number, ...constants: number[]) => number);\ndeclare class SurfacePattern {\n /** Function body: receives (u, v) in surface mm, returns height displacement. */\n readonly body: string;\n /** Named constants injected into the function. */\n readonly constants?: Record<string, number>;\n constructor(body: string, constants?: Record<string, number>);\n}\ntype Pattern2DInput = Pattern2D | number;\ndeclare abstract class Pattern2D extends SurfacePattern {\n protected constructor(body: string);\n /** Add this pattern to one or more patterns or constant height offsets. */\n add(...patterns: Pattern2DInput[]): Pattern2D;\n /** Subtract another pattern or constant height offset from this pattern. */\n subtract(pattern: Pattern2DInput): Pattern2D;\n /** Multiply this pattern by one or more patterns or numeric scale factors. */\n multiply(...patterns: Pattern2DInput[]): Pattern2D;\n /** Keep the lower height between this pattern and one or more other patterns. */\n min(...patterns: Pattern2DInput[]): Pattern2D;\n /** Keep the higher height between this pattern and one or more other patterns. */\n max(...patterns: Pattern2DInput[]): Pattern2D;\n /** Limit pattern height to the inclusive `[min, max]` range in millimeters. */\n clamp(min: number, max: number): Pattern2D;\n /** Convert negative heights to positive heights. */\n abs(): Pattern2D;\n /** Flip the pattern height sign. */\n negate(): Pattern2D;\n}\ninterface Pattern2DSineWaveOptions {\n /** Direction the wave advances in UV space. Default: [1, 0]. */\n direction?: Vec2;\n /** Distance between wave peaks in surface millimeters. */\n wavelength: number;\n /** Height amplitude in millimeters. Default: 1. */\n amplitude?: number;\n /** Phase offset in radians. Default: 0. */\n phase?: number;\n /** Constant height offset in millimeters. Default: 0. */\n bias?: number;\n}\ninterface Pattern2DStripesOptions {\n /** Direction perpendicular to the stripe bands in UV space. Default: [1, 0]. */\n direction?: Vec2;\n /** Center-to-center spacing in surface millimeters. */\n spacing: number;\n /** Stripe width in surface millimeters. */\n width: number;\n /** Stripe groove depth in millimeters. Default: 1. */\n depth?: number;\n}\ninterface Pattern2DOverUnderWeaveOptions {\n /** Thread center-to-center spacing. A number uses the same spacing for U and V. */\n spacing: number | Vec2;\n /** Thread width. A number uses the same width for U and V. */\n threadWidth: number | Vec2;\n /** Thread groove depth in millimeters. Default: 0.8. */\n depth?: number;\n /** Relative height of the under-crossing thread. Default: 0.15. */\n underScale?: number;\n}\ndeclare class Pattern2DBuilder {\n /** Create a constant-height pattern in millimeters. */\n constant(value?: number): Pattern2D;\n /** Create a sinusoidal wave pattern in UV space. */\n sineWave(options: Pattern2DSineWaveOptions): Pattern2D;\n /** Create recessed stripe bands in UV space. */\n stripes(options: Pattern2DStripesOptions): Pattern2D;\n /** Create an over-under woven relief pattern in UV space. */\n overUnderWeave(options: Pattern2DOverUnderWeaveOptions): Pattern2D;\n}\ndeclare function pattern2d(): Pattern2DBuilder;\ninterface SurfaceDisplaceOptions {\n /** Override auto-detected UV mode. Default: \'auto\' (detects from SDF tree). */\n uv?: "auto" | "sphere" | "cylinder" | "torus" | "triplanar";\n /** Triplanar blend sharpness — higher = crisper transitions. Default: 4. Only used in triplanar mode. */\n triplanarSharpness?: number;\n}\ninterface SdfFunctionOptions {\n /** Finite design bounds for this opaque custom field. */\n bounds: SdfBounds | [\n Vec3$1,\n Vec3$1\n ];\n /** Named finite constants referenced from the expression body. */\n constants?: SdfFunctionConstants;\n /** Conservative maximum step for shader-preview tooling. */\n maxStep?: number;\n /** Optional divisor for non-distance fields. Values above 1 slow shader tooling. */\n lipschitz?: number;\n}\ninterface SdfVisualMetadata {\n colorHex?: string;\n materialProps?: ShapeMaterialProps;\n bounds?: SdfBounds;\n}\ntype SculptMaterialPreset = "ceramic" | "porcelain" | "soft-rubber" | "glass" | "mint-glass" | "candy" | "strawberry-gel" | "metal" | "clay";\ntype SculptPolishInput = SculptMaterialPreset | (ShapeMaterialProps & {\n color?: string;\n});\ndeclare function knownSculptMaterialPresets(): SculptMaterialPreset[];\n/**\n * An immutable SDF expression. Supports SDF-specific operations (smooth booleans,\n * domain warps, etc.), can be returned directly for native preview, and converts\n * to a ForgeCAD Shape via `.toShape()` when materialization is needed.\n */\ndeclare class SdfShape {\n /** @internal */\n readonly _node: SdfNode;\n /** @internal */\n readonly _visual: SdfVisualMetadata;\n /** @internal */\n constructor(node: SdfNode, visual?: SdfVisualMetadata);\n /** Display color carried by this implicit leaf. */\n get colorHex(): string | undefined;\n /** Display material carried by this implicit leaf. */\n get materialProps(): ShapeMaterialProps | undefined;\n /** Explicit bounds carried by this implicit leaf, if any. */\n get explicitBounds(): SdfBounds | undefined;\n private withNode;\n private withVisual;\n /** Clone this SDF expression and its visual metadata. */\n clone(): SdfShape;\n /** Alias for clone(). */\n duplicate(): SdfShape;\n /**\n * Mesh this SDF into a ForgeCAD Shape. Typed SDF trees materialize through Rust\n * Manifold Dual Contouring; dynamic trees (custom/noise/voronoi/displace/blend\n * callbacks) mesh through the Surface Nets pipeline.\n * Once converted, the result is a regular Shape — booleans, transforms, export all work.\n */\n toShape(options?: SdfToShapeOptions): Shape;\n /** Set the display color for this implicit leaf. */\n color(value: string | undefined): SdfShape;\n /** Set PBR display material properties for this implicit leaf. */\n material(props: ShapeMaterialProps): SdfShape;\n /** Set explicit preview/meshing bounds for this implicit leaf. */\n bounds(bounds: SdfBounds | [\n Vec3$1,\n Vec3$1\n ]): SdfShape;\n bounds(min: Vec3$1, max: Vec3$1): SdfShape;\n /** Sculpt-style alias for translate(). */\n at(x: number, y: number, z: number): SdfShape;\n /**\n * Sculpt-style alias for translate().\n * @softDeprecated .at(x, y, z)\n * @deprecated use .at(x, y, z)\n */\n move(x: number, y: number, z: number): SdfShape;\n /** Sculpt-style alias for rotateZ(). */\n spin(angleDeg: number): SdfShape;\n /** Sculpt-style tilt around X, Y, Z, or a custom axis. */\n tilt(angleDeg: number, axis?: "x" | "y" | "z" | Vec3$1): SdfShape;\n /**\n * Round all edges of a primitive box while preserving its outer dimensions.\n * Sugar over `offset()`: the box is shrunk by `radius` on every side, then\n * dilated back out with `offset(radius)`, which rounds every edge and corner.\n *\n * For any other shape, use `.offset(radius)` directly (note it grows the part\n * by `radius`), or `Sculpt.box(x, y, z, { radius })` for a rounded box.\n */\n round(radius: number): SdfShape;\n /** Sculpt-style smooth blend with another implicit shape. */\n blend(other: SdfShape, options?: number | {\n radius?: number;\n }): SdfShape;\n /**\n * Sculpt-style alias for blend().\n * @softDeprecated .blend(other, { radius })\n * @deprecated use .blend(other, { radius })\n */\n goop(other: SdfShape, options?: number | {\n radius?: number;\n }): SdfShape;\n /** Sculpt-style smooth carve/subtract. */\n carve(other: SdfShape, options?: number | {\n radius?: number;\n }): SdfShape;\n /**\n * Sculpt-style smooth intersection/keep operation.\n * @softDeprecated .smoothIntersect(other, radius)\n * @deprecated use .smoothIntersect(other, radius)\n */\n keep(other: SdfShape, options?: number | {\n radius?: number;\n }): SdfShape;\n /** Apply a Sculpt material preset or direct material props. */\n polish(input?: SculptPolishInput): SdfShape;\n /**\n * SDF union (sharp).\n *\n * ```js\n * sdf.box(20, 20, 8).union(sdf.cylinder(16, 6), sdf.sphere(7))\n * ```\n */\n union(...others: SdfShape[]): SdfShape;\n /** SDF difference (sharp) — subtracts others from this. */\n subtract(...others: SdfShape[]): SdfShape;\n /**\n * SDF intersection (sharp). Also the canonical way to fill a body with a\n * lattice or clip an infinite field to a design space:\n *\n * ```js\n * // Lattice fill — keep only the gyroid inside the body\n * sdf.sphere(18).intersect(sdf.gyroid({ cellSize: 6, wallThickness: 0.8 }))\n *\n * // Clip an infinite field to a box-shaped design space\n * sdf.gyroid({ cellSize: 6, wallThickness: 0.8 }).intersect(sdf.box(40, 25, 16))\n * ```\n */\n intersect(...others: SdfShape[]): SdfShape;\n /**\n * Clip this SDF to an explicit box-shaped design space.\n * @softDeprecated .intersect(sdf.box(x, y, z))\n * @deprecated use .intersect(sdf.box(x, y, z))\n */\n clipBox(x: number, y: number, z: number): SdfShape;\n /**\n * Keep only the material where this shape overlaps another SDF pattern.\n * @softDeprecated .intersect(pattern)\n * @deprecated use .intersect(pattern)\n */\n fillWith(pattern: SdfShape): SdfShape;\n /**\n * Keep only the gyroid lattice inside this shape.\n * @softDeprecated .intersect(sdf.gyroid({ cellSize, wallThickness }))\n * @deprecated use .intersect(sdf.gyroid({ cellSize, wallThickness }))\n */\n fillWithGyroid(options: TpmsOptions): SdfShape;\n /**\n * Keep only the Schwarz-P lattice inside this shape.\n * @softDeprecated .intersect(sdf.schwarzP({ cellSize, wallThickness }))\n * @deprecated use .intersect(sdf.schwarzP({ cellSize, wallThickness }))\n */\n fillWithSchwarzP(options: TpmsOptions): SdfShape;\n /**\n * Keep only the diamond TPMS lattice inside this shape.\n * @softDeprecated .intersect(sdf.diamond({ cellSize, wallThickness }))\n * @deprecated use .intersect(sdf.diamond({ cellSize, wallThickness }))\n */\n fillWithDiamond(options: TpmsOptions): SdfShape;\n /**\n * Keep only the lidinoid TPMS lattice inside this shape.\n * @softDeprecated .intersect(sdf.lidinoid({ cellSize, wallThickness }))\n * @deprecated use .intersect(sdf.lidinoid({ cellSize, wallThickness }))\n */\n fillWithLidinoid(options: TpmsOptions): SdfShape;\n /** Smooth union — blends shapes together with a smooth radius. */\n smoothUnion(other: SdfShape, radius: number): SdfShape;\n /** Smooth difference — smoothly carves other from this. */\n smoothSubtract(other: SdfShape, radius: number): SdfShape;\n /** Smooth intersection — smoothly intersects. */\n smoothIntersect(other: SdfShape, radius: number): SdfShape;\n /** Morph between this shape and another. t=0 → this, t=1 → other. */\n morph(other: SdfShape, t: number): SdfShape;\n /** Translate this SDF by the given offsets in millimeters. */\n translate(x: number, y: number, z: number): SdfShape;\n /** Rotate around an arbitrary axis through the origin. */\n rotate(axis: [\n number,\n number,\n number\n ], angleDeg: number): SdfShape;\n /** Rotate around the X axis by the given angle in degrees. */\n rotateX(angleDeg: number): SdfShape;\n /** Rotate around the Y axis by the given angle in degrees. */\n rotateY(angleDeg: number): SdfShape;\n /** Rotate around the Z axis by the given angle in degrees. */\n rotateZ(angleDeg: number): SdfShape;\n /** Uniformly scale this SDF around the origin. */\n scale(factor: number): SdfShape;\n /** Twist around the Z axis. */\n twist(degreesPerUnit: number): SdfShape;\n /** Bend around the Z axis with given radius. */\n bend(radius: number): SdfShape;\n /** Repeat in space. Spacing of 0 on an axis means no repetition. Count of 0 = infinite. */\n repeat(spacing: Vec3$1, count?: Vec3$1): SdfShape;\n /**\n * Arrange this SDF in a circular array around the Z axis.\n *\n * The source shape is translated by `offset` in +X before arraying. This uses\n * angular domain folding, so evaluation stays O(1): the source SDF is sampled\n * twice no matter how many copies are requested.\n */\n circularArray(count: number, offset?: number): SdfShape;\n /** Hollow out, keeping only a shell of given thickness. */\n shell(thickness: number): SdfShape;\n /**\n * Offset the distance field by a constant amount in millimeters.\n *\n * Positive `distance` dilates: every surface moves outward by `distance`,\n * which rounds convex edges and corners — and grows the part by `distance`.\n * Negative `distance` erodes: surfaces move inward, shrinking the part and\n * thinning walls. This is the canonical SDF field offset (`d − distance`)\n * and works on ANY implicit shape — sharp booleans, TPMS lattices,\n * `sdf.fromFunction()` fields — not just primitives.\n *\n * Like `shell()`, the result is approximate on fields that are not exact\n * distance fields (for example after `twist()`, `bend()`, or smooth booleans).\n *\n * ```js\n * // Round every edge of a sharp union by 2mm (grows the part by 2mm)\n * sdf.box(20, 20, 8).union(sdf.cylinder(16, 6)).offset(2)\n *\n * // Erode a lattice by 0.2mm to compensate printed over-extrusion\n * sdf.sphere(18).intersect(sdf.gyroid({ cellSize: 6, wallThickness: 1.2 })).offset(-0.2)\n * ```\n */\n offset(distance: number): SdfShape;\n /**\n * Displace the surface by a function of position, or by a pattern SdfShape.\n *\n * ```js\n * // Function displacement\n * shape.displace((x, y, z) => Math.sin(x) * 0.5)\n *\n * // Pattern displacement from a 3D SDF field\n * shape.displace(sdf.knurl({ pitch: 2, depth: 0.3 }))\n * ```\n */\n displace(fn: ((x: number, y: number, z: number) => number) | SdfShape, constants?: Record<string, number>): SdfShape;\n /**\n * Displace the surface using a 2D pattern in surface-local UV coordinates.\n *\n * Automatically detects the shape\'s UV parametrization (sphere, cylinder, torus)\n * from the SDF tree. Falls back to triplanar mapping for arbitrary shapes.\n *\n * UV coordinates are in **surface millimeters** — patterns defined with `spacing: 3`\n * always produce 3mm spacing, regardless of shape size.\n *\n * Prefer `sdf.pattern2d()` or built-in surface patterns when the relief should\n * stay on the native shader and meshing path. Callback functions are supported\n * for experimentation, but they are opaque to the typed pattern optimizer.\n *\n * ```js\n * // Native typed pattern — auto-detects sphere UV\n * const p = sdf.pattern2d()\n * const ribs = p.stripes({ spacing: 3, width: 0.8, depth: 0.35 })\n * .add(p.sineWave({ direction: [0, 1], wavelength: 14, amplitude: 0.08 }))\n *\n * sdf.sphere(27).shell(3)\n * .surfaceDisplace(ribs)\n * .toShape()\n *\n * // Custom 2D pattern via function\n * shape.surfaceDisplace((u, v) => -Math.sin(u * 2) * 0.3)\n * ```\n */\n surfaceDisplace(pattern: SurfacePattern | ((u: number, v: number) => number), options?: SurfaceDisplaceOptions): SdfShape;\n /** Create concentric onion layers. */\n onion(layers: number, thickness: number): SdfShape;\n}\ndeclare function sphere(radius: number): SdfShape;\ndeclare function box(x: number, y: number, z: number): SdfShape;\ndeclare function cylinder(height: number, radius: number): SdfShape;\ndeclare function torus(majorRadius: number, minorRadius: number): SdfShape;\ndeclare function capsule(height: number, radius: number): SdfShape;\ndeclare function cone(height: number, radius: number): SdfShape;\ndeclare function smoothUnion(a: SdfShape, b: SdfShape, options: {\n radius: number;\n}): SdfShape;\ndeclare function smoothDifference(a: SdfShape, b: SdfShape, options: {\n radius: number;\n}): SdfShape;\ndeclare function smoothIntersection(a: SdfShape, b: SdfShape, options: {\n radius: number;\n}): SdfShape;\ndeclare function morph(a: SdfShape, b: SdfShape, t: number): SdfShape;\ninterface BlendOptions {\n /** Optional named constants accessible in the blend function. */\n constants?: Record<string, number>;\n}\ndeclare function blend(a: SdfShape, b: SdfShape, fn: (x: number, y: number, z: number) => number, options?: BlendOptions): SdfShape;\ninterface TpmsOptions {\n cellSize: number;\n /**\n * Dimensionless field threshold kept for compatibility.\n * Prefer `wallThickness` for approximate millimeter units.\n */\n thickness?: number;\n /** Approximate physical wall thickness in millimeters. */\n wallThickness?: number;\n /** Override TPMS thickness interpretation. Defaults to metric when `wallThickness` is used, field-threshold when `thickness` is used. */\n tpmsThicknessMode?: TpmsThicknessMode;\n}\ninterface TpmsBlockOptions extends TpmsOptions {\n type?: "gyroid" | "schwarzP" | "diamond" | "lidinoid";\n size: Vec3$1;\n}\ndeclare function gyroid(options: TpmsOptions): SdfShape;\ndeclare function schwarzP(options: TpmsOptions): SdfShape;\ndeclare function diamond(options: TpmsOptions): SdfShape;\ndeclare function lidinoid(options: TpmsOptions): SdfShape;\ndeclare function withinBox(shape: SdfShape, options: {\n size: Vec3$1;\n}): SdfShape;\ndeclare function tpmsBlock(options: TpmsBlockOptions): SdfShape;\ninterface NoiseOptions {\n /** Spatial frequency — smaller = larger features. Default: 0.1 */\n scale?: number;\n /** Peak displacement amplitude. Default: 1 */\n amplitude?: number;\n /** fBm octaves (1 = plain simplex, higher = more detail). Default: 1 */\n octaves?: number;\n /** Seed for deterministic variation. Default: 0 */\n seed?: number;\n}\ndeclare function noise(options?: NoiseOptions): SdfShape;\ninterface VoronoiOptions {\n /** Size of each Voronoi cell in world units. Default: 10 */\n cellSize?: number;\n /** Wall thickness between cells. Default: 1 */\n wallThickness?: number;\n /** Seed for deterministic variation. Default: 0 */\n seed?: number;\n /**\n * Projection weight for membrane suppression (0..1). Controls how much of\n * the surface-normal distance component is removed from Voronoi cell distances.\n * 0 = no projection (classic 3D voronoi with membranes).\n * 1 = full tangent-plane projection (pure 2D pattern on surface).\n * Default: 0.85. Only active when voronoi is intersected with another shape.\n */\n suppressionThreshold?: number;\n}\ndeclare function voronoi(options?: VoronoiOptions): SdfShape;\ninterface HoneycombOptions {\n /** Size of each hex cell. Default: 8 */\n cellSize?: number;\n /** Wall thickness. Default: 1 */\n wallThickness?: number;\n}\ndeclare function honeycomb(options?: HoneycombOptions): SdfShape;\ninterface WavesOptions {\n /** Distance between wave peaks. Default: 10 */\n wavelength?: number;\n /** Height of waves. Default: 1 */\n amplitude?: number;\n /** Axis along which waves propagate: \'x\', \'y\', or \'z\'. Default: \'x\' */\n axis?: "x" | "y" | "z";\n}\ndeclare function waves(options?: WavesOptions): SdfShape;\ninterface KnurlOptions {\n /** Distance between knurl ridges. Default: 3 */\n pitch?: number;\n /** Depth of knurl grooves. Default: 0.5 */\n depth?: number;\n /** Helix angle in degrees. Default: 30 */\n angle?: number;\n}\ndeclare function knurl(options?: KnurlOptions): SdfShape;\ninterface PerforatedOptions {\n /** Hole radius. Default: 3 */\n radius?: number;\n /** Center-to-center spacing. Default: 8 */\n spacing?: number;\n}\ndeclare function perforated(options?: PerforatedOptions): SdfShape;\ninterface ScalesOptions {\n /** Scale diameter. Default: 5 */\n size?: number;\n /** How much scales protrude. Default: 0.8 */\n depth?: number;\n}\ndeclare function scales(options?: ScalesOptions): SdfShape;\ninterface BrickOptions {\n /** Brick width. Default: 10 */\n width?: number;\n /** Brick height. Default: 5 */\n height?: number;\n /** Mortar groove depth. Default: 0.5 */\n depth?: number;\n /** Mortar gap width. Default: 1 */\n mortar?: number;\n}\ndeclare function brick(options?: BrickOptions): SdfShape;\ninterface WeaveOptions {\n /** Thread center-to-center spacing (for intersection patterns). Default: 5 */\n spacing?: number;\n /** Thread half-width. Default: 1 */\n threadRadius?: number;\n}\ndeclare function weave(options?: WeaveOptions): SdfShape;\ninterface BasketWeaveOptions {\n /** Spacing between threads in mm (both directions). Default: 3 */\n spacing?: number;\n /** Thread width in mm. Default: 1.5 */\n threadWidth?: number;\n /** Thread protrusion depth in mm. Default: 0.8 */\n depth?: number;\n}\ndeclare function basketWeave(options?: BasketWeaveOptions): SurfacePattern;\ndeclare function fromFunction(fn: SdfFunctionSource, options: SdfFunctionOptions): SdfShape;\ndeclare function twist(shape: SdfShape, degreesPerUnit: number): SdfShape;\ndeclare function bend(shape: SdfShape, radius: number): SdfShape;\ndeclare function repeat(shape: SdfShape, spacing: Vec3$1, count?: Vec3$1): SdfShape;\ndeclare function circularArray(shape: SdfShape, count: number, offset?: number): SdfShape;\ntype ToShapeTreeResult = Shape | ShapeGroup;\ninterface CombineOptions {\n op?: "union" | "intersection";\n}\n/**\n * Materialize one SDF leaf or all SDF leaves in a renderable tree.\n *\n * Raw `SdfShape` values become mesh-backed `Shape`s. Plain objects and arrays\n * preserve their renderable children as a `ShapeGroup` when more than one leaf\n * is found. Non-renderable metadata is ignored for materialization and remains\n * available to callers through normal `require()` return values.\n */\ndeclare function toShape(value: unknown, options?: SdfToShapeOptions): ToShapeTreeResult;\n/**\n * Collapse a tree of SDF leaves into one continuous SDF field.\n *\n * This intentionally discards per-leaf color/material identity because the\n * result is one scalar field. Use plain object returns for multi-material SDF\n * preview, and use `sdf.combine(...)` only when you want one implicit body.\n *\n * The canonical spelling is the namespaced `sdf.combine(value, { op })`; the\n * bare `combine` runtime global is kept for compatibility only.\n *\n * @softDeprecated sdf.combine(value, { op })\n * @deprecated use sdf.combine(value, { op })\n */\ndeclare function combine(value: unknown, options?: CombineOptions): SdfShape;\ninterface JointOverlayViewConfigOptions {\n enabled?: boolean;\n axisColor?: string;\n axisCoreColor?: string;\n arcColor?: string;\n zeroColor?: string;\n arcVisualLimitDeg?: number;\n axisLengthScale?: number;\n axisLengthMin?: number;\n axisLineRadiusScale?: number;\n axisLineRadiusMin?: number;\n axisLineRadiusMax?: number;\n spokeLineRadiusScale?: number;\n spokeLineRadiusMin?: number;\n spokeLineRadiusMax?: number;\n arcLineRadiusScale?: number;\n arcLineRadiusMin?: number;\n arcLineRadiusMax?: number;\n axisDotRadiusScale?: number;\n axisDotRadiusMin?: number;\n axisArrowRadiusScale?: number;\n axisArrowRadiusMin?: number;\n axisArrowLengthScale?: number;\n axisArrowLengthMin?: number;\n axisArrowOffsetFactor?: number;\n arcRadiusScale?: number;\n arcRadiusMin?: number;\n arcDotRadiusScale?: number;\n arcDotRadiusMin?: number;\n arcArrowRadiusScale?: number;\n arcArrowRadiusMin?: number;\n arcArrowLengthScale?: number;\n arcArrowLengthMin?: number;\n arcArrowOffsetFactor?: number;\n arcStepDeg?: number;\n arcMinSteps?: number;\n arcTubeSegmentsMin?: number;\n arcTubeSegmentsFactor?: number;\n arcTubeRadialSegments?: number;\n}\ninterface ViewConfigOptions {\n jointOverlay?: JointOverlayViewConfigOptions;\n}\n/**\n * Configure viewport helper visuals for the current script execution.\n *\n * **Details**\n *\n * Controls renderer-only overlays that appear in the viewport but are not part of the geometry.\n * Currently supports the joint overlay that renders axis arrows and arc indicators when\n * joint controls are active. Multiple calls merge — later values override earlier ones per key.\n *\n * This does **not** trigger a geometry recompute; it only affects the visual helpers drawn on\n * top of the 3D scene.\n *\n * **Example**\n *\n * ```js\n * viewConfig({\n * jointOverlay: {\n * axisColor: \'#13dfff\',\n * arcColor: \'#ff7a1a\',\n * axisLineRadiusScale: 0.03,\n * arcLineRadiusScale: 0.022,\n * },\n * });\n * ```\n *\n * @param options.jointOverlay - Joint overlay appearance. Supports `enabled`, `axisColor`, `axisCoreColor`, `arcColor`, `zeroColor`, `axisLengthScale`, `arcVisualLimitDeg`, `arcStepDeg`, and detailed radius/arrow sizing properties\n * @returns void\n * @softDeprecated scene({ jointOverlay: { ... } }) — same keys, on the one renderer-config global\n * @deprecated use scene({ jointOverlay: { ... } }) — same keys, on the one renderer-config global\n * @category Scene Configuration\n */\ndeclare function viewConfig(options?: ViewConfigOptions): void;\ninterface SceneCameraConfig {\n position?: [\n number,\n number,\n number\n ];\n target?: [\n number,\n number,\n number\n ];\n up?: [\n number,\n number,\n number\n ];\n fov?: number;\n type?: "perspective" | "orthographic";\n}\ntype SceneViewCameraConfig = SceneCameraConfig & {\n position: [\n number,\n number,\n number\n ];\n target: [\n number,\n number,\n number\n ];\n};\ninterface SceneViewConfig {\n /** Explicit camera state for this named render view. */\n camera: SceneViewCameraConfig;\n}\ntype SceneViewInputConfig = SceneViewConfig | SceneViewCameraConfig;\ntype SceneViewMap = Record<string, SceneViewConfig>;\ntype SceneJourneyDiagnosticLevel = "error" | "warning";\ninterface SceneJourneyDiagnostic {\n level: SceneJourneyDiagnosticLevel;\n message: string;\n stepId?: string;\n suggestions?: string[];\n}\ninterface SceneJourneyStepConfig {\n /** Stable step id used by viewer links and Next/Back state. */\n id: string;\n /** Viewer-facing title. Defaults to the step id. */\n title?: string;\n /** Object name or slash-separated tree path to focus. */\n focus?: string;\n /** Short optional viewer caption. */\n caption?: string;\n /** Optional explicit camera for this step. When omitted, the viewer fits `focus`. */\n camera?: SceneViewCameraConfig;\n /** Resolved object id after script execution, when `focus` matched exactly one object. */\n resolvedFocusId?: string | null;\n /** Resolved object tree path or name after script execution. */\n resolvedFocusPath?: string | null;\n /** Resolution diagnostics for this step. */\n diagnostics?: SceneJourneyDiagnostic[];\n}\ninterface SceneJourneyConfig {\n /** Viewer-facing journey title. Defaults to the journey id. */\n title?: string;\n /** Optional starting step id. Defaults to the first step. */\n startsAt?: string;\n /** Whether the viewer should offer or auto-open the journey. First slice supports opt-in. */\n behavior?: "opt-in" | "auto";\n /** Ordered journey spine. Branches can be added later without changing this core contract. */\n steps: SceneJourneyStepConfig[];\n /** True unless any journey or step diagnostic has level "error". */\n valid?: boolean;\n /** Whole-journey diagnostics, including unresolved startsAt and step target diagnostics. */\n diagnostics?: SceneJourneyDiagnostic[];\n}\ntype SceneJourneyMap = Record<string, SceneJourneyConfig>;\ntype SceneLightType = "ambient" | "directional" | "point" | "spot" | "hemisphere";\ninterface SceneLightConfig {\n type: SceneLightType;\n color?: string;\n intensity?: number;\n position?: [\n number,\n number,\n number\n ];\n /** Target for directional/spot lights */\n target?: [\n number,\n number,\n number\n ];\n /** Ground color for hemisphere lights */\n groundColor?: string;\n /** Sky color alias for hemisphere lights (same as color) */\n skyColor?: string;\n /** Spot light cone angle in radians */\n angle?: number;\n /** Spot light penumbra (0–1) */\n penumbra?: number;\n /** Point/spot light decay */\n decay?: number;\n /** Point/spot light distance (0 = infinite) */\n distance?: number;\n /** Whether this light casts shadows */\n castShadow?: boolean;\n}\ninterface SceneEnvironmentConfig {\n /** Built-in preset name or \'none\' to disable */\n preset?: "studio" | "sunset" | "dawn" | "warehouse" | "forest" | "apartment" | "lobby" | "city" | "park" | "night" | "none";\n /** Environment map intensity */\n intensity?: number;\n /** Use environment map as scene background */\n background?: boolean;\n}\ninterface SceneBackgroundGradient {\n top: string;\n bottom: string;\n}\ninterface SceneFogConfig {\n color?: string;\n /** Linear fog near distance */\n near?: number;\n /** Linear fog far distance */\n far?: number;\n /** Exponential fog density (if set, uses FogExp2 instead of linear Fog) */\n density?: number;\n}\ninterface SceneGroundConfig {\n /** Show a ground plane */\n visible?: boolean;\n /** Ground color */\n color?: string;\n /** Offset below the model\'s bounding box minimum Z. Default 0 (flush with model bottom). */\n offset?: number;\n /** Receive shadows on the ground */\n receiveShadow?: boolean;\n}\ninterface SceneCaptureConfig {\n /** Frames for one full orbit rotation (default: 72) */\n framesPerTurn?: number;\n /** Frozen frames before motion starts (default: 6) */\n holdFrames?: number;\n /** Orbit pitch angle in degrees (default: auto from camera) */\n pitchDeg?: number;\n /** Output frame rate (default: 24) */\n fps?: number;\n /** Output frame size in pixels (default: 960) */\n size?: number;\n /** Canvas background color for capture (default: \'#252526\') */\n background?: string;\n}\ninterface SceneOptions {\n background?: string | SceneBackgroundGradient;\n camera?: SceneCameraConfig;\n views?: Record<string, SceneViewInputConfig>;\n journeys?: Record<string, SceneJourneyConfig>;\n lights?: SceneLightConfig[];\n environment?: SceneEnvironmentConfig;\n fog?: SceneFogConfig;\n ground?: SceneGroundConfig;\n /** Default capture parameters for `forgecad capture` — CLI flags override these. */\n capture?: SceneCaptureConfig;\n /**\n * Joint-overlay helper visuals (axis arrows and arc indicators shown when\n * joint controls are active). Renderer-only — no geometry recompute.\n */\n jointOverlay?: JointOverlayViewConfigOptions;\n}\n/**\n * Configure the scene environment for the current script execution.\n *\n * **Details**\n *\n * Controls camera, named render views, guided journeys, lighting, background, fog, environment\n * maps, capture defaults, and the joint-control helper overlay\n * (`jointOverlay` — axis arrows and arc indicators, renderer-only). Multiple `scene()` calls\n * merge per-key — later values win — so configuration can be split across calls.\n *\n * Two behavioral cliffs:\n * - Specifying `lights` removes **all** default lights. Include your own ambient light or the\n * scene is fully dark.\n * - Setting `camera.position` disables auto-framing — the viewport no longer auto-fits the\n * geometry on script reload.\n *\n * Named views are repeatable cameras checked in with the model code. Canonical shape is\n * `{ camera: { position, target } }`; a direct `{ position, target }` shorthand is also\n * accepted. Render one with `--view <name>` (see CLI docs).\n *\n * Journeys are ordered `steps`, each focusing a returned object by name/tree path with an\n * optional caption and camera. In the viewer they are opt-in (an Explore control; the camera\n * does not move until started). Inspect resolved targets with `forgecad run --journeys`.\n *\n * Post-processing is disabled for now while the browser EffectComposer flicker path is\n * being rebuilt. Existing scripts that pass `postProcessing` continue running, but the\n * option is not part of the active scene API.\n *\n * All numeric values accept `param()` expressions.\n *\n * **Example**\n *\n * ```js\n * scene({\n * background: { top: \'#000814\', bottom: \'#001d3d\' },\n * camera: { position: [160, -120, 100], target: [0, 0, 50], fov: 52 },\n * views: {\n * hero: { camera: { position: [180, -140, 90], target: [0, 0, 25], fov: 38 } },\n * },\n * journeys: {\n * tour: { steps: [{ id: \'earth\', focus: \'Earth\', caption: \'Fit and inspect Earth.\' }] },\n * },\n * lights: [\n * { type: \'ambient\', color: \'#001233\', intensity: 0.08 },\n * { type: \'directional\', position: [50, -30, 200], color: \'#ffd60a\', intensity: 1.2 },\n * ],\n * fog: { color: \'#000814\', near: 100, far: 450 },\n * jointOverlay: { axisColor: \'#13dfff\', arcColor: \'#ff7a1a\' },\n * });\n * ```\n *\n * @param options.background - Solid color hex string or `{ top, bottom }` vertical gradient\n * @param options.camera - Camera override: `position` `[x,y,z]`, `target` `[x,y,z]`, `fov` (degrees 1–179), `up` `[x,y,z]`, `type` (`\'perspective\' | \'orthographic\'`)\n * @param options.views - Named render cameras for `forgecad render 3d --view <name>`. Each view is either `{ camera }` or a direct camera shorthand with required `position` and `target`, optional `up`, `fov`, and `type`\n * @param options.journeys - Guided viewer journeys keyed by id. Each journey has ordered `steps`; each step can use `focus` to target a returned object by name/tree path, plus optional `title`, `caption`, and explicit `camera`\n * @param options.lights - Full light array (replaces all defaults). Each entry: `type` (`\'ambient\' | \'directional\' | \'point\' | \'spot\' | \'hemisphere\'`), `color`, `intensity`, `position`, `target`, `castShadow`. Hemisphere accepts `skyColor`/`groundColor`. Spot/point accept `angle`, `penumbra`, `decay`, `distance`\n * @param options.environment - Built-in HDRI preset: `preset` (`\'studio\' | \'sunset\' | \'dawn\' | \'warehouse\' | \'forest\' | \'apartment\' | \'lobby\' | \'city\' | \'park\' | \'night\' | \'none\'`), `intensity`, `background`\n * @param options.fog - Atmospheric fog: `color`, `near`/`far` (linear) or `density` (exponential FogExp2)\n * @param options.ground - Ground plane: `visible`, `color`, `offset` (below model bottom), `receiveShadow`\n * @param options.capture - Capture defaults for `forgecad capture`: `framesPerTurn` (12–720, default 72), `holdFrames` (0–300, default 6), `pitchDeg` (-80–80), `fps` (1–60, default 24), `size` (pixels, default 960), `background`\n * @param options.jointOverlay - Joint-control helper overlay (renderer-only): `enabled`, `axisColor`, `axisCoreColor`, `arcColor`, `zeroColor`, `axisLengthScale`, `arcVisualLimitDeg`, `arcStepDeg`, and detailed radius/arrow sizing properties\n * @returns void\n * @category Scene Configuration\n */\ndeclare function scene(options: SceneOptions): void;\ntype SketchFaceTarget = SketchFace3D | string | FaceRef;\ninterface ShapeFeatureExtentSideOptions {\n depth?: number;\n upToFace?: SketchFaceTarget | FaceRef;\n through?: boolean;\n}\ninterface ShapeFeatureExtentOptions {\n forward: ShapeFeatureExtentSideOptions;\n reverse?: ShapeFeatureExtentSideOptions;\n}\ninterface ShapeHoleThreadOptions {\n designation?: string;\n pitch?: number;\n class?: string;\n handedness?: "right" | "left";\n depth?: number;\n modeled?: boolean;\n}\ninterface ShapeHoleOptions {\n diameter: number;\n depth?: number;\n upToFace?: SketchFaceTarget | FaceRef;\n extent?: ShapeFeatureExtentOptions;\n u?: number;\n v?: number;\n counterbore?: {\n diameter: number;\n depth: number;\n };\n countersink?: {\n diameter: number;\n angleDeg?: number;\n };\n thread?: ShapeHoleThreadOptions;\n}\ninterface ShapeCutoutOptions {\n depth?: number;\n upToFace?: SketchFaceTarget | FaceRef;\n extent?: ShapeFeatureExtentOptions;\n taperScale?: number | [\n number,\n number\n ];\n}\ninterface Shape {\n /**\n * Drill a hole into this solid at a face.\n *\n * @param faceOrRef Target face — a face selector string (e.g. `\'top\'`), a placed sketch, or a `FaceRef`.\n * @param opts Hole parameters: `diameter` (required), optional `depth`, `counterbore`, `countersink`, `thread`, `u`/`v` offset, `upToFace`, `extent`.\n *\n * @example\n * box(50, 50, 20).hole(\'top\', { diameter: 8, depth: 10 })\n * box(50, 50, 20).hole(\'top\', { diameter: 6, counterbore: { diameter: 12, depth: 3 } })\n */\n hole(faceOrRef: SketchFaceTarget | FaceRef, opts: ShapeHoleOptions): Shape;\n /**\n * Cut a profile-shaped pocket through a face using a placed sketch.\n *\n * The sketch must be placed on a face with `Sketch.onFace(...)`. The cut follows the sketch\'s 2D profile.\n *\n * @param sketch Sketch placed on a face via `.onFace()`.\n * @param opts Optional `depth`, `upToFace`, `extent`, `taperScale`.\n *\n * @example\n * const profile = circle2d(10).onFace(body, \'top\');\n * body.cutout(profile, { depth: 5 })\n */\n cutout(sketch: Sketch, opts?: ShapeCutoutOptions): Shape;\n}\ninterface PocketOptions {\n /**\n * Shrink the face boundary inward by this many mm before extruding.\n * Produces angled walls when combined with depth. Default: 0 (full face).\n */\n inset?: number;\n /**\n * Scale the face profile uniformly (e.g. 0.8 = 80% of the face area).\n * Mutually exclusive with `inset`; `inset` takes precedence if both are set.\n */\n scale?: number;\n /** Corner join style when using `inset`. Default: \'Round\'. */\n join?: "Square" | "Round" | "Miter";\n}\ntype BossOptions = PocketOptions;\ninterface Shape {\n /**\n * Cut a pocket (cavity) into this solid through the named face.\n *\n * @param face Which face to cut into — e.g. `\'top\'`, `\'front\'`, or a face query.\n * @param depth How deep the pocket goes into the solid (mm).\n * @param opts Optional `inset` (shrink boundary, mm), `scale` (uniform scale, e.g. 0.8), `join` (`\'Round\'`/`\'Square\'`/`\'Miter\'`).\n *\n * @example\n * box(100, 100, 20).pocket(\'top\', 8)\n * box(100, 100, 20).pocket(\'top\', 8, { inset: 5 })\n * box(100, 100, 20).pocket(\'top\', 8, { scale: 0.8 })\n */\n pocket(face: FaceSelector, depth: number, opts?: PocketOptions): Shape;\n /**\n * Add a boss (protrusion) from the named face.\n *\n * @param face Which face to protrude from — e.g. `\'top\'`, `\'front\'`, or a face query.\n * @param height Height of the protrusion above the face (mm).\n * @param opts Optional `inset` (shrink boundary, mm), `scale` (uniform scale, e.g. 0.8), `join` (`\'Round\'`/`\'Square\'`/`\'Miter\'`).\n *\n * @example\n * box(100, 100, 20).boss(\'top\', 5)\n * box(100, 100, 20).boss(\'top\', 10, { scale: 0.6 })\n */\n boss(face: FaceSelector, height: number, opts?: BossOptions): Shape;\n}\ntype Vec3$2 = [\n number,\n number,\n number\n];\ntype SlicePlane = "xy" | "xz" | "yz" | Vec3$2;\ninterface FromSlicesSlice {\n /** Plane normal: axis name or arbitrary unit vector. */\n on: SlicePlane;\n /** Signed offset along the normal from the origin. */\n at: number;\n /** 2D cross-section profile on that plane. */\n profile: Sketch;\n}\ninterface FromSlicesOptions {\n /** Marching-grid edge length for level-set meshing (Manifold only). */\n edgeLength?: number;\n /** Extra bounding-box padding (Manifold only). */\n boundsPadding?: number;\n}\nexport namespace Shape {\n /**\n * Construct a 3D shape from cross-section slices on one or more planes.\n *\n * Slices with the same normal direction are lofted together. Slices with\n * different normals are combined via smooth radial blending — each\n * silhouette constrains the shape\'s extent, producing smooth ellipsoidal\n * cross-sections.\n *\n * @example\n * ```js\n * // Egg from two orthogonal silhouettes\n * const eggProfile = ellipse(15, 25);\n * return Shape.fromSlices([\n * { on: \'xz\', at: 0, profile: eggProfile },\n * { on: \'yz\', at: 0, profile: eggProfile },\n * ]);\n * ```\n *\n * @example\n * ```js\n * // Vase with cross-section transitions\n * return Shape.fromSlices([\n * { on: \'xy\', at: 0, profile: circle2d(20) },\n * { on: \'xy\', at: 40, profile: rect(25, 25) },\n * { on: \'xy\', at: 80, profile: circle2d(8) },\n * { on: \'xz\', at: 0, profile: vaseOutline },\n * ]);\n * ```\n */\n function fromSlices(slices: FromSlicesSlice[], options?: FromSlicesOptions): Shape;\n}\ntype ExplodeAxis = "x" | "y" | "z";\ntype ExplodeDirection = "radial" | ExplodeAxis | [\n number,\n number,\n number\n];\ninterface ExplodeDirective {\n /** Multiplier applied to `amount` for this node */\n stage?: number;\n /** Direction mode for this node */\n direction?: ExplodeDirection;\n /** Optional axis lock after direction is resolved */\n axisLock?: ExplodeAxis;\n}\ninterface ExplodeConfigOptions {\n amount?: number;\n stages?: number[];\n mode?: ExplodeDirection;\n axisLock?: ExplodeAxis;\n byName?: Record<string, ExplodeDirective>;\n byPath?: Record<string, ExplodeDirective>;\n}\ntype ExplodeViewDirection = ExplodeDirection;\ninterface ExplodeViewDirective extends ExplodeDirective {\n}\ninterface ExplodeViewOptions {\n /** Set false to disable viewport explode offsets for this script output. */\n enabled?: boolean;\n /** Scales the UI explode amount. Default: 1 */\n amountScale?: number;\n /**\n * Per-depth stage multipliers (depth 1 = first level).\n * If depth exceeds this array, the last value is reused.\n * Default when omitted: reciprocal depth (1, 1/2, 1/3, ...)\n */\n stages?: number[];\n /** Global direction mode fallback. Default: \'radial\' */\n mode?: ExplodeViewDirection;\n /** Global axis lock fallback. */\n axisLock?: ExplodeAxis;\n /** Per-object overrides by final object name. */\n byName?: Record<string, ExplodeViewDirective>;\n /** Per-tree-path overrides using slash-separated object tree segments. */\n byPath?: Record<string, ExplodeViewDirective>;\n}\n/**\n * Configure how the viewport explode slider offsets returned objects.\n *\n * **Details**\n *\n * Offsets are resolved from the returned object tree, not a flat list. In `radial` mode each\n * node follows its parent branch direction, then fans locally from the immediate parent\n * center — nested assemblies peel apart level by level. In fixed-axis or fixed-vector modes,\n * the branch follows that axis/vector but nested descendants fan out perpendicular by default.\n *\n * Multiple calls merge — later values override earlier ones on a per-key basis. `byName` and\n * `byPath` maps are merged entry-by-entry.\n *\n * For programmatic explode applied before returning (without the slider), use `lib.explode()`\n * instead.\n *\n * **Example**\n *\n * ```js\n * explodeView({\n * amountScale: 1.2,\n * stages: [0.35, 0.8],\n * mode: \'radial\',\n * byPath: { \'Drive/Shaft\': { direction: [1, 0, 0], stage: 1.6 } },\n * });\n * ```\n *\n * @param options.enabled - Set `false` to disable viewport explode offsets for this script\n * @param options.amountScale - Multiplier applied to the UI slider amount. Default: 1\n * @param options.stages - Per-depth stage multipliers (depth 1 = first level). If depth exceeds the array, the last value is reused. Default: reciprocal depth (`1, 1/2, 1/3, ...`)\n * @param options.mode - Global direction mode: `\'radial\'` | `\'x\'` | `\'y\'` | `\'z\'` | `[x, y, z]`. Default: `\'radial\'`\n * @param options.axisLock - Constrain motion to a single world axis: `\'x\'` | `\'y\'` | `\'z\'`\n * @param options.byName - Per-object overrides keyed by final returned object name: `{ stage?, direction?, axisLock? }`\n * @param options.byPath - Per-tree-path overrides using slash-separated path segments (e.g. `\'Drive/Shaft\'`): `{ stage?, direction?, axisLock? }`\n * @returns void\n * @see {@link explode} for applying explode offsets programmatically before returning geometry\n * @category Explode View\n */\ndeclare function explodeView(options?: ExplodeViewOptions): void;\ntype JointViewType = "revolute" | "prismatic";\ntype JointViewAxis = [\n number,\n number,\n number\n];\ninterface JointViewInput {\n name: string;\n child: string;\n parent?: string;\n type?: JointViewType;\n axis?: JointViewAxis;\n pivot?: [\n number,\n number,\n number\n ];\n min?: number;\n max?: number;\n default?: number;\n unit?: string;\n hidden?: boolean;\n}\ninterface JointViewAnimationKeyframeInput {\n /** Timeline position [0, 1]. If omitted from ALL keyframes, positions are auto-computed from tick weights. */\n at?: number;\n /** Relative weight of the segment from this keyframe to the next (default 1). Only used in tick-based mode (when `at` is omitted). Last keyframe\'s ticks value is ignored. */\n ticks?: number;\n values: Record<string, number>;\n}\ninterface JointViewAnimationInput {\n name: string;\n duration?: number;\n loop?: boolean;\n continuous?: boolean;\n keyframes: JointViewAnimationKeyframeInput[];\n}\ninterface JointViewCouplingTermInput {\n joint: string;\n ratio?: number;\n}\ninterface JointViewCouplingInput {\n joint: string;\n terms: JointViewCouplingTermInput[];\n offset?: number;\n}\ninterface JointViewDef {\n name: string;\n child: string;\n parent?: string;\n type: JointViewType;\n axis: JointViewAxis;\n pivot: [\n number,\n number,\n number\n ];\n min?: number;\n max?: number;\n defaultValue: number;\n unit?: string;\n hidden?: boolean;\n}\ninterface JointViewAnimationKeyframeDef {\n at: number;\n values: Record<string, number>;\n}\ninterface JointViewAnimationDef {\n name: string;\n duration: number;\n loop: boolean;\n continuous: boolean;\n keyframes: JointViewAnimationKeyframeDef[];\n}\ninterface JointViewCouplingTermDef {\n joint: string;\n ratio: number;\n}\ninterface JointViewCouplingDef {\n joint: string;\n terms: JointViewCouplingTermDef[];\n offset: number;\n}\ninterface JointsViewOptions {\n enabled?: boolean;\n joints?: JointViewInput[];\n couplings?: JointViewCouplingInput[];\n animations?: JointViewAnimationInput[];\n defaultAnimation?: string;\n}\ninterface CollectedJointsView {\n enabled?: boolean;\n motionSource?: "viewport" | "assembly";\n joints: JointViewDef[];\n couplings: JointViewCouplingDef[];\n animations: JointViewAnimationDef[];\n defaultAnimation?: string;\n}\n/**\n * Register legacy viewport-only mechanism controls that animate returned objects without re-running the script.\n *\n * **Details**\n *\n * Deprecated: return an `Assembly` instead — returned assemblies expose\n * solver-backed controls automatically. Legacy pitfalls if you must stay here:\n * solve at rest pose (all animated joints = 0) or transforms double-rotate;\n * `pivot` is the world-space joint origin at rest; keyframes interpolate\n * linearly and never auto-wrap across `-180/180` (use accumulating angles with\n * `continuous: true`); mirrored revolute axes need negated tracks (see\n * `Assembly.connect()`); `min`/`max` bound the slider only — keyframes are not\n * clamped; coupled joints cannot be keyframe targets.\n *\n * @param options - `enabled`, `joints`, `couplings`, `animations`, `defaultAnimation`\n * @softDeprecated return the Assembly directly (return mech) — solver-backed controls appear automatically; use mech.addAnimation(name, { keyframes }) for animation clips\n * @deprecated use return the Assembly directly (return mech) — solver-backed controls appear automatically; use mech.addAnimation(name, { keyframes }) for animation clips\n * @skillSuppress Compatibility-only viewport FK API. Prefer returning `Assembly` directly so controls move through the solver-backed link/edge kinematics model.\n * @category Animation\n */\ndeclare function jointsView(options?: JointsViewOptions): void;\ninterface BomOpts {\n /**\n * Quantity unit label, e.g. "mm", "pieces", "kg".\n * Default: "pieces"\n */\n unit?: string;\n /**\n * Optional explicit grouping key used during report aggregation.\n */\n key?: string;\n /** Material name, e.g. "steel", "birch plywood", "nylon" */\n material?: string;\n /** Overall dimensions `[width, height]` or `[width, height, thickness]` in the entry\'s unit */\n dimensions?: number[];\n /** Cross-section dimensions `[w, h]` for tubes and profiles */\n section?: number[];\n /** Wall thickness for hollow sections (mm) */\n wall?: number;\n /** Diameter for round stock, bolts, dowels (mm) */\n diameter?: number;\n /** Length for fasteners (mm) */\n length?: number;\n /** Manufacturing process, e.g. "laser cut", "CNC", "welded" */\n process?: string;\n /** Free-form notes */\n notes?: string;\n /** Wood grain direction, e.g. "long", "cross" */\n grain?: string;\n /** Extensible for domain-specific fields */\n [key: string]: unknown;\n}\n/**\n * Register a Bill of Materials entry for report export.\n *\n * **Details**\n *\n * BOM entries are accumulated during script execution and exported alongside\n * the model in report views. Rows are grouped by normalized\n * `description + unit`. Pass an explicit `key` to force multiple descriptions\n * to collapse into a single line item.\n *\n * - `quantity` must be a finite number `>= 0`. A quantity of `0` is silently\n * ignored (useful for conditional scripting with `param()`-driven counts).\n * - `unit` defaults to `"pieces"` when omitted or empty.\n * - The assembly `solved.bom()` / `solved.bomCsv()` API is separate and\n * covers per-part assembly metadata; this function is for free-form\n * purchased-item annotation.\n * - `bom()` is injected into every `.forge.js` script. Call it directly; do\n * not write `const { bom } = require(...)`, because top-level declarations\n * named `bom` collide with the built-in runtime name.\n *\n * **Example**\n *\n * ```ts\n * const tubeLen = param("Tube Length", 1200, { min: 300, max: 4000, unit: "mm" });\n * const boltCount = param("Bolt Count", 16, { min: 0, max: 200, integer: true });\n *\n * bom(tubeLen, "iron tube 30 x 20", { unit: "mm" });\n * bom(boltCount, "M4 bolt, 16 mm length");\n * bom(4, "rubber foot", { key: "foot-rubber" }); // explicit aggregation key\n *\n * // Structured metadata for richer reports:\n * bom(tubeLen, "rectangular steel tube", {\n * unit: "mm",\n * material: "steel",\n * section: [30, 20],\n * wall: 3,\n * });\n * ```\n *\n * @param quantity - Quantity of the item; must be finite and `>= 0`\n * @param description - Human-readable item text, e.g. `"M4 bolt, 16 mm length"`\n * @param opts - Optional `unit` label (default `"pieces"`), aggregation `key`, and structured metadata (`material`, `section`, `wall`, `diameter`, `length`, `process`, `notes`, `grain`, etc.)\n * @category Bill of Materials\n */\ndeclare function bom(quantity: number, description: string, opts?: BomOpts): void;\ntype CompareAlignMode = "none" | "center" | "center-scale";\ninterface CompareWithOptions {\n /** Candidate alignment before scoring. Defaults to no automatic alignment. */\n align?: CompareAlignMode;\n /** Distance tolerance in model units for coverage scoring. Defaults to the comparison scorer\'s auto tolerance. */\n toleranceMm?: number;\n /** Surface samples per direction for numeric scoring. Defaults to the comparison scorer\'s standard sample count. */\n samples?: number;\n /** Human label for the reference model in inspection manifests. */\n label?: string;\n}\n/**\n * Declare a reference model for comparison inspection.\n *\n * **Details**\n *\n * `compareWith()` lets a model carry its own comparison target for inspection\n * workflows. `forgecad inspect compare overlay model.forge.js`\n * uses this reference to render the same Difference Only comparison overlay as\n * the live viewport. Amber marks candidate mismatch evidence, cyan marks\n * reference mismatch evidence, and faint model context keeps the overlay\n * readable. When the CLI can resolve the referenced file, the manifest also\n * includes the same geometric score produced by `forgecad compare 3d`.\n *\n * The path is resolved relative to the file that calls `compareWith()`. It may\n * point to another `.forge.js` file or an imported CAD asset such as `.stl`,\n * `.obj`, `.3mf`, `.step`, or `.stp`.\n *\n * **Example**\n *\n * ```js\n * compareWith(\'./reference.3mf\', { align: \'center\', toleranceMm: 0.25, samples: 3000 });\n * return rebuiltBearing;\n * ```\n *\n * @param path - Reference model path, relative to the current ForgeCAD file\n * @param options - Comparison scoring and manifest options\n * @returns void\n * @category Inspection\n */\ndeclare function compareWith(path: string, options?: CompareWithOptions): void;\ntype CutPlaneExcludeInput = string | string[];\ninterface CutPlaneOptions {\n /** Optional offset along the plane normal (primarily for object-form overload). */\n offset?: number;\n /** Object names to keep uncut for this plane. */\n exclude?: CutPlaneExcludeInput;\n}\n/**\n * Define a named section plane for inspecting internal geometry.\n *\n * **Details**\n *\n * Registers a cut plane that appears as a toggle in the viewport View Panel. When enabled,\n * geometry on the positive side of the plane (the side the normal points toward) is clipped away,\n * revealing the internal cross-section. The newly exposed section faces render with a hatched\n * overlay; pre-existing coplanar boundary faces are left unhatched.\n *\n * Planes are registered once per script run. The viewport toggle state (on/off) persists across\n * parameter changes without re-running the script. The `exclude` option only works correctly when\n * the excluded object names are stable across parameter changes.\n *\n * Accepts two overloads: `cutPlane(name, normal, offset?, options?)` or\n * `cutPlane(name, normal, options?)` where options may include `offset`.\n *\n * **Example**\n *\n * ```js\n * const cutZ = param(\'Cut Height\', 10, { min: -50, max: 50, unit: \'mm\' });\n * cutPlane(\'Inspection\', [0, 0, 1], cutZ, { exclude: [\'Probe\', \'Fasteners\'] });\n * ```\n *\n * @param name - Display label shown in the viewport View Panel\n * @param normal - Plane normal `[x, y, z]` — geometry on this side is clipped away\n * @param offset - Distance from origin along the normal. Default: 0\n * @param options.exclude - Object name(s) to keep uncut for this plane\n * @returns void\n * @category Cut Plane\n */\ndeclare function cutPlane(name: string, normal: [\n number,\n number,\n number\n], offset?: number, options?: CutPlaneOptions): void;\ndeclare function cutPlane(name: string, normal: [\n number,\n number,\n number\n], options?: CutPlaneOptions): void;\ntype RenderLabelAnchor = "center" | "top" | "bottom" | "left" | "right" | "top-left" | "top-right" | "bottom-left" | "bottom-right";\ninterface RenderLabelOptions {\n /** Text color as any CSS color string. */\n color?: string;\n /** Background color as any CSS color string. Use `\'transparent\'` for no pill background. */\n background?: string;\n /** Font size in CSS pixels. Defaults to 12. */\n size?: number;\n /** Additional world-space offset from `at`. */\n offset?: [\n number,\n number,\n number\n ];\n /** Which point of the label box is anchored to `at`. Defaults to `\'center\'`. */\n anchor?: RenderLabelAnchor;\n /** When false, the label is hidden when occluded by scene geometry. Defaults to true. */\n alwaysOnTop?: boolean;\n}\ninterface RenderLabelDef extends RenderLabelOptions {\n id: string;\n text: string;\n at: [\n number,\n number,\n number\n ];\n offset: [\n number,\n number,\n number\n ];\n anchor: RenderLabelAnchor;\n alwaysOnTop: boolean;\n}\n/**\n * Viewport-only presentation helpers.\n *\n * Use `Viewport.label()` for temporary review, debug, tutorial, or explicitly\n * requested presentation annotations that should appear in the viewer but should\n * not become CAD geometry. This keeps annotations separate from semantic face\n * labels and from `text2d()` geometry.\n *\n * @category Viewport Labels\n */\ndeclare const Viewport: {\n /**\n * Add a render-only viewport label at a world-space point.\n *\n * **Details**\n *\n * `Viewport.label()` is for temporary review, debug, tutorial, or explicitly\n * requested presentation overlays. It does not create sketches, meshes,\n * B-rep topology, exported text, or face labels, so it stays off the OCCT\n * path. Default production models should be understandable from physical\n * geometry, materials, part boundaries, and named objects, not viewport\n * annotations.\n *\n * Use `text2d()` only when the letters should become manufactured geometry,\n * such as raised lettering, engraved serial numbers, or exported nameplates.\n *\n * Labels are collected during script execution and rendered by the viewport\n * as lightweight overlay annotations. They are ignored by exports and do not\n * appear in `objects`.\n *\n * **Example**\n *\n * ```js\n * Viewport.label(\'Bearing bore\', [0, 0, 18], {\n * color: \'#f8fafc\',\n * background: \'#0f172acc\',\n * offset: [0, 0, 8],\n * anchor: \'bottom\',\n * });\n *\n * return box(40, 30, 12);\n * ```\n *\n * @param text - Annotation text to display in the viewport\n * @param at - World-space anchor point `[x, y, z]`\n * @param options - Visual label options\n * @returns void\n * @category Viewport Labels\n */\n label(text: string, at: [\n number,\n number,\n number\n ], options?: RenderLabelOptions): void;\n /**\n * Highlight any geometry for visual debugging in the viewport.\n *\n * **Details**\n *\n * `Viewport.highlight()` draws render-only debug overlays — distinctive\n * colored markers that appear in the viewport but never become geometry,\n * never export, and never appear in `objects`.\n *\n * Supported inputs:\n * - `[x, y, z]` — 3D point\n * - `[[x1,y1,z1], [x2,y2,z2]]` — edge (line segment)\n * - `{ normal: [x,y,z], offset: number }` — plane by normal + distance from origin\n * - `{ normal: [x,y,z], point: [x,y,z] }` — plane by normal + point on plane\n * - `Shape` — highlight an entire 3D shape\n * - `FaceRef` (from `shape.face(\'top\')`) — highlight as plane at face center\n * - `EdgeRef` (from `shape.edge(\'left\')`) — highlight as edge segment\n * - `string` — 2D sketch entity ID (e.g. `\'L0\'`, `\'P0\'`)\n *\n * Pass `{ labels: true }` with a `Shape` to annotate every user-authored\n * labeled face with its name (the face-label debugging view).\n *\n * **Example**\n *\n * ```js\n * const b = box(30, 20, 15);\n * Viewport.highlight([0, 0, 0], { color: \'cyan\', label: \'origin\' });\n * Viewport.highlight(b.face(\'top\'), { color: \'red\' });\n * Viewport.highlight(b, { labels: true }); // annotate all user-labeled faces\n * return b;\n * ```\n *\n * @param target - Point, edge, plane, Shape, FaceRef, EdgeRef, or sketch entity ID\n * @param options - `color`, `label`, `pulse`, `size` (points/planes), `labels` (Shape only)\n * @returns void\n * @category Viewport Labels\n */\n highlight(target: unknown, options?: HighlightOptions): void;\n};\ntype Vec3$3 = [\n number,\n number,\n number\n];\ninterface HermiteCurveEndpoint {\n /** Position */\n point: Vec3$3;\n /** Tangent direction (will be normalized internally) */\n tangent: Vec3$3;\n /** Weight: scales tangent magnitude relative to chord length. Default 1.0. */\n weight?: number;\n}\ndeclare class HermiteCurve3D {\n /** Start position */\n readonly p0: Vec3$3;\n /** End position */\n readonly p1: Vec3$3;\n /** Scaled tangent at start (direction * weight * chordLength) */\n readonly t0: Vec3$3;\n /** Scaled tangent at end (direction * weight * chordLength) */\n readonly t1: Vec3$3;\n /** Chord length (straight-line distance between endpoints) */\n readonly chordLength: number;\n constructor(start: HermiteCurveEndpoint, end: HermiteCurveEndpoint);\n /** Evaluate position at parameter t ∈ [0, 1] */\n pointAt(t: number): Vec3$3;\n /** Evaluate tangent (first derivative) at parameter t ∈ [0, 1] */\n tangentAt(t: number): Vec3$3;\n /** Evaluate curvature vector (second derivative) at parameter t ∈ [0, 1] */\n curvatureAt(t: number): Vec3$3;\n /** Sample the curve as a polyline of evenly-spaced parameter values. */\n sample(count?: number): Vec3$3[];\n /** Approximate arc length by sampling. */\n length(samples?: number): number;\n /**\n * Sample with adaptive density — more points where curvature is higher.\n * Returns at least `minCount` points, up to `maxCount`.\n */\n sampleAdaptive(minCount?: number, maxCount?: number): Vec3$3[];\n /** Convert to a format compatible with sweep() path input. */\n toPolyline(samples?: number): Vec3$3[];\n}\ninterface QuinticHermiteCurveEndpoint {\n /** Position */\n point: Vec3$3;\n /** Tangent direction (will be normalized internally) */\n tangent: Vec3$3;\n /** Second derivative / curvature vector. Default [0, 0, 0]. */\n curvature?: Vec3$3;\n /** Weight: scales tangent magnitude relative to chord length. Default 1.0. */\n weight?: number;\n}\ndeclare class QuinticHermiteCurve3D {\n /** Start position */\n readonly p0: Vec3$3;\n /** End position */\n readonly p1: Vec3$3;\n /** Scaled tangent at start (direction * weight * chordLength) */\n readonly t0: Vec3$3;\n /** Scaled tangent at end (direction * weight * chordLength) */\n readonly t1: Vec3$3;\n /** Scaled second derivative at start (curvature * weight² * chordLength²) */\n readonly c0: Vec3$3;\n /** Scaled second derivative at end (curvature * weight² * chordLength²) */\n readonly c1: Vec3$3;\n /** Chord length (straight-line distance between endpoints) */\n readonly chordLength: number;\n constructor(start: QuinticHermiteCurveEndpoint, end: QuinticHermiteCurveEndpoint);\n /** Evaluate position at parameter t ∈ [0, 1] */\n pointAt(t: number): Vec3$3;\n /** Evaluate tangent (first derivative, normalized) at parameter t ∈ [0, 1] */\n tangentAt(t: number): Vec3$3;\n /** Evaluate curvature vector (second derivative) at parameter t ∈ [0, 1] */\n curvatureAt(t: number): Vec3$3;\n /** Sample the curve as a polyline of evenly-spaced parameter values. */\n sample(count?: number): Vec3$3[];\n /** Approximate arc length by sampling. */\n length(samples?: number): number;\n /**\n * Sample with adaptive density — more points where curvature is higher.\n * Returns at least `minCount` points, up to `maxCount`.\n */\n sampleAdaptive(minCount?: number, maxCount?: number): Vec3$3[];\n /** Convert to a format compatible with sweep() path input. */\n toPolyline(samples?: number): Vec3$3[];\n}\ntype Vec3$4 = [\n number,\n number,\n number\n];\ninterface NurbsCurve3DOptions {\n /** Polynomial degree (default 3 = cubic). Must be ≥ 1. */\n degree?: number;\n /** Rational weights, one per control point (default: all 1.0 = non-rational). */\n weights?: number[];\n /** Knot vector (default: uniform clamped). Must have length = controlPoints.length + degree + 1. */\n knots?: number[];\n /** Whether the curve is closed/periodic (default false). */\n closed?: boolean;\n}\ndeclare class NurbsCurve3D {\n readonly controlPoints: Vec3$4[];\n readonly weights: number[];\n readonly knots: number[];\n readonly degree: number;\n readonly closed: boolean;\n constructor(points: Vec3$4[], options?: NurbsCurve3DOptions);\n /**\n * Evaluate the curve at parameter t ∈ [0, 1].\n * Uses De Boor\'s algorithm — exact, O(degree²).\n */\n pointAt(t: number): Vec3$4;\n /**\n * Evaluate the unit tangent vector at parameter t ∈ [0, 1].\n */\n tangentAt(t: number): Vec3$4;\n /**\n * Sample the curve uniformly at `count` points.\n */\n sample(count?: number): Vec3$4[];\n /**\n * Sample with adaptive density — more points in high-curvature regions.\n */\n sampleAdaptive(minCount?: number, maxCount?: number): Vec3$4[];\n /**\n * Approximate arc length by summing polyline segment lengths.\n */\n length(samples?: number): number;\n /** Convert to a format compatible with sweep() path input. */\n toPolyline(samples?: number): Vec3$4[];\n private estimateCurvature;\n}\ninterface Route3DFromPolylineOptions {\n /** Bend radius applied to every virtual interior corner. Default 0 keeps sharp polyline corners. */\n cornerRadius?: number;\n /** Name for the start port. Default "start". */\n startPort?: string;\n /** Name for the end port. Default "end". */\n endPort?: string;\n /** Preferred up vector for deterministic port frames. Default [0, 0, 1]. */\n up?: Vec3;\n}\ninterface Route3DToPolylineOptions {\n /** Approximate target point count for the full route. */\n samples?: number;\n /** Maximum angular spacing on arc segments. Default 6 degrees. */\n maxAngleDeg?: number;\n}\ntype Route3DVec3 = Vec3;\ninterface RoutePortFrame {\n name: string;\n origin: Route3DVec3;\n axis: Route3DVec3;\n xAxis: Route3DVec3;\n yAxis: Route3DVec3;\n station: number;\n}\ntype Route3DSegment = {\n kind: "line";\n from: Route3DVec3;\n to: Route3DVec3;\n length: number;\n} | {\n kind: "arc";\n center: Route3DVec3;\n radius: number;\n axis: Route3DVec3;\n start: Route3DVec3;\n end: Route3DVec3;\n sweepDeg: number;\n length: number;\n};\n/**\n * Metadata-bearing analytic 3D route made from line and arc segments.\n *\n * Use `Curve.Route.fromPolyline()` when you know the virtual design skeleton points\n * and bend radius. ForgeCAD computes tangent trim points, bend arcs, total\n * length, and named start/end port frames. Pass the route directly to `sweep()`.\n *\n * ```js\n * const route = Curve.Route.fromPolyline(\n * [[0, 0, 0], [0, 0, 80], [60, 0, 80]],\n * { cornerRadius: 24, startPort: "inlet", endPort: "outlet" },\n * );\n * const pipe = sweep(difference2d(circle2d(8), circle2d(6)), route);\n * const outlet = route.port("outlet");\n * ```\n */\ndeclare class Route3D {\n private readonly plan;\n private constructor();\n /** Build a line/arc route from virtual polyline corner points. */\n static fromPolyline(points: Route3DVec3[], options?: Route3DFromPolylineOptions): Route3D;\n /** Total centerline length, including line and bend arc segments. */\n get length(): number;\n /** Exact line and arc segments that make up this route. */\n get segments(): Route3DSegment[];\n /** Named port frames, keyed by port name. */\n get ports(): Record<string, RoutePortFrame>;\n /** Return one named route port frame. */\n port(name: string): RoutePortFrame;\n /** Convert this route to the compile plan consumed by sweep(). */\n toSweepPathPlan(): SweepPathCompilePlan;\n /** Sample this analytic route as a polyline for inspection or backend lowering. */\n toPolyline(options?: number | Route3DToPolylineOptions): Route3DVec3[];\n}\ntype Vec2$1 = [\n number,\n number\n];\ntype Vec3$5 = [\n number,\n number,\n number\n];\ninterface Spline2DOptions {\n /** Closed loop (default true). */\n closed?: boolean;\n /** Catmull-Rom tension in [0, 1]. 0 = very round, 1 = linear-ish. Default 0.5. */\n tension?: number;\n /** Samples per segment (minimum 3). Default 16. */\n samplesPerSegment?: number;\n /**\n * For open splines, provide stroke width to return a solid Sketch.\n * If omitted for open splines, an error is thrown.\n */\n strokeWidth?: number;\n /** Stroke join for open splines. Default \'Round\'. */\n join?: "Round" | "Square";\n}\ninterface Spline3DOptions {\n /** Closed loop (default false). */\n closed?: boolean;\n /** Catmull-Rom tension in [0, 1]. 0 = very round, 1 = linear-ish. Default 0.5. */\n tension?: number;\n}\ninterface LoftOptions {\n /** Marching-grid edge length for level-set meshing. Smaller = finer. */\n edgeLength?: number;\n /** Optional extra bounds padding. */\n boundsPadding?: number;\n}\ninterface FieldLoftOptions extends LoftOptions {\n /** Simplification control after field extraction. Default is topology-safe simplification. */\n simplify?: boolean | "safe";\n /** Hard post-extraction triangle budget. Must be a positive integer. If safe simplification cannot reach it, the build fails. */\n maxTriangles?: number;\n}\ninterface SweepOptions {\n /** Number of samples when path is a Curve3D. Default 48. */\n samples?: number;\n /** Marching-grid edge length for level-set meshing. Smaller = finer. */\n edgeLength?: number;\n /** Optional extra bounds padding. */\n boundsPadding?: number;\n /**\n * Preferred "up" vector for local profile frame.\n * Auto fallback is used near parallel segments.\n */\n up?: Vec3$5;\n}\ninterface VariableSweepSection {\n /** Parameter along the spine (0 = start, 1 = end). */\n t: number;\n /** Cross-section profile at this station. */\n profile: Sketch;\n}\ninterface VariableSweepOptions {\n /** Number of samples when spine is a Curve3D. Default 48. */\n samples?: number;\n /** Marching-grid edge length for level-set meshing. Smaller = finer. */\n edgeLength?: number;\n /** Optional extra bounds padding. */\n boundsPadding?: number;\n /**\n * Preferred "up" vector for local profile frame.\n * Auto fallback is used near parallel segments.\n */\n up?: Vec3$5;\n}\ndeclare class Curve3D {\n readonly points: Vec3$5[];\n readonly closed: boolean;\n readonly tension: number;\n constructor(points: Vec3$5[], options?: Spline3DOptions);\n /** Sample the curve with a fixed number of points per segment. */\n sampleBySegment(samplesPerSegment?: number): Vec3$5[];\n /** Sample the curve to an approximate total point count. */\n sample(count?: number): Vec3$5[];\n /** Map normalized t ∈ [0,1] to segment index + local parameter. */\n private segmentAt;\n /** Return the position on the curve at normalized parameter `t` in `[0, 1]`. O(1), no allocations. */\n pointAt(t: number): Vec3$5;\n /** Return a unit tangent vector at normalized parameter `t` in `[0, 1]`. O(1), analytical derivative. */\n tangentAt(t: number): Vec3$5;\n /** Approximate the curve length by polyline sampling. */\n length(samples?: number): number;\n}\n/**\n * Build a smooth Catmull-Rom spline sketch from 2D control points.\n *\n * A closed spline (default) returns a filled profile. An open spline requires\n * a strokeWidth option to produce a solid sketch. Use tension (0..1, default 0.5)\n * to control curve tightness.\n */\ndeclare function spline2d(points: Vec2$1[], options?: Spline2DOptions): Sketch;\n/**\n * Loft between multiple sketches along Z stations.\n *\n * Profiles can differ in topology and vertex count: interpolation is done on\n * signed-distance fields and meshed with level-set extraction. Heights must be\n * strictly increasing. Compatible loft stacks can also stay on the maintained\n * export-backend path.\n *\n * The surface is smooth through 3+ stations (C1 spanwise interpolation, like\n * CAD lofts), so it can bow slightly past the straight ruling between\n * stations; sections are matched exactly at their stations. Two-station lofts\n * are ruled. `edgeLength` caps the sample spacing in curved or twisted\n * regions (quality presets scale it); straight regions keep input density.\n *\n * Performance note: loft is significantly heavier than primitive/extrude/revolve.\n * If the part is axis-symmetric (bottles, vases, knobs), prefer revolve().\n */\ndeclare function loft(profiles: Sketch[], heights: number[], options?: LoftOptions): Shape;\ntype SweepPathInput = Curve3D | HermiteCurve3D | QuinticHermiteCurve3D | NurbsCurve3D | Route3D | Vec3$5[];\ndeclare function sweep(profile: Sketch, path: SweepPathInput, options?: SweepOptions): Shape;\n/**\n * Sweep a variable cross-section along a 3D spine curve.\n *\n * Unlike sweep(), which uses a single constant profile, variableSweep()\n * interpolates between multiple profiles at different stations along the spine.\n * This enables organic shapes like tapering tubes, bone-like structures, and\n * sculptural forms.\n *\n * Each section specifies a t parameter (0 = start, 1 = end of spine) and a\n * 2D profile sketch. The SDF-based level-set mesher smoothly blends between\n * profiles at intermediate positions.\n *\n * Performance note: like sweep(), this uses level-set meshing internally.\n */\ndeclare function variableSweep(spine: SweepPathInput, sections: VariableSweepSection[], options?: VariableSweepOptions): Shape;\n/**\n * Combine 2D sketches into a single profile using an additive boolean union.\n *\n * **Details**\n *\n * Accepts individual sketches or arrays: `union2d(a, b, c)` or `union2d([a, b, c])`.\n * Uses Manifold\'s batch operation — faster than chaining `.add()` one by one when\n * combining many sketches.\n *\n * **Example**\n *\n * ```ts\n * const cross = union2d(rect(60, 10), rect(10, 60));\n * ```\n *\n * @returns A new sketch containing the union of all inputs\n * @see {@link Sketch.add} for the chainable method equivalent\n * @category Sketch Booleans\n */\ndeclare function union2d(...inputs: SketchOperandInput[]): Sketch;\n/**\n * Subtract one or more 2D sketches from a base sketch.\n *\n * **Details**\n *\n * The first sketch is the base; all subsequent sketches are subtracted from it.\n * Accepts individual sketches or arrays: `difference2d(base, c1, c2)` or\n * `difference2d([base, c1, c2])`.\n * Uses Manifold\'s batch operation — faster than chaining `.subtract()` one by one.\n *\n * **Example**\n *\n * ```ts\n * const donut = difference2d(circle2d(50), circle2d(30));\n * ```\n *\n * @returns A new sketch with all subsequent inputs subtracted from the first\n * @see {@link Sketch.subtract} for the chainable method equivalent\n * @category Sketch Booleans\n */\ndeclare function difference2d(...inputs: SketchOperandInput[]): Sketch;\n/**\n * Keep only the area where all input sketches overlap (intersection boolean).\n *\n * **Details**\n *\n * Accepts individual sketches or arrays: `intersection2d(a, b)` or `intersection2d([a, b, c])`.\n * Uses Manifold\'s batch operation — faster than chaining `.intersect()` one by one.\n *\n * **Example**\n *\n * ```ts\n * const lens = intersection2d(circle2d(30).translate(-10, 0), circle2d(30).translate(10, 0));\n * ```\n *\n * @returns A new sketch containing only the shared area of all inputs\n * @see {@link Sketch.intersect} for the chainable method equivalent\n * @category Sketch Booleans\n */\ndeclare function intersection2d(...inputs: SketchOperandInput[]): Sketch;\ntype RectVertexName = "bottomLeft" | "bottomRight" | "topRight" | "topLeft";\ntype RectSideName = "bottom" | "right" | "top" | "left";\ninterface RectOptions {\n /** Bottom-left x coordinate. Default: 0. */\n x?: number;\n /** Bottom-left y coordinate. Default: 0. */\n y?: number;\n /** Width (along x). Default: 10. */\n width?: number;\n /** Height (along y). Default: 10. */\n height?: number;\n /** Prevent 180° rotation (ensures bottom edge points rightward). Default: false. */\n blockRotation?: boolean;\n}\n/**\n * Typed handle for a constrained axis-aligned rectangle in the solver.\n *\n * Structural constraints pre-applied:\n * `horizontal(bottom)`, `horizontal(top)`, `vertical(left)`, `vertical(right)`,\n * `ccw(bl, br, tr, tl)`.\n *\n * This leaves **4 DOF** (position x/y, width, height). Use `sk.fix()`,\n * `sk.length()`, `sk.shapeWidth()`, etc. to pin them.\n */\ninterface ConstrainedRect {\n readonly bottomLeft: PointId;\n readonly bottomRight: PointId;\n readonly topRight: PointId;\n readonly topLeft: PointId;\n /** bottom-left → bottom-right */\n readonly bottom: LineId;\n /** bottom-right → top-right */\n readonly right: LineId;\n /** top-right → top-left */\n readonly top: LineId;\n /** top-left → bottom-left */\n readonly left: LineId;\n /**\n * Center point constrained to the geometric center via `midpoint` on the diagonal.\n * Can be used in further constraints: `sk.fix(rect.center, 0, 0)`,\n * `sk.coincident(rect.center, other)`.\n */\n readonly center: PointId;\n /** ShapeId for `shapeWidth`, `shapeHeight`, `shapeArea`, `shapeCentroidX/Y`. */\n readonly shape: ShapeId;\n /** CCW-ordered vertex array: [bottomLeft, bottomRight, topRight, topLeft]. */\n readonly vertices: [\n PointId,\n PointId,\n PointId,\n PointId\n ];\n /** CCW-ordered side array: [bottom, right, top, left]. */\n readonly sides: [\n LineId,\n LineId,\n LineId,\n LineId\n ];\n /** Named vertex lookup. */\n vertex(name: RectVertexName): PointId;\n /** Named side lookup. */\n side(name: RectSideName): LineId;\n}\n/**\n * Add an axis-aligned rectangle concept to the builder.\n *\n * Creates 4 vertices (CCW: bl→br→tr→tl), 4 sides, 4 structural constraints\n * (`horizontal`/`vertical` on each side), CCW winding, a center point, a loop,\n * and a shape. Returns a `ConstrainedRect` handle with 4 DOF (x, y, width, height).\n *\n * Use `sk.rect()` as the shorthand builder method.\n *\n * **Example**\n *\n * ```ts\n * const sk = constrainedSketch();\n * const r = sk.rect({ x: 0, y: 0, width: 100, height: 50 });\n * sk.fix(r.bottomLeft, 0, 0);\n * sk.length(r.bottom, 120); // override initial width\n * return sk.solve().extrude(10);\n * ```\n *\n * @param sk - The builder to add the rectangle to.\n * @param options - Initial position and size.\n * @returns A `ConstrainedRect` handle with named vertices, sides, and center.\n * @softDeprecated sk.rect({ x: 0, y: 0, width: 100, height: 50 }) — same options, called on the constrainedSketch() builder\n * @deprecated use sk.rect({ x: 0, y: 0, width: 100, height: 50 }) — same options, called on the constrainedSketch() builder\n * @category Constrained Sketches\n */\ndeclare function addRect(sk: ConstrainedSketchBuilder, options?: RectOptions): ConstrainedRect;\ninterface ConstrainedSketchBuilder {\n /**\n * Add an axis-aligned rectangle concept.\n * Returns a `ConstrainedRect` handle with named vertices, sides, and center.\n */\n rect(options?: RectOptions): ConstrainedRect;\n}\ninterface PolygonOptions {\n /** Initial vertex coordinates. Minimum 3 points. */\n points: ReadonlyArray<readonly [\n number,\n number\n ]>;\n /**\n * Whether to register a closed loop for sketch generation.\n * Default: true.\n */\n addLoop?: boolean;\n /** Prevent 180° rotation (ensures first edge maintains its initial direction). Default: false. */\n blockRotation?: boolean;\n}\n/**\n * Typed handle for a general constrained polygon in the solver.\n *\n * Structural constraints pre-applied: `ccw(vertices)` for winding enforcement.\n *\n * `sides[i]` goes from `vertices[i]` → `vertices[(i+1) % n]`.\n */\ninterface ConstrainedPolygon {\n /** CCW-ordered PointIds. */\n readonly vertices: PointId[];\n /**\n * CCW-ordered LineIds.\n * `sides[i]` runs from `vertices[i]` → `vertices[(i+1) % n]`.\n */\n readonly sides: LineId[];\n /** ShapeId for `shapeWidth`, `shapeHeight`, `shapeArea`, `shapeCentroidX/Y`. */\n readonly shape: ShapeId;\n /** Get vertex by index. */\n vertex(index: number): PointId;\n /** Get side by index (side `i` goes vertex `i` → vertex `(i+1) % n`). */\n side(index: number): LineId;\n}\n/**\n * Add a general polygon concept to the builder.\n *\n * Creates n vertices and n sides (CCW: `sides[i]` from `vertices[i]` →\n * `vertices[(i+1) % n]`). Applies a `ccw` constraint to enforce winding.\n * All dimensional constraints (lengths, angles, position) are left to the caller.\n *\n * Use `sk.addPolygon()` as the shorthand builder method.\n *\n * **Example**\n *\n * ```ts\n * const sk = constrainedSketch();\n * const tri = sk.addPolygon({ points: [[0,0],[100,0],[50,80]] });\n * sk.fix(tri.vertex(0), 0, 0);\n * sk.length(tri.side(0), 100);\n * return sk.solve().extrude(5);\n * ```\n *\n * @param sk - The builder to add the polygon to.\n * @param options - Vertex coordinates and loop registration options.\n * @returns A `ConstrainedPolygon` handle with indexed vertex and side access.\n * @softDeprecated sk.addPolygon({ points: [[0,0],[100,0],[50,80]] }) — same options, called on the constrainedSketch() builder\n * @deprecated use sk.addPolygon({ points: [[0,0],[100,0],[50,80]] }) — same options, called on the constrainedSketch() builder\n * @category Constrained Sketches\n */\ndeclare function addPolygon(sk: ConstrainedSketchBuilder, options: PolygonOptions): ConstrainedPolygon;\ninterface ConstrainedSketchBuilder {\n /**\n * Add a general polygon concept (CCW winding enforced).\n * Returns a `ConstrainedPolygon` handle.\n */\n addPolygon(options: PolygonOptions): ConstrainedPolygon;\n}\ninterface RegularPolygonOptions {\n /** Number of sides (minimum 3). */\n sides: number;\n /** Circumradius — distance from center to vertex. Default: 10. */\n radius?: number;\n /** Center x coordinate. Default: 0. */\n cx?: number;\n /** Center y coordinate. Default: 0. */\n cy?: number;\n /**\n * Angle (in degrees) of vertex[0] measured from the +X axis (CCW positive).\n * Default: 0 (rightmost vertex).\n */\n startAngle?: number;\n /** Prevent 180° rotation (ensures first edge maintains its initial direction). Default: false. */\n blockRotation?: boolean;\n}\n/**\n * Typed handle for a constrained regular polygon in the solver.\n *\n * Structural constraints pre-applied:\n * - `equal(sides[0], sides[1])`, ..., `equal(sides[n-2], sides[n-1])` — equal sides\n * - `ccw(vertices)` — CCW winding\n *\n * Leaves **4 DOF** (center x/y, radius/scale, rotation). The center point is\n * tracked by the solver and exposed for further constraints.\n *\n * Note: Equal sides + CCW + regular initial placement makes this\n * "practically regular" for the solver. If you need geometrically exact\n * regularity (equal angles too), add `angleBetween` constraints on each pair\n * of adjacent sides.\n */\ninterface ConstrainedRegularPolygon extends ConstrainedPolygon {\n /**\n * Center point. Use `sk.fix(poly.center, x, y)` to pin location,\n * or `sk.coincident(poly.center, other)` to align with other geometry.\n */\n readonly center: PointId;\n}\n/**\n * Add a regular n-gon concept to the builder.\n *\n * Vertices are placed at `(cx + r·cos(startAngle + i·2π/n), cy + r·sin(...))`.\n * Equal-radius and equal-side constraints enforce regularity (4 DOF: center x/y, radius, rotation).\n * The center point is tracked by the solver and exposed via the returned handle.\n *\n * Use `sk.regularPolygon()` as the shorthand builder method.\n *\n * **Example**\n *\n * ```ts\n * const sk = constrainedSketch();\n * const hex = sk.regularPolygon({ sides: 6, radius: 25 });\n * sk.fix(hex.center, 0, 0);\n * sk.length(hex.side(0), 30); // all sides change (equal constraint)\n * return sk.solve().extrude(5);\n * ```\n *\n * @param sk - The builder to add the polygon to.\n * @param options - Number of sides, circumradius, center position, and rotation.\n * @returns A `ConstrainedRegularPolygon` handle extending `ConstrainedPolygon` with a `center` point.\n * @softDeprecated sk.regularPolygon({ sides: 6, radius: 25 }) — same options, called on the constrainedSketch() builder\n * @deprecated use sk.regularPolygon({ sides: 6, radius: 25 }) — same options, called on the constrainedSketch() builder\n * @category Constrained Sketches\n */\ndeclare function addRegularPolygon(sk: ConstrainedSketchBuilder, options: RegularPolygonOptions): ConstrainedRegularPolygon;\ninterface ConstrainedSketchBuilder {\n /**\n * Add a regular n-gon concept (equal sides, CCW winding).\n * Returns a `ConstrainedRegularPolygon` handle with a center point.\n */\n regularPolygon(options: RegularPolygonOptions): ConstrainedRegularPolygon;\n}\ninterface GroupRectOptions {\n /** Bottom-left x coordinate (world). Default: 0. */\n x?: number;\n /** Bottom-left y coordinate (world). Default: 0. */\n y?: number;\n /** Width (along x in local coords). Required. */\n width: number;\n /** Height (along y in local coords). Required. */\n height: number;\n /** Allow the solver to rotate this rectangle. Default: false. */\n allowRotation?: boolean;\n}\ninterface ConstrainedGroupRect extends SketchGroupHandle {\n readonly bottomLeft: PointId;\n readonly bottomRight: PointId;\n readonly topRight: PointId;\n readonly topLeft: PointId;\n readonly bottom: LineId;\n readonly right: LineId;\n readonly top: LineId;\n readonly left: LineId;\n readonly shape: ShapeId;\n}\ninterface ConstrainedSketchBuilder {\n /**\n * Add a rigid rectangle as a group concept.\n * Returns a `ConstrainedGroupRect` handle with named vertices and sides.\n * The rectangle is fixed in shape — only position (and optionally rotation) varies.\n */\n groupRect(options: GroupRectOptions): ConstrainedGroupRect;\n}\ntype PointArg$1 = [\n number,\n number\n] | [\n number,\n number,\n number\n] | Point2D;\ninterface DimOpts {\n offset?: number;\n label?: string;\n color?: string;\n component?: string | string[];\n currentComponent?: boolean;\n}\n/**\n * Add a dimension annotation between two points, or along an entity.\n *\n * **Details**\n *\n * Dimension annotations are purely visual callouts rendered in the viewport\n * and report export. They do not affect geometry or constrain the model.\n *\n * Point arguments accept 2D tuples `[x, y]`, 3D tuples `[x, y, z]`, or\n * `Point2D` objects (Z is treated as 0 for 2D inputs).\n *\n * Entity arguments: pass a single `Line2D` (from a constrained sketch) or an\n * `EdgeRef` (from `shape.edge(\'left\')`) as the first argument to dimension\n * along that entity directly — no manual endpoint extraction needed.\n *\n * **Ownership Rules (Report Pages)**\n *\n * - `currentComponent: true` — deterministic ownership by the calling import\n * instance. Use when authoring reusable imported parts.\n * - `component: "Part Name"` — route dimension to another named returned object.\n * - Multiple owners: dimension is shared and appears on the assembly overview page.\n * - No ownership set: report export infers ownership via endpoint-in-bbox.\n *\n * **Example**\n *\n * ```ts\n * dim([-w / 2, 0, 0], [w / 2, 0, 0], { label: "Width" });\n * dim([0, 0, -h / 2], [0, 0, h / 2], { label: "Height", offset: 14 });\n * dim([0, 0, 0], [100, 0, 0], { component: "Base", color: "#00AAFF" });\n * dim(sk.line(a, b), { label: "Span", offset: -8 }); // Line2D entity\n * dim(myBox.edge("top-right"), { label: "Depth" }); // EdgeRef entity\n * ```\n *\n * @param from - Start point as `[x, y]`, `[x, y, z]`, or `Point2D` — or a\n * `Line2D` / `EdgeRef` entity (then pass `opts` as the second argument)\n * @param to - End point as `[x, y]`, `[x, y, z]`, or `Point2D`\n * @param opts - Optional: `offset` (default 10), `label`, `color` (hex),\n * `component` (string or string[] — report ownership), `currentComponent` (boolean)\n * @category Dimensions\n */\ndeclare function dim(line: Line2D, opts?: DimOpts): void;\ndeclare function dim(edge: EdgeRef, opts?: DimOpts): void;\ndeclare function dim(from: PointArg$1, to: PointArg$1, opts?: DimOpts): void;\n/**\n * Add a dimension annotation along a `Line2D`.\n *\n * **Details**\n *\n * Convenience wrapper around {@link dim} that extracts the start and end\n * points from a constrained-sketch `Line2D` entity. All `opts` are forwarded\n * unchanged.\n *\n * **Example**\n *\n * ```ts\n * const a = point(0, 0);\n * const b = point(100, 0);\n * dimLine(line(a, b), { label: "Span", offset: -8 });\n * ```\n *\n * @param l - A `Line2D` entity from the constrained sketch builder\n * @param opts - Optional: `offset`, `label`, `color`, `component`, `currentComponent`\n * @see {@link dim} for point-to-point annotations\n * @softDeprecated dim(line, opts) — dim() accepts a Line2D (or EdgeRef) directly\n * @deprecated use dim(line, opts) — dim() accepts a Line2D (or EdgeRef) directly\n * @category Dimensions\n */\ndeclare function dimLine(l: Line2D, opts?: DimOpts): void;\ntype RegionSelection = "all" | "largest";\ninterface DxfImportOptions {\n /** Keep all disconnected regions, or only the largest. */\n regionSelection?: RegionSelection;\n /** Keep at most this many regions (largest-first). */\n maxRegions?: number;\n /** Drop regions below this absolute area threshold. */\n minRegionArea?: number;\n /** Drop regions below this ratio of largest-region area. */\n minRegionAreaRatio?: number;\n /** Curve flattening tolerance in DXF drawing units. */\n flattenTolerance?: number;\n /** Minimum segment count for a full circle or 360° of arcs. */\n arcSegments?: number;\n /** Endpoint tolerance used to chain LINE/ARC entities into closed loops. */\n joinTolerance?: number;\n /** Global scale applied after DXF parsing. */\n scale?: number;\n /** Maximum imported sketch width. If exceeded, geometry is uniformly downscaled. */\n maxWidth?: number;\n /** Maximum imported sketch height. If exceeded, geometry is uniformly downscaled. */\n maxHeight?: number;\n /** Recenter imported geometry so its 2D bounds center is at CAD origin. */\n centerOnOrigin?: boolean;\n /** Simplification tolerance for final sketch cleanup. */\n simplify?: number;\n}\ninterface SketchDxfOptions {\n /** DXF layer name. Default: "0" */\n layer?: string;\n /** DXF color index (1–255, AutoCAD ACI). Default: 7 (white/black) */\n colorIndex?: number;\n}\n/**\n * Export a 2D sketch as a DXF string (R12/AC1009 — maximally compatible).\n *\n * **Details**\n *\n * For regular sketches, each polygon loop becomes a closed `LWPOLYLINE`.\n * For constrained sketches, exports raw `LINE`, `CIRCLE`, and `ARC` entities\n * from the constraint edge geometry, which preserves internal/shared edges\n * that `toPolygons()` would merge away.\n *\n * The R12 format is chosen for maximum compatibility with CAM tools,\n * laser-cutter software, and older CAD readers.\n *\n * **Example**\n *\n * ```ts\n * const s = rect(100, 60);\n * const dxf = sketchToDxf(s, { layer: \'cut\' });\n * ```\n *\n * @param sketch - A 2D `Sketch` to export\n * @param options - Optional DXF `layer` name and AutoCAD `colorIndex` (ACI 1–255)\n * @returns DXF markup string in R12/AC1009 format\n * @see {@link sketchToSvg} for SVG export\n * @category Export\n */\ndeclare function sketchToDxf(sketch: Sketch, options?: SketchDxfOptions): string;\ninterface SketchSvgOptions {\n /** Stroke color. Default: "black" */\n stroke?: string;\n /** Stroke width in sketch units. Default: 0.5 */\n strokeWidth?: number;\n /** Fill color. Default: "none" */\n fill?: string;\n /** Padding around the sketch bounding box in sketch units. Default: 2 */\n padding?: number;\n /** If set, scale so 1 sketch-unit = this many px. Otherwise auto-fit. */\n pixelsPerUnit?: number;\n}\n/**\n * Export a 2D sketch as an SVG string.\n *\n * **Details**\n *\n * For regular sketches, exports filled polygon regions. For constrained\n * sketches, exports raw edge geometry (LINE, ARC, CIRCLE) which preserves\n * internal/shared edges that `toPolygons()` would merge away.\n *\n * The SVG uses the sketch\'s native coordinate system (Y-up) with a CSS\n * transform that flips Y so the output renders correctly in SVG\'s Y-down\n * space. Coordinates are in sketch units (typically mm).\n *\n * **Example**\n *\n * ```ts\n * const s = rect(100, 60);\n * const svg = sketchToSvg(s, { stroke: \'#333\', strokeWidth: 0.8 });\n * ```\n *\n * @param sketch - A 2D `Sketch` to export\n * @param options - Optional stroke, fill, padding, and pixel-per-unit scale\n * @returns SVG markup string\n * @see {@link sketchToDxf} for DXF export\n * @category Export\n */\ndeclare function sketchToSvg(sketch: Sketch, options?: SketchSvgOptions): string;\n/**\n * Round a tracked vertical solid edge with a circular fillet.\n *\n * **Details**\n *\n * Compiler-owned fillet for a narrow tracked-edge subset on solids.\n *\n * This is **not** a general 2D sketch-corner fillet. It currently works only\n * on tracked vertical edges from `box()` or `Rectangle2D` extrusions (plus\n * rigid transforms and supported preserved descendants of those). Generic\n * sketch extrudes, including `rect(...).extrude(...)`, are outside the\n * supported subset right now.\n *\n * **Supported edges:**\n * - Tracked vertical edges from `box()` or `Rectangle2D.extrude()`\n * - Rigid transforms between tracked source and target\n * - Untouched sibling tracked vertical edges after earlier `filletTrackedEdge`/`chamferTrackedEdge`\n *\n * **Not supported:** edges after shell, hole, cut, trim, difference,\n * intersection, generic sketch extrudes, or tapered extrudes.\n *\n * Canonical quadrants: `vert-bl → [1,-1]`, `vert-br → [-1,-1]`,\n * `vert-tr → [-1,1]`, `vert-tl → [1,1]`\n *\n * **Example**\n *\n * ```ts\n * const base = Rectangle2D.fromDimensions(0, 0, 50, 50).extrude(20);\n * const filleted = filletTrackedEdge(base, base.edge(\'vert-br\'), 5, [-1, -1]);\n * ```\n *\n * @param shape - Compile-covered solid to modify\n * @param edge - `EdgeRef` from the same tracked target body (e.g. `base.edge(\'vert-br\')`)\n * @param radius - Fillet radius (must be positive and finite)\n * @param quadrant - Corner direction sign pair (default: `[-1, -1]`)\n * @param segments - Arc resolution (default: `16`)\n * @returns A new Shape with the fillet applied\n * @see {@link fillet} for the general-purpose variant — it takes the same exact compiler-owned path for tracked edges, without the quadrant parameter\n * @see {@link chamferTrackedEdge} for beveled tracked edges\n * @softDeprecated fillet(shape, radius, shape.edge(\'vert-br\')) — same exact compiler-owned path, no quadrant tuple needed\n * @deprecated use fillet(shape, radius, shape.edge(\'vert-br\')) — same exact compiler-owned path, no quadrant tuple needed\n * @category Edge Features\n */\ndeclare function filletTrackedEdge(shape: Shape, edge: EdgeRef, radius: number, quadrant?: [\n number,\n number\n], segments?: number): Shape;\n/**\n * Bevel a tracked vertical solid edge with a 45° chamfer.\n *\n * **Details**\n *\n * Compiler-owned chamfer for tracked vertical edges. Requires a\n * compile-plan-covered target. This is not a general 2D sketch-corner tool;\n * supported subset and quadrant semantics are the same as `filletTrackedEdge()` - see\n * that function for details.\n *\n * **Example**\n *\n * ```ts\n * const base = Rectangle2D.fromDimensions(0, 0, 50, 50).extrude(20);\n * const chamfered = chamferTrackedEdge(base, base.edge(\'vert-br\'), 3, [-1, -1]);\n * ```\n *\n * @param shape - Compile-covered solid to modify\n * @param edge - `EdgeRef` from the same tracked target body (e.g. `base.edge(\'vert-br\')`)\n * @param size - Chamfer size (must be positive and finite)\n * @param quadrant - Corner direction sign pair (default: `[-1, -1]`)\n * @returns A new Shape with the chamfer applied\n * @see {@link chamfer} for the general-purpose variant — it takes the same exact compiler-owned path for tracked edges, without the quadrant parameter\n * @see {@link filletTrackedEdge} for rounded tracked edges\n * @softDeprecated chamfer(shape, size, shape.edge(\'vert-br\')) — same exact compiler-owned path, no quadrant tuple needed\n * @deprecated use chamfer(shape, size, shape.edge(\'vert-br\')) — same exact compiler-owned path, no quadrant tuple needed\n * @category Edge Features\n */\ndeclare function chamferTrackedEdge(shape: Shape, edge: EdgeRef, size: number, quadrant?: [\n number,\n number\n]): Shape;\ninterface FilletCornerSpec {\n index: number;\n radius: number;\n segments?: number;\n}\ntype PointInput = [\n number,\n number\n] | Point2D;\n/**\n * Create a polygon from points with specific corners rounded to arc fillets.\n *\n * **Details**\n *\n * Each corner spec identifies a vertex by its index in the `points` array and\n * the desired fillet `radius`. Both convex and concave corners are supported.\n *\n * Constraints:\n * - Collinear corners cannot be filleted (throws an error)\n * - Two neighboring fillets whose tangent lengths overlap the same edge will throw\n * - Radius must be positive and small enough to fit within the adjacent edge lengths\n *\n * Prefer the Sketch methods: they work on any composed sketch (after booleans,\n * `attachTo`, text), select corners by position instead of brittle indices, and\n * have chamfer twins — `sketch.filletCorners(radius)`, `sketch.filletCorner([x, y], radius)`,\n * `sketch.chamferCorners(size)`, `sketch.chamferCorner([x, y], size)`.\n *\n * **Example**\n *\n * ```ts\n * const roof = filletCorners(roofPoints, [\n * { index: 3, radius: 19 },\n * { index: 4, radius: 19 },\n * { index: 5, radius: 19 },\n * ]);\n * ```\n *\n * @param points - Closed polygon vertices as `[x, y][]` or `Point2D[]`\n * @param corners - Fillet specs — each has `index` (vertex index), `radius`, and optional `segments`\n * @returns A new polygon sketch with the specified corners filleted\n * @see {@link Sketch.filletCorner} for seed-point corner selection on any sketch\n * @softDeprecated polygon(points).filletCorner([x, y], radius) — seed-point corner selection; or .filletCorners(radius) for all corners\n * @deprecated use polygon(points).filletCorner([x, y], radius) — seed-point corner selection; or .filletCorners(radius) for all corners\n * @category Sketch Operations\n */\ndeclare function filletCorners(points: PointInput[], corners: FilletCornerSpec[]): Sketch;\n/**\n * Pre-load and cache a font for use with `text2d()`.\n *\n * **Details**\n *\n * Fonts are cached by their source string (or `cacheKey` for `ArrayBuffer` sources),\n * so repeated calls with the same path are free. Pre-loading is useful when you call\n * `text2d()` many times with the same font — it avoids repeated disk reads.\n *\n * Built-in font names that work everywhere (browser + CLI):\n * - `\'sans-serif\'` or `\'inter\'` — bundled Inter Regular\n *\n * **Example**\n *\n * ```ts\n * const font = loadFont(\'/path/to/Arial Bold.ttf\');\n * text2d(\'Title\', { size: 12, font }).extrude(1.5);\n * text2d(\'Subtitle\', { size: 8, font }).extrude(1);\n * ```\n *\n * @param source - Built-in name (`\'sans-serif\'`/`\'inter\'`), file path (CLI only), or `ArrayBuffer`\n * @param cacheKey - Optional cache key when passing an `ArrayBuffer`\n * @returns Parsed opentype.js Font object\n * @see {@link text2d} to use the loaded font\n * @category Sketch Text\n */\ndeclare function loadFont(source: string | ArrayBuffer, cacheKey?: string): opentype$1.Font;\ninterface HelixOptions {\n /** Radius from the central Z axis to the helix centerline. */\n radius: number;\n /** Axial distance per full turn. Provide any two of `pitch`, `turns`, and `height`. */\n pitch?: number;\n /** Number of full rotations around the axis. Provide any two of `pitch`, `turns`, and `height`. */\n turns?: number;\n /** Total height along +Z. Provide any two of `pitch`, `turns`, and `height`. */\n height?: number;\n /** Start angle in degrees. Default 0 starts on +X. */\n startAngle?: number;\n /** Reverse winding direction when viewed from +Z. */\n clockwise?: boolean;\n /** Point samples per turn for the metadata path. Default 32. */\n samplesPerTurn?: number;\n}\ninterface HelixCoilOptions extends HelixOptions {\n /** Radius of the circular wire profile. Required unless a custom profile is passed. */\n wireRadius?: number;\n /** Segment count for the default circular wire profile. Default 24. */\n profileSegments?: number;\n /** Sweep path samples per turn. Default 32. */\n divisionsPerTurn?: number;\n}\ntype Vec2$2 = [\n number,\n number\n];\ntype Vec3$6 = [\n number,\n number,\n number\n];\ninterface SurfaceTessellationOptions {\n /** `uniform` uses resolution directly; `adaptive` lets the Truck kernel refine open sheets from chord error. */\n mode?: "uniform" | "adaptive";\n /** Target chord-error tolerance in model units for adaptive Truck tessellation. */\n tolerance?: number;\n /** Minimum adaptive samples per direction. */\n minResolution?: number;\n /** Maximum adaptive samples per direction. Defaults to `resolution` when omitted. */\n maxResolution?: number;\n}\ninterface SurfaceDomainOptions {\n /** Lower U parameter bound in normalized surface space (default 0). */\n uMin?: number;\n /** Upper U parameter bound in normalized surface space (default 1). */\n uMax?: number;\n /** Lower V parameter bound in normalized surface space (default 0). */\n vMin?: number;\n /** Upper V parameter bound in normalized surface space (default 1). */\n vMax?: number;\n}\ninterface SurfaceTrimNurbsCurveOptions {\n /** Curve trim loop kind. */\n kind: "nurbs";\n /** NURBS UV control points in normalized post-domain space. */\n controlPoints: Vec2$2[];\n /** Optional rational weights. Defaults to 1.0 for each control point. */\n weights?: number[];\n /** Optional knot vector. Defaults to uniform clamped knots. */\n knots?: number[];\n /** Curve degree. Defaults to cubic where possible. */\n degree?: number;\n /** Number of UV samples used by the Truck kernel for trim triangulation. */\n samples?: number;\n}\ntype SurfaceTrimLoopInput = Vec2$2[] | SurfaceTrimNurbsCurveOptions;\ninterface SurfaceTrimOptions {\n /** Outer trim loop in normalized post-domain UV space. */\n outer: SurfaceTrimLoopInput;\n /** Optional hole loops in normalized post-domain UV space. */\n holes?: SurfaceTrimLoopInput[];\n}\ninterface NurbsSurfaceOptions {\n /** Degree in U direction (default 3). */\n degreeU?: number;\n /** Degree in V direction (default 3). */\n degreeV?: number;\n /** Weights grid — same dimensions as controlGrid (default: all 1.0). */\n weights?: number[][];\n /** Knot vector in U direction (default: uniform clamped). */\n knotsU?: number[];\n /** Knot vector in V direction (default: uniform clamped). */\n knotsV?: number[];\n /** Sheet thickness — if > 0, thickens the surface into a solid (default 0 = surface only). */\n thickness?: number;\n /** Tessellation resolution — points per direction (default 32). */\n resolution?: number;\n /** Optional rectangular parameter domain in normalized [0, 1] U/V space. */\n domain?: SurfaceDomainOptions;\n /** Optional polygonal or NURBS-curve UV trim loops. SDF, Truck, and OCCT support open trimmed surfaces; Manifold supports sampled thickened trimmed solids. */\n trim?: SurfaceTrimOptions;\n /** Optional Truck kernel tessellation controls for render mesh generation. */\n tessellation?: SurfaceTessellationOptions;\n /** Explicit opt-in for sampled approximation paths on non-exact backends. */\n approximate?: boolean;\n}\ndeclare class NurbsSurface {\n readonly controlGrid: Vec3$6[][];\n readonly weightsGrid: number[][];\n readonly knotsU: number[];\n readonly knotsV: number[];\n readonly degreeU: number;\n readonly degreeV: number;\n readonly nU: number;\n readonly nV: number;\n readonly domain: SurfaceDomainCompilePlan;\n constructor(controlGrid: Vec3$6[][], options?: NurbsSurfaceOptions);\n /**\n * Evaluate the surface at parameters (u, v) ∈ [0, 1]².\n * Uses tensor product evaluation: evaluate basis functions in U and V independently.\n */\n pointAt(u: number, v: number): Vec3$6;\n /**\n * Evaluate the surface unit normal at (u, v) from analytic first derivatives.\n *\n * Uses Algorithm A2.3 basis-function derivatives with the rational quotient\n * rule, so the normal is exact (no finite-difference epsilon, no error near\n * the boundary). Constant chain-rule factors from the parameter remap scale Su\n * and Sv positively and cancel under normalization, so they are omitted.\n */\n normalAt(u: number, v: number): Vec3$6;\n /** Analytic first partial derivatives S_u, S_v (rational quotient rule). */\n derivativesAt(u: number, v: number): {\n S: Vec3$6;\n Su: Vec3$6;\n Sv: Vec3$6;\n };\n /**\n * Tessellate the surface into a triangle mesh.\n * Returns positions, normals, and triangle indices.\n */\n tessellate(resU?: number, resV?: number): {\n positions: Vec3$6[];\n normals: Vec3$6[];\n indices: number[];\n };\n private remapU;\n private remapV;\n}\n/**\n * Create a NURBS surface from a grid of control points.\n *\n * The control grid is indexed as `controlGrid[u][v]` — each row is a curve\n * in the V direction, and columns trace curves in the U direction.\n *\n * With default options, creates a bicubic non-rational B-spline surface\n * with uniform clamped knots.\n *\n * @example\n * // Simple 4×4 control grid — a gently curved surface\n * const grid = [\n * [[0,0,0], [10,0,2], [20,0,2], [30,0,0]],\n * [[0,10,1], [10,10,5], [20,10,5], [30,10,1]],\n * [[0,20,1], [10,20,5], [20,20,5], [30,20,1]],\n * [[0,30,0], [10,30,2], [20,30,2], [30,30,0]],\n * ];\n * const surface = nurbsSurface(grid, { thickness: 2 });\n *\n * @softDeprecated Surface.Nurbs(controlGrid, options) — same arguments and options, including thickness\n * @deprecated use Surface.Nurbs(controlGrid, options) — same arguments and options, including thickness\n */\ndeclare function nurbsSurface(controlGrid: Vec3$6[][], options?: NurbsSurfaceOptions): Shape;\n/**\n * Layout helpers — eliminate manual trigonometry for common positioning patterns.\n *\n * These functions return arrays of {x, y} positions that can be used with\n * translate(), circularPattern seed placement, or any other positioning API.\n */\ninterface LayoutPoint {\n x: number;\n y: number;\n}\ninterface CircularLayoutOptions {\n /** Angle of the first element in degrees (default: 0 = +X axis). */\n startDeg?: number;\n /** Center X coordinate (default: 0). */\n centerX?: number;\n /** Center Y coordinate (default: 0). */\n centerY?: number;\n}\n/**\n * Compute evenly-spaced positions around a circle.\n *\n * Eliminates the most common trig pattern in CAD scripts:\n * ```js\n * // Before — manual trig\n * for (let i = 0; i < 12; i++) {\n * const angle = i * 30 * Math.PI / 180;\n * markers.push(marker.translate(r * Math.cos(angle), r * Math.sin(angle), 0));\n * }\n *\n * // After — declarative\n * for (const {x, y} of circularLayout(12, r)) {\n * markers.push(marker.translate(x, y, 0));\n * }\n * ```\n */\ndeclare function circularLayout(count: number, radius: number, options?: CircularLayoutOptions): LayoutPoint[];\ninterface PolygonVerticesOptions {\n /** Angle of the first vertex in degrees (default: 90 = top). */\n startDeg?: number;\n /** Center X coordinate (default: 0). */\n centerX?: number;\n /** Center Y coordinate (default: 0). */\n centerY?: number;\n}\n/**\n * Compute the vertex positions of a regular polygon.\n *\n * Default orientation places the first vertex at the top (90 degrees),\n * matching the convention used by `ngon()`.\n *\n * Eliminates manual Math.sqrt(3) for triangles, pentagon vertex math, etc:\n * ```js\n * // Before — manual equilateral triangle\n * const v1 = [center.x - r/2, center.y + r * Math.sqrt(3)/2];\n * const v2 = [center.x - r/2, center.y - r * Math.sqrt(3)/2];\n * const v3 = [center.x + r, center.y];\n *\n * // After — declarative\n * const [v1, v2, v3] = polygonVertices(3, r);\n * ```\n */\ndeclare function polygonVertices(sides: number, radius: number, options?: PolygonVerticesOptions): LayoutPoint[];\ntype LoftAxis = "X" | "Y" | "Z";\ntype LoftGuideRailSide = "left" | "right" | "front" | "back" | "center";\ninterface LoftPath2DLike {\n toPolyline(samples?: number): Array<[\n number,\n number\n ]>;\n}\ntype LoftPath2D = Array<[\n number,\n number\n]> | LoftPath2DLike;\ntype LoftGuideRailPath = Vec3[] | Curve3D | HermiteCurve3D | QuinticHermiteCurve3D | NurbsCurve3D;\ninterface LoftStation {\n profile: Sketch;\n position: number;\n}\ninterface LoftGuideRail {\n side: LoftGuideRailSide;\n path: LoftGuideRailPath;\n}\ninterface LoftWithGuideRailsOptions extends LoftOptions {\n /** Primary station axis. Default Z. */\n axis?: LoftAxis;\n /** Number of generated loft stations including ends. Default scales with station count. */\n samples?: number;\n /** Number of points sampled from curve-backed rails before axis interpolation. Default 64. */\n railSamples?: number;\n}\n/**\n * Namespaced loft helpers for guided station lofts.\n *\n * `Loft.withGuideRails(...)` keeps the public API out of the global namespace\n * while letting guide rails control the side silhouette of the generated loft.\n * Use left/right/front/back rails to constrain envelope sides, and a center rail\n * to move the section centers along a path.\n */\ndeclare const Loft: {\n /** Create a loft station from a 2D profile and an axis position. */\n station(profile: Sketch, position: number): LoftStation;\n /**\n * Loft by interpolating signed-distance fields instead of matching vertices.\n *\n * Use this path when profiles change character, such as round shafts blending\n * into flat, cruciform, or lobed tips. It is Manifold-only, mesh-based, and\n * slower than stitched lofting, but it avoids profile-point correspondence\n * artifacts because it blends profile fields instead of boundary vertices.\n */\n field(profiles: Sketch[], heights: number[], options?: FieldLoftOptions): Shape;\n /** Create a guide rail that constrains the section-local negative-X side. */\n leftRail(path: LoftGuideRailPath): LoftGuideRail;\n /** Create a guide rail that constrains the section-local positive-X side. */\n rightRail(path: LoftGuideRailPath): LoftGuideRail;\n /** Create a guide rail that constrains the section-local positive-Y side. */\n frontRail(path: LoftGuideRailPath): LoftGuideRail;\n /** Create a guide rail that constrains the section-local negative-Y side. */\n backRail(path: LoftGuideRailPath): LoftGuideRail;\n /** Create a guide rail that moves section centers along the loft. */\n centerRail(path: LoftGuideRailPath): LoftGuideRail;\n /**\n * Place a 2D guide path onto the XZ plane.\n *\n * The path\'s first coordinate becomes X and its second coordinate becomes Z.\n * Use this for left/right silhouette rails authored with `path()` or `constrainedSketch()`.\n */\n pathOnXz(path: LoftPath2D, y?: number): Vec3[];\n /**\n * Place a 2D guide path onto the YZ plane.\n *\n * The path\'s first coordinate becomes Y and its second coordinate becomes Z.\n * Use this for front/back crown rails authored with `path()` or `constrainedSketch()`.\n */\n pathOnYz(path: LoftPath2D, x?: number): Vec3[];\n /**\n * Place a 2D guide path onto the XY plane.\n *\n * The path\'s first coordinate becomes X and its second coordinate becomes Y.\n * Use this when lofting along X or Y and a rail lives in a horizontal sketch plane.\n */\n pathOnXy(path: LoftPath2D, z?: number): Vec3[];\n /**\n * Loft through profile stations while forcing generated sections to follow guide rails.\n *\n * Stations define the cross-section family. Guide rails define the side or center\n * paths the loft must pass through. With opposite side rails, the section is scaled\n * to touch both rails. With one side rail, the section keeps its interpolated size\n * unless a center rail is also present.\n */\n withGuideRails(stations: LoftStation[], rails: LoftGuideRail[], options?: LoftWithGuideRailsOptions): Shape;\n};\ndeclare class PathBuilder {\n private segs;\n private x;\n private y;\n /** Current departure tangent unit vector. */\n private dirX;\n private dirY;\n /**\n * Current cursor X position.\n *\n * @returns The current X coordinate.\n */\n getX(): number;\n /**\n * Current cursor Y position.\n *\n * @returns The current Y coordinate.\n */\n getY(): number;\n /**\n * Move the cursor to an absolute position without drawing a segment.\n *\n * When called after the initial `path()`, this establishes the start of the outline.\n * Calling `moveTo` again mid-path starts a new sub-path (hole in `close()`, separate\n * segment for `stroke()`).\n *\n * @param x - Absolute X coordinate.\n * @param y - Absolute Y coordinate.\n * @category Path Builder\n */\n moveTo(x: number, y: number): this;\n /**\n * Draw a straight line from the current cursor to an absolute position.\n *\n * @param x - Absolute X coordinate of the endpoint.\n * @param y - Absolute Y coordinate of the endpoint.\n * @category Path Builder\n */\n lineTo(x: number, y: number): this;\n /**\n * Draw a horizontal line segment by `dx` units from the current cursor.\n *\n * Positive `dx` moves right; negative moves left.\n *\n * @param dx - Horizontal displacement.\n * @category Path Builder\n */\n lineH(dx: number): this;\n /**\n * Draw a vertical line segment by `dy` units from the current cursor.\n *\n * Positive `dy` moves up; negative moves down.\n *\n * @param dy - Vertical displacement.\n * @category Path Builder\n */\n lineV(dy: number): this;\n /**\n * Draw a line at the given angle and length from the current cursor.\n *\n * Angle convention: `0°` points right (+X), `90°` points up (+Y).\n *\n * **Example**\n *\n * ```ts\n * // L-bracket with angled return\n * path().moveTo(0, 0).lineH(50).lineV(-70).lineAngled(20, 235).stroke(4);\n * ```\n *\n * @param length - Length of the line segment.\n * @param degrees - Angle in degrees (0 = right, 90 = up).\n * @category Path Builder\n */\n lineAngled(length: number, degrees: number): this;\n /**\n * Draw a line by a relative `(dx, dy)` displacement from the current cursor.\n *\n * @param dx - Horizontal displacement.\n * @param dy - Vertical displacement.\n * @category Path Builder\n */\n lineBy(dx: number, dy: number): this;\n /**\n * Draw an arc to a point offset from the current cursor.\n *\n * @param dx - Relative X displacement to the end point.\n * @param dy - Relative Y displacement to the end point.\n * @param radius - Arc radius.\n * @param clockwise - Sweep direction. Default: `false` (CCW).\n * @category Path Builder\n */\n arcBy(dx: number, dy: number, radius: number, clockwise?: boolean): this;\n /**\n * Draw a cubic Bezier using control points relative to the current cursor.\n *\n * @param dcp1x - Relative X of the first control point.\n * @param dcp1y - Relative Y of the first control point.\n * @param dcp2x - Relative X of the second control point.\n * @param dcp2y - Relative Y of the second control point.\n * @param dx - Relative X of the end point.\n * @param dy - Relative Y of the end point.\n * @category Path Builder\n */\n bezierBy(dcp1x: number, dcp1y: number, dcp2x: number, dcp2y: number, dx: number, dy: number): this;\n /**\n * Draw a circular arc from the current position to (x, y) with the given radius.\n * `clockwise=true` → arc curves to the right of the start→end direction.\n * `clockwise=false` → arc curves to the left of the start→end direction.\n */\n arcTo(x: number, y: number, radius: number, clockwise?: boolean): this;\n /**\n * G1-continuous arc — radius derived from current tangent + endpoint.\n * Throws if endpoint is collinear with current direction.\n */\n tangentArcTo(x: number, y: number): this;\n /**\n * Draw an arc defined by center, radius, and angle range (no trig needed).\n * If the path has no segments yet, automatically moves to the arc start.\n * Positive sweep (startDeg < endDeg) = CCW, negative = CW.\n *\n * ```js\n * // Arc centered at (10, 0), radius 50, from -30° to +30°\n * path().arc(10, 0, 50, -30, 30).stroke(8, \'Round\')\n * ```\n */\n arc(cx: number, cy: number, radius: number, startDeg: number, endDeg: number): this;\n /**\n * Arc around a known center point, sweeping by the given angle.\n * Radius is derived from the distance between the current position and the center.\n * Positive sweep = CCW (math convention), negative = CW.\n *\n * ```js\n * // Arc 90° CCW around (50, 50)\n * path().moveTo(70, 50).arcAround(50, 50, 90)\n * // Arc 45° CW around the origin\n * path().moveTo(10, 0).arcAround(0, 0, -45)\n * ```\n */\n arcAround(cx: number, cy: number, sweepDeg: number): this;\n /**\n * Arc around a center point given as an offset from the current position.\n * `(dx, dy)` is the vector from the current point to the center.\n * Positive sweep = CCW (math convention), negative = CW.\n *\n * ```js\n * // Arc 90° CCW around a center 20 units to the right\n * path().moveTo(50, 50).arcAroundRelative(20, 0, 90)\n * // Equivalent to: path().moveTo(50, 50).arcAround(70, 50, 90)\n * ```\n */\n arcAroundRelative(dx: number, dy: number, sweepDeg: number): this;\n /**\n * Smooth three-arc end cap from the current position to (endX, endY).\n * Inserts: small corner arc → large cap arc → small corner arc, all G1-continuous.\n */\n smoothCapTo(endX: number, endY: number, cornerRadius: number, capRadius: number): this;\n /**\n * Cubic bezier from current position to (x, y) via two control points.\n */\n bezierTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): this;\n /**\n * G1-continuous cubic bezier — first control point is auto-derived from\n * the current tangent direction. `weight` controls how far the auto-placed\n * control point extends along the tangent (default: 1/3 of the chord).\n *\n * The second control point `(cp2x, cp2y)` must be provided — it controls\n * the arrival curvature. For a fully automatic smooth curve, see `smoothThrough`.\n */\n tangentBezierTo(cp2x: number, cp2y: number, x: number, y: number, weight?: number): this;\n /**\n * Catmull-Rom spline through a list of waypoints from the current position.\n * The current position is included as the first point. The last waypoint\n * becomes the new cursor position.\n *\n * @param waypoints — intermediate + final points (at least 1)\n * @param tension — 0 = very round, 1 = linear (default 0.5)\n */\n smoothThrough(waypoints: [\n number,\n number\n ][], tension?: number): this;\n /**\n * Rational B-spline edge to (x, y) with explicit control points and weights.\n *\n * The control points define the B-spline shape between the current position\n * and (x, y). The current position is NOT included in `controlPoints` — it is\n * automatically prepended. The endpoint (x, y) is the last control point.\n *\n * @param controlPoints — interior + endpoint control points (endpoint = last)\n * @param opts.weights — rational weights (default: all 1.0)\n * @param opts.degree — B-spline degree (default: control point count - 1, capped at 3)\n */\n nurbsTo(controlPoints: [\n number,\n number\n ][], opts?: {\n weights?: number[];\n degree?: number;\n }): this;\n /**\n * Exact circular arc to (x, y) using a rational quadratic NURBS.\n *\n * Unlike `arcTo()` which tessellates to a polyline, this preserves the\n * exact arc definition. When extruded through the OCCT backend, it produces\n * a true cylindrical face — not a faceted approximation.\n *\n * @param x — endpoint X\n * @param y — endpoint Y\n * @param opts.radius — arc radius (default: auto-computed from chord)\n * @param opts.clockwise — winding direction (default: false = CCW)\n */\n exactArcTo(x: number, y: number, opts?: {\n radius?: number;\n clockwise?: boolean;\n }): this;\n /**\n * Round the last corner (the junction between the previous two segments)\n * with a tangent arc of the given radius.\n *\n * Must be called after at least two line/arc segments that form a corner.\n * The fillet trims back both segments and inserts a tangent arc.\n *\n * ```js\n * path().moveTo(0,0).lineTo(10,0).lineTo(10,10).fillet(2).lineTo(0,10).close()\n * ```\n */\n fillet(radius: number): this;\n /**\n * Chamfer the last corner with a straight cut of the given distance.\n *\n * ```js\n * path().moveTo(0,0).lineTo(10,0).lineTo(10,10).chamfer(2).lineTo(0,10).close()\n * ```\n */\n chamfer(distance: number): this;\n private computeFilletGeom;\n private computeChamferGeom;\n private getSegEnd;\n private getSegDirAt;\n private trimLastSegEnd;\n /**\n * Mirror all existing segments across an axis and append the mirrored copy\n * in reverse order, creating a symmetric path. The axis passes through the\n * current cursor position.\n *\n * @param axis — \'x\' mirrors across the local X-axis (flips Y),\n * \'y\' mirrors across the local Y-axis (flips X),\n * or `[nx, ny]` for an arbitrary axis direction.\n *\n * ```js\n * // Build right half, mirror to get full symmetric profile\n * path().moveTo(0,0).lineTo(10,0).lineTo(10,5).mirror(\'x\').close()\n * ```\n */\n mirror(axis: "x" | "y" | [\n number,\n number\n ]): this;\n /**\n * Label the most recently added segment. Labels are born here and grow\n * into face names when the sketch is extruded, lofted, swept, or revolved.\n *\n * Labels must be unique within a path. Each segment can have at most one label.\n */\n label(name: string): this;\n /**\n * Label the closing segment and close the path. Shorthand for labeling the\n * implicit line from the last point back to the start, then closing.\n */\n closeLabel(name: string): Sketch;\n /** Label for the implicit closing segment (set by closeLabel). */\n private _closingLabel;\n /** Expand all segments into a flat tessellated polyline. */\n private tessellate;\n /**\n * Return the open path as a sampled 2D polyline.\n *\n * This is for construction geometry such as guide rails, measured centerlines,\n * and curve-driven helpers where the authored path should stay open instead of\n * becoming a filled sketch or stroked profile.\n *\n * **Example**\n *\n * ```ts\n * const rail = path()\n * .moveTo(24, 0)\n * .bezierTo(32, 44, 28, 92, 18, 120)\n * .toPolyline();\n * ```\n *\n * @returns A sampled open polyline.\n * @category Path Builder\n */\n toPolyline(): [\n number,\n number\n ][];\n /**\n * Close the path and return a filled `Sketch`.\n *\n * **Details**\n *\n * The winding of the polygon is automatically corrected to CCW (the expected\n * orientation for ForgeCAD sketches). If the path contains multiple sub-paths\n * (started with subsequent `moveTo` calls), the first sub-path is the outer\n * contour and subsequent sub-paths become holes subtracted from it.\n *\n * Edge labels (assigned with `.label(\'name\')`) are transferred to the resulting\n * sketch and propagate through `extrude()`, `revolve()`, `loft()`, and `sweep()`\n * into named faces on the resulting `Shape`.\n *\n * **Example**\n *\n * ```ts\n * const triangle = path().moveTo(0, 0).lineH(50).lineV(30).close();\n *\n * // With a hole (second sub-path)\n * const frame = path()\n * .moveTo(0, 0).lineH(40).lineV(30).lineH(-40).close(); // outer\n * // (hole would be added with another moveTo and line sequence before close)\n * ```\n *\n * @returns A filled `Sketch` with correct CCW winding.\n * @category Path Builder\n */\n close(): Sketch;\n /**\n * Close the path and return an offset version of the filled Sketch.\n * Positive delta expands outward, negative shrinks inward.\n */\n closeOffset(delta: number, join?: "Round" | "Square" | "Miter"): Sketch;\n /**\n * Thicken an open polyline (centerline) into a solid filled profile with uniform width.\n *\n * **Details**\n *\n * Expands the path into a closed profile `width` units wide (half-width on each side\n * of the centerline). Use `\'Round\'` for ribs, wire traces, and organic profiles —\n * it adds semicircular endcaps and rounds joins. Use `\'Square\'` (default) for sharp\n * miter joins without endcaps.\n *\n * Not the same as rounding corners of a closed polygon — for mixed sharp-and-rounded\n * outlines, build the polygon first and apply `.filletCorner([x, y], radius)` per corner.\n *\n * **Example**\n *\n * ```ts\n * // Square-join L-bracket\n * const bracket = path().moveTo(0, 0).lineH(50).lineV(-70).lineAngled(20, 235).stroke(4);\n *\n * // Round-join rib\n * const rib = path().moveTo(0, 0).lineH(60).stroke(6, \'Round\');\n *\n * // Equivalent standalone form\n * const wire = stroke([[0, 0], [50, 0], [50, -70]], 4);\n * ```\n *\n * @param width - Total stroke width (half on each side of the centerline).\n * @param join - `\'Square\'` for miter joins (default) or `\'Round\'` for rounded joins\n * and semicircular endcaps.\n * @returns A filled `Sketch` representing the thickened profile.\n * @category Path Builder\n */\n stroke(width: number, join?: "Round" | "Square"): Sketch;\n /** Split segments into sub-paths at each moveTo. */\n private splitSubPaths;\n /** Build semantic ProfileEdge array from path segments (for pathProfile compile plan). */\n private buildProfileEdges;\n private profileEdgeCountForSeg;\n /** Tessellate a sub-path (sequence of segments). */\n private tessellateSegs;\n}\n/**\n * Create a new `PathBuilder` for tracing a 2D outline point by point.\n *\n * **Details**\n *\n * `PathBuilder` is a fluent API for constructing 2D profiles using a mix of line\n * segments, arcs, bezier curves, and splines. Always start with `.moveTo(x, y)` to\n * set the starting point. Call `.close()` to get a filled `Sketch`, or `.stroke(width)`\n * to thicken an open polyline into a solid profile.\n *\n * Edge labels can be assigned with `.label(\'name\')` after any segment — they propagate\n * through extrusion, revolve, loft, and sweep into named faces on the resulting `Shape`.\n *\n * **Example**\n *\n * ```ts\n * // Closed triangle\n * const triangle = path().moveTo(0, 0).lineH(50).lineV(30).close();\n *\n * // L-shaped bracket as a stroke\n * const bracket = path().moveTo(0, 0).lineH(50).lineV(-70).lineAngled(20, 235).stroke(4);\n *\n * // Labeled edges for downstream face references\n * const slot = path()\n * .moveTo(0, 0)\n * .lineTo(30, 0).label(\'bottom\')\n * .lineTo(30, 10)\n * .lineTo(0, 10).label(\'top\')\n * .close();\n * ```\n *\n * @returns A new `PathBuilder` instance with the cursor at the origin.\n * @category Path Builder\n */\ndeclare function path(): PathBuilder;\n/**\n * Thicken a 2D polyline (centerline) into a solid filled profile of uniform width.\n *\n * **Details**\n *\n * Standalone equivalent of `path()...stroke(width, join)`. Use for centerline-based\n * geometry — ribs, wire traces, brackets. For rounding corners of a *closed* outline\n * use `.filletCorners(radius)` (all corners) or `.filletCorner([x, y], radius)`\n * (one corner) instead.\n *\n * @param points - Centerline vertices as `[x, y]` pairs (at least 2)\n * @param width - Total stroke width (half on each side of the centerline)\n * @param join - `\'Square\'` for miter joins (default) or `\'Round\'` for rounded joins and semicircular endcaps\n * @returns A filled `Sketch` representing the thickened profile\n * @category Sketch Primitives\n */\ndeclare function stroke(points: [\n number,\n number\n][], width: number, join?: "Round" | "Square"): Sketch;\n/**\n * Repeat a shape in a linear pattern along a direction vector and union the copies.\n *\n * **Details**\n *\n * Creates `count` copies of `shape`, each offset by `(dx*i, dy*i, dz*i)`\n * from the original. All copies are unioned into a single `Shape`. Distinct\n * compiler ownership is assigned to each copy so face identity via\n * owner-scoped canonical queries still works post-merge.\n *\n * **Example**\n *\n * ```ts\n * // 5 cylinders, 20mm apart along X\n * linearPattern(cylinder(10, 3), 5, 20, 0)\n * ```\n *\n * @param shape - The shape to repeat\n * @param count - Total number of copies (including the original at index 0)\n * @param dx - X step per copy\n * @param dy - Y step per copy\n * @param dz - Z step per copy (default: `0`)\n * @returns Union of all copies\n * @see {@link circularPattern} for rotational patterns\n * @see {@link mirrorCopy} for mirror patterns\n * @category Tracked Shapes\n */\ndeclare function linearPattern(shape: Shape, count: number, dx: number, dy: number, dz?: number): Shape;\ninterface CircularPatternOptions {\n /** Center X of the rotation (default: 0). Used when the rotation axis is Z. */\n centerX?: number;\n /** Center Y of the rotation (default: 0). Used when the rotation axis is Z. */\n centerY?: number;\n /** Rotation axis direction (default: [0, 0, 1] = Z axis). */\n axis?: [\n number,\n number,\n number\n ];\n /** Pivot point for the rotation (default: [0, 0, 0]). Overrides centerX/centerY when set. */\n origin?: [\n number,\n number,\n number\n ];\n}\n/**\n * Repeat a shape in a circular pattern around an axis and union the copies.\n *\n * **Details**\n *\n * Distributes `count` copies evenly around the rotation axis (360° / count\n * per step). All copies are unioned into a single `Shape`. Distinct compiler\n * ownership is assigned to each copy — post-merge face identity via\n * owner-scoped canonical queries still works for pattern descendants.\n *\n * Two calling conventions:\n * - **Simple** (Z axis): `circularPattern(shape, 6)` or\n * `circularPattern(shape, 6, centerX, centerY)`\n * - **Advanced** (arbitrary axis): `circularPattern(shape, 6, { axis, origin })`\n *\n * **Example**\n *\n * ```ts\n * // 8 holes evenly spaced around origin\n * circularPattern(cylinder(12, 4).translate(30, 0, -1), 8)\n *\n * // Circular pattern around X axis\n * circularPattern(myFeature, 4, { axis: [1, 0, 0], origin: [0, 0, 50] })\n * ```\n *\n * @param shape - The shape to repeat\n * @param count - Total number of copies (including the original at index 0)\n * @param centerXOrOpts - Center X offset, or options object with `axis`/`origin`/`centerX`/`centerY`\n * @param centerY - Center Y offset (only when using the simple two-number form)\n * @returns Union of all copies\n * @see {@link linearPattern} for linear patterns\n * @see {@link mirrorCopy} for mirror patterns\n * @category Tracked Shapes\n */\ndeclare function circularPattern(shape: Shape, count: number, centerXOrOpts?: number | CircularPatternOptions, centerY?: number): Shape;\n/**\n * Repeat a 2D sketch in a linear pattern and union the copies.\n *\n * @param sketch - The sketch to repeat\n * @param count - Total number of copies\n * @param dx - X step per copy\n * @param dy - Y step per copy (default: `0`)\n * @returns Union of all copies as a single Sketch\n * @category Tracked Shapes\n */\ndeclare function linearPattern2d(sketch: Sketch, count: number, dx: number, dy?: number): Sketch;\n/**\n * Repeat a 2D sketch in a circular pattern around a center point and union the copies.\n *\n * @param sketch - The sketch to repeat\n * @param count - Total number of copies\n * @param centerXOrOpts - Center X, or options object with `centerX`/`centerY`/`startDeg`\n * @param centerY - Center Y (only when using the two-number form)\n * @returns Union of all copies as a single Sketch\n * @category Tracked Shapes\n */\ndeclare function circularPattern2d(sketch: Sketch, count: number, centerXOrOpts?: number | {\n centerX?: number;\n centerY?: number;\n startDeg?: number;\n}, centerY?: number): Sketch;\n/**\n * Mirror a shape across a plane and union the mirror with the original.\n *\n * **Details**\n *\n * The mirror plane passes through the origin and is defined by its normal\n * vector. The mirrored copy is unioned with the original to produce a single\n * symmetric Shape.\n *\n * **Example**\n *\n * ```ts\n * // Mirror across the YZ plane (X=0)\n * mirrorCopy(box(50, 30, 10), [1, 0, 0])\n * ```\n *\n * @param shape - The shape to mirror\n * @param normal - Normal of the mirror plane (e.g. `[1, 0, 0]` for YZ plane)\n * @returns Union of original and its mirror\n * @see {@link linearPattern} for linear repetition\n * @see {@link circularPattern} for circular repetition\n * @category Tracked Shapes\n */\ndeclare function mirrorCopy(shape: Shape, normal: [\n number,\n number,\n number\n]): Shape;\n/**\n * Create a 2D rectangle centered at the origin.\n *\n * **Example**\n *\n * ```ts\n * rect(40, 20).extrude(5);\n * ```\n *\n * @param width - Width along the X axis\n * @param height - Height along the Y axis\n * @returns A centered rectangle sketch\n * @category Sketch Primitives\n */\ndeclare function rect(width: number, height: number): Sketch;\n/**\n * Create a 2D circle centered at the origin.\n *\n * **Details**\n *\n * Omit `segments` for a smooth (auto-tessellated) circle. Pass an integer to get a\n * regular polygon approximation — e.g. `6` for a hexagon, `8` for an octagon.\n *\n * **Example**\n *\n * ```ts\n * circle2d(25).extrude(10); // smooth cylinder\n * circle2d(25, 6).extrude(10); // hexagonal prism\n * ```\n *\n * @param radius - Radius of the circle\n * @param segments - Number of polygon sides (omit for smooth)\n * @returns A circle sketch\n * @category Sketch Primitives\n */\ndeclare function circle2d(radius: number, segments?: number): Sketch;\n/**\n * Create a 2D rectangle with rounded corners, centered at the origin.\n *\n * **Details**\n *\n * The corner radius is automatically clamped to `min(width/2, height/2)` so it\n * can never exceed the shape dimensions.\n *\n * **Example**\n *\n * ```ts\n * roundedRect(60, 30, 5).extrude(3);\n * ```\n *\n * @param width - Width along the X axis\n * @param height - Height along the Y axis\n * @param radius - Corner fillet radius (clamped to fit)\n * @returns A rounded rectangle sketch\n * @category Sketch Primitives\n */\ndeclare function roundedRect(width: number, height: number, radius: number): Sketch;\n/**\n * Create a 2D polygon from an array of `[x, y]` points or `Point2D` objects.\n *\n * **Details**\n *\n * Winding order is normalized automatically — clockwise (CW) input is silently\n * reversed to CCW before being passed to the geometry kernel.\n *\n * **Example**\n *\n * ```ts\n * polygon([[0, 0], [50, 0], [25, 40]]).extrude(5); // triangle\n * ```\n *\n * @param points - Closed polygon vertices as `[x, y]` pairs or `Point2D` objects\n * @returns A polygon sketch\n * @category Sketch Primitives\n */\ndeclare function polygon(points: ([\n number,\n number\n] | Point2D)[]): Sketch;\n/**\n * Create a regular polygon inscribed in a circle of the given radius.\n *\n * **Details**\n *\n * `radius` is the center-to-vertex (circumradius) distance. Use `sides` of `3` for a\n * triangle, `6` for a hexagon, etc. The first vertex is at the top (−90° from +X).\n *\n * **Example**\n *\n * ```ts\n * ngon(6, 20).extrude(10); // hexagonal prism, circumradius 20\n * ```\n *\n * @param sides - Number of polygon sides\n * @param radius - Circumradius (center to vertex)\n * @returns A regular polygon sketch\n * @category Sketch Primitives\n */\ndeclare function ngon(sides: number, radius: number): Sketch;\n/**\n * Create a 2D ellipse centered at the origin.\n *\n * **Example**\n *\n * ```ts\n * ellipse(30, 15).extrude(5);\n * ellipse(30, 15, 32).extrude(5); // lower-resolution approximation\n * ```\n *\n * @param rx - Radius along the X axis\n * @param ry - Radius along the Y axis\n * @param segments - Number of polygon sides (default 64)\n * @returns An ellipse sketch\n * @category Sketch Primitives\n */\ndeclare function ellipse(rx: number, ry: number, segments?: number): Sketch;\n/**\n * Create a slot (oblong / stadium shape) — a rectangle with semicircular ends, centered at the origin.\n *\n * **Example**\n *\n * ```ts\n * slot(40, 10).extrude(3); // 40mm long, 10mm wide slot\n * ```\n *\n * @param length - Overall length (tip to tip)\n * @param width - Overall width (= diameter of the semicircular ends)\n * @returns A slot sketch\n * @category Sketch Primitives\n */\ndeclare function slot(length: number, width: number): Sketch;\n/**\n * Create an arc-shaped slot (banana / annular sector) centered at the origin.\n *\n * **Details**\n *\n * The slot is symmetric about the +X axis. The two ends are closed with\n * semicircular caps. `pitchRadius` is the distance from the origin to the\n * centerline of the slot, and `thickness` is the radial width of the slot.\n *\n * **Example**\n *\n * ```ts\n * arcSlot(135, 74, 40).extrude(5); // pitch R135, 74° sweep, 40mm wide\n * ```\n *\n * @param pitchRadius - Distance from the center to the middle of the slot\n * @param sweepDeg - Angular extent of the slot in degrees\n * @param thickness - Radial width of the slot\n * @returns An arc slot sketch\n * @category Sketch Primitives\n */\ndeclare function arcSlot(pitchRadius: number, sweepDeg: number, thickness: number): Sketch;\n/**\n * Create a star shape with alternating outer and inner radii.\n *\n * **Details**\n *\n * The first tip points down (−90°). The same shape — and the whole family of\n * alternating-radius rosettes — is a two-line recipe over the layout\n * primitives:\n *\n * ```ts\n * const tips = polygonVertices(5, 30, { startDeg: -90 });\n * const valleys = polygonVertices(5, 12, { startDeg: -90 + 180 / 5 });\n * polygon(tips.flatMap((tip, i) => [tip, valleys[i]])).extrude(4);\n * ```\n *\n * **Example**\n *\n * ```ts\n * star(5, 30, 12).extrude(4); // five-pointed star\n * ```\n *\n * @param points - Number of star points\n * @param outerR - Radius to the tips\n * @param innerR - Radius to the valleys\n * @returns A star sketch\n * @softDeprecated polygon(tips.flatMap((tip, i) => [tip, valleys[i]])) with tips = polygonVertices(n, outerR, { startDeg: -90 }) and valleys = polygonVertices(n, innerR, { startDeg: -90 + 180 / n })\n * @deprecated use polygon(tips.flatMap((tip, i) => [tip, valleys[i]])) with tips = polygonVertices(n, outerR, { startDeg: -90 }) and valleys = polygonVertices(n, innerR, { startDeg: -90 + 180 / n })\n * @category Sketch Primitives\n */\ndeclare function star(points: number, outerR: number, innerR: number): Sketch;\ntype Vec3$7 = [\n number,\n number,\n number\n];\ninterface SurfacePatchOptions {\n /** Number of samples along each direction. Default 24. */\n resolution?: number;\n /** Thickness of the generated solid. Default 0 for an open exact sheet. */\n thickness?: number;\n /** Allow explicit approximation for non-exact curve inputs such as Curve3D samples. */\n approximate?: boolean;\n}\n/**\n * Create a smooth surface patch from 4 boundary curves (Coons patch).\n *\n * The four curves form the boundary of a quadrilateral patch:\n * - bottom: u=0..1 at v=0 (from corner00 to corner10)\n * - top: u=0..1 at v=1 (from corner01 to corner11)\n * - left: v=0..1 at u=0 (from corner00 to corner01)\n * - right: v=0..1 at u=1 (from corner10 to corner11)\n *\n * The interior is filled using bilinear Coons patch interpolation:\n * P(u,v) = Lc(u,v) + Ld(u,v) - B(u,v)\n *\n * The result is a thin solid created by offsetting the surface mesh\n * along its normals by the specified thickness.\n *\n * Note: curves should meet at corners. Small gaps are tolerated.\n *\n * @softDeprecated Surface.Patch(curves, { approximate: true }) — Surface.Patch defaults approximate: false (pass { approximate: true } for sampled Curve3D/Vec3[] boundaries) and returns an open sheet; use .thicken(t) instead of the thickness option\n * @deprecated use Surface.Patch(curves, { approximate: true }) — Surface.Patch defaults approximate: false (pass { approximate: true } for sampled Curve3D/Vec3[] boundaries) and returns an open sheet; use .thicken(t) instead of the thickness option\n */\ndeclare function surfacePatch(curves: {\n bottom: Curve3D | Vec3$7[];\n top: Curve3D | Vec3$7[];\n left: Curve3D | Vec3$7[];\n right: Curve3D | Vec3$7[];\n}, options?: SurfacePatchOptions): Shape;\ninterface SvgImportOptions {\n /**\n * Which geometry channels to include:\n * - `auto`: prefer fills; if no fill geometry exists, fall back to strokes\n * - `fill`: import only filled regions\n * - `stroke`: import only stroke geometry\n * - `fill-and-stroke`: include both\n */\n include?: "auto" | "fill" | "stroke" | "fill-and-stroke";\n /** Keep all disconnected regions, or only the largest. */\n regionSelection?: "all" | "largest";\n /** Keep at most this many regions (largest-first). */\n maxRegions?: number;\n /** Drop regions below this absolute area threshold. */\n minRegionArea?: number;\n /** Drop regions below this ratio of largest-region area. */\n minRegionAreaRatio?: number;\n /**\n * Curve flattening tolerance in SVG user units.\n * Smaller = more segments, higher fidelity.\n */\n flattenTolerance?: number;\n /** Minimum segment count for arc discretization. */\n arcSegments?: number;\n /** Global scale applied after SVG parsing. */\n scale?: number;\n /**\n * Maximum imported sketch width.\n * If exceeded, geometry is uniformly downscaled to fit.\n */\n maxWidth?: number;\n /**\n * Maximum imported sketch height.\n * If exceeded, geometry is uniformly downscaled to fit.\n */\n maxHeight?: number;\n /**\n * Recenter imported geometry so its 2D bounds center is at CAD origin.\n */\n centerOnOrigin?: boolean;\n /** Simplification tolerance for final sketch cleanup. */\n simplify?: number;\n /**\n * Flip SVG Y-down coordinates to CAD Y-up.\n * Enabled by default.\n */\n invertY?: boolean;\n}\ninterface TextOptions {\n /**\n * Cap height of the text in model units.\n * All other dimensions (stroke weight, spacing) scale proportionally.\n * @default 10\n */\n size?: number;\n /**\n * Extra space between characters in model units.\n * Negative values tighten the tracking.\n * @default 0\n */\n letterSpacing?: number;\n /**\n * Horizontal alignment relative to x = 0.\n * - `\'left\'` — left edge at x = 0 (default)\n * - `\'center\'` — centred on x = 0\n * - `\'right\'` — right edge at x = 0\n * @default \'left\'\n */\n align?: "left" | "center" | "right";\n /**\n * Vertical alignment relative to y = 0.\n * - `\'baseline\'` — y = 0 is the text baseline (bottom of capital letters)\n * - `\'center\'` — y = 0 is the vertical midpoint of the cap height\n * - `\'top\'` — y = 0 is the top of capital letters\n * @default \'baseline\'\n */\n baseline?: "baseline" | "center" | "top";\n /**\n * Font to use for text rendering.\n *\n * - `\'sans-serif\'` or `\'inter\'` — bundled Inter font (works everywhere, including browser)\n * - **file path** — path to a TTF, OTF, or WOFF font file (CLI/Node only)\n * - **Font object** — a previously loaded opentype.js Font (from `loadFont()`)\n * - **omitted** — uses the bundled Inter font (same as `\'sans-serif\'`)\n *\n * @example\n * text2d(\'Hello World\', { size: 10 }) // default Inter\n * text2d(\'Custom Font\', { size: 10, font: \'/path/to/font.ttf\' })\n */\n font?: string | opentype$1.Font;\n /**\n * Bezier flattening tolerance in model units.\n * Smaller = more polygon segments = smoother curves.\n * @default auto (0.5% of size)\n */\n flattenTolerance?: number;\n}\n/**\n * Build a filled 2D Sketch from a text string.\n *\n * **Details**\n *\n * The Sketch origin is at the left end of the text baseline by default. Use `align`\n * and `baseline` options to adjust placement. Text is rendered using the bundled\n * Inter font by default, or any TTF/OTF/WOFF font you provide.\n *\n * `text2d()` creates real geometry. For temporary viewport annotations, prefer\n * `Viewport.label()` so the text stays off the geometry and OCCT compile paths.\n * Do not use either form of text to make unclear production geometry readable;\n * model the physical artifact clearly instead.\n *\n * Alignment reference table:\n *\n * | `align` | `baseline` | Origin |\n * |------------|--------------|-------------------------------------|\n * | `\'left\'` | `\'baseline\'` | Bottom-left of first char (default) |\n * | `\'center\'` | `\'center\'` | Dead center of text block |\n * | `\'right\'` | `\'top\'` | Top-right corner |\n *\n * **Example**\n *\n * ```ts\n * // Extruded nameplate\n * text2d(\'FORGE CAD\', { size: 8 }).extrude(1.2);\n *\n * // Centered label on the XY plane\n * text2d(\'V 2.0\', { size: 6, align: \'center\', baseline: \'center\' });\n *\n * // Engraved text cut into the top face of a box\n * const label = text2d(\'REV A\', { size: 5, align: \'center\', baseline: \'center\' });\n * plate.subtract(label.onFace(plate, \'top\', { protrude: -0.5 }).extrude(1));\n *\n * // Custom TTF font\n * text2d(\'Hello\', { size: 10, font: \'/path/to/Arial.ttf\' }).extrude(1);\n *\n * // Pre-loaded font for reuse\n * const font = loadFont(\'/path/to/Arial Bold.ttf\');\n * text2d(\'Title\', { size: 12, font }).extrude(1.5);\n * ```\n *\n * @param content - The text string to render\n * @param options - Rendering options (size, letterSpacing, align, baseline, font, flattenTolerance)\n * @returns A filled Sketch of the rendered letterforms\n * @see {@link textWidth} to measure text width without creating geometry\n * @see {@link loadFont} to pre-load a font for reuse\n * @see {@link Viewport.label} for temporary viewport annotations\n * @category Sketch Text\n */\ndeclare function text2d(content: string, options?: TextOptions): Sketch;\n/**\n * Measure the rendered advance width of a string without creating any geometry.\n *\n * **Details**\n *\n * Uses the same font metrics as `text2d()`. Useful for computing layout dimensions\n * before building the actual sketch — e.g. sizing a plate to fit a label.\n *\n * **Example**\n *\n * ```ts\n * const w = textWidth(\'SERIAL: 001\', { size: 6 });\n * const plate = box(w + 10, 12, 2);\n * ```\n *\n * @param content - The text string to measure\n * @param options - Accepts `size`, `letterSpacing`, and `font` (same as `text2d`)\n * @returns Rendered advance width in model units\n * @see {@link text2d} to create the actual geometry\n * @category Sketch Text\n */\ndeclare function textWidth(content: string, options?: Pick<TextOptions, "size" | "letterSpacing" | "font">): number;\ninterface ListParamFieldDef {\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n integer?: boolean;\n boolean?: boolean;\n choices?: string[];\n}\n/**\n * Shorthand alias for `Param.number()` — declare a numeric slider parameter.\n *\n * See `Param.number()` for full documentation, examples, and default range rules.\n *\n * @param name - Display label and override key\n * @param defaultValue - Initial value when no override is set\n * @param opts - Range, step, unit, and display options\n * @returns The current value (default or overridden)\n * @see {@link Param} for the full parameter namespace\n * @category Parameters\n * @internal\n */\ndeclare function param(name: string, defaultValue: number, opts?: {\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n integer?: boolean;\n reverse?: boolean;\n}): number;\n/**\n * Parameter namespace — the primary way to declare user-facing parameters.\n *\n * **Details**\n *\n * All parameter types live under `Param.*` for discoverability and clean scripts.\n * The standalone function `param()` is kept as the shorthand alias for\n * `Param.number()`.\n *\n * **Example**\n *\n * ```ts\n * const width = Param.number("Width", 50, { min: 10, max: 100, unit: "mm" });\n * const label = Param.string("Label", "Hello World");\n * const show = Param.bool("Show Holes", true);\n * const style = Param.choice("Style", "round", ["round", "square"]);\n * ```\n *\n * @concept\n * @category Parameters\n */\ndeclare const Param: {\n /**\n * Declare a numeric parameter that renders as a slider in the UI.\n *\n * **Details**\n *\n * Each call registers a slider control. When the user moves the\n * slider the entire script re-executes with the new value. Parameter values\n * are also overridable from `require()` imports or the CLI `--param` flag —\n * the `name` string is the key used in both cases.\n *\n * Default range rules when options are omitted:\n * - `min` defaults to `0`\n * - `max` defaults to `defaultValue * 4`\n * - `step` is auto-calculated: `1` for integer params, `0.1` for ranges ≤ 100,\n * `1` for larger ranges\n *\n * The `unit` option is cosmetic only — no conversion is performed.\n * Use `integer: true` for counts, sides, quantities (rounds to whole numbers;\n * step defaults to `1`).\n *\n * **Example**\n *\n * ```ts\n * const width = Param.number("Width", 50);\n * const angle = Param.number("Angle", 45, { min: 0, max: 180, unit: "°" });\n * const sides = Param.number("Sides", 6, { min: 3, max: 12, integer: true });\n * ```\n *\n * **Parameter overrides** — key must match `name` exactly:\n *\n * ```ts\n * // Via require()\n * const bracket = require("./bracket.forge.js", { Width: 80 });\n *\n * // Via CLI\n * // forgecad run model.forge.js --param "Wall Thickness=3"\n * ```\n *\n * Also available as the shorthand alias `param()`.\n */\n number(name: string, defaultValue: number, opts?: {\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n integer?: boolean;\n reverse?: boolean;\n }): number;\n /**\n * Declare a string parameter that renders as a text input in the UI.\n *\n * **Details**\n *\n * String parameters let users type free-form text — labels, names, inscriptions,\n * file paths, etc. The `name` string is the override key.\n *\n * **Example**\n *\n * ```ts\n * const label = Param.string("Label", "Hello World");\n * const name = Param.string("Name", "Part-001", { maxLength: 20 });\n * ```\n *\n * Override via import:\n * ```ts\n * const tag = require("./tag.forge.js", { Label: "Custom Text" });\n * ```\n *\n * Only available as `Param.string()` — no standalone alias.\n */\n string(name: string, defaultValue: string, opts?: {\n maxLength?: number;\n }): string;\n /**\n * Declare a boolean parameter that renders as a checkbox in the UI.\n *\n * **Details**\n *\n * Internally stored as `0`/`1`. When overriding from CLI or `require()`, pass\n * `1` for true and `0` for false. The `name` string is the override key.\n *\n * **Example**\n *\n * ```ts\n * const showHoles = Param.bool("Show Holes", true);\n * if (showHoles) return difference(plate, cylinder(10, 5).translate(50, 30, 0));\n * return plate;\n * ```\n *\n * Override via import:\n * ```ts\n * const pan = require("./pan.forge.js", { "Show Lid": 0 });\n * ```\n *\n */\n bool(name: string, defaultValue: boolean): boolean;\n /**\n * Declare a choice parameter that renders as a dropdown in the UI.\n *\n * **Details**\n *\n * `defaultValue` must exactly match one entry in `choices`. Returns the\n * selected string label. Prefer `Param.choice` over `Param.number` when a slider\n * would hide intent — named choices like `"wok"` are self-describing.\n *\n * Overrides may be passed as the choice label string (preferred) or as a\n * numeric index. The `name` string is the override key.\n *\n * **Example**\n *\n * ```ts\n * const panStyle = Param.choice("Pan Style", "frying-pan", ["frying-pan", "saute-pan", "wok"]);\n * if (panStyle === "wok") return buildWok();\n * ```\n *\n * Override via import:\n * ```ts\n * const pan = require("./pan.forge.js", { "Pan Style": "wok" });\n * ```\n *\n * Override via CLI:\n * ```bash\n * forgecad run model.forge.js --param "Pan Style=wok"\n * ```\n *\n */\n choice(name: string, defaultValue: string, choices: string[]): string;\n /**\n * Declare a list parameter — an array of struct items with per-field UI controls.\n *\n * **Details**\n *\n * Each item in the list is a struct whose fields each render as their own\n * control (slider, checkbox, or dropdown). The user can add/remove rows up to\n * `minItems`/`maxItems` bounds.\n *\n * Field types:\n * - Boolean fields (`boolean: true` in field defs) return as `boolean`\n * - Choice fields (`choices: [...]` in field defs) return as `string`\n * - All other fields return as `number`\n */\n list<T extends Record<string, number | boolean | string>>(name: string, defaultItems: T[], opts: {\n fields: Partial<Record<keyof T & string, ListParamFieldDef>>;\n minItems?: number;\n maxItems?: number;\n }): T[];\n};\ntype Vec3$8 = [\n number,\n number,\n number\n];\ninterface BSplineSurface {\n grid: Vec3$8[][];\n knotsU: number[];\n knotsV: number[];\n degreeU: number;\n degreeV: number;\n}\ninterface SurfaceCurvature {\n k1: number;\n k2: number;\n K: number;\n H: number;\n}\n/** Anything that can stand in for a 3D curve in a network. */\ntype CurveInput = NurbsCurve3D | ReadonlyArray<Vec3$8>;\n/** A reference to one boundary of a Sheet (an iso-parameter curve). */\ninterface SheetEdge {\n readonly sheet: Sheet;\n /** Which parameter is held fixed along this edge. */\n readonly fixed: "u" | "v";\n /** The fixed value (0 or 1). */\n readonly value: 0 | 1;\n}\n/** A parametric open surface value (control grid + knots + analytic differential geometry). */\ndeclare class Sheet {\n readonly surface: BSplineSurface;\n constructor(surface: BSplineSurface);\n /** Edge naming follows parameter direction (documented): front=v0, rear=v1, left=u0, right=u1. */\n get frontEdge(): SheetEdge;\n get rearEdge(): SheetEdge;\n get leftEdge(): SheetEdge;\n get rightEdge(): SheetEdge;\n pointAt(u: number, v: number): Vec3$8;\n normalAt(u: number, v: number): Vec3$8;\n curvatureAt(u: number, v: number): SurfaceCurvature;\n /** Largest principal curvature magnitude over a sampling grid (for offset safety). */\n private maxAbsPrincipalCurvature;\n /**\n * Offset the sheet along its analytic normals into a watertight solid shell of\n * the given wall thickness. Throws if the wall would self-intersect on a\n * concave region (no silent degenerate solid).\n */\n thicken(wall: number, options?: {\n resolution?: number;\n }): Shape;\n /** Per-edge continuity match against a neighbor (returns a NEW Sheet). */\n matchEdge(edge: SheetEdge): MatchEdgeBuilder;\n}\ndeclare class CurveNetBuilder {\n private lengthwiseCurves;\n private crosswiseCurves;\n private railCurves;\n private cageGrid;\n private degU?;\n private degV?;\n private built;\n lengthwise(...curves: CurveInput[]): this;\n crosswise(...curves: CurveInput[]): this;\n alongRails(railA: CurveInput, railB: CurveInput): this;\n sections(...curves: CurveInput[]): this;\n cage(grid: Vec3$8[][]): this;\n degree(n: number): this;\n degree(u: number, v: number): this;\n /** Build (once) and return the Sheet. */\n toSheet(): Sheet;\n private buildGrid;\n get frontEdge(): SheetEdge;\n get rearEdge(): SheetEdge;\n get leftEdge(): SheetEdge;\n get rightEdge(): SheetEdge;\n get surface(): BSplineSurface;\n pointAt(u: number, v: number): Vec3$8;\n normalAt(u: number, v: number): Vec3$8;\n curvatureAt(u: number, v: number): SurfaceCurvature;\n thicken(wall: number, options?: {\n resolution?: number;\n }): Shape;\n matchEdge(edge: SheetEdge): MatchEdgeBuilder;\n}\ntype CurveNet = CurveNetBuilder;\ndeclare class MatchEdgeBuilder {\n private readonly sheet;\n private readonly edge;\n constructor(sheet: Sheet, edge: SheetEdge);\n toG0(neighbor: SheetEdge): Sheet;\n toG1(neighbor: SheetEdge): Sheet;\n toG2(neighbor: SheetEdge): Sheet;\n}\ninterface ContinuityReport {\n /** Worst positional gap between the two edges (model units). */\n maxPositionGap: number;\n /** Worst cross-boundary tangent angle (degrees; 0 = G1). */\n maxTangentAngleDeg: number;\n /** Worst relative normal-curvature mismatch (0 = G2). */\n maxCurvatureRelErr: number;\n}\ndeclare class BridgeBuilder {\n private readonly edgeA;\n private readonly edgeB;\n private bulgeA;\n private bulgeB;\n constructor(edgeA: SheetEdge, edgeB: SheetEdge);\n /** Tune the influence of each side (Rhino-style bulge). */\n bulge(a: number, b: number): this;\n g0(): Sheet;\n g1(): Sheet;\n g2(): Sheet;\n private build;\n}\ntype ExactCurveInput = EdgeRef | NurbsCurve3D | Curve3D | HermiteCurve3D | QuinticHermiteCurve3D | Vec3[];\ninterface SurfaceMatchConstraintInput {\n target: EdgeRef;\n continuity?: SurfaceContinuity;\n}\ninterface SurfaceCommonOptions {\n resolution?: number;\n approximate?: boolean;\n}\ninterface SurfaceAnalyticCommonOptions {\n /** Tessellation/sample hint for non-OCCT previews and diagnostics. */\n resolution?: number;\n /** Optional normalized UV trims in this surface\'s bounded parameter domain. */\n trim?: SurfaceTrimOptions;\n}\ninterface SurfacePlaneOptions extends SurfaceAnalyticCommonOptions {\n /** Center of the finite plane patch. Defaults to the origin. */\n origin?: Vec3;\n /** Plane normal. Defaults to +Z. */\n normal?: Vec3;\n /** Plane-local +U direction. Defaults to a stable perpendicular axis. */\n xAxis?: Vec3;\n /** Physical width along the +U axis. */\n width: number;\n /** Physical height along the +V axis. */\n height: number;\n}\ninterface SurfaceCylinderOptions extends SurfaceAnalyticCommonOptions {\n /** Center of the cylinder base. Defaults to the origin. */\n origin?: Vec3;\n /** Cylinder axis from base toward top. Defaults to +Z. */\n axis?: Vec3;\n /** Direction for angle 0 around the axis. Defaults to a stable perpendicular axis. */\n xAxis?: Vec3;\n radius: number;\n height: number;\n startDeg?: number;\n endDeg?: number;\n}\ninterface SurfaceConeOptions extends SurfaceAnalyticCommonOptions {\n /** Center of the cone/frustum base. Defaults to the origin. */\n origin?: Vec3;\n /** Cone axis from base toward top. Defaults to +Z. */\n axis?: Vec3;\n /** Direction for angle 0 around the axis. Defaults to a stable perpendicular axis. */\n xAxis?: Vec3;\n radiusBottom: number;\n radiusTop: number;\n height: number;\n startDeg?: number;\n endDeg?: number;\n}\ninterface SurfaceSphereOptions extends SurfaceAnalyticCommonOptions {\n center?: Vec3;\n /** Polar axis. Defaults to +Z. */\n axis?: Vec3;\n /** Direction for longitude 0 around the axis. Defaults to a stable perpendicular axis. */\n xAxis?: Vec3;\n radius: number;\n startDeg?: number;\n endDeg?: number;\n minLatitudeDeg?: number;\n maxLatitudeDeg?: number;\n}\ninterface SurfaceTorusOptions extends SurfaceAnalyticCommonOptions {\n center?: Vec3;\n /** Axis through the torus hole. Defaults to +Z. */\n axis?: Vec3;\n /** Direction for major angle 0 around the axis. Defaults to a stable perpendicular axis. */\n xAxis?: Vec3;\n majorRadius: number;\n minorRadius: number;\n startDeg?: number;\n endDeg?: number;\n tubeStartDeg?: number;\n tubeEndDeg?: number;\n}\ninterface SurfaceSolidOptions {\n tolerance?: number;\n validate?: boolean;\n}\ninterface SurfacePatchOptions$1 extends SurfaceCommonOptions {\n match?: Partial<Record<"u0" | "u1" | "v0" | "v1", SurfaceMatchConstraintInput>>;\n style?: SurfaceFillStyle;\n}\ninterface SurfaceFillInput extends SurfaceCommonOptions {\n boundaries: Array<{\n name: string;\n curve: ExactCurveInput;\n }>;\n match?: Record<string, SurfaceMatchConstraintInput>;\n style?: SurfaceFillStyle;\n}\ninterface SurfaceBoundaryInput extends SurfaceCommonOptions {\n u: [\n ExactCurveInput,\n ExactCurveInput\n ];\n v: [\n ExactCurveInput,\n ExactCurveInput\n ];\n match?: Partial<Record<"u0" | "u1" | "v0" | "v1", SurfaceMatchConstraintInput>>;\n style?: SurfaceFillStyle;\n}\ninterface SurfaceExtendOptions {\n edge: "u0" | "u1" | "v0" | "v1";\n length: number;\n continuity?: SurfaceContinuity;\n}\ninterface SurfacePlaneOp {\n normal: Vec3;\n offset?: number;\n}\ninterface BlendEdgeOptions {\n shape?: Shape;\n edges: EdgeRef[];\n radius: number;\n continuity?: SurfaceContinuity;\n segments?: number;\n}\ninterface BlendCornerYOptions extends Omit<BlendEdgeOptions, "edges"> {\n edges: EdgeRef[];\n cornerTolerance?: number;\n minBranchAngleDeg?: number;\n}\ninterface BlendSurfaceOptions extends SurfaceFillInput {\n}\ninterface EdgeContinuityThresholds {\n continuity?: SurfaceContinuity;\n samples?: number;\n positionTolerance?: number;\n tangentToleranceDeg?: number;\n curvatureTolerance?: number;\n}\ninterface EdgeContinuityEdgeReport {\n edgeIndex: number;\n continuity: SurfaceContinuity;\n maxPositionalGap: number;\n maxTangentAngleDeg: number;\n maxCurvatureGap: number;\n}\ninterface EdgeContinuityReport {\n requested: SurfaceContinuity;\n passed: boolean;\n sampledEdges: number;\n edges: EdgeContinuityEdgeReport[];\n worst: {\n continuity: SurfaceContinuity;\n maxPositionalGap: number;\n maxTangentAngleDeg: number;\n maxCurvatureGap: number;\n };\n}\ninterface CurvatureSample {\n t: number;\n curvature: number;\n point: Vec3;\n}\ninterface SurfaceHealthReport {\n tinyEdges: Array<{\n index: number;\n length: number;\n }>;\n sliverFaces: Array<{\n index: number;\n area: number;\n sliverScore: number;\n }>;\n valid: boolean;\n}\ninterface BRepValidityOptions {\n tolerance?: number;\n requireClosed?: boolean;\n requireManifold?: boolean;\n requireSolid?: boolean;\n minVolume?: number;\n}\ntype BRepValidityIssueKind = "brep-check-invalid" | "open-edge" | "non-manifold-edge" | "degenerate-face" | "zero-volume" | "inverted-orientation";\ninterface BRepValidityIssue {\n kind: BRepValidityIssueKind;\n message: string;\n index?: number;\n faceCount?: number;\n length?: number;\n volume?: number;\n area?: number;\n}\ninterface BRepValidityReport {\n ok: boolean;\n valid: boolean;\n closed: boolean;\n manifold: boolean;\n solid: boolean;\n oriented: boolean;\n volume: number;\n edgeCount: number;\n faceCount: number;\n openEdges: number;\n nonManifoldEdges: number;\n degenerateFaces: number;\n errors: BRepValidityIssue[];\n}\ndeclare const Surface: {\n /** Create a finite analytic plane sheet that can be trimmed, sewn, thickened, or used as a low-level face. */\n Plane(options: SurfacePlaneOptions): Shape;\n /** Create a finite analytic cylindrical sheet, optionally bounded by start/end angles. */\n Cylinder(options: SurfaceCylinderOptions): Shape;\n /** Create a finite analytic conical or frustum sheet, optionally bounded by start/end angles. */\n Cone(options: SurfaceConeOptions): Shape;\n /** Create a finite analytic spherical sheet bounded by longitude and latitude ranges. */\n Sphere(options: SurfaceSphereOptions): Shape;\n /** Create a finite analytic torus sheet bounded by major and tube angle ranges. */\n Torus(options: SurfaceTorusOptions): Shape;\n /**\n * Create an exact NURBS surface from a grid of control points.\n *\n * The control grid is indexed as `controlGrid[u][v]` — each row is a curve in\n * the V direction, and columns trace curves in the U direction. With default\n * options this builds a bicubic non-rational B-spline sheet with uniform\n * clamped knots; `NurbsSurfaceOptions` controls degrees, weights, knots,\n * trim loops, tessellation, domain, and an optional `thickness` to return a\n * thin solid instead of an open sheet.\n *\n * @example\n * // Simple 4×4 control grid — a gently curved surface\n * const grid = [\n * [[0,0,0], [10,0,2], [20,0,2], [30,0,0]],\n * [[0,10,1], [10,10,5], [20,10,5], [30,10,1]],\n * [[0,20,1], [10,20,5], [20,20,5], [30,20,1]],\n * [[0,30,0], [10,30,2], [20,30,2], [30,30,0]],\n * ];\n * const sheet = Surface.Nurbs(grid);\n * const panel = Surface.Nurbs(grid, { thickness: 2 });\n */\n Nurbs(controlGrid: Vec3[][], options?: NurbsSurfaceOptions): Shape;\n Ruled(curveA: ExactCurveInput, curveB: ExactCurveInput, options?: SurfaceCommonOptions): Shape;\n /**\n * Create a smooth open surface sheet from 4 boundary curves (Coons patch).\n *\n * The four curves form the boundary of a quadrilateral patch and should meet\n * at corners (small gaps are tolerated). Boundaries are exact by default:\n * pass `NurbsCurve3D` values or `Shape.edge()` refs, or set\n * `{ approximate: true }` to accept sampled `Curve3D`/`Vec3[]` boundaries.\n * The result is an open sheet — call `.thicken(t)` for a thin solid.\n *\n * @example\n * const sheet = Surface.Patch({ bottom, top, left, right });\n * const panel = Surface.Patch({ bottom, top, left, right }).thicken(1.5);\n */\n Patch(curves: {\n bottom: ExactCurveInput;\n top: ExactCurveInput;\n left: ExactCurveInput;\n right: ExactCurveInput;\n }, options?: SurfacePatchOptions$1): Shape;\n Boundary(input: SurfaceBoundaryInput): Shape;\n Fill(input: SurfaceFillInput): Shape;\n Sew(shapes: Shape[], options?: {\n tolerance?: number;\n }): Shape;\n /** Sew surface faces or consume an existing sewn shell and make a solid B-rep. */\n Solid(input: Shape | Shape[], options?: SurfaceSolidOptions): Shape;\n Extend(shape: Shape, options: SurfaceExtendOptions): Shape;\n Trim(shape: Shape, tool: Shape | SurfacePlaneOp): Shape;\n Split(shape: Shape, tool: Shape | SurfacePlaneOp): [\n Shape,\n Shape\n ];\n Match(shape: Shape, options: {\n edge: "u0" | "u1" | "v0" | "v1";\n target: EdgeRef;\n continuity?: SurfaceContinuity;\n }): Shape;\n /**\n * Renamed alias of `Surface.Match()` — kept runtime-alive for existing scripts.\n *\n * @softDeprecated Surface.Match()\n * @deprecated use Surface.Match()\n */\n MatchEdge: (shape: Shape, options: {\n edge: "u0" | "u1" | "v0" | "v1";\n target: EdgeRef;\n continuity?: SurfaceContinuity;\n }) => Shape;\n /**\n * Begin a curve-network (Gordon) surface — the class-A keystone. Chain\n * `.lengthwise(...)/.crosswise(...)` (or `.alongRails(a,b).sections(...)`, or\n * `.cage(grid)`), then `.thicken(wall)` to get a solid Shape. Returns a fluent\n * `Sheet` builder with analytic point/normal/curvature queries and named edges.\n */\n Net(): CurveNet;\n};\ndeclare const Blend: {\n Edge(options: BlendEdgeOptions): Shape;\n Surface(options: BlendSurfaceOptions): Shape;\n /**\n * Build a transition strip between two `Surface.Net` sheet edges. Chain\n * `.bulge(a, b)` then `.g0()/.g1()/.g2()` for the continuity order. Returns a\n * `Sheet`; verify the seam with `Analysis.EdgeMatch`.\n */\n Bridge(edgeA: SheetEdge, edgeB: SheetEdge): BridgeBuilder;\n /**\n * @alpha\n * Current implementation uses continuity-controlled edge fillets on solid edges.\n * It does not yet provide a dedicated open-sheet or multi-patch Y-corner solver.\n * Follow progress: https://github.com/KoStard/forgecad-private/issues/162\n */\n CornerY(options: BlendCornerYOptions): Shape;\n};\ndeclare const Analysis: {\n EdgeContinuity(shape: Shape, options?: EdgeContinuityThresholds): EdgeContinuityReport;\n SurfaceContinuity(shape: Shape, options?: EdgeContinuityThresholds): EdgeContinuityReport;\n /**\n * Measure G0/G1/G2 agreement between two `Surface.Net` sheet edges: worst\n * position gap, cross-boundary tangent angle (0 = G1), and normal-curvature\n * mismatch (0 = G2). The reflection/fairness check for matched panel seams.\n */\n EdgeMatch(edgeA: SheetEdge, edgeB: SheetEdge, options?: {\n samples?: number;\n }): ContinuityReport;\n CurvatureComb(input: NurbsCurve3D | EdgeRef, options?: {\n samples?: number;\n }): CurvatureSample[];\n SurfaceHealth(shape: Shape, options?: {\n tinyEdgeThreshold?: number;\n sliverThreshold?: number;\n }): SurfaceHealthReport;\n /** Validate B-rep/shell/solid structure and return closedness, manifoldness, orientation, and issue diagnostics. */\n BRepValidity(shape: Shape, options?: BRepValidityOptions): BRepValidityReport;\n};\ntype VerificationStatus = "pass" | "fail";\ninterface VerificationResult {\n id: string;\n label: string;\n status: VerificationStatus;\n message: string;\n /** Verification category for quality gates and UI filtering. */\n kind?: "interface";\n /** 1-based source line number, if captured from the call stack */\n line?: number;\n /** Human-readable expected value for display */\n expected?: string;\n /** Human-readable actual value for display */\n actual?: string;\n /** Spec name — when set, the UI groups this result under a collapsible section */\n group?: string;\n}\ninterface SpecResult {\n /** Spec name */\n name: string;\n /** Number of checks that passed */\n passed: number;\n /** Total number of checks */\n total: number;\n /** The individual verification results added by this spec */\n results: VerificationResult[];\n}\n/**\n * A named, reusable bundle of verification checks.\n *\n * Create one with `spec(name, checkFn)`, then call `.check(...shapes)` to\n * run the checks. Results appear grouped under the spec name in the Checks\n * panel. Can be reused across multiple shapes and imported via `require()`.\n *\n * @category Parameters\n */\ninterface Spec {\n /** The display name of this spec */\n readonly name: string;\n /**\n * Run this spec\'s checks against one or more shapes.\n * All `verify.*` calls inside the check function are automatically\n * grouped under this spec\'s name in the Checks panel.\n */\n check(...args: unknown[]): SpecResult;\n}\n/**\n * Create a named, reusable bundle of verification checks.\n *\n * **Details**\n *\n * A spec groups related `verify.*` calls under a collapsible header in the\n * Checks panel. This makes large check suites scannable. Specs can be applied\n * to multiple shapes and can check relationships between parts.\n *\n * Specs can be defined in separate `.forge.js` files and imported via\n * `require()` to share them across models.\n *\n * `spec.check()` returns a `SpecResult` — you can inspect it programmatically\n * or ignore the return value and let the Checks panel show results.\n *\n * **Example**\n *\n * ```ts\n * const printable = spec("Fits printer bed", (shape) => {\n * verify.notEmpty("Has geometry", shape);\n * const bb = shape.boundingBox();\n * verify.lessThan("Width < 220mm", bb.max[0] - bb.min[0], 220);\n * verify.lessThan("Depth < 220mm", bb.max[1] - bb.min[1], 220);\n * verify.lessThan("Height < 250mm", bb.max[2] - bb.min[2], 250);\n * });\n *\n * // Reuse on multiple shapes\n * printable.check(bracket);\n * printable.check(standoff);\n *\n * // Check relationships between parts\n * const fitSpec = spec("Assembly fit", (partA, partB) => {\n * verify.notColliding("No interference", partA, partB, 10);\n * });\n * fitSpec.check(bracket, standoff);\n * ```\n *\n * **Spec-first workflow:** Write specs before building geometry. Checks go\n * from red to green as you build — effectively TDD for CAD.\n *\n * @param name - Display name in the Checks panel (e.g. `"Fits printer bed"`)\n * @param checkFn - Function that calls `verify.*` methods; receives args from `.check()`\n * @returns A `Spec` object with a `.check(...args)` method\n * @category Parameters\n */\ndeclare function spec(name: string, checkFn: (...args: any[]) => void): Spec;\ninterface ShapeLike {\n boundingBox(): {\n min: number[];\n max: number[];\n };\n isEmpty(): boolean;\n intersect?(other: ShapeLike): ShapeLike;\n volume(): number;\n surfaceArea(): number;\n}\ninterface ConnectorDistanceLike {\n connectorDistance(nameA: string, nameB: string): number;\n}\ninterface FaceRefLike {\n normal: [\n number,\n number,\n number\n ];\n center: [\n number,\n number,\n number\n ];\n}\ndeclare const verify: {\n /**\n * Custom predicate check.\n *\n * @param label Short name shown in the panel ("gear clearance")\n * @param check Function that returns true (pass) or false (fail)\n * @param message Optional extra context shown on failure\n */\n that(label: string, check: () => boolean, message?: string): void;\n /**\n * Check that two numbers are approximately equal (within tolerance).\n */\n equal(label: string, actual: number, expected: number, tolerance?: number, message?: string): void;\n /**\n * Check that two numbers are NOT equal (differ by more than tolerance).\n */\n notEqual(label: string, actual: number, unexpected: number, tolerance?: number, message?: string): void;\n /** Check that actual > min. */\n greaterThan(label: string, actual: number, min: number, message?: string): void;\n /** Check that actual < max. */\n lessThan(label: string, actual: number, max: number, message?: string): void;\n /** Check that min <= actual <= max. */\n inRange(label: string, actual: number, min: number, max: number, message?: string): void;\n /**\n * Check that the bounding-box centers of two shapes coincide within tolerance (mm).\n */\n centersCoincide(label: string, a: ShapeLike, b: ShapeLike, tolerance?: number): void;\n /**\n * Check the distance between two named connectors on a shape or group.\n *\n * Use this when connectors + `matchTo()` define a static assembly interface.\n * It proves the mate at runtime, unlike a plain source-level connector\n * declaration. The common case is `expected = 0`, meaning the two connector\n * origins should coincide after placement.\n *\n * **Example**\n *\n * ```ts\n * verify.connectorDistance("leg is seated", bench, "Rail.leg_0", "Leg0.head", 0, 0.01);\n * ```\n */\n connectorDistance(label: string, target: ConnectorDistanceLike, connectorA: string, connectorB: string, expected?: number, tolerance?: number): void;\n /**\n * Declare the expected physical connectivity component count for the returned visible model.\n *\n * **Details**\n *\n * Use this for generated mechanical models that should have a clear component graph:\n * one connected fixture, a purchased part plus a removable cartridge, a root assembly plus\n * named intentional ghosts, and so on. `forgecad inspect mechanical-integrity` resolves the returned\n * visible objects with the same physical-connectivity analysis used in the quality gate and\n * fails if the actual component count differs.\n *\n * This catches the common generated-CAD failure where a script returns a visually plausible\n * artifact but the handle, screw, washer, cover, or terminal block is actually a separate island.\n *\n * **Example**\n *\n * ```ts\n * verify.physicalComponentCount("vise is one connected installed assembly", 1);\n * ```\n */\n physicalComponentCount(label: string, expected: number): void;\n /**\n * Declare that two visible objects intentionally overlap because the overlap is real manufacturing intent.\n *\n * **Details**\n *\n * Use this only for overlaps that a mechanical reviewer would accept as actual matter sharing volume:\n * welded/fused regions, overmolded inserts, potted electronics, cast-in hardware, or deliberately\n * bonded laminations. This is not a shortcut for screws without holes, shafts without bores, covers\n * without pockets, or parts placed with collision as a positioning hack.\n *\n * `forgecad inspect mechanical-integrity --collisions` only honors this declaration when both shapes are\n * returned as visible objects and the exact collision report finds that same object pair. Unused or\n * non-visible declarations fail the quality gate so annotations cannot hide unrelated collisions.\n *\n * **Example**\n *\n * ```ts\n * verify.intentionalOverlap("rubber grip is overmolded on handle", rubberGrip, handleCore, "overmolded insert");\n * ```\n */\n intentionalOverlap(label: string, a: ShapeLike, b: ShapeLike, reason: string): void;\n /**\n * Check that two shapes do not share positive volume.\n *\n * Face-to-face contact is allowed; use `verify.minClearance()` when an actual\n * running gap is required.\n *\n * @param searchLength Search radius for minGap diagnostics (mm, default 1.0)\n */\n notColliding(label: string, a: ShapeLike, b: ShapeLike, searchLength?: number): void;\n /**\n * Check that a minimum clearance gap exists between two shapes.\n */\n minClearance(label: string, a: ShapeLike, b: ShapeLike, minGap: number, searchLength?: number): void;\n /**\n * Check that the clearance gap between two shapes is inside an allowed range.\n *\n * **Details**\n *\n * Use this for seated and retained interfaces where a part must be close\n * enough to be mechanically accountable, but must not collide beyond the\n * allowed minimum. It catches both failure modes that make generated CAD look\n * fake: parts floating away from their receiver, and parts intersecting their\n * receiver because the pocket, bore, or running clearance was not modeled.\n *\n * For contact, use a narrow range such as `[-0.01, 0.05]` to tolerate tiny\n * numerical noise. For a running fit, use the intended clearance band.\n *\n * Manifold-backed shapes use exact min-gap distance. Other backends use a\n * mesh-derived min-gap check and say so in the verification message; keep\n * `forgecad inspect mechanical-integrity --collisions` in the acceptance gate for\n * positive-volume interference.\n *\n * **Example**\n *\n * ```ts\n * verify.clearanceBetween("cover is seated on gasket", cover, gasket, -0.01, 0.05);\n * verify.clearanceBetween("carriage runs inside rail", carriage, rail, 0.2, 0.5);\n * ```\n */\n clearanceBetween(label: string, a: ShapeLike, b: ShapeLike, minGap: number, maxGap: number, searchLength?: number): void;\n /**\n * Check that two face normals are parallel (within toleranceDeg degrees).\n */\n parallel(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void;\n /**\n * Check that two face normals are perpendicular (within toleranceDeg degrees).\n */\n perpendicular(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void;\n /**\n * Check that a face is coplanar with (same plane as) another face,\n * meaning they are parallel AND their centers lie on the same plane.\n */\n coplanar(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number, toleranceMm?: number): void;\n /**\n * Check that a face center lies at a specific position (within toleranceMm).\n */\n faceAt(label: string, face: FaceRefLike, expectedPos: [\n number,\n number,\n number\n ], toleranceMm?: number): void;\n /**\n * Check that two face normals point in the same direction (not antiparallel).\n * Stricter than parallel — both |angle| AND sign must match.\n */\n sameDirection(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void;\n /**\n * Check that a shape is empty.\n */\n isEmpty(label: string, shape: ShapeLike, message?: string): void;\n /**\n * Check that a shape is NOT empty.\n */\n notEmpty(label: string, shape: ShapeLike, message?: string): void;\n /**\n * Check that a shape\'s volume is approximately equal to expected (mm³).\n *\n * @param expected Expected volume in mm³\n * @param tolerance Absolute tolerance in mm³ (default 1.0)\n */\n volumeApprox(label: string, shape: ShapeLike, expected: number, tolerance?: number): void;\n /**\n * Check that a shape\'s surface area is approximately equal to expected (mm²).\n *\n * @param expected Expected surface area in mm²\n * @param tolerance Absolute tolerance in mm² (default 1.0)\n */\n areaApprox(label: string, shape: ShapeLike, expected: number, tolerance?: number): void;\n /**\n * Check that a shape\'s bounding box has approximately the given size.\n *\n * @param expectedSize [sizeX, sizeY, sizeZ] in mm\n * @param tolerance Per-axis tolerance in mm (default 0.1)\n */\n boundingBoxSize(label: string, shape: ShapeLike, expectedSize: [\n number,\n number,\n number\n ], tolerance?: number): void;\n /**\n * Check that every sampled seam on a shape meets a requested continuity threshold.\n */\n edgeContinuity(label: string, shape: ShapeLike, options?: EdgeContinuityThresholds): void;\n /**\n * Check that a shape has no tiny edges below the requested threshold.\n */\n noTinyEdges(label: string, shape: ShapeLike, threshold?: number): void;\n /**\n * Check that a shape has no sliver faces below the requested score threshold.\n */\n noSliverFaces(label: string, shape: ShapeLike, threshold?: number): void;\n /**\n * Best-effort exact-shape validity guard for self-intersections or broken B-Rep topology.\n */\n noSelfIntersection(label: string, shape: ShapeLike): void;\n};\n/**\n * Register a mock (context) object for visualization and inspection.\n *\n * **Details**\n *\n * Mock objects appear in the viewport and inspection analysis when you run a\n * file directly, but are excluded when the file is imported via `require()`.\n * This lets you model the surrounding context — walls, bolts, mating parts —\n * without polluting the module\'s exports.\n *\n * The shape is returned unchanged, so you can reference it for alignment,\n * dimensioning, and `verify` checks.\n *\n * Mock objects participate in focused inspection commands such as\n * `forgecad inspect fit interference` and `forgecad inspect physical gaps`.\n * Their names appear with a `(mock)` suffix in reports.\n *\n * In the viewport, mock objects render at reduced opacity so they are\n * visually distinct from real geometry.\n *\n * **Example**\n *\n * ```ts\n * // bracket.forge.js\n * const wall = mock(box(100, 200, 10).translate(0, 0, -5), "wall");\n * const bolt = mock(cylinder(3, 15).translate(10, 15, 0), "bolt");\n *\n * const bracket = box(20, 30, 5);\n * verify.notColliding("bracket vs wall", bracket, wall);\n *\n * return bracket;\n * // When imported: only bracket is exported\n * // When run directly: bracket + wall + bolt all visible\n * ```\n *\n * @param shape - The context shape to register as a mock\n * @param name - Optional display name (defaults to "Mock N")\n * @returns The same shape, unchanged — use it for alignment and verify checks\n * @category Context\n */\ndeclare function mock<T extends Shape>(shape: T, name?: string): T;\ntype SourceFrameUpAxis = "+X" | "-X" | "+Y" | "-Y" | "+Z" | "-Z";\ninterface ImportSourceFrame {\n /** Source-file axis that should become ForgeCAD +Z after import. */\n up: SourceFrameUpAxis;\n}\ninterface ImportSourceFrameOptions {\n /**\n * Coordinate frame declared by the imported file.\n *\n * ForgeCAD remains Z-up. `sourceFrame.up` tells ForgeCAD which source-file\n * axis represents up so the import can be rotated into ForgeCAD\'s Z-up world.\n */\n sourceFrame?: ImportSourceFrame;\n}\ninterface MeshImportOptions extends ImportSourceFrameOptions {\n /** Uniform scale factor applied to the imported mesh (e.g. 25.4 for inch→mm). */\n scale?: number;\n /** Center the mesh at the origin based on its bounding box. */\n center?: boolean;\n /** For 3MF files, import one build item/resource object by stable ref or name. */\n object?: string;\n /** For 3MF files, import build items/resource objects as named ShapeGroup children. */\n separateObjects?: boolean;\n}\ninterface StepImportOptions extends ImportSourceFrameOptions {\n}\ntype SculptBlendInput = SdfShape | readonly SdfShape[];\ninterface SculptBlendOptions {\n /** Smooth blend radius in model units. Default: 4. */\n radius?: number;\n}\ninterface SculptBoxOptions {\n /** Rounded-box radius. Omit for a sharp SDF box. */\n radius?: number;\n}\ninterface SculptTubeOptions {\n /** Tube radius in model units. Used as the default when points do not carry their own radius. Default: 3. */\n radius?: number;\n /** Smooth blend radius between tube segments and joints. Default: smallest point radius. */\n blend?: number;\n /** Smooth the control polyline into a Catmull-Rom curve before sweeping. Default: false for tube/path, true for curve. */\n curve?: boolean | "linear" | "catmull-rom";\n /** Curve samples per control-point interval when curve smoothing is enabled. Default: 8, capped at 8 for realtime preview. */\n segments?: number;\n /** Catmull-Rom tangent scale when curve smoothing is enabled. Default: 0.5. */\n tension?: number;\n /** Material/color preset applied to the final tube. */\n polish?: SculptPolishInput;\n}\ntype SculptVec3 = Vec3$1 | readonly [\n number,\n number,\n number\n];\ntype SculptPathPoint = SculptVec3 | readonly [\n number,\n number,\n number,\n number\n] | {\n point: SculptVec3;\n radius?: number;\n} | {\n position: SculptVec3;\n radius?: number;\n} | {\n at: SculptVec3;\n radius?: number;\n};\ntype SculptPointList = readonly SculptPathPoint[];\ntype SculptLookPreset = "gallery" | "soft-studio" | "candy-shop" | "midnight" | "workbench";\ntype SculptBlendArg = SculptBlendInput | SculptBlendOptions;\ndeclare function sculptSphere(radius: number): SdfShape;\ndeclare function sculptBox(x: number, y: number, z: number, options?: SculptBoxOptions): SdfShape;\ndeclare function sculptCylinder(height: number, radius: number): SdfShape;\ndeclare function sculptDisk(radius: number, thickness?: number): SdfShape;\ndeclare function sculptCapsule(height: number, radius: number): SdfShape;\ndeclare function sculptTorus(majorRadius: number, minorRadius: number): SdfShape;\ndeclare function sculptCone(height: number, radius: number): SdfShape;\ndeclare function sculptTubePublic(points: SculptPointList, options?: SculptTubeOptions): SdfShape;\ndeclare function sculptCurve(points: SculptPointList, options?: SculptTubeOptions): SdfShape;\ndeclare function sculptBlend(first?: SculptBlendArg, optionsOrShape?: SculptBlendArg, ...rest: SculptBlendArg[]): SdfShape;\ndeclare function sculptUnion(first?: SculptBlendInput, ...rest: SculptBlendInput[]): SdfShape;\ndeclare function sculptCarve(base: SdfShape, cutters: SculptBlendInput, options?: SculptBlendOptions): SdfShape;\ndeclare function sculptKeep(first?: SculptBlendArg, optionsOrShape?: SculptBlendArg, ...rest: SculptBlendArg[]): SdfShape;\ndeclare function sculptPolish(shape: SdfShape, input?: SculptPolishInput): SdfShape;\ndeclare function sculptMaterial(input?: SculptPolishInput): ShapeMaterialProps & {\n color?: string;\n};\ndeclare function sculptLook(preset?: SculptLookPreset): SceneOptions;\ndeclare const Sculpt: {\n /** Create a liquid SDF sphere centered at the origin. */\n sphere: typeof sculptSphere;\n /** Create a liquid SDF box; pass `{ radius }` for a rounded box. */\n box: typeof sculptBox;\n /** Create a liquid SDF cylinder centered at the origin, axis along Z. */\n cylinder: typeof sculptCylinder;\n /** Create a thin circular disk centered at the origin, axis along Z. Useful as a circular cutter or insert. */\n disk: typeof sculptDisk;\n /**\n * Alias for `Sculpt.disk()`.\n * @softDeprecated Sculpt.disk(radius, thickness)\n * @deprecated use Sculpt.disk(radius, thickness)\n */\n circle: typeof sculptDisk;\n /** Create a liquid SDF capsule centered at the origin, axis along Z. */\n capsule: typeof sculptCapsule;\n /** Create a liquid SDF torus lying in the XY plane. */\n torus: typeof sculptTorus;\n /** Create a liquid SDF cone. */\n cone: typeof sculptCone;\n /** Create a smooth tube through a list of 3D points. */\n tube: typeof sculptTubePublic;\n /** Create a smooth variable-thickness sweep through 3D control points. */\n curve: typeof sculptCurve;\n /**\n * Alias for `Sculpt.tube()`; points may use [x, y, z, radius] for variable thickness.\n * @softDeprecated Sculpt.tube(points, options)\n * @deprecated use Sculpt.tube(points, options)\n */\n path: typeof sculptTubePublic;\n /** Smoothly blend one or more SDF shapes into a continuous body. */\n blend: typeof sculptBlend;\n /** Sharply union one or more SDF shapes. */\n union: typeof sculptUnion;\n /** Smoothly subtract one or more cutter shapes from a base shape. */\n carve: typeof sculptCarve;\n /** Smoothly intersect one or more SDF shapes. */\n keep: typeof sculptKeep;\n /** Apply a Sculpt material preset or direct material properties. */\n polish: typeof sculptPolish;\n /** Resolve a Sculpt material preset to ForgeCAD material properties. */\n material: typeof sculptMaterial;\n /** Return a polished scene preset tuned for liquid SDF preview. */\n look: typeof sculptLook;\n /** List the built-in Sculpt material preset names. */\n knownMaterials: typeof knownSculptMaterialPresets;\n};\n/**\n * SDF modeling — signed distance field primitives, smooth booleans, TPMS lattices,\n * domain warps, and surface patterns.\n *\n * Return `SdfShape` values directly from a script for native raymarch preview; plain\n * objects and arrays of SDF leaves render too (object keys become named preview parts).\n * Shapes live as a lazy expression tree — call `.toShape()` / `toShape(...)` only at the\n * materialization boundary: export, mesh booleans, or mixed SDF/manifold projects.\n *\n * SDF geometry is implicit and sampled, not B-rep/exact. Use with caution when precision,\n * tolerances, or exact export matter.\n *\n * @example\n * ```js\n * return {\n * shell: sdf.smoothUnion(sdf.sphere(10), sdf.box(15, 15, 15), { radius: 3 }).shell(2),\n * core: sdf.gyroid({ cellSize: 6, wallThickness: 0.8 })\n * .intersect(sdf.sphere(18))\n * .color(\'#ffcf5a\'),\n * };\n * ```\n */\ndeclare const sdf: {\n /** Create an SDF sphere centered at the origin. */\n sphere: typeof sphere;\n /** Create an SDF box centered at the origin with given full dimensions (not half-extents). */\n box: typeof box;\n /** Create an SDF cylinder centered at the origin, axis along Z. */\n cylinder: typeof cylinder;\n /** Create an SDF torus centered at the origin, lying in the XY plane. */\n torus: typeof torus;\n /** Create an SDF capsule centered at the origin, axis along Z. */\n capsule: typeof capsule;\n /** Create an SDF cone with base at z=0 and tip at z=height. */\n cone: typeof cone;\n /** Smooth union — blends shapes together with a smooth transition radius. */\n smoothUnion: typeof smoothUnion;\n /** Smooth difference — smoothly subtracts b from a. */\n smoothDifference: typeof smoothDifference;\n /** Smooth intersection — smoothly intersects a and b. */\n smoothIntersection: typeof smoothIntersection;\n /**\n * Morph between two SDF shapes. t=0 → a, t=1 → b.\n * @softDeprecated a.morph(b, t)\n * @deprecated use a.morph(b, t)\n */\n morph: typeof morph;\n /**\n * Spatially blend between two SDF patterns.\n * The blend function receives (x, y, z) and returns 0..1:\n * 0 = fully pattern `a`, 1 = fully pattern `b`.\n */\n blend: typeof blend;\n /** Gyroid TPMS lattice — the most common lattice for additive manufacturing. */\n gyroid: typeof gyroid;\n /** Schwarz-P TPMS lattice — isotropic pore structure. */\n schwarzP: typeof schwarzP;\n /** Diamond TPMS lattice — stiffest TPMS structure. */\n diamond: typeof diamond;\n /** Lidinoid TPMS lattice — visually distinct from gyroid, popular in research and art. */\n lidinoid: typeof lidinoid;\n /**\n * TPMS block preset clipped to an explicit design space.\n * @softDeprecated sdf.gyroid({ cellSize, wallThickness }).intersect(sdf.box(x, y, z))\n * @deprecated use sdf.gyroid({ cellSize, wallThickness }).intersect(sdf.box(x, y, z))\n */\n tpmsBlock: typeof tpmsBlock;\n /**\n * Clip an SDF shape to a box-shaped design space.\n * @softDeprecated shape.intersect(sdf.box(x, y, z))\n * @deprecated use shape.intersect(sdf.box(x, y, z))\n */\n withinBox: typeof withinBox;\n /** 3D Simplex noise field — produces organic, natural-looking displacements. */\n noise: typeof noise;\n /** 3D Voronoi pattern — organic cellular structures like bone, coral, or soap bubbles. */\n voronoi: typeof voronoi;\n /** Honeycomb (hexagonal) lattice pattern. Intersect with your shape to apply. */\n honeycomb: typeof honeycomb;\n /** Sinusoidal wave ridges — parallel ridges along an axis. */\n waves: typeof waves;\n /** Knurl pattern — crossed helical grooves for grips and handles. */\n knurl: typeof knurl;\n /** Perforated plate pattern — regular array of cylindrical holes. */\n perforated: typeof perforated;\n /** Fish/dragon scale pattern — overlapping circular scales in hex-packed rows. */\n scales: typeof scales;\n /** Brick/stone wall pattern — running bond with mortar grooves. */\n brick: typeof brick;\n /** Grid lattice pattern — two families of infinite slabs crossing at 90°. */\n weave: typeof weave;\n /** Basket weave surface pattern — threads with over-under crossings in UV space. Returns a SurfacePattern for use with `.surfaceDisplace()`. */\n basketWeave: typeof basketWeave;\n /** Create typed, composable 2D surface patterns for `.surfaceDisplace()`. */\n pattern2d: typeof pattern2d;\n /**\n * Twist an SDF shape around the Z axis.\n * @softDeprecated shape.twist(degreesPerUnit)\n * @deprecated use shape.twist(degreesPerUnit)\n */\n twist: typeof twist;\n /**\n * Bend an SDF shape around the Z axis.\n * @softDeprecated shape.bend(radius)\n * @deprecated use shape.bend(radius)\n */\n bend: typeof bend;\n /**\n * Repeat an SDF shape in space.\n * @softDeprecated shape.repeat(spacing, count)\n * @deprecated use shape.repeat(spacing, count)\n */\n repeat: typeof repeat;\n /**\n * Arrange an SDF shape in a circular array around the Z axis.\n * @softDeprecated shape.circularArray(count, offset)\n * @deprecated use shape.circularArray(count, offset)\n */\n circularArray: typeof circularArray;\n /** A 2D surface pattern — a heightmap function for use with `.surfaceDisplace()`. */\n SurfacePattern: typeof SurfacePattern;\n /** Create a custom SDF from one expression; shader-safe expressions keep shader metadata for tooling. */\n fromFunction: typeof fromFunction;\n /**\n * Collapse a plain object/array tree of SDF leaves into one continuous implicit field.\n * Per-leaf color/material identity is intentionally discarded — the result is one\n * scalar field. Use plain object returns for multi-material SDF preview, and\n * `sdf.combine(...)` only when you want one implicit body. Explicit shape lists\n * are covered by `a.union(b, c)`; pass `{ op: \'intersection\' }` to intersect instead.\n *\n * ```js\n * // Built a part as a named tree for preview, now need one field to shell:\n * const parts = { body: sdf.box(40, 30, 12), boss: sdf.cylinder(10, 8) };\n * return sdf.combine(parts).shell(1.2);\n * ```\n */\n combine: typeof combine;\n /** Sculpt-like facade: friendly liquid-modeling verbs backed by the same SDF kernel. */\n Sculpt: {\n sphere: (radius: number) => SdfShape;\n box: (x: number, y: number, z: number, options?: SculptBoxOptions) => SdfShape;\n cylinder: (height: number, radius: number) => SdfShape;\n disk: (radius: number, thickness?: number) => SdfShape;\n circle: (radius: number, thickness?: number) => SdfShape;\n capsule: (height: number, radius: number) => SdfShape;\n torus: (majorRadius: number, minorRadius: number) => SdfShape;\n cone: (height: number, radius: number) => SdfShape;\n tube: (points: SculptPointList, options?: SculptTubeOptions) => SdfShape;\n curve: (points: SculptPointList, options?: SculptTubeOptions) => SdfShape;\n path: (points: SculptPointList, options?: SculptTubeOptions) => SdfShape;\n blend: (first?: SculptBlendInput | SculptBlendOptions, optionsOrShape?: SculptBlendInput | SculptBlendOptions, ...rest: (SculptBlendInput | SculptBlendOptions)[]) => SdfShape;\n union: (first?: SculptBlendInput, ...rest: SculptBlendInput[]) => SdfShape;\n carve: (base: SdfShape, cutters: SculptBlendInput, options?: SculptBlendOptions) => SdfShape;\n keep: (first?: SculptBlendInput | SculptBlendOptions, optionsOrShape?: SculptBlendInput | SculptBlendOptions, ...rest: (SculptBlendInput | SculptBlendOptions)[]) => SdfShape;\n polish: (shape: SdfShape, input?: SculptPolishInput) => SdfShape;\n material: (input?: SculptPolishInput) => ShapeMaterialProps & {\n color?: string;\n };\n look: (preset?: SculptLookPreset) => SceneOptions;\n knownMaterials: typeof knownSculptMaterialPresets;\n };\n};\ntype GeometryBackend = "manifold" | "occt" | "truck" | "sdf" | "hybrid" | "unknown";\ntype GeometryRepresentation = "mesh-solid" | "brep-solid" | "surface" | "mixed";\ntype GeometryFidelity = "kernel-native" | "exact" | "sampled" | "deformed" | "mixed" | "unknown";\ntype GeometryTopology = "none" | "synthetic" | "kernel";\ntype GeometrySource = "primitive" | "extrude" | "revolve" | "boolean" | "sheet-metal" | "shell" | "fillet" | "chamfer" | "level-set" | "loft" | "sweep" | "surface-patch" | "deform" | "draft" | "offset-solid" | "imported" | "sdf" | "fromSlices" | "unknown";\ninterface GeometryInfo {\n backend: GeometryBackend;\n representation: GeometryRepresentation;\n fidelity: GeometryFidelity;\n topology: GeometryTopology;\n sources: GeometrySource[];\n}\ninterface ShapeLike$1 {\n toShape(): Shape;\n}\ntype ShapeOperandInput = Shape | ShapeLike$1 | readonly (Shape | ShapeLike$1)[];\ntype ShapeRuntimePayload = ShapeBackend | Manifold;\ntype ShapeAnchorTarget = Shape | {\n referencePoint(ref: string): [\n number,\n number,\n number\n ];\n} | {\n _bbox(): {\n min: number[];\n max: number[];\n };\n};\ntype RotationPointLike = PlacementAnchorLike | Vec3;\ninterface ShapeMaterialProps {\n /** Metalness factor (0 = dielectric, 1 = metal). Default: 0.05 */\n metalness?: number;\n /** Roughness factor (0 = mirror, 1 = fully diffuse). Default: 0.35 */\n roughness?: number;\n /** Emissive glow color (hex string, e.g. "#ff6b35"). */\n emissive?: string;\n /** Emissive intensity multiplier. Default: 1 */\n emissiveIntensity?: number;\n /** Opacity (0 = fully transparent, 1 = fully opaque). Default: 1 */\n opacity?: number;\n /** Render as wireframe. Default: false */\n wireframe?: boolean;\n /** Clearcoat intensity (0–1). Default: 0.1 */\n clearcoat?: number;\n /** Clearcoat roughness (0–1). Default: 0.4 */\n clearcoatRoughness?: number;\n /** Glass/translucency transmission factor (0–1). Renderer support depends on target. */\n transmission?: number;\n /** Index of refraction for transmissive materials. Typical glass is ~1.45. */\n ior?: number;\n /** Approximate transmissive volume thickness in model units. */\n thickness?: number;\n /** Specular highlight intensity (0–1). */\n specularIntensity?: number;\n /** Specular highlight tint. */\n specularColor?: string;\n /** Reflection strength for supported renderers (0–1). */\n reflectivity?: number;\n}\ntype ShapeReferenceKind = "face" | "face-set" | "edge" | "edge-set" | "point" | "point-set";\ntype ShapeReferenceStatus = "alive" | "deleted" | "ambiguous";\ntype ShapeReferenceCardinality = "zero" | "one" | "many";\ninterface ShapeReferenceResolution {\n path: string;\n sourcePath: string;\n shapeName?: string;\n kind: ShapeReferenceKind;\n status: ShapeReferenceStatus;\n cardinality: ShapeReferenceCardinality;\n rule: string;\n faces: FaceRef[];\n edges: EdgeSegment[];\n points: Vec3[];\n}\n/**\n * A first-class reference path over a shape\'s semantic faces and face relationships.\n *\n * Created with `shape.ref("lid/back")`, then refined through methods such as\n * `.point()` or `.edges()`. The reference stores intent as a readable path and\n * resolves lazily against the current shape metadata.\n */\ndeclare class ShapeRef {\n private readonly shape;\n readonly path: string;\n private readonly optional;\n constructor(shape: Shape, path: string, optional?: boolean);\n /** Resolve this reference into its current faces, edges, or points. */\n resolve(): ShapeReferenceResolution;\n /** The resolved reference kind, such as `face`, `edge-set`, or `point`. */\n get kind(): ShapeReferenceKind;\n /** Whether the reference currently resolves to zero, one, or many matches. */\n get cardinality(): ShapeReferenceCardinality;\n /** Return the reference lifecycle status for the current shape state. */\n status(): ShapeReferenceStatus;\n /** Return a human-readable explanation of how this reference resolved. */\n explain(): string;\n /** Name this derived reference so the same shape can resolve it by `shape.ref(name)`. */\n as(name: string): ShapeRef;\n /** Return an optional reference that resolves to zero matches instead of throwing when missing. */\n maybe(): ShapeRef;\n /** Mark that a multi-match reference is intentionally being used as a set. */\n all(): ShapeRef;\n /** Require this reference to resolve to exactly one match. */\n one(): ShapeRef;\n /** Resolve this reference as one or more faces. */\n faces(): FaceRef[];\n /** Resolve this reference as exactly one face. */\n face(): FaceRef;\n /** Resolve this reference as one or more edges. Face references return boundary edges. */\n edges(): EdgeSegment[];\n /** Resolve this reference as exactly one edge. */\n edge(): EdgeSegment;\n /** Resolve this reference as one or more points. Faces use centers and edges use midpoints. */\n points(): Vec3[];\n /** Resolve this reference as exactly one point. */\n point(): Vec3;\n /** Return the structured JSON-friendly reference resolution. */\n toJSON(): ShapeReferenceResolution;\n /** Return a compact display form for this reference path. */\n toString(): string;\n}\n/**\n * Core 3D solid shape. All operations are immutable and return new shapes.\n *\n * Supports transforms (translate, rotate, scale, mirror, transform, rotateAround, pointAlong),\n * booleans (add, subtract, intersect), cutting (split, splitByPlane, trimByPlane),\n * shelling, anchor positioning (attachTo, onFace), placement references, and queries\n * (volume, surfaceArea, boundingBox, isEmpty, numTri, geometryInfo).\n */\ndeclare class Shape {\n colorHex: string | undefined;\n materialProps: ShapeMaterialProps | undefined;\n constructor(payload: ShapeRuntimePayload, color?: string, geometryInfo?: Partial<GeometryInfo>);\n /** @internal Use .color() instead. */\n setColor(value: string | undefined): Shape;\n /** Set the color of this shape (hex string, e.g. "#ff0000"). Returns a new Shape with the color applied. */\n color(value: string | undefined): Shape;\n /**\n * Set PBR material properties for this shape\'s visual appearance.\n *\n * **Details**\n *\n * Returns a new Shape with the specified material properties merged on top of any previously\n * set properties. All properties are optional — omitted keys retain their current value.\n * Material properties survive transforms and boolean operations.\n *\n * Use `.color()` to set the base diffuse color; `.material()` controls how that color behaves\n * under light (metalness, roughness, clearcoat) and can add emissive glow independent of\n * lighting.\n *\n * **Example**\n *\n * ```js\n * box(50, 50, 50).material({ metalness: 0.9, roughness: 0.1 }); // polished metal\n * sphere(30).material({ emissive: \'#ff6b35\', emissiveIntensity: 2 }); // glowing\n * cylinder(40, 20).material({ opacity: 0.4, clearcoat: 1.0, clearcoatRoughness: 0.02 }); // ice\n *\n * // Chainable with other shape methods\n * box(100, 100, 10).color(\'#gold\').material({ metalness: 0.95, roughness: 0.05 }).translate(0, 0, 50);\n * ```\n *\n * @param props.metalness - Metallic vs. dielectric (0–1). Default: 0.05\n * @param props.roughness - Surface roughness; 0 = mirror, 1 = matte. Default: 0.35\n * @param props.emissive - Glow color hex string (e.g. `\'#ff6b35\'`). Glows independently of lighting\n * @param props.emissiveIntensity - Glow multiplier. Default: 1\n * @param props.opacity - Transparency; 0 = fully transparent, 1 = fully opaque. Default: 1\n * @param props.wireframe - Render as wireframe. Default: false\n * @param props.clearcoat - Clearcoat layer intensity (0–1). Default: 0.1\n * @param props.clearcoatRoughness - Clearcoat layer roughness (0–1). Default: 0.4\n * @returns A new Shape with the merged material properties applied\n * @category Materials\n */\n material(props: ShapeMaterialProps): Shape;\n /** Return a new Shape wrapper for explicit duplication in scripts. */\n clone(): Shape;\n /** Alias for clone() */\n duplicate(): Shape;\n /** Inspect which backend/representation produced this solid. */\n geometryInfo(): GeometryInfo;\n /** Name this shape as a reference namespace for diagnostics and future published refs. */\n as(name: string): Shape;\n /** Resolve a semantic reference path like `lid`, `lid/back`, or a midpoint selector on `lid/back`. */\n ref(path: string): ShapeRef;\n /** Attach named placement references that survive normal transforms and imports. */\n withReferences(refs: PlacementReferenceInput): Shape;\n /** List named placement references carried by this shape. */\n referenceNames(kind?: PlacementReferenceKind): string[];\n /**\n * Deprecated alias for `withConnectors()`.\n * @deprecated Use `withConnectors()` instead.\n */\n withPorts(_ports: Record<string, PortInput>): Shape;\n /**\n * Deprecated alias for `connectorNames()`.\n * @deprecated Use `connectorNames()` instead.\n */\n portNames(): string[];\n /** Resolve a named placement reference or built-in anchor to a 3D point. */\n referencePoint(ref: PlacementAnchorLike): [\n number,\n number,\n number\n ];\n /**\n * Resolve a face by user-authored label or compiler-owned name. Returns a `FaceRef`\n * that can be passed to `.onFace()`, `projectToPlane()`, or used directly in placement.\n *\n * **Details**\n *\n * `.face(name)` is a pure label lookup — it finds faces by user-authored labels, not by\n * geometric queries. Labels are born in sketches via `.label()` / `.labelEdges()` and\n * grow into face names through extrude, loft, revolve, and sweep. They are stable\n * references that travel with the geometry.\n *\n * Labels must be unique within a shape. Use `.prefixLabels()` before combining shapes\n * with `union()` / `difference()` to avoid collisions. Collision detection throws a\n * clear error with a fix suggestion.\n *\n * Boolean survival: `union()` and `intersection()` carry labels from every operand;\n * `difference()` carries only the base (first) operand\'s labels — cutter labels are\n * dropped. A surviving label addresses whatever portion of its face survives the\n * boolean; cutters may split or erase it, and a lineage shared by multiple union\n * operands resolves as a face set rather than a single face.\n *\n * For compile-covered shapes (extrude, loft, etc.) the lookup resolves via the shape\'s\n * compile plan. As a fallback, planar-faced mesh shapes (e.g. results of boolean ops)\n * are resolved via coplanar triangle clustering.\n *\n * **Example**\n *\n * ```ts\n * // Edge labels become side face names after extrude\n * const profile = path()\n * .moveTo(0, 0)\n * .lineTo(100, 0).label(\'floor\')\n * .lineTo(100, 50).label(\'wall\')\n * .lineTo(0, 50).label(\'ceiling\')\n * .closeLabel(\'left-wall\');\n * const room = profile.extrude(30, { labels: { start: \'base\', end: \'top\' } });\n * room.face(\'floor\'); // side face from the labeled edge\n * room.face(\'base\'); // base cap (user-specified)\n *\n * // .labelEdges() shorthand for sequential edge labeling\n * const plate = rect(100, 50).labelEdges(\'south\', \'east\', \'north\', \'west\');\n * const solid = plate.extrude(20, { labels: { start: \'bottom\', end: \'top\' } });\n * solid.face(\'south\'); // side face\n *\n * // Prefix before combining to avoid collisions\n * const left = wing.prefixLabels(\'l/\');\n * const right = wing.mirror([1, 0, 0]).prefixLabels(\'r/\');\n * const full = union(left, right);\n * full.face(\'l/upper\'); // left wing upper surface\n * ```\n *\n * @param selector - A user-authored label string, compiler-owned face name, or FaceQuery.\n * @returns A `FaceRef` carrying the face\'s center, normal, and local axes.\n * @see {@link Shape.faceNames} — list available face names on this shape\n * @see {@link Shape.prefixLabels} — prefix labels before combining shapes\n * @category Face References\n */\n face(selector: FaceSelector): FaceRef;\n /**\n * Return faces matching a query, or label semantic faces when passed a mapping.\n *\n * Mapping form returns a new shape:\n * `shape.faces({ lid: \'top\', walls: [\'front\', \'back\', \'left\', \'right\'] })`.\n */\n faces(): FaceRef[];\n faces(query: FaceQuery): FaceRef[];\n faces(mapping: Record<string, string | string[]>): Shape;\n /** List defined semantic face names currently available on this shape. */\n faceNames(): string[];\n /** Prefix all user-authored face labels, including semantic labels from `faces(mapping)`. Returns a new shape with modified labels. */\n prefixLabels(prefix: string): Shape;\n /** Rename a single face label. Returns a new shape. */\n renameLabel(from: string, to: string): Shape;\n /** Remove specific face labels. Returns a new shape. */\n dropLabels(...names: string[]): Shape;\n /** Remove all face labels. Returns a new shape. */\n dropAllLabels(): Shape;\n /** Get a named topology edge. Only available on shapes with tracked topology (from box/cylinder/extrude). */\n edge(name: string): EdgeRef;\n /** List named topology edge names. Returns empty array if shape has no tracked topology. */\n edgeNames(): string[];\n /**\n * Return all boundary edges of a named face.\n *\n * **Details**\n *\n * Finds edges where one adjacent mesh face belongs to the target face and the\n * other belongs to a different face. The result is coalesced (tessellation\n * fragments merged) and can be passed directly to `fillet()` or `chamfer()`.\n *\n * This is a topological query — no coordinates, no tolerances, no minimum-length\n * hacks. It works because an edge is the boundary between two faces.\n *\n * **Example**\n *\n * ```js\n * // Fillet all top edges of a mounting plate\n * let plate = box(120, 80, 6).faces({ workSurface: \'top\' })\n * plate = fillet(plate, 3, plate.edgesOf(\'workSurface\'))\n *\n * // Shelled enclosure — fillet the outer lip\n * let body = box(80, 50, 35).faces({ opening: \'top\' })\n * body = body.shell(2, { openFaces: [\'top\'] })\n * body = fillet(body, 1.5, body.edgesOf(\'opening\'))\n *\n * // Filter: only concave edges (after a boolean subtraction)\n * body.edgesOf(\'top\', { concave: true })\n * ```\n *\n * @param faceLabel - Name of the face whose edges to return.\n * @param options - Optional: exclude faces, convexity filter, minLength.\n * @returns EdgeSegment[] ready for fillet/chamfer. Throws if face not found.\n * @see {@link Shape.edgesBetween} — edges shared between two faces\n * @see {@link Shape.faces} — assign labels to faces\n * @see {@link Shape.ref} — resolve semantic face and edge paths\n * @category Edge Queries\n */\n edgesOf(faceLabel: string, options?: EdgesOfOptions): EdgeSegment[];\n /**\n * Return edges shared between two named faces.\n *\n * **Details**\n *\n * An edge is "between" faces A and B when one of its adjacent mesh triangles\n * belongs to A and the other belongs to B. This is the most precise topological\n * edge selection — "fillet the edges where the top meets the wall."\n *\n * The second argument can be a single face name or an array (edges between A\n * and any of B1, B2, ...).\n *\n * **Example**\n *\n * ```js\n * // Fillet the edge where lid meets one wall\n * let body = box(100, 60, 30).faces({ lid: \'top\', wall: \'side-left\' })\n * body = fillet(body, 2, body.edgesBetween(\'lid\', \'wall\'))\n *\n * // Fillet a cylinder rim — where the flat cap meets the curved barrel\n * let tube = cylinder(30, 10).faces({ cap: \'top\', barrel: \'side\' })\n * tube = fillet(tube, 1, tube.edgesBetween(\'cap\', \'barrel\'))\n *\n * // Multiple target faces at once\n * body.edgesBetween(\'lid\', [\'left-wall\', \'right-wall\', \'front-wall\', \'back-wall\'])\n * ```\n *\n * @param faceA - First face name.\n * @param faceB - Second face name, or array of face names.\n * @returns EdgeSegment[] ready for fillet/chamfer. Throws if faces don\'t share edges.\n * @see {@link Shape.edgesOf} — all edges of a single face\n * @see {@link Shape.faces} — assign labels to faces\n * @see {@link Shape.ref} — resolve semantic face and edge paths\n * @category Edge Queries\n */\n edgesBetween(faceA: string, faceB: string | string[]): EdgeSegment[];\n /** @internal Resolve a user face label or canonical name to a FaceRef. */\n private _resolveFaceLabel;\n /** @internal Resolve a user face label, face set, or canonical name to one or more FaceRefs. */\n private _resolveFaceLabels;\n /** Get the transformation history for a specific face. */\n faceHistory(name: string): FaceTransformationHistory;\n /**\n * Translate the shape so the given anchor or reference lands on the target coordinate.\n *\n * Accepts any built-in anchor name (`\'bottom\'`, `\'center\'`, `\'top-front-left\'`, etc.)\n * or a custom placement reference attached via `withReferences()`.\n *\n * ```javascript\n * // Ground a shape — put its bottom face center at Z = 0\n * shape.placeReference(\'bottom\', [0, 0, 0])\n *\n * // Center at the world origin\n * shape.placeReference(\'center\', [0, 0, 0])\n *\n * // Align left edge to X = 10\n * shape.placeReference(\'left\', [10, 0, 0])\n * ```\n */\n placeReference(ref: PlacementAnchorLike, target: [\n number,\n number,\n number\n ], offset?: [\n number,\n number,\n number\n ]): Shape;\n /**\n * Translate using polar coordinates (radius + angle in degrees).\n * Eliminates manual `r * Math.cos(angle * PI/180)` calculations.\n *\n * Example: `shape.translatePolar(50, 30)` moves 50mm at 30 degrees from +X.\n */\n translatePolar(radius: number, angleDeg: number, z?: number): Shape;\n /** Move the shape relative to its current position. All transforms are immutable and return new shapes. */\n translate(x: number, y: number, z: number): Shape;\n /** Position the shape so its bounding box min corner is at the given global coordinate. */\n moveTo(x: number, y: number, z: number): Shape;\n /** Position the shape relative to another shape\'s local coordinate system (bounding box min corner). */\n moveToLocal(target: Shape | {\n toShape(): Shape;\n }, x: number, y: number, z: number): Shape;\n /** Rotate around an arbitrary axis through the origin. Unlike `Sketch.rotate()` (bounding-box center), this pivots at the world origin — pass `options.pivot` to rotate in place. */\n rotate(axis: [\n number,\n number,\n number\n ], angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): Shape;\n /** Rotate around the X axis by the given angle in degrees. */\n rotateX(angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): Shape;\n /** Rotate around the Y axis by the given angle in degrees. */\n rotateY(angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): Shape;\n /** Rotate around the Z axis by the given angle in degrees. */\n rotateZ(angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): Shape;\n /** Apply a 4x4 affine transform matrix (column-major) or a Transform object. */\n transform(m: Mat4 | Transform): Shape;\n /** Scale the shape uniformly or per-axis from the shape\'s bounding box center. Accepts a single number or [x, y, z] array. */\n scale(v: number | [\n number,\n number,\n number\n ]): Shape;\n /** Scale the shape uniformly or per-axis from an explicit pivot point. */\n scaleAround(pivot: [\n number,\n number,\n number\n ], v: number | [\n number,\n number,\n number\n ]): Shape;\n /** Mirror across a plane through the shape\'s bounding box center, defined by its normal vector. */\n mirror(normal: [\n number,\n number,\n number\n ]): Shape;\n /** Mirror across a plane through an explicit point, defined by its normal vector. */\n mirrorThrough(point: [\n number,\n number,\n number\n ], normal: [\n number,\n number,\n number\n ]): Shape;\n /**\n * Reorient a shape so its primary axis (Z) points along the given direction.\n * Useful for laying cylinders/extrusions along X or Y without thinking about Euler angles.\n * The shape\'s origin stays at [0,0,0] — translate after pointAlong to position it.\n *\n * Example: cylinder(40, 5).pointAlong([1, 0, 0]) — lays cylinder along X, starting at origin\n */\n pointAlong(direction: [\n number,\n number,\n number\n ]): Shape;\n /**\n * Rotate around an arbitrary axis through a pivot point.\n * Equivalent to: translate(-pivot) → rotate around axis → translate(+pivot)\n * @internal Prefer rotate(), rotateX(), rotateY(), rotateZ() for public use.\n */\n rotateAroundAxis(axis: [\n number,\n number,\n number\n ], angleDeg: number, pivot?: [\n number,\n number,\n number\n ]): Shape;\n /**\n * Rotate around an axis until a moving point reaches the target line/plane defined by the axis and target point.\n * `movingPoint` / `targetPoint` may be raw world points or this shape\'s anchors/references.\n */\n rotateAroundTo(axis: [\n number,\n number,\n number\n ], pivot: [\n number,\n number,\n number\n ], movingPoint: RotationPointLike, targetPoint: RotationPointLike, options?: RotateAroundToOptions): Shape;\n /** Unwrap any object with toShape() without circular import. */\n private static _unwrap;\n /**\n * Warn if a boolean operation had no geometric effect.\n * Compares volumes before and after; if they match within tolerance, the operation was a no-op.\n */\n private static _checkBooleanNoOp;\n /** Union this shape with others (additive boolean). Method form of union(). */\n add(...others: ShapeOperandInput[]): Shape;\n /** Subtract other shapes from this one. Method form of difference(). */\n subtract(...others: ShapeOperandInput[]): Shape;\n /** Keep only the overlap with other shapes. Method form of intersection(). */\n intersect(...others: ShapeOperandInput[]): Shape;\n /** Alias for add() — matches the free-function union() naming. */\n union(...others: ShapeOperandInput[]): Shape;\n /** Alias for subtract() — matches the free-function difference() naming. */\n difference(...others: ShapeOperandInput[]): Shape;\n /** Alias for intersect() — matches the free-function intersection() naming. */\n intersection(...others: ShapeOperandInput[]): Shape;\n /** Split into [inside, outside] by another shape. */\n split(cutter: Shape | {\n toShape(): Shape;\n }): [\n Shape,\n Shape\n ];\n /** Split by infinite plane. Returns [positive-side, negative-side]. */\n splitByPlane(normal: [\n number,\n number,\n number\n ], originOffset?: number): [\n Shape,\n Shape\n ];\n /** Keep the positive side of the plane and discard the opposite side. */\n trimByPlane(normal: [\n number,\n number,\n number\n ], originOffset?: number): Shape;\n /** Hollow out compile-covered boxes, cylinders, and straight extrudes.\n * `openFaces` names any subset of the base shape\'s labeled faces to leave open (no wall).\n */\n shell(thickness: number, opts?: {\n openFaces?: string[];\n }): Shape;\n /** Offset-thicken an exact open surface or shell into a solid. */\n thicken(thickness: number): Shape;\n /** Get the axis-aligned bounding box as { min: [x,y,z], max: [x,y,z] }. */\n boundingBox(): ShapeRuntimeBounds;\n /** Volume in mm cubed. */\n volume(): number;\n /** Surface area in mm squared. */\n surfaceArea(): number;\n /** True if the shape contains no geometry. */\n isEmpty(): boolean;\n /** Number of disconnected solid bodies in this shape. */\n numBodies(): number;\n /** Triangle count of the mesh representation. */\n numTri(): number;\n /** Extract triangle mesh for Three.js rendering */\n getMesh(): ShapeRuntimeMesh;\n /** Slice the runtime solid by a plane normal to local Z at the given offset. */\n slice(offset?: number): any;\n /** Orthographically project the runtime solid onto the local XY plane. */\n project(): any;\n /**\n * Position this shape relative to another using named 3D anchor points.\n *\n * Anchors are bounding-box-relative: \'center\', face centers (\'top\', \'front\', ...),\n * edge midpoints (\'top-front\', \'back-left\', ...), and corners (\'top-front-left\', ...).\n * Anchor word order is flexible: \'front-left\' and \'left-front\' are equivalent.\n * Named placement references (from withReferences) can also be used as anchors.\n */\n attachTo(target: ShapeAnchorTarget, targetAnchor: PlacementAnchorLike, selfAnchor?: PlacementAnchorLike, offset?: [\n number,\n number,\n number\n ]): Shape;\n /**\n * Place this shape on a face of a parent shape.\n *\n * Think of it like sticking a label on a box surface:\n * - `face` picks which surface (\'front\', \'back\', \'top\', etc.)\n * - `u, v` position within that face\'s 2D plane (from center)\n * - front/back: u = left/right (X), v = up/down (Z)\n * - left/right: u = forward/back (Y), v = up/down (Z)\n * - top/bottom: u = left/right (X), v = forward/back (Y)\n * - `protrude` = how far the child sticks out (positive = outward from face)\n */\n onFace(parent: ShapeAnchorTarget, face: "front" | "back" | "left" | "right" | "top" | "bottom", opts?: {\n u?: number;\n v?: number;\n protrude?: number;\n }): Shape;\n /**\n * Slide this shape along an axis until a labeled face is embedded in the target body.\n *\n * Position the shape roughly first (translate/rotate), then call seatInto to\n * auto-adjust the penetration depth. No manual coordinate math needed.\n *\n * ```js\n * // Wing root embeds into fuselage — adapts to any fuselage shape\n * wing.translate(0, wingY, 0).seatInto(fuselage, \'root\');\n *\n * // Sensor pod sits flush on fuselage surface\n * pod.translate(0, station, radius + 20).seatInto(fuselage, \'base\', { depth: \'flush\' });\n *\n * // Antenna with 3mm gasket standoff\n * mast.translate(0, station, radius + 50).seatInto(fuselage, \'mount\', { depth: \'flush\', gap: 3 });\n * ```\n *\n * @param target - The body to embed into (must be a watertight solid).\n * @param surface - Label of the face on this shape that connects (from `.faces(mapping)`).\n * @param options - Movement axis (defaults to face normal direction), depth mode, and gap control.\n * @returns A new Shape translated along the movement axis.\n */\n seatInto(target: Shape, surface: string, options?: SeatIntoOptions): Shape;\n /**\n * Slide this shape until a target\'s labeled face is fully covered (inside this shape).\n *\n * The inverse of `seatInto`: instead of embedding *your* face into the target,\n * you move until the *target\'s* face is embedded inside you.\n *\n * ```js\n * // Nacelle moves up until pylon\'s bottom face is inside the nacelle\n * nacelle.translate(rough).seatOver(pylon, \'bottom\');\n *\n * // Cap slides down over a post until post\'s top face is covered\n * cap.translate(rough).seatOver(post, \'top\');\n * ```\n *\n * @param target - The shape whose face you want to cover.\n * @param targetSurface - Label of the face on the TARGET shape to cover.\n * @param options - Movement axis (defaults to negative of target face normal), depth mode, and gap control.\n * @returns A new Shape translated along the movement axis.\n */\n seatOver(target: Shape, targetSurface: string, options?: SeatIntoOptions): Shape;\n /** Attach named connectors — attachment points that survive transforms and imports.\n * Connectors can be bare (position + orientation) or typed (with connectorType/gender for compatibility matching). */\n withConnectors(connectors: Record<string, ConnectorInput>): Shape;\n /** List all connector names on this shape. */\n connectorNames(): string[];\n /** Get all connectors of a given type. */\n connectorsByType(type: string): Array<{\n name: string;\n port: ConnectorDef;\n }>;\n /** Distance between two connector origins on this shape. */\n connectorDistance(nameA: string, nameB: string): number;\n /** Get measurements metadata from a connector. */\n connectorMeasurements(name: string): Record<string, number | string>;\n /**\n * Position this shape by matching connectors to a target.\n *\n * Alignment: with a single connector pair, the shape translates and rotates so the connector\n * origins coincide and the axes oppose (plug-in model); `up` pins the roll. With multiple pairs,\n * the connector origins define the rigid transform — still author meaningful `axis`/`up` values\n * so the same connectors remain useful for `connect()`, audits, and future matching.\n *\n * Overloads:\n * - Single pair: `matchTo(target, selfConn, targetConn, options?)`\n * - Dictionary (same target): `matchTo(target, { selfConn: targetConn, ... }, options?)`\n * - Multi-target: `matchTo([ [target1, selfConn1, targetConn1], ... ], options?)`\n */\n matchTo(targetOrPairs: Shape | MatchTarget | Array<[\n Shape | MatchTarget,\n string,\n string\n ]>, selfConnOrDict?: string | Record<string, string>, targetConnOrOptions?: string | MatchToOptions, maybeOptions?: MatchToOptions): Shape;\n}\ntype MatchTarget = Shape | {\n portNames?(): string[];\n};\n/**\n * Create a rectangular box. Centered on XY, base at Z=0.\n *\n * All ForgeCAD dimensions are millimeters; all angles are degrees (applies to every API, not just `box`).\n *\n * Extents:\n * - X: `[-width/2, width/2]`\n * - Y: `[-depth/2, depth/2]`\n * - Z: `[0, height]`\n *\n * This origin convention (centered on XY, base at Z=0) applies to all volumetric\n * primitives that have a base. There is no `center: true` option — recenter with\n * `.translate(0, 0, -height/2)` or `.placeReference(\'center\', [0, 0, 0])`.\n *\n * For named faces, build from a labeled sketch:\n * `rect(width, depth).labelEdges(\'s\', \'e\', \'n\', \'w\').extrude(height, { labels: { start: \'bottom\', end: \'top\' } })`.\n */\ndeclare function box$1(width: number, depth: number, height: number): Shape;\n/**\n * Create a cylinder or cone with named faces and edges.\n * Centered on XY, base at Z=0.\n *\n * Extents:\n * - X/Y: centered at the origin\n * - Z: `[0, height]`\n *\n * `radiusTop` defaults to `radius`. Set `radiusTop` smaller to taper the side,\n * or `0` for a pointy cone. Use `segments` to create regular prisms\n * (for example `6` for a hexagonal prism).\n *\n * Named faces: `top`, `bottom`, `side`\n * Named edges: `top-rim`, `bottom-rim`\n */\ndeclare function cylinder$1(height: number, radius: number, radiusTop?: number, segments?: number): Shape;\n/**\n * Create a sphere centered at the origin.\n *\n * Extents:\n * - X: `[-radius, radius]`\n * - Y: `[-radius, radius]`\n * - Z: `[-radius, radius]`\n *\n * Use `segments` for lower-poly approximations.\n */\ndeclare function sphere$1(radius: number, segments?: number): Shape;\n/**\n * Create a torus (donut shape) lying in the XY plane. Centered on all axes.\n *\n * Extents:\n * - X: `[-(majorRadius + minorRadius), +(majorRadius + minorRadius)]`\n * - Y: `[-(majorRadius + minorRadius), +(majorRadius + minorRadius)]`\n * - Z: `[-minorRadius, minorRadius]`\n *\n * The origin is the center of the ring.\n */\ndeclare function torus$1(majorRadius: number, minorRadius: number, segments?: number): Shape;\n/**\n * Combine shapes into a single solid (additive boolean).\n *\n * Accepts individual shapes, or an array of shapes. `union()` returns one\n * solid, so only the first operand\'s color is preserved in the result. Use\n * `group()` when you want separate child colors or identities.\n */\ndeclare function union(...inputs: ShapeOperandInput[]): Shape;\n/**\n * Subtract shapes from a base shape (subtractive boolean).\n *\n * The first shape is the base; all subsequent shapes are subtracted from it.\n * Accepts individual shapes, or an array of shapes.\n */\ndeclare function difference(...inputs: ShapeOperandInput[]): Shape;\n/**\n * Keep only the overlapping volume of the input shapes (intersection boolean).\n *\n * Requires at least two shapes. Accepts individual shapes, or an array.\n */\ndeclare function intersection(...inputs: ShapeOperandInput[]): Shape;\ntype Constraint3DType = "flush" | "align" | "parallel" | "faceDistance" | "concentric" | "axisParallel" | "pointCoincident" | "pointOnFace" | "pointOnAxis" | "angle" | "fixed";\ninterface BodyFeatureRef {\n bodyId: string;\n featureName: string;\n}\ninterface Constraint3D {\n id: string;\n type: Constraint3DType;\n refA: BodyFeatureRef;\n refB: BodyFeatureRef;\n value?: number;\n}\ndeclare class MateBuilder {\n readonly constraints: Constraint3D[];\n private nextId;\n private add;\n /** Constrain two faces so they stay flush. */\n flush(faceA: string, faceB: string): string;\n /** Constrain two faces so their normals align. */\n align(faceA: string, faceB: string): string;\n /** Constrain two faces so they remain parallel. */\n parallel(faceA: string, faceB: string): string;\n /** Constrain the distance between two faces. */\n faceDistance(faceA: string, faceB: string, distance: number): string;\n /** Constrain two axes to share the same center line. */\n concentric(axisA: string, axisB: string): string;\n /** Constrain two axes to remain parallel. */\n axisParallel(axisA: string, axisB: string): string;\n /** Constrain two points to coincide. */\n pointCoincident(pointA: string, pointB: string): string;\n /** Constrain a point to lie on a face. */\n pointOnFace(point: string, face: string): string;\n /** Constrain a point to lie on an axis. */\n pointOnAxis(point: string, axis: string): string;\n /** Constrain the angle between two faces. */\n angle(faceA: string, faceB: string, degrees: number): string;\n /** Total constraint equations. */\n get totalEquations(): number;\n}\ntype SimColliderMode = "convex" | "box" | "visual" | "none";\ninterface SimMaterialOptions {\n densityKgM3?: number;\n staticFriction?: number;\n dynamicFriction?: number;\n restitution?: number;\n}\ninterface SimMaterialDef extends SimMaterialOptions {\n kind: "material";\n name: string;\n}\ninterface SimColliderDef {\n kind: "collider";\n mode: SimColliderMode;\n reason?: string;\n}\ninterface SimContactDef {\n kind: "wheelSurface" | "gripperSurface";\n connectorName: string;\n}\ninterface SimBodyOptions {\n massKg?: number;\n densityKgM3?: number;\n material?: SimMaterialDef;\n collider?: SimColliderDef;\n contacts?: Record<string, SimContactDef>;\n}\ninterface SimBodyDef extends SimBodyOptions {\n kind: "body";\n}\ninterface SimPassiveDriveOptions {\n damping?: number;\n friction?: number;\n}\ninterface SimVelocityDriveOptions extends SimPassiveDriveOptions {\n maxTorqueNm: number;\n maxSpeedRpm: number;\n}\ntype SimDriveDef = ({\n kind: "passive";\n} & SimPassiveDriveOptions) | ({\n kind: "velocity";\n} & SimVelocityDriveOptions);\ntype SimProfileName = "Robot-Body-Runnable" | "Robot-Body-Isaac" | "Prop-Robotics-Physx";\ninterface SimProfileDef {\n kind: "profile";\n name: SimProfileName;\n}\ninterface SimDiffDriveControllerOptions {\n leftJoints: string[];\n rightJoints: string[];\n wheelSeparationMm: number;\n wheelRadiusMm: number;\n topic?: string;\n odomTopic?: string;\n tfTopic?: string;\n frameId?: string;\n odomFrameId?: string;\n maxLinearVelocity?: number;\n maxAngularVelocity?: number;\n linearAcceleration?: number;\n angularAcceleration?: number;\n}\ninterface SimDiffDriveControllerDef extends SimDiffDriveControllerOptions {\n kind: "diffDrive";\n}\ntype SimControllerDef = SimDiffDriveControllerDef;\ninterface SimAssemblySimulationOptions {\n profile: SimProfileDef;\n rootPart?: string;\n controllers?: SimControllerDef[];\n}\ninterface SimAssemblySimulationDef extends SimAssemblySimulationOptions {\n kind: "simulation";\n}\ndeclare function material(name: string, options?: SimMaterialOptions): SimMaterialDef;\ndeclare function body(options: SimBodyOptions): SimBodyDef;\ndeclare function passiveDrive(options?: SimPassiveDriveOptions): SimDriveDef;\ndeclare function velocityDrive(options: SimVelocityDriveOptions): SimDriveDef;\ndeclare function diffDrive(options: SimDiffDriveControllerOptions): SimDiffDriveControllerDef;\n/**\n * Simulation-readiness metadata constructors for robot and physics-aware assets.\n *\n * Use `Sim.body(...)` on `assembly().addPart(...)`, `Sim.drive.*(...)` on\n * `assembly().connect(...)`, and finish the root assembly with\n * `assembly.withSimulation(...)`.\n *\n * @category Assembly\n */\ndeclare const Sim: {\n /** Create a named physical material with density and contact coefficients for simulation export and checks. */\n readonly material: typeof material;\n /** Describe one assembly part as a physical body with mass/density, material, collider intent, and optional contact surfaces. */\n readonly body: typeof body;\n /** Collision-geometry intent constructors for physical parts. */\n readonly collider: {\n /** Use a generated collision mesh for the part. This is the default fast rigid-body collider for irregular parts. */\n readonly convexHull: () => SimColliderDef;\n /** Use the part bounding box as the collision geometry. This is fastest and works well for chassis and simple blocks. */\n readonly boundingBox: () => SimColliderDef;\n /** Use the visual mesh as collision geometry. This is exact but usually slower in physics engines. */\n readonly visualMesh: () => SimColliderDef;\n /** Disable collision for a part with an explicit reason, such as a sensor-only or decorative object. */\n readonly none: (reason: string) => SimColliderDef;\n };\n /** Joint-drive intent constructors for passive or powered assembly joints. */\n readonly drive: {\n /** Mark a joint as passive while preserving damping and friction metadata for simulation export. */\n readonly passive: typeof passiveDrive;\n /** Mark a revolute joint as velocity-driven with torque and speed limits. Speed is authored in rpm and exported as deg/s or rad/s as needed. */\n readonly velocity: typeof velocityDrive;\n };\n /** Contact-surface metadata over existing part connectors. */\n readonly contact: {\n /** Mark a connector as the wheel tread contact surface for offline checks and downstream simulation metadata. */\n readonly wheelSurface: (connectorName: string) => SimContactDef;\n /** Mark a connector as a gripper pad/contact surface for offline checks and downstream grasp-readiness metadata. */\n readonly gripperSurface: (connectorName: string) => SimContactDef;\n };\n /** Named validation/export profile constructors. */\n readonly profile: {\n /** SimReady-style profile for a robot body that should be runnable in a physics simulator. */\n readonly robotBodyRunnable: () => SimProfileDef;\n /** SimReady-style profile for robot bodies targeting Isaac Sim readiness. */\n readonly robotBodyIsaac: () => SimProfileDef;\n /** SimReady-style profile for robotics assets with PhysX-ready rigid bodies and colliders. */\n readonly roboticsAssetPhysx: () => SimProfileDef;\n };\n /** Standard controller metadata constructors for simulator package generation. */\n readonly controller: {\n /** Describe a differential-drive controller from left/right wheel joints and wheel dimensions. */\n readonly diffDrive: typeof diffDrive;\n };\n};\ninterface AssemblyFrameOptions {\n origin: [\n number,\n number,\n number\n ];\n axis: [\n number,\n number,\n number\n ];\n up: [\n number,\n number,\n number\n ];\n fixed?: boolean;\n metadata?: Record<string, unknown>;\n}\ninterface AssemblyFrameDef {\n name: string;\n origin: Vec3;\n axis: Vec3;\n up: Vec3;\n fixed: boolean;\n metadata?: Record<string, unknown>;\n}\ntype AssemblyFrameJointType = "fixed" | "revolute" | "prismatic";\ninterface AssemblyFixedFrameJointOptions {\n parent: string;\n child: string;\n metadata?: Record<string, unknown>;\n}\ninterface AssemblyMovingFrameJointOptions {\n parent: string;\n child: string;\n min?: number;\n max?: number;\n default?: number;\n unit?: string;\n control?: boolean;\n metadata?: Record<string, unknown>;\n}\ninterface AssemblyFrameJointDef {\n name: string;\n type: AssemblyFrameJointType;\n parent: string;\n child: string;\n rest: Transform;\n min?: number;\n max?: number;\n defaultValue: number;\n unit?: string;\n control: boolean;\n metadata?: Record<string, unknown>;\n}\ninterface AssemblyFrameEdgeOptions {\n name?: string;\n metadata?: Record<string, unknown>;\n}\ninterface AssemblyFrameEdgeDef {\n name: string;\n a: string;\n b: string;\n metadata?: Record<string, unknown>;\n}\ninterface SolvedAssemblyFrameEdgeDef extends AssemblyFrameEdgeDef {\n start: Vec3;\n end: Vec3;\n solvedLength: number;\n}\ninterface SolvedAssemblyFrameDef extends AssemblyFrameDef {\n transform: Transform;\n}\ntype AssemblyPart = Shape | ShapeGroup;\ntype JointType = "fixed" | "revolute" | "prismatic";\ntype JointState = Record<string, number | undefined>;\ntype AssemblyControlState = Record<string, number | undefined>;\ninterface PartMetadata {\n material?: string;\n process?: string;\n tolerance?: string;\n qty?: number;\n notes?: string;\n densityKgM3?: number;\n massKg?: number;\n /** Viewport organization tags applied to scene objects produced from this part. */\n tags?: string | readonly string[];\n [key: string]: unknown;\n}\ninterface PartOptions {\n transform?: TransformInput;\n metadata?: PartMetadata;\n sim?: SimBodyDef;\n mate?: AssemblyPartMateInput | AssemblyPartMateInput[];\n bindToFrame?: string;\n}\ninterface AssemblyLinkOptions {\n /** Initial world-space position of this link before kinematic constraints solve it. */\n at?: [\n number,\n number,\n number\n ];\n /** Keep the link locked at its authored `at` position during solves. */\n fixed?: boolean;\n /** User metadata carried through the kinematic graph for inspection and tooling. */\n metadata?: Record<string, unknown>;\n}\ninterface AssemblyKinematicControlOptions {\n min?: number;\n max?: number;\n default?: number;\n unit?: string;\n}\ninterface AssemblyKinematicLimitOptions {\n min?: number;\n max?: number;\n}\ninterface AssemblyEdgeBetweenLinksOptions {\n name?: string;\n length?: number | "lockCurrent" | "free";\n min?: number;\n max?: number;\n visualOnly?: boolean;\n control?: AssemblyKinematicControlOptions;\n metadata?: Record<string, unknown>;\n}\ninterface AssemblyAngleBetweenLinksOptions {\n name?: string;\n value?: number;\n min?: number;\n max?: number;\n control?: boolean | AssemblyKinematicControlOptions;\n limit?: AssemblyKinematicLimitOptions;\n metadata?: Record<string, unknown>;\n}\ninterface AssemblyAngleReferenceDef {\n kind: "worldDirection";\n direction: Vec3;\n}\ninterface AssemblyPartMateInput {\n /** Name of a connector declared on the part (via `withConnectors()`). */\n connector: string;\n /** Name of the link this connector\'s origin is pinned to. */\n toLink: string;\n /**\n * Optional second link to orient toward. When set, the part is rotated so the\n * connector\'s **axis** aims from `toLink` toward `aimLink`, posing an oriented\n * bone instead of only translating it. For full pose without relying on a\n * connector axis, declare a second mate (two connectors → two links).\n */\n aimLink?: string;\n}\ninterface JointOptions {\n frame?: TransformInput;\n origin?: [\n number,\n number,\n number\n ];\n axis?: Vec3;\n min?: number;\n max?: number;\n default?: number;\n unit?: string;\n effort?: number;\n velocity?: number;\n damping?: number;\n friction?: number;\n drive?: SimDriveDef;\n /** Connector refs that define this joint contract. Usually set by `connect()` / `match()`. */\n connectorRefs?: JointConnectorRefs;\n /**\n * Slave this joint to another joint: `value = ratio × source + offset`.\n *\n * Use for mechanisms with one physical DOF expressed through several joints —\n * a mirrored gripper jaw (`ratio: -1`), a gear pair, a drive crank turning\n * with its servo. A followed joint stops being an independent control: the\n * Motion view drives it from its source, `solve()` derives its value (a\n * direct state override is ignored with a warning), and limits still clamp\n * the derived value.\n */\n follows?: JointFollowOptions;\n}\ninterface JointFollowOptions {\n /** Name of the source joint that drives this one. */\n joint: string;\n /** Multiplier applied to the source joint value (default 1). */\n ratio?: number;\n /** Constant added after the ratio (default 0). */\n offset?: number;\n}\ninterface JointConnectorRefs {\n parent: string;\n child: string;\n parentAlign?: PortAlign;\n childAlign?: PortAlign;\n}\ninterface AssemblyAnimationOptions {\n /** Animation length in seconds (default chosen by the viewer). */\n duration?: number;\n /** Loop the animation (default false). */\n loop?: boolean;\n /** Interpolate continuously through keyframes instead of pausing on each. */\n continuous?: boolean;\n /** Keyframes of control values by joint/control name. `at` (0..1) or `ticks` control timing. */\n keyframes: JointViewAnimationInput["keyframes"];\n /** Make this the animation that plays when the model loads. */\n default?: boolean;\n}\ninterface JointCouplingTerm {\n joint: string;\n ratio?: number;\n}\ninterface JointCouplingOptions {\n terms: JointCouplingTerm[];\n offset?: number;\n}\ninterface GearRatioLike {\n jointRatio: number;\n /** Phase offset in degrees for legacy coupling export metadata. */\n phaseDeg?: number;\n}\ninterface GearCouplingOptions {\n ratio?: number;\n pair?: GearRatioLike;\n driverTeeth?: number;\n drivenTeeth?: number;\n mesh?: "external" | "internal" | "bevel" | "face";\n offset?: number;\n driverOrigin?: [\n number,\n number,\n number\n ];\n drivenOrigin?: [\n number,\n number,\n number\n ];\n}\ninterface PartRecord {\n name: string;\n part: AssemblyPart;\n base: Transform;\n metadata?: PartMetadata;\n sim?: SimBodyDef;\n mates: AssemblyPartMateInput[];\n bindToFrame?: string;\n}\ninterface JointCouplingTermRecord {\n joint: string;\n ratio: number;\n}\ninterface AssemblyPartDef {\n name: string;\n part: AssemblyPart;\n base: Transform;\n metadata?: PartMetadata;\n sim?: SimBodyDef;\n mates: AssemblyPartMateInput[];\n bindToFrame?: string;\n}\ninterface AssemblyJointSimulationDef {\n drive?: SimDriveDef;\n}\ninterface AssemblyLinkDef {\n name: string;\n at: Vec3;\n fixed: boolean;\n metadata?: Record<string, unknown>;\n}\ninterface AssemblyEdgeBetweenLinksDef {\n name: string;\n a: string;\n b: string;\n length: number | null;\n min?: number;\n max?: number;\n visualOnly: boolean;\n control?: AssemblyKinematicControlOptions;\n metadata?: Record<string, unknown>;\n}\ninterface AssemblyAngleBetweenLinksDef {\n name: string;\n a?: string;\n b: string;\n c: string;\n reference?: AssemblyAngleReferenceDef;\n target?: number;\n min?: number;\n max?: number;\n control?: AssemblyKinematicControlOptions;\n metadata?: Record<string, unknown>;\n}\ninterface AssemblyDerivedLinkDef {\n name: string;\n fromLink: string;\n towardLink: string;\n /** Signed: positive moves from `fromLink` toward `towardLink`, negative moves away. */\n distance: number;\n}\ninterface AssemblyKinematicGraphDef {\n links: AssemblyLinkDef[];\n edges: AssemblyEdgeBetweenLinksDef[];\n angles: AssemblyAngleBetweenLinksDef[];\n derivedLinks: AssemblyDerivedLinkDef[];\n}\ninterface SolvedAssemblyLinkDef {\n name: string;\n position: Vec3;\n fixed: boolean;\n gaugeFixed?: boolean;\n}\ninterface SolvedAssemblyEdgeDef {\n name: string;\n a: string;\n b: string;\n length: number | null;\n solvedLength: number;\n residual: number;\n visualOnly: boolean;\n}\ninterface SolvedAssemblyAngleDef {\n name: string;\n a?: string;\n b: string;\n c: string;\n reference?: AssemblyAngleReferenceDef;\n target?: number;\n solvedValue: number;\n residual: number;\n control?: AssemblyKinematicControlOptions;\n}\ninterface SolvedAssemblyDerivedLinkDef extends AssemblyDerivedLinkDef {\n position: Vec3;\n}\ninterface AssemblyFloatingComponentDef {\n links: string[];\n gaugeLink: string;\n}\ninterface SolvedAssemblyKinematics {\n links: SolvedAssemblyLinkDef[];\n edges: SolvedAssemblyEdgeDef[];\n angles: SolvedAssemblyAngleDef[];\n derivedLinks: SolvedAssemblyDerivedLinkDef[];\n frameEdges?: SolvedAssemblyFrameEdgeDef[];\n controls: Record<string, number>;\n floatingComponents: AssemblyFloatingComponentDef[];\n diagnostics: string[];\n maxResidual: number;\n converged: boolean;\n}\ninterface AssemblyJointDef {\n name: string;\n type: JointType;\n parent: string;\n child: string;\n frame: Transform;\n axis: Vec3;\n min?: number;\n max?: number;\n defaultValue: number;\n unit?: string;\n effort?: number;\n velocity?: number;\n damping?: number;\n friction?: number;\n sim?: AssemblyJointSimulationDef;\n connectorRefs?: JointConnectorRefs;\n}\ninterface AssemblyJointCouplingDef {\n joint: string;\n terms: JointCouplingTermRecord[];\n offset: number;\n}\ninterface AssemblyDefinition {\n name: string;\n sim?: SimAssemblySimulationDef;\n parts: AssemblyPartDef[];\n joints: AssemblyJointDef[];\n jointCouplings: AssemblyJointCouplingDef[];\n kinematics: AssemblyKinematicGraphDef;\n frames: AssemblyFrameDef[];\n frameJoints: AssemblyFrameJointDef[];\n frameEdges: AssemblyFrameEdgeDef[];\n}\ninterface BomRow {\n part: string;\n qty: number;\n material?: string;\n process?: string;\n tolerance?: string;\n notes?: string;\n metadata?: PartMetadata;\n}\ninterface CollisionOptions {\n parts?: string[];\n ignorePairs?: Array<[\n string,\n string\n ]>;\n minOverlapVolume?: number;\n}\ninterface CollisionFinding {\n partA: string;\n partB: string;\n overlapVolume: number;\n}\ninterface JointSweepFrame {\n value: number;\n collisions: CollisionFinding[];\n warnings: string[];\n}\n/**\n * Convert an array of BOM rows into a CSV string.\n *\n * **Details**\n *\n * Produces a CSV with columns: `part`, `qty`, `material`, `process`, `tolerance`, `notes`.\n * String values are quoted and internal double-quotes are escaped. Prefer calling\n * `solvedAssembly.bomCsv()` directly — this function is exposed for custom BOM processing.\n *\n * @param rows - BOM rows from `SolvedAssembly.bom()`\n * @returns CSV string with header row\n * @see {@link SolvedAssembly.bomCsv} for the all-in-one method\n * @softDeprecated solvedAssembly.bomCsv() — the method form covers BOM CSV export\n * @deprecated use solvedAssembly.bomCsv() — the method form covers BOM CSV export\n * @category Assembly\n */\ndeclare function bomToCsv(rows: BomRow[]): string;\ninterface MateMetadata {\n explodeHints: Record<string, {\n direction: Vec3;\n }>;\n dof: number;\n converged: boolean;\n}\n/**\n * The result of solving an assembly at a specific joint state.\n *\n * **Details**\n *\n * `SolvedAssembly` holds world-space transforms for every part at a given pose.\n * Top-level scripts can return a `SolvedAssembly` directly for display. Use\n * `toGroup()` when you specifically need a `ShapeGroup` for composition,\n * group-style transforms, or named-child lookup. Do not call `toGroup()` just\n * to make a solved assembly render. Use `getPart()` / `getTransform()` to\n * inspect individual parts programmatically.\n *\n * **Validation**\n *\n * Call `collisionReport()` to detect overlapping parts at this solved pose.\n *\n * **Example**\n *\n * ```ts\n * const solved = mech.solve({ shoulder: 45, elbow: -20 });\n * console.log("Collisions", solved.collisionReport());\n * return solved;\n * ```\n *\n * @category Assembly\n */\ndeclare class SolvedAssembly {\n readonly name: string;\n private readonly parts;\n private readonly transforms;\n private readonly jointValues;\n private readonly solveWarnings;\n private readonly _mateMetadata;\n private readonly _kinematics;\n private readonly _frames;\n private readonly _usedPortRefs;\n constructor(name: string, parts: Map<string, PartRecord>, transforms: Map<string, Transform>, jointValues: JointState, solveWarnings: string[], _mateMetadata?: MateMetadata | null, _kinematics?: SolvedAssemblyKinematics | null, _frames?: Map<string, SolvedAssemblyFrameDef>, usedPortRefs?: ReadonlySet<string>);\n /** Return any warnings generated during solve (clamped joints, unconverged mates, etc.). */\n warnings(): string[];\n /** Return a snapshot of resolved joint values (after clamping and coupling). */\n getJointState(): JointState;\n /**\n * Explode direction hints derived from mate constraints, or null if no mates.\n *\n * @softDeprecated explode hints derive automatically from connect()/match() joints — use explodeView()\n * @deprecated use explode hints derive automatically from connect()/match() joints — use explodeView()\n */\n get mateExplodeHints(): Record<string, {\n direction: Vec3;\n }> | null;\n /**\n * Remaining degrees of freedom after mate constraints, or null if no mates.\n *\n * @softDeprecated diagnostics for the legacy mate() solver — position parts with connect()/match() and inspect kinematics instead\n * @deprecated use diagnostics for the legacy mate() solver — position parts with connect()/match() and inspect kinematics instead\n */\n get mateDof(): number | null;\n /**\n * Whether the mate constraint solver converged, or null if no mates.\n *\n * @softDeprecated diagnostics for the legacy mate() solver — position parts with connect()/match() and inspect kinematics instead\n * @deprecated use diagnostics for the legacy mate() solver — position parts with connect()/match() and inspect kinematics instead\n */\n get mateConverged(): boolean | null;\n /** Solved assembly-native kinematic or frame-edge overlay data, or null when no rig overlay data was declared. */\n get kinematics(): SolvedAssemblyKinematics | null;\n /** Return the solved world position of a kinematic link. */\n getLinkPosition(linkName: string): Vec3;\n /** Return the solved world transform for a named rig frame. */\n getFrame(frameName: string): Transform;\n /** Return solved rig frames, including origin, axis, up, and transform. */\n get frames(): SolvedAssemblyFrameDef[];\n /**\n * Return the world-space `Transform` for the named part at the solved pose.\n *\n * @param partName - Name as registered with `addPart()`\n * @returns The world transform (position + orientation) of the part\n * @category Assembly\n */\n getTransform(partName: string): Transform;\n /**\n * Return the named part already positioned at its solved world transform.\n *\n * @param partName - Name as registered with `addPart()`\n * @returns The part (`Shape` or `ShapeGroup`) at its solved world position\n * @category Assembly\n */\n getPart(partName: string): AssemblyPart;\n /** @internal */\n _toSceneObjects(options?: {\n bakeTransforms?: boolean;\n }): Array<{\n name: string;\n shape?: Shape;\n group?: Array<{\n name: string;\n shape: Shape;\n tags?: string[];\n assemblyTransform?: Mat4;\n }>;\n metadata?: PartMetadata;\n assemblyTransform?: Mat4;\n }>;\n /**\n * Convert all solved parts into a `ShapeGroup` with named children.\n *\n * **Details**\n *\n * Each part becomes a named child in the group, already positioned at its\n * solved world transform. Use this only when you specifically need a\n * `ShapeGroup` for composition, `ShapeGroup` transforms, or named-child\n * access. Top-level scripts can return the `SolvedAssembly` directly; do not\n * call `toGroup()` just to make a solved assembly render.\n *\n * **Example**\n *\n * ```ts\n * const armGroup = mech.solve({ shoulder: 60 }).toGroup(); // only because we need rotateZ()\n * return armGroup.rotateZ(90);\n * ```\n *\n * @returns `ShapeGroup` with one child per part, each at its solved world position\n * @category Assembly\n */\n toGroup(): ShapeGroup;\n /**\n * Return an array of named scene objects for the viewport renderer.\n *\n * **Details**\n *\n * Each part becomes `{ name, shape }` or `{ name, group: [...] }` if the\n * part is a `ShapeGroup`. Top-level scripts should normally return the\n * `SolvedAssembly` directly. Use `toGroup()` when you need `ShapeGroup`\n * behavior; use this method only for advanced scene-graph control where you\n * need access to the flat per-part array with metadata.\n *\n * @returns Array of `{ name, shape?, group?, metadata? }` for each part\n * @category Assembly\n */\n toSceneObjects(): Array<{\n name: string;\n shape?: Shape;\n group?: Array<{\n name: string;\n shape: Shape;\n tags?: string[];\n }>;\n metadata?: PartMetadata;\n }>;\n /**\n * Backward-compatible alias for `toSceneObjects()`.\n *\n * @softDeprecated toSceneObjects() — same return shape; or return the SolvedAssembly directly\n * @deprecated use toSceneObjects() — same return shape; or return the SolvedAssembly directly\n */\n toScene(): Array<{\n name: string;\n shape?: Shape;\n group?: Array<{\n name: string;\n shape: Shape;\n tags?: string[];\n }>;\n metadata?: PartMetadata;\n }>;\n /**\n * Generate a bill of materials for all parts in the solved assembly.\n *\n * @returns Array of `BomRow` with part name, qty, and any metadata (material, process, etc.)\n * @see {@link bomCsv} for CSV-formatted output\n * @category Assembly\n */\n bom(): BomRow[];\n /**\n * Generate a bill of materials as a CSV string.\n *\n * @returns CSV-formatted BOM with columns: part, qty, material, process, tolerance, notes\n * @see {@link bom} for the structured row array\n * @category Assembly\n */\n bomCsv(): string;\n /**\n * Detect overlapping (colliding) part pairs in this solved pose.\n *\n * **Details**\n *\n * Computes boolean intersections between all part pairs and returns findings\n * where the overlap volume exceeds `minOverlapVolume` (default 0.1 mm³).\n *\n * **Example**\n *\n * ```ts\n * const solved = mech.solve({ shoulder: 35, elbow: 60 });\n * console.log("Collisions", solved.collisionReport());\n * ```\n *\n * @param options - Filter by `parts`, ignore specific `ignorePairs`, or set `minOverlapVolume`\n * @returns Array of `{ partA, partB, overlapVolume }` for each interference found\n * @category Assembly\n */\n collisionReport(options?: CollisionOptions): CollisionFinding[];\n /**\n * Compute the minimum gap (clearance) between two parts in this solved pose.\n *\n * **Details**\n *\n * Returns `0` if the parts are touching or overlapping. Manifold-backed parts use\n * the exact Manifold gap query. SDF-backed parts use a mesh-derived sampled gap.\n * `searchLength` bounds the Manifold search radius in mm — increase it for widely\n * separated Manifold parts.\n *\n * @param partA - Name of the first part\n * @param partB - Name of the second part\n * @param searchLength - Maximum search radius in mm (default 10)\n * @returns Minimum gap in mm; `0` means touching or interfering\n * @category Assembly\n */\n minClearance(partA: string, partB: string, searchLength?: number): number;\n}\ninterface ConnectOptions {\n as?: string;\n type?: JointType;\n /** Lower joint-slider limit; solve clamps to it with a warning. Not a physical stop — enforce real travel limits with stop geometry. */\n min?: number;\n /** Upper joint-slider limit; same semantics as `min`. */\n max?: number;\n default?: number;\n unit?: string;\n /**\n * @deprecated Connectors now always meet face-to-face (anti-parallel axes).\n * This parameter is ignored. If your connectors produce wrong orientation,\n * fix the connector axis directions instead of using flip.\n */\n flip?: boolean;\n /** Which point on the parent connector to align: \'start\', \'middle\' (default), or \'end\'. */\n parentAlign?: PortAlign;\n /** Which point on the child connector to align: \'start\', \'middle\' (default), or \'end\'. */\n childAlign?: PortAlign;\n /** Shorthand: set both parentAlign and childAlign at once. */\n align?: PortAlign;\n /** Slave this joint to another joint: `value = ratio × source + offset` (e.g. a mirrored jaw with `ratio: -1`). */\n follows?: JointFollowOptions;\n effort?: number;\n velocity?: number;\n damping?: number;\n friction?: number;\n drive?: SimDriveDef;\n}\ninterface ToJointsViewOptions {\n defaults?: Record<string, number>;\n overrides?: Record<string, Partial<JointViewInput>>;\n animations?: JointViewAnimationInput[];\n couplings?: JointViewCouplingInput[];\n defaultAnimation?: string;\n enabled?: boolean;\n}\n/**\n * Container for a kinematic mechanism made up of links, relationships, and parts.\n * See {@link assembly} for the link-graph vs connector-frame decision rules.\n *\n * **Details**\n *\n * Returning an unsolved `Assembly` keeps the graph available to the runtime;\n * return `mech.solve({ theta: 60 })` for a fixed pose instead.\n *\n * **Return types**\n *\n * | Return value | Standalone | `require()` result type |\n * |---|---|---|\n * | `Assembly` (unsolved) | yes | `ImportedAssembly` |\n * | `SolvedAssembly` | yes | `SolvedAssembly` |\n *\n * @category Assembly\n */\ndeclare class Assembly {\n readonly name: string;\n private readonly parts;\n private readonly joints;\n private readonly jointCouplings;\n private readonly frames;\n private readonly frameJoints;\n private readonly frameEdges;\n private readonly links;\n private readonly linkEdges;\n private readonly linkAngles;\n private readonly derivedLinks;\n private readonly _mateFns;\n private readonly viewAnimations;\n private _defaultViewAnimation;\n private _refs;\n private _simulation?;\n private readonly _portsByPart;\n private readonly _usedPortRefs;\n private _connectCounter;\n private _frameEdgeCounter;\n private _linkEdgeCounter;\n private _linkAngleCounter;\n constructor(name?: string);\n private _fork;\n /** Connector refs (e.g. "PartName.connectorName") consumed by connect/match calls. */\n get usedConnectorRefs(): ReadonlySet<string>;\n /**\n * Compatibility alias for `usedConnectorRefs`.\n *\n * @softDeprecated usedConnectorRefs — same ReadonlySet\n * @deprecated use usedConnectorRefs — same ReadonlySet\n */\n get usedPortRefs(): ReadonlySet<string>;\n /**\n * Register mate constraints between parts.\n * Constraints are solved during `solve()` to derive part positions and explode hints.\n * Part references use "partName:featureName" format.\n *\n * @softDeprecated connect("Parent.connectorA", "Child.connectorB") / match("Child.connectorB", "Parent.connectorA") — connector-frame joints replace string-ref mates\n * @deprecated use connect("Parent.connectorA", "Child.connectorB") / match("Child.connectorB", "Parent.connectorA") — connector-frame joints replace string-ref mates\n */\n mate(fn: (m: MateBuilder) => void): Assembly;\n /**\n * Attach named placement reference points to this assembly.\n * These are surfaced automatically on the ImportedAssembly when this file is\n * imported via require(), so consumers can use placeReference() without\n * re-declaring them.\n * Returns a new Assembly — does not mutate.\n */\n withReferences(refs: Pick<PlacementReferenceInput, "points">): Assembly;\n /** @internal — used by require() to seed ImportedAssembly refs. */\n getReferences(): PlacementReferences;\n /**\n * Attach the root simulation contract for this assembly.\n *\n * Use this after adding physical parts and joints. Robot-body profiles require\n * `rootPart`; asset profiles can describe one-part or multi-part physical assets.\n * URDF/SDF/MJCF/USD exporters and `forgecad check simready` read this\n * contract directly from the returned assembly.\n *\n * @category Assembly\n */\n withSimulation(options: SimAssemblySimulationOptions): Assembly;\n /**\n * Attach named connectors to a specific part or the assembly as a whole.\n *\n * **Details**\n *\n * Connectors declared this way are in the part\'s local coordinate system.\n * They are captured automatically if the incoming `Shape` already has\n * connectors via `shape.withConnectors(...)`, but you can also add or\n * override connectors after the fact with this method.\n *\n * Use the single-argument overload to attach assembly-level connectors —\n * these are exposed when this assembly is imported as a sub-assembly.\n *\n * @param partName - Part name (must already be added via `addPart`)\n * @param connectors - Map of connector name → `ConnectorInput`\n * @returns `this` for chaining\n * @see {@link connect} for using connectors to create joints\n * @category Connectors\n */\n withConnectors(partName: string, connectors: Record<string, ConnectorInput>): Assembly;\n withConnectors(connectors: Record<string, ConnectorInput>): Assembly;\n /**\n * Backward-compatible alias for `withConnectors()`.\n *\n * @deprecated Use `withConnectors()` instead.\n */\n withPorts(partNameOrPorts: string | Record<string, PortInput>, maybePorts?: Record<string, PortInput>): Assembly;\n /** Get connectors declared on a part in part-local space. */\n getConnectors(partName: string): ConnectorMap;\n /**\n * Backward-compatible alias for `getConnectors()`.\n *\n * @deprecated Use `getConnectors()` instead.\n */\n getPorts(partName: string): PortMap;\n /** @internal — set already-normalized connectors directly (used by mergeInto). */\n _setPortsDirect(partName: string, ports: PortMap): void;\n /** @internal — get all connector maps, keyed by part name. */\n getAllPorts(): Map<string, PortMap>;\n /**\n * Parse a "PartName.connectorName" reference and return the resolved connector.\n * Throws descriptive errors if the part or connector doesn\'t exist.\n */\n getConnector(ref: string): {\n partName: string;\n connectorName: string;\n connector: ConnectorDef;\n };\n /**\n * Compatibility alias for `getConnector()`.\n *\n * @softDeprecated getConnector(ref) — same "PartName.connectorName" ref; fields are partName/connectorName/connector\n * @deprecated use getConnector(ref) — same "PartName.connectorName" ref; fields are partName/connectorName/connector\n */\n getPort(ref: string): {\n partName: string;\n portName: string;\n port: PortDef;\n };\n /**\n * Add a named rig frame to the assembly.\n *\n * A frame is a solved pose: `origin` plus orientation. `axis` is the frame\'s\n * primary direction and `up` fixes roll around that axis. Use frames for\n * robot links, joint axes, and parts that must carry orientation. Use\n * `link()` for solved points in distance/angle graphs.\n *\n * @category Assembly\n */\n frame(name: string, options: AssemblyFrameOptions): Assembly;\n private resolveFrameJointEndpoint;\n private addFrameJoint;\n /**\n * Rigidly attach a child rig frame to a parent rig frame.\n *\n * Fixed joints carry frame hierarchy but do not expose a Motion control.\n *\n * @category Assembly\n */\n fixedJoint(name: string, options: AssemblyFixedFrameJointOptions): Assembly;\n /**\n * Add a revolute rig-frame joint.\n *\n * The child frame rotates around the parent frame\'s `axis` direction. Moving\n * frame joints appear in Motion by default; pass `control: false` to keep the\n * joint solved at its default value without showing a Motion control.\n *\n * @category Assembly\n */\n revoluteJoint(name: string, options: AssemblyMovingFrameJointOptions): Assembly;\n /**\n * Add a prismatic rig-frame joint.\n *\n * The child frame translates along the parent frame\'s `axis` direction. Moving\n * frame joints appear in Motion by default; pass `control: false` to keep the\n * joint solved at its default value without showing a Motion control.\n *\n * @category Assembly\n */\n prismaticJoint(name: string, options: AssemblyMovingFrameJointOptions): Assembly;\n /**\n * Add a visual skeleton edge between two rig frame origins.\n *\n * Frame edges follow the solved frame poses produced by `fixedJoint()`,\n * `revoluteJoint()`, and `prismaticJoint()`. They do not add constraints,\n * degrees of freedom, parts, or geometry; use them to make a frame-only rig\n * readable in the Motion/rig inspection overlay.\n *\n * @category Assembly\n */\n edgeBetweenFrames(a: string, b: string, options?: AssemblyFrameEdgeOptions): Assembly;\n /**\n * Add a named kinematic link to the assembly graph.\n *\n * Links are assembly-native solved points. They can exist before any geometry\n * is attached, can be displayed by the viewport, and are solved by\n * link/edge/angle constraints.\n *\n * A link is not a rigid-body frame. It has a world position but no orientation\n * basis. Use `connect()` when a physical part must inherit a connector frame\n * and rotate about a real hinge/slider axis.\n *\n * @category Assembly\n */\n link(name: string, options?: AssemblyLinkOptions): Assembly;\n /**\n * Add a relationship edge between two kinematic links.\n *\n * By default the edge captures the authored distance between links as a\n * structural length. Pass `{ length: \'free\' }` or `{ visualOnly: true }` for\n * a non-structural overlay edge.\n *\n * @category Assembly\n */\n edgeBetweenLinks(a: string, b: string, options?: AssemblyEdgeBetweenLinksOptions): Assembly;\n /**\n * Add an angle relationship among three kinematic links.\n *\n * The middle link is the vertex. When `control` is set, `solve(state)` reads\n * the control value from `state[name]` and solves dependent links from that\n * driven angle.\n *\n * @category Assembly\n */\n addAngleBetweenLinks(a: string, b: string, c: string, options?: AssemblyAngleBetweenLinksOptions): Assembly;\n /**\n * Add an absolute angle relationship from a world direction to a link segment.\n *\n * The first link is the vertex/pivot and the second link is the moving point.\n * A value of `0` places `fromLink -> toLink` along `direction` in the\n * mechanism plane; positive angles rotate counter-clockwise in that plane.\n *\n * Use `Points.polar(1, angleDeg)` when the reference direction is planar and\n * angle-based instead of axis-aligned.\n *\n * @category Assembly\n */\n addAngleBetweenLinkSegmentAndWorldDirection(fromLink: string, toLink: string, direction: Vec3, options?: AssemblyAngleBetweenLinksOptions): Assembly;\n /**\n * @deprecated Use `addAngleBetweenLinkSegmentAndWorldDirection(fromLink, toLink, [1, 0, 0], options)`.\n * @skillSuppress Compatibility-only renamed API. Use `addAngleBetweenLinkSegmentAndWorldDirection()` instead.\n */\n addAngleOfLinkSegmentFromXAxis(fromLink: string, toLink: string, options?: AssemblyAngleBetweenLinksOptions): Assembly;\n /**\n * @deprecated Use `addAngleBetweenLinkSegmentAndWorldDirection(fromLink, toLink, [0, 1, 0], options)`.\n * @skillSuppress Compatibility-only renamed API. Use `addAngleBetweenLinkSegmentAndWorldDirection()` instead.\n */\n addAngleOfLinkSegmentFromYAxis(fromLink: string, toLink: string, options?: AssemblyAngleBetweenLinksOptions): Assembly;\n private assertNotDerivedStructuralLink;\n private assertCanBecomeDerivedLink;\n /**\n * Create a derived link on the line through `fromLink` and `towardLink`, at a\n * **signed** distance from `fromLink`.\n *\n * **Sign convention** (read this first):\n * - `distance > 0` — the point moves from `fromLink` **toward** `towardLink`.\n * - `distance < 0` — the point moves from `fromLink` **away from** `towardLink`\n * (the coupler-extension case, e.g. the Chebyshev lambda linkage\'s trace\n * point beyond the rocker joint).\n * - `distance` greater than the solved edge length places the point **beyond**\n * `towardLink`, still on the same line.\n *\n * Derived links are trace/reference points. They are recomputed after the\n * primary link solve and cannot participate in structural edges or angle\n * constraints. Because the distance is one signed parameter, a `param()`-driven\n * value can sweep continuously from extension (negative) through `fromLink`\n * (zero) to beyond `towardLink` (large positive).\n *\n * **Example**\n *\n * ```ts\n * // Chebyshev lambda linkage: trace point C3 extends beyond C2, away from C1.\n * mech.linkAlong(\'C3\', \'C2\', \'C1\', -2.5 * a);\n * // Midpoint-style reference 30 mm from A toward B:\n * mech.linkAlong(\'probe\', \'A\', \'B\', 30);\n * ```\n *\n * @param name - Name for the derived link (created if it does not exist)\n * @param fromLink - Link the distance is measured from\n * @param towardLink - Link that defines the positive direction\n * @param distance - Signed distance in mm (negative extends away from `towardLink`)\n * @returns `this` for chaining\n * @category Assembly\n */\n linkAlong(name: string, fromLink: string, towardLink: string, distance: number): Assembly;\n /**\n * Create a derived link at a fixed distance from `fromLink` toward `towardLink`.\n *\n * Derived links are trace/reference points. They are recomputed after the\n * primary link solve and cannot participate in structural edges or angle\n * constraints.\n *\n * @softDeprecated linkAlong(name, fromLink, towardLink, distance) — same arguments; positive distance moves toward towardLink, negative moves away\n * @deprecated use linkAlong(name, fromLink, towardLink, distance) — same arguments; positive distance moves toward towardLink, negative moves away\n * @category Assembly\n */\n linkToward(name: string, fromLink: string, towardLink: string, distance: number): Assembly;\n /**\n * Create a derived link at a fixed distance from `fromLink` away from `awayFromLink`.\n *\n * Use this for coupler trace/extension points such as the Chebyshev lambda\n * linkage\'s point beyond the rocker joint.\n *\n * @softDeprecated linkAlong(name, fromLink, awayFromLink, -distance) — negate the distance: negative values extend away from the reference link\n * @deprecated use linkAlong(name, fromLink, awayFromLink, -distance) — negate the distance: negative values extend away from the reference link\n * @category Assembly\n */\n linkAwayFrom(name: string, fromLink: string, awayFromLink: string, distance: number): Assembly;\n private normalizeAngleOptions;\n /** Return the assembly-native kinematic graph definition. */\n describeKinematics(): AssemblyKinematicGraphDef;\n /**\n * @deprecated `addFrame()` has been removed. Use `frame()` for rig frames, or\n * `addPart(name, group())` for an empty placeholder part.\n * @internal\n */\n addFrame(_name: string, _options?: PartOptions): Assembly;\n /**\n * Add a named part to the assembly.\n *\n * **Details**\n *\n * Connectors declared on the part (via `withConnectors()`) are captured automatically.\n * Parts are positioned at world origin by default unless a `transform` is\n * provided in `options`. For root parts (no incoming joint), `transform` is\n * their final world position.\n *\n * `options.mate` is for point-link attachments. During `solve()`, ForgeCAD\n * translates the part so the named connector origin lands on the solved link\n * position. The part keeps its existing orientation; connector `axis` and `up`\n * are not used for link mating. Use this for markers, sensors, labels, and\n * other geometry that should ride on a solved point. Use `connect()` for\n * oriented physical parts such as limbs, levers, hinges, and wheels.\n *\n * When a part is a `ShapeGroup`, name the group children explicitly to get\n * readable viewport labels (e.g. `"Base Assembly.Body"` instead of\n * `"Base Assembly.1"`):\n *\n * ```ts\n * const housing = group(\n * { name: "Body", shape: body },\n * { name: "Lid", shape: lid },\n * );\n * assembly.addPart("Base Assembly", housing);\n * ```\n *\n * @param name - Unique part name (must not already exist)\n * @param part - The `Shape` or `ShapeGroup` geometry\n * @param options - Optional `{ transform, metadata }` (material, process, qty, tags, etc.)\n * @returns `this` for chaining\n * @category Assembly\n */\n addPart(name: string, part: AssemblyPart, options?: PartOptions): Assembly;\n /**\n * Add a kinematic joint between a parent and child part.\n *\n * **Details**\n *\n * `frame` is a transform from the **parent part frame** to the **joint frame\n * at zero state**. The child\'s world position is computed as:\n *\n * ```\n * childWorld = parentWorld × frame × motion(value) × childBase\n * ```\n *\n * For revolute joints `value` is in degrees; for prismatic joints `value` is\n * in mm.\n *\n * @param name - Unique joint name\n * @param type - `\'revolute\'`, `\'prismatic\'`, or `\'fixed\'`\n * @param parent - Name of the parent part (already added via `addPart`)\n * @param child - Name of the child part (already added via `addPart`)\n * @param options - Frame, axis, limits, effort, velocity, damping, friction\n * @returns `this` for chaining\n * @category Joints\n * @internal\n */\n addJoint(name: string, type: JointType, parent: string, child: string, options?: JointOptions): Assembly;\n /**\n * Shorthand for `addJoint(name, \'revolute\', parent, child, options)`.\n *\n * `min`/`max` bound the editor\'s joint slider and clamp solve state (with a\n * warning) — they are not physical stops. Enforce real travel limits with\n * stop geometry.\n *\n * @param name - Unique joint name\n * @param parent - Parent part name\n * @param child - Child part name\n * @param options - Frame, axis, min/max degrees, effort, velocity, damping, friction\n * @returns `this` for chaining\n * @category Joints\n * @internal\n */\n addRevolute(name: string, parent: string, child: string, options?: JointOptions): Assembly;\n /**\n * Shorthand for `addJoint(name, \'prismatic\', parent, child, options)`.\n *\n * @param name - Unique joint name\n * @param parent - Parent part name\n * @param child - Child part name\n * @param options - Frame, axis, min/max mm, effort, velocity, damping, friction\n * @returns `this` for chaining\n * @category Joints\n * @internal\n */\n addPrismatic(name: string, parent: string, child: string, options?: JointOptions): Assembly;\n /**\n * Shorthand for `addJoint(name, \'fixed\', parent, child, options)`.\n *\n * Fixed joints rigidly attach a child part to its parent at `frame` with no motion.\n * Before calling `mergeInto()`, use `addFixed()` to collapse multiple root parts\n * into a single root.\n *\n * @param name - Unique joint name\n * @param parent - Parent part name\n * @param child - Child part name\n * @param options - Optional frame transform for placement offset\n * @returns `this` for chaining\n * @category Joints\n * @internal\n */\n addFixed(name: string, parent: string, child: string, options?: JointOptions): Assembly;\n /**\n * Connect two parts by aligning their declared connectors, automatically computing frame and axis.\n *\n * **Details**\n *\n * Connector refs use `"PartName.connectorName"`. The child connector origin\n * lands exactly on the parent connector origin; joint frame and axis are\n * derived from the connector geometry — no manual `frame`/`axis` math.\n *\n * Frame semantics: `origin` is the pivot/contact point, `axis` the hinge or\n * slide direction, `up` locks the part\'s zero-state twist. Omitted `up` gets a\n * deterministic perpendicular — provide `up` whenever rest orientation matters.\n * (`addPart(..., { mate })` translates only; see `addPart`.)\n *\n * **Face-to-face:** each connector\'s axis points outward from its part;\n * mating makes the axes anti-parallel, like a plug meeting a socket (same\n * convention as `matchTo()`).\n *\n * **Revolute sign:** a positive joint value follows the right-hand rule about\n * the **child** connector\'s placed axis. Because face-to-face mating makes\n * the axes anti-parallel, that is the *left*-hand rule about the parent\n * connector\'s outward axis — if `+30` swings the opposite way you expected,\n * you predicted from the parent\'s axis. `forgecad debug assembly` prints each\n * joint\'s resolved world axis.\n *\n * **Mirrored revolute axes:** because of the right-hand rule, a mirrored\n * hinge axis (`[1, 0, 0]` vs `[-1, 0, 0]`) rotates oppositely for the\n * same `+theta`: negate the mirrored side\'s value and mirror limits as\n * `[min, max] -> [-max, -min]`. Prismatic joints have no handedness flip. Use\n * an explicit per-side sign mapping (or side-neutral link controls) for\n * bilateral mechanisms.\n *\n * Joint type defaults to the connector\'s `kind`. For `start`/`end` connectors,\n * `align` / `parentAlign` / `childAlign` (`\'start\' | \'middle\' | \'end\'`) choose\n * which point meets.\n *\n * **Example**\n *\n * ```ts\n * const frame = box(100, 10, 80).withConnectors({\n * hinge: connector("hinge", { origin: [0, 0, 40], axis: [0, 0, 1], up: [1, 0, 0] }),\n * });\n * const door = box(60, 4, 80).withConnectors({\n * hinge: connector("hinge", { origin: [0, 0, 40], axis: [0, 0, -1], up: [1, 0, 0] }),\n * });\n * assembly("Door").addPart("Frame", frame).addPart("Door", door)\n * .connect("Frame.hinge", "Door.hinge", { as: "swing", min: 0, max: 110 });\n * ```\n *\n * @param parentConnectorRef - `"PartName.connectorName"` on the parent side\n * @param childConnectorRef - `"PartName.connectorName"` on the child side\n * @param options - `as` (joint name), `type`, limits, alignment — see `ConnectOptions`\n * @returns `this` for chaining\n * @see {@link match} for typed connector matching with gender/type validation\n * @category Connectors\n */\n connect(parentConnectorRef: string, childConnectorRef: string, options?: ConnectOptions): Assembly;\n /**\n * Auto-create a joint by matching typed connectors between two parts.\n *\n * **Details**\n *\n * Connectors can carry a `connectorType` string and a `gender`\n * (`\'male\'`, `\'female\'`, or `\'neutral\'`). `match()` validates type and gender\n * compatibility (use `{ force: true }` to skip validation) and creates the\n * joint automatically from the connector\'s `kind` metadata.\n *\n * The `pairs` map is `{ childConnector: parentConnector }`. The first pair\n * drives joint creation; additional pairs are validated but do not create\n * additional joints (they constrain the same rigid connection).\n *\n * Define connectors on shapes with `shape.withConnectors(...)`:\n *\n * ```ts\n * const door = doorShape.withConnectors({\n * hinge_top: connector.male("hinge", { origin: [0, 0, 90], axis: [0, 0, 1] }),\n * hinge_bottom: connector.male("hinge", { origin: [0, 0, 10], axis: [0, 0, 1] }),\n * });\n * ```\n *\n * Then match in the assembly:\n *\n * ```ts\n * const mech = assembly("Door")\n * .addPart("Frame", frame)\n * .addPart("Door", door)\n * .match("Door", "Frame", { hinge_top: "hinge_top", hinge_bottom: "hinge_bottom" });\n * // Matching connectors computes the placement relationship automatically.\n * ```\n *\n * @param childPartName - Part name of the child\n * @param parentPartName - Part name of the parent\n * @param pairs - Map of `{ childConnectorName: parentConnectorName }`\n * @param options - `force` (skip gender/type validation), `as` (joint name override)\n * @returns `this` for chaining\n * @see {@link connect} for connector-based connections without type/gender validation\n * @category Connectors\n */\n match(childPartName: string, parentPartName: string, pairs: Record<string, string>, options?: MatchToOptions & {\n as?: string;\n }): Assembly;\n /**\n * @deprecated `addJointCoupling()` has been removed from the modeling API.\n * Model mechanism behavior with assembly links, `edgeBetweenLinks()`, and\n * controlled `addAngleBetweenLinks()` constraints instead.\n * @internal\n */\n addJointCoupling(_jointName: string, _options: JointCouplingOptions): Assembly;\n /**\n * @deprecated `addGearCoupling()` has been removed from the modeling API.\n * Gear behavior should be expressed by the assembly kinematic graph, not by\n * separate joint-mimic sugar.\n * @internal\n */\n addGearCoupling(_drivenJointName: string, _driverJointName: string, _options?: GearCouplingOptions): Assembly;\n private solveFrames;\n private solveFrameEdges;\n private attachFrameEdgesToKinematics;\n private solveKinematicLinks;\n private buildKinematicWorkplane;\n private solveKinematicLinksPlanar;\n private evaluateDerivedLinks;\n private solveKinematicLinksLegacy;\n /**\n * Solve the assembly at the given control state and return positioned parts.\n *\n * **Details**\n *\n * Solves assembly-native kinematic links first. Controlled\n * `addAngleBetweenLinks()` relationships read values from `state` by name,\n * clamp to their declared limits, and expose the solved graph on\n * `SolvedAssembly.kinematics`. Angles solve in the plane of their three\n * authored link positions, so a limb that swings out of the `z = 0` plane\n * poses correctly; structural edges hold their bone lengths so a fully\n * angle-driven serial chain follows forward kinematics.\n *\n * Connector mates declared on `addPart(..., { mate })` attach geometry to\n * solved links while preserving part and connector identity:\n * - one mate **positions** the connector origin on its link;\n * - a mate with `aimLink` (or a second mate to another link) also\n * **orients** the part, rotating an oriented bone to span its links rather\n * than only translating it;\n * - a third mate **pins the roll** about the bone axis (full frame), e.g. a\n * bore or clevis that must face a specific way.\n *\n * Connector-frame joints created by `connect()` / `match()` are also\n * evaluated; their values are read from `state` by joint name and clamped to\n * joint limits.\n *\n * **Example**\n *\n * ```ts\n * return mech.solve({ theta: 45 });\n * ```\n *\n * @param state - Map of control or legacy joint name → value\n * @returns `SolvedAssembly` with all parts at their computed world positions\n * @category Assembly\n */\n solve(state?: JointState): SolvedAssembly;\n private descendantPartNames;\n private jointDrivesCoupling;\n private sweepCollisionOptions;\n /**\n * @deprecated `sweepJoint()` has been removed from the modeling API. Motion\n * sweeps are diagnostics and will be exposed from CLI/debug tooling instead.\n * @internal\n */\n sweepJoint(_jointName: string, _from: number, _to: number, _steps: number, _baseState?: JointState, _collisionOptions?: CollisionOptions): JointSweepFrame[];\n /**\n * Internal diagnostic sweep used by assembly audits until CLI sweep tooling\n * owns this workflow.\n *\n * @internal\n */\n _sweepJointForDiagnostics(jointName: string, from: number, to: number, steps: number, baseState?: JointState, collisionOptions?: CollisionOptions): JointSweepFrame[];\n /**\n * Build the control view used by returned assemblies.\n *\n * @internal\n */\n _collectAssemblyControlsView(options?: ToJointsViewOptions, state?: JointState): CollectedJointsView;\n private buildJointsViewOptions;\n /**\n * Register a named keyframe animation for this assembly\'s Motion view.\n *\n * **Details**\n *\n * Works with the returned-assembly controls path: return the unsolved\n * `Assembly` and the animation appears in the Motion tab alongside the\n * solver-backed joint controls. Keyframes hold control values by joint\n * name; joints declared with `follows` are derived automatically and must\n * not appear in keyframes.\n *\n * **Example**\n *\n * ```ts\n * robot.addAnimation("Pick and place", {\n * duration: 12,\n * loop: true,\n * keyframes: [\n * { values: { J1: 0, J2: -90 } },\n * { values: { J1: 45, J2: -30 } },\n * { values: { J1: 0, J2: -90 } },\n * ],\n * });\n * return robot;\n * ```\n *\n * @param name - Animation name shown in the Motion tab\n * @param options - `duration` (seconds), `loop`, `continuous`, `keyframes`, `default` (make this the animation that plays on load)\n * @returns `this` for chaining\n * @category Assembly\n */\n addAnimation(name: string, options: AssemblyAnimationOptions): Assembly;\n /**\n * Deprecated adapter that derives viewport-only FK controls from the assembly graph.\n *\n * **Details**\n *\n * Prefer `return mech;` — the runtime exposes returned-assembly controls\n * automatically and re-runs `solve(state)` on every change, which handles\n * closed loops and avoids stacking a viewport transform on solved geometry.\n * This adapter remains only for legacy scripts that want the old\n * viewport-only FK behavior; see {@link jointsView} for its caveats.\n *\n * @softDeprecated return the Assembly directly (return mech) — solver-backed controls appear automatically; use addAnimation(name, { keyframes }) for animation clips\n * @deprecated use return the Assembly directly (return mech) — solver-backed controls appear automatically; use addAnimation(name, { keyframes }) for animation clips\n * @skillSuppress Compatibility-only viewport FK adapter. Prefer returning `Assembly` directly so controls move through the solver-backed link/edge kinematics model.\n * @param options - `defaults`, `animations`, `couplings`, `overrides`, `enabled`\n * @category Assembly\n */\n toJointsView(options?: ToJointsViewOptions): void;\n /** Return the serializable assembly definition used by solve/inspect pipelines. */\n describe(): AssemblyDefinition;\n}\n/**\n * Create an assembly container with named parts, connectors, and kinematic links.\n *\n * **Use this from iteration 1 for any model with moving parts.** Do not build one\n * static pose and retrofit motion later.\n *\n * **Details**\n *\n * Two motion tools:\n *\n * - **Link-graph kinematics** (`link()`, `edgeBetweenLinks()`,\n * `addAngleBetweenLinks()`) solve named point positions — a link is a point,\n * not a rigid-body frame. Use when the hard part is solving positions,\n * especially closed loops.\n * - **Connector-frame joints** (`connect()` / `match()`) align full connector\n * frames (`origin`, `axis`, `up`) and derive joint frame + axis. Use for\n * serial articulated parts whose orientation matters: hips, hinges, drums,\n * sliders, wheels.\n *\n * `addPart(..., { mate })` places geometry on the solved link graph by\n * **translation only**: one mate pins a connector origin to a link, two mates\n * orient a part to span two solved links, a third pins roll. Right for markers\n * and point-following geometry; use `connect()`/`match()` when the part needs\n * a deterministic rest orientation.\n *\n * Return the `Assembly` itself to expose its joints and driven link controls in\n * the editor; moving a control re-runs `solve(state)`, so closed loops move\n * through the real solver instead of a viewport-only FK approximation.\n *\n * If no link in a connected kinematic component is fixed, ForgeCAD chooses a\n * deterministic gauge link for solving and reports a floating-component warning.\n *\n * A file that returns an `Assembly` is importable via `require()` and yields an\n * `ImportedAssembly`; use `mergeInto()` to flatten it into a parent assembly.\n *\n * **Point-link example** (mates a marker to the solved `tip` point; does not\n * orient a bar along `ground -> tip`):\n *\n * ```ts\n * const marker = box(8, 8, 4).withConnectors({\n * center: connector({ origin: [0, 0, 0], axis: [0, 0, 1] }),\n * });\n *\n * const mech = assembly("Linkage")\n * .link("ground", { at: [0, 0, 0], fixed: true })\n * .link("worldX", { at: [10, 0, 0], fixed: true })\n * .link("tip", { at: [40, 0, 0] })\n * .edgeBetweenLinks("ground", "tip", { name: "bar" })\n * .addAngleBetweenLinks("worldX", "ground", "tip", {\n * name: "theta",\n * control: { min: 0, max: 120, default: 30 },\n * })\n * .addPart("Tip marker", marker, { mate: { connector: "center", toLink: "tip" } });\n *\n * return mech;\n * ```\n *\n * @param name - Display name for the assembly (used in BOM, simulation export, etc.)\n * @returns A new `Assembly` builder\n * @category Assembly\n */\ndeclare function assembly(name?: string): Assembly;\ninterface MergeIntoOptions {\n /**\n * Prefix applied to every part name and joint name from the sub-assembly.\n * E.g. prefix "Left Arm" turns part "Base" into "Left Arm.Base".\n * Strongly recommended to avoid name collisions when merging multiple instances.\n */\n prefix?: string;\n /** Part name in the parent assembly to attach the sub-assembly root to. */\n mountParent: string;\n /** Name for the new mount joint in the parent graph. */\n mountJoint: string;\n /** Joint type for the mount connection (default: \'fixed\'). */\n mountType?: JointType;\n /** Frame, axis, limits, and other options for the mount joint. */\n mountOptions?: JointOptions;\n}\n/**\n * A wrapper around an imported `Assembly` that provides kinematic access and\n * convenient transform helpers.\n *\n * **Details**\n *\n * When a `.forge.js` file returns an unsolved `Assembly`, `require()` wraps it\n * in an `ImportedAssembly`. This preserves the kinematic structure — you can\n * call `solve()` and `mergeInto()` — and converts to a static `ShapeGroup` via\n * the explicit `toGroup(state?)` boundary when group-style transforms are needed.\n *\n * **Kinematic access**\n *\n * ```ts\n * const arm = require("./arm.forge.js");\n *\n * const solved = arm.solve({ shoulder: 45 }); // full kinematic solve\n * const link = arm.getPart("Link", { shoulder: 60 }); // single part at state\n * const group = arm.toGroup({ shoulder: 45 }); // only when ShapeGroup behavior is needed\n * ```\n *\n * **Static positioning** — convert explicitly, then transform the group\n * (`toGroup()` solves at default joint values and discards kinematics):\n *\n * ```ts\n * const positioned = arm.toGroup().rotateZ(-90).translate(0, -20, 50);\n * ```\n *\n * **Merging into a parent**\n *\n * ```ts\n * require("./arm.forge.js").mergeInto(robot, {\n * prefix: "Left Arm",\n * mountParent: "Chassis",\n * mountJoint: "leftMount",\n * mountOptions: { frame: Transform.identity().translate(-70, 0, 10) },\n * });\n * ```\n *\n * @category Assembly\n */\ndeclare class ImportedAssembly {\n private readonly _assembly;\n private readonly _refs;\n private readonly _offset;\n constructor(_assembly: Assembly, _refs?: PlacementReferences, _offset?: readonly [\n number,\n number,\n number\n ]);\n /** The underlying Assembly, for advanced composition and inspection. */\n get assembly(): Assembly;\n /** Solve the assembly at the given joint state (defaults to each joint\'s default value). */\n solve(state?: JointState): SolvedAssembly;\n /**\n * Return a specific named part positioned at the given joint state, with any\n * stored placement offset applied.\n *\n * @softDeprecated getPart(name, state?) — same offset-aware accessor; note that solve(state).getPart(name) does NOT apply the placeReference() offset stored on the imported assembly\n * @deprecated use getPart(name, state?) — same offset-aware accessor; note that solve(state).getPart(name) does NOT apply the placeReference() offset stored on the imported assembly\n */\n part(name: string, state?: JointState): AssemblyPart;\n /**\n * Return a specific named part positioned at the solved pose, with any stored\n * placement offset applied.\n *\n * **Details**\n *\n * This mirrors `SolvedAssembly.getPart()` for imported assemblies, with one\n * addition: any offset stored by `placeReference()` is applied, so the part\n * lands where the imported assembly was placed. (`solve(state).getPart(name)`\n * returns the part in the assembly\'s own coordinates, without that offset.)\n *\n * @param partName - Name as registered with `addPart()`\n * @param state - Optional joint state; defaults to each joint\'s default value\n * @returns The part (`Shape` or `ShapeGroup`) at its solved, placed position\n * @category Assembly\n */\n getPart(partName: string, state?: JointState): AssemblyPart;\n /**\n * Convert all assembly parts to a ShapeGroup with named children.\n * Use this for composition, transforms, or child lookup — not as a required\n * render step for assemblies.\n * Child names match the part names used in the assembly.\n * Any stored placement offset and placement references are forwarded to the group.\n */\n toGroup(state?: JointState): ShapeGroup;\n /**\n * Attach named placement reference points to this assembly.\n * Points are simple 3D coordinates (relative to the assembly\'s own origin).\n * Returns a new ImportedAssembly — does not mutate.\n */\n withReferences(refs: Pick<PlacementReferenceInput, "points">): ImportedAssembly;\n /** List all attached placement reference names. */\n referenceNames(kind?: PlacementReferenceKind): string[];\n /**\n * Translate the assembly so the named reference point lands on `target`.\n * Returns a new ImportedAssembly — does not mutate.\n * All point refs are translated by the same delta.\n */\n placeReference(ref: string, target: [\n number,\n number,\n number\n ], offset?: [\n number,\n number,\n number\n ]): ImportedAssembly;\n /**\n * Solve at defaults and return a translated ShapeGroup.\n *\n * @softDeprecated toGroup().translate(x, y, z) — the explicit toGroup(state?) call shows where kinematics become a static group; or placeReference() to position by a named point\n * @deprecated use toGroup().translate(x, y, z) — the explicit toGroup(state?) call shows where kinematics become a static group; or placeReference() to position by a named point\n */\n translate(x: number, y: number, z: number): ShapeGroup;\n /**\n * Solve at defaults and return a rotated ShapeGroup.\n *\n * @softDeprecated toGroup().rotate(axis, angleDeg, options) — the explicit toGroup(state?) call shows where kinematics become a static group\n * @deprecated use toGroup().rotate(axis, angleDeg, options) — the explicit toGroup(state?) call shows where kinematics become a static group\n */\n rotate(axis: [\n number,\n number,\n number\n ], angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): ShapeGroup;\n /**\n * Solve at defaults and return a ShapeGroup rotated around X.\n *\n * @softDeprecated toGroup().rotateX(angleDeg, options) — the explicit toGroup(state?) call shows where kinematics become a static group\n * @deprecated use toGroup().rotateX(angleDeg, options) — the explicit toGroup(state?) call shows where kinematics become a static group\n */\n rotateX(angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): ShapeGroup;\n /**\n * Solve at defaults and return a ShapeGroup rotated around Y.\n *\n * @softDeprecated toGroup().rotateY(angleDeg, options) — the explicit toGroup(state?) call shows where kinematics become a static group\n * @deprecated use toGroup().rotateY(angleDeg, options) — the explicit toGroup(state?) call shows where kinematics become a static group\n */\n rotateY(angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): ShapeGroup;\n /**\n * Solve at defaults and return a ShapeGroup rotated around Z.\n *\n * @softDeprecated toGroup().rotateZ(angleDeg, options) — the explicit toGroup(state?) call shows where kinematics become a static group\n * @deprecated use toGroup().rotateZ(angleDeg, options) — the explicit toGroup(state?) call shows where kinematics become a static group\n */\n rotateZ(angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): ShapeGroup;\n /**\n * Solve at defaults and return a scaled ShapeGroup.\n *\n * @softDeprecated toGroup().scale(v) — the explicit toGroup(state?) call shows where kinematics become a static group\n * @deprecated use toGroup().scale(v) — the explicit toGroup(state?) call shows where kinematics become a static group\n */\n scale(v: number | [\n number,\n number,\n number\n ]): ShapeGroup;\n /**\n * Solve at defaults and return a mirrored ShapeGroup.\n *\n * @softDeprecated toGroup().mirror(normal) — the explicit toGroup(state?) call shows where kinematics become a static group\n * @deprecated use toGroup().mirror(normal) — the explicit toGroup(state?) call shows where kinematics become a static group\n */\n mirror(normal: [\n number,\n number,\n number\n ]): ShapeGroup;\n /**\n * Solve at defaults and return a colored ShapeGroup.\n *\n * @softDeprecated toGroup().color(hex) — the explicit toGroup(state?) call shows where kinematics become a static group\n * @deprecated use toGroup().color(hex) — the explicit toGroup(state?) call shows where kinematics become a static group\n */\n color(hex: string): ShapeGroup;\n /** Solve at defaults, get a named child part from the resulting group. */\n child(name: string): Shape | Sketch | ShapeGroup;\n /**\n * Detect overlapping part pairs at the default solved pose.\n *\n * **Details**\n *\n * This mirrors `SolvedAssembly.collisionReport()` for imported assemblies.\n * Use `solve(state).collisionReport(options)` when inspecting a non-default\n * joint state.\n *\n * @param options - Filter by `parts`, ignore specific `ignorePairs`, or set `minOverlapVolume`\n * @returns Array of `{ partA, partB, overlapVolume }` for each interference found\n * @category Assembly\n */\n collisionReport(options?: CollisionOptions): CollisionFinding[];\n /**\n * Compute the minimum gap between two parts at the default solved pose.\n *\n * **Details**\n *\n * This mirrors `SolvedAssembly.minClearance()` for imported assemblies. Use\n * `solve(state).minClearance(partA, partB, searchLength)` when inspecting a\n * non-default joint state.\n *\n * @param partA - Name of the first part\n * @param partB - Name of the second part\n * @param searchLength - Maximum search radius in mm (default 10)\n * @returns Minimum gap in mm; `0` means touching or interfering\n * @category Assembly\n */\n minClearance(partA: string, partB: string, searchLength?: number): number;\n /**\n * Flatten this sub-assembly\'s parts and relationships into `parent` and wire a mount relationship.\n *\n * **Details**\n *\n * All part, link, and legacy joint names from the sub-assembly are prefixed\n * with `"${options.prefix}."` to avoid collisions; connectors are forwarded\n * with the same prefix. After the merge, drive controls from the parent using\n * the prefixed names:\n *\n * ```ts\n * parent.solve({ "Left Arm.theta": 45, "Right Arm.theta": -20 })\n * ```\n *\n * The sub-assembly must have exactly one root part before it can be merged\n * (collapse multiple roots with `addFixed()` first). See the\n * {@link ImportedAssembly} class docs for a full merge example.\n *\n * @param parent - The target assembly to merge into\n * @param options - `prefix`, `mountParent`, `mountJoint`, `mountType`, `mountOptions`\n * @returns `parent` for chaining\n * @category Assembly\n */\n mergeInto(parent: Assembly, options: MergeIntoOptions): Assembly;\n}\ninterface RevoluteJointOpts {\n axis?: [\n number,\n number,\n number\n ];\n min?: number;\n max?: number;\n default?: number;\n unit?: string;\n reverse?: boolean;\n}\n/**\n * @deprecated `joint()` has been removed from the modeling API. Use\n * `assembly().link(...).edgeBetweenLinks(...).addAngleBetweenLinks(...)`\n * with controls for mechanisms.\n * @internal\n */\ndeclare function joint(_name: string, _shape: Shape, _pivot: [\n number,\n number,\n number\n], _opts?: RevoluteJointOpts): Shape;\ndeclare const DEFERRED_EDGE_SELECTION_MARKER: unique symbol;\ntype DeferredEdgeSelectionMode = "all" | "first";\ninterface DeferredEdgeSelection {\n readonly [DEFERRED_EDGE_SELECTION_MARKER]: true;\n readonly query: EdgeQuery;\n readonly mode: DeferredEdgeSelectionMode;\n}\n/**\n * Select all edges from a shape that match the given query.\n *\n * **Details**\n *\n * Uses the active kernel\'s native topology query when available (Truck),\n * otherwise extracts sharp edges from the mesh (dihedral angle > 1°), applies\n * all filters in the query, and returns the matching `EdgeSegment[]`. When\n * `near` is specified the results are sorted closest-first.\n *\n * Works on any shape — primitives, booleans, shells, and imported meshes.\n * Use this when tracked topology is unavailable (e.g. after a difference or\n * on imported geometry). For simpler cases, pass an `EdgeQuery` directly to\n * `fillet()` or `chamfer()` instead of calling `selectEdges` separately.\n *\n * **Example**\n *\n * ```ts\n * // Fillet all top edges of a box\n * const topEdges = selectEdges(part, { atZ: 20, perpendicular: [0, 0, 1] });\n * let result = part;\n * for (const edge of coalesceEdges(topEdges)) {\n * result = fillet(result, 2, edge);\n * }\n * ```\n *\n * @param shape - The solid to extract edges from\n * @param query - Optional filter/sort criteria\n * @returns Array of matching edge segments (may be empty)\n * @see {@link selectEdge} for the single-match variant\n * @see {@link coalesceEdges} to merge split tessellation segments\n * @category Edge Queries\n */\ndeclare function selectEdges(shape: Shape, query?: EdgeQuery): EdgeSegment[];\n/**\n * Select the single best-matching edge from a shape.\n *\n * **Details**\n *\n * When `near` is specified, returns the edge whose midpoint is closest to\n * that point. Otherwise returns the first matching edge in mesh order.\n * Throws if no edges match the query — useful as a guard when you expect\n * exactly one result.\n *\n * **Example**\n *\n * ```ts\n * // Chamfer one specific edge near a known point\n * const bottomEdge = selectEdge(part, { near: [25, 0, 0], atZ: 0 });\n * result = chamfer(result, 1.5, bottomEdge);\n * ```\n *\n * @param shape - The solid to extract edges from\n * @param query - Filter/sort criteria; use `near` to pick the closest match\n * @returns The single best-matching edge segment\n * @throws If no edges match the query\n * @see {@link selectEdges} for multi-match variant\n * @category Edge Queries\n */\ndeclare function selectEdge(shape: Shape, query?: EdgeQuery): EdgeSegment;\n/**\n * Merge collinear edge segments into longer logical edges.\n *\n * **Details**\n *\n * Tessellation often splits one geometric edge into multiple short segments.\n * `coalesceEdges` groups adjacent collinear segments and merges each group\n * into a single `EdgeSegment` spanning the full extent. This is usually\n * needed before passing edges to `fillet()` or `chamfer()` on non-primitive\n * shapes.\n *\n * The `tolerance` controls the maximum perpendicular distance from\n * collinearity before two segments are considered non-collinear. Default: `0.01`.\n *\n * **Example**\n *\n * ```ts\n * const topEdges = selectEdges(part, { atZ: 20 });\n * for (const edge of coalesceEdges(topEdges)) {\n * result = fillet(result, 2, edge);\n * }\n * ```\n *\n * @param segments - Edge segments from `selectEdges()`\n * @param tolerance - Max perpendicular deviation for collinearity (default: `0.01`)\n * @returns Merged edge segments\n * @see {@link selectEdges} to obtain the input segments\n * @category Edge Queries\n */\ndeclare function coalesceEdges(segments: EdgeSegment[], tolerance?: number): EdgeSegment[];\ntype EdgeReferenceLike = {\n edges(): EdgeSegment[];\n};\ntype EdgeSelector = EdgeSegment | EdgeSegment[] | EdgeQuery | EdgeRef | EdgeReferenceLike | DeferredEdgeSelection;\n/**\n * Apply experimental fillets (rounded edges) to one or more edges of a shape.\n *\n * **Details**\n *\n * **Experimental**: edge finishes (fillet and chamfer) are backend-sensitive.\n * The Manifold backend is known to produce incorrect results for some\n * edge-finish cases, and the OCCT backend can be very slow, especially with\n * broad edge selections. Prefer profile-level rounding where the design allows\n * (`sketch.filletCorners(radius)` before extruding — exact and fast); otherwise\n * use targeted edge selectors and inspect the result before treating it as\n * production-ready geometry.\n *\n * Edge selections compile into backend operations; unsupported selections fail\n * as explicit kernel gaps instead of using TypeScript geometry fallbacks.\n *\n * The `edges` parameter is flexible:\n * - Omit to fillet **all** sharp edges\n * - Pass an `EdgeQuery` for an inline filter (most common)\n * - Pass an `EdgeSegment` or `EdgeSegment[]` from `selectEdges()` for\n * pre-selected edges\n * - Pass a tracked `EdgeRef` from `shape.edge(\'vert-br\')` (vertical edges of\n * `box()` / `Rectangle2D` extrusions) — this takes the **exact**\n * compiler-owned path, not the mesh-approximate one\n *\n * Throws if no edges match the selection, or if `radius` is not a positive\n * finite number.\n *\n * Selectorless (all-edges) calls draw from a per-run broad edge-feature\n * budget. Exceeding it throws — except in live preview, which skips the\n * finish with a warning for responsiveness. Explicit edge selectors are never\n * budgeted; `FORGECAD_BROAD_EDGE_FEATURE_BUDGET` / `FORGECAD_ALLOW_BROAD_EDGE_FEATURES=1`\n * raise or lift the budget.\n *\n * **Example**\n *\n * ```ts\n * // Fillet all edges\n * fillet(myShape, 2)\n *\n * // Fillet only top convex edges\n * fillet(myShape, 1.5, { atZ: 20, convex: true })\n *\n * // Fillet vertical edges selected beforehand\n * const edges = selectEdges(myShape, { parallel: [0, 0, 1] })\n * fillet(myShape, 3, edges)\n *\n * // Exact compiler-owned fillet on a tracked box edge\n * const base = box(50, 50, 20)\n * fillet(base, 5, base.edge(\'vert-br\'))\n * ```\n *\n * @param shape - The solid to modify\n * @param radius - Fillet radius (must be positive and finite)\n * @param edges - Which edges to fillet: `EdgeSegment`, `EdgeSegment[]`, `EdgeQuery`, tracked `EdgeRef`, or `undefined` (all)\n * @param segments - Arc resolution hint for kernels that tessellate generated blends (default: `16`)\n * @returns A new Shape with the fillets applied\n * @see {@link chamfer} for beveled edges\n * @see {@link selectEdges} to pre-select edges\n * @experimental\n * @category Edge Features\n */\ndeclare function fillet(shape: Shape, radius: number, edges?: EdgeSelector, segments?: number): Shape;\n/**\n * Apply experimental chamfers (beveled edges) to one or more edges of a shape.\n *\n * **Details**\n *\n * **Experimental**: same backend caveats as {@link fillet} — Manifold may be\n * incorrect for some edge-finish cases, OCCT can be very slow on broad\n * selections; prefer profile-level rounding or targeted selectors and inspect\n * the result.\n *\n * Produces a 45° bevel at the specified `size` (distance from edge). Edge\n * selections compile into backend operations; unsupported selections fail as\n * explicit kernel gaps instead of using TypeScript geometry fallbacks.\n *\n * Selectorless (all-edges) calls draw from a per-run broad edge-feature\n * budget. Exceeding it throws — except in live preview, which skips the\n * finish with a warning for responsiveness. Explicit edge selectors are never\n * budgeted; `FORGECAD_BROAD_EDGE_FEATURE_BUDGET` / `FORGECAD_ALLOW_BROAD_EDGE_FEATURES=1`\n * raise or lift the budget.\n *\n * The `edges` parameter accepts the same options as `fillet()`: inline\n * `EdgeQuery`, pre-selected `EdgeSegment`/`EdgeSegment[]`, a tracked `EdgeRef`\n * from `shape.edge(\'vert-br\')` (exact compiler-owned path), or `undefined`\n * (all sharp edges).\n *\n * **Example**\n *\n * ```ts\n * // Chamfer all edges\n * chamfer(myShape, 1)\n *\n * // Chamfer only vertical edges\n * chamfer(myShape, 2, { parallel: [0, 0, 1] })\n *\n * // Exact compiler-owned chamfer on a tracked box edge\n * const base = box(50, 50, 20)\n * chamfer(base, 3, base.edge(\'vert-br\'))\n * ```\n *\n * @param shape - The solid to modify\n * @param size - Chamfer size — distance from the edge (must be positive and finite)\n * @param edges - Which edges to chamfer: `EdgeSegment`, `EdgeSegment[]`, `EdgeQuery`, tracked `EdgeRef`, or `undefined` (all)\n * @returns A new Shape with the chamfers applied\n * @see {@link fillet} for rounded edges\n * @experimental\n * @category Edge Features\n */\ndeclare function chamfer(shape: Shape, size: number, edges?: EdgeSelector): Shape;\n/**\n * Apply a draft angle (taper) to vertical faces for mold extraction.\n *\n * **Details**\n *\n * Adds a taper angle to the vertical faces of a solid so that it can be\n * extracted from a mold. The neutral plane is the Z position where the draft\n * angle is zero — faces above and below are tapered symmetrically. Typical\n * values for injection molding are 1–5°.\n *\n * SDF, Manifold, and Truck lower supported vertical-prism solids with Z-axis\n * pull directions to a tapered loft. OCCT uses its native draft operation when\n * available.\n *\n * **Example**\n *\n * ```ts\n * // Add 3° draft to a box for injection molding\n * draft(myBox, 3)\n *\n * // Draft with custom pull direction and neutral plane\n * draft(myShape, 2, [0, 0, 1], 10)\n * ```\n *\n * @param shape - The solid to modify\n * @param angleDeg - Draft angle in degrees (typically 1–5 for injection molding)\n * @param pullDirection - Mold pull direction (default: `[0, 0, 1]` = Z-up)\n * @param neutralPlaneOffset - Z-offset of the neutral plane (default: `0`)\n * @returns A new Shape with draft applied\n * @category Edge Features\n */\ndeclare function draft(shape: Shape, angleDeg: number, pullDirection?: [\n number,\n number,\n number\n], neutralPlaneOffset?: number): Shape;\n/**\n * Uniformly offset all surfaces of a solid inward or outward.\n *\n * **Details**\n *\n * Unlike `shell()`, which hollows a solid by removing one face, `offsetSolid()`\n * produces a new solid whose every surface is shifted by `thickness`. Positive\n * values grow the shape outward; negative values shrink it inward.\n *\n * Requires the OCCT backend. Throws on Manifold.\n *\n * **Example**\n *\n * ```ts\n * // Grow a box outward by 1mm on all sides\n * offsetSolid(myBox, 1)\n *\n * // Shrink a shape inward by 0.5mm\n * offsetSolid(myShape, -0.5)\n * ```\n *\n * @param shape - The solid to offset\n * @param thickness - Offset distance (positive = outward, negative = inward; must be non-zero and finite)\n * @returns A new Shape with offset surfaces\n * @category Edge Features\n */\ndeclare function offsetSolid(shape: Shape, thickness: number): Shape;\ntype MetricSize = "M2" | "M2.5" | "M3" | "M4" | "M5" | "M6" | "M8" | "M10";\ntype FastenerFit = "close" | "normal" | "loose" | "tap";\ninterface FastenerHoleOptions {\n /** Thread standard — only `\'iso-metric\'` is supported. */\n standard?: "iso-metric";\n /** ISO metric thread size, e.g. `\'M5\'`. Supported: M2–M10. */\n size: MetricSize;\n /**\n * Clearance fit class (default: `\'normal\'`).\n *\n * | Fit | Hole dia | Use when |\n * |-----|----------|----------|\n * | `\'close\'` | nominal + ~0.2 mm | Press-location or close-tolerance |\n * | `\'normal\'` | nominal + ~0.5 mm | Standard through-bolt clearance |\n * | `\'loose\'` | nominal + 1–2 mm | Adjustment slots or misaligned patterns |\n * | `\'tap\'` | ISO tap drill | Tapped hole in mating part |\n */\n fit?: FastenerFit;\n /** Hole depth in mm. */\n depth: number;\n /**\n * Optional counterbore at the top of the hole (wider recess for a socket-head cap).\n * `diameter` defaults to the ISO head-clearance diameter for the chosen size.\n */\n counterbore?: {\n depth: number;\n diameter?: number;\n };\n /**\n * Optional countersink at the top of the hole (conical recess for a flat-head screw).\n * `angleDeg` defaults to `90`.\n */\n countersink?: {\n diameter: number;\n angleDeg?: number;\n };\n /**\n * When `true` (default), the hole is centered on Z=0 (extends from −depth/2 to +depth/2).\n * When `false`, the top face is at Z=0 and the hole extends downward.\n */\n center?: boolean;\n /** Polygon segment count for the cylinder approximation (default: 48). */\n segments?: number;\n}\ninterface TSlotProfileOptions {\n /** Outer profile size (square). */\n size?: number;\n /** Slot mouth width (the narrow opening at each side). */\n slotWidth?: number;\n /** Wider interior slot cavity width. Must be >= slotWidth. */\n slotInnerWidth?: number;\n /** Total slot depth from outer face inward. */\n slotDepth?: number;\n /** Depth of the narrow mouth before it widens into slotInnerWidth. */\n slotNeckDepth?: number;\n /** Outer shell wall thickness. */\n wall?: number;\n /** Central cross-web thickness. */\n web?: number;\n /** Center boss diameter (solid material around center bore). */\n centerBossDia?: number;\n /** Center bore diameter (for tapping/through-hole). Set 0 to disable. */\n centerBoreDia?: number;\n /** Outer corner radius. */\n outerCornerRadius?: number;\n /** Segment count used for circular features in 2D. */\n segments?: number;\n}\ninterface TSlotExtrusionOptions extends TSlotProfileOptions {\n}\ninterface Profile2020BSlot6ProfileOptions {\n /** Slot mouth width. */\n slotWidth?: number;\n /** Wider inner slot width. */\n slotInnerWidth?: number;\n /** Slot depth from outer face. */\n slotDepth?: number;\n /** Depth of the narrow neck before widening into slotInnerWidth. */\n slotNeckDepth?: number;\n /** Center core bore diameter (set 0 to disable). */\n centerBoreDia?: number;\n /** Solid boss diameter around center bore (must exceed centerBoreDia when bore is enabled). */\n centerBossDia?: number;\n /** Width of diagonal ribs connecting center boss to corner regions. */\n diagonalWebWidth?: number;\n /** Outside corner radius. */\n outerCornerRadius?: number;\n /** Circle segment count. */\n segments?: number;\n}\ninterface Profile2020BSlot6Options extends Profile2020BSlot6ProfileOptions {\n}\ninterface ExplodeNamedItem {\n name: string;\n shape?: Shape | ShapeGroup;\n sketch?: Sketch;\n color?: string;\n group?: ExplodeItem[];\n explode?: ExplodeDirective;\n}\ntype ExplodeItem = Shape | Sketch | ShapeGroup | ExplodeNamedItem;\ninterface ExplodeOptions extends ExplodeConfigOptions {\n}\ntype BeltVec2 = [\n number,\n number\n];\ntype BeltMode = "open" | "crossed";\ninterface TangentCircle2D {\n name?: string;\n center: BeltVec2;\n radius: number;\n}\ninterface BeltPulley2D {\n name?: string;\n center: BeltVec2;\n pitchRadius: number;\n}\ninterface TangentLoop2DOptions {\n /** `open` uses external tangents; `crossed` uses internal tangents. */\n mode?: BeltMode;\n}\ninterface BeltDriveOptions {\n pulleys: BeltPulley2D[] | Record<string, Omit<BeltPulley2D, "name"> & {\n name?: string;\n }>;\n /** Belt width along +Z. */\n beltWidth: number;\n /** Belt thickness in the pulley plane. Default 2mm. */\n beltThickness?: number;\n /**\n * Reserved for multi-pulley route intent. The first implementation supports\n * two-pulley routes and rejects multi-pulley calls with explicit guidance.\n */\n route?: "outer" | BeltRouteContact[];\n /** Visual stroke width for the returned pitch path sketch. Default 0.25mm. */\n pitchPathWidth?: number;\n}\ninterface BeltRouteContact {\n pulley: string;\n wrap?: "cw" | "ccw" | "short" | "long";\n tangentIn?: "left" | "right" | "internal" | "external";\n tangentOut?: "left" | "right" | "internal" | "external";\n}\ninterface BeltLineSpan {\n kind: "line";\n fromPulley: string;\n toPulley: string;\n from: BeltVec2;\n to: BeltVec2;\n length: number;\n}\ninterface BeltWrapArc {\n kind: "arc";\n pulley: string;\n center: BeltVec2;\n pitchRadius: number;\n from: BeltVec2;\n to: BeltVec2;\n sweepDeg: number;\n wrapDeg: number;\n length: number;\n tangentIn: BeltVec2;\n tangentOut: BeltVec2;\n}\ntype BeltPathSegment = BeltLineSpan | BeltWrapArc;\ninterface BeltDriveResult {\n belt: Shape;\n beltProfile: Sketch;\n pitchPath: Sketch;\n route: TangentLoop2D;\n length: number;\n wraps: BeltWrapArc[];\n wrapByPulley: Record<string, BeltWrapArc>;\n straightSpans: BeltLineSpan[];\n skippedPulleys: string[];\n}\ndeclare class TangentLoop2D {\n readonly circles: TangentCircle2D[];\n readonly mode: BeltMode;\n readonly segments: BeltPathSegment[];\n readonly straightSpans: BeltLineSpan[];\n readonly wraps: BeltWrapArc[];\n readonly wrapByPulley: Record<string, BeltWrapArc>;\n readonly length: number;\n constructor(circles: TangentCircle2D[], options?: TangentLoop2DOptions);\n /** Convert the loop centerline into a thin visual sketch. */\n toSketch(width?: number): Sketch;\n /** Convert the loop into a filled profile using the pitch path itself as the boundary. */\n toProfile(): Sketch;\n /** Build a belt band sketch by offsetting the route to inner and outer pulley radii. */\n offsetBand(thickness: number): Sketch;\n}\ninterface SpurGearOptions {\n module: number;\n teeth: number;\n pressureAngleDeg?: number;\n faceWidth: number;\n backlash?: number;\n clearance?: number;\n addendum?: number;\n dedendum?: number;\n boreDiameter?: number;\n segmentsPerTooth?: number;\n}\ninterface SectorGearOptions extends Omit<SpurGearOptions, "teeth"> {\n teethOnFullCircle: number;\n toothCount: number;\n firstTooth?: number;\n body?: Shape;\n}\ninterface GearBodyDiskOptions {\n outerRadius: number;\n faceWidth: number;\n boreDiameter?: number;\n segments?: number;\n}\ninterface GearBodyDiskWithHubOptions extends GearBodyDiskOptions {\n hubDiameter: number;\n hubFaceWidth?: number;\n}\ninterface GearBodySpokedOptions extends GearBodyDiskOptions {\n rimWidth: number;\n hubDiameter: number;\n spokeCount: number;\n spokeWidth: number;\n}\ninterface GearBodyFromProfileOptions {\n faceWidth: number;\n boreDiameter?: number;\n}\ndeclare function gearBodyDisk(options: GearBodyDiskOptions): Shape;\ndeclare function gearBodyDiskWithHub(options: GearBodyDiskWithHubOptions): Shape;\ndeclare function gearBodySpoked(options: GearBodySpokedOptions): Shape;\ndeclare function gearBodyFromProfile(profile: Sketch, options: GearBodyFromProfileOptions): Shape;\ntype DriveWheelRegionKind = "body" | "spurTeeth" | "solidArc" | "custom";\ninterface DriveWheelRegionMeta {\n name: string;\n kind: DriveWheelRegionKind;\n fromAngleDeg?: number;\n toAngleDeg?: number;\n innerRadius?: number;\n outerRadius?: number;\n module?: number;\n teethOnFullCircle?: number;\n toothCount?: number;\n pitchRadius?: number;\n rootRadius?: number;\n faceWidth?: number;\n}\ninterface DriveWheelMeta {\n kind: "driveWheel";\n faceWidth: number;\n boreDiameter: number;\n regions: DriveWheelRegionMeta[];\n}\ninterface DriveWheelOptions {\n body?: Shape;\n faceWidth?: number;\n boreDiameter?: number;\n}\ninterface DriveWheelSpurTeethRegionOptions extends Omit<SpurGearOptions, "teeth" | "faceWidth" | "boreDiameter"> {\n name?: string;\n teethOnFullCircle: number;\n toothCount: number;\n firstTooth?: number;\n faceWidth?: number;\n}\ninterface DriveWheelSolidArcRegionOptions {\n name?: string;\n fromAngleDeg: number;\n toAngleDeg: number;\n innerRadius?: number;\n outerRadius: number;\n faceWidth?: number;\n segments?: number;\n}\ninterface DriveWheelShapeRegionOptions {\n fromAngleDeg?: number;\n toAngleDeg?: number;\n innerRadius?: number;\n outerRadius?: number;\n}\ndeclare class DriveWheelBuilder {\n private readonly body?;\n private readonly faceWidth?;\n private readonly boreDiameter;\n private readonly regions;\n constructor(options?: DriveWheelOptions);\n /**\n * Add an involute spur-tooth window on part of the pitch circle.\n */\n addSpurTeethBetween(options: DriveWheelSpurTeethRegionOptions): this;\n /**\n * Add a constant-radius solid arc region such as a dwell, stop, or pusher.\n */\n addSolidArcBetween(options: DriveWheelSolidArcRegionOptions): this;\n /**\n * Add a fully custom region shape while preserving region metadata.\n */\n addShapeRegion(name: string, shape: Shape, options?: DriveWheelShapeRegionOptions): this;\n /**\n * Build the final wheel shape with a bore connector and region metadata.\n */\n build(): Shape;\n private measurements;\n private regionMetadata;\n private resolveFaceWidth;\n private resolveBuildFaceWidth;\n private defaultBodyRadius;\n private resolveName;\n}\ninterface FaceGearOptions extends SpurGearOptions {\n /** Which face carries the teeth (default: `\'top\'`). */\n side?: "top" | "bottom";\n /** Axial tooth height in mm (default: module). */\n toothHeight?: number;\n}\ninterface SideGearOptions extends FaceGearOptions {\n}\ninterface RingGearOptions {\n module: number;\n teeth: number;\n pressureAngleDeg?: number;\n faceWidth: number;\n backlash?: number;\n clearance?: number;\n addendum?: number;\n dedendum?: number;\n rimWidth?: number;\n outerDiameter?: number;\n segmentsPerTooth?: number;\n}\ninterface RackGearOptions {\n module: number;\n teeth: number;\n pressureAngleDeg?: number;\n faceWidth: number;\n backlash?: number;\n clearance?: number;\n addendum?: number;\n dedendum?: number;\n baseHeight?: number;\n}\ninterface BevelGearOptions {\n module: number;\n teeth: number;\n pressureAngleDeg?: number;\n faceWidth: number;\n backlash?: number;\n clearance?: number;\n addendum?: number;\n dedendum?: number;\n boreDiameter?: number;\n pitchAngleDeg?: number;\n mateTeeth?: number;\n shaftAngleDeg?: number;\n segmentsPerTooth?: number;\n}\ninterface GearPairSpec {\n module: number;\n teeth: number;\n pressureAngleDeg?: number;\n faceWidth?: number;\n backlash?: number;\n clearance?: number;\n addendum?: number;\n dedendum?: number;\n boreDiameter?: number;\n segmentsPerTooth?: number;\n}\ninterface GearPairOptions {\n pinion: Shape | GearPairSpec;\n gear: Shape | GearPairSpec;\n backlash?: number;\n centerDistance?: number;\n place?: boolean;\n phaseDeg?: number;\n}\ninterface GearPairDiagnostic {\n level: "info" | "warn" | "error";\n code: string;\n message: string;\n}\ninterface GearPairResult {\n pinion: Shape;\n gear: Shape;\n centerDistance: number;\n centerDistanceNominal: number;\n backlash: number;\n pressureAngleDeg: number;\n workingPressureAngleDeg: number;\n contactRatio: number;\n jointRatio: number;\n speedReduction: number;\n /** Phase rotation (degrees) for the gear around its shaft axis for correct tooth\n * mesh alignment. When `place: true` this is already baked into `gear`.\n * When `place: false`, rotate the gear by this amount before positioning. */\n phaseDeg: number;\n diagnostics: GearPairDiagnostic[];\n status: "ok" | "warn" | "error";\n}\ninterface GearMeshPlacement {\n pinionAxis: [\n number,\n number,\n number\n ];\n gearAxis: [\n number,\n number,\n number\n ];\n pinionCenter: [\n number,\n number,\n number\n ];\n gearCenter: [\n number,\n number,\n number\n ];\n}\ninterface BevelGearPairSpec extends GearPairSpec {\n}\ninterface BevelGearPairOptions {\n pinion: Shape | BevelGearPairSpec;\n gear: Shape | BevelGearPairSpec;\n shaftAngleDeg?: number;\n backlash?: number;\n place?: boolean;\n phaseDeg?: number;\n}\ninterface FaceGearSpec extends GearPairSpec {\n side?: "top" | "bottom";\n toothHeight?: number;\n}\ninterface SideGearSpec extends FaceGearSpec {\n}\ninterface SideGearPairOptions {\n side: Shape | SideGearSpec;\n vertical: Shape | GearPairSpec;\n backlash?: number;\n centerDistance?: number;\n meshPlaneZ?: number;\n place?: boolean;\n phaseDeg?: number;\n}\ninterface BevelGearPairResult extends GearMeshPlacement {\n pinion: Shape;\n gear: Shape;\n shaftAngleDeg: number;\n pinionPitchAngleDeg: number;\n gearPitchAngleDeg: number;\n coneDistance: number;\n backlash: number;\n jointRatio: number;\n speedReduction: number;\n /** Phase rotation (degrees) for gear tooth mesh alignment. See GearPairResult.phaseDeg. */\n phaseDeg: number;\n diagnostics: GearPairDiagnostic[];\n status: "ok" | "warn" | "error";\n}\ninterface SideGearPairResult {\n side: Shape;\n vertical: Shape;\n centerDistance: number;\n centerDistanceNominal: number;\n backlash: number;\n pressureAngleDeg: number;\n meshPlaneZ: number;\n radialOverlap: number;\n jointRatio: number;\n speedReduction: number;\n /** Phase rotation (degrees) for the vertical gear. See GearPairResult.phaseDeg. */\n phaseDeg: number;\n diagnostics: GearPairDiagnostic[];\n status: "ok" | "warn" | "error";\n}\ninterface FaceGearPairOptions {\n face: Shape | FaceGearSpec;\n vertical: Shape | GearPairSpec;\n backlash?: number;\n centerDistance?: number;\n meshPlaneZ?: number;\n place?: boolean;\n phaseDeg?: number;\n}\ninterface FaceGearPairResult {\n face: Shape;\n vertical: Shape;\n centerDistance: number;\n centerDistanceNominal: number;\n backlash: number;\n pressureAngleDeg: number;\n meshPlaneZ: number;\n radialOverlap: number;\n jointRatio: number;\n speedReduction: number;\n /** Phase rotation (degrees) for the vertical gear. See GearPairResult.phaseDeg. */\n phaseDeg: number;\n diagnostics: GearPairDiagnostic[];\n status: "ok" | "warn" | "error";\n}\ntype Vec2$3 = [\n number,\n number\n];\ninterface BoltPatternOptions {\n /** Metric bolt size (\'M2\'–\'M10\'). */\n size: MetricSize;\n /** Clearance class. Default: \'normal\'. */\n fit?: FastenerFit;\n /** List of [x, y] positions in the pattern\'s 2D coordinate frame. */\n positions: Vec2$3[];\n /** Number of circle segments per hole. Default: 48. */\n segments?: number;\n}\ninterface BoltPatternCutOptions {\n /**\n * Z position of the top of the cutters (before punching through). For a cut\n * from z=0 downward through a 20mm plate, pass `from: -1` and `depth: 22` to\n * ensure the cutter fully punches through with some margin.\n */\n from?: number;\n /** Optional counterbore. */\n counterbore?: {\n depth: number;\n diameter?: number;\n };\n /** Optional countersink. */\n countersink?: {\n diameter: number;\n angleDeg?: number;\n };\n}\ninterface BoltPattern {\n /** Bolt size (\'M5\' etc.). */\n readonly size: MetricSize;\n /** Clearance diameter in mm, derived from size + fit. */\n readonly dia: number;\n /** Positions in 2D (XY of the plane being cut). */\n readonly positions: ReadonlyArray<Vec2$3>;\n /** Min X across all positions — useful for housing sizing. */\n readonly minX: number;\n /** Max X across all positions. */\n readonly maxX: number;\n /** Min Y across all positions. */\n readonly minY: number;\n /** Max Y across all positions. */\n readonly maxY: number;\n /**\n * Subtract the pattern from a shape. Cuts one ISO-clearance hole at each position.\n *\n * @param shape - Shape to cut.\n * @param depth - Depth of the cut along Z.\n * @param options - Optional counterbore/countersink and Z offset.\n */\n cut(shape: Shape, depth: number, options?: BoltPatternCutOptions): Shape;\n}\ntype WasherStandard = "din-125-a";\ninterface FastenerSetDimensions {\n size: MetricSize;\n nominalDiameter: number;\n boltLength: number;\n clearanceDia: number;\n tapDia: number;\n nutAcrossFlats: number;\n nutHeight: number;\n washerOuterDia: number;\n washerInnerDia: number;\n washerThickness: number;\n}\ninterface FastenerSetOptions {\n /** Include a DIN 125-A washer under the bolt head (default: `true`). */\n washerUnderHead?: boolean;\n /** Include a DIN 125-A washer under the nut (default: `true`). */\n washerUnderNut?: boolean;\n /**\n * Clearance fit class for the through-hole cutter (default: `\'normal\'`).\n *\n * | Fit | Hole dia | Use when |\n * |-----|----------|----------|\n * | `\'close\'` | nominal + ~0.2 mm | Press-location or close-tolerance |\n * | `\'normal\'` | nominal + ~0.5 mm | Standard through-bolt clearance |\n * | `\'loose\'` | nominal + 1–2 mm | Adjustment slots or misaligned patterns |\n * | `\'tap\'` | ISO tap drill | Tapped hole in mating part |\n */\n fit?: FastenerFit;\n /** Polygon segment count for all circular geometry (default: 36). */\n segments?: number;\n}\ninterface FastenerSetResult {\n /** Hex bolt: head top at z=0, threaded shaft extends toward −Z by `boltLength`. */\n bolt: Shape;\n /** Hex nut centered at z=0. */\n nut: Shape;\n /** Flat washer centered at z=0. Null when washerUnderHead is false. */\n washerUnderHead: Shape | null;\n /** Flat washer centered at z=0. Null when washerUnderNut is false. */\n washerUnderNut: Shape | null;\n /** Clearance-hole cutter (cylinder) centered at z=0, for subtracting from a through-plate. */\n clearanceHole: Shape;\n /** Tap-drill cutter (cylinder) centered at z=0, for subtracting from a tapped plate. */\n tappedHole: Shape;\n /** Reference dimensions for BOM, placement calculations, and documentation. */\n dims: FastenerSetDimensions;\n}\n/**\n * Pre-built parametric parts available in user scripts as `lib.*`.\n *\n * **Details**\n *\n * Every key in this object becomes a method or namespace on the `lib` object\n * exposed to `.forge.js` scripts — see the member list below for the catalog.\n * Sizes outside the supported ranges throw at runtime with a descriptive error.\n *\n * @category Part Library\n */\ndeclare const partLibrary: {\n /**\n * Plain cylindrical through-hole cutter centered on Z=0. Legacy helper —\n * `Shape.hole()` places, validates, and recesses holes on a face directly.\n * @softDeprecated shape.hole(face, { diameter, depth }) — face-relative blind/through holes with counterbore/countersink/thread support\n * @deprecated use shape.hole(face, { diameter, depth }) — face-relative blind/through holes with counterbore/countersink/thread support\n */\n boltHole: (diameter: number, depth: number) => Shape;\n/**\n * ISO metric fastener hole cutter with optional counterbore or countersink.\n *\n * **Details**\n *\n * Returns a cutter shape (subtract from a solid to produce the hole). Sizes\n * outside M2–M10 will throw.\n *\n * **Example**\n *\n * ```ts\n * const plate = box(60, 40, 8)\n * .subtract(lib.fastenerHole({ size: \'M5\', fit: \'normal\', depth: 8 })\n * .translate(15, 10, 4));\n * ```\n *\n * @param opts - Hole configuration including size, fit class, depth, and optional recesses.\n * @returns A cutter shape centered on Z=0 (or positioned at Z=0 top when `center: false`).\n * @category Fasteners\n */\n\n fastenerHole(opts: FastenerHoleOptions): Shape;\n /**\n * Counterbore hole cutter — through-hole with a wider cylindrical recess at\n * the top, built from four positional numbers. Legacy helper — `Shape.hole()`\n * takes named options and places the hole on a face directly.\n * @softDeprecated shape.hole(face, { diameter: holeDia, counterbore: { diameter: boreDia, depth: boreDepth } }) — through by default; pass depth for blind holes\n * @deprecated use shape.hole(face, { diameter: holeDia, counterbore: { diameter: boreDia, depth: boreDepth } }) — through by default; pass depth for blind holes\n */\n counterbore: (holeDia: number, boreDia: number, boreDepth: number, totalDepth: number) => Shape;\n/**\n * Rectangular hollow tube (thin-wall box section).\n *\n * Both the outer and inner boxes are centered on the XY plane with their base at Z=0.\n *\n * @param outerX - Outer width in mm.\n * @param outerY - Outer depth in mm.\n * @param outerZ - Length (height) of the tube in mm.\n * @param wall - Wall thickness in mm (removed uniformly from all four sides).\n * @returns A hollow rectangular tube solid.\n * @category Fasteners\n */\n\n tube(outerX: number, outerY: number, outerZ: number, wall: number): Shape;\n/**\n * Hollow cylindrical pipe.\n *\n * Centered on the XY plane, extending upward along +Z from z=0 to z=height.\n * For complex routed pipe geometry, see `lib.pipeRoute`.\n *\n * @param height - Pipe length in mm.\n * @param outerRadius - Outer radius in mm.\n * @param wall - Wall thickness in mm.\n * @param segments - Polygon approximation segments (default: 32).\n * @returns A hollow cylinder solid.\n * @category Fasteners\n */\n\n pipe(height: number, outerRadius: number, wall: number, segments?: number): Shape;\n/**\n * Apply deterministic exploded-view offsets to an assembly tree.\n *\n * **Details**\n *\n * Traverses arrays, nested `{ name, group: [...] }` structures, and `ShapeGroup` outputs,\n * translating each node while preserving names, colors, and nesting; returns the same structure\n * type as the input. `radial` mode is branch-aware and parent-relative — nested assemblies peel\n * apart level by level. Named items accept an inline `explode: { stage?, direction?, axisLock? }`\n * override. Bakes the offset into geometry (e.g. driven by a `param()` slider); for a\n * viewport-only slider use `explodeView()` instead.\n *\n * **Example**\n *\n * ```js\n * const explodeAmt = param(\'Explode\', 0, { min: 0, max: 40, unit: \'mm\' });\n *\n * return lib.explode(assembly, {\n * amount: explodeAmt,\n * stages: [0.4, 0.8],\n * mode: \'radial\',\n * byName: { Shaft: { direction: [1, 0, 0], stage: 1.4 } },\n * });\n * ```\n *\n * @param items - Array of `Shape | Sketch | ShapeGroup | { name, shape?, sketch?, group?, explode? }`, or a `ShapeGroup`\n * @param options.amount - Total explode distance in model units\n * @param options.stages - Per-depth stage multipliers (depth 1 = first level). Default: reciprocal depth (`1, 1/2, 1/3, ...`)\n * @param options.mode - Direction mode: `\'radial\'` | `\'x\'` | `\'y\'` | `\'z\'` | `[x, y, z]`. Default: `\'radial\'`\n * @param options.axisLock - Constrain motion to a world axis: `\'x\'` | `\'y\'` | `\'z\'`\n * @param options.byName - Per-object overrides keyed by name: `{ stage?, direction?, axisLock? }`\n * @param options.byPath - Per-tree-path overrides using slash-separated path segments\n * @returns Same structure type as input, with translated geometry\n * @see {@link explodeView} for viewport-slider-driven explode without rerunning the script\n * @category Explode View\n */\n\n explode<T extends ExplodeItem[] | ShapeGroup>(items: T, options?: ExplodeOptions): T;\n /**\n * Generic hex nut with a cylindrical bore. Legacy twin of `lib.nut()`.\n * @softDeprecated lib.nut(holeDia, { acrossFlats, height, boreDiameter: holeDia }) — exact same geometry\n * @deprecated use lib.nut(holeDia, { acrossFlats, height, boreDiameter: holeDia }) — exact same geometry\n */\n hexNut: (acrossFlats: number, height: number, holeDia: number) => Shape;\n/**\n * L-shaped mounting bracket with optional through-holes.\n *\n * Produces a right-angle bracket: a horizontal base plate and a vertical wall.\n * Both legs share `width`. Optional holes are drilled through the base (along Z)\n * and the wall (along Y).\n *\n * @param width - Width of the bracket (both legs share this dimension) in mm.\n * @param height - Wall leg height in mm.\n * @param depth - Base leg depth in mm.\n * @param thick - Material thickness in mm (both legs).\n * @param holeDia - Hole diameter in mm. Pass `0` (default) to omit holes.\n * @returns An L-bracket solid with base at Z=0.\n * @category Fasteners\n */\n\n bracket(width: number, height: number, depth: number, thick: number, holeDia?: number): Shape;\n/**\n * Rectangular grid of cylindrical hole cutters.\n *\n * Returns the union of `rows × cols` cylinders laid out on a regular grid.\n * Subtract from a solid to produce the full pattern.\n *\n * **Example**\n *\n * ```ts\n * const pattern = lib.holePattern(3, 4, 20, 20, 4, 10);\n * const panel = box(80, 70, 10).subtract(pattern.translate(-30, -20, 0));\n * ```\n *\n * @param rows - Number of rows in the grid.\n * @param cols - Number of columns in the grid.\n * @param spacingX - Column pitch in mm.\n * @param spacingY - Row pitch in mm.\n * @param holeDia - Hole diameter in mm.\n * @param depth - Hole depth in mm.\n * @returns Union of cutter cylinders, origin at the first hole center.\n * @category Fasteners\n */\n\n holePattern(rows: number, cols: number, spacingX: number, spacingY: number, holeDia: number, depth: number): Shape;\n/**\n * External helical thread — clean mesh, no SDF grid artifacts.\n *\n * **Details**\n *\n * Builds a cross-section with a single trapezoidal tooth from the root radius\n * out to the crest radius, then twist-extrudes it so the tooth traces a helix.\n * Manifold\'s extrude+twist produces structured quad-based geometry that follows\n * the thread profile cleanly.\n *\n * Returns a threaded cylinder along +Z from z=0 to z=length.\n *\n * @param diameter - Nominal (crest) diameter in mm.\n * @param pitch - Thread pitch in mm (axial distance between crests).\n * @param length - Thread length in mm.\n * @param options - Optional overrides: `depth` (tooth height, default 0.35×pitch), `segments` (default 36).\n * @returns A threaded cylinder solid.\n * @category Fasteners\n */\n\n thread(diameter: number, pitch: number, length: number, options?: {\n depth?: number;\n segments?: number;\n}): Shape;\n/**\n * ISO-style hex bolt with real helical threads.\n *\n * **Details**\n *\n * The hex head sits from z=0 up to z=headHeight. The shaft extends downward\n * along −Z by `length` mm. An unthreaded shank section is included when\n * `threadLength < length`.\n *\n * Default proportions follow ISO 4762 loosely: pitch ≈ 0.15×diameter,\n * head height ≈ 0.65×diameter, across-flats ≈ 1.6×diameter.\n *\n * For standard M-size bolts pre-configured for a complete joint, use\n * {@link fastenerSet} instead.\n *\n * @param diameter - Nominal shaft diameter in mm.\n * @param length - Shaft length in mm (head height not included).\n * @param options - Optional overrides for pitch, head height, across-flats, thread length, and segments.\n * @returns A hex bolt solid with head top at z=0.\n * @category Fasteners\n */\n\n bolt(diameter: number, length: number, options?: {\n pitch?: number;\n headHeight?: number;\n headAcrossFlats?: number;\n threadLength?: number;\n segments?: number;\n}): Shape;\n/**\n * ISO-style hex nut with a clearance bore.\n *\n * **Details**\n *\n * Constructed from the intersection of three rotated slabs with a cylindrical\n * bore subtracted. The nut is centered at the origin, height along Z\n * (−height/2 to +height/2).\n *\n * Default proportions follow ISO 4032 loosely: height ≈ 0.8×diameter,\n * across-flats ≈ 1.6×diameter. The bore is a clearance bore — `diameter`\n * + 0.2 mm by default, override with `boreDiameter` for exact bore control —\n * not modelled with helical threads, for rendering efficiency.\n *\n * For standard M-size nuts pre-configured for a complete joint, use\n * {@link fastenerSet} instead.\n *\n * @param diameter - Nominal thread diameter in mm.\n * @param options - Optional overrides for height, across-flats, exact bore diameter, and segments.\n * @returns A hex nut solid centered at origin.\n * @category Fasteners\n */\n\n nut(diameter: number, options?: {\n height?: number;\n acrossFlats?: number;\n /** Exact bore diameter in mm. Default: `diameter + 0.2` (clearance bore). */\n boreDiameter?: number;\n segments?: number;\n}): Shape;\n/**\n * ISO metric flat washer (DIN 125-A).\n *\n * **Details**\n *\n * Returns a flat ring centered at the origin, thickness along Z. Dimensions\n * are taken from {@link WASHER_TABLE}. Sizes outside M2–M10 will throw.\n *\n * @param size - ISO metric thread size, e.g. `\'M5\'`. Supported: M2–M10.\n * @param options - Optional `standard` (only `\'din-125-a\'` supported) and `segments` (default 48).\n * @returns A flat washer ring solid centered at origin.\n * @category Fasteners\n */\n\n washer(size: MetricSize, options?: {\n standard?: WasherStandard;\n segments?: number;\n}): Shape;\n/**\n * Complete ISO metric fastener set — bolt, nut, optional washers, and matching hole cutters.\n *\n * **Details**\n *\n * Returns all geometry for one bolted joint: the bolt, nut, up to two washers,\n * a clearance-hole cutter, and a tap-drill cutter. All shapes are returned\n * **un-positioned** (each on the Z-axis). Place them with `.translate()`.\n *\n * Sizes outside M4–M10 are supported for the washer (M2–M10); unsupported\n * combinations will throw.\n *\n * **Example**\n *\n * ```ts\n * const hw = lib.fastenerSet(\'M5\', 20);\n *\n * const topPlate = box(60, 40, 8).translate(0, 0, 12)\n * .subtract(hw.clearanceHole.translate(15, 10, 12));\n * const botPlate = box(60, 40, 8)\n * .subtract(hw.clearanceHole.translate(15, 10, 0));\n *\n * return [\n * { name: \'Top Plate\', shape: topPlate },\n * { name: \'Bot Plate\', shape: botPlate },\n * { name: \'Bolt\', shape: hw.bolt.translate(15, 10, 20) },\n * { name: \'Nut\', shape: hw.nut.translate(15, 10, -4) },\n * ];\n * ```\n *\n * @param size - ISO metric thread size, e.g. `\'M5\'`. Supported: M4–M10 (bolt/nut), M2–M10 (washer).\n * @param boltLength - Nominal shaft length in mm (head height not included). Must be > 0.\n * @param options - Washer inclusion, fit class, and segment count.\n * @returns A {@link FastenerSetResult} with all joint shapes and reference dimensions.\n * @category Fasteners\n */\n\n fastenerSet(size: MetricSize, boltLength: number, options?: FastenerSetOptions): FastenerSetResult;\n/**\n * Route a pipe (solid or hollow) through 3D waypoints with smooth bends.\n *\n * This is a convenience recipe over `Curve.Route.fromPolyline()` and `sweep()`.\n * Use `Curve.Route` directly when you need route metadata, named ports, or\n * custom swept profiles.\n */\n\n pipeRoute(points: [\n number,\n number,\n number\n][], radius: number, options?: {\n bendRadius?: number;\n wall?: number;\n segments?: number;\n}): Shape;\n/**\n * Pipe elbow — a curved pipe section for connecting two pipe directions.\n *\n * This is a convenience recipe over `Curve.Arc()` and `sweep()`. Use\n * `Curve.Route.fromPolyline()` directly when you need named route ports,\n * segment metadata, or multi-bend pipe runs.\n *\n * @param pipeRadius - Pipe outer radius\n * @param bendRadius - Centerline bend radius (distance from arc center to pipe center)\n * @param angle - Bend angle in degrees (e.g. 90 for a right-angle bend)\n * @param options.wall - Wall thickness for hollow pipe\n * @param options.segments - Circumferential segments (default 32)\n * @param options.from - Incoming direction vector (default [0,0,1])\n * @param options.to - Outgoing direction vector (overrides angle if both from/to given)\n */\n\n elbow(pipeRadius: number, bendRadius: number, angle?: number | {\n from?: [\n number,\n number,\n number\n ];\n to?: [\n number,\n number,\n number\n ];\n wall?: number;\n segments?: number;\n}, options?: {\n wall?: number;\n segments?: number;\n from?: [\n number,\n number,\n number\n ];\n to?: [\n number,\n number,\n number\n ];\n}): Shape;\n/**\n * Create a flat open-belt body around two pulley pitch circles.\n *\n * The belt is generated as a tangent loop in the XY plane and extruded along\n * +Z by `beltWidth`. The result includes the solid belt, the 2D belt profile,\n * a thin pitch-path sketch for visualization, total belt length, tangent spans,\n * and wrap metadata for each pulley.\n *\n * For more than two pulleys, the API intentionally asks for route intent before\n * geometry is created. Use `route: "outer"` for the future outside-envelope\n * mode, or an ordered route for future serpentine/idler layouts.\n *\n * ```ts\n * const drive = lib.beltDrive({\n * pulleys: [\n * { name: "motor", center: [0, 0], pitchRadius: 12 },\n * { name: "output", center: [80, 0], pitchRadius: 28 },\n * ],\n * beltWidth: 8,\n * beltThickness: 2,\n * });\n * return drive.belt;\n * ```\n *\n * @category Belt Drives\n */\n\n beltDrive(options: BeltDriveOptions): BeltDriveResult;\n/**\n * Build a closed 2D route made from common tangent spans and pulley wrap arcs.\n *\n * Use this when you need reusable belt/chain route geometry before creating a\n * solid body. The first implementation supports two circles. `mode: "open"`\n * uses external tangents; `mode: "crossed"` uses internal tangents.\n *\n * ```ts\n * const route = lib.tangentLoop2d([\n * { center: [0, 0], radius: 12 },\n * { center: [80, 0], radius: 28 },\n * ]);\n * const belt = route.offsetBand(2).extrude(8);\n * ```\n *\n * @category Belt Drives\n */\n\n tangentLoop2d(circles: TangentCircle2D[], options?: TangentLoop2DOptions): TangentLoop2D;\n/**\n * Build a 2D T-slot cross-section sketch.\n *\n * Default parameters describe a 20x20 B-type profile with slot 6.\n * Use this when you want a drawing-ready profile sketch before extrusion.\n */\n\n tSlotProfile(options?: TSlotProfileOptions): Sketch;\n/**\n * Build a T-slot extrusion from the generated 2D profile.\n * Extrudes along +Z by default.\n */\n\n tSlotExtrusion(length: number, options?: TSlotExtrusionOptions): Shape;\n/**\n * Accurate-ish 2D profile for 20x20 B-type slot 6.\n *\n * Returns a drawing-ready Sketch centered at origin.\n */\n\n profile2020BSlot6Profile(options?: Profile2020BSlot6ProfileOptions): Sketch;\n/**\n * 20x20 B-type slot 6 extrusion with profile-accurate defaults.\n *\n * Pass option overrides if your supplier\'s profile differs slightly.\n */\n\n profile2020BSlot6(length: number, options?: Profile2020BSlot6Options): Shape;\n/**\n * Involute external spur gear with optional center bore.\n *\n * Specify module, teeth, faceWidth as required parameters. Optional tuning includes\n * pressureAngleDeg (default 20), backlash, clearance, addendum, dedendum, boreDiameter,\n * and segmentsPerTooth (default 10).\n *\n * **Connectors (for assembly-based positioning):**\n * - `bore`: revolute connector at the bore center, axis along +Z. Carries\n * measurements: `{ module, teeth, pitchRadius, outerRadius, faceWidth }`.\n *\n * Use `.connect("Housing.seat", "Gear.bore")` to mount a gear on a shaft seat.\n */\n\n spurGear(options: SpurGearOptions): Shape;\n/**\n * Conical bevel gear generated from a tapered involute extrusion. Specify pitchAngleDeg directly or derive it from mateTeeth + shaftAngleDeg.\n *\n * **Connectors (for assembly-based positioning):**\n * - `bore`: revolute connector at the large-end bore center (Z=0), axis along -Z (away from teeth).\n * - `apex`: connector at the cone apex above the gear (the point where the pitch cone converges),\n * axis along +Z. Useful for meshing two bevel gears — their apices should coincide.\n *\n * Carries measurements: `{ module, teeth, pitchRadius, pitchAngleDeg, coneDistance, faceWidth }`.\n */\n\n bevelGear(options: BevelGearOptions): Shape;\n/**\n * Face gear (crown style) where the teeth project axially from one face\n * (top or bottom) of the disk instead of the outer cylindrical rim.\n *\n * Uses the same involute tooth sizing as spurGear, then projects the tooth\n * band axially from the chosen side (`side`, default `\'top\'`) with axial\n * tooth height `toothHeight` (default: one module). To mesh it with a\n * perpendicular spur pinion, prefer `lib.faceGearPair()` — it rotates and\n * positions the pinion at the face tooth band for you.\n *\n * **Connectors (for assembly-based positioning):**\n *\n * - `bore`: revolute connector at the bore center on the body side (the face\n * opposite the teeth), axis pointing away from the teeth. Carries\n * measurements: `{ module, teeth, pitchRadius, outerRadius, faceWidth, toothSide }`.\n *\n * ```js\n * const crown = lib.faceGear({ module: 1.5, teeth: 30, faceWidth: 5, boreDiameter: 6 });\n * assembly().connect("Housing.crown_seat", "Crown.bore");\n * ```\n */\n\n faceGear(options: FaceGearOptions): Shape;\n /**\n * Legacy name for `lib.faceGear()` — identical arguments and geometry.\n * @softDeprecated lib.faceGear(options) — identical arguments\n * @deprecated use lib.faceGear(options) — identical arguments\n */\n sideGear: (options: SideGearOptions) => Shape;\n/**\n * Internal ring gear with involute-derived tooth spaces. Specify rimWidth or outerDiameter for the annular body.\n *\n * **Connectors (for assembly-based positioning):**\n * - `bore`: connector at the ring center, axis along +Z. For planetary gearboxes, this is\n * where the ring mounts to the housing. Carries measurements:\n * `{ module, teeth, pitchRadius, innerRadius, outerRadius, faceWidth }`.\n */\n\n ringGear(options: RingGearOptions): Shape;\n/**\n * Linear rack gear with pressure-angle flanks. Use with spurGear for rack-and-pinion mechanisms.\n *\n * **Orientation:** teeth run along the X axis with tooth tips pointing +Y (pitch line at Y=0).\n * The rack is extruded +Z by `faceWidth`. Rotate the rack to align with a different slide axis.\n *\n * **Connectors (for assembly-based positioning):**\n * - `teeth`: prismatic connector at the pitch line center, axis along +X (slide direction).\n * Carries measurements: `{ module, teeth, faceWidth, length }`.\n *\n * Connect to a housing\'s rack channel:\n * ```js\n * housing.withConnectors({\n * rack_channel: connector("rack-channel", {\n * origin: [pitchR, 0, channelZ], axis: [1, 0, 0], kind: "prismatic",\n * }),\n * });\n * assembly.connect("Housing.rack_channel", "Rack.teeth", { as: "slide" });\n * ```\n */\n\n rackGear(options: RackGearOptions): Shape;\n/**\n * Build or validate a spur-gear pair and return ratio, backlash, and mesh diagnostics.\n *\n * Accepts either shapes from spurGear() or analytical specs for each member.\n * When place is true (default), the gear is auto-positioned at the correct center distance.\n */\n\n gearPair(options: GearPairOptions): GearPairResult;\n/** Build or validate a bevel-gear pair and return ratio diagnostics plus recommended joint placement vectors. */\n\n bevelGearPair(options: BevelGearPairOptions): BevelGearPairResult;\n/**\n * Pair helper for a face (crown) gear + perpendicular "vertical" spur gear.\n * Auto-placement rotates the spur around +Y and positions it to mesh at the face tooth band.\n */\n\n faceGearPair(options: FaceGearPairOptions): FaceGearPairResult;\n /**\n * Legacy name for `lib.faceGearPair()` — same options with the side member renamed to face.\n * @softDeprecated lib.faceGearPair({ face, vertical, ... }) — same options with the side member renamed to face; diagnostics use facegear.* codes\n * @deprecated use lib.faceGearPair({ face, vertical, ... }) — same options with the side member renamed to face; diagnostics use facegear.* codes\n */\n sideGearPair: (options: SideGearPairOptions) => SideGearPairResult;\n/**\n * Coupling ratio between two meshed spur gears.\n *\n * When gear A turns 1°, gear B turns `-teethA / teethB` degrees (negative because\n * meshed external gears rotate in opposite directions).\n *\n * @example\n * ```js\n * const drivenPerDriver = lib.gearRatio(12, 24); // -0.5\n * verify.equal("external spur ratio", drivenPerDriver, -0.5);\n * ```\n *\n * Pass `{ internal: true }` for internal gear pairs (ring gear + spur/planet),\n * where the two rotate in the same direction.\n */\n\n gearRatio(teethA: number, teethB: number, options?: {\n internal?: boolean;\n}): number;\n/**\n * Coupling ratio between a pinion and a rack.\n *\n * When the pinion rotates by `θ` degrees, the rack slides by `θ × (π × module × teeth / 360)` mm.\n * Equivalently, 1mm of rack travel = `180 / (π × pitchRadius)` degrees of pinion rotation.\n *\n * @example\n * ```js\n * const pinionDegPerMm = lib.rackRatio(1.5, 12); // ~6.37 deg/mm\n * ```\n */\n\n rackRatio(module: number, pinionTeeth: number): number;\n/**\n * Planetary gear reduction ratio when the ring is held fixed.\n *\n * Input: sun. Output: carrier. Ratio: `1 + ringTeeth / sunTeeth`.\n * One turn of the sun produces `1 / ratio` turns of the carrier.\n */\n\n planetaryRatio(sunTeeth: number, ringTeeth: number): number;\n/**\n * Define a bolt pattern once and cut it from multiple parts.\n *\n * @example\n * ```js\n * const bolts = lib.boltPattern({\n * size: \'M5\',\n * positions: [[20, 15], [-20, 15], [20, -15], [-20, -15]],\n * });\n *\n * const base = bolts.cut(box(60, 50, 10), 12, { from: -1 });\n * const cover = bolts.cut(box(60, 50, 3), 5, { from: -1 });\n * // Same positions in both parts — guaranteed aligned.\n * ```\n *\n * @category Part Library\n */\n\n boltPattern(options: BoltPatternOptions): BoltPattern;\n /** Start a composable exceptional gear or drive wheel. */\n/**\n * Start a composable exceptional gear or drive wheel.\n */\n\n driveWheel(options?: DriveWheelOptions): DriveWheelBuilder;\n /** Read functional-region metadata from a drive wheel shape. */\n/**\n * Read the functional-region metadata attached by `driveWheel().build()`.\n */\n\n readDriveWheelMeta(shape: Shape): DriveWheelMeta | null;\n /** Involute sector gear with teeth on only part of the pitch circle. */\n/**\n * Involute sector gear with teeth on only part of the pitch circle.\n *\n * Specify the full-circle pitch as `teethOnFullCircle`, then choose the active\n * tooth window with `firstTooth` and `toothCount`. The body is separate from\n * the tooth region: pass a `gearBody...` shape for spokes, hubs, and product\n * styling, or omit it for a simple root-radius disk.\n *\n * **Example**\n *\n * ```ts\n * const body = lib.gearBodies.spoked({\n * outerRadius: 22, rimWidth: 3, hubDiameter: 10,\n * spokeCount: 5, spokeWidth: 2.5, faceWidth: 8, boreDiameter: 5,\n * });\n * const sector = lib.sectorGear({\n * module: 1.25, teethOnFullCircle: 36, toothCount: 10,\n * faceWidth: 8, body,\n * });\n * ```\n */\n\n sectorGear(options: SectorGearOptions): Shape;\n /** Gear body preset namespace: disk, diskWithHub, spoked, and fromProfile. */\n gearBodies: {\n readonly disk: typeof gearBodyDisk;\n readonly diskWithHub: typeof gearBodyDiskWithHub;\n readonly spoked: typeof gearBodySpoked;\n readonly fromProfile: typeof gearBodyFromProfile;\n };\n /**\n * Flat alias for `lib.gearBodies.disk()`.\n * @softDeprecated lib.gearBodies.disk(options) — identical arguments\n * @deprecated use lib.gearBodies.disk(options) — identical arguments\n */\n gearBodyDisk: (options: GearBodyDiskOptions) => Shape;\n /**\n * Flat alias for `lib.gearBodies.diskWithHub()`.\n * @softDeprecated lib.gearBodies.diskWithHub(options) — identical arguments\n * @deprecated use lib.gearBodies.diskWithHub(options) — identical arguments\n */\n gearBodyDiskWithHub: (options: GearBodyDiskWithHubOptions) => Shape;\n /**\n * Flat alias for `lib.gearBodies.spoked()`.\n * @softDeprecated lib.gearBodies.spoked(options) — identical arguments\n * @deprecated use lib.gearBodies.spoked(options) — identical arguments\n */\n gearBodySpoked: (options: GearBodySpokedOptions) => Shape;\n /**\n * Flat alias for `lib.gearBodies.fromProfile()`.\n * @softDeprecated lib.gearBodies.fromProfile(profile, options) — identical arguments\n * @deprecated use lib.gearBodies.fromProfile(profile, options) — identical arguments\n */\n gearBodyFromProfile: (profile: Sketch, options: GearBodyFromProfileOptions) => Shape;\n};\ninterface ProductMaterial {\n color?: string;\n material?: ShapeMaterialProps;\n}\ninterface ClearMaterialOptions {\n tint?: string;\n opacity?: number;\n}\ninterface ColorMaterialOptions {\n color?: string;\n}\ntype ProductProfileKind = "oval" | "roundedRect" | "circle" | "superEllipse" | "custom";\ninterface ProductProfileDescriptor {\n sketch: Sketch;\n width: number;\n depth: number;\n kind: ProductProfileKind;\n radius?: number;\n}\ninterface ProductProfileOptions {\n segments?: number;\n}\ninterface ProductSuperEllipseOptions extends ProductProfileOptions {\n /** Higher values produce squarer product surfaces; 2 is an ellipse. */\n exponent?: number;\n}\ntype ProductRailKind = "bezier" | "nurbs" | "polyline";\ninterface ProductRailSpec {\n kind: ProductRailKind;\n points: Vec3[];\n degree?: number;\n name?: string;\n}\ntype ProductScenePreset = "product" | "service" | "mechanical";\ninterface ProductStationProfile {\n sketch: Sketch;\n width: number;\n depth: number;\n kind: ProductProfileKind;\n radius?: number;\n exponent?: number;\n}\ninterface ProductStationSpec {\n name: string;\n center: Vec3;\n profile: ProductStationProfile;\n crown?: number;\n}\ninterface ProductStationSuperEllipseOptions {\n segments?: number;\n exponent?: number;\n}\ndeclare class ProductStationBuilder {\n readonly name: string;\n private centerValue;\n private profileValue?;\n private crownValue;\n constructor(name: string);\n /** Position this station in world coordinates. */\n at(point: Vec3): this;\n /** Convenience for traditional Z-up section stacks. */\n z(z: number): this;\n /** Convenience for product bodies running front-to-back along Y. */\n y(y: number): this;\n /** Convenience for product bodies running left-to-right along X. */\n x(x: number): this;\n /** Use an oval cross-section with full width and depth dimensions. */\n oval(width: number, depth: number, options?: {\n segments?: number;\n }): this;\n /** Use a superellipse cross-section for soft-square product surfaces. */\n superEllipse(width: number, depth: number, options?: ProductStationSuperEllipseOptions): this;\n /** Use a rounded-rectangle cross-section with the given corner radius. */\n roundedRect(width: number, depth: number, radius: number): this;\n /** Use a circular cross-section from a full diameter. */\n circle(diameter: number, options?: {\n segments?: number;\n }): this;\n /** Use a custom 2D sketch as the station cross-section. */\n custom(sketch: Sketch, width: number, depth: number): this;\n /** Set the station crown amount for soft product-section intent. */\n crown(amount: number): this;\n /** Return the immutable station spec consumed by Product.skin(). */\n toSpec(): ProductStationSpec;\n}\n/** Primary world axis used to order ProductSkin loft stations. */\ntype ProductSkinAxis = "X" | "Y" | "Z";\n/** Semantic side of a ProductSkin. `back` is accepted as an alias for `rear`. */\ntype ProductSkinSide = "left" | "right" | "top" | "bottom" | "front" | "rear" | "back";\n/** Reported lowering mode for ProductSkin and conformal feature diagnostics. */\ntype ProductSkinRepresentation = "exact" | "sampled" | "mixed" | "fallback";\ninterface ProductSkinRefQuery {\n /** Side of the product skin. `front` is the minimum axis cap, `rear`/`back` is the maximum axis cap. */\n side: ProductSkinSide;\n /** Across-side parameter for side refs. Defaults to 0.5. */\n u?: number;\n /** Along-axis parameter, 0 at the first cap and 1 at the rear/back cap. Defaults to 0.5. */\n v?: number;\n /** Positive distance away from the surface along the resolved normal. */\n offset?: number;\n}\ninterface ProductSurfaceFrame {\n point: Vec3;\n normal: Vec3;\n tangentU: Vec3;\n tangentV: Vec3;\n matrix: Mat4;\n skin: string;\n representation: ProductSkinRepresentation;\n}\ninterface ProductSkinDiagnostics {\n representation: ProductSkinRepresentation;\n lowering: string[];\n warnings: string[];\n stationNames: string[];\n railNames: string[];\n}\ninterface ProductAttachOptions {\n offset?: number;\n inset?: number;\n}\ninterface ProductPanelAttachOptions extends ProductAttachOptions {\n at?: Partial<ProductSkinRefQuery>;\n thickness?: number;\n material?: ProductMaterial;\n color?: string;\n}\ntype ProductRefInput = ProductSurfaceRef;\n/** Options shared by Product.ribbon() builders and Product.surface(...).ribbon(...). */\ninterface ProductRibbonBuildOptions {\n /** Width across the surface in millimeters. */\n width?: number;\n /** Solid thickness outward from the source surface in millimeters. */\n thickness?: number;\n /** Positive clearance between the source surface and the ribbon\'s inner face. */\n offset?: number;\n /** Samples along the ribbon path. Higher values bend more smoothly. */\n samples?: number;\n /** Samples across the ribbon width. Use 3+ to visibly wrap over curved cross-sections. */\n widthSamples?: number;\n /** Tessellation resolution passed to the lowered NURBS surface. */\n resolution?: number;\n /** Apply a product material preset to the ribbon. */\n material?: ProductMaterial;\n /** Apply a simple color override. */\n color?: string;\n}\ntype ProductSurfaceRibbonOptions = ProductRibbonBuildOptions;\n/** Path point for Product.ribbon().on(...): either a side/u/v query or a resolved surface ref. */\ntype ProductRibbonPathPoint = ProductSkinRefQuery | ProductSurfaceRef;\n/** Side-local path point for Product.surface(side).ribbon(...); the surface helper supplies `side`. */\ninterface ProductSurfacePathPoint {\n /** Across-side parameter on the bound side. Defaults to 0.5. */\n u?: number;\n /** Along-axis parameter, 0 at the first cap and 1 at the rear/back cap. Defaults to 0.5. */\n v?: number;\n /** Positive distance away from the surface along the resolved normal. */\n offset?: number;\n}\n/** Diagnostics describing how a conformal ribbon was sampled and lowered. */\ninterface ProductRibbonDiagnostics {\n /** Ribbon shape name. */\n name: string;\n /** Source skin name when the ribbon follows a ProductSkin directly. */\n skin?: string;\n /** Source skin side when all path points are on one semantic side. */\n side?: ProductSkinSide;\n /** Number of control path points supplied before interpolation. */\n pathPointCount: number;\n /** Final ribbon width in millimeters. */\n width: number;\n /** Final ribbon solid thickness in millimeters. */\n thickness: number;\n /** Final normal offset from the source surface in millimeters. */\n offset: number;\n /** Final sample count along the ribbon path. */\n samples: number;\n /** Final sample count across the ribbon width. */\n widthSamples: number;\n /** NURBS tessellation resolution used for the lowered surface. */\n resolution: number;\n /** Lowering primitive used for the ribbon shape. */\n lowering: "nurbsSurface";\n /** Expected fidelity inherited from the source skin/ref sampling mode. */\n expectedFidelity: ProductSkinRepresentation;\n /** Number of generated width samples clamped to the valid side span. */\n clampedUCount: number;\n /** Largest absolute u-distance lost to side-span clamping. */\n maxUClampDistance: number;\n /** Non-fatal sampling and lowering warnings. */\n warnings: string[];\n}\n/** Shape plus diagnostics returned by ProductRibbonBuilder.buildWithDiagnostics(). */\ninterface ProductRibbonResult {\n /** Lowered conformal ribbon shape. */\n shape: Shape;\n /** Sampling and lowering diagnostics for the returned shape. */\n diagnostics: ProductRibbonDiagnostics;\n}\ndeclare class ProductSurfaceRef {\n private readonly skin;\n private readonly query;\n readonly name?: string | undefined;\n constructor(skin: ProductSkin, query: ProductSkinRefQuery, name?: string | undefined);\n /** Resolve this semantic surface ref into a point, normal, tangents, and placement matrix. */\n frame(overrides?: Partial<ProductSkinRefQuery>): ProductSurfaceFrame;\n /** Return a copy of this ref with side/u/v/offset overrides. */\n with(overrides: Partial<ProductSkinRefQuery>): ProductSurfaceRef;\n /** Place a detail shape or group on this ref\'s local surface frame. */\n attach(detail: Shape | ShapeGroup, options?: ProductAttachOptions): Shape | ShapeGroup;\n /** Return the serializable side/u/v query behind this ref. */\n querySpec(): ProductSkinRefQuery;\n}\ndeclare class ProductSkin {\n readonly name: string;\n readonly shape: Shape;\n readonly axis: ProductSkinAxis;\n readonly stations: ProductStationSpec[];\n readonly rails: Record<string, ProductRailSpec>;\n private readonly refQueries;\n private readonly axisMin;\n private readonly axisMax;\n private readonly diagnosticsValue;\n constructor(name: string, shape: Shape, axis: ProductSkinAxis, stations: ProductStationSpec[], rails: Record<string, ProductRailSpec>, refs: Record<string, ProductSkinRefQuery>, diagnostics: Omit<ProductSkinDiagnostics, "stationNames" | "railNames">);\n /** Return the renderable shape generated for this product skin. */\n toShape(): Shape;\n /** Create a group containing this skin plus named child details. */\n with(...children: GroupInput[]): ShapeGroup;\n /** Boolean-union structural details into the skin body. */\n integrate(...details: Shape[]): Shape;\n /** Return lowering representation, station names, rail names, and warnings. */\n diagnostics(): ProductSkinDiagnostics;\n /** Create a side/u/v surface-ref query on this skin. */\n uv(side: ProductSkinSide, u?: number, v?: number): ProductSkinRefQuery;\n /** Resolve a named ref published with Product.skin().refs(...). */\n ref(name: string): ProductSurfaceRef;\n /** Create a sampled curve as a sequence of surface refs on this skin. */\n curveOnSurface(name: string, points: Array<Partial<ProductSkinRefQuery> & {\n side: ProductSkinSide;\n }>): ProductSurfaceRef[];\n /**\n * Create a fluent surface helper for refs and conformal features on one side of this skin.\n *\n * Use this when several refs or ribbons share the same skin side; side-local helpers keep\n * path points concise and make it harder to mix sides accidentally.\n */\n surface(side: ProductSkinSide): ProductSurfaceBuilder;\n /** Interpolate center, width, and depth at a normalized v or absolute axis value. */\n stationAt(vOrAxis: number): {\n center: Vec3;\n width: number;\n depth: number;\n dWidth: number;\n dDepth: number;\n axisValue: number;\n exponent: number;\n kind: ProductProfileKind;\n };\n /** Build a local surface frame from a side/u/v query. */\n frame(query: ProductSkinRefQuery): ProductSurfaceFrame;\n}\ndeclare class ProductSkinBuilder {\n readonly name: string;\n private axisValue;\n private stationsValue;\n private railsValue;\n private refsValue;\n private materialValue;\n private colorValue;\n private edgeLengthValue;\n private wallValue;\n constructor(name: string);\n /** Choose the primary station axis for the skin loft. */\n axis(axis: ProductSkinAxis): this;\n /** Set named cross-section stations for the product skin. */\n stations(stations: Array<ProductStationBuilder | ProductStationSpec>): this;\n /** Attach named guide rails for product-skin construction and downstream surface references. */\n rails(rails: Record<string, ProductRailSpec>): this;\n /** Publish a named semantic surface ref on the skin. */\n ref(name: string, query: ProductSkinRefQuery): this;\n /** Publish multiple named semantic surface refs on the skin. */\n refs(refs: Record<string, ProductSkinRefQuery>): this;\n /** Create a side/u/v surface-ref query for use in refs(...) or Product.ref(...). */\n uv(side: ProductSkinSide, u?: number, v?: number): ProductSkinRefQuery;\n /** Apply a product material preset to the lowered skin. */\n material(material: ProductMaterial): this;\n /** Apply a simple color override to the lowered skin. */\n color(color: string): this;\n /** Set the sampled loft target edge length. */\n edgeLength(value: number): this;\n /** Record intended wall thickness for product design metadata. Use explicit shelling when the model needs real inner-wall geometry. */\n wall(thickness: number): this;\n /** Lower stations and refs into a ProductSkin body. */\n build(): ProductSkin;\n}\ndeclare class ProductPanelBuilder {\n readonly name: string;\n private profileValue;\n private thicknessValue;\n private materialValue;\n private colorValue;\n constructor(name: string);\n /** Use a rounded rectangle panel profile. */\n rounded(width: number, height: number, radius?: number): this;\n /** Use an oval panel profile. */\n oval(width: number, height: number): this;\n /** Use a custom 2D panel profile. */\n profile(profile: Sketch): this;\n /** Set panel extrusion thickness. */\n thickness(thickness: number): this;\n /** Apply a product material preset to the panel. */\n material(material: ProductMaterial): this;\n /** Apply a simple color override to the panel. */\n color(color: string): this;\n /** Build the panel in local coordinates. */\n build(): Shape;\n /** Build and attach this panel to a ProductSurfaceRef. */\n attachTo(ref: ProductRefInput, options?: ProductPanelAttachOptions): Shape;\n}\n/**\n * Fluent builder behind the legacy `Product.spout()` feature.\n *\n * @softDeprecated Product.place(loft(sections, heights), ref) — loft local sections at origin, then place on the ProductSurfaceRef frame\n * @deprecated use Product.place(loft(sections, heights), ref) — loft local sections at origin, then place on the ProductSurfaceRef frame\n */\ndeclare class ProductSpoutBuilder {\n readonly name: string;\n private fromValue?;\n private sectionsValue;\n private projectionValue;\n private materialValue;\n private colorValue;\n private edgeLengthValue;\n constructor(name: string);\n /** Set the skin ref this spout projects from. */\n from(ref: ProductSurfaceRef): this;\n /** Set local spout section profiles from root to mouth. */\n sections(sections: Array<Sketch | ProductStationBuilder | ProductStationSpec>): this;\n /** Set the projection length along the source ref normal. */\n projection(length: number): this;\n /** Set the sampled loft target edge length for the spout. */\n edgeLength(value: number): this;\n /** Apply a product material preset to the spout. */\n material(material: ProductMaterial): this;\n /** Apply a simple color override to the spout. */\n color(color: string): this;\n /** Build the spout in local coordinates. */\n build(): Shape;\n /** Build and place the spout on its source ref. */\n attach(options?: ProductAttachOptions): Shape;\n}\n/**\n * Result bundle of the legacy `Product.handle()` builder.\n *\n * @softDeprecated build handles with sweep(gripProfile, spinePoints) plus Product.place(pad, ref) landing pads and group(...) the parts\n * @deprecated use build handles with sweep(gripProfile, spinePoints) plus Product.place(pad, ref) landing pads and group(...) the parts\n */\ndeclare class ProductHandleFeature {\n readonly grip: Shape;\n readonly upperPad: Shape;\n readonly lowerPad: Shape;\n constructor(grip: Shape, upperPad: Shape, lowerPad: Shape);\n /** Return the physical shapes that make up this handle feature. */\n structural(): Shape[];\n /** Boolean-union the handle feature into a single shape. */\n toShape(): Shape;\n /** Return the handle as a named ShapeGroup preserving child colors. */\n toGroup(): ShapeGroup;\n}\n/**\n * Fluent builder behind the legacy `Product.handle()` feature.\n *\n * @softDeprecated sweep(gripProfile, spinePoints) with spine points derived from ref.frame().point, plus Product.place(pad, ref) for landing pads\n * @deprecated use sweep(gripProfile, spinePoints) with spine points derived from ref.frame().point, plus Product.place(pad, ref) for landing pads\n */\ndeclare class ProductHandleBuilder {\n readonly name: string;\n private upperRef?;\n private lowerPoint?;\n private spineValue?;\n private gripProfile;\n private materialValue;\n private padMaterialValue;\n private edgeLengthValue;\n constructor(name: string);\n /** Set the upper body ref and lower world anchor for the handle. */\n between(upper: ProductSurfaceRef, lower: Vec3): this;\n /** Set an explicit handle centerline from points or a rail spec. */\n spine(points: Vec3[] | ProductRailSpec): this;\n /** Set the grip cross-section profile. */\n grip(profile: Sketch): this;\n /** Apply a product material preset to the grip. */\n material(material: ProductMaterial): this;\n /** Apply a product material preset to handle landing pads. */\n padMaterial(material: ProductMaterial): this;\n /** Set the sampled loft target edge length for the grip. */\n edgeLength(value: number): this;\n /** Build the handle grip and landing pads. */\n build(): ProductHandleFeature;\n}\n/** Fluent helper bound to one ProductSkin side for refs and side-local conformal features. */\ndeclare class ProductSurfaceBuilder {\n private readonly skin;\n readonly side: ProductSkinSide;\n constructor(skin: ProductSkin, side: ProductSkinSide);\n /** Create a ref on this skin side. */\n ref(u?: number, v?: number, offset?: number): ProductSurfaceRef;\n /** Create a side/u/v query on this skin side. */\n uv(u?: number, v?: number, offset?: number): ProductSkinRefQuery;\n /** Resolve a point/frame on this surface using the builder\'s side. */\n frame(query?: Partial<ProductSkinRefQuery>): ProductSurfaceFrame;\n /**\n * Start a conformal ribbon on this skin side.\n *\n * Path points use side-local `u`/`v` coordinates; this builder supplies the side.\n * The returned ProductRibbonBuilder is already bound to the source skin and can be further\n * configured before build(). Use `widthSamples` >= 3 when the ribbon must visibly wrap over\n * curved product sections instead of behaving like a flat strip.\n */\n ribbon(name: string, points: ProductSurfacePathPoint[], options?: ProductRibbonBuildOptions): ProductRibbonBuilder;\n}\n/** Builder for thin trim, label, grip, and split-line features that bend with a ProductSkin surface. */\ndeclare class ProductRibbonBuilder {\n readonly name: string;\n private skinValue?;\n private queryPath;\n private refPath;\n private widthValue;\n private thicknessValue;\n private offsetValue;\n private samplesValue;\n private widthSamplesValue;\n private resolutionValue;\n private materialValue;\n private colorValue;\n private lastDiagnosticsValue?;\n constructor(name: string);\n /**\n * Follow a ProductSkin with side/u/v path queries or refs.\n *\n * This is the highest-fidelity mode because every interpolated sample is resolved through\n * ProductSkin.frame(), so the ribbon bends along the selected side as station width/depth changes.\n * All query path points must stay on one side; split side transitions into separate ribbons.\n */\n on(skin: ProductSkin, points: ProductRibbonPathPoint[], options?: ProductRibbonBuildOptions): this;\n /**\n * Follow explicit surface refs.\n *\n * Useful for named refs or paths assembled elsewhere. The builder resolves each ref frame and\n * interpolates between those frames; use on(skin, points) when you need full skin-side sampling\n * between sparse control points.\n */\n fromRefs(points: ProductSurfaceRef[], options?: ProductRibbonBuildOptions): this;\n /** Set ribbon width in millimeters. */\n width(width: number): this;\n /** Set solid thickness outward from the source surface in millimeters. */\n thickness(thickness: number): this;\n /** Set positive clearance between the source surface and the ribbon\'s inner face. */\n offset(offset: number): this;\n /** Set samples along the path. */\n samples(samples: number): this;\n /** Set samples across the width. Use 3+ to bend over curved cross-sections. */\n widthSamples(samples: number): this;\n /** Set NURBS tessellation resolution. */\n resolution(resolution: number): this;\n /** Apply a product material preset. */\n material(material: ProductMaterial): this;\n /** Apply a simple color override. */\n color(color: string): this;\n /** Build a conformal ribbon as a thin NURBS surface solid. */\n build(options?: ProductRibbonBuildOptions): Shape;\n /**\n * Build a conformal ribbon and return surface-feature diagnostics.\n *\n * Use this while validating API usage or model fidelity; diagnostics report sampling counts,\n * side-span clamping, lowering mode, and warnings that should be visible in reviews.\n */\n buildWithDiagnostics(options?: ProductRibbonBuildOptions): ProductRibbonResult;\n /** Return diagnostics from the most recent build, if this builder has been built. */\n diagnostics(): ProductRibbonDiagnostics | undefined;\n private applyOptions;\n private centerDesiredNormal;\n private samplePathQuery;\n private buildSkinGrid;\n private buildRefGrid;\n private makeDiagnostics;\n private cloneDiagnostics;\n}\ndeclare const Product: {\n /** Start a named product skin builder. */\n skin(name: string): ProductSkinBuilder;\n /** Start a named cross-section station for Product.skin(...).stations(...). */\n station(name: string): ProductStationBuilder;\n /** Namespaced rail builders for product skin guide rails and handle spines. */\n rail: {\n bezier(points: Vec3[], options?: {\n name?: string;\n }): ProductRailSpec;\n nurbs(points: Vec3[], options?: {\n degree?: number;\n name?: string;\n }): ProductRailSpec;\n polyline(points: Vec3[], options?: {\n name?: string;\n }): ProductRailSpec;\n };\n /** Product profile helper namespace: oval, superEllipse, roundedRect, and circle — for stations, panels, trims, and openings. */\n profiles: {\n /** Create a centered oval profile using full product width and depth dimensions. */\n oval(width: number, depth: number, options?: ProductProfileOptions): Sketch;\n /** Create a centered superellipse profile for soft square-to-oval product sections. */\n superEllipse(width: number, depth: number, options?: ProductSuperEllipseOptions): Sketch;\n /** Create a centered rounded-rectangle product profile. */\n roundedRect(width: number, depth: number, radius: number): Sketch;\n /** Create a centered circular product profile from its diameter. */\n circle(diameter: number, options?: ProductProfileOptions): Sketch;\n };\n /** Namespaced product material presets for molded plastic, rubber, metal, and transparent parts. */\n materials: {\n mattePlastic(color?: string): ProductMaterial;\n softRubber(options?: ColorMaterialOptions): ProductMaterial;\n clearPolycarbonate(options?: ClearMaterialOptions): ProductMaterial;\n brushedSteel(options?: ColorMaterialOptions): ProductMaterial;\n };\n /** Apply a product material preset to a Shape. */\n applyMaterial(shape: Shape, preset: ProductMaterial | undefined): Shape;\n /** Apply an opinionated scene preset for product review renders. */\n scenePreset(name: ProductScenePreset): void;\n /**\n * Create a centered oval profile from full width/depth dimensions.\n *\n * @softDeprecated Product.profiles.oval(width, depth, options) — identical arguments\n * @deprecated use Product.profiles.oval(width, depth, options) — identical arguments\n */\n ovalProfile: (width: number, depth: number, options?: ProductProfileOptions) => Sketch;\n /**\n * Create a centered rounded-rectangle profile.\n *\n * @softDeprecated Product.profiles.roundedRect(width, depth, radius) — identical arguments\n * @deprecated use Product.profiles.roundedRect(width, depth, radius) — identical arguments\n */\n roundedRectProfile: (width: number, depth: number, radius: number) => Sketch;\n /**\n * Create a centered circular profile from full diameter.\n *\n * @softDeprecated Product.profiles.circle(diameter, options) — identical arguments\n * @deprecated use Product.profiles.circle(diameter, options) — identical arguments\n */\n circleProfile: (diameter: number, options?: ProductProfileOptions) => Sketch;\n /**\n * Create a centered superellipse profile for soft-square product sections.\n *\n * @softDeprecated Product.profiles.superEllipse(width, depth, options) — identical arguments\n * @deprecated use Product.profiles.superEllipse(width, depth, options) — identical arguments\n */\n superEllipseProfile: (width: number, depth: number, options?: ProductSuperEllipseOptions) => Sketch;\n /** Measure the width and depth of a 2D profile sketch. */\n profileSize(sketch: Sketch): {\n width: number;\n depth: number;\n };\n /** Describe a custom sketch as a product profile. */\n describeProfile(sketch: Sketch, kind?: ProductProfileKind, radius?: number): ProductProfileDescriptor;\n /** Scale an existing profile sketch to a target width/depth. */\n scaleProfileTo(sketch: Sketch, width: number, depth: number): Sketch;\n /** Create an ad-hoc ProductSurfaceRef from a skin and side/u/v query. */\n ref(skin: ProductSkin, query: ProductSkinRefQuery): ProductSurfaceRef;\n /**\n * Create a fluent surface helper for refs and conformal features on one side of a skin.\n *\n * Equivalent to skin.surface(side), useful when writing in Product.* namespace style.\n */\n surface(skin: ProductSkin, side: ProductSkinSide): ProductSurfaceBuilder;\n /** Start a panel feature builder. */\n panel(name: string): ProductPanelBuilder;\n /**\n * Start a conformal ribbon/trim builder for details that should bend with a ProductSkin.\n *\n * Call .on(skin, points) for side/u/v sampling or .fromRefs(points) for explicit surface refs,\n * then configure width, thickness, offset, sampling, material, and color before build().\n */\n ribbon(name: string): ProductRibbonBuilder;\n /**\n * Start a spout/nozzle feature builder.\n *\n * @softDeprecated Product.place(loft(sectionSketches, heights).as(\'spout\'), ref) — loft the local spout sections at origin, then place the result on the ProductSurfaceRef frame (or ref.attach(...) with offset/inset options)\n * @deprecated use Product.place(loft(sectionSketches, heights).as(\'spout\'), ref) — loft the local spout sections at origin, then place the result on the ProductSurfaceRef frame (or ref.attach(...) with offset/inset options)\n */\n spout: (name: string) => ProductSpoutBuilder;\n /**\n * Start a handle feature builder.\n *\n * @softDeprecated sweep(gripProfile, spinePoints) with spine points derived from ref.frame().point, plus Product.place(padShape, ref) for landing pads — composes loft/sweep with connector frames instead of a fixed handle archetype\n * @deprecated use sweep(gripProfile, spinePoints) with spine points derived from ref.frame().point, plus Product.place(padShape, ref) for landing pads — composes loft/sweep with connector frames instead of a fixed handle archetype\n */\n handle: (name: string) => ProductHandleBuilder;\n /** Place a shape or group on a ProductSurfaceRef. */\n place(detail: Shape | ShapeGroup, ref: ProductRefInput, options?: ProductAttachOptions): Shape | ShapeGroup;\n /** Small blended landing volume for manual structural bridges and connection proofs. */\n landing(name: string, radius?: number, material?: ProductMaterial): Shape;\n};\ntype SurfaceCarrierKind = "cylinder" | "plane" | "productSkin";\ninterface CylinderSurfaceCoordinate {\n kind?: "cylinder";\n angle: number;\n z: number;\n offset?: number;\n}\ninterface PlaneSurfaceCoordinate {\n kind?: "plane";\n x: number;\n y: number;\n offset?: number;\n}\ninterface ProductSkinSurfaceCoordinate {\n kind?: "productSkin";\n side?: ProductSkinSide;\n u?: number;\n v?: number;\n offset?: number;\n}\ntype SurfaceCoordinate = CylinderSurfaceCoordinate | PlaneSurfaceCoordinate | ProductSkinSurfaceCoordinate;\ninterface SurfaceFrame {\n point: Vec3;\n normal: Vec3;\n tangentAlong: Vec3;\n tangentAcross: Vec3;\n matrix: Mat4;\n carrier: string;\n representation: SurfaceCarrierKind | string;\n coordinate: SurfaceCoordinate;\n}\ntype SurfaceDiagnosticCode = "carrier.cylinder" | "carrier.plane" | "carrier.productSkinWarning" | "region.centerOutOfBounds" | "region.productSkinBoundaryOutOfBounds" | "region.zBoundaryOutOfBounds" | "region.boundarySelfIntersection" | "region.holeIntentRecorded" | "member.extendedToJunctionCapDeferred" | "feature.attached" | "feature.edgeRadiusNotRounded" | "feature.plateOpeningLowered" | "feature.plateCounterboreLowered" | "feature.bandOpeningLowered" | "feature.bandRibPatternLowered" | "feature.bandProfileRailsLowered" | "feature.bandCupWebLowered" | "feature.notLowered" | "join.anchorMissing" | "join.anchorAmbiguous" | "join.selectedAnchorIncomplete" | "join.insufficientOverlap" | "join.anchorsTooClose" | "join.radiusExceedsSpan" | "join.opposedNormals" | "join.multiTargetExplicit" | "join.memberUnjoined" | "join.explicitRecorded" | "join.endpointDistance" | "join.landingDistance" | "join.selectedAnchorDistance" | "join.noEndpointAnchors" | "join.autoAmbiguousSharedEndpoint" | "join.autoSharedEndpointLowered" | "join.autoNoSharedEndpoints" | "join.junctionNodeCompiled" | "join.junctionLowered" | "compiler.bandFeatureOpeningsLowered" | "compiler.bandSweepLowered" | "compiler.plateFeatureOpeningsLowered" | "compiler.platePrismLowered" | "compiler.mirroredMemberLowered" | "compiler.debugBuild";\ninterface SurfaceDiagnostic {\n category: "carrier" | "path" | "region" | "member" | "feature" | "join" | "compiler";\n level: "info" | "warning" | "error";\n message: string;\n subject?: string;\n /** Stable machine-readable repair hook. Messages may change; codes should not. */\n code?: SurfaceDiagnosticCode;\n}\ninterface SurfaceBounds {\n u?: [\n number,\n number\n ];\n v?: [\n number,\n number\n ];\n angle?: [\n number,\n number\n ];\n z?: [\n number,\n number\n ];\n x?: [\n number,\n number\n ];\n y?: [\n number,\n number\n ];\n}\ninterface SurfaceAnchor<C extends SurfaceCoordinate = SurfaceCoordinate> {\n carrier: CarrierSurface<C>;\n coordinate: C;\n frame(): SurfaceFrame;\n}\ninterface CarrierSurface<C extends SurfaceCoordinate = SurfaceCoordinate> {\n readonly name: string;\n readonly kind: SurfaceCarrierKind;\n pointAt(coordinate: C): Vec3;\n mirrorPoint?(point: Vec3): Vec3;\n frameAt(coordinate: C, tangentHint?: Vec3): SurfaceFrame;\n normalAt(coordinate: C): Vec3;\n tangentAt(coordinate: C, tangentHint?: Vec3): Vec3;\n bounds(): SurfaceBounds;\n offset(distance: number): CarrierSurface<C>;\n diagnostics(): SurfaceDiagnostic[];\n mirrorCoordinate(coordinate: C): C;\n}\ninterface SurfacePathSample<C extends SurfaceCoordinate = SurfaceCoordinate> {\n t: number;\n coordinate: C;\n point: Vec3;\n frame: SurfaceFrame;\n}\ntype AngleMode = "shortest" | "explicit";\ndeclare class SurfacePath<C extends SurfaceCoordinate = SurfaceCoordinate> {\n readonly carrier: CarrierSurface<C>;\n readonly points: C[];\n readonly closedValue: boolean;\n private readonly angleMode;\n constructor(carrier: CarrierSurface<C>, points: C[], closedValue?: boolean, angleMode?: AngleMode);\n closed(): SurfacePath<C>;\n mirror(): SurfacePath<C>;\n coordinateAt(t: number): C;\n sample(count?: number): SurfacePathSample<C>[];\n length(samples?: number): number;\n}\ndeclare class SurfacePathBuilder<C extends SurfaceCoordinate = SurfaceCoordinate> {\n readonly carrier: CarrierSurface<C>;\n private pointsValue;\n private closedValue;\n private angleMode;\n constructor(carrier: CarrierSurface<C>);\n from(coordinate: C): this;\n through(coordinate: C): this;\n to(coordinate: C): this;\n around(input: {\n z: number;\n fromAngle: number;\n toAngle: number;\n offset?: number;\n }): this;\n closed(): this;\n mirror(): SurfacePath<C>;\n build(): SurfacePath<C>;\n sample(count?: number): SurfacePathSample<C>[];\n}\ndeclare class CylinderCarrier implements CarrierSurface<CylinderSurfaceCoordinate> {\n readonly name: string;\n readonly kind: "cylinder";\n private radiusValue;\n private heightValue;\n private clearanceValue;\n private centerValue;\n constructor(name: string);\n diameter(value: number): this;\n radius(value: number): this;\n height(value: number): this;\n clearance(value: number): this;\n center(point: Vec3): this;\n path(): SurfacePathBuilder<CylinderSurfaceCoordinate>;\n anchor(angle: number, z?: number, options?: {\n offset?: number;\n }): SurfaceAnchor<CylinderSurfaceCoordinate>;\n front(options?: {\n z?: number;\n offset?: number;\n }): SurfaceAnchor<CylinderSurfaceCoordinate>;\n back(options?: {\n z?: number;\n offset?: number;\n }): SurfaceAnchor<CylinderSurfaceCoordinate>;\n left(options?: {\n z?: number;\n offset?: number;\n }): SurfaceAnchor<CylinderSurfaceCoordinate>;\n right(options?: {\n z?: number;\n offset?: number;\n }): SurfaceAnchor<CylinderSurfaceCoordinate>;\n top(options?: {\n angle?: number;\n offset?: number;\n }): SurfaceAnchor<CylinderSurfaceCoordinate>;\n bottom(options?: {\n angle?: number;\n offset?: number;\n }): SurfaceAnchor<CylinderSurfaceCoordinate>;\n pointAt(coordinate: CylinderSurfaceCoordinate): Vec3;\n mirrorPoint(point: Vec3): Vec3;\n normalAt(coordinate: CylinderSurfaceCoordinate): Vec3;\n tangentAt(coordinate: CylinderSurfaceCoordinate, tangentHint?: Vec3): Vec3;\n frameAt(coordinate: CylinderSurfaceCoordinate, tangentHint?: Vec3): SurfaceFrame;\n bounds(): SurfaceBounds;\n offset(distance: number): CylinderCarrier;\n diagnostics(): SurfaceDiagnostic[];\n mirrorCoordinate(coordinate: CylinderSurfaceCoordinate): CylinderSurfaceCoordinate;\n radiusValueWithClearance(): number;\n}\ndeclare class PlaneCarrier implements CarrierSurface<PlaneSurfaceCoordinate> {\n readonly name: string;\n readonly kind: "plane";\n private widthValue;\n private heightValue;\n private originValue;\n private normalValue;\n private xAxisValue;\n private offsetValue;\n constructor(name: string);\n size(width: number, height: number): this;\n origin(point: Vec3): this;\n normal(normal: Vec3): this;\n path(): SurfacePathBuilder<PlaneSurfaceCoordinate>;\n anchor(x?: number, y?: number, options?: {\n offset?: number;\n }): SurfaceAnchor<PlaneSurfaceCoordinate>;\n left(options?: {\n y?: number;\n offset?: number;\n }): SurfaceAnchor<PlaneSurfaceCoordinate>;\n right(options?: {\n y?: number;\n offset?: number;\n }): SurfaceAnchor<PlaneSurfaceCoordinate>;\n top(options?: {\n x?: number;\n offset?: number;\n }): SurfaceAnchor<PlaneSurfaceCoordinate>;\n bottom(options?: {\n x?: number;\n offset?: number;\n }): SurfaceAnchor<PlaneSurfaceCoordinate>;\n pointAt(coordinate: PlaneSurfaceCoordinate): Vec3;\n mirrorPoint(point: Vec3): Vec3;\n normalAt(): Vec3;\n tangentAt(coordinate: PlaneSurfaceCoordinate, tangentHint?: Vec3): Vec3;\n frameAt(coordinate: PlaneSurfaceCoordinate, tangentHint?: Vec3): SurfaceFrame;\n bounds(): SurfaceBounds;\n offset(distance: number): PlaneCarrier;\n diagnostics(): SurfaceDiagnostic[];\n mirrorCoordinate(coordinate: PlaneSurfaceCoordinate): PlaneSurfaceCoordinate;\n}\ninterface ProductSkinSideTransitionInput {\n name?: string;\n v?: number;\n offset?: number;\n}\ninterface ProductSkinSideTransition {\n name?: string;\n from: ProductSkinSurfaceCoordinate;\n to: ProductSkinSurfaceCoordinate;\n}\ninterface ProductSkinSideRouteInput {\n name?: string;\n sides: ProductSkinSide[];\n from: ProductSkinSurfaceCoordinate;\n to: ProductSkinSurfaceCoordinate;\n v?: number;\n offset?: number;\n}\ninterface ProductSkinSideRouteSegment {\n name: string;\n side: ProductSkinSide;\n from: ProductSkinSurfaceCoordinate;\n to: ProductSkinSurfaceCoordinate;\n startAnchorName?: string;\n endAnchorName?: string;\n}\ninterface ProductSkinSideRoute {\n name?: string;\n transitions: ProductSkinSideTransition[];\n segments: ProductSkinSideRouteSegment[];\n}\ndeclare class ProductSkinCarrier implements CarrierSurface<ProductSkinSurfaceCoordinate> {\n readonly skin: ProductSkin;\n readonly name: string;\n private readonly sideValue?;\n private readonly offsetValue;\n readonly kind: "productSkin";\n constructor(skin: ProductSkin, name?: string, sideValue?: ProductSkinSide | undefined, offsetValue?: number);\n surface(side: ProductSkinSide): ProductSkinCarrier;\n path(): SurfacePathBuilder<ProductSkinSurfaceCoordinate>;\n /**\n * Return matching side-local coordinates for an explicit split-member transition.\n *\n * Each SurfacePath still stays on one ProductSkin side. Use this helper to create\n * one member ending on `from`, another starting on `to`, then join named anchors.\n *\n * Rules: only adjacent `left`/`top`/`right`/`bottom` sides are supported — for\n * front/rear caps use `Product.panel()`. `v` is normalized 0–1 along the shared\n * boundary (default 0.5); `name` must be non-empty when provided; `offset` lifts\n * both coordinates off the surface. Throws if the returned boundary coordinates\n * are not physically coincident — check side order, `v`, and `offset`.\n */\n sideTransition(fromSide: ProductSkinSide, toSide: ProductSkinSide, input?: ProductSkinSideTransitionInput): ProductSkinSideTransition;\n /**\n * Return a sequence of matching side-local coordinates for an explicit multi-side split-member route.\n *\n * Each adjacent side pair becomes one named transition. Build one member per side\n * segment, add transition anchors at each returned pair, then join the anchors.\n * The same validation as `sideTransition()` applies to every adjacent pair.\n */\n sideTransitionChain(sides: ProductSkinSide[], input?: ProductSkinSideTransitionInput): ProductSkinSideTransition[];\n /**\n * Return side-local member segments for a generated multi-side split-member route.\n *\n * The route still compiles as explicit members plus named-anchor joins. This\n * helper only generates the per-side segment endpoints and transition names.\n */\n sideRoute(input: ProductSkinSideRouteInput): ProductSkinSideRoute;\n pointAt(coordinate: ProductSkinSurfaceCoordinate): Vec3;\n mirrorPoint(point: Vec3): Vec3;\n normalAt(coordinate: ProductSkinSurfaceCoordinate): Vec3;\n tangentAt(coordinate: ProductSkinSurfaceCoordinate, tangentHint?: Vec3): Vec3;\n frameAt(coordinate: ProductSkinSurfaceCoordinate, tangentHint?: Vec3): SurfaceFrame;\n bounds(): SurfaceBounds;\n offset(distance: number): ProductSkinCarrier;\n diagnostics(): SurfaceDiagnostic[];\n mirrorCoordinate(coordinate: ProductSkinSurfaceCoordinate): ProductSkinSurfaceCoordinate;\n}\ntype MemberFeatureType = "roundedSlot" | "roundedCutout" | "counterbore" | "lip" | "cup" | "ribPattern";\ninterface MemberFeature {\n type: MemberFeatureType;\n name?: string;\n length?: number;\n width?: number;\n diameter?: number;\n counterboreDiameter?: number;\n clearanceDiameter?: number;\n height?: number;\n depth?: number;\n count?: number;\n along?: number;\n across?: number;\n verticalTravel?: number;\n}\ndeclare class RoundedSlotBuilder {\n private readonly lengthValue;\n private readonly widthValue;\n private alongValue?;\n private acrossValue?;\n private verticalTravelValue;\n constructor(lengthValue: number, widthValue: number);\n verticalTravel(value: number): this;\n at(input: {\n along?: number;\n across?: number;\n z?: number;\n }): this;\n named(name: string): MemberFeature;\n toFeature(name?: string): MemberFeature;\n}\ndeclare class CounterboreBuilder {\n private readonly counterboreDiameterValue;\n private readonly clearanceDiameterValue;\n private readonly depthValue;\n private alongValue?;\n private acrossValue?;\n constructor(counterboreDiameterValue: number, clearanceDiameterValue: number, depthValue: number);\n at(input: {\n along?: number;\n across?: number;\n z?: number;\n }): this;\n named(name: string): MemberFeature;\n toFeature(name?: string): MemberFeature;\n}\n/** Legacy single-function namespace — kept runtime-alive for existing scripts. */\ndeclare const Slot: {\n /**\n * Create a rounded member-local slot feature.\n *\n * @softDeprecated SurfaceMembers.roundedSlot({ length, width }) — same builder, same fluent chain\n * @deprecated use SurfaceMembers.roundedSlot({ length, width }) — same builder, same fluent chain\n */\n rounded: (input: {\n length: number;\n width: number;\n }) => RoundedSlotBuilder;\n};\n/** Legacy single-function namespace — kept runtime-alive for existing scripts. */\ndeclare const Counterbore: {\n /**\n * Create a cylindrical member-local counterbore feature.\n *\n * @softDeprecated SurfaceMembers.counterbore({ diameter, clearanceDiameter, depth }) — same builder, same fluent chain\n * @deprecated use SurfaceMembers.counterbore({ diameter, clearanceDiameter, depth }) — same builder, same fluent chain\n */\n cylindrical: (input: {\n diameter: number;\n clearanceDiameter: number;\n depth: number;\n }) => CounterboreBuilder;\n};\n/** Legacy single-function namespace — kept runtime-alive for existing scripts. */\ndeclare const Ribs: {\n /**\n * Create repeated ribs that belong to a surface member before lowering.\n *\n * @softDeprecated SurfaceMembers.ribs({ count, height }) — same feature object\n * @deprecated use SurfaceMembers.ribs({ count, height }) — same feature object\n */\n repeated: (input: {\n count: number;\n height: number;\n }) => MemberFeature;\n};\ntype MemberOutwardDirection = "outside" | "inside";\ninterface MemberSectionStation {\n t: number;\n width?: number;\n thickness?: number;\n}\ninterface MemberSectionInput {\n width?: number;\n thickness: number;\n edgeRadius?: number;\n direction?: MemberOutwardDirection;\n material?: ProductMaterial;\n stations?: MemberSectionStation[];\n}\ninterface MemberSection {\n width?: number;\n thickness: number;\n edgeRadius: number;\n direction: MemberOutwardDirection;\n material?: ProductMaterial;\n stations: MemberSectionStation[];\n}\ntype SurfaceMemberAnchorRole = "start" | "end" | "center" | "landing" | "feature" | "boundary" | "explicit";\ninterface SurfaceMemberAnchorIR {\n role: SurfaceMemberAnchorRole;\n kind: "member-end" | "member-center" | "member-landing" | "member-boundary" | "plate-edge" | "plate-center" | "plate-landing" | "feature-center" | "surface-coordinate";\n name?: string;\n point: Vec3;\n normal?: Vec3;\n}\ninterface SurfaceMemberIR {\n name: string;\n kind: SurfaceMemberKind;\n section: MemberSection;\n features: MemberFeature[];\n mirrorOf?: string;\n anchors?: SurfaceMemberAnchorIR[];\n}\ninterface SurfaceJoinIR {\n from: string;\n to: string[];\n fromAnchor?: string;\n toAnchor?: string;\n radius?: number;\n style?: string;\n priority?: number;\n continuity?: string;\n name?: string;\n}\ninterface SurfaceBodyIR {\n name: string;\n carrier?: string;\n members: SurfaceMemberIR[];\n joins: SurfaceJoinIR[];\n diagnostics: SurfaceDiagnostic[];\n}\ninterface MemberMesh {\n vertices: Vec3[];\n triangles: Array<[\n number,\n number,\n number\n ]>;\n}\ntype SurfaceBandCap = "square" | "round" | "tapered" | "extended-to-junction";\ntype WidthEasing = "linear" | "easeInOut";\ntype WidthProfile = number | ((t: number) => number) | {\n stations: Array<{\n t: number;\n width: number;\n }>;\n easing?: WidthEasing;\n} | {\n from: number;\n to: number;\n easing?: WidthEasing;\n};\ninterface SurfaceBandBoundarySample {\n t: number;\n left: Vec3;\n right: Vec3;\n center: Vec3;\n width: number;\n}\ninterface SurfaceBandHoleInput {\n length: number;\n width: number;\n along?: number;\n across?: number;\n}\ninterface SurfaceBandHoleRegion {\n name: string;\n kind: "rounded-slot";\n centerAcross: number;\n centerAlong: number;\n length: number;\n width: number;\n loop: Array<[\n number,\n number\n ]>;\n}\ninterface SurfaceRegion {\n readonly kind: string;\n boundaries(samples?: number): SurfaceBandBoundarySample[];\n /** Member-local holes or cut regions recorded on this surface region, when supported by the region type. */\n holes?(): SurfaceBandHoleRegion[];\n diagnostics?(samples?: number): SurfaceDiagnostic[];\n}\ndeclare class SurfaceBand<C extends SurfaceCoordinate = SurfaceCoordinate> implements SurfaceRegion {\n readonly centerPath: SurfacePath<C>;\n readonly widthProfile: WidthProfile;\n readonly capStyle: SurfaceBandCap;\n readonly kind = "band";\n private holeInputs;\n constructor(centerPath: SurfacePath<C>, widthProfile: WidthProfile, capStyle?: SurfaceBandCap);\n widthAt(t: number): number;\n boundaries(samples?: number): SurfaceBandBoundarySample[];\n /** Return a new band with a named member-local rounded-slot hole region recorded as inspectable intent. */\n withHole(name: string, input: SurfaceBandHoleInput): SurfaceBand<C>;\n /** Resolve recorded hole regions into member-local across/along loops. */\n holes(): SurfaceBandHoleRegion[];\n diagnostics(samples?: number): SurfaceDiagnostic[];\n}\ndeclare function surfaceBand<C extends SurfaceCoordinate>(path: SurfacePath<C> | SurfacePathBuilder<C>, width: WidthProfile, cap?: SurfaceBandCap): SurfaceBand<C>;\ntype SurfaceMemberKind = "band" | "plate";\ninterface SurfaceMemberExplicitAnchor<C extends SurfaceCoordinate = SurfaceCoordinate> {\n name: string;\n coordinate: C;\n carrierName?: string;\n}\ninterface SurfaceMemberSpec<C extends SurfaceCoordinate = SurfaceCoordinate> {\n name: string;\n kind: SurfaceMemberKind;\n path?: SurfacePath<C> | SurfacePathBuilder<C>;\n anchor?: SurfaceAnchor<C>;\n size?: {\n width: number;\n height: number;\n };\n section: MemberSection;\n features: MemberFeature[];\n capStyle?: SurfaceBandCap;\n explicitAnchors?: SurfaceMemberExplicitAnchor<C>[];\n}\ninterface CompiledSurfaceMember {\n name: string;\n shape: Shape;\n mesh: MemberMesh;\n diagnostics: SurfaceDiagnostic[];\n anchors: {\n start?: CompiledSurfaceMemberAnchor;\n end?: CompiledSurfaceMemberAnchor;\n center: CompiledSurfaceMemberAnchor;\n landings?: CompiledSurfaceMemberAnchor[];\n boundaries?: CompiledSurfaceMemberAnchor[];\n features?: CompiledSurfaceMemberAnchor[];\n explicit?: CompiledSurfaceMemberAnchor[];\n };\n}\ninterface CompiledSurfaceMemberAnchor extends SurfaceMemberAnchorIR {\n frame?: SurfaceFrame;\n}\ndeclare function compileSurfaceMember<C extends SurfaceCoordinate>(input: Omit<SurfaceMemberSpec<C>, "section"> & {\n section: MemberSection | MemberSectionInput;\n}): CompiledSurfaceMember;\ninterface SurfaceBodyDiagnostics {\n name: string;\n memberCount: number;\n joinCount: number;\n lowering: "directMemberMeshes";\n messages: SurfaceDiagnostic[];\n}\ninterface SurfaceBodyBuildResult {\n shape: Shape | ShapeGroup;\n diagnostics: SurfaceBodyDiagnostics;\n ir: SurfaceBodyIR;\n}\ninterface MemberRecord<C extends SurfaceCoordinate = SurfaceCoordinate> {\n spec: Partial<SurfaceMemberSpec<C>> & {\n name: string;\n };\n features: MemberFeature[];\n mirrorOf?: string;\n}\ndeclare class SurfaceJoinBuilder {\n private readonly body;\n private readonly join;\n constructor(body: SurfaceBodyBuilder, join: SurfaceJoinIR);\n /** Select named anchors on the source and target members before lowering this join. */\n betweenAnchors(fromAnchor: string, toAnchor: string): this;\n blend(input?: {\n radius?: number;\n style?: string;\n priority?: number;\n continuity?: string;\n }): SurfaceBodyBuilder;\n}\ndeclare class SurfaceMemberBuilder<C extends SurfaceCoordinate = SurfaceCoordinate> {\n private readonly body;\n private readonly record;\n constructor(body: SurfaceBodyBuilder, record: MemberRecord<C>);\n plate(): this;\n band(): this;\n at(anchor: SurfaceAnchor<C>): this;\n size(width: number, height: number): this;\n path(path: SurfacePath<C> | SurfacePathBuilder<C>): this;\n section(section: MemberSectionInput): this;\n cap(style: SurfaceBandCap): this;\n slot(name: string, feature: MemberFeature | RoundedSlotBuilder): this;\n cutout(name: string, feature: MemberFeature | RoundedSlotBuilder): this;\n counterbore(name: string, feature: MemberFeature | CounterboreBuilder): this;\n /** Add a named anchor at a carrier surface coordinate for explicit member joins. */\n anchorAt(name: string, coordinate: C | SurfaceAnchor<C>): this;\n features(features: MemberFeature | MemberFeature[]): this;\n profile(name: string, options?: {\n depth?: number;\n height?: number;\n }): this;\n mirrorOf(memberName: string): SurfaceBodyBuilder;\n member(name: string): SurfaceMemberBuilder;\n join(from: string, to: string | string[]): SurfaceJoinBuilder;\n autoJoinAtSharedAnchors(): SurfaceBodyBuilder;\n build(): Shape | ShapeGroup;\n buildWithDiagnostics(): SurfaceBodyBuildResult;\n buildDebug(): SurfaceBodyBuildResult;\n}\n/**\n * Builder for a named surface-member body. Owns named members — `band()` or `plate()` —\n * and the joins between them. Features (slots, cutouts, lips, cups, ribs) attach to a\n * member\'s local coordinate system before lowering; this is not a global boolean recipe.\n */\ndeclare class SurfaceBodyBuilder {\n readonly name: string;\n private carrierValue?;\n private records;\n private joinsValue;\n constructor(name: string);\n carrier(carrier: CarrierSurface): this;\n member(name: string): SurfaceMemberBuilder;\n /**\n * Declare a join between named members. Only a limited join set lowers to real\n * geometry: close endpoint pairs, selected named-anchor pairs (`.betweenAnchors()`),\n * and sampled band/plate landing pads. Farther, missing-anchor, or ambiguous joins\n * remain diagnostic-only intent — decompose the design into supported joins instead\n * of expecting a fallback.\n */\n join(from: string, to: string | string[]): SurfaceJoinBuilder;\n /**\n * Lower only unambiguous shared-endpoint pairs (exactly two members sharing a point)\n * into junction geometry. Shared points with more than two members produce a warning\n * diagnostic — declare explicit `.join(...)` relationships instead.\n */\n autoJoinAtSharedAnchors(): this;\n /** Build and return only the member + junction geometry. Use `buildWithDiagnostics()` for the member graph and diagnostic codes. */\n build(): Shape | ShapeGroup;\n /**\n * Build geometry plus a serializable member graph (IR) and diagnostics. Every\n * diagnostic carries a stable `code` field (e.g. `region.centerOutOfBounds`) —\n * repair loops must match on `code`, never on message prose. Clipped or crossing\n * regions and invalid joins are reported as diagnostics, never silently accepted\n * as valid geometry.\n */\n buildWithDiagnostics(): SurfaceBodyBuildResult;\n /** `buildWithDiagnostics()` plus visible debug markers: anchors, join connections/radii, band centerlines and boundary rails, frame axes, and feature outlines. */\n buildDebug(): SurfaceBodyBuildResult;\n private buildJoinDebugShapes;\n private buildPathDebugShapes;\n private buildFeatureDebugShapes;\n private debugFeatureOutlineMesh;\n private debugPlateFeatureOutlineMesh;\n private debugBandFeatureOutlineMesh;\n private debugBandRibPatternMesh;\n private debugBandProfileFeatureMesh;\n private compileMemberSpecs;\n private describeJoinAnchors;\n private lowerEndpointJunctions;\n private lowerAutoSharedEndpointJunctions;\n private resolveMemberSpecs;\n private completeRecord;\n private resolveMirror;\n private toIR;\n}\n/**\n * Start a surface-member body builder for straps, inlays, guards, braces, cuffs, and similar\n * physical members that live on a carrier surface.\n *\n * @example\n * const carrier = Carrier.cylinder(\'guard-envelope\').diameter(84).height(36).clearance(2);\n * const guard = SurfaceBody(\'simple-guard\')\n * .carrier(carrier)\n * .member(\'left-strut\')\n * .band()\n * .path(carrier.path().from({ angle: -132, z: 6 }).to({ angle: -58, z: 18 }))\n * .section({ width: 5.5, thickness: 2.8, edgeRadius: 0.6 })\n * .member(\'right-strut\')\n * .mirrorOf(\'left-strut\')\n * .member(\'front-hoop\')\n * .band()\n * .path(carrier.path().around({ z: 18, fromAngle: -58, toAngle: 58 }))\n * .section({ width: 6.2, thickness: 3, edgeRadius: 0.7 })\n * .join(\'left-strut\', \'front-hoop\').blend({ radius: 3.2 })\n * .join(\'right-strut\', \'front-hoop\').blend({ radius: 3.2 })\n * .build();\n */\ndeclare function SurfaceBody(name: string): SurfaceBodyBuilder;\n/**\n * Factory for carrier surfaces — the coordinate-and-frame owners that surface members\n * (`SurfaceBody`) live on.\n *\n * **Details**\n *\n * A carrier owns surface-local coordinates and 3D frames; members and paths are authored\n * in carrier coordinates, never in raw Cartesian math. Cylinder coordinates are\n * `{ angle, z }` with `angle` in degrees — paths handle seam wrapping, so never compute\n * positions with trig. `clearance()`/`offset()` lift geometry off the nominal surface.\n * A ProductSkin carrier path stays on one side (`left`/`right`/`top`/`bottom`); for\n * multi-side detail, split into one member per side and join them at the matching\n * side-local coordinates from `sideTransition()` / `sideTransitionChain()` / `sideRoute()`.\n *\n * **Example**\n *\n * ```ts\n * // Bottle-cage arm: a curved band on a cylinder, authored in degrees + mm\n * const bottle = Carrier.cylinder(\'bottle\').diameter(74).height(170).clearance(1.5);\n * const arm = bottle.path()\n * .from({ angle: -145, z: 18 })\n * .through({ angle: -80, z: 72 })\n * .to({ angle: -34, z: 112 });\n * ```\n */\ndeclare const Carrier: {\n /** Create an analytic cylinder carrier for bottles, limbs, tubes, guards, and cuffs. */\n cylinder(name: string): CylinderCarrier;\n /** Create an analytic plane carrier for plates and local flat construction surfaces. */\n plane(name: string): PlaneCarrier;\n /** Adapt an existing ProductSkin into the general surface-member carrier protocol. */\n productSkin(skin: ProductSkin): ProductSkinCarrier;\n /** Reserved stub for future parameterized mesh carriers; arbitrary mesh parameterization is not implemented yet. */\n mesh(name: string): never;\n /** Reserved stub for future scan/body-fit carriers; arbitrary scan parameterization is not implemented yet. */\n scan(name: string): never;\n};\ndeclare const SurfaceMembers: {\n Body: typeof SurfaceBody;\n Band: typeof SurfaceBand;\n band: typeof surfaceBand;\n compileMember: typeof compileSurfaceMember;\n /**\n * Create a rounded member-local slot feature for `SurfaceMemberBuilder.slot()`/`.cutout()`.\n *\n * Returns a fluent `RoundedSlotBuilder`: chain `.verticalTravel(mm)` to extend\n * the slot for vertical bottle-drop style insertion (travel is summed into the\n * slot length) and `.at({ along, across })` (or `{ z }`) to position it in\n * member-local coordinates.\n *\n * @example\n * const arm = body.member(\'arm\', armPath)\n * .slot(\'upper-mount-slot\', SurfaceMembers.roundedSlot({ length: 12, width: 5.7 }).verticalTravel(6).at({ z: 82 }));\n */\n roundedSlot(input: {\n length: number;\n width: number;\n }): RoundedSlotBuilder;\n /**\n * Create a cylindrical member-local counterbore feature for `SurfaceMemberBuilder.counterbore()`.\n *\n * `diameter` is the counterbore pocket diameter and must be larger than\n * `clearanceDiameter`, the through-hole for the fastener shank. Chain\n * `.at({ along, across })` (or `{ z }`) to position it in member-local\n * coordinates.\n *\n * @example\n * const strap = body.member(\'strap\', strapPath)\n * .counterbore(\'head-pocket\', SurfaceMembers.counterbore({ diameter: 9.8, clearanceDiameter: 5.7, depth: 3 }).at({ z: 58 }));\n */\n counterbore(input: {\n diameter: number;\n clearanceDiameter: number;\n depth: number;\n }): CounterboreBuilder;\n /**\n * Create a repeated-rib stiffening feature for `SurfaceMemberBuilder.features()`.\n *\n * Ribs belong to the surface member and follow its carrier-surface lowering;\n * `count` ribs of the given `height` are distributed along the member.\n *\n * @example\n * const grip = body.member(\'grip\', gripPath)\n * .features(SurfaceMembers.ribs({ count: 18, height: 0.35 }));\n */\n ribs(input: {\n count: number;\n height: number;\n }): MemberFeature;\n};\ndeclare function distance(a: Vec3, b: Vec3): number;\ndeclare function midpoint(a: Vec3, b: Vec3): Vec3;\ndeclare function lerp(a: Vec3, b: Vec3, t: number): Vec3;\ndeclare function direction(a: Vec3, b: Vec3): Vec3;\ndeclare function offset(point: Vec3, dir: Vec3, amount: number): Vec3;\ndeclare const Points: {\n /** Euclidean distance between two 3D points. */\n readonly distance: typeof distance;\n /** Center point between two 3D points. */\n readonly midpoint: typeof midpoint;\n /** Linearly interpolate between two 3D points. t=0 returns a, t=1 returns b. */\n readonly lerp: typeof lerp;\n /** Unit direction vector from a to b. Throws if a and b are the same point. */\n readonly direction: typeof direction;\n /** Move a point along a direction vector by a given amount. */\n readonly offset: typeof offset;\n /** Compute a 2D point at distance and angle (degrees) from an optional origin. */\n readonly polar: typeof polar;\n};\ninterface WoodBoardOptions {\n /** Wood species, e.g. "birch", "oak", "plywood". Default: "wood" */\n species?: string;\n /** Material description for BOM, e.g. "birch plywood". Default: species value */\n material?: string;\n /** Grain direction: "long" (along width) or "cross" (along height). Default: "long" */\n grain?: string;\n /** Color hex string for visualization. Default: "#d2b48c" (tan/wood) */\n color?: string;\n /** If false, skip automatic BOM registration. Default: true */\n autoBom?: boolean;\n}\n/**\n * A board of wood with metadata for manufacturing: grain direction, species,\n * and dimensions. The underlying geometry is a simple box.\n *\n * WoodBoard operations are immutable. Joint operations return new boards instead\n * of carving the original in-place, and transform methods preserve all metadata.\n *\n * @category Woodworking\n */\ndeclare class WoodBoard {\n /** The underlying 3D shape. */\n shape: Shape;\n /** Board width (mm) — the longer flat dimension */\n readonly width: number;\n /** Board height (mm) — the shorter flat dimension */\n readonly height: number;\n /** Board thickness (mm) */\n readonly thickness: number;\n /** Grain direction: "long" or "cross" */\n readonly grain: string;\n /** Wood species, e.g. "birch", "oak" */\n readonly species: string;\n /** Material label for BOM */\n readonly material: string;\n constructor(width: number, height: number, thickness: number, opts?: WoodBoardOptions);\n /**\n * Subtract a cutter from this board, returning a new board.\n * Used by joint functions (dado, rabbet, mortiseAndTenon).\n */\n cut(cutter: Shape): WoodBoard;\n /** Create a new WoodBoard with a pre-built shape, preserving metadata. No BOM re-registration. */\n private _withShape;\n /** Translate the board in 3D space. */\n translate(x: number, y: number, z: number): WoodBoard;\n /** Rotate the board around an axis by a given angle in degrees. */\n rotate(axis: [\n number,\n number,\n number\n ], angleDeg: number, options?: {\n pivot?: [\n number,\n number,\n number\n ];\n }): WoodBoard;\n /** Rotate the board around the X axis by a given angle in degrees. */\n rotateX(angleDeg: number): WoodBoard;\n /** Rotate the board around the Y axis by a given angle in degrees. */\n rotateY(angleDeg: number): WoodBoard;\n /** Rotate the board around the Z axis by a given angle in degrees. */\n rotateZ(angleDeg: number): WoodBoard;\n /** Mirror the board across a plane defined by its normal. */\n mirror(normal: [\n number,\n number,\n number\n ]): WoodBoard;\n /** Set the board\'s display color. */\n color(value: string): WoodBoard;\n /** Clone the board (creates an independent copy of the underlying shape). */\n clone(): WoodBoard;\n}\n/**\n * Options for a dado joint — a channel cut across the face of a board.\n * @category Woodworking\n */\ninterface DadoOptions {\n /** Distance from the bottom edge of the host to the bottom of the channel (mm). */\n fromBottom?: number;\n /** Distance from the top edge of the host to the top of the channel (mm). Alternative to fromBottom. */\n fromTop?: number;\n /** Channel depth into the board thickness (mm). Default: 1/3 of host thickness. */\n depth?: number;\n /** Fit tolerance. Default: \'snug\'. */\n fit?: "friction" | "snug" | "slip";\n /** If set, the dado stops this far from the front edge (mm), creating a stopped dado. */\n stopped?: number;\n}\ndeclare function dado(host: WoodBoard, guest: WoodBoard, opts: DadoOptions): WoodBoard;\n/**\n * Options for a rabbet joint — an L-shaped step along an edge.\n * @category Woodworking\n */\ninterface RabbetOptions {\n /** Which edge to cut the rabbet on. */\n edge: "top" | "bottom" | "left" | "right" | "back";\n /** How far into the board face the rabbet extends (mm). */\n width: number;\n /** How deep the step is cut into the thickness (mm). */\n depth: number;\n}\ndeclare function rabbet(board: WoodBoard, opts: RabbetOptions): WoodBoard;\n/**\n * Options for a mortise-and-tenon joint.\n * @category Woodworking\n */\ninterface MortiseAndTenonOptions {\n /** \'blind\' (default): mortise doesn\'t go through. \'through\': mortise goes all the way. */\n style?: "blind" | "through";\n /** Position of the mortise center along the mortise board height. */\n position?: {\n fromTop?: number;\n fromBottom?: number;\n };\n /** Tenon thickness (mm). Default: 1/3 of tenon board thickness. */\n tenonThickness?: number;\n /** Tenon width (mm). Default: 60% of tenon board height. */\n tenonWidth?: number;\n /** Tenon length (mm). Default: 2/3 of mortise board thickness for blind, full for through. */\n tenonLength?: number;\n /** Corner radius for mortise (mm). Default: 0 (square corners, hand tools). */\n cornerRadius?: number;\n /** Fit tolerance. Default: \'snug\'. */\n fit?: "friction" | "snug" | "slip" | "knockdown";\n}\ninterface MortiseAndTenonResult {\n mortiseBoard: WoodBoard;\n tenonBoard: WoodBoard;\n}\ndeclare function mortiseAndTenon(mortiseBoard: WoodBoard, tenonBoard: WoodBoard, opts?: MortiseAndTenonOptions): MortiseAndTenonResult;\n/**\n * Woodworking namespace — create boards and cut joints.\n *\n * **Boards:** `Wood.board()` creates a WoodBoard with grain, species, and BOM metadata.\n *\n * **Joints:** `Wood.dado()`, `Wood.rabbet()`, and `Wood.mortiseAndTenon()` are\n * immutable — they return new board value(s) with the joint cut applied.\n *\n * @category Woodworking\n */\ndeclare const Wood: {\n /**\n * Create a wood board with metadata for manufacturing.\n *\n * The board is a box(width, height, thickness) centered on XY, base at Z=0.\n * Width along X, height along Y, thickness along Z (0 to thickness).\n *\n * @param width - Board width in mm (X-axis).\n * @param height - Board height in mm (Y-axis).\n * @param thickness - Board thickness in mm (Z-axis).\n * @param opts - Species, grain, color, and BOM options.\n * @returns A new WoodBoard instance.\n * @category Woodworking\n */\n readonly board: (width: number, height: number, thickness: number, opts?: WoodBoardOptions) => WoodBoard;\n /**\n * Cut a dado (channel) across the face of a host board for a guest board to sit in.\n *\n * Returns a new host board with the dado cut applied.\n *\n * @category Woodworking\n */\n readonly dado: typeof dado;\n /**\n * Cut a rabbet (L-shaped step) along an edge of a board.\n *\n * Returns a new board with the rabbet cut applied.\n *\n * @category Woodworking\n */\n readonly rabbet: typeof rabbet;\n /**\n * Cut a mortise in one board and shape a tenon on another.\n *\n * Returns new boards with the mortise pocket and tenon cuts applied.\n *\n * @category Woodworking\n */\n readonly mortiseAndTenon: typeof mortiseAndTenon;\n};\n/**\n * Highlight all user-labeled faces on a shape for visual debugging.\n *\n * Shows each user-authored label name in the viewport for visual debugging.\n * Returns the shape unchanged for chaining: `return showLabels(myShape)`.\n *\n * @softDeprecated Viewport.highlight(shape, { labels: true }) — then return the shape; highlight returns void\n * @deprecated use Viewport.highlight(shape, { labels: true }) — then return the shape; highlight returns void\n */\ndeclare function showLabels(shape: Shape): Shape;\n/** Cross-section: slice a 3D shape with a plane and return the intersection as a 2D Sketch. */\ndeclare function intersectWithPlane(shape: Shape, plane: PlaneSpec): Sketch;\n/**\n * Extract the boundary profile of a named face as a 2D sketch.\n *\n * The result is returned in the face\'s local 2D coordinate system, making it convenient\n * for offsets, pocket profiles, or follow-up sketch operations driven by an existing face.\n *\n * @param shape - Source shape containing the face.\n * @param face - Named face selector to extract.\n * @returns A sketch of the face boundary in face-local coordinates.\n */\ndeclare function faceProfile(shape: Shape, face: FaceSelector): Sketch;\n/** Orthographically project a 3D shape onto a plane and return the silhouette as a 2D Sketch. */\ndeclare function projectToPlane(shape: Shape, plane: PlaneSpec): Sketch;\ninterface SheetMetalOptions {\n /**\n * Base panel dimensions. This is the flat blank before flanges are applied.\n */\n panel: {\n /** Width of the panel along the X axis. */\n width: number;\n /** Height of the panel along the Y axis. */\n height: number;\n };\n /** Sheet thickness in mm. Applied uniformly across the panel and all flanges. */\n thickness: number;\n /** Inside bend radius in mm. Must be ≥ 0. Typically 0.5–2× the sheet thickness. */\n bendRadius: number;\n /**\n * Bend allowance model used when computing the flat-pattern developed length.\n *\n * Currently only K-factor is supported. The K-factor (0–1) describes how far\n * the neutral axis sits from the inner bend surface. Typical values:\n * - Soft materials / large radius: 0.50\n * - General sheet steel: 0.42–0.44\n * - Hard materials / tight radius: 0.30–0.38\n */\n bendAllowance: {\n /** K-factor (neutral-axis offset, 0–1). */\n kFactor: number;\n };\n /**\n * Corner relief cut at each bend intersection.\n *\n * Prevents material overlap when two flanges meet at a corner. Defaults to a\n * rectangular relief sized to `bendRadius + thickness` if omitted.\n */\n cornerRelief?: {\n /** Relief shape — only `\'rect\'` is supported in v1. */\n kind?: "rect";\n /** Side length of the square relief cut in mm. */\n size: number;\n };\n}\ninterface SheetMetalFlangeOptions {\n /** Flange leg length in mm, measured from the outside of the bend to the tip. */\n length: number;\n /**\n * Bend angle in degrees (default: `90`).\n *\n * Only `90°` is supported in v1. Values other than 90 will be rejected at\n * build time.\n */\n angleDeg?: number;\n}\ninterface SheetMetalCutoutOptions {\n /** Horizontal offset within the region, measured from the region centre (mm). Default: `0`. */\n u?: number;\n /** Vertical offset within the region, measured from the region centre (mm). Default: `0`. */\n v?: number;\n /**\n * Anchor point on the sketch that aligns to `(u, v)`.\n *\n * Use `\'center\'` for most cases. For asymmetric profiles, verify orientation\n * by placing one test cutout before committing to the final position.\n *\n * Default: `\'center\'`.\n */\n selfAnchor?: Anchor;\n}\ninterface SheetMetalCutoutOp {\n region: SheetMetalPlanarRegionName;\n sketch: Sketch;\n u: number;\n v: number;\n selfAnchor: Anchor;\n}\n/**\n * An immutable sheet metal part that accumulates flanges and cutouts.\n *\n * Each mutating method returns a **new** `SheetMetalPart`; the original is\n * unchanged. The part does not produce geometry until you call `.folded()` or\n * `.flatPattern()`.\n *\n * @category Sheet Metal\n */\ndeclare class SheetMetalPart {\n private readonly model;\n private readonly cutouts;\n constructor(model: SheetMetalModel, cutouts?: readonly SheetMetalCutoutOp[]);\n /**\n * Add a 90° flange along one edge of the base panel.\n *\n * **Details**\n *\n * Each of the four edges (`\'top\'`, `\'right\'`, `\'bottom\'`, `\'left\'`) may carry\n * at most one flange. Calling `.flange()` twice for the same edge throws.\n *\n * Corner reliefs are automatically inserted at the intersections of adjacent\n * flanges. Build flanges before cutouts — validate with `.folded()` and\n * `.flatPattern()` after each addition.\n *\n * **Example**\n *\n * ```ts\n * const part = sheetMetal({ panel: { width: 100, height: 60 }, thickness: 1.5, bendRadius: 2, bendAllowance: { kFactor: 0.42 } })\n * .flange(\'top\', { length: 15 })\n * .flange(\'bottom\', { length: 15 });\n * ```\n *\n * @param edge - Which panel edge gets the flange: `\'top\' | \'right\' | \'bottom\' | \'left\'`.\n * @param options - Flange leg length and optional bend angle (only 90° in v1).\n * @returns A new `SheetMetalPart` with the flange added.\n * @category Sheet Metal\n */\n flange(edge: SheetMetalEdge, options: SheetMetalFlangeOptions): SheetMetalPart;\n /**\n * Subtract a 2D sketch cutout from a planar region of the sheet metal part.\n *\n * **Details**\n *\n * `region` must be `\'panel\'` or one of `\'flange-top\'`, `\'flange-right\'`,\n * `\'flange-bottom\'`, `\'flange-left\'` (only available once the corresponding\n * flange has been added). Cutouts inside bend regions are **not** supported\n * in v1.\n *\n * `sketch` must be an **unplaced** compile-covered 2D profile (e.g. the result\n * of `circle2d()`, `rect()`, `roundedRect()`). Passing an already-placed\n * sketch (one that has had `.onFace(...)` called on it) will throw.\n *\n * **Authoring order:** Add all flanges before adding cutouts. Add panel\n * cutouts before flange cutouts. Add one region at a time and validate with\n * `.folded()` / `.flatPattern()` after each step.\n *\n * **Example**\n *\n * ```ts\n * const part = sheetMetal({ panel: { width: 180, height: 110 }, thickness: 1.5, bendRadius: 2, bendAllowance: { kFactor: 0.42 } })\n * .flange(\'top\', { length: 18 })\n * .cutout(\'panel\', rect(72, 36), { selfAnchor: \'center\' })\n * .cutout(\'flange-top\', roundedRect(26, 10, 5), { selfAnchor: \'center\' });\n * ```\n *\n * @param region - Target planar region: `\'panel\'` or `\'flange-<edge>\'`.\n * @param sketch - Unplaced 2D sketch profile to cut through the region.\n * @param options - Optional placement offsets and self-anchor point.\n * @returns A new `SheetMetalPart` with the cutout added.\n * @category Sheet Metal\n */\n cutout(region: SheetMetalPlanarRegionName, sketch: Sketch, options?: SheetMetalCutoutOptions): SheetMetalPart;\n /**\n * Return all semantic region names currently available on this part.\n *\n * **Details**\n *\n * The returned list always includes `\'panel\'`. For every flange that has been\n * added, the list also includes the corresponding `\'flange-<edge>\'` and\n * `\'bend-<edge>\'` entries.\n *\n * Use this to discover valid targets for `.cutout()` or for querying faces\n * by region after materializing with `.folded()`.\n *\n * Defended region names:\n * `panel` | `flange-top` | `flange-right` | `flange-bottom` | `flange-left` |\n * `bend-top` | `bend-right` | `bend-bottom` | `bend-left`\n *\n * @returns Array of semantic region name strings for this part\'s current configuration.\n * @category Sheet Metal\n */\n regionNames(): SheetMetalRegionName[];\n /**\n * Materialize the 3D folded solid.\n *\n * **Details**\n *\n * Applies all flanges (bent up at their configured angles) and all registered\n * cutouts, then returns the resulting `Shape`. The shape is compiler-owned\n * and exact-exportable (STEP, IGES, etc.).\n *\n * Prefer calling `.folded()` to validate each build step before proceeding\n * to the final model.\n *\n * @returns The fully folded 3D sheet metal solid.\n * @see {@link SheetMetalPart.flatPattern} for the unfolded blank.\n * @category Sheet Metal\n */\n folded(): Shape;\n /**\n * Materialize the flat-pattern (unfolded blank) for fabrication.\n *\n * **Details**\n *\n * Unfolds all flanges using the K-factor bend allowance and lays the result\n * flat in the XY plane. Cutouts are projected into the flat geometry.\n * The returned shape is exact-exportable and ready for laser / waterjet / CNC\n * nesting workflows.\n *\n * The developed length of each bend zone is:\n * `BA = (bendRadius + kFactor × thickness) × angleDeg × π / 180`\n *\n * @returns The flat 2D blank as a 3D solid of uniform thickness.\n * @see {@link SheetMetalPart.folded} for the 3D folded shape.\n * @category Sheet Metal\n */\n flatPattern(): Shape;\n private buildOutput;\n}\n/**\n * Create a parametric sheet metal part with flanges, bend allowances, and flat-pattern unfolding.\n *\n * **Details**\n *\n * `sheetMetal()` keeps one semantic model and derives both a folded 3D solid\n * and an accurate flat pattern from it. The K-factor bend allowance is applied\n * during unfolding. This is a strict v1 subset — it does not infer sheet metal\n * from arbitrary solids.\n *\n * **Recommended authoring order:**\n * 1. Define the base panel + thickness + bend parameters.\n * 2. Chain `.flange()` calls for each edge. Validate with `.folded()` and\n * `.flatPattern()` before adding cutouts.\n * 3. Add panel cutouts, then flange cutouts one region at a time.\n * 4. Validate after each new cutout region.\n *\n * **v1 limitations:** one base panel, up to four 90° edge flanges, constant\n * thickness, explicit K-factor, rectangular corner reliefs, planar cutouts\n * only. No hems, jogs, lofted bends, non-90° flanges, or bend-region cutouts.\n *\n * **Example**\n *\n * ```ts\n * const cover = sheetMetal({\n * panel: { width: 180, height: 110 },\n * thickness: 1.5,\n * bendRadius: 2,\n * bendAllowance: { kFactor: 0.42 },\n * cornerRelief: { size: 4 },\n * })\n * .flange(\'top\', { length: 18 })\n * .flange(\'right\', { length: 18 })\n * .flange(\'bottom\', { length: 18 })\n * .flange(\'left\', { length: 18 })\n * .cutout(\'panel\', rect(72, 36), { selfAnchor: \'center\' })\n * .cutout(\'flange-right\', roundedRect(26, 10, 5), { selfAnchor: \'center\' });\n *\n * const folded = cover.folded();\n * const flat = cover.flatPattern();\n * ```\n *\n * @param options - Panel geometry, thickness, bend radius, K-factor, and optional corner relief.\n * @returns A `SheetMetalPart` builder. Call `.folded()` or `.flatPattern()` to produce geometry.\n * @category Sheet Metal\n */\ndeclare function sheetMetal(options: SheetMetalOptions): SheetMetalPart;\ninterface FingerJointOptions {\n /** Explicit finger count (must be odd, >= 3). Default: auto from length/thickness. */\n fingers?: number;\n /** Explicit finger width. Default: auto. */\n fingerWidth?: number;\n /** Extra clearance per side (mm). Default: 0. */\n clearance?: number;\n /** Laser kerf (mm). Default: 0. */\n kerf?: number;\n /** Whether edge starts with full finger or half. Default: \'full\'. */\n endStyle?: "full" | "half";\n}\ninterface FingerJointResult {\n /** Even-position finger rects (tabs for side A, slots for side B). */\n tabProfile: Sketch;\n /** Odd-position finger rects (tabs for side B, slots for side A). */\n matingProfile: Sketch;\n /** Full rectangle minus odd slot cuts. */\n slotProfile: Sketch;\n}\ninterface TabSlotOptions {\n /** Number of tabs. Default: auto (length / (4 * thickness)). */\n tabCount?: number;\n /** Tab width. Default: 2 * thickness. */\n tabWidth?: number;\n /** Extra clearance per side (mm). Default: 0. */\n clearance?: number;\n /** Laser kerf (mm). Default: 0. */\n kerf?: number;\n /** Distance from panel edges to first/last tab center. Default: thickness. */\n inset?: number;\n}\ninterface TabSlotResult {\n tabs: Sketch;\n slots: Sketch;\n}\ninterface LivingHingeOptions {\n /** Slit pattern style. Default: \'straight\'. */\n pattern?: "straight" | "serpentine";\n /** Explicit slit width (beyond kerf). Default: 0. */\n slitWidth?: number;\n /** Distance between slit rows. Default: 2 * thickness. */\n rowSpacing?: number;\n /** Length of each slit. Default: 0.7 * (length - 2 * landWidth). */\n slitLength?: number;\n /** Uncut material between slit ends and row edges. Default: 2 * thickness. */\n landWidth?: number;\n /** Target bend radius - auto-computes row spacing. Overrides rowSpacing. */\n bendRadius?: number;\n /** Material thickness (needed for bend radius calc). Default: 3. */\n thickness?: number;\n}\ninterface SnapFitOptions {\n /** Tab beam length. Default: 4 * thickness. */\n tabLength?: number;\n /** Tab beam width. Default: thickness. */\n tabWidth?: number;\n /** How much the barb protrudes beyond the beam. Default: 0.3 * thickness. */\n overhang?: number;\n /** Slot clearance per side. Default: 0.1. */\n clearance?: number;\n /** Laser kerf. Default: 0. */\n kerf?: number;\n /** Barb style. Default: \'barb\'. */\n style?: "arrow" | "barb";\n}\ninterface SnapFitResult {\n tab: Sketch;\n slot: Sketch;\n}\n/**\n * Apply kerf compensation to a complete part outline (outer boundary + holes).\n *\n * Offsets inward by half-kerf: the outer boundary shrinks and inner holes grow.\n * This is correct because the laser beam removes material on both sides of the\n * cut line.\n *\n * @softDeprecated sketch.offset(-kerf / 2) — or let FlatPart.profile(kerf) / Laser.kit({ kerf }) apply kerf compensation automatically\n * @deprecated use sketch.offset(-kerf / 2) — or let FlatPart.profile(kerf) / Laser.kit({ kerf }) apply kerf compensation automatically\n */\ndeclare function kerfCompensateOutline(sketch: Sketch, kerf: number): Sketch;\n/**\n * Apply kerf compensation to joint protrusions (tabs, fingers).\n *\n * These grow by half-kerf so they are slightly oversized and fit tightly in\n * their mating slots after the laser removes material.\n *\n * @softDeprecated sketch.offset(kerf / 2) — or let FlatPart.profile(kerf) / Laser.kit({ kerf }) apply kerf compensation automatically\n * @deprecated use sketch.offset(kerf / 2) — or let FlatPart.profile(kerf) / Laser.kit({ kerf }) apply kerf compensation automatically\n */\ndeclare function kerfCompensateTabs(sketch: Sketch, kerf: number): Sketch;\n/**\n * Apply kerf compensation to joint cutouts (slots, holes that receive tabs).\n *\n * These grow by half-kerf so tabs can fit into them after the laser removes\n * material from both sides of the slot walls.\n *\n * @softDeprecated sketch.offset(kerf / 2) — or let FlatPart.profile(kerf) / Laser.kit({ kerf }) apply kerf compensation automatically\n * @deprecated use sketch.offset(kerf / 2) — or let FlatPart.profile(kerf) / Laser.kit({ kerf }) apply kerf compensation automatically\n */\ndeclare function kerfCompensateSlots(sketch: Sketch, kerf: number): Sketch;\ninterface PartJoints {\n /** Geometry to ADD to the base profile (tabs, fingers protruding from edges). */\n additions?: Sketch[];\n /** Geometry to SUBTRACT from the base profile (slots, holes for mating tabs). */\n subtractions?: Sketch[];\n}\n/**\n * Build a kerf-compensated part profile.\n *\n * 1. Start with the base profile.\n * 2. Kerf-compensate each tab addition (grow by kerf/2), then union with base.\n * 3. Kerf-compensate each slot subtraction (grow by kerf/2), then subtract from base.\n * 4. Kerf-compensate the resulting outline (shrink by kerf/2).\n *\n * Order matters: joints modify geometry BEFORE outline compensation so the\n * final inward offset applies uniformly to the assembled profile.\n *\n * Sign conventions: the outline shrinks by kerf/2 while tab additions and slot\n * subtractions grow by kerf/2 (the beam removes material on both sides of the\n * cut line).\n *\n * @softDeprecated FlatPart.profile(kerf) / FlatPart.solid(kerf) — or Laser.kit({ kerf }), which compensates every part automatically\n * @deprecated use FlatPart.profile(kerf) / FlatPart.solid(kerf) — or Laser.kit({ kerf }), which compensates every part automatically\n */\ndeclare function kerfCompensatePart(baseProfile: Sketch, joints: PartJoints, kerf: number): Sketch;\ninterface MaterialKerfEntry {\n material: string;\n thickness: number;\n kerf: number;\n laserType: string;\n notes?: string;\n}\n/**\n * Common kerf values. Users should always test-cut to verify for their specific setup.\n *\n * @softDeprecated Laser.COMMON_KERFS\n * @deprecated use Laser.COMMON_KERFS\n */\ndeclare const COMMON_KERFS: MaterialKerfEntry[];\n/**\n * Look up kerf for a material + thickness + laser combo.\n *\n * If `laserType` is omitted, returns the first matching material + thickness\n * entry. Returns `undefined` when no match is found.\n *\n * @softDeprecated Laser.lookupKerf(material, thickness, laserType)\n * @deprecated use Laser.lookupKerf(material, thickness, laserType)\n */\ndeclare function lookupKerf(material: string, thickness: number, laserType?: string): number | undefined;\ninterface EdgeInfo {\n name: string;\n start: [\n number,\n number\n ];\n end: [\n number,\n number\n ];\n length: number;\n /** Outward-facing normal. */\n normal: [\n number,\n number\n ];\n}\ninterface FlatPartOptions {\n material?: string;\n qty?: number;\n color?: string;\n}\n/** Tracks a joint connection for assembly preview. */\ninterface JointRecord {\n type: "finger" | "tabSlot" | "snapFit";\n partA: string;\n partB: string;\n edgeA: string;\n edgeB: string;\n /** Fold angle in degrees. Default: 90. */\n foldAngle: number;\n}\ndeclare class FlatPart {\n readonly name: string;\n readonly thickness: number;\n readonly options: FlatPartOptions;\n private _baseProfile;\n private _edges;\n private _additions;\n private _subtractions;\n private _joints;\n private _partNumber;\n constructor(name: string, baseProfile: Sketch, thickness: number, edges: Map<string, EdgeInfo>, options?: FlatPartOptions);\n /** All edges as a read-only map. */\n get edges(): ReadonlyMap<string, EdgeInfo>;\n /** Look up a named edge. Throws if the edge does not exist. */\n edge(name: string): EdgeInfo;\n /** All edge names on this part. */\n edgeNames(): string[];\n /** BOM part number assigned to this flat part. */\n get partNumber(): number;\n /** Update the BOM part number for this flat part. */\n set partNumber(n: number);\n /** Joint records that attach this part to other parts in the kit. */\n get joints(): readonly JointRecord[];\n /** Requested quantity of this part in the kit. Defaults to `1`. */\n get quantity(): number;\n /** Add geometry (e.g. protruding tabs) to the part profile. */\n addGeometry(sketch: Sketch): void;\n /** Subtract geometry (e.g. slot cuts) from the part profile. */\n subtractGeometry(sketch: Sketch): void;\n /** Record a joint connection for assembly preview. */\n addJoint(record: JointRecord): void;\n /** Final 2D profile with joints and optional kerf compensation. */\n profile(kerf?: number): Sketch;\n /** 3D solid — extrude the profile by material thickness. */\n solid(kerf?: number): Shape;\n}\n/**\n * Create a rectangular flat panel with 4 named edges.\n *\n * Profile origin at bottom-left corner.\n * Edges: bottom (y=0), right (x=width), top (y=height), left (x=0).\n * Edge traversal follows CCW winding order.\n *\n * @softDeprecated Laser.panel(name, width, height, thickness, options) — identical arguments\n * @deprecated use Laser.panel(name, width, height, thickness, options) — identical arguments\n */\ndeclare function flatPanel(name: string, width: number, height: number, thickness: number, options?: FlatPartOptions): FlatPart;\n/**\n * Create a flat part from an arbitrary profile with user-named edges.\n *\n * Edge normals are computed automatically (perpendicular to direction, rotated 90deg CW).\n *\n * @softDeprecated Laser.part(name, profile, thickness, edges, options) — identical arguments\n * @deprecated use Laser.part(name, profile, thickness, edges, options) — identical arguments\n */\ndeclare function flatPart(name: string, profile: Sketch, thickness: number, edges?: Record<string, {\n start: [\n number,\n number\n ];\n end: [\n number,\n number\n ];\n}>, options?: FlatPartOptions): FlatPart;\n/**\n * Connect two parts with finger joints along specified edges.\n *\n * Adds finger geometry to partA\'s edge, cuts matching slots from partB\'s edge.\n * The joint profiles are positioned along each edge using rotation + translation.\n *\n * @softDeprecated Laser.fingerJoint(partA, edgeA, partB, edgeB, options) — identical arguments\n * @deprecated use Laser.fingerJoint(partA, edgeA, partB, edgeB, options) — identical arguments\n */\ndeclare function fingerJoint(partA: FlatPart, edgeNameA: string, partB: FlatPart, edgeNameB: string, options?: FingerJointOptions & {\n foldAngle?: number;\n}): void;\n/**\n * Connect two parts with tab-and-slot joints along specified edges.\n *\n * Adds tab geometry to partA\'s edge, cuts matching slots from partB\'s edge.\n *\n * @softDeprecated Laser.tabSlot(partA, edgeA, partB, edgeB, options) — identical arguments\n * @deprecated use Laser.tabSlot(partA, edgeA, partB, edgeB, options) — identical arguments\n */\ndeclare function tabSlot(partA: FlatPart, edgeNameA: string, partB: FlatPart, edgeNameB: string, options?: TabSlotOptions & {\n foldAngle?: number;\n}): void;\ninterface PackedPiece {\n description: string;\n material: string;\n /** Width as placed (may differ from original if rotated). */\n width: number;\n /** Height as placed. */\n height: number;\n /** Original width before placement. */\n origWidth: number;\n /** Original height before placement. */\n origHeight: number;\n x: number;\n y: number;\n rotated: boolean;\n}\ninterface PackedSheet {\n sheetIndex: number;\n material: string;\n pieces: PackedPiece[];\n sheetWidth: number;\n sheetHeight: number;\n usedArea: number;\n /** Ordered guillotine cuts to separate all pieces on this sheet. */\n cuts: GuillotineCut[];\n}\ninterface GuillotineCut {\n /** 1-based sequence number. */\n step: number;\n /** \'V\' = vertical cut (splits left/right), \'H\' = horizontal cut (splits top/bottom). */\n direction: "V" | "H";\n /** Cut line start X (mm from sheet origin). */\n x1: number;\n /** Cut line start Y (mm from sheet origin). */\n y1: number;\n /** Cut line end X (mm from sheet origin). */\n x2: number;\n /** Cut line end Y (mm from sheet origin). */\n y2: number;\n /** Cut length in mm. */\n lengthMm: number;\n}\ninterface CuttingLayoutResult {\n sheets: PackedSheet[];\n totalPieces: number;\n totalSheets: number;\n totalUsedArea: number;\n totalSheetArea: number;\n wastePercent: number;\n kerf: number;\n /** Total length of all cuts across all sheets (mm). */\n totalCutLength: number;\n}\ninterface AssemblyPreviewOptions {\n /** Kerf compensation passed to each part\'s solid(). Default: 0 */\n kerf?: number;\n /** Fold amount: 0 = flat layout, 1 = fully assembled. Default: 1 */\n fold?: number;\n /** Explode distance: 0 = assembled, >0 = parts spread outward. Default: 0 */\n explode?: number;\n}\ninterface AssemblyPreviewResult {\n /** All part shapes grouped for display. */\n shapes: ShapeGroup;\n /** Individual transformed shapes keyed by part name. */\n partShapes: Map<string, Shape>;\n}\n/**\n * Generate a 3D assembly preview from flat parts and their joint records.\n *\n * The preview can fold joints partially or fully and optionally apply exploded spacing\n * so part relationships are easier to inspect visually.\n *\n * @param parts - Flat parts to assemble.\n * @param joints - Joint definitions connecting the parts.\n * @param options - Fold, explode, and kerf preview options.\n * @returns Preview shapes plus a per-part shape map.\n * @softDeprecated Laser.kit(...).assemblyPreview(options) — the kit collects joints and applies its kerf (default 0.2 mm; this standalone form defaulted kerf to 0, so use Laser.kit({ kerf: 0 }) for byte-identical preview geometry); Laser.assemblyPreview(parts, joints, options) keeps the standalone form\n * @deprecated use Laser.kit(...).assemblyPreview(options) — the kit collects joints and applies its kerf (default 0.2 mm; this standalone form defaulted kerf to 0, so use Laser.kit({ kerf: 0 }) for byte-identical preview geometry); Laser.assemblyPreview(parts, joints, options) keeps the standalone form\n */\ndeclare function assemblyPreview(parts: FlatPart[], joints: JointRecord[], options?: AssemblyPreviewOptions): AssemblyPreviewResult;\ninterface AssemblyStep {\n /** 1-based step number. */\n stepNumber: number;\n /** Human-readable instruction. */\n description: string;\n /** The part being added in this step. */\n partName: string;\n /** Part number (for cross-ref with cut sheets). */\n partNumber: number;\n /** Which existing part it connects to. */\n connectsTo: string;\n /** Joint type used. */\n jointType: "finger" | "tabSlot" | "snapFit";\n /** The edge on the new part. */\n newPartEdge: string;\n /** The edge on the existing part. */\n existingPartEdge: string;\n /** Fold angle in degrees. */\n foldAngle: number;\n /** Part names in the assembly so far (after this step). */\n assembledParts: string[];\n}\ninterface AssemblyInstructionsOptions {\n /** Part to start from. Default: part with most joint connections. */\n rootPart?: string;\n}\ninterface AssemblyInstructionsResult {\n steps: AssemblyStep[];\n /** Total number of parts in the assembly. */\n totalParts: number;\n /** Parts not connected to the joint graph (orphans). */\n orphanParts: string[];\n}\n/**\n * Generate step-by-step assembly instructions from flat parts and joints.\n *\n * Algorithm:\n * 1. Build adjacency graph from joints\n * 2. Pick root part (most connections, or user-specified)\n * 3. BFS from root, creating one step per part addition\n * 4. Each step describes: which part to add, where it connects, how to orient it\n *\n * Heuristics for step ordering:\n * - Start with the part that has the most connections (the base)\n * - Add parts that connect to already-assembled parts first (BFS order)\n * - Among candidates at the same BFS depth, prefer parts with more\n * connections to already-assembled parts (structurally stable)\n *\n * @softDeprecated Laser.kit(...).assemblyInstructions(options) — the kit collects joints for you; Laser.instructions(parts, joints, options) keeps the standalone form\n * @deprecated use Laser.kit(...).assemblyInstructions(options) — the kit collects joints for you; Laser.instructions(parts, joints, options) keeps the standalone form\n */\ndeclare function assemblyInstructions(parts: FlatPart[], joints: JointRecord[], options?: AssemblyInstructionsOptions): AssemblyInstructionsResult;\n/**\n * Format assembly instructions as a human-readable text document.\n *\n * Includes a "Step 0" preamble identifying the base part, followed by\n * numbered steps, and a note about any orphan parts.\n *\n * @softDeprecated Laser.kit(...).formatInstructions(options) — or Laser.formatInstructions(result) for the standalone form\n * @deprecated use Laser.kit(...).formatInstructions(options) — or Laser.formatInstructions(result) for the standalone form\n */\ndeclare function formatInstructions(result: AssemblyInstructionsResult): string;\ninterface LaserKitOptions {\n /** Default material label for parts that don\'t specify one. */\n material?: string;\n /** Stock sheet width in mm (default 600). */\n sheetWidth?: number;\n /** Stock sheet height in mm (default 400). */\n sheetHeight?: number;\n /** Laser kerf in mm (default 0.2). */\n kerf?: number;\n}\ninterface LaserKitBomEntry {\n partNumber: number;\n name: string;\n quantity: number;\n material: string;\n widthMm: number;\n heightMm: number;\n}\ndeclare class LaserKit {\n private _parts;\n private _nextPartNumber;\n private _options;\n constructor(options?: LaserKitOptions);\n /** Laser kerf in mm. */\n get kerf(): number;\n /** All registered parts (flat, in insertion order). */\n get parts(): readonly FlatPart[];\n /** Default material label. */\n get material(): string;\n /** Stock sheet width in mm. */\n get sheetWidth(): number;\n /** Stock sheet height in mm. */\n get sheetHeight(): number;\n /**\n * Register a flat part with this kit.\n * Assigns a sequential part number and records the quantity.\n */\n addPart(part: FlatPart, overrides?: {\n qty?: number;\n }): this;\n /** Generate nested cut sheets using guillotine bin-packing. */\n cutSheets(): CuttingLayoutResult;\n /** Bill of materials listing every part with dimensions. */\n bom(): LaserKitBomEntry[];\n /** Individual SVG string for each part profile, keyed by part name. */\n partSvgs(): Map<string, string>;\n /** Combined inventory SVG showing all parts in a labeled grid. */\n inventorySvg(): string;\n /** Collect deduplicated joints from all registered parts. */\n private _collectJoints;\n /** 3D fold-up preview of the assembled kit. */\n assemblyPreview(options?: Omit<AssemblyPreviewOptions, "kerf">): AssemblyPreviewResult;\n /** Step-by-step assembly instructions. */\n assemblyInstructions(options?: AssemblyInstructionsOptions): AssemblyInstructionsResult;\n /** Human-readable assembly instructions text. */\n formatInstructions(options?: AssemblyInstructionsOptions): string;\n}\n/**\n * Top-level factory for creating a LaserKit container.\n *\n * @softDeprecated Laser.kit(options) — identical arguments\n * @deprecated use Laser.kit(options) — identical arguments\n */\ndeclare function laserKit(options?: LaserKitOptions): LaserKit;\n/**\n * Laser-cutting namespace — flat parts, joints, kits, kerf data, and assembly previews.\n *\n * **Workflow:** create parts with `Laser.panel()` / `Laser.part()`, connect them\n * with `Laser.fingerJoint()` / `Laser.tabSlot()`, then collect them in a\n * `Laser.kit()` for BOM, sheet nesting, SVG export, and assembly previews.\n * The kit applies kerf compensation automatically from its `kerf` option.\n *\n * @category Laser Cutting\n */\ndeclare const Laser: {\n /**\n * Create a rectangular flat panel with 4 named edges.\n *\n * Profile origin at the bottom-left corner.\n * Edges: `bottom` (y=0), `right` (x=width), `top` (y=height), `left` (x=0).\n * Edge traversal follows CCW winding order.\n *\n * @param name - Unique part name used in BOM, joints, and instructions.\n * @param width - Panel width in mm (X-axis).\n * @param height - Panel height in mm (Y-axis).\n * @param thickness - Material thickness in mm.\n * @param options - Material, quantity, and color options.\n * @returns A new FlatPart with the four named edges.\n * @category Laser Cutting\n */\n readonly panel: (name: string, width: number, height: number, thickness: number, options?: FlatPartOptions) => FlatPart;\n /**\n * Create a flat part from an arbitrary 2D profile with user-named edges.\n *\n * Edge normals are computed automatically (perpendicular to the edge\n * direction, rotated 90 degrees clockwise).\n *\n * @param name - Unique part name used in BOM, joints, and instructions.\n * @param profile - 2D outline of the part.\n * @param thickness - Material thickness in mm.\n * @param edges - Named edges as `{ start: [x, y], end: [x, y] }` segments along the outline.\n * @param options - Material, quantity, and color options.\n * @returns A new FlatPart with the given named edges.\n * @category Laser Cutting\n */\n readonly part: (name: string, profile: Sketch, thickness: number, edges?: Record<string, {\n start: [\n number,\n number\n ];\n end: [\n number,\n number\n ];\n }>, options?: FlatPartOptions) => FlatPart;\n /**\n * Connect two parts with finger joints along the named edges.\n *\n * Adds finger geometry to partA\'s edge and cuts matching slots from partB\'s\n * edge; the joint is also recorded on both parts for assembly previews and\n * instructions.\n *\n * @param partA - Part that receives the protruding fingers.\n * @param edgeNameA - Edge of partA to joint along.\n * @param partB - Part that receives the mating slots.\n * @param edgeNameB - Edge of partB to joint along.\n * @param options - Finger sizing plus `foldAngle` in degrees (default: 90).\n * @category Laser Cutting\n */\n readonly fingerJoint: (partA: FlatPart, edgeNameA: string, partB: FlatPart, edgeNameB: string, options?: FingerJointOptions & {\n foldAngle?: number;\n }) => void;\n /**\n * Connect two parts with tab-and-slot joints along the named edges.\n *\n * Adds tab geometry to partA\'s edge and cuts matching slots from partB\'s\n * edge; the joint is also recorded on both parts for assembly previews and\n * instructions.\n *\n * @param partA - Part that receives the protruding tabs.\n * @param edgeNameA - Edge of partA to joint along.\n * @param partB - Part that receives the mating slots.\n * @param edgeNameB - Edge of partB to joint along.\n * @param options - Tab sizing plus `foldAngle` in degrees (default: 90).\n * @category Laser Cutting\n */\n readonly tabSlot: (partA: FlatPart, edgeNameA: string, partB: FlatPart, edgeNameB: string, options?: TabSlotOptions & {\n foldAngle?: number;\n }) => void;\n /**\n * Create a LaserKit container for a flat-pack project.\n *\n * The kit collects FlatPart instances, assigns sequential part numbers,\n * generates a bill of materials, nests parts onto cut sheets, exports SVG\n * views, and produces kerf-compensated assembly previews and step-by-step\n * instructions. Kerf compensation uses the kit\'s `kerf` option (default 0.2 mm).\n *\n * @param options - Default material, sheet size, and kerf.\n * @returns A new LaserKit.\n * @category Laser Cutting\n */\n readonly kit: (options?: LaserKitOptions) => LaserKit;\n /**\n * Generate a 3D assembly preview from flat parts and their joint records.\n *\n * Prefer `Laser.kit(...).assemblyPreview(options)` — the kit collects the\n * joint records and applies its kerf automatically. This standalone form\n * defaults `kerf` to 0.\n *\n * @param parts - Flat parts to assemble.\n * @param joints - Joint definitions connecting the parts.\n * @param options - Fold, explode, and kerf preview options.\n * @returns Preview shapes plus a per-part shape map.\n * @category Laser Cutting\n */\n readonly assemblyPreview: (parts: FlatPart[], joints: JointRecord[], options?: AssemblyPreviewOptions) => AssemblyPreviewResult;\n /**\n * Generate step-by-step assembly instructions from flat parts and joints.\n *\n * Prefer `Laser.kit(...).assemblyInstructions(options)` — the kit collects\n * the joint records for you. Steps are ordered BFS from the most-connected\n * (base) part so each new part attaches to already-assembled parts.\n *\n * @param parts - Flat parts in the kit.\n * @param joints - Joint definitions connecting the parts.\n * @param options - Root-part override.\n * @returns Ordered steps plus orphan-part diagnostics.\n * @category Laser Cutting\n */\n readonly instructions: (parts: FlatPart[], joints: JointRecord[], options?: AssemblyInstructionsOptions) => AssemblyInstructionsResult;\n /**\n * Format assembly instructions as a human-readable text document.\n *\n * Includes a "Step 0" preamble identifying the base part, followed by\n * numbered steps, and a note about any orphan parts.\n *\n * @param result - Result of `Laser.instructions()` or `kit.assemblyInstructions()`.\n * @returns Formatted plain-text instructions.\n * @category Laser Cutting\n */\n readonly formatInstructions: (result: AssemblyInstructionsResult) => string;\n /**\n * Look up kerf for a material + thickness + laser combo in `Laser.COMMON_KERFS`.\n *\n * If `laserType` is omitted, returns the first matching material + thickness\n * entry. Returns `undefined` when no match is found. Always test-cut to\n * verify kerf for a specific machine.\n *\n * @param material - Material key, e.g. `\'birch-plywood\'`, `\'mdf\'`, `\'acrylic\'`.\n * @param thickness - Material thickness in mm.\n * @param laserType - Optional laser type, e.g. `\'CO2-40W\'`.\n * @returns Full kerf width in mm, or `undefined`.\n * @category Laser Cutting\n */\n readonly lookupKerf: (material: string, thickness: number, laserType?: string) => number | undefined;\n /**\n * Common full-kerf values by material, thickness, and laser type.\n *\n * Reference data only — kerf varies per machine, lens, and focus; always\n * test-cut to verify before committing a sheet.\n *\n * @category Laser Cutting\n */\n readonly COMMON_KERFS: MaterialKerfEntry[];\n};\ninterface CurveArcOptions {\n /** Arc start point. */\n start: Vec3;\n /** Arc end point. */\n end: Vec3;\n /** Tangent direction at the start point. Magnitude is ignored. */\n tangent: Vec3;\n}\ninterface CurveBlendEndpoint {\n /** Endpoint position. */\n point: Vec3;\n /** Tangent direction at this endpoint. Magnitude is ignored. */\n tangent: Vec3;\n /** Tangent reach relative to the endpoint chord length. Default 1. */\n weight?: number;\n}\ninterface CurveBlendG2Endpoint extends CurveBlendEndpoint {\n /** Optional endpoint curvature/second-derivative vector. Default is zero. */\n curvature?: Vec3;\n}\ninterface CurveFitOptions {\n /** Polynomial degree. Default is cubic, reduced automatically for short point lists. */\n degree?: number;\n /** Maximum allowed interpolation residual in model units. Default 1e-7. */\n tolerance?: number;\n /**\n * Interpolate a closed periodic loop through the points. The loop closes from\n * the last point back to the first automatically — do not repeat the first\n * point at the end.\n */\n closed?: boolean;\n}\ntype CurveTrimInput = NurbsCurve3D | Vec3[];\ntype CurveTrimOutput<T extends CurveTrimInput> = T extends NurbsCurve3D ? NurbsCurve3D : Vec3[];\ninterface CurveHelixPath extends Curve3D {\n readonly radius: number;\n readonly pitch: number;\n readonly turns: number;\n readonly height: number;\n readonly startAngle: number;\n readonly clockwise: boolean;\n}\ninterface CurveHelixCoil {\n (options: HelixCoilOptions): Shape;\n (profile: Sketch, options: HelixCoilOptions): Shape;\n}\n/**\n * Canonical exact/smooth 3D curve constructors.\n *\n * `Curve.*` is the public home for reference curves and route centerlines that\n * feed `sweep`, `variableSweep`, route visualization, and future path consumers.\n * Standalone 3D curve constructors have been collapsed into this namespace.\n */\ndeclare const Curve: {\n /**\n * Create an exact G1 blend curve between two directed endpoints.\n *\n * The returned curve is a cubic non-rational `NurbsCurve3D`: ForgeCAD converts\n * the endpoint positions and tangents into Bezier control points, so the curve\n * can feed `sweep` and exact surface boundaries through the existing `nurbs`\n * IR rather than a sampled polyline.\n *\n * @example\n * const rail = Curve.Blend(\n * { point: [0, 0, 0], tangent: [1, 0, 0], weight: 0.8 },\n * { point: [40, 20, 8], tangent: [0, 1, 0], weight: 0.8 },\n * );\n * const tube = sweep(circle2d(2), rail);\n */\n Blend(start: CurveBlendEndpoint, end: CurveBlendEndpoint): NurbsCurve3D;\n /**\n * Create an exact G2 blend curve between two directed endpoints.\n *\n * This is the curvature-aware companion to `Curve.Blend()`. It returns a\n * degree-5 non-rational `NurbsCurve3D` that matches endpoint position,\n * tangent direction, and optional curvature/second-derivative vectors.\n *\n * @example\n * const rail = Curve.BlendG2(\n * { point: [0, 0, 0], tangent: [1, 0, 0], curvature: [0, 0.02, 0] },\n * { point: [50, 20, 0], tangent: [0, 1, 0], curvature: [-0.02, 0, 0] },\n * );\n */\n BlendG2(start: CurveBlendG2Endpoint, end: CurveBlendG2Endpoint): NurbsCurve3D;\n /**\n * Create an exact circular 3D arc from start, end, and start tangent.\n *\n * The returned curve is a rational quadratic `NurbsCurve3D`, split into\n * stable spans when needed, so it can feed `sweep` without sampling the\n * authoring intent away.\n *\n * @example\n * const rail = Curve.Arc({\n * start: [40, 0, 0],\n * end: [0, 40, 0],\n * tangent: [0, 1, 0],\n * });\n * const tube = sweep(circle2d(2), rail);\n */\n Arc(options: CurveArcOptions): NurbsCurve3D;\n /**\n * Create an exact straight 3D NURBS line segment.\n *\n * @example\n * const rail = Curve.Line([0, 0, 0], [80, 0, 15]);\n * const rib = sweep(circle2d(2), rail);\n */\n Line(start: Vec3, end: Vec3): NurbsCurve3D;\n /**\n * Create a polyline path as cloned 3D points.\n *\n * Identity helper — every path consumer accepts the raw `Vec3[]` array\n * directly, so this wrapper adds nothing over a plain point-array literal.\n *\n * @softDeprecated pass the Vec3[] points array directly — sweep(profile, points) and Curve.Trim/Reverse accept raw point arrays; use Curve.Route.fromPolyline(points) when the centerline needs bend/port metadata\n * @deprecated use pass the Vec3[] points array directly — sweep(profile, points) and Curve.Trim/Reverse accept raw point arrays; use Curve.Route.fromPolyline(points) when the centerline needs bend/port metadata\n */\n Polyline: (points: Vec3[]) => Vec3[];\n /**\n * Create a smooth Catmull-Rom spline path.\n *\n * Returns a sampled `Curve3D` approximation, which exact consumers reject\n * (`Curve.Trim`/`Curve.Reverse`, exact surface boundaries).\n *\n * @softDeprecated Curve.Fit(points) — exact NURBS interpolation through the same points (add { closed: true } for loops); unlike the sampled Catmull-Rom spline it works with Curve.Trim/Reverse, sweeps, and exact surface boundaries\n * @deprecated use Curve.Fit(points) — exact NURBS interpolation through the same points (add { closed: true } for loops); unlike the sampled Catmull-Rom spline it works with Curve.Trim/Reverse, sweeps, and exact surface boundaries\n */\n Spline: (points: Vec3[], options?: Spline3DOptions) => Curve3D;\n /**\n * Create an exact NURBS 3D curve from control points, weights, knots, and degree.\n *\n * @example\n * const rail = Curve.Nurbs([[0, 0, 0], [30, 4, 12], [60, -4, 12], [90, 0, 0]]);\n * const tube = sweep(circle2d(2), rail);\n */\n Nurbs(points: Vec3[], options?: NurbsCurve3DOptions): NurbsCurve3D;\n /**\n * Fit a non-rational NURBS curve that interpolates every input point.\n *\n * This is global B-spline interpolation, not approximate curve reduction:\n * ForgeCAD computes chord-length parameters, averaged clamped knots, solves\n * the control points, then verifies the interpolation residual against\n * `tolerance`. With `{ closed: true }` the fit is standard periodic B-spline\n * interpolation: the curve loops smoothly from the last point back to the\n * first (do not repeat the first point at the end).\n *\n * @example\n * const rail = Curve.Fit(\n * [[0, 0, 0], [20, 8, 12], [50, -4, 18], [80, 0, 0]],\n * { degree: 3, tolerance: 0.001 },\n * );\n * const tube = sweep(circle2d(2), rail);\n *\n * // Closed loop through four points — no duplicated closing point\n * const loop = Curve.Fit(\n * [[30, 0, 0], [0, 30, 0], [-30, 0, 0], [0, -30, 0]],\n * { closed: true },\n * );\n */\n Fit(points: Vec3[], options?: CurveFitOptions): NurbsCurve3D;\n /**\n * Extract an exact curve segment from normalized parameter `start` to `end`.\n *\n * `NurbsCurve3D` inputs are trimmed with exact knot insertion/subdomain\n * extraction. Polyline point arrays are trimmed by arclength over their exact\n * line segments. Sampled `Curve3D` splines are rejected until ForgeCAD has a\n * tolerance-controlled rebuild path.\n */\n Trim<T extends CurveTrimInput>(curve: T, start: number, end: number): CurveTrimOutput<T>;\n /**\n * Reverse an exact curve without changing its geometry.\n *\n * `NurbsCurve3D` inputs reverse control points, weights, and knots. Polyline\n * point arrays are cloned and reversed. Sampled `Curve3D` splines are rejected\n * until ForgeCAD has a tolerance-controlled rebuild path.\n */\n Reverse<T extends CurveTrimInput>(curve: T): CurveTrimOutput<T>;\n /**\n * Build analytic 3D line/arc routes for sweeps.\n *\n * `Curve.Route.fromPolyline()` is the canonical route API. It returns a\n * `Route3D` value object, preserving exact route segments, named port\n * frames, and the lowerable `route3d` sweep compile plan.\n *\n * @example\n * const route = Curve.Route.fromPolyline(\n * [[0, 0, 0], [0, 0, 50], [40, 0, 50]],\n * { cornerRadius: 12, startPort: \'inlet\', endPort: \'outlet\' },\n * );\n * const tube = sweep(circle2d(4), route);\n */\n Route: typeof Route3D;\n /**\n * Build helical paths and swept coils.\n *\n * `Curve.Helix` is the canonical namespace for helical paths and coils.\n * It uses the same sweep-based lowering as other curve paths.\n *\n * @example\n * const guide = Curve.Helix.path({ radius: 20, pitch: 6, turns: 4 });\n * const spring = Curve.Helix.coil({ radius: 20, pitch: 6, turns: 4, wireRadius: 1 });\n */\n Helix: {\n path(options: HelixOptions): CurveHelixPath;\n coil: CurveHelixCoil;\n };\n};\n/**\n * Import a module with optional ForgeCAD parameter overrides. Returns the module\'s exports.\n *\n * When importing a `.forge.js` file, most return values are passed through exactly as the script\n * returns them. Assembly returns have one extra composition rule: an unsolved `Assembly` is wrapped\n * as an `ImportedAssembly`, preserving `solve(state)` and `mergeInto()` across file boundaries,\n * while a returned `SolvedAssembly` stays a `SolvedAssembly`. If the script returns a metadata\n * object (e.g. `{ shape: myShape, bolts: {...} }`), the caller receives the full object —\n * renderable values and metadata together.\n *\n * **Script return contract:** a `.forge.js` script returns one of three shapes: a single\n * renderable (Shape, ShapeGroup, Sketch, SdfShape, Assembly), an array of renderables or\n * named descriptors (`{ name, shape|sketch|group }`), or a metadata object mixing renderable\n * values with plain data. When a script runs directly, renderable entries of a metadata\n * object are rendered under their key names and non-renderable entries are silently\n * skipped — both halves of the metadata contract: one return value serves the viewport\n * and `require()` callers.\n *\n * **Assembly return contract**\n *\n * | `.forge.js` return value | `require()` result |\n * |---|---|\n * | `Assembly` | `ImportedAssembly` |\n * | `SolvedAssembly` | `SolvedAssembly` |\n *\n * `ImportedAssembly` exposes default-pose helpers such as `getPart()`, `collisionReport()`, and\n * `minClearance()`. Use `solve(state)` first when inspecting a non-default pose.\n *\n * **Path rule:** Always include the file extension in relative imports: use\n * `require("./part.forge.js")` for model files and `require("./helpers.js")` for plain helper\n * modules. ForgeCAD does not apply Node-style extension inference, so `require("./part")` will\n * not find `part.forge.js` or `part.js`.\n *\n * **Parameter scoping:** Parameters declared in required files are automatically namespaced with\n * a `"filename#N / "` prefix (e.g. `"bracket.forge.js#1 / Width"`). This prevents collisions\n * when multiple files declare same-named params. Each file\'s params appear as separate sliders.\n *\n * **Parameter overrides:** When passing overrides, use the bare param name (not the scoped name).\n * Overrides are type-checked — unrecognized keys throw an error with typo suggestions.\n *\n * **Multi-file assembly pattern** — pass cross-cutting design values from the assembly to parts:\n *\n * ```js\n * // assembly.forge.js — owns cross-cutting params, passes to parts\n * const wall = param("Wall", 3);\n * const baseH = param("Base Height", 20);\n *\n * const mount = require(\'./motor-mount.forge.js\', { Wall: wall });\n * const base = require(\'./base-body.forge.js\', { Wall: wall, Height: baseH });\n * ```\n *\n * **Metadata pattern** — parts publish interface data alongside geometry:\n *\n * ```js\n * // motor-mount.forge.js\n * return { shape: mount, bolts: { dia: 5.3, pos: holePositions } };\n *\n * // base-body.forge.js\n * const mount = require(\'./motor-mount.forge.js\');\n * mount.bolts.pos // access the metadata\n * mount.shape // access the geometry\n * ```\n *\n * **Forge-aware builder module pattern** — use `.forge.js` modules for reusable\n * sketch, profile, shape, or assembly builders that need ForgeCAD runtime APIs:\n *\n * ```js\n * // profiles.forge.js — inspectable on its own, reusable through require()\n * function wheelProfile() {\n * return circle2d(40).subtract(circle2d(18));\n * }\n *\n * return {\n * preview: [{ name: \'Wheel profile\', sketch: wheelProfile() }],\n * make: { wheelProfile },\n * };\n *\n * // main.forge.js\n * const profiles = require(\'./profiles.forge.js\');\n * const wheel = profiles.make.wheelProfile().extrude(8);\n * ```\n *\n * Keep exported builders pure over top-level constants, top-level `param()` values,\n * or explicit function arguments. Do not declare new `param()` values inside an\n * exported builder if callers need `require(\'./profiles.forge.js\', { Width: 80 })`\n * overrides: import overrides are validated while the module loads, before any\n * exported builder is called. Use plain `.js` modules only for pure constants,\n * tables, math helpers, and formatting code that does not construct ForgeCAD geometry.\n *\n * **Entry detection (Node semantics):** `require.main` is the entry script\'s module\n * object, so `require.main === module` is true only in the file being run directly.\n * Part files use it to build standalone preview geometry only when opened directly —\n * importers then skip that work entirely:\n *\n * ```js\n * // part.forge.js\n * function bracket() { ... }\n * if (require.main === module) {\n * return { preview: [{ name: \'Bracket\', shape: bracket() }] }; // direct run: render it\n * }\n * return { make: { bracket } }; // imported: builders only\n * ```\n *\n * @concept import\n */\ndeclare function require$1(path: string, paramOverrides?: Record<string, number | string>): any;\n/** Namespaced file-format import helpers — the single vocabulary for bringing external geometry files into a model. @concept import */\ndeclare const Import: {\n /** Parse a DXF file and return closed 2D profile geometry as a Sketch. The result can be extruded directly. */\n dxfSketch(fileName: string, options?: DxfImportOptions): Sketch;\n /** Parse an SVG file and return it as a Sketch with options for region filtering, scaling, and simplification. */\n svgSketch(fileName: string, options?: SvgImportOptions): Sketch;\n /**\n * Import an external mesh file (STL, OBJ, 3MF).\n *\n * By default, 3MF build items are flattened into one Shape for compatibility.\n * Use `separateObjects: true` to import 3MF build items/resource objects as a\n * named ShapeGroup whose children are targetable by `forgecad ls`. Use `object`\n * to import one item by the stable ref/name reported by `forgecad run`.\n *\n * For 3MF sources, `forgecad run` prints a source-structure table with one line\n * per build item: `[3mf:build:NNN:object:N] name type=... verts=... tris=...\n * bbox=[min] → [max]`. Build items are numbered from `001`; files with no build\n * items list resource objects as `3mf:object:N` instead. Per-item bboxes reveal\n * multi-part structure — account for every substantial item before flattening.\n * Pass any listed stable ref or name as `object` to import that item alone.\n *\n * Use `sourceFrame: { up: "+Y" }` when the file was authored in a non-Z-up\n * coordinate system. ForgeCAD remains Z-up; the import is rotated so the named\n * source axis becomes ForgeCAD +Z. Supported values: `"+X"`, `"-X"`, `"+Y"`,\n * `"-Y"`, `"+Z"`, `"-Z"`.\n *\n * ```js\n * const all = Import.mesh("./assembly.3mf", { separateObjects: true });\n * const pin = all.child("Pin #001");\n * const plate = Import.mesh("./assembly.3mf", { object: "3mf:build:001:object:7" });\n * const yUpPart = Import.mesh("./part.obj", { sourceFrame: { up: "+Y" } });\n * ```\n */\n mesh(fileName: string, options?: MeshImportOptions): Shape | ShapeGroup;\n /** Import a STEP file (.step, .stp) as an exact OCCT-backed Shape. Preserves NURBS curves, B-spline surfaces, and exact topology. Requires running with the OCCT backend. Use `sourceFrame: { up: "+Y" }` to rotate Y-up source files into ForgeCAD\'s Z-up world. */\n step(fileName: string, options?: StepImportOptions): Shape;\n};\n/**\n * Parse an SVG file and return it as a Sketch.\n *\n * @softDeprecated Import.svgSketch(file, options)\n * @deprecated use Import.svgSketch(file, options)\n * @concept import\n */\ndeclare function importSvgSketch(fileName: string, options?: SvgImportOptions): Sketch;\n/**\n * Import an external mesh file (STL, OBJ, 3MF).\n *\n * Use `sourceFrame: { up: "+Y" }` to rotate a non-Z-up source file into\n * ForgeCAD\'s Z-up world.\n *\n * @softDeprecated Import.mesh(file, options)\n * @deprecated use Import.mesh(file, options)\n * @concept import\n */\ndeclare function importMesh(fileName: string, options?: MeshImportOptions): Shape | ShapeGroup;\n/**\n * Import a STEP file (.step, .stp) as an exact OCCT-backed Shape.\n *\n * Use `sourceFrame: { up: "+Y" }` to rotate a non-Z-up source file into\n * ForgeCAD\'s Z-up world.\n *\n * @softDeprecated Import.step(file)\n * @deprecated use Import.step(file)\n * @concept import\n */\ndeclare function importStep(fileName: string, options?: StepImportOptions): Shape;\n/**\n * Highlight any geometry for visual debugging in the viewport.\n *\n * Supported inputs:\n * - `string` — sketch entity ID (e.g. `\'L0\'`, `\'P0\'`, `\'C0\'`)\n * - `[x, y, z]` — 3D point\n * - `[[x1,y1,z1], [x2,y2,z2]]` — edge (line segment)\n * - `{ normal: [x,y,z], offset: number }` — plane by normal + distance from origin\n * - `{ normal: [x,y,z], point: [x,y,z] }` — plane by normal + point on plane\n * - `Shape` — highlight entire 3D shape\n * - `FaceRef` (from `shape.face(\'top\')`) — highlight as plane at face center\n * - `EdgeRef` (from `shape.edge(\'left\')`) — highlight as edge segment\n *\n * @softDeprecated Viewport.highlight(target, options) — same arguments, namespaced with the other render-only overlays\n * @deprecated use Viewport.highlight(target, options) — same arguments, namespaced with the other render-only overlays\n * @concept visual\n */\ndeclare function highlight(entityId: string, opts?: HighlightOptions): void;\ndeclare function highlight(point: [\n number,\n number,\n number\n], opts?: HighlightOptions): void;\ndeclare function highlight(edge: [\n [\n number,\n number,\n number\n ],\n [\n number,\n number,\n number\n ]\n], opts?: HighlightOptions): void;\ndeclare function highlight(plane: {\n normal: [\n number,\n number,\n number\n ];\n offset: number;\n}, opts?: HighlightOptions): void;\ndeclare function highlight(plane: {\n normal: [\n number,\n number,\n number\n ];\n point: [\n number,\n number,\n number\n ];\n}, opts?: HighlightOptions): void;\ndeclare function highlight(shape: Shape, opts?: HighlightOptions): void;\ndeclare function highlight(face: FaceRef, opts?: HighlightOptions): void;\ndeclare function highlight(edge: EdgeRef, opts?: HighlightOptions): void;\n/** All library parts. Access via `lib.xxx()` in scripts. */\ndeclare const lib: typeof partLibrary;\n\n/** Route step factories. Access via `route.line()`, `route.fillet()`, etc. */\ndeclare const route: typeof routeStepFactories;\n\n/** Connector factory. Create attachment points: `connector({...})`, `connector.male(type, {...})`, etc. */\ndeclare const connector: typeof connectorFactory;\n';
1185
1185
  const EDITOR_RESULT_STATUS_BOTTOM_PADDING = 28;
1186
1186
  const EDITOR_ERROR_STATUS_BOTTOM_PADDING = 88;
1187
1187
  function resolveMonacoTheme(theme) {
@@ -1685,6 +1685,8 @@ function CommandPalette() {
1685
1685
  const viewPanelOpen = useForgeStore((s) => s.viewPanelOpen);
1686
1686
  const toggleViewPanel = useForgeStore((s) => s.toggleViewPanel);
1687
1687
  const requestViewCommand = useForgeStore((s) => s.requestViewCommand);
1688
+ const cameraControlMode = useForgeStore((s) => s.cameraControlMode);
1689
+ const setCameraControlMode = useForgeStore((s) => s.setCameraControlMode);
1688
1690
  const focusObjectsWithTag = useForgeStore((s) => s.focusObjectsWithTag);
1689
1691
  const clearFocusedObject = useForgeStore((s) => s.clearFocusedObject);
1690
1692
  const openObjectSearch = useForgeStore((s) => s.openObjectSearch);
@@ -1968,6 +1970,15 @@ function CommandPalette() {
1968
1970
  close();
1969
1971
  }
1970
1972
  },
1973
+ {
1974
+ id: "toggle-fly-camera",
1975
+ label: cameraControlMode === "fly" ? "Return to Orbit Camera" : "Enter Fly Camera",
1976
+ searchText: "fly camera drone first person game viewport explore walkthrough",
1977
+ action: () => {
1978
+ setCameraControlMode(cameraControlMode === "fly" ? "orbit" : "fly");
1979
+ close();
1980
+ }
1981
+ },
1971
1982
  ...hasSearchableObjects ? [
1972
1983
  {
1973
1984
  id: "search-objects",
@@ -3655,6 +3666,12 @@ function FileExplorer() {
3655
3666
  }
3656
3667
  setRenamingPath(null);
3657
3668
  };
3669
+ const beginRename = (path) => {
3670
+ if (isReadOnly) return;
3671
+ setRenamingPath(path);
3672
+ setRenameValue(getBaseName(path));
3673
+ setContextMenu(null);
3674
+ };
3658
3675
  const handleFileDrop = async (e, targetFolder) => {
3659
3676
  e.preventDefault();
3660
3677
  e.stopPropagation();
@@ -3844,8 +3861,11 @@ function FileExplorer() {
3844
3861
  lastClickedPath.current = node.path;
3845
3862
  openProjectFileInNewWindow(node.path);
3846
3863
  },
3847
- onDoubleClick: () => {
3864
+ onDoubleClick: (e) => {
3865
+ e.stopPropagation();
3866
+ if (isRenaming) return;
3848
3867
  if (isFolder) toggleFolder(node.path);
3868
+ else beginRename(node.path);
3849
3869
  },
3850
3870
  onContextMenu: (e) => {
3851
3871
  e.preventDefault();
@@ -3901,6 +3921,7 @@ function FileExplorer() {
3901
3921
  autoFocus: true,
3902
3922
  value: renameValue,
3903
3923
  onChange: (e) => setRenameValue(e.target.value),
3924
+ onFocus: (e) => e.currentTarget.select(),
3904
3925
  onBlur: () => handleRename(node.path),
3905
3926
  onKeyDown: (e) => {
3906
3927
  if (e.key === "Enter") handleRename(node.path);
@@ -4139,9 +4160,7 @@ function FileExplorer() {
4139
4160
  {
4140
4161
  className: "fc-file-context-menu-item",
4141
4162
  onClick: () => {
4142
- setRenamingPath(contextMenu.path);
4143
- setRenameValue(getBaseName(contextMenu.path));
4144
- setContextMenu(null);
4163
+ beginRename(contextMenu.path);
4145
4164
  },
4146
4165
  children: "Rename"
4147
4166
  }
@@ -4801,6 +4820,18 @@ const sections = [
4801
4820
  { keys: "Esc", description: "Clear focused objects" }
4802
4821
  ]
4803
4822
  },
4823
+ {
4824
+ title: "Fly Camera",
4825
+ shortcuts: [
4826
+ { keys: "Click viewport", description: "Lock mouse steering" },
4827
+ { keys: "W / A / S / D", description: "Move through the model" },
4828
+ { keys: "Mouse", description: "Steer camera" },
4829
+ { keys: "Space", description: "Move up" },
4830
+ { keys: "Shift Space", description: "Move down" },
4831
+ { keys: "Alt", description: "Fine movement" },
4832
+ { keys: "Esc", description: "Release mouse or exit fly" }
4833
+ ]
4834
+ },
4804
4835
  {
4805
4836
  title: "Code Editor",
4806
4837
  shortcuts: [
@@ -9780,6 +9811,14 @@ function useViewPanelState() {
9780
9811
  const setScanGranularity = useForgeStore((s) => s.setScanGranularity);
9781
9812
  const inspectPointSampleCount = useForgeStore((s) => s.inspectPointSampleCount);
9782
9813
  const setInspectPointSampleCount = useForgeStore((s) => s.setInspectPointSampleCount);
9814
+ const inspectColormap = useForgeStore((s) => s.inspectColormap);
9815
+ const setInspectColormap = useForgeStore((s) => s.setInspectColormap);
9816
+ const inspectIsolinesEnabled = useForgeStore((s) => s.inspectIsolinesEnabled);
9817
+ const setInspectIsolinesEnabled = useForgeStore((s) => s.setInspectIsolinesEnabled);
9818
+ const inspectIsolineSpacing = useForgeStore((s) => s.inspectIsolineSpacing);
9819
+ const setInspectIsolineSpacing = useForgeStore((s) => s.setInspectIsolineSpacing);
9820
+ const inspectCriticalLineEnabled = useForgeStore((s) => s.inspectCriticalLineEnabled);
9821
+ const setInspectCriticalLineEnabled = useForgeStore((s) => s.setInspectCriticalLineEnabled);
9783
9822
  const comparisonInspectMode = useForgeStore((s) => s.comparisonInspectMode);
9784
9823
  const setComparisonInspectMode = useForgeStore((s) => s.setComparisonInspectMode);
9785
9824
  const comparisonCandidateOpacity = useForgeStore((s) => s.comparisonCandidateOpacity);
@@ -9788,6 +9827,8 @@ function useViewPanelState() {
9788
9827
  const setComparisonReferenceOpacity = useForgeStore((s) => s.setComparisonReferenceOpacity);
9789
9828
  const projectionMode = useForgeStore((s) => s.projectionMode);
9790
9829
  const setProjectionMode = useForgeStore((s) => s.setProjectionMode);
9830
+ const cameraControlMode = useForgeStore((s) => s.cameraControlMode);
9831
+ const setCameraControlMode = useForgeStore((s) => s.setCameraControlMode);
9791
9832
  const gridEnabled = useForgeStore((s) => s.gridEnabled);
9792
9833
  const axesVisible = useForgeStore((s) => s.axesVisible);
9793
9834
  const viewCubeVisible = useForgeStore((s) => s.viewCubeVisible);
@@ -9965,6 +10006,14 @@ function useViewPanelState() {
9965
10006
  setScanGranularity,
9966
10007
  inspectPointSampleCount,
9967
10008
  setInspectPointSampleCount,
10009
+ inspectColormap,
10010
+ setInspectColormap,
10011
+ inspectIsolinesEnabled,
10012
+ setInspectIsolinesEnabled,
10013
+ inspectIsolineSpacing,
10014
+ setInspectIsolineSpacing,
10015
+ inspectCriticalLineEnabled,
10016
+ setInspectCriticalLineEnabled,
9968
10017
  comparisonInspectMode,
9969
10018
  setComparisonInspectMode,
9970
10019
  comparisonCandidateOpacity,
@@ -9973,6 +10022,8 @@ function useViewPanelState() {
9973
10022
  setComparisonReferenceOpacity,
9974
10023
  projectionMode,
9975
10024
  setProjectionMode,
10025
+ cameraControlMode,
10026
+ setCameraControlMode,
9976
10027
  gridEnabled,
9977
10028
  axesVisible,
9978
10029
  viewCubeVisible,
@@ -10219,10 +10270,20 @@ function ViewPanel() {
10219
10270
  setScanGranularity,
10220
10271
  inspectPointSampleCount,
10221
10272
  setInspectPointSampleCount,
10273
+ inspectColormap,
10274
+ setInspectColormap,
10275
+ inspectIsolinesEnabled,
10276
+ setInspectIsolinesEnabled,
10277
+ inspectIsolineSpacing,
10278
+ setInspectIsolineSpacing,
10279
+ inspectCriticalLineEnabled,
10280
+ setInspectCriticalLineEnabled,
10222
10281
  comparisonCandidateOpacity,
10223
10282
  setComparisonCandidateOpacity,
10224
10283
  projectionMode,
10225
10284
  setProjectionMode,
10285
+ cameraControlMode,
10286
+ setCameraControlMode,
10226
10287
  gridEnabled,
10227
10288
  axesVisible,
10228
10289
  viewCubeVisible,
@@ -10457,6 +10518,21 @@ function ViewPanel() {
10457
10518
  };
10458
10519
  const renderViewTab = () => /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
10459
10520
  /* @__PURE__ */ jsxRuntimeExports.jsxs(Section, { title: "Camera", children: [
10521
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fc-view-panel-control-block", children: [
10522
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FieldLabel, { children: "Control" }),
10523
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(ButtonGrid, { min: 96, children: [
10524
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: btn(cameraControlMode === "orbit"), onClick: () => setCameraControlMode("orbit"), title: "Orbit camera", children: "Orbit" }),
10525
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
10526
+ "button",
10527
+ {
10528
+ className: btn(cameraControlMode === "fly"),
10529
+ onClick: () => setCameraControlMode("fly"),
10530
+ title: "Fly camera. Click the viewport to steer; Esc releases the mouse.",
10531
+ children: "Fly"
10532
+ }
10533
+ )
10534
+ ] })
10535
+ ] }),
10460
10536
  /* @__PURE__ */ jsxRuntimeExports.jsxs(ButtonGrid, { min: 68, children: [
10461
10537
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: btn(), onClick: () => requestViewCommand({ type: "snap", view: "iso" }), children: "Home" }),
10462
10538
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: btn(), onClick: () => requestViewCommand({ type: "fit" }), children: "Fit" }),
@@ -10692,7 +10768,60 @@ function ViewPanel() {
10692
10768
  }
10693
10769
  )
10694
10770
  ] })
10695
- ] })
10771
+ ] }),
10772
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fc-view-panel-control-block", children: [
10773
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FieldLabel, { children: "Colormap" }),
10774
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
10775
+ "select",
10776
+ {
10777
+ className: "fc-view-panel-input",
10778
+ value: inspectColormap,
10779
+ onChange: (event) => setInspectColormap(resolveColormapName(event.target.value)),
10780
+ "aria-label": "Heatmap colormap",
10781
+ children: COLORMAP_OPTIONS.map((option) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: option.name, children: option.label }, option.name))
10782
+ }
10783
+ )
10784
+ ] }),
10785
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fc-view-panel-control-block", children: [
10786
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(ToggleRow, { children: [
10787
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
10788
+ "input",
10789
+ {
10790
+ type: "checkbox",
10791
+ checked: inspectIsolinesEnabled,
10792
+ onChange: (event) => setInspectIsolinesEnabled(event.target.checked)
10793
+ }
10794
+ ),
10795
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Isolines" })
10796
+ ] }),
10797
+ inspectIsolinesEnabled && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fc-view-panel-row", children: [
10798
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FieldLabel, { children: "Spacing" }),
10799
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
10800
+ "input",
10801
+ {
10802
+ className: "fc-view-panel-input fc-view-panel-number-input",
10803
+ type: "number",
10804
+ min: 0.01,
10805
+ step: 0.1,
10806
+ value: inspectIsolineSpacing,
10807
+ onChange: (event) => setInspectIsolineSpacing(Number(event.target.value)),
10808
+ "aria-label": "Isoline value spacing",
10809
+ title: "Value spacing between isolines"
10810
+ }
10811
+ )
10812
+ ] })
10813
+ ] }),
10814
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fc-view-panel-control-block", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(ToggleRow, { children: [
10815
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
10816
+ "input",
10817
+ {
10818
+ type: "checkbox",
10819
+ checked: inspectCriticalLineEnabled,
10820
+ onChange: (event) => setInspectCriticalLineEnabled(event.target.checked)
10821
+ }
10822
+ ),
10823
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Critical line" })
10824
+ ] }) })
10696
10825
  ] }),
10697
10826
  inspectChannel === "comparison" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fc-view-panel-control-block", children: [
10698
10827
  /* @__PURE__ */ jsxRuntimeExports.jsx(FieldLabel, { children: "Opacity" }),
@@ -13217,7 +13346,7 @@ function MobileCommandPalette({ onClose, onOpenFilePicker, onOpenJoints }) {
13217
13346
  const match = input.match(/gist\.github\.com\/(?:[^/]+\/)?([a-f0-9]+)/i);
13218
13347
  const gistId = match ? match[1] : input.trim();
13219
13348
  __vitePreload(async () => {
13220
- const { fetchGistModel: fetchGistModel2 } = await import("./app-C9ct2hRD.js").then((n) => n.aC);
13349
+ const { fetchGistModel: fetchGistModel2 } = await import("./app-D6ccu2Xx.js").then((n) => n.aC);
13221
13350
  return { fetchGistModel: fetchGistModel2 };
13222
13351
  }, true ? __vite__mapDeps([0]) : void 0).then(
13223
13352
  ({ fetchGistModel: fetchGistModel2 }) => fetchGistModel2(gistId).then((model) => {
@@ -13235,7 +13364,7 @@ function MobileCommandPalette({ onClose, onOpenFilePicker, onOpenJoints }) {
13235
13364
  const input = window.prompt("Paste a URL to a .forge.js file:");
13236
13365
  if (!input) return;
13237
13366
  __vitePreload(async () => {
13238
- const { fetchUrlModel: fetchUrlModel2 } = await import("./app-C9ct2hRD.js").then((n) => n.aC);
13367
+ const { fetchUrlModel: fetchUrlModel2 } = await import("./app-D6ccu2Xx.js").then((n) => n.aC);
13239
13368
  return { fetchUrlModel: fetchUrlModel2 };
13240
13369
  }, true ? __vite__mapDeps([0]) : void 0).then(
13241
13370
  ({ fetchUrlModel: fetchUrlModel2 }) => fetchUrlModel2(input.trim()).then((model) => {