forgecad 0.7.0 → 0.8.0

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 (158) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/{AdminPage-DAu1C1ST.js → AdminPage-D4bocK4E.js} +1 -1
  3. package/dist/assets/{DocsPage-Gc_BCdqC.js → DocsPage-D3A_g8V3.js} +85 -45
  4. package/dist/assets/{EditorApp-DG1-oUSV.css → EditorApp-BWYUSpUN.css} +133 -51
  5. package/dist/assets/EditorApp-Cihhqcsq.js +11692 -0
  6. package/dist/assets/{EmbedViewer-CEO8XbV8.js → EmbedViewer-kWjKaC_t.js} +1 -1
  7. package/dist/assets/LandingPageProofDriven-Bg2IUc3l.css +856 -0
  8. package/dist/assets/LandingPageProofDriven-DXkKlyhI.js +601 -0
  9. package/dist/assets/{PricingPage-BSrxu6d7.js → PricingPage-BsU5vzEx.js} +1 -1
  10. package/dist/assets/{SettingsPage-FUCSIRq6.js → SettingsPage-PqvpAKIs.js} +1 -1
  11. package/dist/assets/{evalWorker-KoR0SNKq.js → evalWorker-C-hzNUMy.js} +2218 -286
  12. package/dist/assets/{index-wTEK39at.js → index-Pz321YAt.js} +7416 -1481
  13. package/dist/assets/{index-CyVd1D4D.css → index-ay13WNfa.css} +501 -2
  14. package/dist/assets/{manifold-B1sGWdYk.js → manifold-BcbjWLIo.js} +3 -3
  15. package/dist/assets/{manifold-D7o0N50J.js → manifold-DBckbFgx.js} +1 -1
  16. package/dist/assets/{manifold-G5sBaXzi.js → manifold-O2AAGXyj.js} +1 -1
  17. package/dist/assets/{reportWorker-DYcRHhv9.js → reportWorker-Dxr-5A7w.js} +2003 -259
  18. package/dist/docs/index.html +2 -2
  19. package/dist/docs-raw/CLI.md +488 -0
  20. package/dist/docs-raw/generated/assembly.md +19 -11
  21. package/dist/docs-raw/generated/concepts.md +1023 -360
  22. package/dist/docs-raw/generated/core.md +1165 -264
  23. package/dist/docs-raw/generated/curves.md +168 -1
  24. package/dist/docs-raw/generated/lib.md +10 -5
  25. package/dist/docs-raw/generated/output.md +1 -1
  26. package/dist/docs-raw/generated/sdf.md +208 -0
  27. package/dist/docs-raw/generated/sketch.md +1281 -329
  28. package/dist/docs-raw/generated/viewport.md +29 -2
  29. package/dist/index.html +2 -2
  30. package/dist/landing/proof-ams-adapter.png +0 -0
  31. package/dist/landing/proof-bolt-and-nut.png +0 -0
  32. package/dist/landing/proof-fillet-enclosure.png +0 -0
  33. package/dist/landing/proof-glasses.png +0 -0
  34. package/dist/landing/proof-gyroid.png +0 -0
  35. package/dist/sitemap.xml +6 -6
  36. package/dist-cli/forgecad.js +3148 -555
  37. package/dist-cli/forgecad.js.map +1 -1
  38. package/dist-cli/{solver-FV7TJZGI.js → solver-46FFSK2U.js} +1 -3
  39. package/dist-cli/{solver-FV7TJZGI.js.map → solver-46FFSK2U.js.map} +1 -1
  40. package/dist-skill/CONTEXT.md +3700 -1153
  41. package/dist-skill/SKILL-dev.md +15 -17
  42. package/dist-skill/SKILL.md +14 -9
  43. package/dist-skill/docs/API/core/concepts.md +28 -1
  44. package/dist-skill/docs/CLI.md +488 -0
  45. package/dist-skill/docs/generated/assembly.md +19 -11
  46. package/dist-skill/docs/generated/core.md +1165 -264
  47. package/dist-skill/docs/generated/curves.md +168 -1
  48. package/dist-skill/docs/generated/lib.md +10 -5
  49. package/dist-skill/docs/generated/output.md +1 -1
  50. package/dist-skill/docs/generated/sdf.md +208 -0
  51. package/dist-skill/docs/generated/sketch.md +1281 -329
  52. package/dist-skill/docs/generated/viewport.md +29 -2
  53. package/dist-skill/docs/guides/joint-design.md +78 -0
  54. package/dist-skill/docs-dev/API/core/concepts.md +28 -1
  55. package/dist-skill/docs-dev/CLI.md +488 -0
  56. package/dist-skill/docs-dev/coding.md +1 -1
  57. package/dist-skill/docs-dev/component-model.md +164 -0
  58. package/dist-skill/docs-dev/generated/assembly.md +19 -11
  59. package/dist-skill/docs-dev/generated/core.md +1165 -264
  60. package/dist-skill/docs-dev/generated/curves.md +168 -1
  61. package/dist-skill/docs-dev/generated/lib.md +10 -5
  62. package/dist-skill/docs-dev/generated/output.md +1 -1
  63. package/dist-skill/docs-dev/generated/sdf.md +208 -0
  64. package/dist-skill/docs-dev/generated/sketch.md +1281 -329
  65. package/dist-skill/docs-dev/generated/viewport.md +29 -2
  66. package/dist-skill/docs-dev/guides/joint-design.md +78 -0
  67. package/examples/api/attachTo-basics.forge.js +3 -3
  68. package/examples/api/bill-of-materials.forge.js +9 -9
  69. package/examples/api/bolt-pattern.forge.js +5 -5
  70. package/examples/api/boolean-operations.forge.js +2 -2
  71. package/examples/api/bounding-box-visualizer.forge.js +1 -1
  72. package/examples/api/clone-duplicate.forge.js +1 -1
  73. package/examples/api/connector-assembly.forge.js +4 -2
  74. package/examples/api/connector-basics.forge.js +5 -5
  75. package/examples/api/constrained-sketch-mechanical.forge.js +4 -4
  76. package/examples/api/elbow-test.forge.js +3 -3
  77. package/examples/api/extrude-options.forge.js +4 -4
  78. package/examples/api/fillet-showcase.forge.js +1 -1
  79. package/examples/api/gears-tier1.forge.js +5 -5
  80. package/examples/api/group-test.forge.js +2 -2
  81. package/examples/api/mesh-import-slats.forge.js +3 -3
  82. package/examples/api/patterns.forge.js +3 -3
  83. package/examples/api/pointAlong-orientation.forge.js +2 -2
  84. package/examples/api/profile-2020-b-slot6.forge.js +4 -4
  85. package/examples/api/sketch-rounding-strategies.forge.js +1 -1
  86. package/examples/api/smooth-curve-connections.forge.js +1 -1
  87. package/examples/api/transition-curves.forge.js +3 -3
  88. package/examples/constraints/01-fully-constrained-rect.forge.js +2 -2
  89. package/examples/constraints/02-underconstrained-triangle.forge.js +1 -1
  90. package/examples/constraints/03-redundant-constraints.forge.js +2 -2
  91. package/examples/constraints/05-parallel-with-linedistance.forge.js +2 -2
  92. package/examples/constraints/06-complex-spectrogram.forge.js +1 -1
  93. package/examples/constraints/07-perpendicular-chain.forge.js +4 -4
  94. package/examples/constraints/08-symmetric-bracket.forge.js +4 -4
  95. package/examples/constraints/09-stress-spiral.forge.js +1 -1
  96. package/examples/constraints/10-stress-honeycomb.forge.js +1 -1
  97. package/examples/constraints/11-surface-grid.forge.js +2 -2
  98. package/examples/constraints/12-surface-nested.forge.js +4 -4
  99. package/examples/constraints/13-surface-complex.forge.js +1 -1
  100. package/examples/exact-arc-housing.forge.js +12 -0
  101. package/examples/furniture/adjustable-table.forge.js +13 -13
  102. package/examples/furniture/bathroom.forge.js +15 -15
  103. package/examples/furniture/chair.forge.js +12 -12
  104. package/examples/furniture/picture-frame.forge.js +6 -6
  105. package/examples/furniture/shoe-rack-doors.forge.js +8 -8
  106. package/examples/furniture/shoe-rack.forge.js +7 -7
  107. package/examples/furniture/table-lamp.forge.js +8 -8
  108. package/examples/gcode/lissajous-vase.forge.js +4 -4
  109. package/examples/gcode/math-surface.forge.js +3 -3
  110. package/examples/gcode/parametric-vase.forge.js +4 -4
  111. package/examples/gcode/spiral-tower.forge.js +4 -4
  112. package/examples/generative/crystal-growth.forge.js +7 -7
  113. package/examples/generative/frost-spires.forge.js +6 -6
  114. package/examples/generative/golden-spiral-tower.forge.js +8 -8
  115. package/examples/generative/molten-forge.forge.js +6 -6
  116. package/examples/generative/neon-coral.forge.js +7 -7
  117. package/examples/mechanical/3d-printer.forge.js +9 -9
  118. package/examples/mechanical/5-finger-robot-hand.forge.js +4 -4
  119. package/examples/mechanical/airplane-propeller.forge.js +7 -7
  120. package/examples/mechanical/bolt-and-nut.forge.js +10 -10
  121. package/examples/mechanical/door-with-hinges.forge.js +7 -7
  122. package/examples/mechanical/fillet-enclosure.forge.js +14 -10
  123. package/examples/mechanical/headphone-hanger-v2.forge.js +9 -9
  124. package/examples/mechanical/robot_hand.forge.js +10 -10
  125. package/examples/mechanical/robot_hand_2.forge.js +17 -17
  126. package/examples/nurbs-surface.forge.js +8 -0
  127. package/examples/nurbs-tube.forge.js +7 -0
  128. package/examples/products/bottle.forge.js +7 -7
  129. package/examples/products/chess-set.forge.js +6 -6
  130. package/examples/products/classical-piano.forge.js +9 -9
  131. package/examples/products/clock.forge.js +21 -21
  132. package/examples/products/cup.forge.js +5 -5
  133. package/examples/products/iphone.forge.js +12 -12
  134. package/examples/products/laptop.forge.js +9 -9
  135. package/examples/products/laser-cut-box.forge.js +6 -6
  136. package/examples/products/laser-cut-tray.forge.js +6 -6
  137. package/examples/products/liquid-soap-dispenser.forge.js +5 -5
  138. package/examples/products/origami-fish.forge.js +6 -6
  139. package/examples/products/spiderman-cake.forge.js +2 -2
  140. package/examples/shelf/container.forge.js +5 -5
  141. package/examples/shelf/shelf-unit.forge.js +6 -6
  142. package/examples/toolbox/bolted-joint.forge.js +5 -5
  143. package/package.json +3 -1
  144. package/dist/assets/EditorApp-D9bJvtf7.js +0 -11338
  145. package/dist/assets/LandingPage-CdCuEOdC.js +0 -451
  146. package/dist-cli/chunk-PZ5AY32C.js +0 -10
  147. package/dist-cli/chunk-PZ5AY32C.js.map +0 -1
  148. package/dist-skill/docs/CLI/export.md +0 -91
  149. package/dist-skill/docs/CLI/projects.md +0 -107
  150. package/dist-skill/docs/CLI/studio_publishing.md +0 -52
  151. package/dist-skill/docs/CLI/validation.md +0 -66
  152. package/dist-skill/docs-dev/API/core/sdf-advanced.md +0 -92
  153. package/dist-skill/docs-dev/API/core/sdf-primitives.md +0 -58
  154. package/dist-skill/docs-dev/API/core/sdf-workflow.md +0 -42
  155. package/dist-skill/docs-dev/CLI/export.md +0 -91
  156. package/dist-skill/docs-dev/CLI/projects.md +0 -107
  157. package/dist-skill/docs-dev/CLI/studio_publishing.md +0 -52
  158. package/dist-skill/docs-dev/CLI/validation.md +0 -66
