@code3d/core 0.0.1-alpha.0 → 0.0.1-alpha.2
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 +223 -786
- package/THIRD_PARTY.md +40 -0
- package/bld/THIRD_PARTY_NOTICES.txt +182 -0
- package/bld/chunks/chunk-5E6VXJU2.js +30 -0
- package/bld/chunks/chunk-5E6VXJU2.js.map +7 -0
- package/bld/chunks/chunk-63ZUQWQ2.js +19 -0
- package/bld/chunks/chunk-63ZUQWQ2.js.map +7 -0
- package/bld/chunks/chunk-AWYI64EE.js +31 -0
- package/bld/chunks/chunk-AWYI64EE.js.map +7 -0
- package/bld/chunks/chunk-F53RTQPT.js +24787 -0
- package/bld/chunks/chunk-F53RTQPT.js.map +7 -0
- package/bld/chunks/chunk-TJIN6S4R.js +1 -0
- package/bld/chunks/chunk-TJIN6S4R.js.map +7 -0
- package/bld/library/cached.d.ts +33 -0
- package/bld/library/cached.d.ts.map +1 -0
- package/bld/library/font.d.ts +39 -0
- package/bld/library/font.d.ts.map +1 -0
- package/bld/library/google-font.d.ts +14 -0
- package/bld/library/google-font.d.ts.map +1 -0
- package/bld/library/index.d.ts +8 -2
- package/bld/library/index.d.ts.map +1 -1
- package/bld/library/index.js +60 -3
- package/bld/library/index.js.map +7 -1
- package/bld/library/kernel-artifact-codec.d.ts.map +1 -1
- package/bld/library/kernel-cache.d.ts +17 -6
- package/bld/library/kernel-cache.d.ts.map +1 -1
- package/bld/library/material.d.ts +1 -1
- package/bld/library/material.d.ts.map +1 -1
- package/bld/library/open-cascade.d.ts +4 -0
- package/bld/library/open-cascade.d.ts.map +1 -0
- package/bld/library/replicad.d.ts +3 -2
- package/bld/library/replicad.d.ts.map +1 -1
- package/bld/library/replicad.js +10 -24
- package/bld/library/replicad.js.map +7 -1
- package/bld/library/retained-memory.d.ts.map +1 -1
- package/bld/library/runtime.d.ts +144 -80
- package/bld/library/runtime.d.ts.map +1 -1
- package/bld/library/sketch-solver.d.ts +1 -1
- package/bld/library/sketch-solver.d.ts.map +1 -1
- package/bld/library/sketch.d.ts +9 -1
- package/bld/library/sketch.d.ts.map +1 -1
- package/bld/library/text.d.ts +53 -0
- package/bld/library/text.d.ts.map +1 -0
- package/bld/library/three.js +3 -3
- package/bld/library/three.js.map +7 -1
- package/bld/node/index.d.ts.map +1 -1
- package/bld/node/index.js +62 -14
- package/bld/node/index.js.map +7 -1
- package/bld/node/replicad.js +13 -3
- package/bld/node/replicad.js.map +7 -1
- package/bld/tooling/index.d.ts +17 -15
- package/bld/tooling/index.d.ts.map +1 -1
- package/bld/tooling/index.js +147 -19
- package/bld/tooling/index.js.map +7 -1
- package/package.json +18 -6
- package/src/library/cached.ts +132 -0
- package/src/library/font.ts +186 -0
- package/src/library/google-font.ts +97 -0
- package/src/library/index.ts +8 -3
- package/src/library/kernel-artifact-codec.ts +129 -20
- package/src/library/kernel-cache.ts +111 -33
- package/src/library/material.ts +3 -4
- package/src/library/open-cascade.ts +13 -0
- package/src/library/replicad.ts +5 -5
- package/src/library/retained-memory.ts +6 -1
- package/src/library/runtime.ts +2049 -1728
- package/src/library/sketch-solver.ts +3 -1
- package/src/library/sketch.ts +81 -13
- package/src/library/text.ts +477 -0
- package/src/library/tsconfig.json +2 -1
- package/src/node/index.ts +14 -3
- package/src/node/tsconfig.json +2 -1
- package/src/tooling/index.ts +48 -46
- package/src/tooling/tsconfig.json +2 -1
- package/bld/library/alignment-geometry.js +0 -322
- package/bld/library/alignment-geometry.js.map +0 -1
- package/bld/library/bound-solver.js +0 -252
- package/bld/library/bound-solver.js.map +0 -1
- package/bld/library/extrude.js +0 -28
- package/bld/library/extrude.js.map +0 -1
- package/bld/library/kernel-artifact-codec.js +0 -148
- package/bld/library/kernel-artifact-codec.js.map +0 -1
- package/bld/library/kernel-cache.js +0 -245
- package/bld/library/kernel-cache.js.map +0 -1
- package/bld/library/kernel-shapes.js +0 -108
- package/bld/library/kernel-shapes.js.map +0 -1
- package/bld/library/loft.js +0 -161
- package/bld/library/loft.js.map +0 -1
- package/bld/library/material.js +0 -176
- package/bld/library/material.js.map +0 -1
- package/bld/library/model-color.js +0 -36
- package/bld/library/model-color.js.map +0 -1
- package/bld/library/open-cascade-error.js +0 -22
- package/bld/library/open-cascade-error.js.map +0 -1
- package/bld/library/relation-solver.js +0 -258
- package/bld/library/relation-solver.js.map +0 -1
- package/bld/library/retained-memory.js +0 -27
- package/bld/library/retained-memory.js.map +0 -1
- package/bld/library/runtime.js +0 -2954
- package/bld/library/runtime.js.map +0 -1
- package/bld/library/shell.js +0 -221
- package/bld/library/shell.js.map +0 -1
- package/bld/library/sketch-curve-intersections.js +0 -117
- package/bld/library/sketch-curve-intersections.js.map +0 -1
- package/bld/library/sketch-curves.js +0 -89
- package/bld/library/sketch-curves.js.map +0 -1
- package/bld/library/sketch-drag-rules.js +0 -522
- package/bld/library/sketch-drag-rules.js.map +0 -1
- package/bld/library/sketch-face.js +0 -72
- package/bld/library/sketch-face.js.map +0 -1
- package/bld/library/sketch-incidence.js +0 -114
- package/bld/library/sketch-incidence.js.map +0 -1
- package/bld/library/sketch-precision.js +0 -55
- package/bld/library/sketch-precision.js.map +0 -1
- package/bld/library/sketch-regions.js +0 -193
- package/bld/library/sketch-regions.js.map +0 -1
- package/bld/library/sketch-solver.js +0 -578
- package/bld/library/sketch-solver.js.map +0 -1
- package/bld/library/sketch.js +0 -650
- package/bld/library/sketch.js.map +0 -1
- package/bld/library/spatial.js +0 -195
- package/bld/library/spatial.js.map +0 -1
- package/bld/library/topology-id.js +0 -67
- package/bld/library/topology-id.js.map +0 -1
- package/bld/library/topology-inspection.js +0 -267
- package/bld/library/topology-inspection.js.map +0 -1
- package/bld/library/topology.js +0 -455
- package/bld/library/topology.js.map +0 -1
package/bld/library/runtime.js
DELETED
|
@@ -1,2954 +0,0 @@
|
|
|
1
|
-
import { captureModelMaterial } from './material.js';
|
|
2
|
-
import { inspectShapeTopology, } from './topology-inspection.js';
|
|
3
|
-
import { assembleWire, basicFaceExtrusion, BoundingBox, genericSweep, getOC, makeBSplineApproximation, makeBezierCurve, makeCircle, makeCylinder, makeFace, makeHelix, makeLine, makeSphere, makeThreePointArc, makeVertex, sketchCircle, sketchEllipse, sketchPolysides, sketchRectangle, Vector, } from 'replicad';
|
|
4
|
-
import { castOwnedShape, castOwnedShape3D, centeredBoxShape, shapeSubshapes, transformShape, } from './kernel-shapes.js';
|
|
5
|
-
import { addVectors, composeTransforms, frameFromYAxis, identityRigidTransform, invertTransform, rotateVector, origin, negateVector, quaternionAxisAngle, relativeTransform, rotation, rotationAround, translation, } from './spatial.js';
|
|
6
|
-
import { axisRotation, solveBodies, } from './relation-solver.js';
|
|
7
|
-
import { edgeGeometry, faceGeometry, transformGeometry, } from './alignment-geometry.js';
|
|
8
|
-
import { shellWithTopology } from './shell.js';
|
|
9
|
-
import { beginKernelOperationEvaluation, evaluateKernelOperation, kernelOperationKey, findKernelOperation, acceptKernelOperation, } from './kernel-cache.js';
|
|
10
|
-
import { loftWithTopology } from './loft.js';
|
|
11
|
-
import { estimateRetainedBytes } from './retained-memory.js';
|
|
12
|
-
import { extrudeWithTopology } from './extrude.js';
|
|
13
|
-
import { sketchRegionFace } from './sketch-face.js';
|
|
14
|
-
import { formatTopologyId } from './topology-id.js';
|
|
15
|
-
import { booleanWithTopology, chamferEdges, filletEdges, initialShapeTopology, preserveShapeTopology, resolveEdgeSelection, resolveTopologySelection, stableEdgeGroups, stableSurfaceGroups, stableVertexData, topologyEdgeDirections, topologySurfaceDirections, topologyVertexPoints, topologyChildren, withTopologyShape, } from './topology.js';
|
|
16
|
-
import { sketch } from './sketch.js';
|
|
17
|
-
import { encodeKernelArtifact, decodeKernelArtifact, } from './kernel-artifact-codec.js';
|
|
18
|
-
let activeRelate;
|
|
19
|
-
// Geometry and relations are persistent model values. Source provenance belongs
|
|
20
|
-
// only to the evaluation that observed them, including models cached by packages.
|
|
21
|
-
let valueTraces = new WeakMap();
|
|
22
|
-
let operationTraces = new WeakMap();
|
|
23
|
-
/**
|
|
24
|
-
* Start a fresh, serial tooling evaluation without invalidating model geometry.
|
|
25
|
-
* Finish in finally after snapshotting to retain this evaluation's kernel work.
|
|
26
|
-
* An optional cancellation check may throw before any kernel operation starts;
|
|
27
|
-
* complete results survive cancellation, and cleanup removes the check.
|
|
28
|
-
*/
|
|
29
|
-
export function beginModelEvaluation(checkCancelled) {
|
|
30
|
-
valueTraces = new WeakMap();
|
|
31
|
-
operationTraces = new WeakMap();
|
|
32
|
-
return beginKernelOperationEvaluation(checkCancelled);
|
|
33
|
-
}
|
|
34
|
-
function valueTrace(value) {
|
|
35
|
-
let trace = valueTraces.get(value);
|
|
36
|
-
if (!trace) {
|
|
37
|
-
trace = { sourceRefs: [], parameters: [] };
|
|
38
|
-
valueTraces.set(value, trace);
|
|
39
|
-
}
|
|
40
|
-
return trace;
|
|
41
|
-
}
|
|
42
|
-
function transformSolveContext(context, transform) {
|
|
43
|
-
return {
|
|
44
|
-
poses: new Map([...context.poses].map(([model, pose]) => [
|
|
45
|
-
model,
|
|
46
|
-
composeTransforms(transform, pose),
|
|
47
|
-
])),
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
const unitScale = [1, 1, 1];
|
|
51
|
-
let nextNodeId = 1;
|
|
52
|
-
let nextConstraintId = 1;
|
|
53
|
-
let nextOperationId = 1;
|
|
54
|
-
const combineModels = Symbol('combineModels');
|
|
55
|
-
const loftModels = Symbol('loftModels');
|
|
56
|
-
const modelElementKinds = {
|
|
57
|
-
solid: 'frame',
|
|
58
|
-
face: 'face',
|
|
59
|
-
edge: 'line',
|
|
60
|
-
vertex: 'point',
|
|
61
|
-
group: 'frame',
|
|
62
|
-
};
|
|
63
|
-
const defaultModelNames = {
|
|
64
|
-
solid: 'Solid',
|
|
65
|
-
face: 'Face',
|
|
66
|
-
edge: 'Edge',
|
|
67
|
-
vertex: 'Vertex',
|
|
68
|
-
group: 'Group',
|
|
69
|
-
};
|
|
70
|
-
const anchorKind = Symbol('anchorKind');
|
|
71
|
-
const anchorReferenceValue = Symbol('anchorReference');
|
|
72
|
-
const modelFamily = Symbol('modelFamily');
|
|
73
|
-
const modelNamedElements = Symbol('modelNamedElements');
|
|
74
|
-
const boundKind = Symbol('boundKind');
|
|
75
|
-
class ModelAnchor {
|
|
76
|
-
elementKind;
|
|
77
|
-
[anchorReferenceValue];
|
|
78
|
-
constructor(reference) {
|
|
79
|
-
this[anchorReferenceValue] = reference;
|
|
80
|
-
this.elementKind = this[anchorReferenceValue].kind;
|
|
81
|
-
for (const [name, member] of Object.entries(reference.members ?? {})) {
|
|
82
|
-
const pointMember = ['center', 'start', 'midpoint', 'end'].includes(name);
|
|
83
|
-
if (name in this &&
|
|
84
|
-
!pointMember &&
|
|
85
|
-
!(name === 'id' && reference.topology?.selection.kind === 'solid')) {
|
|
86
|
-
throw new Error(`The exposed member ${name} conflicts with the topology API.`);
|
|
87
|
-
}
|
|
88
|
-
Object.defineProperty(this, name, {
|
|
89
|
-
value: modelAnchor(reference.model, `${reference.name}.${name}`, member),
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
get up() {
|
|
94
|
-
return directionalBound(this[anchorReferenceValue], 'up');
|
|
95
|
-
}
|
|
96
|
-
get down() {
|
|
97
|
-
return directionalBound(this[anchorReferenceValue], 'down');
|
|
98
|
-
}
|
|
99
|
-
get left() {
|
|
100
|
-
return directionalBound(this[anchorReferenceValue], 'left');
|
|
101
|
-
}
|
|
102
|
-
get right() {
|
|
103
|
-
return directionalBound(this[anchorReferenceValue], 'right');
|
|
104
|
-
}
|
|
105
|
-
get front() {
|
|
106
|
-
return directionalBound(this[anchorReferenceValue], 'front');
|
|
107
|
-
}
|
|
108
|
-
get back() {
|
|
109
|
-
return directionalBound(this[anchorReferenceValue], 'back');
|
|
110
|
-
}
|
|
111
|
-
flip() {
|
|
112
|
-
const ref = this[anchorReferenceValue];
|
|
113
|
-
const facing = -(ref.bound?.facing ?? ref.facing ?? 1);
|
|
114
|
-
return modelAnchor(ref.model, ref.name, {
|
|
115
|
-
...ref,
|
|
116
|
-
facing,
|
|
117
|
-
bound: ref.bound ? { ...ref.bound, facing } : undefined,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
reverse() {
|
|
121
|
-
const ref = this[anchorReferenceValue];
|
|
122
|
-
return modelAnchor(ref.model, ref.name, {
|
|
123
|
-
...ref,
|
|
124
|
-
direction: -(ref.direction ?? 1),
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
on(target) {
|
|
128
|
-
return Constraint.create(this[anchorReferenceValue], boundReference(target));
|
|
129
|
-
}
|
|
130
|
-
align(target) {
|
|
131
|
-
return Constraint.create(this[anchorReferenceValue], anchorReference(target), 'align');
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
class ModelTopologyElement extends ModelAnchor {
|
|
135
|
-
get kind() {
|
|
136
|
-
return this.#topology.selection.kind;
|
|
137
|
-
}
|
|
138
|
-
get id() {
|
|
139
|
-
const selection = this.#topology.selection;
|
|
140
|
-
return selection.kind === 'solid' ? undefined : selection.id;
|
|
141
|
-
}
|
|
142
|
-
get #topology() {
|
|
143
|
-
return this[anchorReferenceValue].topology;
|
|
144
|
-
}
|
|
145
|
-
get center() {
|
|
146
|
-
const geometry = this.#topology.source[modelGeometry]();
|
|
147
|
-
return this.#pointAnchor('center', topologyCenter(geometry, this.#topology.selection));
|
|
148
|
-
}
|
|
149
|
-
get start() {
|
|
150
|
-
return this.#curvePoint('start', 0);
|
|
151
|
-
}
|
|
152
|
-
get midpoint() {
|
|
153
|
-
return this.#curvePoint('midpoint', 0.5);
|
|
154
|
-
}
|
|
155
|
-
get end() {
|
|
156
|
-
return this.#curvePoint('end', 1);
|
|
157
|
-
}
|
|
158
|
-
#curvePoint(name, parameter) {
|
|
159
|
-
const geometry = this.#topology.source[modelGeometry]().value;
|
|
160
|
-
const basis = geometry.referenceBasis;
|
|
161
|
-
const element = withTopologyShape(basis?.shape ?? geometry.shape, basis?.topology ?? geometry.topology, this.#topology.selection, shape => curveAnchor(shape, parameter));
|
|
162
|
-
return this.#pointAnchor(name, basis ? topologyTransform(basis, element.transform) : element.transform);
|
|
163
|
-
}
|
|
164
|
-
#pointAnchor(name, transform) {
|
|
165
|
-
return modelAnchor(this[anchorReferenceValue].model, `${this[anchorReferenceValue].name}.${name}`, {
|
|
166
|
-
kind: 'point',
|
|
167
|
-
transform: topologyTransform(this.#topology, transform),
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
vertex(id) {
|
|
171
|
-
return this.vertices([id])[0];
|
|
172
|
-
}
|
|
173
|
-
vertices(ids) {
|
|
174
|
-
return this.#select('vertex', ids);
|
|
175
|
-
}
|
|
176
|
-
edge(id) {
|
|
177
|
-
return this.edges([id])[0];
|
|
178
|
-
}
|
|
179
|
-
edges(ids) {
|
|
180
|
-
return this.#select('edge', ids);
|
|
181
|
-
}
|
|
182
|
-
surface(id) {
|
|
183
|
-
return this.surfaces([id])[0];
|
|
184
|
-
}
|
|
185
|
-
surfaces(ids) {
|
|
186
|
-
return this.#select('surface', ids);
|
|
187
|
-
}
|
|
188
|
-
#select(kind, ids) {
|
|
189
|
-
const geometry = this.#topology.source[modelGeometry]().value;
|
|
190
|
-
const selected = topologyChildren(geometry.shape, geometry.topology, this.#topology.selection, kind, ids);
|
|
191
|
-
return topologyReferences(this[anchorReferenceValue].model, `${this[anchorReferenceValue].name}.`, this.#topology, kind, selected);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
const topologyElementKinds = {
|
|
195
|
-
solid: 'frame',
|
|
196
|
-
vertex: 'point',
|
|
197
|
-
edge: 'line',
|
|
198
|
-
surface: 'face',
|
|
199
|
-
};
|
|
200
|
-
export function modelElementReference(value) {
|
|
201
|
-
if (!(value instanceof ModelAnchor))
|
|
202
|
-
return undefined;
|
|
203
|
-
return {
|
|
204
|
-
model: value[anchorReferenceValue].model,
|
|
205
|
-
name: value[anchorReferenceValue].name,
|
|
206
|
-
kind: value[anchorReferenceValue].kind,
|
|
207
|
-
transform: toTransform(value[anchorReferenceValue].transform),
|
|
208
|
-
bound: value[anchorReferenceValue].bound,
|
|
209
|
-
facing: value[anchorReferenceValue].facing ?? 1,
|
|
210
|
-
direction: value[anchorReferenceValue].direction ?? 1,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
export function modelTopologyReference(value) {
|
|
214
|
-
if (!(value instanceof ModelTopologyElement))
|
|
215
|
-
return undefined;
|
|
216
|
-
const topology = value[anchorReferenceValue].topology;
|
|
217
|
-
return {
|
|
218
|
-
model: value[anchorReferenceValue].model,
|
|
219
|
-
geometry: topology.source,
|
|
220
|
-
transform: {
|
|
221
|
-
...topology.transform,
|
|
222
|
-
scale: [topology.scale, topology.scale, topology.scale],
|
|
223
|
-
},
|
|
224
|
-
...topology.selection,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
export function modelTopologyIds(value, kind) {
|
|
228
|
-
const topology = value instanceof ModelTopologyElement
|
|
229
|
-
? value[anchorReferenceValue].topology
|
|
230
|
-
: undefined;
|
|
231
|
-
const source = isModelObject(value)
|
|
232
|
-
? value
|
|
233
|
-
: value instanceof ConstraintExpression
|
|
234
|
-
? value.self()
|
|
235
|
-
: topology?.source;
|
|
236
|
-
const geometry = source?.[modelGeometry]()?.value;
|
|
237
|
-
if (!geometry)
|
|
238
|
-
return undefined;
|
|
239
|
-
return topologyChildren(geometry.shape, geometry.topology, topology?.selection ?? { kind: 'solid' }, kind);
|
|
240
|
-
}
|
|
241
|
-
/** Traceable relation values, including unfinished pivot and axis selections. */
|
|
242
|
-
export class ConstraintExpression {
|
|
243
|
-
kind;
|
|
244
|
-
source;
|
|
245
|
-
target;
|
|
246
|
-
displacement;
|
|
247
|
-
rotations;
|
|
248
|
-
context;
|
|
249
|
-
constraintId;
|
|
250
|
-
get sourceRefs() {
|
|
251
|
-
return valueTrace(this).sourceRefs;
|
|
252
|
-
}
|
|
253
|
-
get parameters() {
|
|
254
|
-
return valueTrace(this).parameters;
|
|
255
|
-
}
|
|
256
|
-
spatialOperation;
|
|
257
|
-
constructor(kind, source, target, displacement, rotations, context, constraintId, previous) {
|
|
258
|
-
this.kind = kind;
|
|
259
|
-
this.source = source;
|
|
260
|
-
this.target = target;
|
|
261
|
-
this.displacement = displacement;
|
|
262
|
-
this.rotations = rotations;
|
|
263
|
-
this.context = context;
|
|
264
|
-
this.constraintId = constraintId;
|
|
265
|
-
valueTraces.set(this, {
|
|
266
|
-
sourceRefs: [...(previous ? valueTrace(previous).sourceRefs : [])],
|
|
267
|
-
parameters: [...(previous ? valueTrace(previous).parameters : [])],
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
/** @internal */
|
|
271
|
-
attachSource(sourceRef) {
|
|
272
|
-
const refs = this.sourceRefs;
|
|
273
|
-
if (!refs.some(ref => ref.file === sourceRef.file &&
|
|
274
|
-
ref.start === sourceRef.start &&
|
|
275
|
-
ref.end === sourceRef.end))
|
|
276
|
-
refs.push(sourceRef);
|
|
277
|
-
}
|
|
278
|
-
/** @internal */
|
|
279
|
-
attachParameters(parameters) {
|
|
280
|
-
appendUniqueParameters(this.parameters, parameters);
|
|
281
|
-
}
|
|
282
|
-
/** @internal */
|
|
283
|
-
self() {
|
|
284
|
-
return this.context?.self;
|
|
285
|
-
}
|
|
286
|
-
/** @internal */
|
|
287
|
-
traceReference() {
|
|
288
|
-
const rebind = (model) => model === this.context?.original ? this.context.self : model;
|
|
289
|
-
return {
|
|
290
|
-
constraintId: this.constraintId,
|
|
291
|
-
source: rebind(this.source.model),
|
|
292
|
-
target: rebind(this.target.model),
|
|
293
|
-
self: this.context?.self,
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
/** @internal */
|
|
297
|
-
storeFor(model, original) {
|
|
298
|
-
const isSelf = (candidate) => candidate === model || candidate === original;
|
|
299
|
-
if (!isSelf(this.source.model) && !isSelf(this.target.model))
|
|
300
|
-
throw new Error('The constraint returned by relate() must involve self or the original receiver.');
|
|
301
|
-
const bind = (reference) => ({
|
|
302
|
-
...reference,
|
|
303
|
-
model: isSelf(reference.model) ? undefined : reference.model,
|
|
304
|
-
});
|
|
305
|
-
const stored = {
|
|
306
|
-
id: this.constraintId,
|
|
307
|
-
kind: this.kind,
|
|
308
|
-
source: bind(this.source),
|
|
309
|
-
target: bind(this.target),
|
|
310
|
-
offset: this.displacement,
|
|
311
|
-
rotations: this.rotations.map(action => ({
|
|
312
|
-
...action,
|
|
313
|
-
pivot: action.pivot.kind === 'around'
|
|
314
|
-
? { ...action.pivot, axis: bind(action.pivot.axis) }
|
|
315
|
-
: action.pivot,
|
|
316
|
-
})),
|
|
317
|
-
};
|
|
318
|
-
valueTraces.set(stored, {
|
|
319
|
-
sourceRefs: [...this.sourceRefs],
|
|
320
|
-
parameters: [...this.parameters],
|
|
321
|
-
});
|
|
322
|
-
return stored;
|
|
323
|
-
}
|
|
324
|
-
/** @internal */
|
|
325
|
-
preview() {
|
|
326
|
-
if (!this.context)
|
|
327
|
-
return undefined;
|
|
328
|
-
const pivot = this.spatialOperation?.pivot;
|
|
329
|
-
const selection = pivot?.kind === 'around'
|
|
330
|
-
? {
|
|
331
|
-
...this.spatialOperation,
|
|
332
|
-
pivot: {
|
|
333
|
-
...pivot,
|
|
334
|
-
axis: {
|
|
335
|
-
...pivot.axis,
|
|
336
|
-
model: pivot.axis.model === this.context.self ||
|
|
337
|
-
pivot.axis.model === this.context.original
|
|
338
|
-
? undefined
|
|
339
|
-
: pivot.axis.model,
|
|
340
|
-
},
|
|
341
|
-
},
|
|
342
|
-
}
|
|
343
|
-
: this.spatialOperation;
|
|
344
|
-
return this.context.original[relationPreview](this.context.self.nodeId, this.storeFor(this.context.self, this.context.original), selection);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
export class Constraint extends ConstraintExpression {
|
|
348
|
-
constructor(kind, source, target, displacement = undefined, rotations = [], context = activeRelate, constraintId = `constraint-${nextConstraintId++}`, previous, spatialOperation) {
|
|
349
|
-
super(kind, source, target, displacement, rotations, context, constraintId, previous);
|
|
350
|
-
this.spatialOperation = spatialOperation;
|
|
351
|
-
}
|
|
352
|
-
/** @internal */
|
|
353
|
-
static create(source, target, kind = 'on') {
|
|
354
|
-
if (kind === 'on')
|
|
355
|
-
source.model[referenceBounds](source, identityRigidTransform);
|
|
356
|
-
else if (source.kind === 'frame' || target.kind === 'frame')
|
|
357
|
-
throw new Error('align() requires points, curves, or surfaces; select geometry on a solid or group.');
|
|
358
|
-
return new Constraint(kind, source, target);
|
|
359
|
-
}
|
|
360
|
-
/**
|
|
361
|
-
* In the target reference axes, on() pins matching bound centers; align()
|
|
362
|
-
* translates self after alignment, retaining the relation's free modes.
|
|
363
|
-
* Explicit zero pins tangential coordinates for on() only.
|
|
364
|
-
* @code3d.param x {kind: 'length', label: 'ΔX'}
|
|
365
|
-
* @code3d.param y {kind: 'length', label: 'ΔY'}
|
|
366
|
-
* @code3d.param z {kind: 'length', label: 'ΔZ'}
|
|
367
|
-
*/
|
|
368
|
-
offset(x, y, z) {
|
|
369
|
-
assertFiniteVector('offset', [x, y, z]);
|
|
370
|
-
return new Constraint(this.kind, this.source, this.target, addVectors(this.displacement ?? origin, [x, y, z]), this.rotations, this.context, this.constraintId, this);
|
|
371
|
-
}
|
|
372
|
-
/**
|
|
373
|
-
* Select this rotation's pivot in self's local coordinates.
|
|
374
|
-
* @code3d.param x {kind: 'length', label: 'Pivot X'}
|
|
375
|
-
* @code3d.param y {kind: 'length', label: 'Pivot Y'}
|
|
376
|
-
* @code3d.param z {kind: 'length', label: 'Pivot Z'}
|
|
377
|
-
*/
|
|
378
|
-
pivot([x, y, z]) {
|
|
379
|
-
assertFiniteVector('pivot', [x, y, z]);
|
|
380
|
-
return new ConstraintPivotChain(this, { kind: 'pivot', point: [x, y, z] });
|
|
381
|
-
}
|
|
382
|
-
/** @code3d.param id {kind: 'vertex', label: 'Pivot vertex'} */
|
|
383
|
-
pivotVertex(id) {
|
|
384
|
-
return new ConstraintPivotChain(this, { kind: 'pivotVertex', id });
|
|
385
|
-
}
|
|
386
|
-
/** Select a positioned axis in the composition. */
|
|
387
|
-
around(axis) {
|
|
388
|
-
const reference = anchorReference(axis);
|
|
389
|
-
if (reference.kind !== 'line')
|
|
390
|
-
throw new Error('around() requires an axis or straight edge.');
|
|
391
|
-
const geometry = reference.topology?.source[modelGeometry]()?.value ??
|
|
392
|
-
(reference.whole ? reference.model[modelGeometry]()?.value : undefined);
|
|
393
|
-
if (geometry) {
|
|
394
|
-
const straight = reference.topology
|
|
395
|
-
? withTopologyShape(geometry.shape, geometry.topology, reference.topology.selection, shape => shape.geomType === 'LINE')
|
|
396
|
-
: geometry.shape.geomType === 'LINE';
|
|
397
|
-
if (!straight)
|
|
398
|
-
throw new Error('around() requires a straight axis; curved edges do not define one rotation axis.');
|
|
399
|
-
}
|
|
400
|
-
return new ConstraintAroundChain(this, { kind: 'around', axis: reference });
|
|
401
|
-
}
|
|
402
|
-
/**
|
|
403
|
-
* Rotate about self's origin and local X, Y, then Z axes, in degrees.
|
|
404
|
-
* @code3d.param x {kind: 'angle', label: 'Rotate X'}
|
|
405
|
-
* @code3d.param y {kind: 'angle', label: 'Rotate Y'}
|
|
406
|
-
* @code3d.param z {kind: 'angle', label: 'Rotate Z'}
|
|
407
|
-
*/
|
|
408
|
-
rotate(x, y, z) {
|
|
409
|
-
return this.withRotation({ kind: 'pivot', point: origin }, [x, y, z], this);
|
|
410
|
-
}
|
|
411
|
-
/** @internal */
|
|
412
|
-
withRotation(pivot, angles, previous) {
|
|
413
|
-
assertFiniteVector('rotate', typeof angles === 'number' ? [angles, 0, 0] : angles);
|
|
414
|
-
return new Constraint(this.kind, this.source, this.target, this.displacement, [...this.rotations, { pivot, angles }], this.context, this.constraintId, previous, { kind: 'rotate', pivot });
|
|
415
|
-
}
|
|
416
|
-
/** @internal */
|
|
417
|
-
chainArguments() {
|
|
418
|
-
return [
|
|
419
|
-
this.kind,
|
|
420
|
-
this.source,
|
|
421
|
-
this.target,
|
|
422
|
-
this.displacement,
|
|
423
|
-
this.rotations,
|
|
424
|
-
this.context,
|
|
425
|
-
this.constraintId,
|
|
426
|
-
this,
|
|
427
|
-
];
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
export class ConstraintPivotChain extends ConstraintExpression {
|
|
431
|
-
constraint;
|
|
432
|
-
selection;
|
|
433
|
-
/** @internal */
|
|
434
|
-
constructor(constraint, selection) {
|
|
435
|
-
super(...constraint.chainArguments());
|
|
436
|
-
this.constraint = constraint;
|
|
437
|
-
this.selection = selection;
|
|
438
|
-
this.spatialOperation = { kind: selection.kind, pivot: selection };
|
|
439
|
-
}
|
|
440
|
-
/**
|
|
441
|
-
* @code3d.param x {kind: 'angle', label: 'Rotate X'}
|
|
442
|
-
* @code3d.param y {kind: 'angle', label: 'Rotate Y'}
|
|
443
|
-
* @code3d.param z {kind: 'angle', label: 'Rotate Z'}
|
|
444
|
-
*/
|
|
445
|
-
rotate(x, y, z) {
|
|
446
|
-
return this.constraint.withRotation(this.selection, [x, y, z], this);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
export class ConstraintAroundChain extends ConstraintExpression {
|
|
450
|
-
constraint;
|
|
451
|
-
selection;
|
|
452
|
-
/** @internal */
|
|
453
|
-
constructor(constraint, selection) {
|
|
454
|
-
super(...constraint.chainArguments());
|
|
455
|
-
this.constraint = constraint;
|
|
456
|
-
this.selection = selection;
|
|
457
|
-
this.spatialOperation = { kind: selection.kind, pivot: selection };
|
|
458
|
-
}
|
|
459
|
-
/** @code3d.param angle {kind: 'angle', label: 'Rotate'} */
|
|
460
|
-
rotate(angle) {
|
|
461
|
-
return this.constraint.withRotation(this.selection, angle, this);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
const modelGeometry = Symbol('modelGeometry');
|
|
465
|
-
const referenceBounds = Symbol('referenceBounds');
|
|
466
|
-
const referenceBoundsParts = Symbol('referenceBoundsParts');
|
|
467
|
-
const modelSnapshotQueries = Symbol('modelSnapshotQueries');
|
|
468
|
-
const relationPreview = Symbol('relationPreview');
|
|
469
|
-
export class ModelObject {
|
|
470
|
-
/** @internal */
|
|
471
|
-
elementKind;
|
|
472
|
-
/** @internal */
|
|
473
|
-
nodeId;
|
|
474
|
-
/** @internal */
|
|
475
|
-
kind;
|
|
476
|
-
/** @internal */
|
|
477
|
-
name;
|
|
478
|
-
/** @internal */
|
|
479
|
-
materialSnapshot;
|
|
480
|
-
/** @internal */
|
|
481
|
-
children;
|
|
482
|
-
/** @internal */
|
|
483
|
-
get sourceRefs() {
|
|
484
|
-
return valueTrace(this).sourceRefs;
|
|
485
|
-
}
|
|
486
|
-
/** @internal */
|
|
487
|
-
get parameters() {
|
|
488
|
-
return valueTrace(this).parameters;
|
|
489
|
-
}
|
|
490
|
-
geometry;
|
|
491
|
-
assembly;
|
|
492
|
-
meshTolerance;
|
|
493
|
-
geometryAnchor;
|
|
494
|
-
elements;
|
|
495
|
-
constraints;
|
|
496
|
-
operation;
|
|
497
|
-
/** @internal */
|
|
498
|
-
[modelGeometry]() {
|
|
499
|
-
return this.geometry;
|
|
500
|
-
}
|
|
501
|
-
constructor(init) {
|
|
502
|
-
if (init.kind !== 'group' && !init.geometry) {
|
|
503
|
-
throw new Error('A geometric model object must contain an OpenCascade shape.');
|
|
504
|
-
}
|
|
505
|
-
this.nodeId = init.nodeId ?? `node-${nextNodeId++}`;
|
|
506
|
-
this.kind = init.kind;
|
|
507
|
-
this.elementKind = modelElementKinds[init.kind];
|
|
508
|
-
this.geometry = init.geometry;
|
|
509
|
-
this.geometryAnchor = init.geometryAnchor ?? {
|
|
510
|
-
kind: this.elementKind,
|
|
511
|
-
transform: identityRigidTransform,
|
|
512
|
-
};
|
|
513
|
-
this.meshTolerance = init.meshTolerance ?? 0.2;
|
|
514
|
-
this.name = init.name ?? defaultModelNames[init.kind];
|
|
515
|
-
this.materialSnapshot = init.material;
|
|
516
|
-
this.children = init.children ?? [];
|
|
517
|
-
this.assembly =
|
|
518
|
-
init.assembly ??
|
|
519
|
-
(this.kind === 'group'
|
|
520
|
-
? ModelObject.createAssembly(this.children)
|
|
521
|
-
: undefined);
|
|
522
|
-
this.constraints = [...(init.constraints ?? [])];
|
|
523
|
-
valueTraces.set(this, {
|
|
524
|
-
sourceRefs: [...(init.sourceRefs ?? [])],
|
|
525
|
-
parameters: [...(init.parameters ?? [])],
|
|
526
|
-
});
|
|
527
|
-
this.operation = init.operation;
|
|
528
|
-
const elements = init.elements ??
|
|
529
|
-
(this.kind === 'solid' && this.geometry
|
|
530
|
-
? solidElements(this.geometry.value.localBounds)
|
|
531
|
-
: {});
|
|
532
|
-
this.elements = this.geometry
|
|
533
|
-
? {
|
|
534
|
-
center: {
|
|
535
|
-
kind: 'point',
|
|
536
|
-
transform: translation(boundsCenter(this.geometry.value.localBounds)),
|
|
537
|
-
},
|
|
538
|
-
...elements,
|
|
539
|
-
}
|
|
540
|
-
: elements;
|
|
541
|
-
for (const [name, element] of Object.entries(this.elements)) {
|
|
542
|
-
if (name in this) {
|
|
543
|
-
throw new Error(`The element name ${name} conflicts with the model API.`);
|
|
544
|
-
}
|
|
545
|
-
Object.defineProperty(this, name, {
|
|
546
|
-
value: modelAnchor(this, name, element),
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
/** @internal */
|
|
551
|
-
static create(init) {
|
|
552
|
-
return new ModelObject(init);
|
|
553
|
-
}
|
|
554
|
-
get up() {
|
|
555
|
-
return directionalBound(this.relationAnchorReference(), 'up');
|
|
556
|
-
}
|
|
557
|
-
get down() {
|
|
558
|
-
return directionalBound(this.relationAnchorReference(), 'down');
|
|
559
|
-
}
|
|
560
|
-
get left() {
|
|
561
|
-
return directionalBound(this.relationAnchorReference(), 'left');
|
|
562
|
-
}
|
|
563
|
-
get right() {
|
|
564
|
-
return directionalBound(this.relationAnchorReference(), 'right');
|
|
565
|
-
}
|
|
566
|
-
get front() {
|
|
567
|
-
return directionalBound(this.relationAnchorReference(), 'front');
|
|
568
|
-
}
|
|
569
|
-
get back() {
|
|
570
|
-
return directionalBound(this.relationAnchorReference(), 'back');
|
|
571
|
-
}
|
|
572
|
-
on(target) {
|
|
573
|
-
return Constraint.create(this.relationAnchorReference(), boundReference(target));
|
|
574
|
-
}
|
|
575
|
-
/** @internal */
|
|
576
|
-
relationAnchorReference() {
|
|
577
|
-
return { model: this, name: 'geometry', ...this.geometryAnchor, whole: true };
|
|
578
|
-
}
|
|
579
|
-
align(target) {
|
|
580
|
-
return Constraint.create(this.relationAnchorReference(), anchorReference(target), 'align');
|
|
581
|
-
}
|
|
582
|
-
reverse() {
|
|
583
|
-
return this.edges()[0].reverse();
|
|
584
|
-
}
|
|
585
|
-
flip() {
|
|
586
|
-
return this.surfaces()[0].flip();
|
|
587
|
-
}
|
|
588
|
-
relate(build) {
|
|
589
|
-
const operation = storedOperation('relate', [
|
|
590
|
-
{ model: this, role: 'source', index: 0 },
|
|
591
|
-
]);
|
|
592
|
-
const related = this.copy({}, operation);
|
|
593
|
-
const previous = activeRelate;
|
|
594
|
-
activeRelate = { self: related, original: this };
|
|
595
|
-
let built;
|
|
596
|
-
try {
|
|
597
|
-
built = build(related);
|
|
598
|
-
}
|
|
599
|
-
finally {
|
|
600
|
-
activeRelate = previous;
|
|
601
|
-
}
|
|
602
|
-
const constraints = Array.isArray(built)
|
|
603
|
-
? built
|
|
604
|
-
: [built];
|
|
605
|
-
const stored = constraints.map(constraint => {
|
|
606
|
-
if (!(constraint instanceof Constraint))
|
|
607
|
-
throw new Error('relate() requires a completed Constraint; finish pivot/around with rotate().');
|
|
608
|
-
return constraint.storeFor(related, this);
|
|
609
|
-
});
|
|
610
|
-
related.constraints.push(...stored);
|
|
611
|
-
operation.inputs.push(...uniqueModels(stored.flatMap(constraintReferences)).map((model, index) => ({
|
|
612
|
-
model,
|
|
613
|
-
role: 'reference',
|
|
614
|
-
index,
|
|
615
|
-
})));
|
|
616
|
-
return related;
|
|
617
|
-
}
|
|
618
|
-
expose(sources) {
|
|
619
|
-
const entries = Object.entries(sources);
|
|
620
|
-
const elements = this.localElements(entries.map(([, source]) => source));
|
|
621
|
-
const exposed = Object.fromEntries(entries.map(([name], index) => [name, elements[index]]));
|
|
622
|
-
const references = entries.map(([, source]) => anchorReference(source).model);
|
|
623
|
-
const operation = storedOperation('expose', [
|
|
624
|
-
{ model: this, role: 'source', index: 0 },
|
|
625
|
-
...uniqueModels(references)
|
|
626
|
-
.filter(model => model !== this)
|
|
627
|
-
.map((model, index) => ({
|
|
628
|
-
model,
|
|
629
|
-
role: 'reference',
|
|
630
|
-
index,
|
|
631
|
-
})),
|
|
632
|
-
]);
|
|
633
|
-
return this.copy({ elements: { ...this.elements, ...exposed } }, operation);
|
|
634
|
-
}
|
|
635
|
-
/** Resolve both own and occurrence references into this model's local frame. */
|
|
636
|
-
localElements(sources) {
|
|
637
|
-
const members = this.memberPoses();
|
|
638
|
-
const external = sources
|
|
639
|
-
.map(source => anchorReference(source).model)
|
|
640
|
-
.filter(model => !members.has(model));
|
|
641
|
-
const context = external.length
|
|
642
|
-
? ModelObject.createSolveContext([this, ...external])
|
|
643
|
-
: undefined;
|
|
644
|
-
return sources.map(source => {
|
|
645
|
-
const reference = anchorReference(source);
|
|
646
|
-
const { kind, transform: frame, topology, members: nested, bound, facing, } = source instanceof ModelObject ? source.exposedElement() : reference;
|
|
647
|
-
const memberPose = members.get(reference.model);
|
|
648
|
-
if (memberPose === null)
|
|
649
|
-
throw new Error("The point or element belongs to multiple occurrences. Expose it through the intended child model's named reference.");
|
|
650
|
-
const transform = memberPose ??
|
|
651
|
-
relativeTransform(reference.model.solvePose(context), this.solvePose(context));
|
|
652
|
-
return transformElement({ kind, transform: frame, topology, members: nested, bound, facing }, transform);
|
|
653
|
-
});
|
|
654
|
-
}
|
|
655
|
-
vertex(id) {
|
|
656
|
-
return this.vertices([id])[0];
|
|
657
|
-
}
|
|
658
|
-
vertices(ids) {
|
|
659
|
-
return this.selectTopology('vertex', ids);
|
|
660
|
-
}
|
|
661
|
-
edge(id) {
|
|
662
|
-
return this.edges([id])[0];
|
|
663
|
-
}
|
|
664
|
-
edges(ids) {
|
|
665
|
-
return this.selectTopology('edge', ids);
|
|
666
|
-
}
|
|
667
|
-
surface(id) {
|
|
668
|
-
return this.surfaces([id])[0];
|
|
669
|
-
}
|
|
670
|
-
surfaces(ids) {
|
|
671
|
-
return this.selectTopology('surface', ids);
|
|
672
|
-
}
|
|
673
|
-
selectTopology(kind, ids) {
|
|
674
|
-
const geometry = this.requireGeometry().value;
|
|
675
|
-
const selected = topologyChildren(geometry.shape, geometry.topology, { kind: 'solid' }, kind, ids);
|
|
676
|
-
return topologyReferences(this, '', { source: this, transform: identityRigidTransform, scale: 1 }, kind, selected);
|
|
677
|
-
}
|
|
678
|
-
/** @internal */
|
|
679
|
-
exposedElement() {
|
|
680
|
-
if (this.kind === 'group')
|
|
681
|
-
return { ...this.geometryAnchor, members: this.elements };
|
|
682
|
-
const geometry = this.requireGeometry().value;
|
|
683
|
-
const kind = (this.kind === 'face' ? 'surface' : this.kind);
|
|
684
|
-
const selection = kind === 'solid'
|
|
685
|
-
? { kind }
|
|
686
|
-
: {
|
|
687
|
-
kind,
|
|
688
|
-
id: geometry.topology[kind === 'surface'
|
|
689
|
-
? 'surfaces'
|
|
690
|
-
: kind === 'edge'
|
|
691
|
-
? 'edges'
|
|
692
|
-
: 'vertices'].ids[0],
|
|
693
|
-
};
|
|
694
|
-
const context = {
|
|
695
|
-
source: this,
|
|
696
|
-
transform: identityRigidTransform,
|
|
697
|
-
scale: 1,
|
|
698
|
-
};
|
|
699
|
-
const anchor = selection.kind === 'solid'
|
|
700
|
-
? this.geometryAnchor
|
|
701
|
-
: topologyReferences(this, '', context, selection.kind, [
|
|
702
|
-
selection.id,
|
|
703
|
-
])[0][anchorReferenceValue];
|
|
704
|
-
return {
|
|
705
|
-
kind: anchor.kind,
|
|
706
|
-
transform: anchor.transform,
|
|
707
|
-
members: this.elements,
|
|
708
|
-
topology: {
|
|
709
|
-
...context,
|
|
710
|
-
selection,
|
|
711
|
-
},
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
material(material) {
|
|
715
|
-
return this.copy({ material: captureModelMaterial(material) }, storedOperation('material', [{ model: this, role: 'source', index: 0 }]));
|
|
716
|
-
}
|
|
717
|
-
originOffset(dx, dy, dz) {
|
|
718
|
-
const offset = [dx, dy, dz];
|
|
719
|
-
assertFiniteVector('originOffset', offset);
|
|
720
|
-
return this.withOrigin(offset, { kind: 'originOffset' });
|
|
721
|
-
}
|
|
722
|
-
originPoint(point) {
|
|
723
|
-
const reference = anchorReference(point);
|
|
724
|
-
if (reference.kind !== 'point')
|
|
725
|
-
throw new Error('originPoint() requires a point reference.');
|
|
726
|
-
const [element] = this.localElements([point]);
|
|
727
|
-
return this.withOrigin(element.transform.position, {
|
|
728
|
-
kind: 'originPoint',
|
|
729
|
-
model: reference.model,
|
|
730
|
-
});
|
|
731
|
-
}
|
|
732
|
-
originVertex(id) {
|
|
733
|
-
const geometry = this.requireGeometry().value;
|
|
734
|
-
const [{ position }] = topologyVertexPoints(geometry.shape, geometry.topology.vertices, [id]);
|
|
735
|
-
return this.withOrigin(position, { kind: 'originVertex', id });
|
|
736
|
-
}
|
|
737
|
-
originCenter() {
|
|
738
|
-
this.requireGeometry();
|
|
739
|
-
const position = this.elements.center.transform.position;
|
|
740
|
-
return this.withOrigin(position, { kind: 'originCenter' });
|
|
741
|
-
}
|
|
742
|
-
withOrigin(offset, selection) {
|
|
743
|
-
const transform = translation(negateVector(offset));
|
|
744
|
-
const operation = storedOperation(selection.kind, [
|
|
745
|
-
{ model: this, role: 'source', index: 0 },
|
|
746
|
-
]);
|
|
747
|
-
if (selection.kind === 'originPoint' && selection.model !== this)
|
|
748
|
-
operation.inputs.push({
|
|
749
|
-
model: selection.model,
|
|
750
|
-
role: 'reference',
|
|
751
|
-
index: 0,
|
|
752
|
-
});
|
|
753
|
-
operation.spatial = { origin, vector: offset };
|
|
754
|
-
if (selection.kind === 'originVertex') {
|
|
755
|
-
operation.selections.push({
|
|
756
|
-
kind: 'vertex',
|
|
757
|
-
input: this,
|
|
758
|
-
ids: [selection.id],
|
|
759
|
-
transform,
|
|
760
|
-
});
|
|
761
|
-
}
|
|
762
|
-
return this.transformed(transform, operation);
|
|
763
|
-
}
|
|
764
|
-
rotate(x, y, z) {
|
|
765
|
-
const angles = [x, y, z];
|
|
766
|
-
assertFiniteVector('rotate', angles);
|
|
767
|
-
const operation = storedOperation('rotate', [
|
|
768
|
-
{ model: this, role: 'source', index: 0 },
|
|
769
|
-
]);
|
|
770
|
-
operation.spatial = { origin, vector: angles };
|
|
771
|
-
return this.transformed(rotationAround(origin, angles), operation);
|
|
772
|
-
}
|
|
773
|
-
transformed(transform, operation) {
|
|
774
|
-
const overrides = {
|
|
775
|
-
geometryAnchor: transformElement(this.geometryAnchor, transform),
|
|
776
|
-
elements: Object.fromEntries(Object.entries(this.elements).map(([name, element]) => [
|
|
777
|
-
name,
|
|
778
|
-
transformElement(element, transform),
|
|
779
|
-
])),
|
|
780
|
-
constraints: this.mapConstraintGeometry(element => transformElement(element, transform), point => composeTransforms(transform, translation(point)).position),
|
|
781
|
-
};
|
|
782
|
-
if (this.assembly)
|
|
783
|
-
return this.copy({
|
|
784
|
-
...overrides,
|
|
785
|
-
assembly: transformSolveContext(this.assembly, transform),
|
|
786
|
-
}, operation);
|
|
787
|
-
const source = this.requireGeometry();
|
|
788
|
-
const geometry = evaluateModelGeometry('transform', [transform.position, transform.quaternion], [source], () => {
|
|
789
|
-
const shape = shapeWithTransform(source.value.shape, transform);
|
|
790
|
-
try {
|
|
791
|
-
return {
|
|
792
|
-
shape,
|
|
793
|
-
topology: preserveShapeTopology(shape, source.value.topology),
|
|
794
|
-
referenceBasis: transformedReferenceBasis(source, transform, 1),
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
|
-
catch (error) {
|
|
798
|
-
shape.delete();
|
|
799
|
-
throw error;
|
|
800
|
-
}
|
|
801
|
-
});
|
|
802
|
-
return this.copyWithGeometry(geometry, overrides, operation);
|
|
803
|
-
}
|
|
804
|
-
mapConstraintGeometry(mapElement, mapPoint) {
|
|
805
|
-
const mapReference = (reference) => reference.model ? reference : { ...reference, ...mapElement(reference) };
|
|
806
|
-
return this.constraints.map(constraint => {
|
|
807
|
-
const mapped = {
|
|
808
|
-
...constraint,
|
|
809
|
-
source: mapReference(constraint.source),
|
|
810
|
-
target: mapReference(constraint.target),
|
|
811
|
-
rotations: constraint.rotations.map(action => ({
|
|
812
|
-
...action,
|
|
813
|
-
pivot: action.pivot.kind === 'around'
|
|
814
|
-
? { ...action.pivot, axis: mapReference(action.pivot.axis) }
|
|
815
|
-
: action.pivot.kind === 'pivot'
|
|
816
|
-
? { ...action.pivot, point: mapPoint(action.pivot.point) }
|
|
817
|
-
: action.pivot,
|
|
818
|
-
})),
|
|
819
|
-
};
|
|
820
|
-
valueTraces.set(mapped, valueTrace(constraint));
|
|
821
|
-
return mapped;
|
|
822
|
-
});
|
|
823
|
-
}
|
|
824
|
-
scaled(factor) {
|
|
825
|
-
assertPositive('scale', factor);
|
|
826
|
-
const source = this.requireGeometry();
|
|
827
|
-
const geometry = evaluateModelGeometry('scaled', [factor], [source], () => {
|
|
828
|
-
const shape = shapeWithScale(source.value.shape, factor);
|
|
829
|
-
try {
|
|
830
|
-
return {
|
|
831
|
-
shape,
|
|
832
|
-
topology: preserveShapeTopology(shape, source.value.topology),
|
|
833
|
-
referenceBasis: transformedReferenceBasis(source, identityRigidTransform, factor),
|
|
834
|
-
};
|
|
835
|
-
}
|
|
836
|
-
catch (error) {
|
|
837
|
-
shape.delete();
|
|
838
|
-
throw error;
|
|
839
|
-
}
|
|
840
|
-
});
|
|
841
|
-
return this.copyWithGeometry(geometry, {
|
|
842
|
-
geometryAnchor: scaleElement(this.geometryAnchor, factor),
|
|
843
|
-
elements: scaleElements(this.elements, factor),
|
|
844
|
-
constraints: this.mapConstraintGeometry(element => scaleElement(element, factor), point => scaleFrame(translation(point), factor).position),
|
|
845
|
-
}, storedOperation('scaled', [{ model: this, role: 'source', index: 0 }]));
|
|
846
|
-
}
|
|
847
|
-
extrude(distance) {
|
|
848
|
-
if (this.kind !== 'face')
|
|
849
|
-
throw new Error('extrude requires a single face model.');
|
|
850
|
-
if (!Number.isFinite(distance) || distance === 0)
|
|
851
|
-
throw new Error('Extrusion distance must be finite and non-zero.');
|
|
852
|
-
const source = this.requireGeometry();
|
|
853
|
-
const direction = rotateVector([0, distance, 0], this.geometryAnchor.transform.quaternion);
|
|
854
|
-
const geometry = evaluateSolidGeometry('extrude', [direction], [source], () => extrudeWithTopology({
|
|
855
|
-
shape: source.value.shape,
|
|
856
|
-
topology: source.value.topology,
|
|
857
|
-
index: 1,
|
|
858
|
-
}, direction));
|
|
859
|
-
return ModelObject.create({
|
|
860
|
-
kind: 'solid',
|
|
861
|
-
name: 'Extrude',
|
|
862
|
-
geometry,
|
|
863
|
-
material: this.materialSnapshot,
|
|
864
|
-
constraints: this.constraints,
|
|
865
|
-
sourceRefs: this.sourceRefs,
|
|
866
|
-
parameters: this.allParameters(),
|
|
867
|
-
meshTolerance: this.meshTolerance,
|
|
868
|
-
operation: storedOperation('extrude', [{ model: this, role: 'receiver', index: 0 }], {
|
|
869
|
-
dimensions: {
|
|
870
|
-
distance: {
|
|
871
|
-
origin: this.elements.center.transform.position,
|
|
872
|
-
vector: direction,
|
|
873
|
-
},
|
|
874
|
-
},
|
|
875
|
-
}),
|
|
876
|
-
});
|
|
877
|
-
}
|
|
878
|
-
cut(tools) {
|
|
879
|
-
return cut(this, tools);
|
|
880
|
-
}
|
|
881
|
-
fillet(radius, edgeIds) {
|
|
882
|
-
assertPositive('radius', radius);
|
|
883
|
-
const source = this.requireSolidGeometry();
|
|
884
|
-
const selectedEdgeIds = resolveEdgeSelection(source.value.topology.edges, edgeIds);
|
|
885
|
-
const geometry = evaluateSolidGeometry('fillet', [radius, selectedEdgeIds], [source], () => {
|
|
886
|
-
const result = filletEdges(source.value.shape, source.value.topology, radius, selectedEdgeIds);
|
|
887
|
-
return { shape: result.shape, topology: result.topology };
|
|
888
|
-
});
|
|
889
|
-
return this.copyWithGeometry(geometry, {}, storedOperation('fillet', [{ model: this, role: 'source', index: 0 }], {
|
|
890
|
-
selections: [
|
|
891
|
-
{
|
|
892
|
-
kind: 'edge',
|
|
893
|
-
input: this,
|
|
894
|
-
ids: selectedEdgeIds,
|
|
895
|
-
transform: identityRigidTransform,
|
|
896
|
-
},
|
|
897
|
-
],
|
|
898
|
-
}));
|
|
899
|
-
}
|
|
900
|
-
chamfer(distance, edgeIds) {
|
|
901
|
-
assertPositive('distance', distance);
|
|
902
|
-
const source = this.requireSolidGeometry();
|
|
903
|
-
const selectedEdgeIds = resolveEdgeSelection(source.value.topology.edges, edgeIds);
|
|
904
|
-
const geometry = evaluateSolidGeometry('chamfer', [distance, selectedEdgeIds], [source], () => {
|
|
905
|
-
const result = chamferEdges(source.value.shape, source.value.topology, distance, selectedEdgeIds);
|
|
906
|
-
return { shape: result.shape, topology: result.topology };
|
|
907
|
-
});
|
|
908
|
-
return this.copyWithGeometry(geometry, {}, storedOperation('chamfer', [{ model: this, role: 'source', index: 0 }], {
|
|
909
|
-
selections: [
|
|
910
|
-
{
|
|
911
|
-
kind: 'edge',
|
|
912
|
-
input: this,
|
|
913
|
-
ids: selectedEdgeIds,
|
|
914
|
-
transform: identityRigidTransform,
|
|
915
|
-
},
|
|
916
|
-
],
|
|
917
|
-
}));
|
|
918
|
-
}
|
|
919
|
-
shell(thickness, removedSurfaceIds = []) {
|
|
920
|
-
if (!Number.isFinite(thickness) || thickness === 0) {
|
|
921
|
-
throw new Error('thickness must be a nonzero finite number.');
|
|
922
|
-
}
|
|
923
|
-
const source = this.requireSolidGeometry();
|
|
924
|
-
const selectedIds = resolveTopologySelection('surface', source.value.topology.surfaces, removedSurfaceIds);
|
|
925
|
-
const geometry = evaluateSolidGeometry('shell', [thickness, selectedIds], [source], () => shellWithTopology(source.value.shape, source.value.topology, thickness, selectedIds));
|
|
926
|
-
return this.copyWithGeometry(geometry, {}, storedOperation('shell', [{ model: this, role: 'source', index: 0 }], {
|
|
927
|
-
selections: [
|
|
928
|
-
{
|
|
929
|
-
kind: 'surface',
|
|
930
|
-
input: this,
|
|
931
|
-
ids: selectedIds,
|
|
932
|
-
transform: identityRigidTransform,
|
|
933
|
-
},
|
|
934
|
-
],
|
|
935
|
-
}));
|
|
936
|
-
}
|
|
937
|
-
/** @internal */
|
|
938
|
-
attachSource(sourceRef) {
|
|
939
|
-
const previous = this.sourceRefs.at(-1);
|
|
940
|
-
if (previous?.file !== sourceRef.file ||
|
|
941
|
-
previous.start !== sourceRef.start ||
|
|
942
|
-
previous.end !== sourceRef.end) {
|
|
943
|
-
this.sourceRefs.push(sourceRef);
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
/** @internal */
|
|
947
|
-
attachParameters(parameters) {
|
|
948
|
-
appendUniqueParameters(this.parameters, parameters);
|
|
949
|
-
}
|
|
950
|
-
/** @internal */
|
|
951
|
-
attachOperationTrace(siteId, execution, order, sourceRef) {
|
|
952
|
-
if (operationTraces.has(this.operation)) {
|
|
953
|
-
return;
|
|
954
|
-
}
|
|
955
|
-
operationTraces.set(this.operation, { siteId, execution, order, sourceRef });
|
|
956
|
-
}
|
|
957
|
-
/** @internal */
|
|
958
|
-
relatedObjects() {
|
|
959
|
-
return [
|
|
960
|
-
...this.children,
|
|
961
|
-
...this.operation.inputs.map(input => input.model),
|
|
962
|
-
...this.constraints.flatMap(constraintReferences),
|
|
963
|
-
];
|
|
964
|
-
}
|
|
965
|
-
/** @internal */
|
|
966
|
-
toSnapshot(meshCache = new Map()) {
|
|
967
|
-
const solveContext = ModelObject.createSolveContext([this]);
|
|
968
|
-
return this.snapshotNode(meshCache, solveContext);
|
|
969
|
-
}
|
|
970
|
-
snapshotNode(meshCache, solveContext, inComposition = false, overrideMaterial) {
|
|
971
|
-
const material = overrideMaterial ?? this.materialSnapshot;
|
|
972
|
-
const pose = this.solvePose(solveContext);
|
|
973
|
-
const constraints = this.constraints.map(constraint => this.constraintSnapshot(constraint, solveContext));
|
|
974
|
-
const parameters = uniqueParameters([
|
|
975
|
-
...this.parameters,
|
|
976
|
-
...this.constraints.flatMap(constraint => valueTrace(constraint).parameters),
|
|
977
|
-
]);
|
|
978
|
-
const common = {
|
|
979
|
-
nodeId: this.nodeId,
|
|
980
|
-
kind: this.kind,
|
|
981
|
-
name: this.name,
|
|
982
|
-
material,
|
|
983
|
-
compositionTransform: toTransform(pose),
|
|
984
|
-
transform: toTransform(inComposition ? pose : identityRigidTransform),
|
|
985
|
-
constraints,
|
|
986
|
-
origin,
|
|
987
|
-
elements: snapshotElements({
|
|
988
|
-
...this.elements,
|
|
989
|
-
...(this.geometry || this.children.length
|
|
990
|
-
? Object.fromEntries(Object.keys(boundDirections).map(direction => [
|
|
991
|
-
direction,
|
|
992
|
-
anchorReference(this[direction]),
|
|
993
|
-
]))
|
|
994
|
-
: {}),
|
|
995
|
-
}),
|
|
996
|
-
sourceRefs: [...this.sourceRefs],
|
|
997
|
-
parameters,
|
|
998
|
-
operation: this.operationSnapshot(meshCache),
|
|
999
|
-
};
|
|
1000
|
-
if (this.kind === 'group') {
|
|
1001
|
-
const childContext = this.assembly;
|
|
1002
|
-
return {
|
|
1003
|
-
...common,
|
|
1004
|
-
children: this.children.map(child => child.snapshotNode(meshCache, childContext, true, material)),
|
|
1005
|
-
};
|
|
1006
|
-
}
|
|
1007
|
-
const geometry = this.requireGeometry();
|
|
1008
|
-
return {
|
|
1009
|
-
...common,
|
|
1010
|
-
children: [],
|
|
1011
|
-
mesh: renderMesh(geometry, geometry.value.shape, meshCache, this.meshTolerance, geometry.value.topology),
|
|
1012
|
-
};
|
|
1013
|
-
}
|
|
1014
|
-
/** @internal */
|
|
1015
|
-
disposeShape(disposed) {
|
|
1016
|
-
const shapes = [
|
|
1017
|
-
...(this.geometry ? [this.geometry.value.shape] : []),
|
|
1018
|
-
...(this.geometry?.value.referenceBasis
|
|
1019
|
-
? [this.geometry.value.referenceBasis.shape]
|
|
1020
|
-
: []),
|
|
1021
|
-
...this.operation.regions.map(region => region.artifact.value),
|
|
1022
|
-
];
|
|
1023
|
-
for (const shape of shapes) {
|
|
1024
|
-
if (!disposed.has(shape)) {
|
|
1025
|
-
disposed.add(shape);
|
|
1026
|
-
shape.delete();
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
this.children.forEach(child => child.disposeShape(disposed));
|
|
1030
|
-
}
|
|
1031
|
-
/** @internal */
|
|
1032
|
-
[loftModels](others, spine, ruled) {
|
|
1033
|
-
const sections = [this, ...others];
|
|
1034
|
-
const solveContext = ModelObject.createSolveContext([
|
|
1035
|
-
...sections,
|
|
1036
|
-
...(spine ? [spine] : []),
|
|
1037
|
-
]);
|
|
1038
|
-
const resultPose = this.solvePose(solveContext);
|
|
1039
|
-
const sectionInputs = sections.map(section => ({
|
|
1040
|
-
model: section,
|
|
1041
|
-
geometry: section.requireGeometry(),
|
|
1042
|
-
transform: relativeTransform(section.solvePose(solveContext), resultPose),
|
|
1043
|
-
}));
|
|
1044
|
-
const spineInput = spine
|
|
1045
|
-
? {
|
|
1046
|
-
model: spine,
|
|
1047
|
-
geometry: spine.requireGeometry(),
|
|
1048
|
-
transform: relativeTransform(spine.solvePose(solveContext), resultPose),
|
|
1049
|
-
}
|
|
1050
|
-
: undefined;
|
|
1051
|
-
const geometry = evaluateSolidGeometry(spine ? 'spine-loft' : 'loft', [
|
|
1052
|
-
ruled,
|
|
1053
|
-
sectionInputs.map(input => [
|
|
1054
|
-
input.transform.position,
|
|
1055
|
-
input.transform.quaternion,
|
|
1056
|
-
]),
|
|
1057
|
-
spineInput
|
|
1058
|
-
? [spineInput.transform.position, spineInput.transform.quaternion]
|
|
1059
|
-
: null,
|
|
1060
|
-
], [
|
|
1061
|
-
...sectionInputs.map(input => input.geometry),
|
|
1062
|
-
...(spineInput ? [spineInput.geometry] : []),
|
|
1063
|
-
], () => buildLoftGeometry(sectionInputs, spineInput, ruled));
|
|
1064
|
-
const inputs = [...sections, ...(spine ? [spine] : [])];
|
|
1065
|
-
return ModelObject.create({
|
|
1066
|
-
kind: 'solid',
|
|
1067
|
-
name: 'Loft',
|
|
1068
|
-
geometry,
|
|
1069
|
-
material: this.materialSnapshot,
|
|
1070
|
-
constraints: this.constraints,
|
|
1071
|
-
sourceRefs: inputs.flatMap(input => input.sourceRefs),
|
|
1072
|
-
parameters: uniqueParameters(inputs.flatMap(input => input.allParameters())),
|
|
1073
|
-
meshTolerance: Math.min(...inputs.map(input => input.meshTolerance)),
|
|
1074
|
-
operation: storedOperation('loft', [
|
|
1075
|
-
{ model: this, role: 'receiver', index: 0 },
|
|
1076
|
-
...others.map((model, index) => ({
|
|
1077
|
-
model,
|
|
1078
|
-
role: 'section',
|
|
1079
|
-
index: index + 1,
|
|
1080
|
-
})),
|
|
1081
|
-
...(spine
|
|
1082
|
-
? [{ model: spine, role: 'spine', index: sections.length }]
|
|
1083
|
-
: []),
|
|
1084
|
-
]),
|
|
1085
|
-
});
|
|
1086
|
-
}
|
|
1087
|
-
/** @internal */
|
|
1088
|
-
[combineModels](operation, others) {
|
|
1089
|
-
const evaluation = this.evaluateBoolean(operation, others);
|
|
1090
|
-
let transferred = false;
|
|
1091
|
-
try {
|
|
1092
|
-
const combined = ModelObject.create({
|
|
1093
|
-
kind: 'solid',
|
|
1094
|
-
geometryAnchor: this.geometryAnchor,
|
|
1095
|
-
geometry: evaluation.geometry,
|
|
1096
|
-
name: this.name,
|
|
1097
|
-
material: this.materialSnapshot,
|
|
1098
|
-
constraints: this.constraints,
|
|
1099
|
-
sourceRefs: [this, ...others].flatMap(model => model.sourceRefs),
|
|
1100
|
-
parameters: uniqueParameters([this, ...others].flatMap(model => model.allParameters())),
|
|
1101
|
-
meshTolerance: Math.min(this.meshTolerance, ...others.map(model => model.meshTolerance)),
|
|
1102
|
-
operation: storedOperation(operation === 'fuse' ? 'union' : operation, [
|
|
1103
|
-
{ model: this, role: 'receiver', index: 0 },
|
|
1104
|
-
...others.map((model, index) => ({
|
|
1105
|
-
model,
|
|
1106
|
-
role: operation === 'cut' ? 'tool' : 'operand',
|
|
1107
|
-
index: index + 1,
|
|
1108
|
-
})),
|
|
1109
|
-
], { regions: evaluation.regions }),
|
|
1110
|
-
});
|
|
1111
|
-
transferred = true;
|
|
1112
|
-
return combined;
|
|
1113
|
-
}
|
|
1114
|
-
finally {
|
|
1115
|
-
if (!transferred) {
|
|
1116
|
-
disposeModelGeometryValue(evaluation.geometry.value);
|
|
1117
|
-
evaluation.regions.forEach(region => region.artifact.value.delete());
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
evaluateBoolean(operation, others) {
|
|
1122
|
-
const solveContext = ModelObject.createSolveContext([this, ...others]);
|
|
1123
|
-
const targetPose = this.solvePose(solveContext);
|
|
1124
|
-
let geometry = this.requireSolidGeometry();
|
|
1125
|
-
let temporaryGeometry;
|
|
1126
|
-
const regions = [];
|
|
1127
|
-
let evaluated = false;
|
|
1128
|
-
try {
|
|
1129
|
-
for (const [otherIndex, other] of others.entries()) {
|
|
1130
|
-
const otherGeometry = other.requireSolidGeometry();
|
|
1131
|
-
const transform = relativeTransform(other.solvePose(solveContext), targetPose);
|
|
1132
|
-
const operand = evaluateKernelShape('transform', [transform.position, transform.quaternion], [otherGeometry], () => shapeWithTransform(otherGeometry.value.shape, transform));
|
|
1133
|
-
try {
|
|
1134
|
-
if (operation === 'cut' || operation === 'fuse') {
|
|
1135
|
-
regions.push({
|
|
1136
|
-
kind: 'intersection',
|
|
1137
|
-
input: other,
|
|
1138
|
-
frame: this,
|
|
1139
|
-
artifact: evaluateKernelShape('boolean-intersection-region', [], [geometry, operand], () => geometry.value.shape.intersect(operand.value)),
|
|
1140
|
-
});
|
|
1141
|
-
}
|
|
1142
|
-
if (operation === 'fuse') {
|
|
1143
|
-
regions.push({
|
|
1144
|
-
kind: 'section',
|
|
1145
|
-
input: other,
|
|
1146
|
-
frame: this,
|
|
1147
|
-
artifact: evaluateKernelShape('boolean-section-region', [], [geometry, operand], () => unionSectionShape(geometry.value.shape, operand.value)),
|
|
1148
|
-
});
|
|
1149
|
-
}
|
|
1150
|
-
const nextGeometry = evaluateSolidGeometry(`boolean-${operation}`, [otherIndex], [geometry, operand], () => {
|
|
1151
|
-
const result = booleanWithTopology({
|
|
1152
|
-
shape: geometry.value.shape,
|
|
1153
|
-
topology: geometry.value.topology,
|
|
1154
|
-
index: otherIndex === 0 ? 1 : undefined,
|
|
1155
|
-
}, {
|
|
1156
|
-
shape: operand.value,
|
|
1157
|
-
topology: otherGeometry.value.topology,
|
|
1158
|
-
index: otherIndex + 2,
|
|
1159
|
-
}, operation);
|
|
1160
|
-
return { shape: result.shape, topology: result.topology };
|
|
1161
|
-
});
|
|
1162
|
-
temporaryGeometry?.value.shape.delete();
|
|
1163
|
-
temporaryGeometry = nextGeometry;
|
|
1164
|
-
geometry = nextGeometry;
|
|
1165
|
-
}
|
|
1166
|
-
finally {
|
|
1167
|
-
operand.value.delete();
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
evaluated = true;
|
|
1171
|
-
return { geometry, regions };
|
|
1172
|
-
}
|
|
1173
|
-
finally {
|
|
1174
|
-
if (!evaluated) {
|
|
1175
|
-
temporaryGeometry?.value.shape.delete();
|
|
1176
|
-
regions.forEach(region => region.artifact.value.delete());
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
allParameters() {
|
|
1181
|
-
return uniqueParameters([
|
|
1182
|
-
...this.parameters,
|
|
1183
|
-
...this.constraints.flatMap(constraint => valueTrace(constraint).parameters),
|
|
1184
|
-
]);
|
|
1185
|
-
}
|
|
1186
|
-
solvePose(context) {
|
|
1187
|
-
return context.poses.get(this);
|
|
1188
|
-
}
|
|
1189
|
-
memberPoses() {
|
|
1190
|
-
const members = new Map([
|
|
1191
|
-
[this, identityRigidTransform],
|
|
1192
|
-
]);
|
|
1193
|
-
if (this.children.length) {
|
|
1194
|
-
const context = this.assembly;
|
|
1195
|
-
for (const child of this.children) {
|
|
1196
|
-
const pose = child.solvePose(context);
|
|
1197
|
-
for (const [member, localPose] of child.memberPoses()) {
|
|
1198
|
-
members.set(member, members.has(member) || localPose === null
|
|
1199
|
-
? null
|
|
1200
|
-
: composeTransforms(pose, localPose));
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
return members;
|
|
1205
|
-
}
|
|
1206
|
-
/** Bounds of the selected finite geometry after a rigid transform. */
|
|
1207
|
-
[referenceBoundsParts](reference, transform) {
|
|
1208
|
-
if (reference.bound) {
|
|
1209
|
-
const frame = composeTransforms(transform, reference.transform);
|
|
1210
|
-
const [x, z] = reference.bound.size;
|
|
1211
|
-
return [
|
|
1212
|
-
{
|
|
1213
|
-
bounds: pointBounds([-1, 1].flatMap(a => [-1, 1].map(b => composeTransforms(frame, translation([(a * x) / 2, 0, (b * z) / 2])).position))),
|
|
1214
|
-
},
|
|
1215
|
-
];
|
|
1216
|
-
}
|
|
1217
|
-
if (reference.whole) {
|
|
1218
|
-
if (this.geometry)
|
|
1219
|
-
return [boundsQuery(this.geometry, transform)];
|
|
1220
|
-
const context = this.assembly;
|
|
1221
|
-
return this.children.flatMap(child => child[referenceBoundsParts](child.relationAnchorReference(), composeTransforms(transform, child.solvePose(context))));
|
|
1222
|
-
}
|
|
1223
|
-
if (reference.topology) {
|
|
1224
|
-
const topology = reference.topology;
|
|
1225
|
-
return [
|
|
1226
|
-
boundsQuery(topology.source.requireGeometry(), composeTransforms(transform, topology.transform), topology.selection, topology.scale),
|
|
1227
|
-
];
|
|
1228
|
-
}
|
|
1229
|
-
if (reference.kind === 'point') {
|
|
1230
|
-
const point = composeTransforms(transform, reference.transform).position;
|
|
1231
|
-
return [{ bounds: [point, point] }];
|
|
1232
|
-
}
|
|
1233
|
-
throw new Error(`The reference ${reference.name} has no finite geometry. Select a model, vertex, edge, or surface for on().`);
|
|
1234
|
-
}
|
|
1235
|
-
/** Bounds of the selected finite geometry after a rigid transform. */
|
|
1236
|
-
[referenceBounds](reference, transform) {
|
|
1237
|
-
const bounds = this[referenceBoundsParts](reference, transform).map(part => 'bounds' in part
|
|
1238
|
-
? part.bounds
|
|
1239
|
-
: evaluateSnapshotQuery(part));
|
|
1240
|
-
return bounds.length === 1 ? bounds[0] : combineBounds(bounds);
|
|
1241
|
-
}
|
|
1242
|
-
/** Pure query collection: it never substitutes geometry or runs author code. */
|
|
1243
|
-
[modelSnapshotQueries]() {
|
|
1244
|
-
const result = [];
|
|
1245
|
-
if (this.geometry || this.children.length) {
|
|
1246
|
-
for (const direction of Object.values(boundDirections)) {
|
|
1247
|
-
const transform = invertTransform(rotation(frameFromYAxis(origin, direction).quaternion));
|
|
1248
|
-
for (const part of this[referenceBoundsParts](this.relationAnchorReference(), transform))
|
|
1249
|
-
if (!('bounds' in part))
|
|
1250
|
-
result.push(part);
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
if (this.geometry)
|
|
1254
|
-
result.push(meshQuery(this.geometry, this.geometry.value.shape, this.meshTolerance, this.geometry.value.topology));
|
|
1255
|
-
for (const region of this.operation.regions)
|
|
1256
|
-
result.push(meshQuery(region.artifact, region.artifact.value, this.meshTolerance));
|
|
1257
|
-
return result;
|
|
1258
|
-
}
|
|
1259
|
-
alignmentGeometry(reference) {
|
|
1260
|
-
if (reference.kind === 'point')
|
|
1261
|
-
return {
|
|
1262
|
-
kind: 'point',
|
|
1263
|
-
point: reference.whole
|
|
1264
|
-
? this.requireGeometry().value.shape.asTuple()
|
|
1265
|
-
: reference.transform.position,
|
|
1266
|
-
};
|
|
1267
|
-
const read = (shape) => reference.kind === 'line'
|
|
1268
|
-
? edgeGeometry(shape, reference.direction ?? 1)
|
|
1269
|
-
: faceGeometry(shape, reference.facing ?? reference.bound?.facing ?? 1);
|
|
1270
|
-
if (reference.topology) {
|
|
1271
|
-
const topology = reference.topology, geometry = topology.source.requireGeometry().value;
|
|
1272
|
-
return withTopologyShape(geometry.shape, geometry.topology, topology.selection, shape => {
|
|
1273
|
-
const scaled = topology.scale === 1
|
|
1274
|
-
? shape
|
|
1275
|
-
: shapeWithScale(shape, topology.scale);
|
|
1276
|
-
try {
|
|
1277
|
-
return transformGeometry(read(scaled), topology.transform);
|
|
1278
|
-
}
|
|
1279
|
-
finally {
|
|
1280
|
-
if (scaled !== shape)
|
|
1281
|
-
scaled.delete();
|
|
1282
|
-
}
|
|
1283
|
-
});
|
|
1284
|
-
}
|
|
1285
|
-
if (reference.whole)
|
|
1286
|
-
return read(this.requireGeometry().value.shape);
|
|
1287
|
-
const direction = rotateVector([
|
|
1288
|
-
0,
|
|
1289
|
-
reference.kind === 'line'
|
|
1290
|
-
? (reference.direction ?? 1)
|
|
1291
|
-
: (reference.facing ?? reference.bound?.facing ?? 1),
|
|
1292
|
-
0,
|
|
1293
|
-
], reference.transform.quaternion);
|
|
1294
|
-
return reference.kind === 'line'
|
|
1295
|
-
? { kind: 'line', point: reference.transform.position, direction }
|
|
1296
|
-
: { kind: 'plane', point: reference.transform.position, normal: direction };
|
|
1297
|
-
}
|
|
1298
|
-
relationElement(reference) {
|
|
1299
|
-
const element = reference.whole
|
|
1300
|
-
? {
|
|
1301
|
-
...this.exposedElement(),
|
|
1302
|
-
name: reference.name,
|
|
1303
|
-
direction: reference.direction,
|
|
1304
|
-
facing: reference.facing,
|
|
1305
|
-
}
|
|
1306
|
-
: reference;
|
|
1307
|
-
const topology = element.topology;
|
|
1308
|
-
let arrow;
|
|
1309
|
-
if (reference.kind === 'line' && topology) {
|
|
1310
|
-
const geometry = topology.source.requireGeometry().value;
|
|
1311
|
-
arrow = withTopologyShape(geometry.shape, geometry.topology, topology.selection, shape => {
|
|
1312
|
-
const edge = shape;
|
|
1313
|
-
const position = (reference.direction ?? 1) === 1 ? 1 : 0;
|
|
1314
|
-
const point = edge.pointAt(position), tangent = edge.tangentAt(position);
|
|
1315
|
-
try {
|
|
1316
|
-
return topologyTransform(topology, frameFromYAxis(point.toTuple(), tangent
|
|
1317
|
-
.toTuple()
|
|
1318
|
-
.map(v => v * (reference.direction ?? 1))));
|
|
1319
|
-
}
|
|
1320
|
-
finally {
|
|
1321
|
-
point.delete();
|
|
1322
|
-
tangent.delete();
|
|
1323
|
-
}
|
|
1324
|
-
});
|
|
1325
|
-
}
|
|
1326
|
-
return {
|
|
1327
|
-
...snapshotElements({ [reference.name]: element })[0],
|
|
1328
|
-
arrow,
|
|
1329
|
-
};
|
|
1330
|
-
}
|
|
1331
|
-
bodyRotations(constraint, indices) {
|
|
1332
|
-
return constraint.rotations.map(({ pivot, angles }) => {
|
|
1333
|
-
if (pivot.kind === 'around') {
|
|
1334
|
-
return pivot.axis.model
|
|
1335
|
-
? {
|
|
1336
|
-
body: indices.get(pivot.axis.model),
|
|
1337
|
-
axis: pivot.axis.transform,
|
|
1338
|
-
angle: angles * (pivot.axis.direction ?? 1),
|
|
1339
|
-
}
|
|
1340
|
-
: {
|
|
1341
|
-
local: axisRotation(pivot.axis.transform, angles * (pivot.axis.direction ?? 1)),
|
|
1342
|
-
};
|
|
1343
|
-
}
|
|
1344
|
-
const frame = translation(pivot.kind === 'pivot'
|
|
1345
|
-
? pivot.point
|
|
1346
|
-
: anchorReference(this.vertex(pivot.id)).transform.position);
|
|
1347
|
-
return {
|
|
1348
|
-
local: composeTransforms(composeTransforms(frame, rotationAround(origin, angles)), invertTransform(frame)),
|
|
1349
|
-
};
|
|
1350
|
-
});
|
|
1351
|
-
}
|
|
1352
|
-
/** @internal */
|
|
1353
|
-
[relationPreview](nodeId, constraint, selection) {
|
|
1354
|
-
// This expression starts from the relate receiver, before sibling constraints
|
|
1355
|
-
// are returned and committed to self. Geometry and node identity stay shared.
|
|
1356
|
-
const owner = this.copy({ nodeId, constraints: [...this.constraints, constraint] }, this.operation);
|
|
1357
|
-
const context = ModelObject.createSolveContext([
|
|
1358
|
-
owner,
|
|
1359
|
-
...(selection?.pivot.kind === 'around' && selection.pivot.axis.model
|
|
1360
|
-
? [selection.pivot.axis.model]
|
|
1361
|
-
: []),
|
|
1362
|
-
]);
|
|
1363
|
-
return {
|
|
1364
|
-
object: {
|
|
1365
|
-
nodeId,
|
|
1366
|
-
compositionTransform: toTransform(owner.solvePose(context)),
|
|
1367
|
-
constraints: owner.constraints.map(value => owner.constraintSnapshot(value, context)),
|
|
1368
|
-
},
|
|
1369
|
-
spatial: selection
|
|
1370
|
-
? owner.constraintSpatial(constraint, selection, context)
|
|
1371
|
-
: undefined,
|
|
1372
|
-
};
|
|
1373
|
-
}
|
|
1374
|
-
constraintSpatial(constraint, selection, context) {
|
|
1375
|
-
const models = [...context.poses.keys()];
|
|
1376
|
-
const actions = this.bodyRotations(constraint, new Map(models.map((model, i) => [model, i])));
|
|
1377
|
-
const stage = actions.length - (selection.kind === 'rotate' ? 1 : 0);
|
|
1378
|
-
const finalPose = this.solvePose(context);
|
|
1379
|
-
let before = finalPose;
|
|
1380
|
-
for (let i = actions.length - 1; i >= stage; i--) {
|
|
1381
|
-
const action = actions[i];
|
|
1382
|
-
before =
|
|
1383
|
-
'local' in action
|
|
1384
|
-
? composeTransforms(before, invertTransform(action.local))
|
|
1385
|
-
: composeTransforms(axisRotation(composeTransforms(models[action.body].solvePose(context), action.axis), -action.angle), before);
|
|
1386
|
-
}
|
|
1387
|
-
const pivot = selection.pivot;
|
|
1388
|
-
let frame;
|
|
1389
|
-
if (pivot.kind === 'around') {
|
|
1390
|
-
frame = composeTransforms(pivot.axis.model ? pivot.axis.model.solvePose(context) : before, (pivot.axis.direction ?? 1) === 1
|
|
1391
|
-
? pivot.axis.transform
|
|
1392
|
-
: composeTransforms(pivot.axis.transform, rotation([1, 0, 0, 0])));
|
|
1393
|
-
}
|
|
1394
|
-
else {
|
|
1395
|
-
const local = translation(pivot.kind === 'pivot'
|
|
1396
|
-
? pivot.point
|
|
1397
|
-
: anchorReference(this.vertex(pivot.id)).transform.position);
|
|
1398
|
-
frame = composeTransforms(before, local);
|
|
1399
|
-
}
|
|
1400
|
-
const angles = constraint.rotations[stage]?.angles ?? origin;
|
|
1401
|
-
const rotationVector = typeof angles === 'number' ? [0, angles, 0] : angles;
|
|
1402
|
-
const localFrame = relativeTransform(frame, finalPose);
|
|
1403
|
-
return {
|
|
1404
|
-
nodeId: this.nodeId,
|
|
1405
|
-
kind: selection.kind,
|
|
1406
|
-
spatial: {
|
|
1407
|
-
origin: localFrame.position,
|
|
1408
|
-
vector: selection.kind === 'rotate'
|
|
1409
|
-
? rotationVector
|
|
1410
|
-
: pivot.kind === 'pivot'
|
|
1411
|
-
? pivot.point
|
|
1412
|
-
: origin,
|
|
1413
|
-
frame: localFrame,
|
|
1414
|
-
rotation: rotationVector,
|
|
1415
|
-
axisOnly: pivot.kind === 'around',
|
|
1416
|
-
},
|
|
1417
|
-
};
|
|
1418
|
-
}
|
|
1419
|
-
/** Fix the assembly frame once, using only its direct member origins. */
|
|
1420
|
-
static createAssembly(children) {
|
|
1421
|
-
const context = ModelObject.createSolveContext(children);
|
|
1422
|
-
const center = children.length
|
|
1423
|
-
? boundsCenter(pointBounds(children.map(child => child.solvePose(context).position)))
|
|
1424
|
-
: origin;
|
|
1425
|
-
return transformSolveContext(context, translation(negateVector(center)));
|
|
1426
|
-
}
|
|
1427
|
-
static createSolveContext(roots) {
|
|
1428
|
-
const models = [];
|
|
1429
|
-
const indices = new Map();
|
|
1430
|
-
const collect = (model) => {
|
|
1431
|
-
if (indices.has(model))
|
|
1432
|
-
return;
|
|
1433
|
-
indices.set(model, models.length);
|
|
1434
|
-
models.push(model);
|
|
1435
|
-
model.constraints.flatMap(constraintReferences).forEach(collect);
|
|
1436
|
-
};
|
|
1437
|
-
roots.forEach(collect);
|
|
1438
|
-
const poses = solveBodies(models.map((model) => ({
|
|
1439
|
-
name: model.name,
|
|
1440
|
-
relations: model.constraints.map(constraint => constraint.kind === 'align'
|
|
1441
|
-
? {
|
|
1442
|
-
kind: 'align',
|
|
1443
|
-
id: constraint.id,
|
|
1444
|
-
source: {
|
|
1445
|
-
body: indices.get(constraint.source.model ?? model),
|
|
1446
|
-
geometry: (constraint.source.model ?? model).alignmentGeometry(constraint.source),
|
|
1447
|
-
transform: constraint.source.transform,
|
|
1448
|
-
},
|
|
1449
|
-
target: {
|
|
1450
|
-
body: indices.get(constraint.target.model ?? model),
|
|
1451
|
-
geometry: (constraint.target.model ?? model).alignmentGeometry(constraint.target),
|
|
1452
|
-
transform: constraint.target.transform,
|
|
1453
|
-
},
|
|
1454
|
-
offset: constraint.offset,
|
|
1455
|
-
rotations: model.bodyRotations(constraint, indices),
|
|
1456
|
-
}
|
|
1457
|
-
: {
|
|
1458
|
-
kind: 'on',
|
|
1459
|
-
id: constraint.id,
|
|
1460
|
-
source: {
|
|
1461
|
-
body: indices.get(constraint.source.model ?? model),
|
|
1462
|
-
key: constraint.source.name,
|
|
1463
|
-
bounds: (orientation) => (constraint.source.model ?? model)[referenceBounds](constraint.source, rotation(orientation)),
|
|
1464
|
-
},
|
|
1465
|
-
target: {
|
|
1466
|
-
body: indices.get(constraint.target.model ?? model),
|
|
1467
|
-
transform: constraint.target.transform,
|
|
1468
|
-
facing: constraint.target.bound.facing,
|
|
1469
|
-
},
|
|
1470
|
-
offset: constraint.offset,
|
|
1471
|
-
rotations: model.bodyRotations(constraint, indices),
|
|
1472
|
-
}),
|
|
1473
|
-
})));
|
|
1474
|
-
return {
|
|
1475
|
-
poses: new Map(models.map((model, index) => [model, poses[index]])),
|
|
1476
|
-
};
|
|
1477
|
-
}
|
|
1478
|
-
constraintSnapshot(constraint, context) {
|
|
1479
|
-
const source = constraint.source.model ?? this;
|
|
1480
|
-
const target = constraint.target.model ?? this;
|
|
1481
|
-
const models = [...context.poses.keys()];
|
|
1482
|
-
const actions = this.bodyRotations(constraint, new Map(models.map((model, i) => [model, i])));
|
|
1483
|
-
let before = this.solvePose(context);
|
|
1484
|
-
for (const action of [...actions].reverse()) {
|
|
1485
|
-
before =
|
|
1486
|
-
'local' in action
|
|
1487
|
-
? composeTransforms(before, invertTransform(action.local))
|
|
1488
|
-
: composeTransforms(axisRotation(composeTransforms(models[action.body].solvePose(context), action.axis), -action.angle), before);
|
|
1489
|
-
}
|
|
1490
|
-
const sourcePose = source === this ? before : source.solvePose(context);
|
|
1491
|
-
const targetPose = target === this ? before : target.solvePose(context);
|
|
1492
|
-
let offsetFrame = composeTransforms(targetPose, constraint.target.transform);
|
|
1493
|
-
if (constraint.kind === 'align') {
|
|
1494
|
-
for (const action of actions)
|
|
1495
|
-
if ('body' in action)
|
|
1496
|
-
offsetFrame = composeTransforms(axisRotation(composeTransforms(models[action.body].solvePose(context), action.axis), action.angle), offsetFrame);
|
|
1497
|
-
return {
|
|
1498
|
-
id: constraint.id,
|
|
1499
|
-
kind: 'align',
|
|
1500
|
-
source: anchorSnapshot(source, constraint.source),
|
|
1501
|
-
target: anchorSnapshot(target, constraint.target),
|
|
1502
|
-
sourceElement: source.relationElement(constraint.source),
|
|
1503
|
-
targetElement: target.relationElement(constraint.target),
|
|
1504
|
-
offsetDirection: 1,
|
|
1505
|
-
offset: constraint.offset ?? origin,
|
|
1506
|
-
offsetFrame: toTransform(offsetFrame),
|
|
1507
|
-
sourceRefs: [...valueTrace(constraint).sourceRefs],
|
|
1508
|
-
parameters: [...valueTrace(constraint).parameters],
|
|
1509
|
-
};
|
|
1510
|
-
}
|
|
1511
|
-
const orientation = composeTransforms(invertTransform(rotation(offsetFrame.quaternion)), rotation(sourcePose.quaternion)).quaternion;
|
|
1512
|
-
const bounds = source[referenceBounds](constraint.source, rotation(orientation));
|
|
1513
|
-
const center = [
|
|
1514
|
-
(bounds[0][0] + bounds[1][0]) / 2,
|
|
1515
|
-
bounds[constraint.target.bound.facing === 1 ? 0 : 1][1],
|
|
1516
|
-
(bounds[0][2] + bounds[1][2]) / 2,
|
|
1517
|
-
];
|
|
1518
|
-
const sourceFrame = relativeTransform({
|
|
1519
|
-
position: addVectors(sourcePose.position, rotateVector(center, offsetFrame.quaternion)),
|
|
1520
|
-
quaternion: offsetFrame.quaternion,
|
|
1521
|
-
}, sourcePose);
|
|
1522
|
-
const sourceSize = [
|
|
1523
|
-
bounds[1][0] - bounds[0][0],
|
|
1524
|
-
bounds[1][1] - bounds[0][1],
|
|
1525
|
-
bounds[1][2] - bounds[0][2],
|
|
1526
|
-
];
|
|
1527
|
-
for (const action of actions) {
|
|
1528
|
-
if ('body' in action)
|
|
1529
|
-
offsetFrame = composeTransforms(axisRotation(composeTransforms(models[action.body].solvePose(context), action.axis), action.angle), offsetFrame);
|
|
1530
|
-
}
|
|
1531
|
-
return {
|
|
1532
|
-
id: constraint.id,
|
|
1533
|
-
kind: constraint.kind,
|
|
1534
|
-
source: anchorSnapshot(source, constraint.source),
|
|
1535
|
-
target: anchorSnapshot(target, constraint.target),
|
|
1536
|
-
sourceBounds: {
|
|
1537
|
-
size: sourceSize,
|
|
1538
|
-
transform: toTransform(composeTransforms(sourceFrame, {
|
|
1539
|
-
position: [
|
|
1540
|
-
0,
|
|
1541
|
-
(constraint.target.bound.facing * sourceSize[1]) / 2,
|
|
1542
|
-
0,
|
|
1543
|
-
],
|
|
1544
|
-
quaternion: [0, 0, 0, 1],
|
|
1545
|
-
})),
|
|
1546
|
-
},
|
|
1547
|
-
sourceElement: {
|
|
1548
|
-
name: `${constraint.source.name}:bound`,
|
|
1549
|
-
kind: 'face',
|
|
1550
|
-
transform: toTransform(sourceFrame),
|
|
1551
|
-
bound: {
|
|
1552
|
-
size: [sourceSize[0], sourceSize[2]],
|
|
1553
|
-
facing: constraint.target.bound.facing === 1 ? -1 : 1,
|
|
1554
|
-
},
|
|
1555
|
-
},
|
|
1556
|
-
targetElement: {
|
|
1557
|
-
name: constraint.target.name,
|
|
1558
|
-
kind: 'face',
|
|
1559
|
-
transform: toTransform(constraint.target.transform),
|
|
1560
|
-
bound: constraint.target.bound,
|
|
1561
|
-
},
|
|
1562
|
-
offsetDirection: source === this ? 1 : -1,
|
|
1563
|
-
offset: constraint.offset ?? origin,
|
|
1564
|
-
offsetFrame: toTransform(offsetFrame),
|
|
1565
|
-
sourceRefs: [...valueTrace(constraint).sourceRefs],
|
|
1566
|
-
parameters: [...valueTrace(constraint).parameters],
|
|
1567
|
-
};
|
|
1568
|
-
}
|
|
1569
|
-
requireGeometry() {
|
|
1570
|
-
if (!this.geometry) {
|
|
1571
|
-
throw new Error('This operation requires geometry and cannot act on a group.');
|
|
1572
|
-
}
|
|
1573
|
-
return this.geometry;
|
|
1574
|
-
}
|
|
1575
|
-
requireSolidGeometry() {
|
|
1576
|
-
if (this.kind !== 'solid') {
|
|
1577
|
-
throw new Error('This operation requires a solid model.');
|
|
1578
|
-
}
|
|
1579
|
-
return this.requireGeometry();
|
|
1580
|
-
}
|
|
1581
|
-
operationSnapshot(meshCache) {
|
|
1582
|
-
const { kind, inputs, regions, selections } = this.operation;
|
|
1583
|
-
const { siteId, execution, order, sourceRef } = operationTraces.get(this.operation) ?? {};
|
|
1584
|
-
return {
|
|
1585
|
-
id: storedOperationId(this.operation),
|
|
1586
|
-
siteId,
|
|
1587
|
-
execution,
|
|
1588
|
-
kind,
|
|
1589
|
-
order,
|
|
1590
|
-
outputNodeId: this.nodeId,
|
|
1591
|
-
inputs: inputs.map(({ model, role, index }) => ({
|
|
1592
|
-
nodeId: model.nodeId,
|
|
1593
|
-
role,
|
|
1594
|
-
index,
|
|
1595
|
-
})),
|
|
1596
|
-
regions: regions.map(region => ({
|
|
1597
|
-
kind: region.kind,
|
|
1598
|
-
inputNodeId: region.input.nodeId,
|
|
1599
|
-
frameNodeId: region.frame.nodeId,
|
|
1600
|
-
mesh: renderMesh(region.artifact, region.artifact.value, meshCache, this.meshTolerance),
|
|
1601
|
-
})),
|
|
1602
|
-
selections: selections.map(selection => ({
|
|
1603
|
-
kind: selection.kind,
|
|
1604
|
-
inputNodeId: selection.input.nodeId,
|
|
1605
|
-
ids: [...selection.ids],
|
|
1606
|
-
transform: toTransform(selection.transform),
|
|
1607
|
-
})),
|
|
1608
|
-
sourceRef,
|
|
1609
|
-
spatial: this.operation.spatial,
|
|
1610
|
-
dimensions: this.operation.dimensions,
|
|
1611
|
-
};
|
|
1612
|
-
}
|
|
1613
|
-
copyWithGeometry(geometry, overrides, operation) {
|
|
1614
|
-
try {
|
|
1615
|
-
return this.copy({ ...overrides, geometry }, operation);
|
|
1616
|
-
}
|
|
1617
|
-
catch (error) {
|
|
1618
|
-
disposeModelGeometryValue(geometry.value);
|
|
1619
|
-
throw error;
|
|
1620
|
-
}
|
|
1621
|
-
}
|
|
1622
|
-
copy(overrides, operation) {
|
|
1623
|
-
return ModelObject.create({
|
|
1624
|
-
kind: this.kind,
|
|
1625
|
-
geometry: this.geometry,
|
|
1626
|
-
geometryAnchor: this.geometryAnchor,
|
|
1627
|
-
name: this.name,
|
|
1628
|
-
material: this.materialSnapshot,
|
|
1629
|
-
children: this.children,
|
|
1630
|
-
assembly: this.assembly,
|
|
1631
|
-
constraints: this.constraints,
|
|
1632
|
-
elements: this.elements,
|
|
1633
|
-
sourceRefs: this.sourceRefs,
|
|
1634
|
-
parameters: this.parameters,
|
|
1635
|
-
meshTolerance: this.meshTolerance,
|
|
1636
|
-
operation,
|
|
1637
|
-
...overrides,
|
|
1638
|
-
});
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1641
|
-
export function circle(radius = 5) {
|
|
1642
|
-
assertPositive('radius', radius);
|
|
1643
|
-
return planarFaceModel('circle', 'Circle', [radius], () => sketchCircle(radius, { plane: 'XZ' }));
|
|
1644
|
-
}
|
|
1645
|
-
export function ellipse(xRadius = 5, zRadius = 3) {
|
|
1646
|
-
assertPositive('xRadius', xRadius);
|
|
1647
|
-
assertPositive('zRadius', zRadius);
|
|
1648
|
-
return planarFaceModel('ellipse', 'Ellipse', [xRadius, zRadius], () => sketchEllipse(xRadius, zRadius, { plane: 'XZ' }));
|
|
1649
|
-
}
|
|
1650
|
-
export function rectangle(x = 10, z = 10) {
|
|
1651
|
-
assertPositive('x', x);
|
|
1652
|
-
assertPositive('z', z);
|
|
1653
|
-
return planarFaceModel('rectangle', 'Rectangle', [x, z], () => sketchRectangle(x, z, { plane: 'XZ' }));
|
|
1654
|
-
}
|
|
1655
|
-
export function regularPolygon(radius = 5, sides = 6, rotation = 0) {
|
|
1656
|
-
assertPositive('radius', radius);
|
|
1657
|
-
if (!Number.isInteger(sides) || sides < 3) {
|
|
1658
|
-
throw new Error('sides must be an integer greater than or equal to 3.');
|
|
1659
|
-
}
|
|
1660
|
-
if (!Number.isFinite(rotation)) {
|
|
1661
|
-
throw new Error('rotation must be a finite number.');
|
|
1662
|
-
}
|
|
1663
|
-
return planarFaceModel('regularPolygon', `${sides}-sided polygon`, [radius, sides, rotation], () => sketchPolysides(radius, sides, 0, { plane: 'XZ' }), face => rotation === 0 ? face : face.rotate(rotation, toPoint(origin), [0, 1, 0]));
|
|
1664
|
-
}
|
|
1665
|
-
/**
|
|
1666
|
-
* @code3d.param x {kind: 'length', label: 'X'}
|
|
1667
|
-
* @code3d.param y {kind: 'length', label: 'Y'}
|
|
1668
|
-
* @code3d.param z {kind: 'length', label: 'Z'}
|
|
1669
|
-
*/
|
|
1670
|
-
export function point([x, y, z] = origin) {
|
|
1671
|
-
const position = [x, y, z];
|
|
1672
|
-
assertFiniteVector('point', position);
|
|
1673
|
-
const geometry = evaluateModelGeometry('point', [position], [], () => ({
|
|
1674
|
-
shape: makeVertex(toPoint(position)),
|
|
1675
|
-
}));
|
|
1676
|
-
return ModelObject.create({
|
|
1677
|
-
kind: 'vertex',
|
|
1678
|
-
name: 'Point',
|
|
1679
|
-
geometry,
|
|
1680
|
-
geometryAnchor: { kind: 'point', transform: translation(position) },
|
|
1681
|
-
operation: storedOperation('point'),
|
|
1682
|
-
});
|
|
1683
|
-
}
|
|
1684
|
-
export function line(startOrEnd, end) {
|
|
1685
|
-
const start = end ? startOrEnd : origin;
|
|
1686
|
-
end ??= startOrEnd;
|
|
1687
|
-
assertCurvePoints('line', [start, end], 2);
|
|
1688
|
-
return curveModel('line', 'Line', [start, end], () => makeLine(toPoint(start), toPoint(end)));
|
|
1689
|
-
}
|
|
1690
|
-
export function arc(start, middle, end) {
|
|
1691
|
-
assertCurvePoints('arc', [start, middle, end], 3);
|
|
1692
|
-
return curveModel('arc', 'Arc', [start, middle, end], () => makeThreePointArc(toPoint(start), toPoint(middle), toPoint(end)));
|
|
1693
|
-
}
|
|
1694
|
-
export function bezier(points) {
|
|
1695
|
-
assertCurvePoints('bezier', points, 2);
|
|
1696
|
-
return curveModel('bezier', 'Bezier curve', points, () => makeBezierCurve(points.map(toPoint)));
|
|
1697
|
-
}
|
|
1698
|
-
export function spline(points) {
|
|
1699
|
-
assertCurvePoints('spline', points, 2);
|
|
1700
|
-
return curveModel('spline', 'Spline', points, () => makeBSplineApproximation(points.map(toPoint)));
|
|
1701
|
-
}
|
|
1702
|
-
export function loft(sections, { spine, ruled = false } = {}) {
|
|
1703
|
-
if (sections.length < 2) {
|
|
1704
|
-
throw new Error('loft requires at least two planar sections.');
|
|
1705
|
-
}
|
|
1706
|
-
const runtimeSections = sections.map(section => requireModelKind(section, 'face', 'Every loft section must be a planar face model.'));
|
|
1707
|
-
const runtimeSpine = spine
|
|
1708
|
-
? requireModelKind(spine, 'edge', 'A loft spine must be a curve model.')
|
|
1709
|
-
: undefined;
|
|
1710
|
-
const [first, ...others] = runtimeSections;
|
|
1711
|
-
return first[loftModels](others, runtimeSpine, ruled);
|
|
1712
|
-
}
|
|
1713
|
-
export function box(x = 10, y = 10, z = 10) {
|
|
1714
|
-
assertPositive('x', x);
|
|
1715
|
-
assertPositive('y', y);
|
|
1716
|
-
assertPositive('z', z);
|
|
1717
|
-
return ModelObject.create({
|
|
1718
|
-
kind: 'solid',
|
|
1719
|
-
name: 'Box',
|
|
1720
|
-
geometry: evaluateSolidGeometry('box', [x, y, z], [], () => ({
|
|
1721
|
-
shape: centeredBoxShape(x, y, z),
|
|
1722
|
-
})),
|
|
1723
|
-
elements: solidElements([
|
|
1724
|
-
[0, -y / 2, 0],
|
|
1725
|
-
[0, y / 2, 0],
|
|
1726
|
-
]),
|
|
1727
|
-
operation: storedOperation('box', [], {
|
|
1728
|
-
dimensions: {
|
|
1729
|
-
x: { origin: [-x / 2, -y / 2, -z / 2], vector: [x, 0, 0] },
|
|
1730
|
-
y: { origin: [-x / 2, -y / 2, -z / 2], vector: [0, y, 0] },
|
|
1731
|
-
z: { origin: [-x / 2, -y / 2, -z / 2], vector: [0, 0, z] },
|
|
1732
|
-
},
|
|
1733
|
-
}),
|
|
1734
|
-
});
|
|
1735
|
-
}
|
|
1736
|
-
export function cylinder(radius = 5, y = 10) {
|
|
1737
|
-
assertPositive('radius', radius);
|
|
1738
|
-
assertPositive('y', y);
|
|
1739
|
-
return ModelObject.create({
|
|
1740
|
-
kind: 'solid',
|
|
1741
|
-
name: 'Cylinder',
|
|
1742
|
-
geometry: evaluateSolidGeometry('cylinder', [radius, y], [], () => ({
|
|
1743
|
-
shape: makeCylinder(radius, y, [0, -y / 2, 0], [0, 1, 0]),
|
|
1744
|
-
})),
|
|
1745
|
-
elements: solidElements([
|
|
1746
|
-
[0, -y / 2, 0],
|
|
1747
|
-
[0, y / 2, 0],
|
|
1748
|
-
]),
|
|
1749
|
-
operation: storedOperation('cylinder'),
|
|
1750
|
-
});
|
|
1751
|
-
}
|
|
1752
|
-
export function tube(outerRadius = 5, innerRadius = 3, y = 10) {
|
|
1753
|
-
assertPositive('outerRadius', outerRadius);
|
|
1754
|
-
assertPositive('innerRadius', innerRadius);
|
|
1755
|
-
assertPositive('y', y);
|
|
1756
|
-
if (innerRadius >= outerRadius) {
|
|
1757
|
-
throw new Error('innerRadius must be smaller than outerRadius.');
|
|
1758
|
-
}
|
|
1759
|
-
return ModelObject.create({
|
|
1760
|
-
kind: 'solid',
|
|
1761
|
-
name: 'Tube',
|
|
1762
|
-
geometry: evaluateSolidGeometry('tube', [outerRadius, innerRadius, y], [], () => {
|
|
1763
|
-
const outer = sketchCircle(outerRadius, {
|
|
1764
|
-
plane: 'XZ',
|
|
1765
|
-
origin: [0, -y / 2, 0],
|
|
1766
|
-
});
|
|
1767
|
-
const inner = sketchCircle(innerRadius, {
|
|
1768
|
-
plane: 'XZ',
|
|
1769
|
-
origin: [0, -y / 2, 0],
|
|
1770
|
-
});
|
|
1771
|
-
// Hole wires run opposite to the outer boundary.
|
|
1772
|
-
inner.wire.wrapped.Reverse();
|
|
1773
|
-
const section = makeFace(outer.wire, [inner.wire]);
|
|
1774
|
-
const direction = new Vector([0, y, 0]);
|
|
1775
|
-
try {
|
|
1776
|
-
return { shape: basicFaceExtrusion(section, direction) };
|
|
1777
|
-
}
|
|
1778
|
-
finally {
|
|
1779
|
-
direction.delete();
|
|
1780
|
-
section.delete();
|
|
1781
|
-
inner.delete();
|
|
1782
|
-
outer.delete();
|
|
1783
|
-
}
|
|
1784
|
-
}),
|
|
1785
|
-
elements: solidElements([
|
|
1786
|
-
[0, -y / 2, 0],
|
|
1787
|
-
[0, y / 2, 0],
|
|
1788
|
-
]),
|
|
1789
|
-
operation: storedOperation('tube'),
|
|
1790
|
-
});
|
|
1791
|
-
}
|
|
1792
|
-
export function coil(coilRadius = 5, wireRadius = 1, pitch = 3, turns = 3) {
|
|
1793
|
-
assertPositive('coilRadius', coilRadius);
|
|
1794
|
-
assertPositive('wireRadius', wireRadius);
|
|
1795
|
-
assertPositive('pitch', pitch);
|
|
1796
|
-
assertPositive('turns', turns);
|
|
1797
|
-
if (wireRadius >= coilRadius) {
|
|
1798
|
-
throw new Error('wireRadius must be smaller than coilRadius.');
|
|
1799
|
-
}
|
|
1800
|
-
if (pitch <= 2 * wireRadius) {
|
|
1801
|
-
throw new Error('pitch must be greater than the wire diameter.');
|
|
1802
|
-
}
|
|
1803
|
-
assertCoilClearance(coilRadius, wireRadius, pitch, turns);
|
|
1804
|
-
const y = pitch * turns;
|
|
1805
|
-
assertPositive('pitch * turns', y);
|
|
1806
|
-
const geometry = evaluateSolidGeometry('coil', [coilRadius, wireRadius, pitch, turns], [], () => {
|
|
1807
|
-
const spine = makeHelix(pitch, y, coilRadius, [0, -y / 2, 0], [0, 1, 0]);
|
|
1808
|
-
const start = spine.pointAt(0);
|
|
1809
|
-
const tangent = spine.tangentAt(0);
|
|
1810
|
-
const circle = makeCircle(wireRadius, start, tangent);
|
|
1811
|
-
const section = assembleWire([circle]);
|
|
1812
|
-
try {
|
|
1813
|
-
return { shape: genericSweep(section, spine, { frenet: true }) };
|
|
1814
|
-
}
|
|
1815
|
-
finally {
|
|
1816
|
-
section.delete();
|
|
1817
|
-
circle.delete();
|
|
1818
|
-
tangent.delete();
|
|
1819
|
-
start.delete();
|
|
1820
|
-
spine.delete();
|
|
1821
|
-
}
|
|
1822
|
-
});
|
|
1823
|
-
// A fractional turn has asymmetric X/Z bounds, but its axis is still Y.
|
|
1824
|
-
// The circular end sections extend beyond the centerline's Y interval.
|
|
1825
|
-
const circumference = 2 * Math.PI * coilRadius;
|
|
1826
|
-
const halfHeight = y / 2 + wireRadius * (circumference / Math.hypot(circumference, pitch));
|
|
1827
|
-
return ModelObject.create({
|
|
1828
|
-
kind: 'solid',
|
|
1829
|
-
name: 'Coil',
|
|
1830
|
-
geometry,
|
|
1831
|
-
elements: solidElements([
|
|
1832
|
-
[0, -halfHeight, 0],
|
|
1833
|
-
[0, halfHeight, 0],
|
|
1834
|
-
]),
|
|
1835
|
-
operation: storedOperation('coil'),
|
|
1836
|
-
});
|
|
1837
|
-
}
|
|
1838
|
-
function assertCoilClearance(radius, wireRadius, pitch, turns) {
|
|
1839
|
-
// Neighboring turns approach obliquely: pitch alone overestimates clearance.
|
|
1840
|
-
// For angular separation t, squared centerline distance is
|
|
1841
|
-
// 2 R² (1 - cos(t)) + (pitch * t / 2π)². Its only possible minimum
|
|
1842
|
-
// between half a turn and a full turn lies after the derivative's minimum.
|
|
1843
|
-
// Beyond a full turn, the Y separation already exceeds the wire diameter.
|
|
1844
|
-
if (turns <= 0.5)
|
|
1845
|
-
return;
|
|
1846
|
-
const fullTurn = 2 * Math.PI;
|
|
1847
|
-
const slopeSquared = (pitch / (fullTurn * radius)) ** 2;
|
|
1848
|
-
if (slopeSquared >= 1)
|
|
1849
|
-
return;
|
|
1850
|
-
let lower = fullTurn - Math.acos(-slopeSquared);
|
|
1851
|
-
if (Math.sin(lower) + slopeSquared * lower >= 0)
|
|
1852
|
-
return;
|
|
1853
|
-
let upper = fullTurn;
|
|
1854
|
-
for (let iteration = 0; iteration < 48; iteration += 1) {
|
|
1855
|
-
const middle = (lower + upper) / 2;
|
|
1856
|
-
if (Math.sin(middle) + slopeSquared * middle < 0)
|
|
1857
|
-
lower = middle;
|
|
1858
|
-
else
|
|
1859
|
-
upper = middle;
|
|
1860
|
-
}
|
|
1861
|
-
const separation = Math.min(fullTurn * turns, (lower + upper) / 2);
|
|
1862
|
-
const distance = Math.hypot(2 * radius * Math.sin(separation / 2), (pitch * separation) / fullTurn);
|
|
1863
|
-
if (distance <= 2 * wireRadius) {
|
|
1864
|
-
throw new Error('Coil turns must not touch or overlap; increase pitch or decrease wireRadius.');
|
|
1865
|
-
}
|
|
1866
|
-
}
|
|
1867
|
-
export function sphere(radius = 5) {
|
|
1868
|
-
assertPositive('radius', radius);
|
|
1869
|
-
return ModelObject.create({
|
|
1870
|
-
kind: 'solid',
|
|
1871
|
-
name: 'Sphere',
|
|
1872
|
-
geometry: evaluateSolidGeometry('sphere', [radius], [], () => ({
|
|
1873
|
-
shape: makeSphere(radius),
|
|
1874
|
-
})),
|
|
1875
|
-
elements: solidElements([
|
|
1876
|
-
[0, -radius, 0],
|
|
1877
|
-
[0, radius, 0],
|
|
1878
|
-
]),
|
|
1879
|
-
operation: storedOperation('sphere'),
|
|
1880
|
-
});
|
|
1881
|
-
}
|
|
1882
|
-
export function frustum(bottomRadius = 5, topRadius = 3, y = 10) {
|
|
1883
|
-
assertPositive('bottomRadius', bottomRadius);
|
|
1884
|
-
assertPositive('topRadius', topRadius);
|
|
1885
|
-
assertPositive('y', y);
|
|
1886
|
-
return ModelObject.create({
|
|
1887
|
-
kind: 'solid',
|
|
1888
|
-
name: 'Frustum',
|
|
1889
|
-
geometry: evaluateSolidGeometry('frustum', [bottomRadius, topRadius, y], [], () => {
|
|
1890
|
-
const bottom = sketchCircle(bottomRadius, {
|
|
1891
|
-
plane: 'XZ',
|
|
1892
|
-
origin: [0, -y / 2, 0],
|
|
1893
|
-
});
|
|
1894
|
-
const top = sketchCircle(topRadius, {
|
|
1895
|
-
plane: 'XZ',
|
|
1896
|
-
origin: [0, y / 2, 0],
|
|
1897
|
-
});
|
|
1898
|
-
return { shape: bottom.loftWith(top, { ruled: true }) };
|
|
1899
|
-
}),
|
|
1900
|
-
elements: solidElements([
|
|
1901
|
-
[0, -y / 2, 0],
|
|
1902
|
-
[0, y / 2, 0],
|
|
1903
|
-
]),
|
|
1904
|
-
operation: storedOperation('frustum'),
|
|
1905
|
-
});
|
|
1906
|
-
}
|
|
1907
|
-
export function regularPrism(radius = 5, y = 10, sides = 6, rotation = 0) {
|
|
1908
|
-
assertPositive('radius', radius);
|
|
1909
|
-
assertPositive('y', y);
|
|
1910
|
-
if (!Number.isInteger(sides) || sides < 3) {
|
|
1911
|
-
throw new Error('sides must be an integer greater than or equal to 3.');
|
|
1912
|
-
}
|
|
1913
|
-
if (!Number.isFinite(rotation)) {
|
|
1914
|
-
throw new Error('rotation must be a finite number.');
|
|
1915
|
-
}
|
|
1916
|
-
return ModelObject.create({
|
|
1917
|
-
kind: 'solid',
|
|
1918
|
-
name: `${sides}-sided prism`,
|
|
1919
|
-
geometry: evaluateSolidGeometry('regular-prism', [radius, y, sides, rotation], [], () => {
|
|
1920
|
-
const sketch = sketchPolysides(radius, sides, 0, {
|
|
1921
|
-
plane: 'XZ',
|
|
1922
|
-
origin: [0, -y / 2, 0],
|
|
1923
|
-
});
|
|
1924
|
-
let shape = sketch.extrude(y, {
|
|
1925
|
-
extrusionDirection: [0, 1, 0],
|
|
1926
|
-
});
|
|
1927
|
-
if (rotation !== 0) {
|
|
1928
|
-
shape = shape.rotate(rotation, [0, 0, 0], [0, 1, 0]);
|
|
1929
|
-
}
|
|
1930
|
-
return { shape };
|
|
1931
|
-
}),
|
|
1932
|
-
elements: solidElements([
|
|
1933
|
-
[0, -y / 2, 0],
|
|
1934
|
-
[0, y / 2, 0],
|
|
1935
|
-
]),
|
|
1936
|
-
operation: storedOperation('regularPrism'),
|
|
1937
|
-
});
|
|
1938
|
-
}
|
|
1939
|
-
/** @internal */
|
|
1940
|
-
export function modelFromReplicadSolid(shape) {
|
|
1941
|
-
let solid;
|
|
1942
|
-
try {
|
|
1943
|
-
solid = normalizeReplicadSolid(shape);
|
|
1944
|
-
}
|
|
1945
|
-
catch (error) {
|
|
1946
|
-
shape.delete();
|
|
1947
|
-
throw error;
|
|
1948
|
-
}
|
|
1949
|
-
// The builder still executes on every call. Cache its actual output, not its
|
|
1950
|
-
// arguments: captured state may change the geometry between invocations.
|
|
1951
|
-
let adopted = false;
|
|
1952
|
-
let geometry;
|
|
1953
|
-
try {
|
|
1954
|
-
geometry = evaluateSolidGeometry('replicad-solid', [solid.serialize()], [], () => {
|
|
1955
|
-
adopted = true;
|
|
1956
|
-
return { shape: solid };
|
|
1957
|
-
});
|
|
1958
|
-
}
|
|
1959
|
-
finally {
|
|
1960
|
-
// A hit returns an independently owned cached copy; discard this output.
|
|
1961
|
-
// A miss transfers ownership to evaluateSolidGeometry, including on error.
|
|
1962
|
-
if (!adopted)
|
|
1963
|
-
solid.delete();
|
|
1964
|
-
}
|
|
1965
|
-
return ModelObject.create({
|
|
1966
|
-
kind: 'solid',
|
|
1967
|
-
name: 'Custom primitive',
|
|
1968
|
-
geometry,
|
|
1969
|
-
operation: storedOperation('primitive'),
|
|
1970
|
-
});
|
|
1971
|
-
}
|
|
1972
|
-
function normalizeReplicadSolid(shape) {
|
|
1973
|
-
const oc = getOC();
|
|
1974
|
-
const type = shape.wrapped.ShapeType();
|
|
1975
|
-
if (type === oc.TopAbs_ShapeEnum.TopAbs_SOLID)
|
|
1976
|
-
return shape;
|
|
1977
|
-
if (type !== oc.TopAbs_ShapeEnum.TopAbs_COMPSOLID &&
|
|
1978
|
-
type !== oc.TopAbs_ShapeEnum.TopAbs_COMPOUND) {
|
|
1979
|
-
throw new Error('A primitive builder must return exactly one OpenCascade solid.');
|
|
1980
|
-
}
|
|
1981
|
-
const containmentLayers = [
|
|
1982
|
-
[oc.TopAbs_ShapeEnum.TopAbs_SHELL, oc.TopAbs_ShapeEnum.TopAbs_SOLID],
|
|
1983
|
-
[oc.TopAbs_ShapeEnum.TopAbs_FACE, oc.TopAbs_ShapeEnum.TopAbs_SHELL],
|
|
1984
|
-
[oc.TopAbs_ShapeEnum.TopAbs_WIRE, oc.TopAbs_ShapeEnum.TopAbs_FACE],
|
|
1985
|
-
[oc.TopAbs_ShapeEnum.TopAbs_EDGE, oc.TopAbs_ShapeEnum.TopAbs_WIRE],
|
|
1986
|
-
[oc.TopAbs_ShapeEnum.TopAbs_VERTEX, oc.TopAbs_ShapeEnum.TopAbs_EDGE],
|
|
1987
|
-
];
|
|
1988
|
-
for (const [find, avoid] of containmentLayers) {
|
|
1989
|
-
const outsideSolid = new oc.TopExp_Explorer(shape.wrapped, find, avoid);
|
|
1990
|
-
try {
|
|
1991
|
-
if (outsideSolid.More()) {
|
|
1992
|
-
throw new Error('A primitive builder must return exactly one OpenCascade solid.');
|
|
1993
|
-
}
|
|
1994
|
-
}
|
|
1995
|
-
finally {
|
|
1996
|
-
outsideSolid.delete();
|
|
1997
|
-
}
|
|
1998
|
-
}
|
|
1999
|
-
const solids = new oc.TopExp_Explorer(shape.wrapped, oc.TopAbs_ShapeEnum.TopAbs_SOLID);
|
|
2000
|
-
let solid;
|
|
2001
|
-
try {
|
|
2002
|
-
if (!solids.More()) {
|
|
2003
|
-
throw new Error('A primitive builder must return exactly one OpenCascade solid.');
|
|
2004
|
-
}
|
|
2005
|
-
solid = castOwnedShape3D(solids.Current());
|
|
2006
|
-
solids.Next();
|
|
2007
|
-
if (solids.More()) {
|
|
2008
|
-
solid.delete();
|
|
2009
|
-
solid = undefined;
|
|
2010
|
-
throw new Error('A primitive builder must return exactly one OpenCascade solid.');
|
|
2011
|
-
}
|
|
2012
|
-
}
|
|
2013
|
-
finally {
|
|
2014
|
-
solids.delete();
|
|
2015
|
-
}
|
|
2016
|
-
shape.delete();
|
|
2017
|
-
return solid;
|
|
2018
|
-
}
|
|
2019
|
-
export function group(children, name = 'Group') {
|
|
2020
|
-
const runtimeChildren = children.map(child => requireModelObject(child, 'Every group child must be a model.'));
|
|
2021
|
-
return ModelObject.create({
|
|
2022
|
-
kind: 'group',
|
|
2023
|
-
name,
|
|
2024
|
-
children: runtimeChildren,
|
|
2025
|
-
operation: storedOperation('group', runtimeChildren.map((model, index) => ({
|
|
2026
|
-
model,
|
|
2027
|
-
role: 'child',
|
|
2028
|
-
index,
|
|
2029
|
-
}))),
|
|
2030
|
-
});
|
|
2031
|
-
}
|
|
2032
|
-
export function union(operands) {
|
|
2033
|
-
const { first, others } = booleanOperands('union', operands);
|
|
2034
|
-
return first[combineModels]('fuse', others);
|
|
2035
|
-
}
|
|
2036
|
-
/**
|
|
2037
|
-
* Extrudes a single face; use faces.map(face => extrude(face, distance)) for multiple regions.
|
|
2038
|
-
* @code3d.param distance {kind: 'length', label: 'Extrusion distance'}
|
|
2039
|
-
*/
|
|
2040
|
-
export function extrude(face, distance) {
|
|
2041
|
-
return requireModelKind(face, 'face', 'extrude requires a single face model.').extrude(distance);
|
|
2042
|
-
}
|
|
2043
|
-
export function cut(stock, tools) {
|
|
2044
|
-
const runtimeStock = requireModelKind(stock, 'solid', 'The cut stock must be a solid model.');
|
|
2045
|
-
if (tools.length === 0) {
|
|
2046
|
-
throw new Error('cut requires at least one tool.');
|
|
2047
|
-
}
|
|
2048
|
-
const runtimeTools = tools.map(tool => requireModelKind(tool, 'solid', 'Every cut tool must be a solid model.'));
|
|
2049
|
-
return runtimeStock[combineModels]('cut', runtimeTools);
|
|
2050
|
-
}
|
|
2051
|
-
export function intersect(operands) {
|
|
2052
|
-
const { first, others } = booleanOperands('intersect', operands);
|
|
2053
|
-
return first[combineModels]('intersect', others);
|
|
2054
|
-
}
|
|
2055
|
-
export function isModelObject(value) {
|
|
2056
|
-
return value instanceof ModelObject;
|
|
2057
|
-
}
|
|
2058
|
-
export function isConstraint(value) {
|
|
2059
|
-
return value instanceof Constraint;
|
|
2060
|
-
}
|
|
2061
|
-
export function isConstraintExpression(value) {
|
|
2062
|
-
return value instanceof ConstraintExpression;
|
|
2063
|
-
}
|
|
2064
|
-
export function constraintPreview(value) {
|
|
2065
|
-
return value.preview();
|
|
2066
|
-
}
|
|
2067
|
-
export function instrumentConstraint(constraint, sourceRef, parameters) {
|
|
2068
|
-
constraint.attachSource(sourceRef);
|
|
2069
|
-
constraint.attachParameters(parameters);
|
|
2070
|
-
}
|
|
2071
|
-
export function instrumentModelOperation(object, instrumentation) {
|
|
2072
|
-
object.attachSource(instrumentation.sourceRef);
|
|
2073
|
-
object.attachParameters(instrumentation.parameters);
|
|
2074
|
-
object.attachOperationTrace(instrumentation.siteId, instrumentation.execution, instrumentation.order, instrumentation.sourceRef);
|
|
2075
|
-
}
|
|
2076
|
-
export function constraintTraceReference(constraint) {
|
|
2077
|
-
return constraint.traceReference();
|
|
2078
|
-
}
|
|
2079
|
-
export function modelObjectRuntimeInfo(object) {
|
|
2080
|
-
return {
|
|
2081
|
-
nodeId: object.nodeId,
|
|
2082
|
-
name: object.name,
|
|
2083
|
-
sourceRefs: object.sourceRefs,
|
|
2084
|
-
};
|
|
2085
|
-
}
|
|
2086
|
-
export function relatedModelObjects(object) {
|
|
2087
|
-
return object.relatedObjects();
|
|
2088
|
-
}
|
|
2089
|
-
function boundsQuery(geometry, transform, selection = { kind: 'solid' }, scale = 1) {
|
|
2090
|
-
return {
|
|
2091
|
-
inputId: geometry.id,
|
|
2092
|
-
geometry: { shape: geometry.value.shape, topology: geometry.value.topology },
|
|
2093
|
-
query: {
|
|
2094
|
-
kind: 'bounds',
|
|
2095
|
-
transform,
|
|
2096
|
-
selection,
|
|
2097
|
-
scale,
|
|
2098
|
-
key: kernelOperationKey('transformed-bounds', [
|
|
2099
|
-
transform.position,
|
|
2100
|
-
transform.quaternion,
|
|
2101
|
-
selection.kind,
|
|
2102
|
-
selection.kind === 'solid' ? null : selection.id,
|
|
2103
|
-
scale,
|
|
2104
|
-
], [geometry]),
|
|
2105
|
-
},
|
|
2106
|
-
};
|
|
2107
|
-
}
|
|
2108
|
-
function meshQuery(artifact, shape, tolerance, topology) {
|
|
2109
|
-
return {
|
|
2110
|
-
inputId: artifact.id,
|
|
2111
|
-
geometry: { shape, topology },
|
|
2112
|
-
query: {
|
|
2113
|
-
kind: 'mesh',
|
|
2114
|
-
tolerance,
|
|
2115
|
-
topology: topology !== undefined,
|
|
2116
|
-
key: kernelOperationKey('render-mesh', [tolerance, 0.2, topology !== undefined], [artifact]),
|
|
2117
|
-
},
|
|
2118
|
-
};
|
|
2119
|
-
}
|
|
2120
|
-
const queryLifecycle = {
|
|
2121
|
-
estimateBytes: estimateRetainedBytes,
|
|
2122
|
-
retain: value => value,
|
|
2123
|
-
instantiate: value => value,
|
|
2124
|
-
release: () => { },
|
|
2125
|
-
};
|
|
2126
|
-
function computeSnapshotQuery(geometry, query) {
|
|
2127
|
-
return query.kind === 'bounds'
|
|
2128
|
-
? computeTransformedBounds(geometry, query)
|
|
2129
|
-
: computeRenderMesh(geometry.shape, query.tolerance, query.topology ? geometry.topology : undefined);
|
|
2130
|
-
}
|
|
2131
|
-
function evaluateSnapshotQuery(input) {
|
|
2132
|
-
const cached = findKernelOperation(input.query.key, queryLifecycle);
|
|
2133
|
-
if (cached)
|
|
2134
|
-
return cached.value;
|
|
2135
|
-
let value;
|
|
2136
|
-
if (input.query.kind === 'mesh') {
|
|
2137
|
-
// Mesh from the serialized input in every execution mode. BinTools can
|
|
2138
|
-
// renormalize a plane axis by a few ULPs, changing Delaunay diagonal ties.
|
|
2139
|
-
// A local query and a remote query must therefore consume the same BREP.
|
|
2140
|
-
executeSnapshotQueryBatch(input.inputId, encodeKernelArtifact(input.inputId, input.geometry), [input.query], () => { }, (_, result) => {
|
|
2141
|
-
value = result;
|
|
2142
|
-
});
|
|
2143
|
-
}
|
|
2144
|
-
else
|
|
2145
|
-
value = computeSnapshotQuery(input.geometry, input.query);
|
|
2146
|
-
acceptKernelOperation(input.query.key, queryLifecycle, value);
|
|
2147
|
-
return value;
|
|
2148
|
-
}
|
|
2149
|
-
export function planModelSnapshotQueries(objects) {
|
|
2150
|
-
const visited = new Set();
|
|
2151
|
-
const keys = new Map();
|
|
2152
|
-
const batches = new Map();
|
|
2153
|
-
function collect(object) {
|
|
2154
|
-
if (visited.has(object))
|
|
2155
|
-
return;
|
|
2156
|
-
visited.add(object);
|
|
2157
|
-
for (const input of object[modelSnapshotQueries]()) {
|
|
2158
|
-
const { query, inputId } = input;
|
|
2159
|
-
const signature = keys.get(query.key.id);
|
|
2160
|
-
if (signature) {
|
|
2161
|
-
if (signature !== query.key.signature)
|
|
2162
|
-
throw new Error(`Kernel operation cache identity collision: ${query.key.id}`);
|
|
2163
|
-
continue;
|
|
2164
|
-
}
|
|
2165
|
-
keys.set(query.key.id, query.key.signature);
|
|
2166
|
-
if (findKernelOperation(query.key, queryLifecycle))
|
|
2167
|
-
continue;
|
|
2168
|
-
let batch = batches.get(inputId);
|
|
2169
|
-
if (!batch) {
|
|
2170
|
-
batch = { input, queries: [], sourceRef: object.sourceRefs.at(-1) };
|
|
2171
|
-
batches.set(inputId, batch);
|
|
2172
|
-
}
|
|
2173
|
-
batch.queries.push(query);
|
|
2174
|
-
}
|
|
2175
|
-
object.children.forEach(collect);
|
|
2176
|
-
}
|
|
2177
|
-
objects.forEach(collect);
|
|
2178
|
-
return [...batches].map(([id, { input, queries, sourceRef }]) => ({
|
|
2179
|
-
id,
|
|
2180
|
-
queries,
|
|
2181
|
-
sourceRef,
|
|
2182
|
-
weight: (1 +
|
|
2183
|
-
(input.geometry.topology?.edges.ids.length ?? 0) +
|
|
2184
|
-
(input.geometry.topology?.surfaces.ids.length ?? 0)) *
|
|
2185
|
-
queries.length,
|
|
2186
|
-
encode: () => encodeKernelArtifact(id, input.geometry),
|
|
2187
|
-
accept: (query, value) => acceptKernelOperation(query.key, queryLifecycle, value),
|
|
2188
|
-
}));
|
|
2189
|
-
}
|
|
2190
|
-
/** Each batch owns its restored shape; completed results leave before cancellation checks. */
|
|
2191
|
-
export function executeSnapshotQueryBatch(id, bytes, queries, checkCancelled, onResult, onRestore) {
|
|
2192
|
-
const started = performance.now();
|
|
2193
|
-
const geometry = decodeKernelArtifact(bytes, id);
|
|
2194
|
-
try {
|
|
2195
|
-
onRestore?.(performance.now() - started);
|
|
2196
|
-
for (const query of queries) {
|
|
2197
|
-
checkCancelled();
|
|
2198
|
-
onResult(query, computeSnapshotQuery(geometry, query));
|
|
2199
|
-
}
|
|
2200
|
-
}
|
|
2201
|
-
finally {
|
|
2202
|
-
geometry.shape.delete();
|
|
2203
|
-
}
|
|
2204
|
-
}
|
|
2205
|
-
export function createModelSnapshotter() {
|
|
2206
|
-
const meshCache = new Map();
|
|
2207
|
-
return object => object.toSnapshot(meshCache);
|
|
2208
|
-
}
|
|
2209
|
-
export function disposeModelObjects(objects) {
|
|
2210
|
-
const disposed = new Set();
|
|
2211
|
-
for (const object of objects) {
|
|
2212
|
-
object.disposeShape(disposed);
|
|
2213
|
-
}
|
|
2214
|
-
}
|
|
2215
|
-
export function retainModelGeometry(objects) {
|
|
2216
|
-
const retained = new Map();
|
|
2217
|
-
const shapes = new Map();
|
|
2218
|
-
const topologies = new Map();
|
|
2219
|
-
const dispose = () => {
|
|
2220
|
-
for (const shape of retained.values())
|
|
2221
|
-
shape.delete();
|
|
2222
|
-
retained.clear();
|
|
2223
|
-
shapes.clear();
|
|
2224
|
-
topologies.clear();
|
|
2225
|
-
};
|
|
2226
|
-
try {
|
|
2227
|
-
for (const object of objects) {
|
|
2228
|
-
const geometry = object[modelGeometry]()?.value;
|
|
2229
|
-
const original = geometry?.shape;
|
|
2230
|
-
if (!original)
|
|
2231
|
-
continue;
|
|
2232
|
-
let shape = retained.get(original);
|
|
2233
|
-
if (!shape) {
|
|
2234
|
-
shape = original.clone();
|
|
2235
|
-
retained.set(original, shape);
|
|
2236
|
-
}
|
|
2237
|
-
shapes.set(object.nodeId, shape);
|
|
2238
|
-
topologies.set(object.nodeId, geometry.topology);
|
|
2239
|
-
}
|
|
2240
|
-
return {
|
|
2241
|
-
shapes,
|
|
2242
|
-
dispose,
|
|
2243
|
-
inspect(nodeId, options) {
|
|
2244
|
-
const shape = shapes.get(nodeId);
|
|
2245
|
-
const topology = topologies.get(nodeId);
|
|
2246
|
-
if (!shape || !topology)
|
|
2247
|
-
throw new Error('The model geometry snapshot is unavailable.');
|
|
2248
|
-
return inspectShapeTopology(shape, topology, options);
|
|
2249
|
-
},
|
|
2250
|
-
};
|
|
2251
|
-
}
|
|
2252
|
-
catch (error) {
|
|
2253
|
-
dispose();
|
|
2254
|
-
throw error;
|
|
2255
|
-
}
|
|
2256
|
-
}
|
|
2257
|
-
export const authoringApi = Object.freeze({
|
|
2258
|
-
sketch,
|
|
2259
|
-
circle,
|
|
2260
|
-
ellipse,
|
|
2261
|
-
extrude,
|
|
2262
|
-
rectangle,
|
|
2263
|
-
regularPolygon,
|
|
2264
|
-
point,
|
|
2265
|
-
line,
|
|
2266
|
-
arc,
|
|
2267
|
-
bezier,
|
|
2268
|
-
spline,
|
|
2269
|
-
loft,
|
|
2270
|
-
box,
|
|
2271
|
-
cylinder,
|
|
2272
|
-
tube,
|
|
2273
|
-
coil,
|
|
2274
|
-
sphere,
|
|
2275
|
-
frustum,
|
|
2276
|
-
regularPrism,
|
|
2277
|
-
group,
|
|
2278
|
-
union,
|
|
2279
|
-
cut,
|
|
2280
|
-
intersect,
|
|
2281
|
-
});
|
|
2282
|
-
function storedOperation(kind, inputs = [], options = {}) {
|
|
2283
|
-
return {
|
|
2284
|
-
runtimeId: `operation-${nextOperationId++}`,
|
|
2285
|
-
kind,
|
|
2286
|
-
inputs: [...inputs],
|
|
2287
|
-
regions: [...(options.regions ?? [])],
|
|
2288
|
-
selections: [...(options.selections ?? [])],
|
|
2289
|
-
dimensions: options.dimensions,
|
|
2290
|
-
};
|
|
2291
|
-
}
|
|
2292
|
-
function storedOperationId(operation) {
|
|
2293
|
-
const trace = operationTraces.get(operation);
|
|
2294
|
-
return trace
|
|
2295
|
-
? `${trace.siteId}:execution:${trace.execution}`
|
|
2296
|
-
: operation.runtimeId;
|
|
2297
|
-
}
|
|
2298
|
-
const shapeLifecycle = {
|
|
2299
|
-
estimateBytes: () => 64,
|
|
2300
|
-
retain: shape => shape.clone(),
|
|
2301
|
-
instantiate: shape => shape.clone(),
|
|
2302
|
-
release: shape => shape.delete(),
|
|
2303
|
-
};
|
|
2304
|
-
const modelGeometryLifecycle = {
|
|
2305
|
-
estimateBytes: geometry => 128 +
|
|
2306
|
-
estimateRetainedBytes([
|
|
2307
|
-
geometry.topology,
|
|
2308
|
-
geometry.localBounds,
|
|
2309
|
-
geometry.referenceBasis?.topology,
|
|
2310
|
-
geometry.referenceBasis?.transform,
|
|
2311
|
-
]),
|
|
2312
|
-
retain: cloneModelGeometryValue,
|
|
2313
|
-
instantiate: cloneModelGeometryValue,
|
|
2314
|
-
release: disposeModelGeometryValue,
|
|
2315
|
-
};
|
|
2316
|
-
function cloneModelGeometryValue(geometry) {
|
|
2317
|
-
return {
|
|
2318
|
-
...geometry,
|
|
2319
|
-
shape: geometry.shape.clone(),
|
|
2320
|
-
referenceBasis: geometry.referenceBasis
|
|
2321
|
-
? {
|
|
2322
|
-
...geometry.referenceBasis,
|
|
2323
|
-
shape: geometry.referenceBasis.shape.clone(),
|
|
2324
|
-
}
|
|
2325
|
-
: undefined,
|
|
2326
|
-
};
|
|
2327
|
-
}
|
|
2328
|
-
function disposeModelGeometryValue(geometry) {
|
|
2329
|
-
geometry.shape.delete();
|
|
2330
|
-
geometry.referenceBasis?.shape.delete();
|
|
2331
|
-
}
|
|
2332
|
-
function evaluateKernelShape(operation, arguments_, inputs, compute) {
|
|
2333
|
-
return evaluateKernelOperation(operation, arguments_, inputs, shapeLifecycle, compute);
|
|
2334
|
-
}
|
|
2335
|
-
function evaluateModelGeometry(operation, arguments_, inputs, compute) {
|
|
2336
|
-
return evaluateKernelOperation(operation, arguments_, inputs, modelGeometryLifecycle, () => {
|
|
2337
|
-
const result = compute();
|
|
2338
|
-
return {
|
|
2339
|
-
...createModelGeometryValue(result.shape, result.topology),
|
|
2340
|
-
referenceBasis: result.referenceBasis,
|
|
2341
|
-
};
|
|
2342
|
-
});
|
|
2343
|
-
}
|
|
2344
|
-
function createModelGeometryValue(shape, topology) {
|
|
2345
|
-
try {
|
|
2346
|
-
return {
|
|
2347
|
-
shape,
|
|
2348
|
-
topology: topology ?? initialShapeTopology(shape),
|
|
2349
|
-
localBounds: shapeBounds(shape),
|
|
2350
|
-
};
|
|
2351
|
-
}
|
|
2352
|
-
catch (error) {
|
|
2353
|
-
shape.delete();
|
|
2354
|
-
throw error;
|
|
2355
|
-
}
|
|
2356
|
-
}
|
|
2357
|
-
function evaluateSolidGeometry(operation, arguments_, inputs, compute) {
|
|
2358
|
-
return evaluateModelGeometry(operation, arguments_, inputs, compute);
|
|
2359
|
-
}
|
|
2360
|
-
function renderMesh(artifact, shape, cache, tolerance, topology) {
|
|
2361
|
-
const cached = cache.get(shape);
|
|
2362
|
-
if (cached) {
|
|
2363
|
-
return cached;
|
|
2364
|
-
}
|
|
2365
|
-
const mesh = evaluateSnapshotQuery(meshQuery(artifact, shape, tolerance, topology));
|
|
2366
|
-
cache.set(shape, mesh);
|
|
2367
|
-
return mesh;
|
|
2368
|
-
}
|
|
2369
|
-
function computeRenderMesh(shape, tolerance, topology) {
|
|
2370
|
-
const surface = shape.mesh({ tolerance, angularTolerance: 0.2 });
|
|
2371
|
-
const wire = shape.meshEdges({ tolerance, angularTolerance: 0.2 });
|
|
2372
|
-
const vertexData = topology
|
|
2373
|
-
? stableVertexData(shape, topology.vertices)
|
|
2374
|
-
: { positions: new Float32Array(), ids: [] };
|
|
2375
|
-
return {
|
|
2376
|
-
vertices: new Float32Array(surface.vertices),
|
|
2377
|
-
normals: new Float32Array(surface.normals),
|
|
2378
|
-
uvs: meshUVs(shape, surface.vertices.length / 3),
|
|
2379
|
-
triangles: new Uint32Array(surface.triangles),
|
|
2380
|
-
edges: new Float32Array(wire.lines),
|
|
2381
|
-
topologyVertices: vertexData.positions,
|
|
2382
|
-
vertexIds: vertexData.ids,
|
|
2383
|
-
surfaceGroups: topology
|
|
2384
|
-
? stableSurfaceGroups(shape, topology.surfaces, surface.faceGroups)
|
|
2385
|
-
: surface.faceGroups.map((group, index) => ({
|
|
2386
|
-
start: group.start,
|
|
2387
|
-
count: group.count,
|
|
2388
|
-
surfaceId: index + 1,
|
|
2389
|
-
})),
|
|
2390
|
-
edgeGroups: topology
|
|
2391
|
-
? stableEdgeGroups(shape, topology.edges, wire.edgeGroups)
|
|
2392
|
-
: wire.edgeGroups.map((group, index) => ({
|
|
2393
|
-
start: group.start,
|
|
2394
|
-
count: group.count,
|
|
2395
|
-
// Context-region edges have their own traversal namespace, just
|
|
2396
|
-
// like the surfaces above. Native handle hashes change on restore.
|
|
2397
|
-
edgeId: index + 1,
|
|
2398
|
-
})),
|
|
2399
|
-
};
|
|
2400
|
-
}
|
|
2401
|
-
function meshUVs(shape, vertexCount) {
|
|
2402
|
-
if (!vertexCount)
|
|
2403
|
-
return undefined;
|
|
2404
|
-
const faces = shape.faces;
|
|
2405
|
-
const location = new (getOC().TopLoc_Location)();
|
|
2406
|
-
const uvs = new Float32Array(vertexCount * 2);
|
|
2407
|
-
let offset = 0;
|
|
2408
|
-
try {
|
|
2409
|
-
// Match the native mesh extractor's face/node traversal exactly.
|
|
2410
|
-
for (const face of faces) {
|
|
2411
|
-
const triangulation = getOC().BRep_Tool.Triangulation(face.wrapped, location, 0);
|
|
2412
|
-
try {
|
|
2413
|
-
if (triangulation.isNull())
|
|
2414
|
-
continue;
|
|
2415
|
-
if (!triangulation.HasUVNodes())
|
|
2416
|
-
return undefined;
|
|
2417
|
-
const count = triangulation.NbNodes();
|
|
2418
|
-
let minU = Infinity, minV = Infinity, maxU = -Infinity, maxV = -Infinity;
|
|
2419
|
-
for (let index = 0; index < count; index++) {
|
|
2420
|
-
const uv = triangulation.UVNode(index + 1);
|
|
2421
|
-
try {
|
|
2422
|
-
const u = uv.X(), v = uv.Y();
|
|
2423
|
-
uvs[(offset + index) * 2] = u;
|
|
2424
|
-
uvs[(offset + index) * 2 + 1] = v;
|
|
2425
|
-
minU = Math.min(minU, u);
|
|
2426
|
-
maxU = Math.max(maxU, u);
|
|
2427
|
-
minV = Math.min(minV, v);
|
|
2428
|
-
maxV = Math.max(maxV, v);
|
|
2429
|
-
}
|
|
2430
|
-
finally {
|
|
2431
|
-
uv.delete();
|
|
2432
|
-
}
|
|
2433
|
-
}
|
|
2434
|
-
for (let index = 0; index < count; index++) {
|
|
2435
|
-
const base = (offset + index) * 2;
|
|
2436
|
-
uvs[base] = maxU === minU ? 0 : (uvs[base] - minU) / (maxU - minU);
|
|
2437
|
-
uvs[base + 1] =
|
|
2438
|
-
maxV === minV ? 0 : (uvs[base + 1] - minV) / (maxV - minV);
|
|
2439
|
-
}
|
|
2440
|
-
offset += count;
|
|
2441
|
-
}
|
|
2442
|
-
finally {
|
|
2443
|
-
triangulation.delete();
|
|
2444
|
-
}
|
|
2445
|
-
}
|
|
2446
|
-
return uvs;
|
|
2447
|
-
}
|
|
2448
|
-
finally {
|
|
2449
|
-
location.delete();
|
|
2450
|
-
faces.forEach(face => face.delete());
|
|
2451
|
-
}
|
|
2452
|
-
}
|
|
2453
|
-
/** Internal bridge from the kernel-independent sketch definition to model geometry. */
|
|
2454
|
-
export function sketchFaceModel(region) {
|
|
2455
|
-
const curves = [region.outer, ...region.holes].map(loop => loop.map(curve => curve.kind === 'line'
|
|
2456
|
-
? ['line', curve.points]
|
|
2457
|
-
: curve.kind === 'circle'
|
|
2458
|
-
? ['circle', curve.center, curve.radius]
|
|
2459
|
-
: ['arc', curve.center, curve.radius, curve.start, curve.sweep]));
|
|
2460
|
-
const geometry = evaluateModelGeometry('sketchFace', curves, [], () => ({
|
|
2461
|
-
shape: sketchRegionFace(region),
|
|
2462
|
-
}));
|
|
2463
|
-
const plane = {
|
|
2464
|
-
kind: 'face',
|
|
2465
|
-
transform: identityRigidTransform,
|
|
2466
|
-
};
|
|
2467
|
-
return ModelObject.create({
|
|
2468
|
-
kind: 'face',
|
|
2469
|
-
name: 'Sketch face',
|
|
2470
|
-
geometry,
|
|
2471
|
-
geometryAnchor: plane,
|
|
2472
|
-
elements: { plane },
|
|
2473
|
-
operation: storedOperation('sketchFace'),
|
|
2474
|
-
});
|
|
2475
|
-
}
|
|
2476
|
-
function planarFaceModel(operation, name, arguments_, buildSketch, transform) {
|
|
2477
|
-
const geometry = evaluateModelGeometry(operation, arguments_, [], () => {
|
|
2478
|
-
const sketch = buildSketch();
|
|
2479
|
-
try {
|
|
2480
|
-
const face = sketch.face();
|
|
2481
|
-
return { shape: transform?.(face) ?? face };
|
|
2482
|
-
}
|
|
2483
|
-
finally {
|
|
2484
|
-
sketch.delete();
|
|
2485
|
-
}
|
|
2486
|
-
});
|
|
2487
|
-
const plane = {
|
|
2488
|
-
kind: 'face',
|
|
2489
|
-
transform: identityRigidTransform,
|
|
2490
|
-
};
|
|
2491
|
-
return ModelObject.create({
|
|
2492
|
-
kind: 'face',
|
|
2493
|
-
name,
|
|
2494
|
-
geometry,
|
|
2495
|
-
geometryAnchor: plane,
|
|
2496
|
-
elements: {
|
|
2497
|
-
plane,
|
|
2498
|
-
},
|
|
2499
|
-
operation: storedOperation(operation),
|
|
2500
|
-
});
|
|
2501
|
-
}
|
|
2502
|
-
function curveModel(operation, name, arguments_, build) {
|
|
2503
|
-
const geometry = evaluateModelGeometry(operation, arguments_, [], () => ({
|
|
2504
|
-
shape: build(),
|
|
2505
|
-
}));
|
|
2506
|
-
const elements = curveElements(geometry.value.shape);
|
|
2507
|
-
return ModelObject.create({
|
|
2508
|
-
kind: 'edge',
|
|
2509
|
-
name,
|
|
2510
|
-
geometry,
|
|
2511
|
-
geometryAnchor: { ...elements.start, kind: 'line' },
|
|
2512
|
-
elements,
|
|
2513
|
-
operation: storedOperation(operation),
|
|
2514
|
-
});
|
|
2515
|
-
}
|
|
2516
|
-
function curveElements(curve) {
|
|
2517
|
-
return {
|
|
2518
|
-
start: curveAnchor(curve, 0),
|
|
2519
|
-
midpoint: curveAnchor(curve, 0.5),
|
|
2520
|
-
end: curveAnchor(curve, 1),
|
|
2521
|
-
};
|
|
2522
|
-
}
|
|
2523
|
-
function curveAnchor(curve, position) {
|
|
2524
|
-
const point = curve.pointAt(position);
|
|
2525
|
-
const tangent = curve.tangentAt(position);
|
|
2526
|
-
try {
|
|
2527
|
-
return {
|
|
2528
|
-
kind: 'point',
|
|
2529
|
-
transform: frameFromYAxis(point.toTuple(), tangent.toTuple()),
|
|
2530
|
-
};
|
|
2531
|
-
}
|
|
2532
|
-
finally {
|
|
2533
|
-
point.delete();
|
|
2534
|
-
tangent.delete();
|
|
2535
|
-
}
|
|
2536
|
-
}
|
|
2537
|
-
function buildLoftGeometry(sections, spine, ruled) {
|
|
2538
|
-
const inputs = [];
|
|
2539
|
-
let spineWire;
|
|
2540
|
-
try {
|
|
2541
|
-
for (const [index, section] of sections.entries()) {
|
|
2542
|
-
inputs.push({
|
|
2543
|
-
shape: shapeWithTransform(section.geometry.value.shape, section.transform),
|
|
2544
|
-
topology: section.geometry.value.topology,
|
|
2545
|
-
index: index + 1,
|
|
2546
|
-
});
|
|
2547
|
-
}
|
|
2548
|
-
if (spine) {
|
|
2549
|
-
const edge = shapeWithTransform(spine.geometry.value.shape, spine.transform);
|
|
2550
|
-
try {
|
|
2551
|
-
spineWire = assembleWire([edge]);
|
|
2552
|
-
}
|
|
2553
|
-
finally {
|
|
2554
|
-
edge.delete();
|
|
2555
|
-
}
|
|
2556
|
-
}
|
|
2557
|
-
return loftWithTopology(inputs, spineWire, ruled);
|
|
2558
|
-
}
|
|
2559
|
-
finally {
|
|
2560
|
-
inputs.forEach(input => input.shape.delete());
|
|
2561
|
-
spineWire?.delete();
|
|
2562
|
-
}
|
|
2563
|
-
}
|
|
2564
|
-
function shapeBounds(shape) {
|
|
2565
|
-
const boundingBox = new BoundingBox();
|
|
2566
|
-
try {
|
|
2567
|
-
// Use analytic geometry, independent of whether a viewport has meshed it.
|
|
2568
|
-
getOC().BRepBndLib.AddOptimal(shape.wrapped, boundingBox.wrapped, false, false);
|
|
2569
|
-
const [minimum, maximum] = boundingBox.bounds;
|
|
2570
|
-
return [
|
|
2571
|
-
[minimum[0], minimum[1], minimum[2]],
|
|
2572
|
-
[maximum[0], maximum[1], maximum[2]],
|
|
2573
|
-
];
|
|
2574
|
-
}
|
|
2575
|
-
finally {
|
|
2576
|
-
boundingBox.delete();
|
|
2577
|
-
}
|
|
2578
|
-
}
|
|
2579
|
-
function boundsCenter(bounds) {
|
|
2580
|
-
const [[minX, minY, minZ], [maxX, maxY, maxZ]] = bounds;
|
|
2581
|
-
return [(minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2];
|
|
2582
|
-
}
|
|
2583
|
-
function solidElements(bounds) {
|
|
2584
|
-
return { axis: { kind: 'line', transform: translation(boundsCenter(bounds)) } };
|
|
2585
|
-
}
|
|
2586
|
-
function constraintReferences(constraint) {
|
|
2587
|
-
return [
|
|
2588
|
-
constraint.source.model,
|
|
2589
|
-
constraint.target.model,
|
|
2590
|
-
...constraint.rotations.map(action => action.pivot.kind === 'around' ? action.pivot.axis.model : undefined),
|
|
2591
|
-
].filter((model) => !!model);
|
|
2592
|
-
}
|
|
2593
|
-
function computeTransformedBounds(geometry, query) {
|
|
2594
|
-
const { transform, selection, scale } = query;
|
|
2595
|
-
return withTopologyShape(geometry.shape, geometry.topology, selection, shape => {
|
|
2596
|
-
const scaled = scale === 1 ? shape : shapeWithScale(shape, scale);
|
|
2597
|
-
try {
|
|
2598
|
-
const moved = shapeWithTransform(scaled, transform);
|
|
2599
|
-
try {
|
|
2600
|
-
return shapeBounds(moved);
|
|
2601
|
-
}
|
|
2602
|
-
finally {
|
|
2603
|
-
moved.delete();
|
|
2604
|
-
}
|
|
2605
|
-
}
|
|
2606
|
-
finally {
|
|
2607
|
-
if (scaled !== shape)
|
|
2608
|
-
scaled.delete();
|
|
2609
|
-
}
|
|
2610
|
-
});
|
|
2611
|
-
}
|
|
2612
|
-
function pointBounds(points) {
|
|
2613
|
-
if (!points.length)
|
|
2614
|
-
throw new Error('Empty geometry has no directional bounds.');
|
|
2615
|
-
return [
|
|
2616
|
-
[0, 1, 2].map(axis => Math.min(...points.map(point => point[axis]))),
|
|
2617
|
-
[0, 1, 2].map(axis => Math.max(...points.map(point => point[axis]))),
|
|
2618
|
-
];
|
|
2619
|
-
}
|
|
2620
|
-
function combineBounds(bounds) {
|
|
2621
|
-
return pointBounds(bounds.flat());
|
|
2622
|
-
}
|
|
2623
|
-
const boundDirections = {
|
|
2624
|
-
up: [0, 1, 0],
|
|
2625
|
-
down: [0, -1, 0],
|
|
2626
|
-
left: [-1, 0, 0],
|
|
2627
|
-
right: [1, 0, 0],
|
|
2628
|
-
front: [0, 0, 1],
|
|
2629
|
-
back: [0, 0, -1],
|
|
2630
|
-
};
|
|
2631
|
-
function directionalBound(reference, direction) {
|
|
2632
|
-
const quaternion = frameFromYAxis(origin, boundDirections[direction]).quaternion;
|
|
2633
|
-
const bounds = reference.model[referenceBounds](reference, invertTransform(rotation(quaternion)));
|
|
2634
|
-
const center = [
|
|
2635
|
-
(bounds[0][0] + bounds[1][0]) / 2,
|
|
2636
|
-
bounds[1][1],
|
|
2637
|
-
(bounds[0][2] + bounds[1][2]) / 2,
|
|
2638
|
-
];
|
|
2639
|
-
return modelAnchor(reference.model, reference.whole ? direction : `${reference.name}.${direction}`, {
|
|
2640
|
-
kind: 'face',
|
|
2641
|
-
transform: composeTransforms(rotation(quaternion), translation(center)),
|
|
2642
|
-
bound: {
|
|
2643
|
-
size: [bounds[1][0] - bounds[0][0], bounds[1][2] - bounds[0][2]],
|
|
2644
|
-
facing: 1,
|
|
2645
|
-
},
|
|
2646
|
-
});
|
|
2647
|
-
}
|
|
2648
|
-
function modelAnchor(model, name, element) {
|
|
2649
|
-
const reference = { ...element, model, name };
|
|
2650
|
-
return (element.topology
|
|
2651
|
-
? new ModelTopologyElement(reference)
|
|
2652
|
-
: new ModelAnchor(reference));
|
|
2653
|
-
}
|
|
2654
|
-
function snapshotElements(elements, prefix = '') {
|
|
2655
|
-
return Object.entries(elements).flatMap(([key, element]) => {
|
|
2656
|
-
const name = `${prefix}${key}`;
|
|
2657
|
-
const topology = element.topology;
|
|
2658
|
-
return [
|
|
2659
|
-
{
|
|
2660
|
-
name,
|
|
2661
|
-
kind: element.kind,
|
|
2662
|
-
transform: toTransform(element.transform),
|
|
2663
|
-
bound: element.bound,
|
|
2664
|
-
facing: element.facing,
|
|
2665
|
-
direction: element.direction,
|
|
2666
|
-
topology: topology
|
|
2667
|
-
? {
|
|
2668
|
-
geometryNodeId: topology.source.nodeId,
|
|
2669
|
-
transform: {
|
|
2670
|
-
...topology.transform,
|
|
2671
|
-
scale: [topology.scale, topology.scale, topology.scale],
|
|
2672
|
-
},
|
|
2673
|
-
...topology.selection,
|
|
2674
|
-
}
|
|
2675
|
-
: undefined,
|
|
2676
|
-
},
|
|
2677
|
-
...snapshotElements(element.members ?? {}, `${name}.`),
|
|
2678
|
-
];
|
|
2679
|
-
});
|
|
2680
|
-
}
|
|
2681
|
-
function scaleElements(elements, factor) {
|
|
2682
|
-
return Object.fromEntries(Object.entries(elements).map(([name, element]) => [
|
|
2683
|
-
name,
|
|
2684
|
-
scaleElement(element, factor),
|
|
2685
|
-
]));
|
|
2686
|
-
}
|
|
2687
|
-
function transformElement(element, transform) {
|
|
2688
|
-
return {
|
|
2689
|
-
...element,
|
|
2690
|
-
transform: composeTransforms(transform, element.transform),
|
|
2691
|
-
topology: element.topology
|
|
2692
|
-
? {
|
|
2693
|
-
...element.topology,
|
|
2694
|
-
transform: composeTransforms(transform, element.topology.transform),
|
|
2695
|
-
}
|
|
2696
|
-
: undefined,
|
|
2697
|
-
members: element.members
|
|
2698
|
-
? Object.fromEntries(Object.entries(element.members).map(([name, member]) => [
|
|
2699
|
-
name,
|
|
2700
|
-
transformElement(member, transform),
|
|
2701
|
-
]))
|
|
2702
|
-
: undefined,
|
|
2703
|
-
};
|
|
2704
|
-
}
|
|
2705
|
-
function scaleElement(element, factor) {
|
|
2706
|
-
return {
|
|
2707
|
-
...element,
|
|
2708
|
-
bound: element.bound
|
|
2709
|
-
? {
|
|
2710
|
-
...element.bound,
|
|
2711
|
-
size: [
|
|
2712
|
-
element.bound.size[0] * factor,
|
|
2713
|
-
element.bound.size[1] * factor,
|
|
2714
|
-
],
|
|
2715
|
-
}
|
|
2716
|
-
: undefined,
|
|
2717
|
-
topology: element.topology
|
|
2718
|
-
? {
|
|
2719
|
-
...element.topology,
|
|
2720
|
-
scale: element.topology.scale * factor,
|
|
2721
|
-
transform: scaleFrame(element.topology.transform, factor),
|
|
2722
|
-
}
|
|
2723
|
-
: undefined,
|
|
2724
|
-
members: element.members
|
|
2725
|
-
? scaleElements(element.members, factor)
|
|
2726
|
-
: undefined,
|
|
2727
|
-
transform: {
|
|
2728
|
-
...element.transform,
|
|
2729
|
-
position: [
|
|
2730
|
-
element.transform.position[0] * factor,
|
|
2731
|
-
element.transform.position[1] * factor,
|
|
2732
|
-
element.transform.position[2] * factor,
|
|
2733
|
-
],
|
|
2734
|
-
},
|
|
2735
|
-
};
|
|
2736
|
-
}
|
|
2737
|
-
function scaleFrame(transform, factor) {
|
|
2738
|
-
return {
|
|
2739
|
-
...transform,
|
|
2740
|
-
position: transform.position.map(value => value * factor),
|
|
2741
|
-
};
|
|
2742
|
-
}
|
|
2743
|
-
function topologyTransform(topology, frame) {
|
|
2744
|
-
return composeTransforms(topology.transform, scaleFrame(frame, topology.scale));
|
|
2745
|
-
}
|
|
2746
|
-
function topologyCenter(geometry, selection) {
|
|
2747
|
-
const { referenceBasis, shape, topology } = geometry.value;
|
|
2748
|
-
if (referenceBasis)
|
|
2749
|
-
return withTopologyShape(referenceBasis.shape, referenceBasis.topology, selection, selected => topologyTransform(referenceBasis, translation(boundsCenter(shapeBounds(selected)))));
|
|
2750
|
-
return withTopologyShape(shape, topology, selection, selected => translation(boundsCenter(shapeBounds(selected))));
|
|
2751
|
-
}
|
|
2752
|
-
function transformedReferenceBasis(source, transform, scale) {
|
|
2753
|
-
const basis = source.value.referenceBasis;
|
|
2754
|
-
return {
|
|
2755
|
-
shape: (basis?.shape ?? source.value.shape).clone(),
|
|
2756
|
-
topology: basis?.topology ?? source.value.topology,
|
|
2757
|
-
transform: composeTransforms(transform, scaleFrame(basis?.transform ?? identityRigidTransform, scale)),
|
|
2758
|
-
scale: (basis?.scale ?? 1) * scale,
|
|
2759
|
-
};
|
|
2760
|
-
}
|
|
2761
|
-
function topologyName(selection) {
|
|
2762
|
-
return selection.kind === 'solid'
|
|
2763
|
-
? 'solid'
|
|
2764
|
-
: formatTopologyId(selection.kind, selection.id);
|
|
2765
|
-
}
|
|
2766
|
-
function topologyReferences(model, prefix, context, kind, ids) {
|
|
2767
|
-
const geometry = context.source[modelGeometry]().value;
|
|
2768
|
-
const basis = geometry.referenceBasis;
|
|
2769
|
-
const { shape, topology } = basis ?? geometry;
|
|
2770
|
-
const frames = kind === 'vertex'
|
|
2771
|
-
? topologyVertexPoints(shape, topology.vertices, ids).map(point => translation(point.position))
|
|
2772
|
-
: (kind === 'edge'
|
|
2773
|
-
? topologyEdgeDirections(shape, topology.edges, ids)
|
|
2774
|
-
: topologySurfaceDirections(shape, topology.surfaces, ids)).map(({ position, direction }) => frameFromYAxis(position, direction));
|
|
2775
|
-
return ids.map((id, index) => {
|
|
2776
|
-
const selection = { kind, id };
|
|
2777
|
-
return new ModelTopologyElement({
|
|
2778
|
-
model,
|
|
2779
|
-
name: `${prefix}${topologyName(selection)}`,
|
|
2780
|
-
kind: topologyElementKinds[kind],
|
|
2781
|
-
transform: topologyTransform(context, basis ? topologyTransform(basis, frames[index]) : frames[index]),
|
|
2782
|
-
topology: { ...context, selection },
|
|
2783
|
-
});
|
|
2784
|
-
});
|
|
2785
|
-
}
|
|
2786
|
-
function unionSectionShape(left, right) {
|
|
2787
|
-
const section = new (getOC().BRepAlgoAPI_Section)(left.wrapped, right.wrapped, false);
|
|
2788
|
-
try {
|
|
2789
|
-
section.Build();
|
|
2790
|
-
const sectionShape = castOwnedShape(section.Shape());
|
|
2791
|
-
const edges = shapeSubshapes(sectionShape, 'edge');
|
|
2792
|
-
if (edges.length === 0) {
|
|
2793
|
-
return sectionShape;
|
|
2794
|
-
}
|
|
2795
|
-
let wire;
|
|
2796
|
-
try {
|
|
2797
|
-
wire = assembleWire(edges);
|
|
2798
|
-
const face = makeFace(wire);
|
|
2799
|
-
sectionShape.delete();
|
|
2800
|
-
return face;
|
|
2801
|
-
}
|
|
2802
|
-
catch {
|
|
2803
|
-
return sectionShape;
|
|
2804
|
-
}
|
|
2805
|
-
finally {
|
|
2806
|
-
edges.forEach(edge => edge.delete());
|
|
2807
|
-
wire?.delete();
|
|
2808
|
-
}
|
|
2809
|
-
}
|
|
2810
|
-
finally {
|
|
2811
|
-
section.delete();
|
|
2812
|
-
}
|
|
2813
|
-
}
|
|
2814
|
-
function boundReference(target) {
|
|
2815
|
-
const reference = target instanceof ModelAnchor ? target[anchorReferenceValue] : undefined;
|
|
2816
|
-
if (!reference?.bound)
|
|
2817
|
-
throw new Error('on() requires a directional bound: up, down, left, right, front, or back.');
|
|
2818
|
-
return reference;
|
|
2819
|
-
}
|
|
2820
|
-
function anchorReference(anchor) {
|
|
2821
|
-
if (anchor instanceof ModelObject) {
|
|
2822
|
-
return anchor.relationAnchorReference();
|
|
2823
|
-
}
|
|
2824
|
-
return anchor[anchorReferenceValue];
|
|
2825
|
-
}
|
|
2826
|
-
function anchorSnapshot(modelOrReference, stored) {
|
|
2827
|
-
const model = stored ? modelOrReference : undefined;
|
|
2828
|
-
const reference = stored ?? modelOrReference;
|
|
2829
|
-
return {
|
|
2830
|
-
nodeId: (model ?? modelOrReference.model).nodeId,
|
|
2831
|
-
name: reference.name,
|
|
2832
|
-
kind: reference.kind,
|
|
2833
|
-
};
|
|
2834
|
-
}
|
|
2835
|
-
function uniqueModels(models) {
|
|
2836
|
-
return [...new Set(models)];
|
|
2837
|
-
}
|
|
2838
|
-
function toTransform(transform) {
|
|
2839
|
-
return { ...transform, scale: unitScale };
|
|
2840
|
-
}
|
|
2841
|
-
function shapeWithTransform(source, transform) {
|
|
2842
|
-
const oc = getOC();
|
|
2843
|
-
let shape = source.clone();
|
|
2844
|
-
const { axis, angleDegrees } = quaternionAxisAngle(transform.quaternion);
|
|
2845
|
-
try {
|
|
2846
|
-
if (Math.abs(angleDegrees) > 1e-9) {
|
|
2847
|
-
const rotated = transformShape(shape, value => {
|
|
2848
|
-
const point = new oc.gp_Pnt(0, 0, 0);
|
|
2849
|
-
const direction = new oc.gp_Dir(...axis);
|
|
2850
|
-
const rotationAxis = new oc.gp_Ax1(point, direction);
|
|
2851
|
-
try {
|
|
2852
|
-
value.SetRotation(rotationAxis, angleDegrees * (Math.PI / 180));
|
|
2853
|
-
}
|
|
2854
|
-
finally {
|
|
2855
|
-
rotationAxis.delete();
|
|
2856
|
-
direction.delete();
|
|
2857
|
-
point.delete();
|
|
2858
|
-
}
|
|
2859
|
-
});
|
|
2860
|
-
shape.delete();
|
|
2861
|
-
shape = rotated;
|
|
2862
|
-
}
|
|
2863
|
-
return transformShape(shape, value => {
|
|
2864
|
-
const vector = new oc.gp_Vec(...transform.position);
|
|
2865
|
-
try {
|
|
2866
|
-
value.SetTranslation(vector);
|
|
2867
|
-
}
|
|
2868
|
-
finally {
|
|
2869
|
-
vector.delete();
|
|
2870
|
-
}
|
|
2871
|
-
});
|
|
2872
|
-
}
|
|
2873
|
-
finally {
|
|
2874
|
-
shape.delete();
|
|
2875
|
-
}
|
|
2876
|
-
}
|
|
2877
|
-
function shapeWithScale(source, scale) {
|
|
2878
|
-
return transformShape(source, value => {
|
|
2879
|
-
const point = new (getOC().gp_Pnt)(0, 0, 0);
|
|
2880
|
-
try {
|
|
2881
|
-
value.SetScale(point, scale);
|
|
2882
|
-
}
|
|
2883
|
-
finally {
|
|
2884
|
-
point.delete();
|
|
2885
|
-
}
|
|
2886
|
-
});
|
|
2887
|
-
}
|
|
2888
|
-
function requireModelObject(value, message) {
|
|
2889
|
-
if (!isModelObject(value)) {
|
|
2890
|
-
throw new Error(message);
|
|
2891
|
-
}
|
|
2892
|
-
return value;
|
|
2893
|
-
}
|
|
2894
|
-
function requireModelKind(value, kind, message) {
|
|
2895
|
-
const object = requireModelObject(value, message);
|
|
2896
|
-
if (object.kind !== kind) {
|
|
2897
|
-
throw new Error(message);
|
|
2898
|
-
}
|
|
2899
|
-
return object;
|
|
2900
|
-
}
|
|
2901
|
-
function booleanOperands(operation, operands) {
|
|
2902
|
-
if (operands.length < 2) {
|
|
2903
|
-
throw new Error(`${operation} requires at least two model operands.`);
|
|
2904
|
-
}
|
|
2905
|
-
const runtimeOperands = operands.map(operand => requireModelKind(operand, 'solid', `Every ${operation} operand must be a solid model.`));
|
|
2906
|
-
return { first: runtimeOperands[0], others: runtimeOperands.slice(1) };
|
|
2907
|
-
}
|
|
2908
|
-
function assertPositive(label, value) {
|
|
2909
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
2910
|
-
throw new Error(`${label} must be a positive finite number.`);
|
|
2911
|
-
}
|
|
2912
|
-
}
|
|
2913
|
-
function assertFiniteVector(label, value) {
|
|
2914
|
-
if (value.some(component => !Number.isFinite(component))) {
|
|
2915
|
-
throw new Error(`${label} must be a finite number.`);
|
|
2916
|
-
}
|
|
2917
|
-
}
|
|
2918
|
-
function assertCurvePoints(label, points, minimum) {
|
|
2919
|
-
if (points.length < minimum) {
|
|
2920
|
-
throw new Error(`${label} requires at least ${minimum} points.`);
|
|
2921
|
-
}
|
|
2922
|
-
points.forEach((point, index) => assertFiniteVector(`${label} point ${index + 1}`, point));
|
|
2923
|
-
const [first, ...rest] = points;
|
|
2924
|
-
if (rest.every(point => point.every((component, index) => component === first[index]))) {
|
|
2925
|
-
throw new Error(`${label} requires at least two distinct points.`);
|
|
2926
|
-
}
|
|
2927
|
-
}
|
|
2928
|
-
function toPoint(vector) {
|
|
2929
|
-
return [vector[0], vector[1], vector[2]];
|
|
2930
|
-
}
|
|
2931
|
-
function appendUniqueParameters(destination, parameters) {
|
|
2932
|
-
for (const parameter of parameters) {
|
|
2933
|
-
if (!hasParameter(destination, parameter)) {
|
|
2934
|
-
destination.push(parameter);
|
|
2935
|
-
}
|
|
2936
|
-
}
|
|
2937
|
-
}
|
|
2938
|
-
function uniqueParameters(parameters) {
|
|
2939
|
-
const unique = [];
|
|
2940
|
-
appendUniqueParameters(unique, parameters);
|
|
2941
|
-
return unique;
|
|
2942
|
-
}
|
|
2943
|
-
function hasParameter(parameters, parameter) {
|
|
2944
|
-
return parameters.some(candidate => candidate.operation === parameter.operation &&
|
|
2945
|
-
candidate.argument === parameter.argument &&
|
|
2946
|
-
candidate.operationRef.file === parameter.operationRef.file &&
|
|
2947
|
-
candidate.operationRef.start === parameter.operationRef.start &&
|
|
2948
|
-
candidate.operationRef.end === parameter.operationRef.end &&
|
|
2949
|
-
candidate.expressionRef.file === parameter.expressionRef.file &&
|
|
2950
|
-
candidate.expressionRef.start === parameter.expressionRef.start &&
|
|
2951
|
-
candidate.expressionRef.end === parameter.expressionRef.end &&
|
|
2952
|
-
candidate.target.id === parameter.target.id);
|
|
2953
|
-
}
|
|
2954
|
-
//# sourceMappingURL=runtime.js.map
|