forgecad 0.9.14 → 0.9.16
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.
- package/LICENSE +6 -4
- package/README.md +8 -4
- package/dist/assets/{AdminPage-eWGs2K6H.js → AdminPage-CXvls4-J.js} +2 -2
- package/dist/assets/{BenchmarkPage-CTrLKfpo.js → BenchmarkPage-B27zk8xL.js} +4 -15
- package/dist/assets/{BlogPage-5nPesyds.js → BlogPage-CMAVvgQL.js} +2 -2
- package/dist/assets/{DocsPage-C4Y3nbYc.js → DocsPage-knf4I4h7.js} +9 -3
- package/dist/assets/EditorApp-BHMQlJ-D.js +14686 -0
- package/dist/assets/{EditorApp-BAnckbsk.css → EditorApp-BpjZgzk0.css} +846 -0
- package/dist/assets/{EmbedViewer-C8fB4n5U.js → EmbedViewer-D7ZGlFjx.js} +3 -3
- package/dist/assets/{LandingPageProofDriven-jSz0LaMM.js → LandingPageProofDriven-CnevhTE8.js} +36 -38
- package/dist/assets/LegalPage-BPTUmqeg.js +39 -0
- package/dist/assets/LegalPage-BRlScr9A.css +91 -0
- package/dist/assets/{PricingPage-B83B90zh.js → PricingPage-B0D4goG_.js} +19 -19
- package/dist/assets/{PricingPage-BMedqFef.css → PricingPage-BPF6HKyO.css} +25 -0
- package/dist/assets/{SettingsPage-DY889pcu.js → SettingsPage-CFF-UgjI.js} +2 -2
- package/dist/assets/app-CE3sYcV7.css +3890 -0
- package/dist/assets/{app-bEww1ic4.js → app-T0pDcSX4.js} +3382 -1069
- package/dist/assets/cli/{render-Cho2uKG_.js → render-C5pcIISc.js} +477 -29
- package/dist/assets/{constructionHistoryWorker-HYwzJY4m.js → constructionHistoryWorker-Ba2Hm58b.js} +928 -243
- package/dist/assets/{evalWorker-CjQwJSE-.js → evalWorker-vkx310U2.js} +8883 -6040
- package/dist/assets/{forgecad_geometry-CH2nvuLA.js → forgecad_geometry-Dgceylq9.js} +43 -1
- package/dist/assets/forgecad_geometry_bg-dD4RNQF1.wasm +0 -0
- package/dist/assets/{inspectWorker-DeRnMVv1.js → inspectWorker-BuTJDVX6.js} +1179 -273
- package/dist/assets/{javascript-70-4uGcz.js → javascript-1kQXfVaz.js} +1 -1
- package/dist/assets/{targets-D6PWsv6X.js → jointPose-B_Cgedn9.js} +71 -3
- package/dist/assets/landing-proof-driven-DiGqdtWa.js +18 -0
- package/dist/assets/{landing-proof-driven-oFYW6mjz.css → landing-proof-driven-ORyigZ6p.css} +13 -7
- package/dist/assets/legalContent-ZfFGMmi4.js +251 -0
- package/dist/assets/{manifold-rmfAcdwF.js → manifold-BWgsjmAM.js} +1 -1
- package/dist/assets/{manifold-uRzgk5O8.js → manifold-D6IFSkhH.js} +2 -2
- package/dist/assets/{manifold-CG9Fokx-.js → manifold-rZexZI0G.js} +1 -1
- package/dist/assets/{reportWorker-4cW_ZpoS.js → reportWorker-0AGij1Ru.js} +8659 -12771
- package/dist/assets/{scalar-sampling-budget-CfDiFvh7.js → scalar-sampling-budget-J5cuzxT1.js} +8050 -6203
- package/dist/assets/{scanProxyWorker-Bs2TDgLw.js → scanProxyWorker-Vl4Wxa1y.js} +50 -6
- package/dist/assets/{solver-DuJAO8S6.js → solver-BZ9LPTHs.js} +1 -1
- package/dist/assets/solver_bg-DAHZJ_rw.wasm +0 -0
- package/dist/assets/{vendor-react-Da3A2QmU.js → vendor-react-6j1Kke-Y.js} +6 -5
- package/dist/cli/render.html +1 -1
- package/dist/docs/index.html +2 -2
- package/dist/docs-raw/AI/ai-native-cad.md +50 -0
- package/dist/docs-raw/AI/usage.md +5 -12
- package/dist/docs-raw/CLI.md +34 -10
- package/dist/docs-raw/component-model.md +27 -11
- package/dist/docs-raw/generated/assembly.md +374 -187
- package/dist/docs-raw/generated/concepts.md +245 -237
- package/dist/docs-raw/generated/core.md +283 -6
- package/dist/docs-raw/generated/curves.md +274 -361
- package/dist/docs-raw/generated/lib.md +9 -19
- package/dist/docs-raw/generated/output.md +29 -4
- package/dist/docs-raw/generated/runtime-names.md +49 -0
- package/dist/docs-raw/generated/sdf.md +31 -0
- package/dist/docs-raw/generated/sheet-metal.md +9 -0
- package/dist/docs-raw/generated/sketch.md +44 -1
- package/dist/docs-raw/generated/viewport.md +11 -3
- package/dist/docs-raw/guides/coordinate-system.md +20 -16
- package/dist/docs-raw/guides/geometry-conventions.md +2 -2
- package/dist/docs-raw/guides/inspection-bundles.md +2 -1
- package/dist/docs-raw/guides/joint-design.md +24 -0
- package/dist/docs-raw/guides/positioning.md +13 -3
- package/dist/docs-raw/legal/privacy.md +63 -0
- package/dist/docs-raw/legal/software-license.md +55 -0
- package/dist/docs-raw/legal/terms.md +87 -0
- package/dist/docs-raw/skills/forgecad-3d-reconstruction.md +1 -1
- package/dist/docs-raw/skills/forgecad-blockout-model.md +1 -1
- package/dist/docs-raw/skills/forgecad-component-model.md +11 -2
- package/dist/docs-raw/skills/forgecad-high-level-spec.md +1 -1
- package/dist/docs-raw/skills/forgecad-image-replicator.md +8 -8
- package/dist/docs-raw/skills/forgecad-lld.md +1 -1
- package/dist/docs-raw/skills/forgecad-make-a-model.md +40 -39
- package/dist/docs-raw/skills/forgecad-model-grader.md +2 -2
- package/dist/docs-raw/skills/forgecad-prepare-prompt.md +2 -2
- package/dist/docs-raw/skills/forgecad-project.md +3 -1
- package/dist/docs-raw/skills/forgecad-reconstruction-benchmark.md +1 -1
- package/dist/docs-raw/skills/forgecad-render-inspect.md +4 -2
- package/dist/docs-raw/skills/forgecad-visual-spec.md +1 -1
- package/dist/docs-raw/skills/forgecad.md +4 -3
- package/dist/docs-raw/welcome.md +2 -0
- package/dist/index.html +40 -12
- package/dist/llms.txt +8 -0
- package/dist/site.webmanifest +1 -1
- package/dist/sitemap.xml +49 -13
- package/dist-cli/{check-compiler-U5SOPN7X.js → check-compiler-SYQ2PWOB.js} +1 -2
- package/dist-cli/{check-query-propagation-XOKNSSYU.js → check-query-propagation-HIAGV62W.js} +1 -2
- package/dist-cli/{chunk-EXWGNL6K.js → chunk-SPZE3DUY.js} +20659 -17930
- package/dist-cli/forgecad.js +3568 -1250
- package/dist-cli/{forgecad_geometry-GYVNKPIE.js → forgecad_geometry-QOQIIP53.js} +42 -1
- package/dist-cli/forgecad_geometry_bg.wasm +0 -0
- package/dist-cli/{solver-46FFSK2U.js → solver-OK4HECRH.js} +0 -1
- package/dist-cli/solver_bg.wasm +0 -0
- package/dist-skill/CONTEXT.md +1192 -725
- package/dist-skill/SKILL.md +3 -2
- package/dist-skill/docs/API/core/concepts.md +64 -1
- package/dist-skill/docs/CLI.md +34 -10
- package/dist-skill/docs/generated/assembly.md +339 -213
- package/dist-skill/docs/generated/core.md +283 -6
- package/dist-skill/docs/generated/curves.md +272 -362
- package/dist-skill/docs/generated/lib.md +9 -19
- package/dist-skill/docs/generated/output.md +29 -4
- package/dist-skill/docs/generated/runtime-names.md +40 -0
- package/dist-skill/docs/generated/sdf.md +31 -0
- package/dist-skill/docs/generated/sheet-metal.md +9 -0
- package/dist-skill/docs/generated/sketch.md +44 -2
- package/dist-skill/docs/generated/viewport.md +2 -87
- package/dist-skill/docs/guides/coordinate-system.md +20 -16
- package/dist-skill/docs/guides/geometry-conventions.md +2 -2
- package/dist-skill/docs/guides/inspection-bundles.md +2 -1
- package/dist-skill/docs/guides/joint-design.md +24 -0
- package/dist-skill/docs/guides/positioning.md +13 -3
- package/dist-skill/library/forgecad-component-model/SKILL.md +10 -1
- package/dist-skill/library/forgecad-image-replicator/SKILL.md +6 -6
- package/dist-skill/library/forgecad-image-replicator/scripts/compare_images.py +166 -0
- package/dist-skill/library/forgecad-make-a-model/SKILL.md +39 -38
- package/dist-skill/library/forgecad-model-grader/SKILL.md +1 -1
- package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +1 -1
- package/dist-skill/library/forgecad-project/SKILL.md +2 -0
- package/dist-skill/library/forgecad-render-inspect/SKILL.md +3 -1
- package/examples/api/assembly-kinematics-foundation.forge.js +65 -0
- package/examples/api/assembly-kinematics-four-bar.forge.js +115 -0
- package/examples/api/assembly-kinematics-limb.forge.js +116 -0
- package/examples/api/connector-frame-rig-chain.forge.js +102 -0
- package/examples/api/exact-sheet-shell-assembly.forge.js +0 -2
- package/examples/api/exact-surface-studio.forge.js +6 -8
- package/examples/api/helix-basics.forge.js +8 -8
- package/examples/api/lean-foundations/README.md +12 -0
- package/examples/api/lean-foundations/curve-blend-exact.forge.js +22 -0
- package/examples/api/lean-foundations/curve-fit-interpolation.forge.js +18 -0
- package/examples/api/lean-foundations/curve-helix-canonicalization.forge.js +27 -0
- package/examples/api/lean-foundations/curve-route-canonicalization.forge.js +16 -0
- package/examples/api/lean-foundations/curve-trim-reverse.forge.js +24 -0
- package/examples/api/lean-foundations/exact-curve-arc.forge.js +36 -0
- package/examples/api/mixed-edge-finishes-proof.forge.js +8 -11
- package/examples/api/route3d-elbow.forge.js +71 -0
- package/examples/api/transition-curves.forge.js +44 -15
- package/examples/api/variable-sweep-test.forge.js +3 -1
- package/examples/api/y-blend-corner-showcase.forge.js +0 -2
- package/examples/generative/coral-vase.forge.js +1 -1
- package/examples/nurbs-tube.forge.js +1 -1
- package/package.json +17 -13
- package/dist/assets/EditorApp-lXv53A1m.js +0 -13610
- package/dist/assets/app-CsHnaBWt.css +0 -1789
- package/dist/assets/forgecad_geometry_bg-C5_E9Oa9.wasm +0 -0
- package/dist/assets/solver_bg-CWvv4lnN.wasm +0 -0
- package/dist/docs-raw/API/README.md +0 -16
- package/dist/docs-raw/API/core/concepts.md +0 -118
- package/dist/docs-raw/INDEX.md +0 -138
- package/dist/docs-raw/RELEASING.md +0 -87
- package/dist/docs-raw/agent-native-api.md +0 -27
- package/dist/docs-raw/beta-deployment.md +0 -304
- package/dist/docs-raw/beta-operations.md +0 -325
- package/dist/docs-raw/blueprint-first.md +0 -145
- package/dist/docs-raw/cli-monetization.md +0 -112
- package/dist/docs-raw/coding-best-practices.md +0 -120
- package/dist/docs-raw/coding.md +0 -340
- package/dist/docs-raw/deployment.md +0 -374
- package/dist/docs-raw/guides/skill-maintenance.md +0 -161
- package/dist/docs-raw/guides/surface-members.md +0 -82
- package/dist/docs-raw/harbor-cli.md +0 -854
- package/dist/docs-raw/internals/backend-vocabulary.md +0 -35
- package/dist/docs-raw/internals/compiler.md +0 -307
- package/dist/docs-raw/internals/constraint-solver-quality.md +0 -161
- package/dist/docs-raw/internals/constraint-solver.md +0 -176
- package/dist/docs-raw/internals/shape-from-slices.md +0 -152
- package/dist/docs-raw/internals/sketch-2d-pipeline.md +0 -108
- package/dist/docs-raw/platform/admin.md +0 -45
- package/dist/docs-raw/platform/architecture.md +0 -82
- package/dist/docs-raw/platform/auth.md +0 -139
- package/dist/docs-raw/platform/email.md +0 -67
- package/dist/docs-raw/platform/google-oauth-setup.md +0 -88
- package/dist/docs-raw/platform/observability.md +0 -197
- package/dist/docs-raw/platform/projects.md +0 -111
- package/dist/docs-raw/platform/sharing.md +0 -90
- package/dist/docs-raw/product/README.md +0 -39
- package/dist/docs-raw/product/api-as-product-language.md +0 -13
- package/dist/docs-raw/product/business-model.md +0 -15
- package/dist/docs-raw/product/competitive-positioning.md +0 -17
- package/dist/docs-raw/product/creative-manufacturing.md +0 -15
- package/dist/docs-raw/product/founder-story.md +0 -11
- package/dist/docs-raw/product/manufacturing-workflows.md +0 -15
- package/dist/docs-raw/product/onboarding-first-experience.md +0 -256
- package/dist/docs-raw/product/product-loop.md +0 -17
- package/dist/docs-raw/product/strategic-decisions.md +0 -22
- package/dist/docs-raw/product/user-outreach-email-templates.md +0 -161
- package/dist/docs-raw/product/user-segments.md +0 -15
- package/dist/docs-raw/product/vision.md +0 -26
- package/dist/docs-raw/rl-environments.md +0 -350
- package/dist/docs-raw/runbook.md +0 -611
- package/dist-cli/check-compiler-U5SOPN7X.js.map +0 -1
- package/dist-cli/check-query-propagation-XOKNSSYU.js.map +0 -1
- package/dist-cli/chunk-EXWGNL6K.js.map +0 -1
- package/dist-cli/forgecad.js.map +0 -1
- package/dist-cli/forgecad_geometry-GYVNKPIE.js.map +0 -1
- package/dist-cli/solver-46FFSK2U.js.map +0 -1
- package/dist-skill/SKILL-dev.md +0 -145
- package/dist-skill/docs-dev/API/core/concepts.md +0 -118
- package/dist-skill/docs-dev/CLI.md +0 -677
- package/dist-skill/docs-dev/agent-native-api.md +0 -27
- package/dist-skill/docs-dev/blueprint-first.md +0 -145
- package/dist-skill/docs-dev/coding-best-practices.md +0 -120
- package/dist-skill/docs-dev/coding.md +0 -340
- package/dist-skill/docs-dev/component-model.md +0 -164
- package/dist-skill/docs-dev/generated/assembly.md +0 -794
- package/dist-skill/docs-dev/generated/core.md +0 -2117
- package/dist-skill/docs-dev/generated/curves.md +0 -2583
- package/dist-skill/docs-dev/generated/lib.md +0 -169
- package/dist-skill/docs-dev/generated/output.md +0 -247
- package/dist-skill/docs-dev/generated/sdf.md +0 -446
- package/dist-skill/docs-dev/generated/sheet-metal.md +0 -504
- package/dist-skill/docs-dev/generated/sketch.md +0 -1811
- package/dist-skill/docs-dev/generated/viewport.md +0 -585
- package/dist-skill/docs-dev/generated/wood.md +0 -108
- package/dist-skill/docs-dev/guides/coordinate-system.md +0 -46
- package/dist-skill/docs-dev/guides/geometry-conventions.md +0 -52
- package/dist-skill/docs-dev/guides/inspection-bundles.md +0 -485
- package/dist-skill/docs-dev/guides/joint-design.md +0 -78
- package/dist-skill/docs-dev/guides/modeling-recipes.md +0 -78
- package/dist-skill/docs-dev/guides/positioning.md +0 -161
- package/dist-skill/docs-dev/guides/skill-maintenance.md +0 -161
- package/dist-skill/docs-dev/internals/backend-vocabulary.md +0 -35
- package/dist-skill/docs-dev/internals/compiler.md +0 -307
- package/dist-skill/docs-dev/internals/constraint-solver-quality.md +0 -161
- package/dist-skill/docs-dev/internals/constraint-solver.md +0 -176
- package/dist-skill/docs-dev/internals/sketch-2d-pipeline.md +0 -108
- package/dist-skill/library/forgecad-image-replicator/scripts/compare_images.mjs +0 -289
- package/examples/api/bolted-service-cover.forge.js +0 -17
- package/examples/api/cable-gland-anchor.forge.js +0 -14
- package/examples/api/captured-cartridge-guide.forge.js +0 -14
- package/examples/api/captured-linear-slide.forge.js +0 -13
- package/examples/api/clevis-pin-joint.forge.js +0 -13
- package/examples/api/datum-enclosure.forge.js +0 -16
- package/examples/api/hose-barb-port.forge.js +0 -14
- package/examples/api/knuckled-hinge-assembly.forge.js +0 -15
- package/examples/api/living-hinge-cover.forge.js +0 -14
- package/examples/api/pcb-terminal-block.forge.js +0 -22
- package/examples/api/pinned-lever-pivot-stack.forge.js +0 -14
- package/examples/api/retained-shaft-knob-stack.forge.js +0 -15
- package/examples/api/routed-tube-clip.forge.js +0 -15
- package/examples/api/seated-bearing-stack.forge.js +0 -30
- package/examples/api/snap-latch-cover.forge.js +0 -14
- package/examples/api/thumb-screw-clamp.forge.js +0 -15
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Oriented serial-chain limb posed entirely by the assembly rig API.
|
|
2
|
+
//
|
|
3
|
+
// The motion skeleton is authored as kinematic LINKS in the limb's sagittal
|
|
4
|
+
// (Y/Z) plane; EDGES hold the bone lengths; ANGLES drive the hip and knee.
|
|
5
|
+
// Bone geometry is built once at the origin and attached with two-connector
|
|
6
|
+
// MATES, so each bone ROTATES to span between its two solved links — it does
|
|
7
|
+
// not merely translate. This is the oriented serial chain that position-only
|
|
8
|
+
// link mates could not pose.
|
|
9
|
+
|
|
10
|
+
scene({
|
|
11
|
+
background: { top: '#cdd3da', bottom: '#5d6770' },
|
|
12
|
+
views: {
|
|
13
|
+
// Look straight down +X so the Y/Z motion plane faces the camera.
|
|
14
|
+
side: { camera: { position: [320, -25, -35], target: [0, -25, -35], up: [0, 0, 1], fov: 34 } },
|
|
15
|
+
iso: { camera: { position: [240, -190, 90], target: [0, -25, -40], up: [0, 0, 1], fov: 34 } },
|
|
16
|
+
},
|
|
17
|
+
lights: [
|
|
18
|
+
{ type: 'ambient', color: '#eef0f4', intensity: 0.4 },
|
|
19
|
+
{ type: 'directional', position: [240, -200, 320], color: '#fff1dd', intensity: 2.2, castShadow: true },
|
|
20
|
+
],
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const r = 7;
|
|
24
|
+
|
|
25
|
+
// A bone running from its `near` connector at the origin to `far` at [0, L, 0].
|
|
26
|
+
// Its connector axis (+Y) is the bone's own length direction.
|
|
27
|
+
function bone(L, color) {
|
|
28
|
+
return cylinder(L, r)
|
|
29
|
+
.pointAlong([0, 1, 0])
|
|
30
|
+
.color(color)
|
|
31
|
+
.material({ metalness: 0.1, roughness: 0.55 })
|
|
32
|
+
.withConnectors({
|
|
33
|
+
near: connector({ origin: [0, 0, 0], axis: [0, 1, 0] }),
|
|
34
|
+
far: connector({ origin: [0, L, 0], axis: [0, 1, 0] }),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ── Skeleton geometry (all at x = 0 → the limb lives in the Y/Z plane) ───────
|
|
39
|
+
const HIP = [0, 0, 0];
|
|
40
|
+
const KNEE = [0, 20, -70]; // forward + down from the hip
|
|
41
|
+
const ANKLE = [0, 50, -110]; // forward + down from the knee
|
|
42
|
+
const HIP_REF = [0, -40, 0]; // a forward marker that fixes the hip's zero angle
|
|
43
|
+
|
|
44
|
+
const thighLen = Points.distance(HIP, KNEE);
|
|
45
|
+
const shinLen = Points.distance(KNEE, ANKLE);
|
|
46
|
+
|
|
47
|
+
// Drivers: return the rig and the editor exposes these as solver-backed
|
|
48
|
+
// assembly controls. Dragging them re-solves the link graph, then updates
|
|
49
|
+
// part transforms; the bone meshes stay local and are reused.
|
|
50
|
+
const DEFAULT_HIP_SWING = 18;
|
|
51
|
+
const DEFAULT_KNEE_BEND = 38;
|
|
52
|
+
|
|
53
|
+
const limb = assembly('Oriented Limb')
|
|
54
|
+
.link('hipRef', { at: HIP_REF, fixed: true })
|
|
55
|
+
.link('hip', { at: HIP, fixed: true })
|
|
56
|
+
.link('knee', { at: KNEE })
|
|
57
|
+
.link('ankle', { at: ANKLE })
|
|
58
|
+
.edgeBetweenLinks('hip', 'knee', { name: 'thigh' })
|
|
59
|
+
.edgeBetweenLinks('knee', 'ankle', { name: 'shin' })
|
|
60
|
+
// The control `default` is the live param value, so the editor sliders (and
|
|
61
|
+
// `--param` on the CLI) drive the pose directly.
|
|
62
|
+
.addAngleBetweenLinks('hipRef', 'hip', 'knee', {
|
|
63
|
+
name: 'hipSwing',
|
|
64
|
+
control: { min: -40, max: 60, default: DEFAULT_HIP_SWING },
|
|
65
|
+
})
|
|
66
|
+
.addAngleBetweenLinks('hip', 'knee', 'ankle', {
|
|
67
|
+
name: 'kneeBend',
|
|
68
|
+
control: { min: 0, max: 120, default: DEFAULT_KNEE_BEND },
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// ── Skin: bones span their links via two-connector mates ─────────────────────
|
|
72
|
+
limb.addPart(
|
|
73
|
+
'HipHub',
|
|
74
|
+
sphere(11)
|
|
75
|
+
.color('#2c2f36')
|
|
76
|
+
.material({ metalness: 0.2, roughness: 0.5 })
|
|
77
|
+
.withConnectors({ center: connector({ origin: [0, 0, 0], axis: [1, 0, 0] }) }),
|
|
78
|
+
{ mate: { connector: 'center', toLink: 'hip' } },
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
limb.addPart('Thigh', bone(thighLen, '#d24b4b'), {
|
|
82
|
+
mate: [
|
|
83
|
+
{ connector: 'near', toLink: 'hip' },
|
|
84
|
+
{ connector: 'far', toLink: 'knee' },
|
|
85
|
+
],
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
limb.addPart('Shin', bone(shinLen, '#2f3338'), {
|
|
89
|
+
mate: [
|
|
90
|
+
{ connector: 'near', toLink: 'knee' },
|
|
91
|
+
{ connector: 'far', toLink: 'ankle' },
|
|
92
|
+
],
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Wheel hub at the ankle — single mate, position-only is exactly right here
|
|
96
|
+
// (a spin-symmetric wheel needs no orientation).
|
|
97
|
+
limb.addPart(
|
|
98
|
+
'Wheel',
|
|
99
|
+
cylinder(13, 18)
|
|
100
|
+
.pointAlong([1, 0, 0])
|
|
101
|
+
.translate(-6.5, 0, 0)
|
|
102
|
+
.color('#b23b3b')
|
|
103
|
+
.material({ metalness: 0.1, roughness: 0.6 })
|
|
104
|
+
.withConnectors({ hub: connector({ origin: [0, 0, 0], axis: [1, 0, 0] }) }),
|
|
105
|
+
{ mate: { connector: 'hub', toLink: 'ankle' } },
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// ── Validation: the solved bones actually reach their joints ─────────────────
|
|
109
|
+
const solved = limb.solve({ hipSwing: DEFAULT_HIP_SWING, kneeBend: DEFAULT_KNEE_BEND });
|
|
110
|
+
const knee = solved.getLinkPosition('knee');
|
|
111
|
+
const ankle = solved.getLinkPosition('ankle');
|
|
112
|
+
verify.equal('thigh edge holds its length', Points.distance(solved.getLinkPosition('hip'), knee), thighLen, 0.01);
|
|
113
|
+
verify.equal('shin edge holds its length', Points.distance(knee, ankle), shinLen, 0.01);
|
|
114
|
+
verify.that('the limb stays in the Y/Z plane', () => Math.abs(knee[0]) < 1e-6 && Math.abs(ankle[0]) < 1e-6);
|
|
115
|
+
|
|
116
|
+
return limb;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Connector-frame rig chain: serial articulated parts need full connector frames.
|
|
2
|
+
//
|
|
3
|
+
// Use link() graphs for solved point skeletons and closed loops. Use connect()
|
|
4
|
+
// when real geometry must inherit orientation from a joint frame. Here the upper
|
|
5
|
+
// leg's local +X axis is locked by connector up vectors, so the part points from
|
|
6
|
+
// the hip pivot toward the knee pivot at every solved pose. Return the Assembly
|
|
7
|
+
// itself so editor controls call solve(state) instead of stacking viewport FK.
|
|
8
|
+
|
|
9
|
+
const DEFAULT_POSE = { hip: 22, knee: -42, wheel: 0 };
|
|
10
|
+
|
|
11
|
+
const upperLength = 68;
|
|
12
|
+
const lowerLength = 58;
|
|
13
|
+
const legWidth = 10;
|
|
14
|
+
const legThickness = 8;
|
|
15
|
+
const hubRadius = 9;
|
|
16
|
+
const wheelRadius = 16;
|
|
17
|
+
const wheelThickness = 8;
|
|
18
|
+
|
|
19
|
+
function hingeConnector(origin, axis, up, type = "planar-hinge") {
|
|
20
|
+
return connector(type, { origin, axis, up, kind: "revolute" });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function legSegment(length, colorHex) {
|
|
24
|
+
const body = box(length, legWidth, legThickness).translate(length / 2, 0, -legThickness / 2);
|
|
25
|
+
const nearHub = cylinder(legThickness, hubRadius).translate(0, 0, -legThickness / 2);
|
|
26
|
+
const farHub = cylinder(legThickness, hubRadius).translate(length, 0, -legThickness / 2);
|
|
27
|
+
|
|
28
|
+
return group(
|
|
29
|
+
{ name: "Body", shape: body },
|
|
30
|
+
{ name: "Near hub", shape: nearHub },
|
|
31
|
+
{ name: "Far hub", shape: farHub },
|
|
32
|
+
).color(colorHex);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const hipDrum = group({
|
|
36
|
+
name: "Drum",
|
|
37
|
+
shape: cylinder(18, 14).translate(0, 0, -9).color("#495057"),
|
|
38
|
+
}).withConnectors({
|
|
39
|
+
hip: hingeConnector([0, 0, 0], [0, 0, 1], [1, 0, 0]),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const upperLeg = legSegment(upperLength, "#d55e5e").withConnectors({
|
|
43
|
+
hip: hingeConnector([0, 0, 0], [0, 0, -1], [1, 0, 0]),
|
|
44
|
+
knee: hingeConnector([upperLength, 0, 0], [0, 0, 1], [1, 0, 0]),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const lowerLeg = legSegment(lowerLength, "#3d87c7").withConnectors({
|
|
48
|
+
knee: hingeConnector([0, 0, 0], [0, 0, -1], [1, 0, 0]),
|
|
49
|
+
axle: hingeConnector([lowerLength, 0, 0], [0, 1, 0], [1, 0, 0], "wheel-axle"),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const wheel = group({
|
|
53
|
+
name: "Wheel",
|
|
54
|
+
shape: cylinder(wheelThickness, wheelRadius)
|
|
55
|
+
.pointAlong([0, 1, 0])
|
|
56
|
+
.translate(0, -wheelThickness / 2, 0)
|
|
57
|
+
.color("#212529"),
|
|
58
|
+
}).withConnectors({
|
|
59
|
+
axle: hingeConnector([0, 0, 0], [0, -1, 0], [1, 0, 0], "wheel-axle"),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const rig = assembly("Connector Frame Rig Chain")
|
|
63
|
+
.addPart("Hip Drum", hipDrum)
|
|
64
|
+
.addPart("Upper Leg", upperLeg)
|
|
65
|
+
.addPart("Lower Leg", lowerLeg)
|
|
66
|
+
.addPart("Wheel", wheel)
|
|
67
|
+
.connect("Hip Drum.hip", "Upper Leg.hip", { as: "hip", min: -35, max: 55, default: DEFAULT_POSE.hip })
|
|
68
|
+
.connect("Upper Leg.knee", "Lower Leg.knee", { as: "knee", min: -90, max: 20, default: DEFAULT_POSE.knee })
|
|
69
|
+
.connect("Lower Leg.axle", "Wheel.axle", { as: "wheel", min: -180, max: 180, default: DEFAULT_POSE.wheel });
|
|
70
|
+
|
|
71
|
+
const solved = rig.solve(DEFAULT_POSE);
|
|
72
|
+
|
|
73
|
+
const upper = solved.getTransform("Upper Leg");
|
|
74
|
+
const lower = solved.getTransform("Lower Leg");
|
|
75
|
+
const wheelTx = solved.getTransform("Wheel");
|
|
76
|
+
|
|
77
|
+
const hip = upper.point([0, 0, 0]);
|
|
78
|
+
const kneeFromUpper = upper.point([upperLength, 0, 0]);
|
|
79
|
+
const kneeFromLower = lower.point([0, 0, 0]);
|
|
80
|
+
const axleFromLower = lower.point([lowerLength, 0, 0]);
|
|
81
|
+
const axleFromWheel = wheelTx.point([0, 0, 0]);
|
|
82
|
+
|
|
83
|
+
const upperLocalX = upper.vector([1, 0, 0]);
|
|
84
|
+
const hipToKnee = Points.direction(hip, kneeFromUpper);
|
|
85
|
+
|
|
86
|
+
verify.equal("knee connector origins coincide", Points.distance(kneeFromUpper, kneeFromLower), 0, 0.001);
|
|
87
|
+
verify.equal("wheel axle connector origins coincide", Points.distance(axleFromLower, axleFromWheel), 0, 0.001);
|
|
88
|
+
verify.equal("upper leg local +X points hip to knee", Points.distance(upperLocalX, hipToKnee), 0, 0.001);
|
|
89
|
+
|
|
90
|
+
scene({
|
|
91
|
+
background: { top: "#edf2f7", bottom: "#ffffff" },
|
|
92
|
+
camera: { position: [115, -190, 120], target: [45, 12, 0], fov: 34 },
|
|
93
|
+
environment: { preset: "studio", intensity: 0.45 },
|
|
94
|
+
lights: [
|
|
95
|
+
{ type: "ambient", color: "#e8edf5", intensity: 0.25 },
|
|
96
|
+
{ type: "directional", position: [120, -140, 180], target: [35, 5, 0], color: "#fff5e6", intensity: 1.6, castShadow: true },
|
|
97
|
+
{ type: "directional", position: [-80, 100, 120], target: [40, 15, 0], color: "#c9ddff", intensity: 0.7 },
|
|
98
|
+
],
|
|
99
|
+
ground: { visible: true, color: "#f6f8fa", height: -wheelRadius, receiveShadow: true },
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return rig;
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
if (getActiveBackend() === 'manifold') setActiveBackend('truck');
|
|
2
|
-
|
|
3
1
|
const span = param('Span', 128);
|
|
4
2
|
const depth = param('Depth', 88);
|
|
5
3
|
const crown = param('Crown', 26);
|
|
@@ -41,7 +39,7 @@ const canopySheet = Surface.Nurbs(
|
|
|
41
39
|
const canopySolid = canopySheet.thicken(wall).translate(-span * 0.82, 0, 0).color('#9fd3ff');
|
|
42
40
|
const canopyGhost = canopySheet.translate(-span * 0.82, 0, wall * 2.4).color('#d9f0ff');
|
|
43
41
|
|
|
44
|
-
const archFront =
|
|
42
|
+
const archFront = Curve.Nurbs(
|
|
45
43
|
[
|
|
46
44
|
[-halfSpan * 0.55, -halfDepth * 0.58, 2],
|
|
47
45
|
[-halfSpan * 0.18, -halfDepth * 0.58, crown * 0.95],
|
|
@@ -50,7 +48,7 @@ const archFront = nurbs3d(
|
|
|
50
48
|
],
|
|
51
49
|
{ degree: 3 },
|
|
52
50
|
);
|
|
53
|
-
const archBack =
|
|
51
|
+
const archBack = Curve.Nurbs(
|
|
54
52
|
[
|
|
55
53
|
[-halfSpan * 0.48, halfDepth * 0.52, 0],
|
|
56
54
|
[-halfSpan * 0.14, halfDepth * 0.52, crown * 0.68],
|
|
@@ -65,7 +63,7 @@ const ruledRibbon = Surface.Ruled(archFront, archBack)
|
|
|
65
63
|
|
|
66
64
|
const boundaryPatch = Surface.Patch(
|
|
67
65
|
{
|
|
68
|
-
bottom:
|
|
66
|
+
bottom: Curve.Nurbs(
|
|
69
67
|
[
|
|
70
68
|
[-halfSpan * 0.48, -halfDepth * 0.42, -2],
|
|
71
69
|
[-halfSpan * 0.16, -halfDepth * 0.46, crown * 0.35],
|
|
@@ -74,7 +72,7 @@ const boundaryPatch = Surface.Patch(
|
|
|
74
72
|
],
|
|
75
73
|
{ degree: 3 },
|
|
76
74
|
),
|
|
77
|
-
top:
|
|
75
|
+
top: Curve.Nurbs(
|
|
78
76
|
[
|
|
79
77
|
[-halfSpan * 0.48, halfDepth * 0.4, 1],
|
|
80
78
|
[-halfSpan * 0.16, halfDepth * 0.43, crown * 0.62],
|
|
@@ -83,7 +81,7 @@ const boundaryPatch = Surface.Patch(
|
|
|
83
81
|
],
|
|
84
82
|
{ degree: 3 },
|
|
85
83
|
),
|
|
86
|
-
left:
|
|
84
|
+
left: Curve.Nurbs(
|
|
87
85
|
[
|
|
88
86
|
[-halfSpan * 0.48, -halfDepth * 0.42, -2],
|
|
89
87
|
[-halfSpan * 0.58, -halfDepth * 0.12, crown * 0.22],
|
|
@@ -92,7 +90,7 @@ const boundaryPatch = Surface.Patch(
|
|
|
92
90
|
],
|
|
93
91
|
{ degree: 3 },
|
|
94
92
|
),
|
|
95
|
-
right:
|
|
93
|
+
right: Curve.Nurbs(
|
|
96
94
|
[
|
|
97
95
|
[halfSpan * 0.48, -halfDepth * 0.42, -2],
|
|
98
96
|
[halfSpan * 0.58, -halfDepth * 0.12, crown * 0.22],
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
// Helix API basics.
|
|
1
|
+
// Curve.Helix API basics.
|
|
2
2
|
//
|
|
3
|
-
// Helix.path() is the reusable centerline primitive.
|
|
4
|
-
// Helix.coil() turns the same helix definition into a solid helix-oriented coil.
|
|
3
|
+
// Curve.Helix.path() is the reusable centerline primitive.
|
|
4
|
+
// Curve.Helix.coil() turns the same helix definition into a solid helix-oriented coil.
|
|
5
5
|
|
|
6
6
|
const radius = Param.number("Radius", 18, { min: 8, max: 32, step: 1, unit: "mm" });
|
|
7
7
|
const pitch = Param.number("Pitch", 6, { min: 2, max: 12, step: 0.5, unit: "mm" });
|
|
8
8
|
const turns = Param.number("Turns", 4, { min: 1, max: 7, step: 0.25 });
|
|
9
9
|
const wireRadius = Param.number("Wire Radius", 1, { min: 0.25, max: 2.5, step: 0.25, unit: "mm" });
|
|
10
10
|
|
|
11
|
-
const guide = Helix.path({
|
|
11
|
+
const guide = Curve.Helix.path({
|
|
12
12
|
radius,
|
|
13
13
|
pitch,
|
|
14
14
|
turns,
|
|
@@ -16,7 +16,7 @@ const guide = Helix.path({
|
|
|
16
16
|
samplesPerTurn: 48,
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
const circularWireCoil = Helix.coil({
|
|
19
|
+
const circularWireCoil = Curve.Helix.coil({
|
|
20
20
|
radius,
|
|
21
21
|
pitch,
|
|
22
22
|
turns,
|
|
@@ -26,7 +26,7 @@ const circularWireCoil = Helix.coil({
|
|
|
26
26
|
}).translate(-70, 0, 0);
|
|
27
27
|
|
|
28
28
|
const rectangularProfile = rect(wireRadius * 2.4, wireRadius * 1.1);
|
|
29
|
-
const rectangularWireCoil = Helix.coil(rectangularProfile, {
|
|
29
|
+
const rectangularWireCoil = Curve.Helix.coil(rectangularProfile, {
|
|
30
30
|
radius,
|
|
31
31
|
height: guide.height,
|
|
32
32
|
turns,
|
|
@@ -49,8 +49,8 @@ const sampledPathMarkers = union(
|
|
|
49
49
|
...guide.sample(9).map(([x, y, z]) => sphere(wireRadius * 0.5).translate(x + 120, y, z)),
|
|
50
50
|
);
|
|
51
51
|
|
|
52
|
-
console.info(`Helix height: ${guide.height.toFixed(2)}`);
|
|
53
|
-
console.info(`Helix centerline length: ${guide.length().toFixed(2)}`);
|
|
52
|
+
console.info(`Curve.Helix height: ${guide.height.toFixed(2)}`);
|
|
53
|
+
console.info(`Curve.Helix centerline length: ${guide.length().toFixed(2)}`);
|
|
54
54
|
|
|
55
55
|
return [
|
|
56
56
|
{ name: "Circular wire coil", shape: circularWireCoil },
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Selected Lean Foundations Examples
|
|
2
|
+
|
|
3
|
+
This pack contains only the currently selected lean API buckets: Curve / Route and route endpoint canonicalization. Surface, standalone Frame, FaceRef, and topology-selector expansion examples were intentionally left out of this split.
|
|
4
|
+
|
|
5
|
+
Run these with `node dist-cli/forgecad.js run <file>`:
|
|
6
|
+
|
|
7
|
+
- `curve-blend-exact.forge.js` - `Curve.Blend` and `Curve.BlendG2` as exact reference rails that also feed `sweep`.
|
|
8
|
+
- `curve-fit-interpolation.forge.js` - `Curve.Fit` interpolation with residual validation.
|
|
9
|
+
- `curve-helix-canonicalization.forge.js` - `Curve.Helix` path and coil canonicalization.
|
|
10
|
+
- `curve-route-canonicalization.forge.js` - `Curve.Route.fromPolyline()` segment and port preservation.
|
|
11
|
+
- `curve-trim-reverse.forge.js` - exact trim and reverse operations.
|
|
12
|
+
- `exact-curve-arc.forge.js` - exact `Curve.Arc` construction and sweep use.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const g1Rail = Curve.Blend(
|
|
2
|
+
{ point: [0, 0, 0], tangent: [1, 0, 0], weight: 0.7 },
|
|
3
|
+
{ point: [48, 22, 10], tangent: [0, 1, 0], weight: 0.65 },
|
|
4
|
+
);
|
|
5
|
+
|
|
6
|
+
const g2Rail = Curve.BlendG2(
|
|
7
|
+
{ point: [0, 18, 0], tangent: [1, 0, 0], curvature: [0, 0.01, 0], weight: 0.55 },
|
|
8
|
+
{ point: [48, 42, 10], tangent: [0, 1, 0], curvature: [-0.01, 0, 0], weight: 0.55 },
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
const g1Tube = sweep(circle2d(1.6), g1Rail);
|
|
12
|
+
const g2Tube = sweep(circle2d(1.2), g2Rail).color("#5a8f7b");
|
|
13
|
+
|
|
14
|
+
const close3d = (a, b, tolerance = 1e-6) => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]) <= tolerance;
|
|
15
|
+
|
|
16
|
+
verify.equal("Curve.Blend returns a cubic NURBS", g1Rail.degree, 3);
|
|
17
|
+
verify.equal("Curve.BlendG2 returns a quintic NURBS", g2Rail.degree, 5);
|
|
18
|
+
verify.that("Curve.Blend hits directed endpoints", () => close3d(g1Rail.pointAt(0), [0, 0, 0]) && close3d(g1Rail.pointAt(1), [48, 22, 10]));
|
|
19
|
+
verify.that("Curve.BlendG2 hits directed endpoints", () => close3d(g2Rail.pointAt(0), [0, 18, 0]) && close3d(g2Rail.pointAt(1), [48, 42, 10]));
|
|
20
|
+
verify.greaterThan("Blend rails are usable as sweep paths", g1Tube.boundingBox().max[0], 45);
|
|
21
|
+
|
|
22
|
+
return group(g1Tube, g2Tube);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const fittedRail = Curve.Fit(
|
|
2
|
+
[
|
|
3
|
+
[0, 0, 0],
|
|
4
|
+
[16, 8, 10],
|
|
5
|
+
[36, -6, 18],
|
|
6
|
+
[58, 10, 12],
|
|
7
|
+
[78, 0, 0],
|
|
8
|
+
],
|
|
9
|
+
{ degree: 3, tolerance: 0.001 },
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
const tube = sweep(circle2d(2), fittedRail);
|
|
13
|
+
|
|
14
|
+
verify.equal('Curve.Fit returns a cubic NURBS for this rail', fittedRail.degree, 3);
|
|
15
|
+
verify.equal('Curve.Fit keeps one control point per interpolated point', fittedRail.controlPoints.length, 5);
|
|
16
|
+
verify.greaterThan('Curve.Fit rail has positive length', fittedRail.length(), 0);
|
|
17
|
+
|
|
18
|
+
return tube;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const guide = Curve.Helix.path({
|
|
2
|
+
radius: 14,
|
|
3
|
+
pitch: 5,
|
|
4
|
+
turns: 3,
|
|
5
|
+
samplesPerTurn: 30,
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const roundCoil = Curve.Helix.coil({
|
|
9
|
+
radius: 14,
|
|
10
|
+
pitch: 5,
|
|
11
|
+
turns: 3,
|
|
12
|
+
wireRadius: 0.8,
|
|
13
|
+
profileSegments: 16,
|
|
14
|
+
divisionsPerTurn: 100,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const rectangularCoil = Curve.Helix.coil(rect(1.2, 0.8), {
|
|
18
|
+
radius: 20,
|
|
19
|
+
height: 15,
|
|
20
|
+
turns: 3,
|
|
21
|
+
divisionsPerTurn: 18,
|
|
22
|
+
}).translate(50, 0, 0);
|
|
23
|
+
|
|
24
|
+
verify.equal('Curve.Helix guide derives height from pitch and turns', guide.height, 15);
|
|
25
|
+
verify.greaterThan('Curve.Helix guide has positive centerline length', guide.length(), 0);
|
|
26
|
+
|
|
27
|
+
return union(roundCoil, rectangularCoil);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const route = Curve.Route.fromPolyline(
|
|
2
|
+
[
|
|
3
|
+
[0, 0, 0],
|
|
4
|
+
[0, 0, 48],
|
|
5
|
+
[36, 0, 48],
|
|
6
|
+
[36, 24, 72],
|
|
7
|
+
],
|
|
8
|
+
{ cornerRadius: 10, startPort: 'inlet', endPort: 'outlet' },
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
const tube = sweep(difference2d(circle2d(5), circle2d(3.5)), route);
|
|
12
|
+
verify.equal('Curve.Route keeps line-arc-line-arc-line route intent', route.segments.length, 5);
|
|
13
|
+
verify.equal('Curve.Route exposes named ports', Object.keys(route.ports).length, 2);
|
|
14
|
+
verify.greaterThan('Curve.Route remains usable as a sweep path', tube.boundingBox().max[2], 70);
|
|
15
|
+
|
|
16
|
+
return route;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const rail = Curve.Fit(
|
|
2
|
+
[
|
|
3
|
+
[0, 0, 0],
|
|
4
|
+
[24, 10, 8],
|
|
5
|
+
[54, -8, 12],
|
|
6
|
+
[84, 0, 0],
|
|
7
|
+
],
|
|
8
|
+
{ degree: 3 },
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
const middle = Curve.Trim(rail, 0.2, 0.8);
|
|
12
|
+
const reversed = Curve.Reverse(middle);
|
|
13
|
+
|
|
14
|
+
const middleTube = sweep(circle2d(1.2), middle).color("#5f8cc0");
|
|
15
|
+
const reversedTube = sweep(circle2d(0.8), reversed).translate(0, 18, 0).color("#c06f5f");
|
|
16
|
+
|
|
17
|
+
const close3d = (a, b, tolerance = 1e-6) => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]) <= tolerance;
|
|
18
|
+
|
|
19
|
+
verify.that("Curve.Trim starts at the source subdomain", () => close3d(middle.pointAt(0), rail.pointAt(0.2)));
|
|
20
|
+
verify.that("Curve.Trim ends at the source subdomain", () => close3d(middle.pointAt(1), rail.pointAt(0.8)));
|
|
21
|
+
verify.that("Curve.Reverse preserves geometry with opposite parameter direction", () => close3d(reversed.pointAt(0), middle.pointAt(1)));
|
|
22
|
+
verify.greaterThan("Trimmed curves feed exact sweep rails", middleTube.boundingBox().max[0], 60);
|
|
23
|
+
|
|
24
|
+
return group(middleTube, reversedTube);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const lower = Curve.Arc({
|
|
2
|
+
start: [36, 0, 0],
|
|
3
|
+
end: [0, 36, 0],
|
|
4
|
+
tangent: [0, 1, 0],
|
|
5
|
+
});
|
|
6
|
+
const upper = Curve.Arc({
|
|
7
|
+
start: [36, 0, 10],
|
|
8
|
+
end: [0, 36, 10],
|
|
9
|
+
tangent: [0, 1, 0],
|
|
10
|
+
});
|
|
11
|
+
const half = Curve.Arc({
|
|
12
|
+
start: [12, 0, 18],
|
|
13
|
+
end: [-12, 0, 18],
|
|
14
|
+
tangent: [0, 1, 0],
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const tube = sweep(circle2d(0.8), lower);
|
|
18
|
+
const upperTube = sweep(circle2d(0.5), upper).color("#5a8f7b");
|
|
19
|
+
|
|
20
|
+
const close3d = (a, b, tolerance = 1e-6) => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]) <= tolerance;
|
|
21
|
+
|
|
22
|
+
verify.that("Curve.Arc hits start, midpoint, and end exactly", () =>
|
|
23
|
+
close3d(lower.pointAt(0), [36, 0, 0]) &&
|
|
24
|
+
close3d(lower.pointAt(0.5), [36 * Math.SQRT1_2, 36 * Math.SQRT1_2, 0]) &&
|
|
25
|
+
close3d(lower.pointAt(1), [0, 36, 0]));
|
|
26
|
+
|
|
27
|
+
verify.that("Curve.Arc emits stable rational spans for half circles", () =>
|
|
28
|
+
half.controlPoints.length === 5 && close3d(half.pointAt(0.5), [0, 12, 18]));
|
|
29
|
+
|
|
30
|
+
verify.that("Curve.Arc feeds sweep as an exact NURBS rail", () =>
|
|
31
|
+
tube.geometryInfo().backend !== "unknown" && tube.boundingBox().max[0] > 30 && tube.boundingBox().max[1] > 30);
|
|
32
|
+
|
|
33
|
+
verify.that("Multiple Curve.Arc rails remain reusable sweep inputs", () =>
|
|
34
|
+
upperTube.boundingBox().max[2] > 10 && upperTube.boundingBox().max[0] > 30);
|
|
35
|
+
|
|
36
|
+
return group(tube, upperTube);
|
|
@@ -27,17 +27,14 @@ const body = plate.subtract(cutout);
|
|
|
27
27
|
const bodyEdges = selectEdges(body);
|
|
28
28
|
const raw = body.color('#7b8490');
|
|
29
29
|
const chamfered = chamfer(body, 0.75, bodyEdges).color('#d3a33a');
|
|
30
|
-
|
|
30
|
+
// Native Truck fillet now produces analytic rolling-ball geometry (cylinders,
|
|
31
|
+
// spheres, and toroidal corner blends) that exports to STEP canonically
|
|
32
|
+
// identical to OpenCascade. See:
|
|
33
|
+
// forgecad export step examples/api/mixed-edge-finishes-proof.forge.js --backend truck
|
|
34
|
+
const filleted = fillet(body, 0.75, bodyEdges).color('#4e8fd6');
|
|
35
|
+
|
|
36
|
+
return [
|
|
31
37
|
{ name: 'Raw', shape: raw.translate(-80, 0, 0) },
|
|
32
38
|
{ name: 'Chamfered', shape: chamfered.translate(30, 0, 0) },
|
|
39
|
+
{ name: 'Filleted', shape: filleted.translate(140, 0, 0) },
|
|
33
40
|
];
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
const filleted = fillet(body, 0.75, bodyEdges).color('#4e8fd6');
|
|
37
|
-
filleted.volume();
|
|
38
|
-
shapes.push({ name: 'Filleted', shape: filleted.translate(140, 0, 0) });
|
|
39
|
-
} catch (error) {
|
|
40
|
-
console.log(`Filleted: unsupported in native Truck (${error instanceof Error ? error.message : String(error)})`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return shapes;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Curve.Route elbow - demonstrates virtual 3D route corners, named route ports,
|
|
2
|
+
// and sweeping an annular profile without hand-computing tangent points.
|
|
3
|
+
|
|
4
|
+
const route = Curve.Route.fromPolyline(
|
|
5
|
+
[
|
|
6
|
+
[0, 0, 0],
|
|
7
|
+
[0, 0, 203.2],
|
|
8
|
+
[203.2, 0, 203.2],
|
|
9
|
+
],
|
|
10
|
+
{ cornerRadius: 114.3, startPort: 'inlet', endPort: 'outlet' },
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
const outerRadius = 44.45;
|
|
14
|
+
const innerRadius = 38.96;
|
|
15
|
+
const flangeRadius = 89.0;
|
|
16
|
+
const boltCircleRadius = 72.3;
|
|
17
|
+
const boltRadius = 5.3;
|
|
18
|
+
const flangeThickness = 17.8;
|
|
19
|
+
const neckLength = 26.645;
|
|
20
|
+
const neckRadius = 55.6;
|
|
21
|
+
|
|
22
|
+
const tubeProfile = difference2d(circle2d(outerRadius, 80), circle2d(innerRadius, 80));
|
|
23
|
+
|
|
24
|
+
const pipe = sweep(tubeProfile, route, { samples: 72 });
|
|
25
|
+
|
|
26
|
+
return pipe;
|
|
27
|
+
|
|
28
|
+
function placeAtPort(shape, port, offset) {
|
|
29
|
+
return shape.pointAlong(port.axis).translate(
|
|
30
|
+
port.origin[0] + port.axis[0] * offset,
|
|
31
|
+
port.origin[1] + port.axis[1] * offset,
|
|
32
|
+
port.origin[2] + port.axis[2] * offset,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function flangePlate() {
|
|
37
|
+
const bore = cylinder(flangeThickness + 2, innerRadius, undefined, 80).translate(0, 0, -1);
|
|
38
|
+
const firstBolt = cylinder(flangeThickness + 2, boltRadius, undefined, 24).translate(boltCircleRadius, 0, -1);
|
|
39
|
+
const bolts = circularPattern(firstBolt, 5);
|
|
40
|
+
return cylinder(flangeThickness, flangeRadius, undefined, 96).subtract(bore, bolts);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function neck() {
|
|
44
|
+
const bore = cylinder(neckLength + 2, innerRadius, undefined, 80).translate(0, 0, -1);
|
|
45
|
+
return cylinder(neckLength, neckRadius, undefined, 80).subtract(bore);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const inlet = route.port('inlet');
|
|
49
|
+
const outlet = route.port('outlet');
|
|
50
|
+
const body = union(
|
|
51
|
+
pipe,
|
|
52
|
+
placeAtPort(neck(), inlet, -4.445),
|
|
53
|
+
placeAtPort(flangePlate(), inlet, 20.0),
|
|
54
|
+
placeAtPort(neck(), outlet, -4.445),
|
|
55
|
+
placeAtPort(flangePlate(), outlet, 20.0),
|
|
56
|
+
).color('#b9c0c8');
|
|
57
|
+
|
|
58
|
+
const bbox = body.boundingBox();
|
|
59
|
+
const bboxSize = [bbox.max[0] - bbox.min[0], bbox.max[1] - bbox.min[1], bbox.max[2] - bbox.min[2]];
|
|
60
|
+
|
|
61
|
+
verify.equal('Curve.Route elbow keeps line-arc-line route intent', route.segments.length, 3);
|
|
62
|
+
verify.equal('Curve.Route elbow exposes inlet and outlet ports', Object.keys(route.ports).length, 2);
|
|
63
|
+
verify.boundingBoxSize('Curve.Route elbow preserves BenchCAD-scale envelope', body, [337, 178, 330], 18);
|
|
64
|
+
verify.that(
|
|
65
|
+
'Curve.Route elbow bbox stays in expected quadrants',
|
|
66
|
+
() => bbox.min[0] < -40 && bbox.max[0] > 235 && bbox.min[2] < -35 && bbox.max[2] > 245 && bboxSize[1] > 175,
|
|
67
|
+
`bbox was ${JSON.stringify(bbox)}`,
|
|
68
|
+
);
|
|
69
|
+
verify.physicalComponentCount('Curve.Route elbow is one connected component', 1);
|
|
70
|
+
|
|
71
|
+
return body;
|