@code3d/core 0.0.1-alpha.11 → 0.0.1-alpha.13

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 (36) hide show
  1. package/README.md +55 -41
  2. package/bld/chunks/{chunk-HZRQUHM2.js → chunk-776Q5P2L.js} +2 -2
  3. package/bld/chunks/{chunk-QK6ZET47.js → chunk-N7JJWNVL.js} +3 -3
  4. package/bld/chunks/{chunk-CL3E2DE4.js → chunk-VFPWXSFD.js} +2 -2
  5. package/bld/chunks/{chunk-JBVIAMJ6.js → chunk-VINZNODV.js} +589 -26
  6. package/bld/chunks/chunk-VINZNODV.js.map +7 -0
  7. package/bld/library/extrude.d.ts +5 -0
  8. package/bld/library/extrude.d.ts.map +1 -1
  9. package/bld/library/index.d.ts +2 -2
  10. package/bld/library/index.d.ts.map +1 -1
  11. package/bld/library/index.js +17 -1
  12. package/bld/library/inspect.d.ts +3 -1
  13. package/bld/library/inspect.d.ts.map +1 -1
  14. package/bld/library/loft.d.ts +7 -1
  15. package/bld/library/loft.d.ts.map +1 -1
  16. package/bld/library/replicad.js +2 -2
  17. package/bld/library/runtime.d.ts +75 -8
  18. package/bld/library/runtime.d.ts.map +1 -1
  19. package/bld/node/index.js +19 -3
  20. package/bld/node/replicad.js +4 -4
  21. package/bld/tooling/index.js +2 -2
  22. package/docs/api.md +143 -13
  23. package/docs/local-coordinates.md +2 -0
  24. package/docs/runtime.md +52 -2
  25. package/docs/topology.md +6 -0
  26. package/docs/values.md +34 -31
  27. package/package.json +1 -1
  28. package/src/library/extrude.ts +129 -2
  29. package/src/library/index.ts +9 -0
  30. package/src/library/inspect.ts +4 -2
  31. package/src/library/loft.ts +67 -2
  32. package/src/library/runtime.ts +646 -8
  33. package/bld/chunks/chunk-JBVIAMJ6.js.map +0 -7
  34. /package/bld/chunks/{chunk-HZRQUHM2.js.map → chunk-776Q5P2L.js.map} +0 -0
  35. /package/bld/chunks/{chunk-QK6ZET47.js.map → chunk-N7JJWNVL.js.map} +0 -0
  36. /package/bld/chunks/{chunk-CL3E2DE4.js.map → chunk-VFPWXSFD.js.map} +0 -0
package/docs/api.md CHANGED
@@ -5,6 +5,9 @@ sidebar:
5
5
  order: 1
6
6
  ---
7
7
 