@@ -0,0 +1,164 @@
1
+ ---
2
+ skill-group: dev-conventions
3
+ skill-order: 4
4
+ ---
5
+
6
+ # The Component Model
7
+
8
+ ## Parts Are Components. Assemblies Are Composition.
9
+
10
+ ForgeCAD's multi-part design model follows one principle:
11
+
12
+ > **A part is a function from props to shape + connectors. It knows nothing about where it sits in the world. The assembly decides.**
13
+
14
+ This is how React changed UI: components render content, the layout system positions them. Components don't know their screen coordinates. Before React, jQuery code positioned elements in page-space. After React, components are local and composable.
15
+
16
+ ForgeCAD applies the same principle to mechanical design. Before: parts compute their assembly-space coordinates (translate to the right X, Y, Z, using a shared-dims file). After: parts build at origin in local space, declare connectors, and the assembly uses `connect()` to position everything.
17
+
18
+ ## The Three Rules
19
+
20
+ ### Rule 1: Parts Build at Origin
21
+
22
+ A part is built in its own local coordinate system. Origin at `[0, 0, 0]`. No knowledge of the assembly. No imports of sibling part files. No global coordinate offsets.
23
+
24
+ ```js
25
+ // WRONG — part positions itself in assembly space
26
+ const rackPlaced = rack.translate(pinionPitchR, 0, layout.pinionZ);
27
+
28
+ // RIGHT — part builds at origin, connector declares the interface
29
+ const rack = lib.rackGear({ ... }).rotateZ(90);
30
+ // pitch line at X=0, teeth face -X, local coordinates only
31
+ return rack.withConnectors({
32
+ teeth: connector("rack-slide", { origin: [0, 0, faceWidth/2], axis: [0, 1, 0] }),
33
+ });
34
+ ```
35
+
36
+ ### Rule 2: Connectors Are the Interface
37
+
38
+ Every part that participates in an assembly declares connectors. A connector says: "here's where I connect, and here's what direction I face." It's the part's public interface — the only thing the assembly needs to know.
39
+
40
+ ```js
41
+ return mount.withConnectors({
42
+ flange: connector("bolt-face", {
43
+ origin: [0, 0, 0], // where the mating surface is
44
+ axis: [0, 0, 1], // direction it faces (outward from part)
45
+ }),
46
+ });
47
+ ```
48
+
49
+ Connectors meet **face-to-face**. Both axes point outward from their respective parts. The system brings them together from opposite sides — like plugging a USB cable into a port.
50
+
51
+ ### Rule 3: The Assembly Is Pure Composition
52
+
53
+ The assembly file does three things: instantiate parts with props, connect them, and define motion. Zero coordinate math. Zero translate calls. Zero shared-dims imports.
54
+
55
+ ```js
56
+ assembly("Gripper")
57
+ .addPart("Base", base)
58
+ .addPart("Mount", mount)
59
+ .connect("Base.mount_face", "Mount.flange", { as: "mount-fix" })
60
+ .addPart("Pinion", pinion)
61
+ .connect("Base.pinion_seat", "Pinion.bore", { as: "spin" })
62
+ // ...
63
+ ```
64
+
65
+ If you're writing `translate()` in an assembly file, something is wrong. Either the part should have a connector, or the prop should carry the dimension.
66
+
67
+ ## Data Flow: Props Down, Metadata Up
68
+
69
+ Data flows in one direction: from parent (assembly) to children (parts).
70
+
71
+ ```
72
+ Assembly (root)
73
+ ├── passes { servo, wall, clearance } → Motor Mount
74
+ │ └── returns { shape, boltPattern, flange connector }
75
+ ├── passes { gears, boltPattern } → Base Body
76
+ │ └── returns { shape, pinionZ, rack connectors }
77
+ ├── passes { gears, rackZ, armSpan } → Jaw Unit
78
+ │ └── returns { shape, armWidth, rack connector }
79
+ └── passes { boltPattern, armSlot } → Cover Plate
80
+ └── returns { shape, seat connector }
81
+ ```
82
+
83
+ **Props flow down:** The assembly knows the cross-cutting decisions (base height, wall thickness, servo model) and passes them to parts as `require()` param overrides.
84
+
85
+ **Metadata flows up:** Parts return their shape + connectors + any computed data that siblings need. The assembly reads this metadata and passes relevant pieces to other parts.
86
+
87
+ **Siblings never import each other.** The cover plate doesn't import the motor mount to read bolt positions. The assembly reads the mount's bolt pattern and passes it to the cover plate. The parent mediates all sibling communication.
88
+
89
+ ```js
90
+ // Assembly mediates sibling data flow
91
+ const mount = require('./motor-mount.forge.js', { Wall: wall });
92
+ const base = require('./base-body.forge.js', {
93
+ Height: baseH,
94
+ BoltPattern: mount.boltPattern, // parent passes mount's data to base
95
+ });
96
+ const cover = require('./cover-plate.forge.js', {
97
+ BoltPattern: mount.boltPattern, // same data to cover
98
+ ArmSlot: jaw.armProfile, // jaw's data to cover
99
+ });
100
+ ```
101
+
102
+ ## Why This Matters
103
+
104
+ ### For Humans
105
+
106
+ Each part file tells a complete story. You open `motor-mount.forge.js` and see everything: its geometry, its connectors, its parameters, its verifications. No chasing imports across 8 files. No 266-line shared-dims.js to understand.
107
+
108
+ Modifying one part doesn't break siblings — connectors are the contract. Change the mount's internal cavity dimensions and nothing else changes, as long as the flange connector stays at the same position.
109
+
110
+ ### For AIs
111
+
112
+ Each part file is self-contained context. An AI generates `base-body.forge.js` without reading `jaw-unit.forge.js`. The assembly file is a high-level plan — generate it from a natural language description. Props are the interface contract — the AI knows exactly what each part needs and provides.
113
+
114
+ ### For Swap-ability
115
+
116
+ Change the servo model → the mount rebuilds with new cavity dimensions → its bolt pattern updates → the assembly passes the new pattern to base and cover → everything adapts. One change, cascading update, zero manual re-derivation.
117
+
118
+ ## The Connector Convention
119
+
120
+ Connectors follow one convention: **face-to-face**.
121
+
122
+ Each connector's axis points **outward** from its part — the direction the connection faces. When two connectors mate, the system negates one axis so they approach from opposite sides.
123
+
124
+ - **Fixed joints** (bolt flange): both faces point outward, system brings them together
125
+ - **Revolute joints** (hinge): both parts point outward along the hinge line, system opposes them
126
+ - **Prismatic joints** (slider): both connectors point along the slide direction (co-directional exception — the slide axis IS the shared direction)
127
+
128
+ ```js
129
+ // Base bottom face points down, mount flange points up → meet in the middle
130
+ base.withConnectors({ mount_face: connector("bolt-face", { origin: [0,0,0], axis: [0,0,-1] }) });
131
+ mount.withConnectors({ flange: connector("bolt-face", { origin: [0,0,0], axis: [0,0,1] }) });
132
+
133
+ // Hinge: frame points up along hinge line, door points down → shared rotation axis
134
+ frame.withConnectors({ hinge: connector("hinge", { origin: [0,0,40], axis: [0,0,1] }) });
135
+ door.withConnectors({ hinge: connector("hinge", { origin: [0,0,40], axis: [0,0,-1] }) });
136
+ ```
137
+
138
+ ## One File vs. Many Files
139
+
140
+ **Default: one file per assembly.** For a project-specific assembly (robot gripper, drone frame), put everything in one file. Parts are sections separated by comments. Shared data is just variables. This maximizes locality — you see the whole design by scrolling.
141
+
142
+ **Split when:** A part is reusable across projects (standard servo cradle, parametric gear pair), or the file exceeds ~300 lines. Split parts into separate `.forge.js` files that export component functions.
143
+
144
+ **Never split for "organization."** Eight files with 40 lines each is worse than one file with 300 lines. The context-switching cost of 8 files destroys locality.
145
+
146
+ ## What This Forbids
147
+
148
+ 1. **No shared-dims files.** If you're writing a file whose only job is to compute derived dimensions from component specs, the assembly should be doing that work (or the parts should derive their own internal dimensions from props).
149
+
150
+ 2. **No sibling imports.** A part file never `require()`s another part file. Data flows through the parent.
151
+
152
+ 3. **No assembly-space coordinates in parts.** A part doesn't know `pinionZ = 14`. It receives `rackZ: 14` as a prop, or it computes its own internal Z from its own props.
153
+
154
+ 4. **No `translate()` in assembly files.** If you're translating a part to position it, use a connector instead. The exception: ghost/reference geometry that isn't structural.
155
+
156
+ 5. **No console.log validation.** Use `verify.*` for spatial checks. It's structured, clickable, and fails visibly.
157
+
158
+ ## Design Gate
159
+
160
+ **Every new multi-part example and every agent-built assembly must follow this model:**
161
+
162
+ > Can you understand each part file without reading any other file? Does the assembly file contain zero coordinate math? Do all inter-part relationships flow through connectors and props?
163
+
164
+ If the answer to any is no, refactor until it's yes.
@@ -33,6 +33,8 @@ bomToCsv(rows: BomRow[]): string
33
33
 
