@code3d/core 0.0.1-alpha.11 → 0.0.1-alpha.12
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/README.md +18 -13
- package/bld/chunks/{chunk-JBVIAMJ6.js → chunk-CQKOD54C.js} +552 -25
- package/bld/chunks/chunk-CQKOD54C.js.map +7 -0
- package/bld/chunks/{chunk-CL3E2DE4.js → chunk-JF6QRICU.js} +2 -2
- package/bld/chunks/{chunk-HZRQUHM2.js → chunk-L4V6X7RH.js} +2 -2
- package/bld/chunks/{chunk-QK6ZET47.js → chunk-QJVTVAYM.js} +3 -3
- package/bld/library/extrude.d.ts +5 -0
- package/bld/library/extrude.d.ts.map +1 -1
- package/bld/library/index.d.ts +2 -2
- package/bld/library/index.d.ts.map +1 -1
- package/bld/library/index.js +11 -1
- package/bld/library/inspect.d.ts +3 -1
- package/bld/library/inspect.d.ts.map +1 -1
- package/bld/library/loft.d.ts +7 -1
- package/bld/library/loft.d.ts.map +1 -1
- package/bld/library/replicad.js +2 -2
- package/bld/library/runtime.d.ts +68 -7
- package/bld/library/runtime.d.ts.map +1 -1
- package/bld/node/index.js +13 -3
- package/bld/node/replicad.js +4 -4
- package/bld/tooling/index.js +2 -2
- package/docs/api.md +134 -13
- package/docs/local-coordinates.md +2 -0
- package/docs/runtime.md +42 -2
- package/docs/topology.md +6 -0
- package/docs/values.md +9 -0
- package/package.json +1 -1
- package/src/library/extrude.ts +129 -2
- package/src/library/index.ts +6 -0
- package/src/library/inspect.ts +4 -2
- package/src/library/loft.ts +67 -2
- package/src/library/runtime.ts +599 -7
- package/bld/chunks/chunk-JBVIAMJ6.js.map +0 -7
- /package/bld/chunks/{chunk-CL3E2DE4.js.map → chunk-JF6QRICU.js.map} +0 -0
- /package/bld/chunks/{chunk-HZRQUHM2.js.map → chunk-L4V6X7RH.js.map} +0 -0
- /package/bld/chunks/{chunk-QK6ZET47.js.map → chunk-QJVTVAYM.js.map} +0 -0
package/docs/api.md
CHANGED
|
@@ -31,6 +31,7 @@ radius. For coils, `coilRadius` is measured to the wire centerline and
|
|
|
31
31
|
`pitch` is the advance per turn. Fractional turns are supported; the wire
|
|
32
32
|
must fit inside the coil radius and neighboring turns must remain separated.
|
|
33
33
|
Use [`@code3d/screws`](../../screws/docs/assembly.mdx) for standard fasteners and matching hole tools.
|
|
34
|
+
Use [`@code3d/gears`](../../gears/README.md) for nominal spur, helical and internal gear parts.
|
|
34
35
|
|
|
35
36
|
To build a solid beyond these primitives, import `definePrimitive` and
|
|
36
37
|
`replicad` from `@code3d/core/replicad`. See
|
|
@@ -40,19 +41,21 @@ To build a solid beyond these primitives, import `definePrimitive` and
|
|
|
40
41
|
|
|
41
42
|
Planar profiles lie in the local XZ plane with a +Y normal.
|
|
42
43
|
|
|
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
|
|
44
|
+
| Function | Meaning |
|
|
45
|
+
| ------------------------------------------ | ------------------------------------------------------------------------- |
|
|
46
|
+
| `circle(radius)` | Circular face |
|
|
47
|
+
| `ellipse(xRadius, zRadius)` | Elliptical face |
|
|
48
|
+
| `rectangle(x, z)` | Rectangular face |
|
|
49
|
+
| `regularPolygon(radius, sides, rotation?)` | Regular polygonal face |
|
|
50
|
+
| `point()` or `point([x, y, z])` | Vertex model |
|
|
51
|
+
| `line([x, y, z])` or `line(start, end)` | Straight edge |
|
|
52
|
+
| `arc(start, middle, end)` | Arc through three points |
|
|
53
|
+
| `bezier(points)` | Bézier curve |
|
|
54
|
+
| `spline(points)` | Interpolating spline |
|
|
55
|
+
| `loft(sections, options?)` | Solid through sections; optional curve spine |
|
|
56
|
+
| `extrude(faceOrFaces, distance)` | Solid extruded along one face's local normal |
|
|
57
|
+
| `revolve(profile, axis, config)` | Solid rotated about a straight directed axis, with optional axial advance |
|
|
58
|
+
| `sweep(profile, spine)` | Solid formed by carrying one face along an open curve |
|
|
56
59
|
|
|
57
60
|
See [local coordinates and placement](local-coordinates.md) for
|
|
58
61
|
the coordinate frame of a model, reference, or composition.
|
|
@@ -83,8 +86,126 @@ export const plate = rectangle(30, 20).extrude(3).fillet(0.5);
|
|
|
83
86
|
export const pin = extrude(circle(2), -10);
|
|
84
87
|
```
|
|
85
88
|
|
|
89
|
+
### Rotational solids
|
|
90
|
+
|
|
91
|
+
`revolve(profile, axis, config: RevolveConfig)` and
|
|
92
|
+
`profile.revolve(axis, config)` rotate one face about a straight directed axis.
|
|
93
|
+
`line(...)` can be passed directly; an existing straight edge or axis reference
|
|
94
|
+
also works.
|
|
95
|
+
`config.angle` is a required finite, non-zero angle in degrees. `config.advance`
|
|
96
|
+
is the signed total distance traveled along the directed axis during that angle;
|
|
97
|
+
it defaults to zero. A positive angle follows the axis's right-hand direction.
|
|
98
|
+
Reversing the axis reverses both the rotation sense and the direction of a positive
|
|
99
|
+
advance. With zero advance, the angle may cover at most one turn. With non-zero
|
|
100
|
+
advance, it may cover multiple turns to form a simple screw-motion solid.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import {circle, line, rectangle, revolve} from '@code3d/core';
|
|
104
|
+
|
|
105
|
+
const axis = line([0, -20, 0], [0, 20, 0]);
|
|
106
|
+
const ringSection = rectangle(4, 6).rotate(90, 0, 0).originOffset(-8, 0, 0);
|
|
107
|
+
export const ring = revolve(ringSection, axis, {angle: 360});
|
|
108
|
+
|
|
109
|
+
const wireSection = circle(1).rotate(90, 0, 0).originOffset(-8, 0, 0);
|
|
110
|
+
export const spring = wireSection.revolve(axis, {angle: 5 * 360, advance: 25});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The authoring signature requires `config`. While editing an incomplete call,
|
|
114
|
+
the App uses 360 degrees and zero advance so its parameter tool can add the
|
|
115
|
+
config object.
|
|
116
|
+
|
|
117
|
+
The result keeps the profile's local frame and is an ordinary `SolidModel`.
|
|
118
|
+
The axis participates in the same relation solve as the profile; its own model
|
|
119
|
+
placement is respected. A helical profile must have one outer boundary without
|
|
120
|
+
holes. Intersecting turns and profiles that cross the axis may fail to produce a
|
|
121
|
+
valid solid; leave clearance between turns and keep the profile off the axis.
|
|
122
|
+
For a multi-turn coil with round wire and automatic pitch clearance checks,
|
|
123
|
+
[`coil`](#solid-primitives) remains the shorter constructor.
|
|
124
|
+
|
|
125
|
+
### Path sweeps
|
|
126
|
+
|
|
127
|
+
`sweep(profile, spine)` and `profile.sweep(spine)` carry one planar face along a
|
|
128
|
+
continuous open `EdgeModel`, such as a line or Bézier curve. The face's local
|
|
129
|
+
origin must meet the path's start, and its normal must point along the starting
|
|
130
|
+
tangent. The operation respects the solved placement of both inputs and returns
|
|
131
|
+
a solid in the profile's local frame; it does not move or rotate the supplied
|
|
132
|
+
profile to fit the path.
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import {bezier, circle, sweep} from '@code3d/core';
|
|
136
|
+
|
|
137
|
+
const profile = circle(2);
|
|
138
|
+
const spine = bezier([
|
|
139
|
+
[0, 0, 0],
|
|
140
|
+
[0, 8, 0],
|
|
141
|
+
[5, 16, 0],
|
|
142
|
+
[5, 24, 0],
|
|
143
|
+
]);
|
|
144
|
+
export const bentRod = sweep(profile, spine);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The path must be open with a non-zero starting tangent. The output is an ordinary
|
|
148
|
+
`SolidModel` that supports subsequent Boolean and finishing operations. Very
|
|
149
|
+
tight bends or self-intersections may prevent the kernel from producing a valid
|
|
150
|
+
solid. One through hole in the profile is supported; profiles with multiple
|
|
151
|
+
holes currently need explicit contour correspondence. Use the
|
|
152
|
+
[App example](../../app/examples/operations/sweep.ts) to inspect
|
|
153
|
+
the profile, path and result.
|
|
154
|
+
|
|
86
155
|
## Measurements
|
|
87
156
|
|
|
157
|
+
### Length and area
|
|
158
|
+
|
|
159
|
+
Read `edge.length` or `line(...).length` for a finite edge's actual arc length.
|
|
160
|
+
A straight edge uses its endpoint distance; a closed edge uses its circumference.
|
|
161
|
+
Read `surface.area` or `faceModel.area` for a finite face's area, including curved
|
|
162
|
+
surfaces and trimming holes. `solid.area` / `solidModel.area` includes every
|
|
163
|
+
boundary face, including inner walls and cavity faces.
|
|
164
|
+
|
|
165
|
+
These properties return ordinary numbers in model units (area in square model
|
|
166
|
+
units). They are read-only. Rotation, origin changes, placement and reversing an
|
|
167
|
+
edge or flipping a face preserve the result. `scaled(s)` multiplies lengths by
|
|
168
|
+
`s` and areas by `s²`; exposed references use the scale of their actual geometry.
|
|
169
|
+
`LineAnchor` and `FaceAnchor` can describe infinite references and have no length
|
|
170
|
+
or area. Groups have no aggregate area.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import {line, rectangle, box} from '@code3d/core';
|
|
174
|
+
const length = line([3, 4, 0]).length; // 5
|
|
175
|
+
const area = rectangle(4, 6).area; // 24
|
|
176
|
+
const surfaceArea = box(2, 3, 4).area; // 52
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Select `.length` or `.area` in App to inspect the measured geometry and value.
|
|
180
|
+
Straight lengths use a dimension line; curves highlight their actual path with an
|
|
181
|
+
arc-length label. Area highlights the finite face or whole solid with an area
|
|
182
|
+
label. The read-only display does not create editable size constraints.
|
|
183
|
+
Try the [length example](../../app/examples/operations/length.ts) and
|
|
184
|
+
[area example](../../app/examples/operations/area.ts).
|
|
185
|
+
|
|
186
|
+
### Volume
|
|
187
|
+
|
|
188
|
+
Read `solid.volume` or `solidModel.volume` for the space occupied by the solid's
|
|
189
|
+
material. Holes and enclosed cavities are excluded. The result is a read-only
|
|
190
|
+
number in cubic model units. Rotation, origin changes and placement preserve it;
|
|
191
|
+
`scaled(s)` multiplies it by `s³`. Exposed solid references include the scale of
|
|
192
|
+
their actual geometry. Faces, edges, infinite references and groups have no volume
|
|
193
|
+
property.
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import {box, tube} from '@code3d/core';
|
|
197
|
+
const blockVolume = box(2, 3, 4).volume; // 24
|
|
198
|
+
const pipeVolume = tube(5, 3, 7).volume; // 112 * Math.PI
|
|
199
|
+
const enlargedVolume = box(2, 3, 4).scaled(2).volume; // 192
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Select `.volume` in App to inspect the whole solid with a volume label at its
|
|
203
|
+
volume centroid. This read-only display uses the getter's recorded result and
|
|
204
|
+
does not create an editable size constraint.
|
|
205
|
+
Try the [volume example](../../app/examples/operations/volume.ts).
|
|
206
|
+
|
|
207
|
+
### Distance between references
|
|
208
|
+
|
|
88
209
|
`distance(a, b, axis?)` returns a non-negative `number` from the models and
|
|
89
210
|
relations available at the call. It accepts vertex, edge, face and solid models,
|
|
90
211
|
non-empty groups, finite topology references, directional bounds, and point
|
|
@@ -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
|
@@ -79,9 +79,9 @@ shows the selected inputs and ambient operands without inventing a result.
|
|
|
79
79
|
These region inspectors use ordinary unlit materials with depth testing disabled,
|
|
80
80
|
so their colors remain visible through the translucent inputs.
|
|
81
81
|
|
|
82
|
-
Core uses this mechanism for distance measurements, relate calls and their
|
|
82
|
+
Core uses this mechanism for length/area/volume properties, distance measurements, relate calls and their
|
|
83
83
|
closures, on/align references, relative transformation stages, group children,
|
|
84
|
-
expose sources, Boolean operands
|
|
84
|
+
expose sources, Boolean operands, loft sections/spines and sweep profiles/spines, plus box and extrusion
|
|
85
85
|
dimensions. Selecting a normal constructor or Boolean function name
|
|
86
86
|
still previews its return value. Inspectors retain the original operation frame,
|
|
87
87
|
so a later relation or a different consumer cannot move its inspection.
|
|
@@ -108,6 +108,46 @@ the owner as `ambient` when the call fails or the reference collection is empty.
|
|
|
108
108
|
The owner therefore has the same background appearance before and after a
|
|
109
109
|
selection; missing or invalid IDs still produce their normal modeling errors.
|
|
110
110
|
|
|
111
|
+
### Getter inspection
|
|
112
|
+
|
|
113
|
+
A getter can declare `@code3d.inspect callback` in its JSDoc. A package can put
|
|
114
|
+
the same annotation on its public `readonly` property declaration when the
|
|
115
|
+
implementation getter is not present in its declarations. The callback receives
|
|
116
|
+
`[]`, with the actual receiver in `context.receiver`, the recorded property value
|
|
117
|
+
in `context.return`, and any `captureInspectData` payload in `context.data`.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import {
|
|
121
|
+
captureInspectData,
|
|
122
|
+
type InspectContext,
|
|
123
|
+
type Model,
|
|
124
|
+
} from '@code3d/core';
|
|
125
|
+
|
|
126
|
+
function inspectSize(_args: readonly [], context: InspectContext<number>) {
|
|
127
|
+
return {target: [context.data as Model]};
|
|
128
|
+
}
|
|
129
|
+
class Part {
|
|
130
|
+
constructor(readonly body: Model) {}
|
|
131
|
+
/** @code3d.inspect inspectSize */
|
|
132
|
+
get size() {
|
|
133
|
+
captureInspectData(this.body);
|
|
134
|
+
return 42;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Read `part.size` normally to record it. Selecting the property runs only its
|
|
140
|
+
inspector; it never runs the getter again. Each reached read keeps its own
|
|
141
|
+
receiver, return and data, even if several reads return the same number. Local
|
|
142
|
+
getters retain their declaration's lexical inspector binding; published callbacks
|
|
143
|
+
must be runtime exports of the package. Optional reads that short-circuit do not
|
|
144
|
+
invoke an inspector. A failed getter can still inspect its captured data, with
|
|
145
|
+
`context.return` undefined. Merely enumerating an object never invokes its getters.
|
|
146
|
+
|
|
147
|
+
Core's `.length`, `.area` and `.volume` use this mechanism. Passive `dimension` annotations
|
|
148
|
+
accept either `start`/`end`, alternative `candidates`, or `at: [x, y, z]` to show
|
|
149
|
+
only a value at an owner's local position. Labels do not add CAD geometry.
|
|
150
|
+
|
|
111
151
|
### Call data
|
|
112
152
|
|
|
113
153
|
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
|
@@ -63,6 +63,15 @@ finite geometry, nested occurrences, axis frames and source-order dependencies.
|
|
|
63
63
|
|
|
64
64
|
## Geometry measurements
|
|
65
65
|
|
|
66
|
+
Finite edges and edge models provide readonly `.length`; finite surfaces and
|
|
67
|
+
face models provide `.area`. Solids provide `.area` for their total boundary
|
|
68
|
+
surface, including inner walls, and `.volume` for material volume, excluding holes
|
|
69
|
+
and cavities. These are plain numbers, follow geometry scaling, and retain the
|
|
70
|
+
original value when later operations create a new model. Infinite axes/planes
|
|
71
|
+
and groups do not have these measurements. See [length and area](api.md#length-and-area)
|
|
72
|
+
and [volume](api.md#volume), and select these properties in App for a read-only
|
|
73
|
+
visual measurement.
|
|
74
|
+
|
|
66
75
|
`model.bounds(relativeTo?)` returns readonly `minimum`, `maximum` and `size`
|
|
67
76
|
XYZ vectors for tight finite geometry bounds. By default it uses the model's
|
|
68
77
|
own local frame. An explicit reference includes solved placement and nested
|
package/package.json
CHANGED
package/src/library/extrude.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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
|
+
}
|
package/src/library/index.ts
CHANGED
|
@@ -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,
|
|
@@ -71,7 +74,9 @@ export {
|
|
|
71
74
|
rectangle,
|
|
72
75
|
regularPolygon,
|
|
73
76
|
regularPrism,
|
|
77
|
+
revolve,
|
|
74
78
|
spline,
|
|
79
|
+
sweep,
|
|
75
80
|
sphere,
|
|
76
81
|
tube,
|
|
77
82
|
union,
|
|
@@ -117,6 +122,7 @@ export type {
|
|
|
117
122
|
NamedElements,
|
|
118
123
|
PlanarElements,
|
|
119
124
|
PointAnchor,
|
|
125
|
+
RevolveConfig,
|
|
120
126
|
Solid,
|
|
121
127
|
SolidModel,
|
|
122
128
|
SolidModificationCapabilities,
|
package/src/library/inspect.ts
CHANGED
|
@@ -49,14 +49,16 @@ export function recordInspectionCalls(
|
|
|
49
49
|
export type DimensionSegment = Readonly<{start: Vec3; end: Vec3}>;
|
|
50
50
|
|
|
51
51
|
type DimensionLines =
|
|
52
|
-
|
|
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
|
|
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;
|
package/src/library/loft.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
+
assembleWire,
|
|
2
3
|
cast,
|
|
3
4
|
getOC,
|
|
5
|
+
measureVolume,
|
|
4
6
|
type AnyShape,
|
|
7
|
+
type Edge,
|
|
5
8
|
type Face,
|
|
6
9
|
type Wire,
|
|
7
10
|
type Shape3D,
|
|
@@ -19,6 +22,66 @@ import {
|
|
|
19
22
|
type TopologyInput,
|
|
20
23
|
type ShapeTopology,
|
|
21
24
|
} from './topology.js';
|
|
25
|
+
import type {Vec3} from './spatial.js';
|
|
26
|
+
|
|
27
|
+
/** Sweep a face along one open edge, keeping the authored starting frame. */
|
|
28
|
+
export function sweepWithTopology(
|
|
29
|
+
profile: TopologyInput,
|
|
30
|
+
path: Edge,
|
|
31
|
+
normal: Vec3,
|
|
32
|
+
): Readonly<{shape: Shape3D; topology: ShapeTopology}> {
|
|
33
|
+
if (path.isClosed || !(path.length > 1e-9))
|
|
34
|
+
throw new Error('Sweep spine must be a non-degenerate open curve.');
|
|
35
|
+
const start = path.pointAt(0);
|
|
36
|
+
let tangent: ReturnType<Edge['tangentAt']> | undefined;
|
|
37
|
+
try {
|
|
38
|
+
tangent = path.tangentAt(0);
|
|
39
|
+
const position = start.toTuple();
|
|
40
|
+
const direction = tangent.toTuple();
|
|
41
|
+
const magnitude = Math.hypot(...direction);
|
|
42
|
+
if (!(magnitude > 1e-9))
|
|
43
|
+
throw new Error('Sweep spine must have a non-zero starting tangent.');
|
|
44
|
+
if (Math.hypot(...position) > 1e-6)
|
|
45
|
+
throw new Error(
|
|
46
|
+
'Sweep profile origin must coincide with the spine start.',
|
|
47
|
+
);
|
|
48
|
+
if (
|
|
49
|
+
(normal[0] * direction[0] +
|
|
50
|
+
normal[1] * direction[1] +
|
|
51
|
+
normal[2] * direction[2]) /
|
|
52
|
+
magnitude <
|
|
53
|
+
1 - 1e-6
|
|
54
|
+
)
|
|
55
|
+
throw new Error(
|
|
56
|
+
'Sweep profile normal must point along the spine start tangent.',
|
|
57
|
+
);
|
|
58
|
+
} finally {
|
|
59
|
+
tangent?.delete();
|
|
60
|
+
start.delete();
|
|
61
|
+
}
|
|
62
|
+
const spine = assembleWire([path]);
|
|
63
|
+
let result: Readonly<{shape: Shape3D; topology: ShapeTopology}> | undefined;
|
|
64
|
+
try {
|
|
65
|
+
result = loftWithTopology([profile], spine, false);
|
|
66
|
+
if (!(Math.abs(measureVolume(result.shape)) > 0))
|
|
67
|
+
throw new Error('Sweep did not produce a non-degenerate solid.');
|
|
68
|
+
const validation = new (getOC().BRepCheck_Analyzer)(result.shape.wrapped);
|
|
69
|
+
try {
|
|
70
|
+
if (!validation.IsValid())
|
|
71
|
+
throw new Error(
|
|
72
|
+
'Sweep produced an invalid solid. Adjust the profile or path to avoid tight bends and intersections.',
|
|
73
|
+
);
|
|
74
|
+
} finally {
|
|
75
|
+
validation.delete();
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
result?.shape.delete();
|
|
80
|
+
throw error;
|
|
81
|
+
} finally {
|
|
82
|
+
spine.delete();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
22
85
|
|
|
23
86
|
export function loftWithTopology(
|
|
24
87
|
sections: readonly TopologyInput[],
|
|
@@ -48,7 +111,7 @@ export function loftWithTopology(
|
|
|
48
111
|
throw new Error('Loft sections must have matching hole counts.');
|
|
49
112
|
if (holes[0].length > 1)
|
|
50
113
|
throw new Error(
|
|
51
|
-
'
|
|
114
|
+
'A swept or lofted profile currently supports at most one hole; multiple holes need explicit correspondence.',
|
|
52
115
|
);
|
|
53
116
|
outer = loftContoursWithTopology(sections, spine, ruled);
|
|
54
117
|
if (!holes[0].length) {
|
|
@@ -115,7 +178,9 @@ function loftContoursWithTopology(
|
|
|
115
178
|
}
|
|
116
179
|
result = castOwnedShape3D(builder.Shape());
|
|
117
180
|
caps[0] = castOwnedShape(builder.FirstShape()) as Face;
|
|
118
|
-
|
|
181
|
+
// A single profile has one inherited start cap; its end cap is new topology.
|
|
182
|
+
if (sections.length > 1)
|
|
183
|
+
caps[sections.length - 1] = castOwnedShape(builder.LastShape()) as Face;
|
|
119
184
|
const topology = transferShapeTopology(
|
|
120
185
|
sections,
|
|
121
186
|
result,
|