8
+ Construct geometry, combine models and query the result with the public Core
9
+ API. For a first runnable model, see the [Core example](../README.md#example).
10
+
8
11
  Import these functions from `@code3d/core`. The editor's TypeScript signatures
9
12
  provide exact overloads and inferred model interfaces.
10
13
 
@@ -31,6 +34,7 @@ radius. For coils, `coilRadius` is measured to the wire centerline and
31
34
  `pitch` is the advance per turn. Fractional turns are supported; the wire
32
35
  must fit inside the coil radius and neighboring turns must remain separated.
33
36
  Use [`@code3d/screws`](../../screws/docs/assembly.mdx) for standard fasteners and matching hole tools.
37
+ Use [`@code3d/gears`](../../gears/README.md) for nominal spur, helical and internal gear parts.
34
38
 
35
39
  To build a solid beyond these primitives, import `definePrimitive` and
36
40
  `replicad` from `@code3d/core/replicad`. See
@@ -40,19 +44,21 @@ To build a solid beyond these primitives, import `definePrimitive` and
40
44
 
41
45
  Planar profiles lie in the local XZ plane with a +Y normal.
42
46
 
43
- | Function | Meaning |
44
- | ------------------------------------------ | -------------------------------------------- |
45
- | `circle(radius)` | Circular face |
46
- | `ellipse(xRadius, zRadius)` | Elliptical face |
47
- | `rectangle(x, z)` | Rectangular face |
48
- | `regularPolygon(radius, sides, rotation?)` | Regular polygonal face |
49
- | `point()` or `point([x, y, z])` | Vertex model |
50
- | `line([x, y, z])` or `line(start, end)` | Straight edge |
51
- | `arc(start, middle, end)` | Arc through three points |
52
- | `bezier(points)` | Bézier curve |
53
- | `spline(points)` | Interpolating spline |
54
- | `loft(sections, options?)` | Solid through sections; optional curve spine |
55
- | `extrude(faceOrFaces, distance)` | Solid extruded along one face's local normal |
47
+ | Function | Meaning |
48
+ | ------------------------------------------ | ------------------------------------------------------------------------- |
49
+ | `circle(radius)` | Circular face |
50
+ | `ellipse(xRadius, zRadius)` | Elliptical face |
51
+ | `rectangle(x, z)` | Rectangular face |
52
+ | `regularPolygon(radius, sides, rotation?)` | Regular polygonal face |
53
+ | `point()` or `point([x, y, z])` | Vertex model |
54
+ | `line([x, y, z])` or `line(start, end)` | Straight edge |
55
+ | `arc(start, middle, end)` | Arc through three points |
56
+ | `bezier(points)` | Bézier curve |
57
+ | `spline(points)` | Interpolating spline |
58
+ | `loft(sections, options?)` | Solid through sections; optional curve spine |
59
+ | `extrude(faceOrFaces, distance)` | Solid extruded along one face's local normal |
60
+ | `revolve(profile, axis, config)` | Solid rotated about a straight directed axis, with optional axial advance |
61
+ | `sweep(profile, spine)` | Solid formed by carrying one face along an open curve |
56
62
 
57
63
  See [local coordinates and placement](local-coordinates.md) for
58
64
  the coordinate frame of a model, reference, or composition.
@@ -83,8 +89,126 @@ export const plate = rectangle(30, 20).extrude(3).fillet(0.5);
83
89
  export const pin = extrude(circle(2), -10);
84
90
  ```
85
91
 
92
+ ### Rotational solids
93
+
94
+ `revolve(profile, axis, config: RevolveConfig)` and
95
+ `profile.revolve(axis, config)` rotate one face about a straight directed axis.
96
+ `line(...)` can be passed directly; an existing straight edge or axis reference
97
+ also works.
98
+ `config.angle` is a required finite, non-zero angle in degrees. `config.advance`
99
+ is the signed total distance traveled along the directed axis during that angle;
100
+ it defaults to zero. A positive angle follows the axis's right-hand direction.
101
+ Reversing the axis reverses both the rotation sense and the direction of a positive
102
+ advance. With zero advance, the angle may cover at most one turn. With non-zero
103
+ advance, it may cover multiple turns to form a simple screw-motion solid.
104
+
105
+ ```ts
106
+ import {circle, line, rectangle, revolve} from '@code3d/core';
107
+
108
+ const axis = line([0, -20, 0], [0, 20, 0]);
109
+ const ringSection = rectangle(4, 6).rotate(90, 0, 0).originOffset(-8, 0, 0);
110
+ export const ring = revolve(ringSection, axis, {angle: 360});
111
+
112
+ const wireSection = circle(1).rotate(90, 0, 0).originOffset(-8, 0, 0);
113
+ export const spring = wireSection.revolve(axis, {angle: 5 * 360, advance: 25});
114
+ ```
115
+
116
+ The authoring signature requires `config`. While editing an incomplete call,
117
+ the App uses 360 degrees and zero advance so its parameter tool can add the
118
+ config object.
119
+
120
+ The result keeps the profile's local frame and is an ordinary `SolidModel`.
121
+ The axis participates in the same relation solve as the profile; its own model
122
+ placement is respected. A helical profile must have one outer boundary without
123
+ holes. Intersecting turns and profiles that cross the axis may fail to produce a
124
+ valid solid; leave clearance between turns and keep the profile off the axis.
125
+ For a multi-turn coil with round wire and automatic pitch clearance checks,
126
+ [`coil`](#solid-primitives) remains the shorter constructor.
127
+
128
+ ### Path sweeps
129
+
130
+ `sweep(profile, spine)` and `profile.sweep(spine)` carry one planar face along a
131
+ continuous open `EdgeModel`, such as a line or Bézier curve. The face's local
132
+ origin must meet the path's start, and its normal must point along the starting
133
+ tangent. The operation respects the solved placement of both inputs and returns
134
+ a solid in the profile's local frame; it does not move or rotate the supplied
135
+ profile to fit the path.
136
+
137
+ ```ts
138
+ import {bezier, circle, sweep} from '@code3d/core';
139
+
140
+ const profile = circle(2);
141
+ const spine = bezier([
142
+ [0, 0, 0],
143
+ [0, 8, 0],
144
+ [5, 16, 0],
145
+ [5, 24, 0],
146
+ ]);
147
+ export const bentRod = sweep(profile, spine);
148
+ ```
149
+
150
+ The path must be open with a non-zero starting tangent. The output is an ordinary
151
+ `SolidModel` that supports subsequent Boolean and finishing operations. Very
152
+ tight bends or self-intersections may prevent the kernel from producing a valid
153
+ solid. One through hole in the profile is supported; profiles with multiple
154
+ holes currently need explicit contour correspondence. Use the
155
+ [App example](../../app/examples/operations/sweep.ts) to inspect
156
+ the profile, path and result.
157
+
86
158
  ## Measurements
87
159
 
160
+ ### Length and area
161
+
162
+ Read `edge.length` or `line(...).length` for a finite edge's actual arc length.
163
+ A straight edge uses its endpoint distance; a closed edge uses its circumference.
164
+ Read `surface.area` or `faceModel.area` for a finite face's area, including curved
165
+ surfaces and trimming holes. `solid.area` / `solidModel.area` includes every
166
+ boundary face, including inner walls and cavity faces.
167
+
168
+ These properties return ordinary numbers in model units (area in square model
169
+ units). They are read-only. Rotation, origin changes, placement and reversing an
170
+ edge or flipping a face preserve the result. `scaled(s)` multiplies lengths by
171
+ `s` and areas by `s²`; exposed references use the scale of their actual geometry.
172
+ `LineAnchor` and `FaceAnchor` can describe infinite references and have no length
173
+ or area. Groups have no aggregate area.
174
+
175
+ ```ts
176
+ import {line, rectangle, box} from '@code3d/core';
177
+ const length = line([3, 4, 0]).length; // 5
178
+ const area = rectangle(4, 6).area; // 24
179
+ const surfaceArea = box(2, 3, 4).area; // 52
180
+ ```
181
+
182
+ Select `.length` or `.area` in App to inspect the measured geometry and value.
183
+ Straight lengths use a dimension line; curves highlight their actual path with an
184
+ arc-length label. Area highlights the finite face or whole solid with an area
185
+ label. The read-only display does not create editable size constraints.
186
+ Try the [length example](../../app/examples/operations/length.ts) and
187
+ [area example](../../app/examples/operations/area.ts).
188
+
189
+ ### Volume
190
+
191
+ Read `solid.volume` or `solidModel.volume` for the space occupied by the solid's
192
+ material. Holes and enclosed cavities are excluded. The result is a read-only
193
+ number in cubic model units. Rotation, origin changes and placement preserve it;
194
+ `scaled(s)` multiplies it by `s³`. Exposed solid references include the scale of
195
+ their actual geometry. Faces, edges, infinite references and groups have no volume
196
+ property.
197
+
198
+ ```ts
199
+ import {box, tube} from '@code3d/core';
200
+ const blockVolume = box(2, 3, 4).volume; // 24
201
+ const pipeVolume = tube(5, 3, 7).volume; // 112 * Math.PI
202
+ const enlargedVolume = box(2, 3, 4).scaled(2).volume; // 192
203
+ ```
204
+
205
+ Select `.volume` in App to inspect the whole solid with a volume label at its
206
+ volume centroid. This read-only display uses the getter's recorded result and
207
+ does not create an editable size constraint.
208
+ Try the [volume example](../../app/examples/operations/volume.ts).
209
+
210
+ ### Distance between references
211
+
88
212
  `distance(a, b, axis?)` returns a non-negative `number` from the models and
89
213
  relations available at the call. It accepts vertex, edge, face and solid models,
90
214
  non-empty groups, finite topology references, directional bounds, and point
@@ -491,6 +615,12 @@ the vertex picker, origin arrows, and rotation rings, see
491
615
 
492
616
  ## Anchors and relations
493
617
 
618
+ Package authors can use `setModelData(model, key, value)` to associate
619
+ package-specific data with a newly built model, and `getModelData(model, key)`
620
+ to read it. Keys are symbols. Data is retained when `.relate()` or
621
+ `.material()` creates a new value; other model operations do not retain it.
622
+ This data stays in process and is not part of model geometry or snapshots.
623
+
494
624
  Solid primitives expose `center` and `axis`; every model provides directional
495
625
  bounds: `up` (+Y), `down` (−Y), `right` (+X), `left` (−X), `front` (+Z),
496
626
  and `back` (−Z), in that model's local frame.
@@ -47,6 +47,8 @@ Only an explicit origin operation chooses a different local zero.
47
47
  | `text` | All returned faces share the text layout's baseline origin, including its glyph advances and offsets. Individual letters are not centered. |
48
48
  | `definePrimitive` | Retain the frame used by the returned geometry; no automatic recentering. |
49
49
  | `extrude` | Inherit the input face's frame, without centering the extrusion. |
50
+ | `revolve` | Inherit the input profile's frame; the axis's solved placement is converted into that frame. |
51
+ | `sweep` | Inherit the input profile's frame; the spine's solved placement is converted into that frame. |
50
52
  | `union`, `intersect` | Inherit the first operand's frame. |
51
53
  | `cut` | Inherit the stock's frame. |
52
54
  | `loft` | Inherit the first section's frame. |
package/docs/runtime.md CHANGED
@@ -70,6 +70,16 @@ Returning `undefined` declines the scope; returning `{}` intentionally displays
70
70
  an empty scene. Target values matching `context.focused.values` receive focus;
71
71
  generated geometry does not inherit focus from its inputs.
72
72
 
73
+ For a function that returns related copies of input models,
74
+ `inspectGroupMembers(result, inputs)` from `@code3d/core` renders each result
75
+ in the solved group frame and keeps its corresponding input as the focus
76
+ identity. This gives array member selection the same positioned, per-member
77
+ highlighting as the `group()` parameter inspector. Use the input array when
78
+ `context.focused.parameter` identifies that argument; when inspecting the
79
+ returned collection itself, use `inspectGroupMembers(result, result)` so its
80
+ members keep their result identities. The viewport does not add the current
81
+ execution's original inputs to this scene.
82
+
73
83
  Selecting an array member focuses that value while keeping the other inspection
74
84
  targets visible at a weaker level; selecting the whole array focuses its members.
75
85
  For cut tools and intersect operands, selected inputs are targets and other inputs
@@ -79,9 +89,9 @@ shows the selected inputs and ambient operands without inventing a result.
79
89
  These region inspectors use ordinary unlit materials with depth testing disabled,
80
90
  so their colors remain visible through the translucent inputs.
81
91
 
82
- Core uses this mechanism for distance measurements, relate calls and their
92
+ Core uses this mechanism for length/area/volume properties, distance measurements, relate calls and their
83
93
  closures, on/align references, relative transformation stages, group children,
84
- expose sources, Boolean operands and loft sections/spines, plus box and extrusion
94
+ expose sources, Boolean operands, loft sections/spines and sweep profiles/spines, plus box and extrusion
85
95
  dimensions. Selecting a normal constructor or Boolean function name
86
96
  still previews its return value. Inspectors retain the original operation frame,
87
97
  so a later relation or a different consumer cannot move its inspection.
@@ -108,6 +118,46 @@ the owner as `ambient` when the call fails or the reference collection is empty.
108
118
  The owner therefore has the same background appearance before and after a
109
119
  selection; missing or invalid IDs still produce their normal modeling errors.
110
120
 
121
+ ### Getter inspection
122
+
123
+ A getter can declare `@code3d.inspect callback` in its JSDoc. A package can put
124
+ the same annotation on its public `readonly` property declaration when the
125
+ implementation getter is not present in its declarations. The callback receives
126
+ `[]`, with the actual receiver in `context.receiver`, the recorded property value
127
+ in `context.return`, and any `captureInspectData` payload in `context.data`.
128
+
129
+ ```ts
130
+ import {
131
+ captureInspectData,
132
+ type InspectContext,
133
+ type Model,
134
+ } from '@code3d/core';
135
+
136
+ function inspectSize(_args: readonly [], context: InspectContext<number>) {
137
+ return {target: [context.data as Model]};
138
+ }
139
+ class Part {
140
+ constructor(readonly body: Model) {}
141
+ /** @code3d.inspect inspectSize */
142
+ get size() {
143
+ captureInspectData(this.body);
144
+ return 42;
145
+ }
146
+ }
147
+ ```
148
+
149
+ Read `part.size` normally to record it. Selecting the property runs only its
150
+ inspector; it never runs the getter again. Each reached read keeps its own
151
+ receiver, return and data, even if several reads return the same number. Local
152
+ getters retain their declaration's lexical inspector binding; published callbacks
153
+ must be runtime exports of the package. Optional reads that short-circuit do not
154
+ invoke an inspector. A failed getter can still inspect its captured data, with
155
+ `context.return` undefined. Merely enumerating an object never invokes its getters.
156
+
157
+ Core's `.length`, `.area` and `.volume` use this mechanism. Passive `dimension` annotations
158
+ accept either `start`/`end`, alternative `candidates`, or `at: [x, y, z]` to show
159
+ only a value at an owner's local position. Labels do not add CAD geometry.
160
+
111
161
  ### Call data
112
162
 
113
163
  The App runs JSDoc inspection callbacks when the corresponding source is selected.
package/docs/topology.md CHANGED
@@ -127,6 +127,12 @@ const corners = boundary[0].vertices();
127
127
  const center = face.center;
128
128
  ```
129
129
 
130
+ Edges expose readonly `.length` for actual arc length; surfaces expose `.area`
131
+ for their trimmed finite area. Exposed solids provide total boundary `.area` and
132
+ material `.volume`, excluding holes and cavities. These measurements include the
133
+ scale of the selected geometry. See [length and area](api.md#length-and-area)
134
+ and [volume](api.md#volume).
135
+
130
136
  Queries use the original model's IDs and stay within the selected element.
131
137
  `face.edge(id)` reports an error if that edge is outside the face. The viewport
132
138
  picker offers only the eligible edges or vertices, including when the geometry
package/docs/values.md CHANGED
@@ -5,6 +5,10 @@ sidebar:
5
5
  order: 2
6
6
  ---
7
7
 
8
+ Build models from reusable values, then measure their geometry or relative
9
+ placement. Queries return ordinary numbers and vectors; modeling operations
10
+ return new model values.
11
+
8
12
  ## Model values and coordinates
9
13
 
10
14
  Operations produce new model values. Building another result must not change an
@@ -22,27 +26,10 @@ angles use degrees. Read [local coordinates](local-coordinates.md)
22
26
  and [relations](relations.mdx) before mixing
23
27
  origin changes, alignment, and rotation.
24
28
 
25
- Constraints express `on` and `align` only. Put relative `offset` and `rotate`
26
- transformations in the `relate` array after the constraints. Zero values add no
27
- centering or orientation condition; use point or axis alignment to center a part.
28
- The App's gizmos edit or insert independent array entries.
29
-
30
- Choose a
31
- center with `pivot([x,y,z])`, self topology with `pivotVertex(id)`/`axisEdge(id)`,
32
- or references with `pivotPoint(pointRef)`/`axisLine(lineRef)`; finish each selector
33
- with `rotate`. Point rotations retain self XYZ axes, including external centers. Consecutive constraints solve jointly; transformations
34
- then act on that result in order. A later constraint starts a new segment using
35
- the preceding pose. Independent offsets use fixed composition axes, and rotations
36
- default to self's current origin. Each completed transformation is one array item,
37
- for example `[offset(0, 8, 0), rotate(0, 25, 0)]`. Only pivot/axis selections
38
- chain into `rotate`; completed transformations cannot chain into another operation.
39
- Keep a selected reference while moving it with
40
- `pivotVertex(id).pivotOffset(dx, dy, dz).rotate(x, y, z)` or
41
- `axisLine(axis).axisOffset(dx, dy, dz).rotate(angle)`. Point offsets use self local
42
- axes; axis offsets use the selected axis frame and preserve its direction.
43
- Each selector accepts one matching offset, followed by `rotate`.
44
- See the [transformation example](../../app/examples/constraints/transformations.ts)
45
- and [placement guide](relations.mdx#transform-a-joint-result).
29
+ Constraints express `on` and `align`; relative `offset` and `rotate` operations
30
+ follow them as separate items in the `relate` array. For pivots, reference axes
31
+ and operation order, see the [placement guide](relations.mdx#transform-a-joint-result)
32
+ and [transformation example](../../app/examples/constraints/transformations.ts).
46
33
 
47
34
  Build readable models from named intermediate values and public operations. A
48
35
  profile followed by extrusion, or solids combined with Boolean operations,
@@ -63,6 +50,29 @@ finite geometry, nested occurrences, axis frames and source-order dependencies.
63
50
 
64
51
  ## Geometry measurements
65
52
 
53
+ Read a model's dimensions in its own frame, or query its bounds and position
54
+ relative to another model:
55
+
56
+ ```ts
57
+ import {box, group, offset} from '@code3d/core';
58
+
59
+ const base = box(20, 4, 20);
60
+ const part = box(8, 12, 4).relate(self => [self.on(base.up), offset(20, 0, 0)]);
61
+ const size = part.bounds().size; // [8, 12, 4]
62
+ const origin = part.position(base); // [20, 8, 0]
63
+ const minimum = part.bounds(base).minimum; // [16, 2, -2]
64
+ export default group([base, part]);
65
+ ```
66
+
67
+ Finite edges and edge models provide readonly `.length`; finite surfaces and
68
+ face models provide `.area`. Solids provide `.area` for their total boundary
69
+ surface, including inner walls, and `.volume` for material volume, excluding holes
70
+ and cavities. These are plain numbers, follow geometry scaling, and retain the
71
+ original value when later operations create a new model. Infinite axes/planes
72
+ and groups do not have these measurements. See [length and area](api.md#length-and-area)
73
+ and [volume](api.md#volume), and select these properties in App for a read-only
74
+ visual measurement.
75
+
66
76
  `model.bounds(relativeTo?)` returns readonly `minimum`, `maximum` and `size`
67
77
  XYZ vectors for tight finite geometry bounds. By default it uses the model's
68
78
  own local frame. An explicit reference includes solved placement and nested
@@ -76,16 +86,7 @@ changes the model or its placement. These methods are available on every model
76
86
  kind, including groups. As model members, `bounds` and `position` are reserved
77
87
  names and cannot be used as exposed element names.
78
88
 
79
- ```ts
80
- import {box, group, offset} from '@code3d/core';
81
-
82
- const base = box(20, 4, 20);
83
- const part = box(8, 12, 4).relate(self => [self.on(base.up), offset(20, 0, 0)]);
84
- const size = part.bounds().size; // [8, 12, 4]
85
- const origin = part.position(base); // [20, 8, 0]
86
- const minimum = part.bounds(base).minimum; // [16, 2, -2]
87
- export default group([base, part]);
88
- ```
89
+ ## Editing incomplete calls
89
90
 
90
91
  Dimension-based primitives and numeric modeling methods retain required TypeScript
91
92
  signatures while providing runtime defaults for omitted or `undefined` values.
@@ -102,6 +103,8 @@ edit and completion share one undo step. Use explicit dimensions in finished
102
103
  models; the [reference](api.md#runtime-defaults-while-editing)
103
104
  lists the actual defaults.
104
105
 
106
+ ## Composition and topology
107
+
105
108
  Topology capabilities follow dimension: vertices expose vertex selection, edges
106
109
  add edge selection, and faces and solids add surface selection. Only solids
107
110
  provide `fillet`, `chamfer`, and `shell`. Groups compose values and support
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code3d/core",
3
- "version": "0.0.1-alpha.11",
3
+ "version": "0.0.1-alpha.13",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -1,5 +1,14 @@
1
- import {getOC, Vector, type Shape3D} from 'replicad';
2
- import {castOwnedShape3D} from './kernel-shapes.js';
1
+ import {
2
+ assembleWire,
3
+ getOC,
4
+ makeHelix,
5
+ makeLine,
6
+ measureVolume,
7
+ Vector,
8
+ type Face,
9
+ type Shape3D,
10
+ } from 'replicad';
11
+ import {castOwnedShape3D, shapeSubshapes} from './kernel-shapes.js';
3
12
  import {
4
13
  transferShapeTopology,
5
14
  type TopologyInput,
@@ -36,3 +45,121 @@ export function extrudeWithTopology(
36
45
  vector.delete();
37
46
  }
38
47
  }
48
+
49
+ /** Sweep a face around a directed axis, optionally advancing along it. */
50
+ export function revolveWithTopology(
51
+ source: TopologyInput,
52
+ center: Vec3,
53
+ direction: Vec3,
54
+ angle: number,
55
+ advance: number,
56
+ ): {shape: Shape3D; topology?: ShapeTopology} {
57
+ const oc = getOC();
58
+ if (advance === 0) {
59
+ const point = new oc.gp_Pnt(...center);
60
+ let dir: InstanceType<typeof oc.gp_Dir> | undefined;
61
+ let axis: InstanceType<typeof oc.gp_Ax1> | undefined;
62
+ let shape: Shape3D | undefined;
63
+ try {
64
+ dir = new oc.gp_Dir(...direction);
65
+ axis = new oc.gp_Ax1(point, dir);
66
+ const builder = new oc.BRepPrimAPI_MakeRevol(
67
+ source.shape.wrapped,
68
+ axis,
69
+ angle * (Math.PI / 180),
70
+ false,
71
+ );
72
+ try {
73
+ if (!builder.IsDone()) throw new Error('Could not revolve the face.');
74
+ shape = castOwnedShape3D(builder.Shape());
75
+ requireRevolvedVolume(shape);
76
+ return {
77
+ shape,
78
+ topology: transferShapeTopology([source], shape, builder),
79
+ };
80
+ } finally {
81
+ builder.delete();
82
+ }
83
+ } catch (error) {
84
+ shape?.delete();
85
+ throw error;
86
+ } finally {
87
+ axis?.delete();
88
+ dir?.delete();
89
+ point.delete();
90
+ }
91
+ }
92
+
93
+ const boundaries = shapeSubshapes(source.shape, 'wire');
94
+ try {
95
+ if (boundaries.length !== 1)
96
+ throw new Error('Helical revolution requires a face without holes.');
97
+ } finally {
98
+ boundaries.forEach(boundary => boundary.delete());
99
+ }
100
+ const end: Vec3 = [
101
+ center[0] + direction[0] * advance,
102
+ center[1] + direction[1] * advance,
103
+ center[2] + direction[2] * advance,
104
+ ];
105
+ let startVector: Vector | undefined;
106
+ let endVector: Vector | undefined;
107
+ let line: ReturnType<typeof makeLine> | undefined;
108
+ let spine: ReturnType<typeof assembleWire> | undefined;
109
+ let guide: ReturnType<typeof makeHelix> | undefined;
110
+ let wire: ReturnType<Face['outerWire']> | undefined;
111
+ let shape: Shape3D | undefined;
112
+ try {
113
+ startVector = new Vector([...center]);
114
+ endVector = new Vector([...end]);
115
+ line = makeLine(startVector, endVector);
116
+ spine = assembleWire([line]);
117
+ guide = makeHelix(
118
+ (advance * 360) / angle,
119
+ advance,
120
+ 1,
121
+ [...center],
122
+ [...direction],
123
+ );
124
+ // Replicad's outerWire consumes its cloned Face wrapper.
125
+ wire = (source.shape as Face).clone().outerWire();
126
+ const builder = new oc.BRepOffsetAPI_MakePipeShell(spine.wrapped);
127
+ try {
128
+ builder.SetMode(
129
+ guide.wrapped,
130
+ false,
131
+ oc.BRepFill_TypeOfContact.BRepFill_NoContact,
132
+ );
133
+ builder.Add(wire.wrapped, false, false);
134
+ if (!builder.IsReady())
135
+ throw new Error(
136
+ 'Could not associate the profile with the rotation axis.',
137
+ );
138
+ builder.Build();
139
+ if (!builder.IsDone() || !builder.MakeSolid())
140
+ throw new Error('Could not construct a solid helical revolution.');
141
+ shape = castOwnedShape3D(builder.Shape());
142
+ requireRevolvedVolume(shape);
143
+ // PipeShell does not expose face-cap history for an input face. Assign
144
+ // stable output IDs without guessing an inherited input correspondence.
145
+ return {shape};
146
+ } finally {
147
+ builder.delete();
148
+ }
149
+ } catch (error) {
150
+ shape?.delete();
151
+ throw error;
152
+ } finally {
153
+ wire?.delete();
154
+ guide?.delete();
155
+ spine?.delete();
156
+ line?.delete();
157
+ endVector?.delete();
158
+ startVector?.delete();
159
+ }
160
+ }
161
+
162
+ function requireRevolvedVolume(shape: Shape3D): void {
163
+ if (!(Math.abs(measureVolume(shape)) > 0))
164
+ throw new Error('Revolution did not produce a non-degenerate solid.');
165
+ }
@@ -13,6 +13,9 @@ export {
13
13
  align,
14
14
  expose,
15
15
  inspectTopologyReference,
16
+ inspectLength,
17
+ inspectArea,
18
+ inspectVolume,
16
19
  } from './runtime.js';
17
20
  export type {
18
21
  Dimension,
@@ -44,8 +47,10 @@ export type {
44
47
  } from './sketch.js';
45
48
 
46
49
  export {
50
+ getModelData,
47
51
  offset,
48
52
  rotate,
53
+ setModelData,
49
54
  pivot,
50
55
  pivotVertex,
51
56
  pivotPoint,
@@ -64,6 +69,7 @@ export {
64
69
  text,
65
70
  frustum,
66
71
  group,
72
+ inspectGroupMembers,
67
73
  intersect,
68
74
  line,
69
75
  loft,
@@ -71,7 +77,9 @@ export {
71
77
  rectangle,
72
78
  regularPolygon,
73
79
  regularPrism,
80
+ revolve,
74
81
  spline,
82
+ sweep,
75
83
  sphere,
76
84
  tube,
77
85
  union,
@@ -117,6 +125,7 @@ export type {
117
125
  NamedElements,
118
126
  PlanarElements,
119
127
  PointAnchor,
128
+ RevolveConfig,
120
129
  Solid,
121
130
  SolidModel,
122
131
  SolidModificationCapabilities,
@@ -49,14 +49,16 @@ export function recordInspectionCalls(
49
49
  export type DimensionSegment = Readonly<{start: Vec3; end: Vec3}>;
50
50
 
51
51
  type DimensionLines =
52
- DimensionSegment | Readonly<{candidates: readonly DimensionSegment[]}>;
52
+ | DimensionSegment
53
+ | Readonly<{candidates: readonly DimensionSegment[]}>
54
+ | Readonly<{at: Vec3}>;
53
55
  type DimensionOptions = Readonly<{
54
56
  owner: Model;
55
57
  value: number;
56
58
  axisLabel?: string;
57
59
  }>;
58
60
 
59
- /** A passive dimension; a candidate is selected on entry and retained while inspecting it. */
61
+ /** A passive measurement. `at` labels a point without a dimension line. */
60
62
  export type Dimension = Readonly<{kind: 'dimension'}> &
61
63
  DimensionOptions &
62
64
  DimensionLines;