34
34
  #### `assembly()` — Create an assembly container with named parts and joints for kinematic mechanisms.
35
35
 
36
+ **Use this from iteration 1 for any model with moving parts.** Hinges, sliders, gears, articulated fingers, doors — all start with `assembly()`, not with manual rotation math. Don't build a static "extended pose" first and refactor to an assembly later: joint sliders, animations, sweeps, collision detection, and robot export all flow from the kinematic graph.
37
+
36
38
  An assembly models a mechanism as a directed graph of parts connected by joints. Parts are the nodes; joints are directed edges from parent to child. The graph must be a forest (no cycles). Root parts (those with no incoming joint) are anchored to world space.
37
39
 
38
40
  Three joint types are supported: `'revolute'` (hinge), `'prismatic'` (slider), and `'fixed'` (rigid attachment). Use `addPart()` to add geometry, `addJoint()` (or the shorthands `addRevolute()`, `addPrismatic()`, `addFixed()`) to connect parts, and `solve()` to compute world-space positions at a given joint state.
@@ -62,7 +64,7 @@ assembly(name?: string): Assembly
62
64
 
63
65
  #### `joint()` — Create a revolute joint that auto-generates a parameter slider and rotates the shape.
64
66
 
65
- This is a convenience wrapper for single-shape, single-joint use cases. It calls [`param()`](/docs/core#param) to create a named angle slider, then applies `rotateAroundAxis()` to the shape. Use the full `Assembly` API for mechanisms with multiple parts and joints.
67
+ This is a convenience wrapper for single-shape, single-joint use cases. It calls `param()` to create a named angle slider, then applies `rotateAroundAxis()` to the shape. Use the full `Assembly` API for mechanisms with multiple parts and joints.
66
68
 
67
69
  ```ts
68
70
  const arm = joint("Shoulder", armShape, [0, 0, 20], {
@@ -268,22 +270,28 @@ addFixed(name: string, parent: string, child: string, options?: JointOptions): A
268
270
 
269
271
  Connector references use `"PartName.connectorName"` format. The system aligns connector origins (child connector lands exactly on parent connector) and derives the joint frame and axis from the connector geometry — no manual `frame` or `axis` math needed.
270
272
 
271
- The joint type is inferred from the connector's `kind` field if not specified in `options`. Use `flip: true` for mirrored parts whose connector axis is reflected.
273
+ **Face-to-face convention:** Connectors always meet face-to-face, like a USB plug meeting a socket. Each connector's axis points "outward" from its part. When two connectors mate, the system brings them together so their axes oppose (anti-parallel). This is the same convention used by `matchTo()`.
274
+
275
+ For a revolute joint (hinge), both connectors' axes should point outward from their respective parts along the hinge line. For a prismatic joint (slider), both axes should point along the slide direction from their part's perspective.
276
+
277
+ The joint type is inferred from the connector's `kind` field if not specified in `options`.
272
278
 
273
279
  When connectors are defined with `start`/`end`, you can control which point on each connector meets via `align` / `parentAlign` / `childAlign` (`'start'`, `'middle'`, `'end'`).
274
280
 
275
281
  Use `connect()` when connector origins must physically coincide (flange-to-flange, bolt-into-bore). For mechanisms where parts share an axis but are deliberately spaced apart, use `addRevolute()` with pre-positioned parts instead.
276
282
 
277
283
  ```ts
278
- const mech = assembly("Arm")
279
- .addPart("Base", base)
280
- .addPart("Link", link)
281
- .connect("Base.top", "Link.shoulder", {
282
- as: "J1",
283
- min: -90, max: 90, default: 0,
284
- });
285
-
286
- return mech.solve({ J1: 45 }).toGroup();
284
+ // Hinge: both axes point outward along the hinge line
285
+ const frame = box(100, 10, 80).withConnectors({
286
+ hinge: connector("hinge", { origin: [0, 0, 40], axis: [0, 0, 1] }),
287
+ });
288
+ const door = box(60, 4, 80).withConnectors({
289
+ hinge: connector("hinge", { origin: [0, 0, 40], axis: [0, 0, -1] }),
290
+ });
291
+ assembly("Door")
292
+ .addPart("Frame", frame)
293
+ .addPart("Door", door)
294
+ .connect("Frame.hinge", "Door.hinge", { as: "swing", min: 0, max: 110 });
287
295
  ```
288
296
 
289
297
  ```ts