@code3d/core 0.0.1-alpha.0 → 0.0.1-alpha.3
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-BF55AIEQ.js +31 -0
- package/bld/chunks/chunk-BF55AIEQ.js.map +7 -0
- package/bld/chunks/chunk-P5VGKAEM.js +30 -0
- package/bld/chunks/chunk-P5VGKAEM.js.map +7 -0
- package/bld/chunks/chunk-RPY2WVCB.js +24834 -0
- package/bld/chunks/chunk-RPY2WVCB.js.map +7 -0
- package/bld/chunks/chunk-TJIN6S4R.js +1 -0
- package/bld/chunks/chunk-TJIN6S4R.js.map +7 -0
- package/bld/chunks/chunk-U5G2VU2I.js +19 -0
- package/bld/chunks/chunk-U5G2VU2I.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 +24 -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 +146 -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/library/topology.d.ts.map +1 -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 +128 -34
- package/src/library/loft.ts +4 -0
- 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 +2064 -1730
- 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/topology.ts +24 -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/src/library/runtime.ts
CHANGED
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
import type {Material} from './three.js';
|
|
2
|
-
import {captureModelMaterial, type ModelMaterialSnapshot} from './material.js';
|
|
3
|
-
import {
|
|
4
|
-
inspectShapeTopology,
|
|
5
|
-
type TopologyInspection,
|
|
6
|
-
type TopologyInspectionOptions,
|
|
7
|
-
} from './topology-inspection.js';
|
|
8
1
|
import {
|
|
9
2
|
assembleWire,
|
|
10
3
|
basicFaceExtrusion,
|
|
11
4
|
BoundingBox,
|
|
12
5
|
genericSweep,
|
|
13
6
|
getOC,
|
|
14
|
-
makeBSplineApproximation,
|
|
15
7
|
makeBezierCurve,
|
|
8
|
+
makeBSplineApproximation,
|
|
16
9
|
makeCircle,
|
|
17
10
|
makeCylinder,
|
|
18
11
|
makeFace,
|
|
@@ -30,9 +23,26 @@ import {
|
|
|
30
23
|
type Edge as ReplicadEdge,
|
|
31
24
|
type Face as ReplicadFace,
|
|
32
25
|
type Vertex as ReplicadVertex,
|
|
33
|
-
type Shape3D,
|
|
34
26
|
type Wire as ReplicadWire,
|
|
27
|
+
type Shape3D,
|
|
35
28
|
} from 'replicad';
|
|
29
|
+
import {
|
|
30
|
+
edgeGeometry,
|
|
31
|
+
faceGeometry,
|
|
32
|
+
transformGeometry,
|
|
33
|
+
type AlignmentGeometry,
|
|
34
|
+
} from './alignment-geometry.js';
|
|
35
|
+
import {cached, cachedArtifact} from './cached.js';
|
|
36
|
+
import {extrudeWithTopology} from './extrude.js';
|
|
37
|
+
import {font, googleFont, type Font} from './font.js';
|
|
38
|
+
import {
|
|
39
|
+
beginKernelOperationEvaluation,
|
|
40
|
+
kernelOperationKey,
|
|
41
|
+
type KernelArtifact,
|
|
42
|
+
type KernelKeyPart,
|
|
43
|
+
type KernelOperationKey,
|
|
44
|
+
type KernelValueLifecycle,
|
|
45
|
+
} from './kernel-cache.js';
|
|
36
46
|
import {
|
|
37
47
|
castOwnedShape,
|
|
38
48
|
castOwnedShape3D,
|
|
@@ -40,17 +50,29 @@ import {
|
|
|
40
50
|
shapeSubshapes,
|
|
41
51
|
transformShape,
|
|
42
52
|
} from './kernel-shapes.js';
|
|
53
|
+
import {loftWithTopology} from './loft.js';
|
|
54
|
+
import {captureModelMaterial, type ModelMaterialSnapshot} from './material.js';
|
|
55
|
+
import {
|
|
56
|
+
axisRotation,
|
|
57
|
+
solveBodies,
|
|
58
|
+
type Body,
|
|
59
|
+
type BodyRotation,
|
|
60
|
+
} from './relation-solver.js';
|
|
61
|
+
import {estimateRetainedBytes} from './retained-memory.js';
|
|
62
|
+
import {shellWithTopology} from './shell.js';
|
|
63
|
+
import {sketchRegionFace} from './sketch-face.js';
|
|
64
|
+
import type {SketchRegion} from './sketch-regions.js';
|
|
43
65
|
import {
|
|
44
66
|
addVectors,
|
|
45
67
|
composeTransforms,
|
|
46
68
|
frameFromYAxis,
|
|
47
69
|
identityRigidTransform,
|
|
48
70
|
invertTransform,
|
|
49
|
-
rotateVector,
|
|
50
|
-
origin,
|
|
51
71
|
negateVector,
|
|
72
|
+
origin,
|
|
52
73
|
quaternionAxisAngle,
|
|
53
74
|
relativeTransform,
|
|
75
|
+
rotateVector,
|
|
54
76
|
rotation,
|
|
55
77
|
rotationAround,
|
|
56
78
|
translation,
|
|
@@ -58,36 +80,14 @@ import {
|
|
|
58
80
|
type RigidTransform,
|
|
59
81
|
type Vec3,
|
|
60
82
|
} from './spatial.js';
|
|
61
|
-
import {
|
|
62
|
-
|
|
63
|
-
solveBodies,
|
|
64
|
-
type Body,
|
|
65
|
-
type BodyRotation,
|
|
66
|
-
} from './relation-solver.js';
|
|
67
|
-
import {
|
|
68
|
-
edgeGeometry,
|
|
69
|
-
faceGeometry,
|
|
70
|
-
transformGeometry,
|
|
71
|
-
type AlignmentGeometry,
|
|
72
|
-
} from './alignment-geometry.js';
|
|
73
|
-
import {shellWithTopology} from './shell.js';
|
|
74
|
-
import {
|
|
75
|
-
beginKernelOperationEvaluation,
|
|
76
|
-
evaluateKernelOperation,
|
|
77
|
-
kernelOperationKey,
|
|
78
|
-
findKernelOperation,
|
|
79
|
-
acceptKernelOperation,
|
|
80
|
-
type KernelOperationKey,
|
|
81
|
-
type KernelArtifact,
|
|
82
|
-
type KernelKeyPart,
|
|
83
|
-
type KernelValueLifecycle,
|
|
84
|
-
} from './kernel-cache.js';
|
|
85
|
-
import {loftWithTopology} from './loft.js';
|
|
86
|
-
import {estimateRetainedBytes} from './retained-memory.js';
|
|
87
|
-
import {extrudeWithTopology} from './extrude.js';
|
|
88
|
-
import {sketchRegionFace} from './sketch-face.js';
|
|
89
|
-
import type {SketchRegion} from './sketch-regions.js';
|
|
83
|
+
import {textGlyphs, textRegionFace, type TextOptions} from './text.js';
|
|
84
|
+
import type {Material} from 'three';
|
|
90
85
|
import {formatTopologyId, type TopologyId} from './topology-id.js';
|
|
86
|
+
import {
|
|
87
|
+
inspectShapeTopology,
|
|
88
|
+
type TopologyInspection,
|
|
89
|
+
type TopologyInspectionOptions,
|
|
90
|
+
} from './topology-inspection.js';
|
|
91
91
|
import {
|
|
92
92
|
booleanWithTopology,
|
|
93
93
|
chamferEdges,
|
|
@@ -99,10 +99,10 @@ import {
|
|
|
99
99
|
stableEdgeGroups,
|
|
100
100
|
stableSurfaceGroups,
|
|
101
101
|
stableVertexData,
|
|
102
|
+
topologyChildren,
|
|
102
103
|
topologyEdgeDirections,
|
|
103
104
|
topologySurfaceDirections,
|
|
104
105
|
topologyVertexPoints,
|
|
105
|
-
topologyChildren,
|
|
106
106
|
withTopologyShape,
|
|
107
107
|
type EdgeId,
|
|
108
108
|
type ShapeTopology,
|
|
@@ -112,11 +112,11 @@ import {
|
|
|
112
112
|
type VertexId,
|
|
113
113
|
} from './topology.js';
|
|
114
114
|
|
|
115
|
-
import {sketch} from './sketch.js';
|
|
116
115
|
import {
|
|
117
|
-
encodeKernelArtifact,
|
|
118
116
|
decodeKernelArtifact,
|
|
117
|
+
encodeKernelArtifact,
|
|
119
118
|
} from './kernel-artifact-codec.js';
|
|
119
|
+
import {sketch} from './sketch.js';
|
|
120
120
|
|
|
121
121
|
export type {Quaternion, Vec3} from './spatial.js';
|
|
122
122
|
export type {EdgeId, SurfaceId, TopologyKind, VertexId} from './topology.js';
|
|
@@ -195,6 +195,7 @@ export type ConstraintSnapshot = Readonly<{
|
|
|
195
195
|
);
|
|
196
196
|
|
|
197
197
|
export type ModelOperationKind =
|
|
198
|
+
| 'sketch'
|
|
198
199
|
| 'box'
|
|
199
200
|
| 'cylinder'
|
|
200
201
|
| 'tube'
|
|
@@ -213,6 +214,7 @@ export type ModelOperationKind =
|
|
|
213
214
|
| 'spline'
|
|
214
215
|
| 'loft'
|
|
215
216
|
| 'sketchFace'
|
|
217
|
+
| 'text'
|
|
216
218
|
| 'extrude'
|
|
217
219
|
| 'primitive'
|
|
218
220
|
| 'material'
|
|
@@ -337,7 +339,6 @@ export type RenderMesh = Readonly<{
|
|
|
337
339
|
|
|
338
340
|
export type ModelSnapshotObject = Readonly<{
|
|
339
341
|
nodeId: string;
|
|
340
|
-
kind: ModelKind;
|
|
341
342
|
name: string;
|
|
342
343
|
/** Effective material, including recursive overrides from enclosing groups. */
|
|
343
344
|
material?: ModelMaterialSnapshot;
|
|
@@ -352,8 +353,11 @@ export type ModelSnapshotObject = Readonly<{
|
|
|
352
353
|
sourceRefs: readonly SourceRef[];
|
|
353
354
|
parameters: readonly ParameterUsage[];
|
|
354
355
|
operation: ModelOperationSnapshot;
|
|
355
|
-
|
|
356
|
-
|
|
356
|
+
}> &
|
|
357
|
+
(
|
|
358
|
+
| Readonly<{kind: ModelKind; mesh?: RenderMesh}>
|
|
359
|
+
| Readonly<{kind: 'reference'; mesh?: never}>
|
|
360
|
+
);
|
|
357
361
|
|
|
358
362
|
type StoredElement = Readonly<{
|
|
359
363
|
kind: ElementKind;
|
|
@@ -377,10 +381,10 @@ type StoredElements = Readonly<Record<string, StoredElement>>;
|
|
|
377
381
|
|
|
378
382
|
type StoredAnchor = StoredElement & Readonly<{name: string}>;
|
|
379
383
|
|
|
380
|
-
type AnchorReference = StoredAnchor & Readonly<{model:
|
|
384
|
+
type AnchorReference = StoredAnchor & Readonly<{model: RelationObject}>;
|
|
381
385
|
|
|
382
386
|
export type ModelElementReference = Readonly<{
|
|
383
|
-
model:
|
|
387
|
+
model: RelationObject;
|
|
384
388
|
name: string;
|
|
385
389
|
kind: ElementKind;
|
|
386
390
|
transform: Transform;
|
|
@@ -390,7 +394,7 @@ export type ModelElementReference = Readonly<{
|
|
|
390
394
|
}>;
|
|
391
395
|
|
|
392
396
|
export type ModelTopologyReference = Readonly<{
|
|
393
|
-
model:
|
|
397
|
+
model: RelationObject;
|
|
394
398
|
geometry: ModelObject;
|
|
395
399
|
transform: Transform;
|
|
396
400
|
}> &
|
|
@@ -398,14 +402,16 @@ export type ModelTopologyReference = Readonly<{
|
|
|
398
402
|
|
|
399
403
|
export type ConstraintTraceReference = Readonly<{
|
|
400
404
|
constraintId: string;
|
|
401
|
-
source:
|
|
402
|
-
target:
|
|
403
|
-
self?:
|
|
405
|
+
source: RelationObject;
|
|
406
|
+
target: RelationObject;
|
|
407
|
+
self?: RelationObject;
|
|
404
408
|
}>;
|
|
405
409
|
|
|
406
410
|
export type ModelOperationInstrumentation = Readonly<{
|
|
407
411
|
siteId: string;
|
|
408
412
|
execution: number;
|
|
413
|
+
/** Distinguishes operation results produced by the same call execution. */
|
|
414
|
+
outputIndex?: number;
|
|
409
415
|
order: number;
|
|
410
416
|
sourceRef: SourceRef;
|
|
411
417
|
parameters: readonly ParameterUsage[];
|
|
@@ -427,7 +433,7 @@ type StoredConstraint = Readonly<{
|
|
|
427
433
|
}>;
|
|
428
434
|
|
|
429
435
|
// An omitted model refers to relate's self, including after immutable copies.
|
|
430
|
-
type RelationReference = StoredAnchor & Readonly<{model?:
|
|
436
|
+
type RelationReference = StoredAnchor & Readonly<{model?: RelationObject}>;
|
|
431
437
|
type PivotSelection =
|
|
432
438
|
| Readonly<{kind: 'pivot'; point: Vec3}>
|
|
433
439
|
| Readonly<{kind: 'pivotVertex'; id: VertexId}>
|
|
@@ -440,11 +446,11 @@ type ConstraintSpatialSelection = Readonly<{
|
|
|
440
446
|
kind: ConstraintSpatialReference['kind'];
|
|
441
447
|
pivot: PivotSelection;
|
|
442
448
|
}>;
|
|
443
|
-
type RelateContext = Readonly<{self:
|
|
449
|
+
type RelateContext = Readonly<{self: RelationObject; original: RelationObject}>;
|
|
444
450
|
let activeRelate: RelateContext | undefined;
|
|
445
451
|
|
|
446
452
|
type StoredOperationInput = Readonly<{
|
|
447
|
-
model:
|
|
453
|
+
model: RelationObject;
|
|
448
454
|
role: ModelOperationInputRole;
|
|
449
455
|
index: number;
|
|
450
456
|
}>;
|
|
@@ -551,7 +557,7 @@ type SolidGeometry = KernelArtifact<
|
|
|
551
557
|
>;
|
|
552
558
|
|
|
553
559
|
type SolveContext = {
|
|
554
|
-
poses: ReadonlyMap<
|
|
560
|
+
poses: ReadonlyMap<RelationObject, RigidTransform>;
|
|
555
561
|
};
|
|
556
562
|
|
|
557
563
|
function transformSolveContext(
|
|
@@ -593,7 +599,7 @@ const defaultModelNames = {
|
|
|
593
599
|
|
|
594
600
|
const anchorKind = Symbol('anchorKind');
|
|
595
601
|
const anchorReferenceValue = Symbol('anchorReference');
|
|
596
|
-
const
|
|
602
|
+
const modelKind = Symbol('modelKind');
|
|
597
603
|
const modelNamedElements = Symbol('modelNamedElements');
|
|
598
604
|
|
|
599
605
|
export interface Anchor<Kind extends ElementKind = ElementKind> {
|
|
@@ -671,18 +677,18 @@ export type ElementSources = Readonly<Record<string, Anchor>>;
|
|
|
671
677
|
export type NamedElements = Readonly<Record<string, Anchor>>;
|
|
672
678
|
|
|
673
679
|
export type ExposedValue<Value> = Value extends {
|
|
674
|
-
readonly [
|
|
680
|
+
readonly [modelKind]: infer Kind;
|
|
675
681
|
readonly [modelNamedElements]: infer Elements;
|
|
676
682
|
}
|
|
677
|
-
? (
|
|
683
|
+
? (Kind extends 'solid'
|
|
678
684
|
? Solid
|
|
679
|
-
:
|
|
685
|
+
: Kind extends 'face'
|
|
680
686
|
? Surface
|
|
681
|
-
:
|
|
687
|
+
: Kind extends 'edge'
|
|
682
688
|
? Edge
|
|
683
|
-
:
|
|
689
|
+
: Kind extends 'vertex'
|
|
684
690
|
? Vertex
|
|
685
|
-
:
|
|
691
|
+
: Kind extends 'group'
|
|
686
692
|
? Anchor<'frame'>
|
|
687
693
|
: Anchor) &
|
|
688
694
|
Elements
|
|
@@ -707,81 +713,76 @@ export type ModelElementKind<Kind extends ModelKind> = Kind extends 'face'
|
|
|
707
713
|
? 'point'
|
|
708
714
|
: 'frame';
|
|
709
715
|
|
|
710
|
-
export type
|
|
711
|
-
|
|
712
|
-
export type ModelFamilyElementKind<Family extends ModelFamily> =
|
|
713
|
-
Family extends ModelKind ? ModelElementKind<Family> : ElementKind;
|
|
714
|
-
|
|
715
|
-
export type ModelForFamily<
|
|
716
|
+
export type ModelForKind<
|
|
716
717
|
Elements extends NamedElements,
|
|
717
|
-
|
|
718
|
-
> =
|
|
718
|
+
Kind extends ModelKind,
|
|
719
|
+
> = Kind extends 'solid'
|
|
719
720
|
? SolidModel<Elements>
|
|
720
|
-
:
|
|
721
|
+
: Kind extends 'face'
|
|
721
722
|
? FaceModel<Elements>
|
|
722
|
-
:
|
|
723
|
+
: Kind extends 'edge'
|
|
723
724
|
? EdgeModel<Elements>
|
|
724
|
-
:
|
|
725
|
+
: Kind extends 'vertex'
|
|
725
726
|
? VertexModel<Elements>
|
|
726
|
-
:
|
|
727
|
+
: Kind extends 'group'
|
|
727
728
|
? GroupModel<Elements>
|
|
728
|
-
:
|
|
729
|
+
: never;
|
|
729
730
|
|
|
730
731
|
export interface ModelCapabilities<
|
|
731
732
|
Elements extends NamedElements,
|
|
732
|
-
|
|
733
|
+
Kind extends ModelKind,
|
|
733
734
|
>
|
|
734
|
-
extends Anchor<
|
|
735
|
-
readonly [
|
|
735
|
+
extends Anchor<ModelElementKind<Kind>>, DirectionalBounds {
|
|
736
|
+
readonly [modelKind]: Kind;
|
|
736
737
|
readonly [modelNamedElements]: Elements;
|
|
737
738
|
relate(
|
|
738
739
|
build: (
|
|
739
|
-
self:
|
|
740
|
+
self: ModelForKind<Elements, Kind>,
|
|
740
741
|
) => Constraint | readonly Constraint[],
|
|
741
|
-
):
|
|
742
|
+
): ModelForKind<Elements, Kind>;
|
|
742
743
|
expose<const Sources extends ElementSources>(
|
|
743
744
|
sources: Sources,
|
|
744
|
-
):
|
|
745
|
+
): ModelForKind<MergedElements<Elements, ExposedElements<Sources>>, Kind>;
|
|
745
746
|
/**
|
|
746
747
|
* Shift the origin by this displacement: every local point becomes p - d.
|
|
747
|
-
* @code3d.param dx {kind: 'length', label: 'Origin ΔX'}
|
|
748
|
-
* @code3d.param dy {kind: 'length', label: 'Origin ΔY'}
|
|
749
|
-
* @code3d.param dz {kind: 'length', label: 'Origin ΔZ'}
|
|
748
|
+
* @code3d.param dx {kind: 'length', default: 0, label: 'Origin ΔX'}
|
|
749
|
+
* @code3d.param dy {kind: 'length', default: 0, label: 'Origin ΔY'}
|
|
750
|
+
* @code3d.param dz {kind: 'length', default: 0, label: 'Origin ΔZ'}
|
|
750
751
|
*/
|
|
751
752
|
originOffset(
|
|
752
753
|
dx: number,
|
|
753
754
|
dy: number,
|
|
754
755
|
dz: number,
|
|
755
|
-
):
|
|
756
|
+
): ModelForKind<Elements, Kind>;
|
|
756
757
|
/** Re-express the model with this point reference at local zero. */
|
|
757
|
-
originPoint(point: PointAnchor):
|
|
758
|
+
originPoint(point: PointAnchor): ModelForKind<Elements, Kind>;
|
|
758
759
|
/**
|
|
759
760
|
* Rotate about the current origin, in degrees, about fixed local X, Y, then Z axes.
|
|
760
|
-
* @code3d.param x {kind: 'angle', label: 'Rotate X'}
|
|
761
|
-
* @code3d.param y {kind: 'angle', label: 'Rotate Y'}
|
|
762
|
-
* @code3d.param z {kind: 'angle', label: 'Rotate Z'}
|
|
761
|
+
* @code3d.param x {kind: 'angle', default: 0, label: 'Rotate X'}
|
|
762
|
+
* @code3d.param y {kind: 'angle', default: 0, label: 'Rotate Y'}
|
|
763
|
+
* @code3d.param z {kind: 'angle', default: 0, label: 'Rotate Z'}
|
|
763
764
|
*/
|
|
764
|
-
rotate(x: number, y: number, z: number):
|
|
765
|
+
rotate(x: number, y: number, z: number): ModelForKind<Elements, Kind>;
|
|
765
766
|
/**
|
|
766
767
|
* Return a value with its entire material replaced, including every group descendant.
|
|
767
768
|
* Capture a Three.js material at assignment, or use a CSS color for the default material.
|
|
768
769
|
*/
|
|
769
|
-
material(material: Material | string):
|
|
770
|
+
material(material: Material | string): ModelForKind<Elements, Kind>;
|
|
770
771
|
}
|
|
771
772
|
|
|
772
773
|
export interface GeometryCapabilities<
|
|
773
774
|
Elements extends NamedElements,
|
|
774
|
-
|
|
775
|
+
Kind extends ModelGeometryKind,
|
|
775
776
|
> extends GeometryQueryCapabilities {
|
|
776
777
|
/** Re-express the model with its geometric center at local zero. */
|
|
777
|
-
originCenter():
|
|
778
|
+
originCenter(): ModelForKind<Elements, Kind>;
|
|
778
779
|
/**
|
|
779
780
|
* Re-express the model with the selected vertex at local zero.
|
|
780
781
|
* @code3d.param id {kind: 'vertex', label: 'Origin vertex'}
|
|
781
782
|
*/
|
|
782
|
-
originVertex(id: VertexId):
|
|
783
|
-
/** @code3d.param factor {kind: 'ratio', label: 'Scale'} */
|
|
784
|
-
scaled(factor: number):
|
|
783
|
+
originVertex(id: VertexId): ModelForKind<Elements, Kind>;
|
|
784
|
+
/** @code3d.param factor {kind: 'ratio', default: 1, label: 'Scale'} */
|
|
785
|
+
scaled(factor: number): ModelForKind<Elements, Kind>;
|
|
785
786
|
}
|
|
786
787
|
|
|
787
788
|
export interface VertexTopologyCapabilities {
|
|
@@ -809,12 +810,12 @@ export interface SolidModificationCapabilities<Elements extends NamedElements> {
|
|
|
809
810
|
/** Subtracts all tools in one boolean operation, equivalent to cut(stock, tools). */
|
|
810
811
|
cut(tools: readonly SolidModel<{}>[]): SolidModel;
|
|
811
812
|
/**
|
|
812
|
-
* @code3d.param radius {kind: 'length', label: 'Fillet radius', constraints: {exclusiveMin: 0}}
|
|
813
|
+
* @code3d.param radius {kind: 'length', default: 1, label: 'Fillet radius', constraints: {exclusiveMin: 0}}
|
|
813
814
|
* @code3d.param edgeIds {kind: 'edge', actions: [{label: 'Use all', action: 'remove-argument'}]}
|
|
814
815
|
*/
|
|
815
816
|
fillet(radius: number, edgeIds?: readonly EdgeId[]): SolidModel<Elements>;
|
|
816
817
|
/**
|
|
817
|
-
* @code3d.param distance {kind: 'length', label: 'Chamfer distance', constraints: {exclusiveMin: 0}}
|
|
818
|
+
* @code3d.param distance {kind: 'length', default: 1, label: 'Chamfer distance', constraints: {exclusiveMin: 0}}
|
|
818
819
|
* @code3d.param edgeIds {kind: 'edge', actions: [{label: 'Use all', action: 'remove-argument'}]}
|
|
819
820
|
*/
|
|
820
821
|
chamfer(distance: number, edgeIds?: readonly EdgeId[]): SolidModel<Elements>;
|
|
@@ -822,7 +823,7 @@ export interface SolidModificationCapabilities<Elements extends NamedElements> {
|
|
|
822
823
|
* Hollow a solid with uniform walls. Positive thickness offsets inward;
|
|
823
824
|
* negative thickness offsets outward. Selected surfaces become openings.
|
|
824
825
|
* Omit the selection, or use [], for a fully enclosed cavity.
|
|
825
|
-
* @code3d.param thickness {kind: 'length', label: 'Wall thickness'}
|
|
826
|
+
* @code3d.param thickness {kind: 'length', default: 1, label: 'Wall thickness'}
|
|
826
827
|
* @code3d.param removedSurfaceIds {kind: 'surface', label: 'Openings', actions: [{label: 'Close all openings', action: 'remove-argument'}]}
|
|
827
828
|
*/
|
|
828
829
|
shell(
|
|
@@ -833,7 +834,7 @@ export interface SolidModificationCapabilities<Elements extends NamedElements> {
|
|
|
833
834
|
|
|
834
835
|
export type Model<Elements extends NamedElements = {}> = ModelCapabilities<
|
|
835
836
|
Elements,
|
|
836
|
-
|
|
837
|
+
ModelKind
|
|
837
838
|
> &
|
|
838
839
|
Elements;
|
|
839
840
|
|
|
@@ -861,7 +862,7 @@ export type FaceModel<Elements extends NamedElements = PlanarElements> =
|
|
|
861
862
|
flip(): Surface;
|
|
862
863
|
/**
|
|
863
864
|
* Extrudes along the face's local plane normal. Signed distance; no recentering.
|
|
864
|
-
* @code3d.param distance {kind: 'length', label: 'Extrusion distance'}
|
|
865
|
+
* @code3d.param distance {kind: 'length', default: 10, label: 'Extrusion distance'}
|
|
865
866
|
*/
|
|
866
867
|
extrude(distance: number): SolidModel;
|
|
867
868
|
} & Elements;
|
|
@@ -1127,7 +1128,9 @@ export function modelTopologyIds(
|
|
|
1127
1128
|
: value instanceof ConstraintExpression
|
|
1128
1129
|
? value.self()
|
|
1129
1130
|
: topology?.source;
|
|
1130
|
-
const geometry = source
|
|
1131
|
+
const geometry = isModelObject(source)
|
|
1132
|
+
? source[modelGeometry]()?.value
|
|
1133
|
+
: undefined;
|
|
1131
1134
|
if (!geometry) return undefined;
|
|
1132
1135
|
return topologyChildren(
|
|
1133
1136
|
geometry.shape,
|
|
@@ -1179,12 +1182,12 @@ export abstract class ConstraintExpression {
|
|
|
1179
1182
|
appendUniqueParameters(this.parameters, parameters);
|
|
1180
1183
|
}
|
|
1181
1184
|
/** @internal */
|
|
1182
|
-
self():
|
|
1185
|
+
self(): RelationObject | undefined {
|
|
1183
1186
|
return this.context?.self;
|
|
1184
1187
|
}
|
|
1185
1188
|
/** @internal */
|
|
1186
1189
|
traceReference(): ConstraintTraceReference {
|
|
1187
|
-
const rebind = (model:
|
|
1190
|
+
const rebind = (model: RelationObject) =>
|
|
1188
1191
|
model === this.context?.original ? this.context.self : model;
|
|
1189
1192
|
return {
|
|
1190
1193
|
constraintId: this.constraintId,
|
|
@@ -1194,8 +1197,8 @@ export abstract class ConstraintExpression {
|
|
|
1194
1197
|
};
|
|
1195
1198
|
}
|
|
1196
1199
|
/** @internal */
|
|
1197
|
-
storeFor(model:
|
|
1198
|
-
const isSelf = (candidate:
|
|
1200
|
+
storeFor(model: RelationObject, original: RelationObject): StoredConstraint {
|
|
1201
|
+
const isSelf = (candidate: RelationObject | undefined) =>
|
|
1199
1202
|
candidate === model || candidate === original;
|
|
1200
1203
|
if (!isSelf(this.source.model) && !isSelf(this.target.model))
|
|
1201
1204
|
throw new Error(
|
|
@@ -1296,11 +1299,12 @@ export class Constraint extends ConstraintExpression {
|
|
|
1296
1299
|
* In the target reference axes, on() pins matching bound centers; align()
|
|
1297
1300
|
* translates self after alignment, retaining the relation's free modes.
|
|
1298
1301
|
* Explicit zero pins tangential coordinates for on() only.
|
|
1299
|
-
* @code3d.param x {kind: 'length', label: 'ΔX'}
|
|
1300
|
-
* @code3d.param y {kind: 'length', label: 'ΔY'}
|
|
1301
|
-
* @code3d.param z {kind: 'length', label: 'ΔZ'}
|
|
1302
|
+
* @code3d.param x {kind: 'length', default: 0, label: 'ΔX'}
|
|
1303
|
+
* @code3d.param y {kind: 'length', default: 0, label: 'ΔY'}
|
|
1304
|
+
* @code3d.param z {kind: 'length', default: 0, label: 'ΔZ'}
|
|
1302
1305
|
*/
|
|
1303
|
-
offset(x: number, y: number, z: number): Constraint
|
|
1306
|
+
offset(x: number, y: number, z: number): Constraint;
|
|
1307
|
+
offset(x = 0, y = 0, z = 0): Constraint {
|
|
1304
1308
|
assertFiniteVector('offset', [x, y, z]);
|
|
1305
1309
|
return new Constraint(
|
|
1306
1310
|
this.kind,
|
|
@@ -1315,11 +1319,12 @@ export class Constraint extends ConstraintExpression {
|
|
|
1315
1319
|
}
|
|
1316
1320
|
/**
|
|
1317
1321
|
* Select this rotation's pivot in self's local coordinates.
|
|
1318
|
-
* @code3d.param x {kind: 'length', label: 'Pivot X'}
|
|
1319
|
-
* @code3d.param y {kind: 'length', label: 'Pivot Y'}
|
|
1320
|
-
* @code3d.param z {kind: 'length', label: 'Pivot Z'}
|
|
1322
|
+
* @code3d.param x {kind: 'length', default: 0, label: 'Pivot X'}
|
|
1323
|
+
* @code3d.param y {kind: 'length', default: 0, label: 'Pivot Y'}
|
|
1324
|
+
* @code3d.param z {kind: 'length', default: 0, label: 'Pivot Z'}
|
|
1321
1325
|
*/
|
|
1322
|
-
pivot([x, y, z]: Vec3): ConstraintPivotChain
|
|
1326
|
+
pivot([x, y, z]: Vec3): ConstraintPivotChain;
|
|
1327
|
+
pivot([x = 0, y = 0, z = 0]: Vec3 = [0, 0, 0]): ConstraintPivotChain {
|
|
1323
1328
|
assertFiniteVector('pivot', [x, y, z]);
|
|
1324
1329
|
return new ConstraintPivotChain(this, {kind: 'pivot', point: [x, y, z]});
|
|
1325
1330
|
}
|
|
@@ -1334,7 +1339,9 @@ export class Constraint extends ConstraintExpression {
|
|
|
1334
1339
|
throw new Error('around() requires an axis or straight edge.');
|
|
1335
1340
|
const geometry =
|
|
1336
1341
|
reference.topology?.source[modelGeometry]()?.value ??
|
|
1337
|
-
(reference.whole
|
|
1342
|
+
(reference.whole && isModelObject(reference.model)
|
|
1343
|
+
? reference.model[modelGeometry]()?.value
|
|
1344
|
+
: undefined);
|
|
1338
1345
|
if (geometry) {
|
|
1339
1346
|
const straight = reference.topology
|
|
1340
1347
|
? withTopologyShape(
|
|
@@ -1353,11 +1360,12 @@ export class Constraint extends ConstraintExpression {
|
|
|
1353
1360
|
}
|
|
1354
1361
|
/**
|
|
1355
1362
|
* Rotate about self's origin and local X, Y, then Z axes, in degrees.
|
|
1356
|
-
* @code3d.param x {kind: 'angle', label: 'Rotate X'}
|
|
1357
|
-
* @code3d.param y {kind: 'angle', label: 'Rotate Y'}
|
|
1358
|
-
* @code3d.param z {kind: 'angle', label: 'Rotate Z'}
|
|
1363
|
+
* @code3d.param x {kind: 'angle', default: 0, label: 'Rotate X'}
|
|
1364
|
+
* @code3d.param y {kind: 'angle', default: 0, label: 'Rotate Y'}
|
|
1365
|
+
* @code3d.param z {kind: 'angle', default: 0, label: 'Rotate Z'}
|
|
1359
1366
|
*/
|
|
1360
|
-
rotate(x: number, y: number, z: number): Constraint
|
|
1367
|
+
rotate(x: number, y: number, z: number): Constraint;
|
|
1368
|
+
rotate(x = 0, y = 0, z = 0): Constraint {
|
|
1361
1369
|
return this.withRotation({kind: 'pivot', point: origin}, [x, y, z], this);
|
|
1362
1370
|
}
|
|
1363
1371
|
/** @internal */
|
|
@@ -1416,11 +1424,12 @@ export class ConstraintPivotChain extends ConstraintExpression {
|
|
|
1416
1424
|
this.spatialOperation = {kind: selection.kind, pivot: selection};
|
|
1417
1425
|
}
|
|
1418
1426
|
/**
|
|
1419
|
-
* @code3d.param x {kind: 'angle', label: 'Rotate X'}
|
|
1420
|
-
* @code3d.param y {kind: 'angle', label: 'Rotate Y'}
|
|
1421
|
-
* @code3d.param z {kind: 'angle', label: 'Rotate Z'}
|
|
1427
|
+
* @code3d.param x {kind: 'angle', default: 0, label: 'Rotate X'}
|
|
1428
|
+
* @code3d.param y {kind: 'angle', default: 0, label: 'Rotate Y'}
|
|
1429
|
+
* @code3d.param z {kind: 'angle', default: 0, label: 'Rotate Z'}
|
|
1422
1430
|
*/
|
|
1423
|
-
rotate(x: number, y: number, z: number): Constraint
|
|
1431
|
+
rotate(x: number, y: number, z: number): Constraint;
|
|
1432
|
+
rotate(x = 0, y = 0, z = 0): Constraint {
|
|
1424
1433
|
return this.constraint.withRotation(this.selection, [x, y, z], this);
|
|
1425
1434
|
}
|
|
1426
1435
|
}
|
|
@@ -1433,8 +1442,9 @@ export class ConstraintAroundChain extends ConstraintExpression {
|
|
|
1433
1442
|
super(...constraint.chainArguments());
|
|
1434
1443
|
this.spatialOperation = {kind: selection.kind, pivot: selection};
|
|
1435
1444
|
}
|
|
1436
|
-
/** @code3d.param angle {kind: 'angle', label: 'Rotate'} */
|
|
1437
|
-
rotate(angle: number): Constraint
|
|
1445
|
+
/** @code3d.param angle {kind: 'angle', default: 0, label: 'Rotate'} */
|
|
1446
|
+
rotate(angle: number): Constraint;
|
|
1447
|
+
rotate(angle = 0): Constraint {
|
|
1438
1448
|
return this.constraint.withRotation(this.selection, angle, this);
|
|
1439
1449
|
}
|
|
1440
1450
|
}
|
|
@@ -1445,173 +1455,70 @@ const referenceBoundsParts = Symbol('referenceBoundsParts');
|
|
|
1445
1455
|
const modelSnapshotQueries = Symbol('modelSnapshotQueries');
|
|
1446
1456
|
const relationPreview = Symbol('relationPreview');
|
|
1447
1457
|
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
declare readonly [modelNamedElements]: Elements;
|
|
1455
|
-
/** @internal */
|
|
1456
|
-
readonly elementKind: ModelElementKind<Kind>;
|
|
1457
|
-
/** @internal */
|
|
1458
|
-
readonly nodeId: string;
|
|
1459
|
-
/** @internal */
|
|
1460
|
-
readonly kind: Kind;
|
|
1461
|
-
/** @internal */
|
|
1462
|
-
readonly name: string;
|
|
1463
|
-
/** @internal */
|
|
1464
|
-
private readonly materialSnapshot?: ModelMaterialSnapshot;
|
|
1465
|
-
/** @internal */
|
|
1466
|
-
readonly children: readonly ModelObject[];
|
|
1467
|
-
/** @internal */
|
|
1468
|
-
get sourceRefs(): SourceRef[] {
|
|
1469
|
-
return valueTrace(this).sourceRefs;
|
|
1470
|
-
}
|
|
1471
|
-
/** @internal */
|
|
1472
|
-
get parameters(): ParameterUsage[] {
|
|
1473
|
-
return valueTrace(this).parameters;
|
|
1474
|
-
}
|
|
1475
|
-
private readonly geometry?: ModelGeometry;
|
|
1476
|
-
private readonly assembly?: SolveContext;
|
|
1477
|
-
private readonly meshTolerance: number;
|
|
1478
|
-
private readonly geometryAnchor: StoredElement;
|
|
1479
|
-
private readonly elements: StoredElements;
|
|
1480
|
-
private constraints: StoredConstraint[];
|
|
1481
|
-
private readonly operation: StoredOperation;
|
|
1458
|
+
type RelationObjectInit = Readonly<{
|
|
1459
|
+
nodeId?: string;
|
|
1460
|
+
constraints?: readonly StoredConstraint[];
|
|
1461
|
+
sourceRefs?: readonly SourceRef[];
|
|
1462
|
+
parameters?: readonly ParameterUsage[];
|
|
1463
|
+
}>;
|
|
1482
1464
|
|
|
1465
|
+
/** A local reference frame and its relations, independent of finite geometry. */
|
|
1466
|
+
export abstract class RelationObject {
|
|
1467
|
+
readonly nodeId: string;
|
|
1483
1468
|
/** @internal */
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
}
|
|
1469
|
+
abstract readonly name: string;
|
|
1470
|
+
protected constraints: StoredConstraint[];
|
|
1487
1471
|
|
|
1488
|
-
|
|
1489
|
-
if (init.kind !== 'group' && !init.geometry) {
|
|
1490
|
-
throw new Error(
|
|
1491
|
-
'A geometric model object must contain an OpenCascade shape.',
|
|
1492
|
-
);
|
|
1493
|
-
}
|
|
1472
|
+
protected constructor(init: RelationObjectInit = {}) {
|
|
1494
1473
|
this.nodeId = init.nodeId ?? `node-${nextNodeId++}`;
|
|
1495
|
-
this.kind = init.kind;
|
|
1496
|
-
this.elementKind = modelElementKinds[
|
|
1497
|
-
init.kind
|
|
1498
|
-
] as unknown as ModelElementKind<Kind>;
|
|
1499
|
-
this.geometry = init.geometry;
|
|
1500
|
-
this.geometryAnchor = init.geometryAnchor ?? {
|
|
1501
|
-
kind: this.elementKind,
|
|
1502
|
-
transform: identityRigidTransform,
|
|
1503
|
-
};
|
|
1504
|
-
this.meshTolerance = init.meshTolerance ?? 0.2;
|
|
1505
|
-
this.name = init.name ?? defaultModelNames[init.kind];
|
|
1506
|
-
this.materialSnapshot = init.material;
|
|
1507
|
-
this.children = init.children ?? [];
|
|
1508
|
-
this.assembly =
|
|
1509
|
-
init.assembly ??
|
|
1510
|
-
(this.kind === 'group'
|
|
1511
|
-
? ModelObject.createAssembly(this.children)
|
|
1512
|
-
: undefined);
|
|
1513
1474
|
this.constraints = [...(init.constraints ?? [])];
|
|
1514
1475
|
valueTraces.set(this, {
|
|
1515
1476
|
sourceRefs: [...(init.sourceRefs ?? [])],
|
|
1516
1477
|
parameters: [...(init.parameters ?? [])],
|
|
1517
1478
|
});
|
|
1518
|
-
this.operation = init.operation;
|
|
1519
|
-
const elements =
|
|
1520
|
-
init.elements ??
|
|
1521
|
-
(this.kind === 'solid' && this.geometry
|
|
1522
|
-
? solidElements(this.geometry.value.localBounds)
|
|
1523
|
-
: {});
|
|
1524
|
-
this.elements = this.geometry
|
|
1525
|
-
? {
|
|
1526
|
-
center: {
|
|
1527
|
-
kind: 'point',
|
|
1528
|
-
transform: translation(
|
|
1529
|
-
boundsCenter(this.geometry.value.localBounds),
|
|
1530
|
-
),
|
|
1531
|
-
},
|
|
1532
|
-
...elements,
|
|
1533
|
-
}
|
|
1534
|
-
: elements;
|
|
1535
|
-
for (const [name, element] of Object.entries(this.elements)) {
|
|
1536
|
-
if (name in this) {
|
|
1537
|
-
throw new Error(
|
|
1538
|
-
`The element name ${name} conflicts with the model API.`,
|
|
1539
|
-
);
|
|
1540
|
-
}
|
|
1541
|
-
Object.defineProperty(this, name, {
|
|
1542
|
-
value: modelAnchor(this, name, element),
|
|
1543
|
-
});
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
|
-
/** @internal */
|
|
1548
|
-
static create<
|
|
1549
|
-
Elements extends NamedElements = {},
|
|
1550
|
-
Kind extends ModelKind = ModelKind,
|
|
1551
|
-
>(init: ModelObjectInit<Kind>): ModelObject<Elements, Kind> {
|
|
1552
|
-
return new ModelObject<Elements, Kind>(init);
|
|
1553
1479
|
}
|
|
1554
1480
|
|
|
1555
|
-
get
|
|
1556
|
-
return
|
|
1557
|
-
}
|
|
1558
|
-
get down(): Bound {
|
|
1559
|
-
return directionalBound(this.relationAnchorReference(), 'down');
|
|
1560
|
-
}
|
|
1561
|
-
get left(): Bound {
|
|
1562
|
-
return directionalBound(this.relationAnchorReference(), 'left');
|
|
1563
|
-
}
|
|
1564
|
-
get right(): Bound {
|
|
1565
|
-
return directionalBound(this.relationAnchorReference(), 'right');
|
|
1566
|
-
}
|
|
1567
|
-
get front(): Bound {
|
|
1568
|
-
return directionalBound(this.relationAnchorReference(), 'front');
|
|
1569
|
-
}
|
|
1570
|
-
get back(): Bound {
|
|
1571
|
-
return directionalBound(this.relationAnchorReference(), 'back');
|
|
1481
|
+
get sourceRefs(): SourceRef[] {
|
|
1482
|
+
return valueTrace(this).sourceRefs;
|
|
1572
1483
|
}
|
|
1573
1484
|
|
|
1574
|
-
|
|
1575
|
-
return
|
|
1576
|
-
this.relationAnchorReference(),
|
|
1577
|
-
boundReference(target),
|
|
1578
|
-
);
|
|
1485
|
+
get parameters(): ParameterUsage[] {
|
|
1486
|
+
return valueTrace(this).parameters;
|
|
1579
1487
|
}
|
|
1580
1488
|
|
|
1581
|
-
|
|
1582
|
-
relationAnchorReference(): AnchorReference {
|
|
1583
|
-
return {model: this, name: 'geometry', ...this.geometryAnchor, whole: true};
|
|
1584
|
-
}
|
|
1489
|
+
protected abstract copyRelations(init: RelationObjectInit): RelationObject;
|
|
1585
1490
|
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
anchorReference(target),
|
|
1590
|
-
'align',
|
|
1491
|
+
protected vertexPosition(_id: VertexId): Vec3 {
|
|
1492
|
+
throw new Error(
|
|
1493
|
+
'pivotVertex() requires model topology, not a sketch reference frame. Use pivot([x, y, z]) instead.',
|
|
1591
1494
|
);
|
|
1592
1495
|
}
|
|
1593
1496
|
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1497
|
+
/** @internal */
|
|
1498
|
+
abstract relatedObjects(): readonly RelationObject[];
|
|
1499
|
+
/** @internal */
|
|
1500
|
+
abstract toSnapshot(
|
|
1501
|
+
meshCache?: Map<AnyShape, RenderMesh>,
|
|
1502
|
+
): ModelSnapshotObject;
|
|
1503
|
+
/** @internal */
|
|
1504
|
+
abstract attachOperationTrace(
|
|
1505
|
+
siteId: string,
|
|
1506
|
+
execution: number,
|
|
1507
|
+
order: number,
|
|
1508
|
+
sourceRef: SourceRef,
|
|
1509
|
+
outputIndex?: number,
|
|
1510
|
+
): void;
|
|
1511
|
+
|
|
1512
|
+
/** Store relations only after the complete callback has returned. */
|
|
1513
|
+
protected addRelations(
|
|
1514
|
+
original: RelationObject,
|
|
1515
|
+
build: () => Constraint | readonly Constraint[],
|
|
1516
|
+
): readonly RelationObject[] {
|
|
1610
1517
|
const previous = activeRelate;
|
|
1611
|
-
activeRelate = {self:
|
|
1518
|
+
activeRelate = {self: this, original};
|
|
1612
1519
|
let built: Constraint | readonly Constraint[];
|
|
1613
1520
|
try {
|
|
1614
|
-
built = build(
|
|
1521
|
+
built = build();
|
|
1615
1522
|
} finally {
|
|
1616
1523
|
activeRelate = previous;
|
|
1617
1524
|
}
|
|
@@ -1623,1459 +1530,1787 @@ export class ModelObject<
|
|
|
1623
1530
|
throw new Error(
|
|
1624
1531
|
'relate() requires a completed Constraint; finish pivot/around with rotate().',
|
|
1625
1532
|
);
|
|
1626
|
-
return constraint.storeFor(
|
|
1533
|
+
return constraint.storeFor(this, original);
|
|
1627
1534
|
});
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
...uniqueModels(stored.flatMap(constraintReferences)).map(
|
|
1631
|
-
(model, index) => ({
|
|
1632
|
-
model,
|
|
1633
|
-
role: 'reference' as const,
|
|
1634
|
-
index,
|
|
1635
|
-
}),
|
|
1636
|
-
),
|
|
1637
|
-
);
|
|
1638
|
-
return related;
|
|
1535
|
+
this.constraints.push(...stored);
|
|
1536
|
+
return uniqueModels(stored.flatMap(constraintReferences));
|
|
1639
1537
|
}
|
|
1640
1538
|
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1539
|
+
[referenceBounds](
|
|
1540
|
+
reference: StoredAnchor,
|
|
1541
|
+
transform: RigidTransform,
|
|
1542
|
+
): LocalBounds {
|
|
1543
|
+
if (reference.bound) {
|
|
1544
|
+
const frame = composeTransforms(transform, reference.transform);
|
|
1545
|
+
const [x, z] = reference.bound.size;
|
|
1546
|
+
return pointBounds(
|
|
1547
|
+
[-1, 1].flatMap(a =>
|
|
1548
|
+
[-1, 1].map(
|
|
1549
|
+
b =>
|
|
1550
|
+
composeTransforms(
|
|
1551
|
+
frame,
|
|
1552
|
+
translation([(a * x) / 2, 0, (b * z) / 2]),
|
|
1553
|
+
).position,
|
|
1554
|
+
),
|
|
1555
|
+
),
|
|
1556
|
+
);
|
|
1557
|
+
}
|
|
1558
|
+
if (reference.kind === 'point') {
|
|
1559
|
+
const point = composeTransforms(transform, reference.transform).position;
|
|
1560
|
+
return [point, point];
|
|
1561
|
+
}
|
|
1562
|
+
throw new Error(
|
|
1563
|
+
`The reference ${reference.name} has no finite geometry. Select a model, vertex, edge, or surface for on().`,
|
|
1651
1564
|
);
|
|
1652
|
-
const operation = storedOperation('expose', [
|
|
1653
|
-
{model: this, role: 'source', index: 0},
|
|
1654
|
-
...uniqueModels(references)
|
|
1655
|
-
.filter(model => model !== this)
|
|
1656
|
-
.map((model, index) => ({
|
|
1657
|
-
model,
|
|
1658
|
-
role: 'reference' as const,
|
|
1659
|
-
index,
|
|
1660
|
-
})),
|
|
1661
|
-
]);
|
|
1662
|
-
return this.copy(
|
|
1663
|
-
{elements: {...this.elements, ...exposed}},
|
|
1664
|
-
operation,
|
|
1665
|
-
) as unknown as RuntimeModel<
|
|
1666
|
-
MergedElements<Elements, ExposedElements<Sources>>,
|
|
1667
|
-
Kind
|
|
1668
|
-
>;
|
|
1669
1565
|
}
|
|
1670
1566
|
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
const
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
bound,
|
|
1688
|
-
facing,
|
|
1689
|
-
} = source instanceof ModelObject ? source.exposedElement() : reference;
|
|
1690
|
-
const memberPose = members.get(reference.model);
|
|
1691
|
-
if (memberPose === null)
|
|
1692
|
-
throw new Error(
|
|
1693
|
-
"The point or element belongs to multiple occurrences. Expose it through the intended child model's named reference.",
|
|
1694
|
-
);
|
|
1695
|
-
const transform =
|
|
1696
|
-
memberPose ??
|
|
1697
|
-
relativeTransform(
|
|
1698
|
-
reference.model.solvePose(context!),
|
|
1699
|
-
this.solvePose(context!),
|
|
1700
|
-
);
|
|
1701
|
-
return transformElement(
|
|
1702
|
-
{kind, transform: frame, topology, members: nested, bound, facing},
|
|
1703
|
-
transform,
|
|
1704
|
-
);
|
|
1705
|
-
});
|
|
1567
|
+
protected alignmentGeometry(reference: StoredAnchor): AlignmentGeometry {
|
|
1568
|
+
if (reference.kind === 'point')
|
|
1569
|
+
return {kind: 'point', point: reference.transform.position};
|
|
1570
|
+
const direction = rotateVector(
|
|
1571
|
+
[
|
|
1572
|
+
0,
|
|
1573
|
+
reference.kind === 'line'
|
|
1574
|
+
? (reference.direction ?? 1)
|
|
1575
|
+
: (reference.facing ?? reference.bound?.facing ?? 1),
|
|
1576
|
+
0,
|
|
1577
|
+
],
|
|
1578
|
+
reference.transform.quaternion,
|
|
1579
|
+
);
|
|
1580
|
+
return reference.kind === 'line'
|
|
1581
|
+
? {kind: 'line', point: reference.transform.position, direction}
|
|
1582
|
+
: {kind: 'plane', point: reference.transform.position, normal: direction};
|
|
1706
1583
|
}
|
|
1707
1584
|
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
return this.selectTopology('edge', ids) as unknown as readonly Edge[];
|
|
1719
|
-
}
|
|
1720
|
-
surface(id: SurfaceId): Surface {
|
|
1721
|
-
return this.surfaces([id])[0];
|
|
1585
|
+
/** @internal */
|
|
1586
|
+
attachSource(sourceRef: SourceRef): void {
|
|
1587
|
+
const previous = this.sourceRefs.at(-1);
|
|
1588
|
+
if (
|
|
1589
|
+
previous?.file !== sourceRef.file ||
|
|
1590
|
+
previous.start !== sourceRef.start ||
|
|
1591
|
+
previous.end !== sourceRef.end
|
|
1592
|
+
) {
|
|
1593
|
+
this.sourceRefs.push(sourceRef);
|
|
1594
|
+
}
|
|
1722
1595
|
}
|
|
1723
|
-
|
|
1724
|
-
|
|
1596
|
+
|
|
1597
|
+
/** @internal */
|
|
1598
|
+
attachParameters(parameters: readonly ParameterUsage[]): void {
|
|
1599
|
+
appendUniqueParameters(this.parameters, parameters);
|
|
1725
1600
|
}
|
|
1726
1601
|
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
ids?: readonly TopologyId[],
|
|
1730
|
-
): readonly ModelTopologyElement[] {
|
|
1731
|
-
const geometry = this.requireGeometry().value;
|
|
1732
|
-
const selected = topologyChildren(
|
|
1733
|
-
geometry.shape,
|
|
1734
|
-
geometry.topology,
|
|
1735
|
-
{kind: 'solid'},
|
|
1736
|
-
kind,
|
|
1737
|
-
ids,
|
|
1738
|
-
);
|
|
1739
|
-
return topologyReferences(
|
|
1740
|
-
this,
|
|
1741
|
-
'',
|
|
1742
|
-
{source: this, transform: identityRigidTransform, scale: 1},
|
|
1743
|
-
kind,
|
|
1744
|
-
selected,
|
|
1745
|
-
);
|
|
1602
|
+
solvePose(context: SolveContext): RigidTransform {
|
|
1603
|
+
return context.poses.get(this)!;
|
|
1746
1604
|
}
|
|
1747
1605
|
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1606
|
+
protected relationElement(reference: StoredAnchor): ElementSnapshot {
|
|
1607
|
+
const element = reference;
|
|
1608
|
+
const topology = element.topology;
|
|
1609
|
+
let arrow: RigidTransform | undefined;
|
|
1610
|
+
if (reference.kind === 'line' && topology) {
|
|
1611
|
+
const geometry = topology.source[modelGeometry]()!.value;
|
|
1612
|
+
arrow = withTopologyShape(
|
|
1613
|
+
geometry.shape,
|
|
1614
|
+
geometry.topology,
|
|
1615
|
+
topology.selection,
|
|
1616
|
+
shape => {
|
|
1617
|
+
const edge = shape as ReplicadEdge;
|
|
1618
|
+
const position = (reference.direction ?? 1) === 1 ? 1 : 0;
|
|
1619
|
+
const point = edge.pointAt(position),
|
|
1620
|
+
tangent = edge.tangentAt(position);
|
|
1621
|
+
try {
|
|
1622
|
+
return topologyTransform(
|
|
1623
|
+
topology,
|
|
1624
|
+
frameFromYAxis(
|
|
1625
|
+
point.toTuple(),
|
|
1626
|
+
tangent
|
|
1627
|
+
.toTuple()
|
|
1628
|
+
.map(v => v * (reference.direction ?? 1)) as unknown as Vec3,
|
|
1629
|
+
),
|
|
1630
|
+
);
|
|
1631
|
+
} finally {
|
|
1632
|
+
point.delete();
|
|
1633
|
+
tangent.delete();
|
|
1634
|
+
}
|
|
1635
|
+
},
|
|
1636
|
+
);
|
|
1637
|
+
}
|
|
1780
1638
|
return {
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
members: this.elements,
|
|
1784
|
-
topology: {
|
|
1785
|
-
...context,
|
|
1786
|
-
selection,
|
|
1787
|
-
},
|
|
1639
|
+
...snapshotElements({[reference.name]: element})[0],
|
|
1640
|
+
arrow,
|
|
1788
1641
|
};
|
|
1789
1642
|
}
|
|
1790
1643
|
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
)
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1644
|
+
private bodyRotations(
|
|
1645
|
+
constraint: StoredConstraint,
|
|
1646
|
+
indices: Map<RelationObject, number>,
|
|
1647
|
+
): BodyRotation[] {
|
|
1648
|
+
return constraint.rotations.map(({pivot, angles}) => {
|
|
1649
|
+
if (pivot.kind === 'around') {
|
|
1650
|
+
return pivot.axis.model
|
|
1651
|
+
? {
|
|
1652
|
+
body: indices.get(pivot.axis.model)!,
|
|
1653
|
+
axis: pivot.axis.transform,
|
|
1654
|
+
angle: (angles as number) * (pivot.axis.direction ?? 1),
|
|
1655
|
+
}
|
|
1656
|
+
: {
|
|
1657
|
+
local: axisRotation(
|
|
1658
|
+
pivot.axis.transform,
|
|
1659
|
+
(angles as number) * (pivot.axis.direction ?? 1),
|
|
1660
|
+
),
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
const frame = translation(
|
|
1664
|
+
pivot.kind === 'pivot' ? pivot.point : this.vertexPosition(pivot.id),
|
|
1665
|
+
);
|
|
1666
|
+
return {
|
|
1667
|
+
local: composeTransforms(
|
|
1668
|
+
composeTransforms(frame, rotationAround(origin, angles as Vec3)),
|
|
1669
|
+
invertTransform(frame),
|
|
1670
|
+
),
|
|
1671
|
+
};
|
|
1672
|
+
});
|
|
1806
1673
|
}
|
|
1807
1674
|
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1675
|
+
/** @internal */
|
|
1676
|
+
[relationPreview](
|
|
1677
|
+
nodeId: string,
|
|
1678
|
+
constraint: StoredConstraint,
|
|
1679
|
+
selection: ConstraintSpatialSelection | undefined,
|
|
1680
|
+
): ConstraintPreview {
|
|
1681
|
+
// This expression starts from the relate receiver, before sibling constraints
|
|
1682
|
+
// are returned and committed to self. Geometry and node identity stay shared.
|
|
1683
|
+
const owner = this.copyRelations({
|
|
1684
|
+
nodeId,
|
|
1685
|
+
constraints: [...this.constraints, constraint],
|
|
1816
1686
|
});
|
|
1687
|
+
const context = RelationObject.createSolveContext([
|
|
1688
|
+
owner,
|
|
1689
|
+
...(selection?.pivot.kind === 'around' && selection.pivot.axis.model
|
|
1690
|
+
? [selection.pivot.axis.model]
|
|
1691
|
+
: []),
|
|
1692
|
+
]);
|
|
1693
|
+
return {
|
|
1694
|
+
object: {
|
|
1695
|
+
nodeId,
|
|
1696
|
+
compositionTransform: toTransform(owner.solvePose(context)),
|
|
1697
|
+
constraints: owner.constraints.map(value =>
|
|
1698
|
+
owner.constraintSnapshot(value, context),
|
|
1699
|
+
),
|
|
1700
|
+
},
|
|
1701
|
+
spatial: selection
|
|
1702
|
+
? owner.constraintSpatial(constraint, selection, context)
|
|
1703
|
+
: undefined,
|
|
1704
|
+
};
|
|
1817
1705
|
}
|
|
1818
1706
|
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1707
|
+
private constraintSpatial(
|
|
1708
|
+
constraint: StoredConstraint,
|
|
1709
|
+
selection: ConstraintSpatialSelection,
|
|
1710
|
+
context: SolveContext,
|
|
1711
|
+
): ConstraintSpatialReference {
|
|
1712
|
+
const models = [...context.poses.keys()];
|
|
1713
|
+
const actions = this.bodyRotations(
|
|
1714
|
+
constraint,
|
|
1715
|
+
new Map(models.map((model, i) => [model, i])),
|
|
1825
1716
|
);
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
{model: this, role: 'source', index: 0},
|
|
1845
|
-
]);
|
|
1846
|
-
if (selection.kind === 'originPoint' && selection.model !== this)
|
|
1847
|
-
operation.inputs.push({
|
|
1848
|
-
model: selection.model,
|
|
1849
|
-
role: 'reference',
|
|
1850
|
-
index: 0,
|
|
1851
|
-
});
|
|
1852
|
-
operation.spatial = {origin, vector: offset};
|
|
1853
|
-
if (selection.kind === 'originVertex') {
|
|
1854
|
-
operation.selections.push({
|
|
1855
|
-
kind: 'vertex',
|
|
1856
|
-
input: this,
|
|
1857
|
-
ids: [selection.id],
|
|
1858
|
-
transform,
|
|
1859
|
-
});
|
|
1717
|
+
const stage = actions.length - (selection.kind === 'rotate' ? 1 : 0);
|
|
1718
|
+
const finalPose = this.solvePose(context);
|
|
1719
|
+
let before = finalPose;
|
|
1720
|
+
for (let i = actions.length - 1; i >= stage; i--) {
|
|
1721
|
+
const action = actions[i];
|
|
1722
|
+
before =
|
|
1723
|
+
'local' in action
|
|
1724
|
+
? composeTransforms(before, invertTransform(action.local))
|
|
1725
|
+
: composeTransforms(
|
|
1726
|
+
axisRotation(
|
|
1727
|
+
composeTransforms(
|
|
1728
|
+
models[action.body].solvePose(context),
|
|
1729
|
+
action.axis,
|
|
1730
|
+
),
|
|
1731
|
+
-action.angle,
|
|
1732
|
+
),
|
|
1733
|
+
before,
|
|
1734
|
+
);
|
|
1860
1735
|
}
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1736
|
+
const pivot = selection.pivot;
|
|
1737
|
+
let frame: RigidTransform;
|
|
1738
|
+
if (pivot.kind === 'around') {
|
|
1739
|
+
frame = composeTransforms(
|
|
1740
|
+
pivot.axis.model ? pivot.axis.model.solvePose(context) : before,
|
|
1741
|
+
(pivot.axis.direction ?? 1) === 1
|
|
1742
|
+
? pivot.axis.transform
|
|
1743
|
+
: composeTransforms(pivot.axis.transform, rotation([1, 0, 0, 0])),
|
|
1744
|
+
);
|
|
1745
|
+
} else {
|
|
1746
|
+
const local = translation(
|
|
1747
|
+
pivot.kind === 'pivot' ? pivot.point : this.vertexPosition(pivot.id),
|
|
1748
|
+
);
|
|
1749
|
+
frame = composeTransforms(before, local);
|
|
1750
|
+
}
|
|
1751
|
+
const angles = constraint.rotations[stage]?.angles ?? origin;
|
|
1752
|
+
const rotationVector: Vec3 =
|
|
1753
|
+
typeof angles === 'number' ? [0, angles, 0] : angles;
|
|
1754
|
+
const localFrame = relativeTransform(frame, finalPose);
|
|
1755
|
+
return {
|
|
1756
|
+
nodeId: this.nodeId,
|
|
1757
|
+
kind: selection.kind,
|
|
1758
|
+
spatial: {
|
|
1759
|
+
origin: localFrame.position,
|
|
1760
|
+
vector:
|
|
1761
|
+
selection.kind === 'rotate'
|
|
1762
|
+
? rotationVector
|
|
1763
|
+
: pivot.kind === 'pivot'
|
|
1764
|
+
? pivot.point
|
|
1765
|
+
: origin,
|
|
1766
|
+
frame: localFrame,
|
|
1767
|
+
rotation: rotationVector,
|
|
1768
|
+
axisOnly: pivot.kind === 'around',
|
|
1769
|
+
},
|
|
1770
|
+
};
|
|
1872
1771
|
}
|
|
1873
1772
|
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
name,
|
|
1883
|
-
transformElement(element, transform),
|
|
1884
|
-
]),
|
|
1885
|
-
),
|
|
1886
|
-
constraints: this.mapConstraintGeometry(
|
|
1887
|
-
element => transformElement(element, transform),
|
|
1888
|
-
point => composeTransforms(transform, translation(point)).position,
|
|
1889
|
-
),
|
|
1773
|
+
static createSolveContext(roots: readonly RelationObject[]): SolveContext {
|
|
1774
|
+
const models: RelationObject[] = [];
|
|
1775
|
+
const indices = new Map<RelationObject, number>();
|
|
1776
|
+
const collect = (model: RelationObject): void => {
|
|
1777
|
+
if (indices.has(model)) return;
|
|
1778
|
+
indices.set(model, models.length);
|
|
1779
|
+
models.push(model);
|
|
1780
|
+
model.constraints.flatMap(constraintReferences).forEach(collect);
|
|
1890
1781
|
};
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1782
|
+
roots.forEach(collect);
|
|
1783
|
+
const poses = solveBodies(
|
|
1784
|
+
models.map((model): Body => ({
|
|
1785
|
+
name: model.name,
|
|
1786
|
+
relations: model.constraints.map(constraint =>
|
|
1787
|
+
constraint.kind === 'align'
|
|
1788
|
+
? {
|
|
1789
|
+
kind: 'align',
|
|
1790
|
+
id: constraint.id,
|
|
1791
|
+
source: {
|
|
1792
|
+
body: indices.get(constraint.source.model ?? model)!,
|
|
1793
|
+
geometry: (
|
|
1794
|
+
constraint.source.model ?? model
|
|
1795
|
+
).alignmentGeometry(constraint.source),
|
|
1796
|
+
transform: constraint.source.transform,
|
|
1797
|
+
},
|
|
1798
|
+
target: {
|
|
1799
|
+
body: indices.get(constraint.target.model ?? model)!,
|
|
1800
|
+
geometry: (
|
|
1801
|
+
constraint.target.model ?? model
|
|
1802
|
+
).alignmentGeometry(constraint.target),
|
|
1803
|
+
transform: constraint.target.transform,
|
|
1804
|
+
},
|
|
1805
|
+
offset: constraint.offset,
|
|
1806
|
+
rotations: model.bodyRotations(constraint, indices),
|
|
1807
|
+
}
|
|
1808
|
+
: {
|
|
1809
|
+
kind: 'on',
|
|
1810
|
+
id: constraint.id,
|
|
1811
|
+
source: {
|
|
1812
|
+
body: indices.get(constraint.source.model ?? model)!,
|
|
1813
|
+
key: constraint.source.name,
|
|
1814
|
+
bounds: (orientation: Quaternion) =>
|
|
1815
|
+
(constraint.source.model ?? model)[referenceBounds](
|
|
1816
|
+
constraint.source,
|
|
1817
|
+
rotation(orientation),
|
|
1818
|
+
),
|
|
1819
|
+
},
|
|
1820
|
+
target: {
|
|
1821
|
+
body: indices.get(constraint.target.model ?? model)!,
|
|
1822
|
+
transform: constraint.target.transform,
|
|
1823
|
+
facing: constraint.target.bound!.facing,
|
|
1824
|
+
},
|
|
1825
|
+
offset: constraint.offset,
|
|
1826
|
+
rotations: model.bodyRotations(constraint, indices),
|
|
1827
|
+
},
|
|
1828
|
+
),
|
|
1829
|
+
})),
|
|
1917
1830
|
);
|
|
1918
|
-
return
|
|
1831
|
+
return {
|
|
1832
|
+
poses: new Map(models.map((model, index) => [model, poses[index]])),
|
|
1833
|
+
};
|
|
1919
1834
|
}
|
|
1920
1835
|
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
):
|
|
1925
|
-
const
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1836
|
+
protected constraintSnapshot(
|
|
1837
|
+
constraint: StoredConstraint,
|
|
1838
|
+
context: SolveContext,
|
|
1839
|
+
): ConstraintSnapshot {
|
|
1840
|
+
const source = constraint.source.model ?? this;
|
|
1841
|
+
const target = constraint.target.model ?? this;
|
|
1842
|
+
const models = [...context.poses.keys()];
|
|
1843
|
+
const actions = this.bodyRotations(
|
|
1844
|
+
constraint,
|
|
1845
|
+
new Map(models.map((model, i) => [model, i])),
|
|
1846
|
+
);
|
|
1847
|
+
let before = this.solvePose(context);
|
|
1848
|
+
for (const action of [...actions].reverse()) {
|
|
1849
|
+
before =
|
|
1850
|
+
'local' in action
|
|
1851
|
+
? composeTransforms(before, invertTransform(action.local))
|
|
1852
|
+
: composeTransforms(
|
|
1853
|
+
axisRotation(
|
|
1854
|
+
composeTransforms(
|
|
1855
|
+
models[action.body].solvePose(context),
|
|
1856
|
+
action.axis,
|
|
1857
|
+
),
|
|
1858
|
+
-action.angle,
|
|
1859
|
+
),
|
|
1860
|
+
before,
|
|
1861
|
+
);
|
|
1862
|
+
}
|
|
1863
|
+
const sourcePose = source === this ? before : source.solvePose(context);
|
|
1864
|
+
const targetPose = target === this ? before : target.solvePose(context);
|
|
1865
|
+
let offsetFrame = composeTransforms(
|
|
1866
|
+
targetPose,
|
|
1867
|
+
constraint.target.transform,
|
|
1868
|
+
);
|
|
1869
|
+
if (constraint.kind === 'align') {
|
|
1870
|
+
for (const action of actions)
|
|
1871
|
+
if ('body' in action)
|
|
1872
|
+
offsetFrame = composeTransforms(
|
|
1873
|
+
axisRotation(
|
|
1874
|
+
composeTransforms(
|
|
1875
|
+
models[action.body].solvePose(context),
|
|
1876
|
+
action.axis,
|
|
1877
|
+
),
|
|
1878
|
+
action.angle,
|
|
1879
|
+
),
|
|
1880
|
+
offsetFrame,
|
|
1881
|
+
);
|
|
1882
|
+
return {
|
|
1883
|
+
id: constraint.id,
|
|
1884
|
+
kind: 'align',
|
|
1885
|
+
source: anchorSnapshot(source, constraint.source),
|
|
1886
|
+
target: anchorSnapshot(target, constraint.target),
|
|
1887
|
+
sourceElement: source.relationElement(constraint.source),
|
|
1888
|
+
targetElement: target.relationElement(constraint.target),
|
|
1889
|
+
offsetDirection: 1,
|
|
1890
|
+
offset: constraint.offset ?? origin,
|
|
1891
|
+
offsetFrame: toTransform(offsetFrame),
|
|
1892
|
+
sourceRefs: [...valueTrace(constraint).sourceRefs],
|
|
1893
|
+
parameters: [...valueTrace(constraint).parameters],
|
|
1941
1894
|
};
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
source,
|
|
1958
|
-
identityRigidTransform,
|
|
1959
|
-
factor,
|
|
1960
|
-
),
|
|
1961
|
-
};
|
|
1962
|
-
} catch (error) {
|
|
1963
|
-
shape.delete();
|
|
1964
|
-
throw error;
|
|
1965
|
-
}
|
|
1966
|
-
});
|
|
1967
|
-
return this.copyWithGeometry(
|
|
1968
|
-
geometry,
|
|
1895
|
+
}
|
|
1896
|
+
const orientation = composeTransforms(
|
|
1897
|
+
invertTransform(rotation(offsetFrame.quaternion)),
|
|
1898
|
+
rotation(sourcePose.quaternion),
|
|
1899
|
+
).quaternion;
|
|
1900
|
+
const bounds = source[referenceBounds](
|
|
1901
|
+
constraint.source,
|
|
1902
|
+
rotation(orientation),
|
|
1903
|
+
);
|
|
1904
|
+
const center: Vec3 = [
|
|
1905
|
+
(bounds[0][0] + bounds[1][0]) / 2,
|
|
1906
|
+
bounds[constraint.target.bound!.facing === 1 ? 0 : 1][1],
|
|
1907
|
+
(bounds[0][2] + bounds[1][2]) / 2,
|
|
1908
|
+
];
|
|
1909
|
+
const sourceFrame = relativeTransform(
|
|
1969
1910
|
{
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
element => scaleElement(element, factor),
|
|
1974
|
-
point => scaleFrame(translation(point), factor).position,
|
|
1911
|
+
position: addVectors(
|
|
1912
|
+
sourcePose.position,
|
|
1913
|
+
rotateVector(center, offsetFrame.quaternion),
|
|
1975
1914
|
),
|
|
1915
|
+
quaternion: offsetFrame.quaternion,
|
|
1976
1916
|
},
|
|
1977
|
-
|
|
1978
|
-
);
|
|
1979
|
-
}
|
|
1980
|
-
|
|
1981
|
-
extrude(this: ModelObject<Elements, 'face'>, distance: number): SolidModel {
|
|
1982
|
-
if (this.kind !== 'face')
|
|
1983
|
-
throw new Error('extrude requires a single face model.');
|
|
1984
|
-
if (!Number.isFinite(distance) || distance === 0)
|
|
1985
|
-
throw new Error('Extrusion distance must be finite and non-zero.');
|
|
1986
|
-
const source = this.requireGeometry();
|
|
1987
|
-
const direction = rotateVector(
|
|
1988
|
-
[0, distance, 0],
|
|
1989
|
-
this.geometryAnchor.transform.quaternion,
|
|
1917
|
+
sourcePose,
|
|
1990
1918
|
);
|
|
1991
|
-
const
|
|
1992
|
-
|
|
1993
|
-
[
|
|
1994
|
-
[
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
1919
|
+
const sourceSize: Vec3 = [
|
|
1920
|
+
bounds[1][0] - bounds[0][0],
|
|
1921
|
+
bounds[1][1] - bounds[0][1],
|
|
1922
|
+
bounds[1][2] - bounds[0][2],
|
|
1923
|
+
];
|
|
1924
|
+
for (const action of actions) {
|
|
1925
|
+
if ('body' in action)
|
|
1926
|
+
offsetFrame = composeTransforms(
|
|
1927
|
+
axisRotation(
|
|
1928
|
+
composeTransforms(
|
|
1929
|
+
models[action.body].solvePose(context),
|
|
1930
|
+
action.axis,
|
|
1931
|
+
),
|
|
1932
|
+
action.angle,
|
|
1933
|
+
),
|
|
1934
|
+
offsetFrame,
|
|
1935
|
+
);
|
|
1936
|
+
}
|
|
1937
|
+
return {
|
|
1938
|
+
id: constraint.id,
|
|
1939
|
+
kind: constraint.kind,
|
|
1940
|
+
source: anchorSnapshot(source, constraint.source),
|
|
1941
|
+
target: anchorSnapshot(target, constraint.target),
|
|
1942
|
+
sourceBounds: {
|
|
1943
|
+
size: sourceSize,
|
|
1944
|
+
transform: toTransform(
|
|
1945
|
+
composeTransforms(sourceFrame, {
|
|
1946
|
+
position: [
|
|
1947
|
+
0,
|
|
1948
|
+
(constraint.target.bound!.facing * sourceSize[1]) / 2,
|
|
1949
|
+
0,
|
|
1950
|
+
],
|
|
1951
|
+
quaternion: [0, 0, 0, 1],
|
|
1952
|
+
}),
|
|
2003
1953
|
),
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
parameters: this.allParameters(),
|
|
2013
|
-
meshTolerance: this.meshTolerance,
|
|
2014
|
-
operation: storedOperation(
|
|
2015
|
-
'extrude',
|
|
2016
|
-
[{model: this, role: 'receiver', index: 0}],
|
|
2017
|
-
{
|
|
2018
|
-
dimensions: {
|
|
2019
|
-
distance: {
|
|
2020
|
-
origin: this.elements.center.transform.position,
|
|
2021
|
-
vector: direction,
|
|
2022
|
-
},
|
|
2023
|
-
},
|
|
1954
|
+
},
|
|
1955
|
+
sourceElement: {
|
|
1956
|
+
name: `${constraint.source.name}:bound`,
|
|
1957
|
+
kind: 'face',
|
|
1958
|
+
transform: toTransform(sourceFrame),
|
|
1959
|
+
bound: {
|
|
1960
|
+
size: [sourceSize[0], sourceSize[2]],
|
|
1961
|
+
facing: constraint.target.bound!.facing === 1 ? -1 : 1,
|
|
2024
1962
|
},
|
|
2025
|
-
|
|
2026
|
-
|
|
1963
|
+
},
|
|
1964
|
+
targetElement: {
|
|
1965
|
+
name: constraint.target.name,
|
|
1966
|
+
kind: 'face',
|
|
1967
|
+
transform: toTransform(constraint.target.transform),
|
|
1968
|
+
bound: constraint.target.bound,
|
|
1969
|
+
},
|
|
1970
|
+
offsetDirection: source === this ? 1 : -1,
|
|
1971
|
+
offset: constraint.offset ?? origin,
|
|
1972
|
+
offsetFrame: toTransform(offsetFrame),
|
|
1973
|
+
sourceRefs: [...valueTrace(constraint).sourceRefs],
|
|
1974
|
+
parameters: [...valueTrace(constraint).parameters],
|
|
1975
|
+
};
|
|
2027
1976
|
}
|
|
1977
|
+
}
|
|
2028
1978
|
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
1979
|
+
/** Plane-only spatial value used by sketches before a B-Rep face exists. */
|
|
1980
|
+
export class SketchFrame extends RelationObject {
|
|
1981
|
+
readonly name = 'Sketch';
|
|
1982
|
+
readonly plane: FaceAnchor;
|
|
1983
|
+
private readonly source?: SketchFrame;
|
|
1984
|
+
private readonly operation = storedOperation('sketch');
|
|
1985
|
+
|
|
1986
|
+
constructor(source?: SketchFrame, init: RelationObjectInit = {}) {
|
|
1987
|
+
super({
|
|
1988
|
+
constraints: source?.constraints,
|
|
1989
|
+
sourceRefs: source?.sourceRefs,
|
|
1990
|
+
parameters: source?.parameters,
|
|
1991
|
+
...init,
|
|
1992
|
+
});
|
|
1993
|
+
this.source = source;
|
|
1994
|
+
this.plane = modelAnchor(this, 'plane', {
|
|
1995
|
+
kind: 'face',
|
|
1996
|
+
transform: identityRigidTransform,
|
|
1997
|
+
}) as FaceAnchor;
|
|
2034
1998
|
}
|
|
2035
1999
|
|
|
2036
|
-
|
|
2037
|
-
this
|
|
2038
|
-
radius: number,
|
|
2039
|
-
edgeIds?: readonly EdgeId[],
|
|
2040
|
-
): SolidModel<Elements> {
|
|
2041
|
-
assertPositive('radius', radius);
|
|
2042
|
-
const source = this.requireSolidGeometry();
|
|
2043
|
-
const selectedEdgeIds = resolveEdgeSelection(
|
|
2044
|
-
source.value.topology.edges,
|
|
2045
|
-
edgeIds,
|
|
2046
|
-
);
|
|
2047
|
-
const geometry = evaluateSolidGeometry(
|
|
2048
|
-
'fillet',
|
|
2049
|
-
[radius, selectedEdgeIds],
|
|
2050
|
-
[source],
|
|
2051
|
-
() => {
|
|
2052
|
-
const result = filletEdges(
|
|
2053
|
-
source.value.shape,
|
|
2054
|
-
source.value.topology,
|
|
2055
|
-
radius,
|
|
2056
|
-
selectedEdgeIds,
|
|
2057
|
-
);
|
|
2058
|
-
return {shape: result.shape, topology: result.topology};
|
|
2059
|
-
},
|
|
2060
|
-
);
|
|
2061
|
-
return this.copyWithGeometry(
|
|
2062
|
-
geometry,
|
|
2063
|
-
{},
|
|
2064
|
-
storedOperation('fillet', [{model: this, role: 'source', index: 0}], {
|
|
2065
|
-
selections: [
|
|
2066
|
-
{
|
|
2067
|
-
kind: 'edge',
|
|
2068
|
-
input: this,
|
|
2069
|
-
ids: selectedEdgeIds,
|
|
2070
|
-
transform: identityRigidTransform,
|
|
2071
|
-
},
|
|
2072
|
-
],
|
|
2073
|
-
}),
|
|
2074
|
-
) as unknown as SolidModel<Elements>;
|
|
2075
|
-
}
|
|
2076
|
-
|
|
2077
|
-
chamfer(
|
|
2078
|
-
this: ModelObject<Elements, 'solid'>,
|
|
2079
|
-
distance: number,
|
|
2080
|
-
edgeIds?: readonly EdgeId[],
|
|
2081
|
-
): SolidModel<Elements> {
|
|
2082
|
-
assertPositive('distance', distance);
|
|
2083
|
-
const source = this.requireSolidGeometry();
|
|
2084
|
-
const selectedEdgeIds = resolveEdgeSelection(
|
|
2085
|
-
source.value.topology.edges,
|
|
2086
|
-
edgeIds,
|
|
2087
|
-
);
|
|
2088
|
-
const geometry = evaluateSolidGeometry(
|
|
2089
|
-
'chamfer',
|
|
2090
|
-
[distance, selectedEdgeIds],
|
|
2091
|
-
[source],
|
|
2092
|
-
() => {
|
|
2093
|
-
const result = chamferEdges(
|
|
2094
|
-
source.value.shape,
|
|
2095
|
-
source.value.topology,
|
|
2096
|
-
distance,
|
|
2097
|
-
selectedEdgeIds,
|
|
2098
|
-
);
|
|
2099
|
-
return {shape: result.shape, topology: result.topology};
|
|
2100
|
-
},
|
|
2101
|
-
);
|
|
2102
|
-
return this.copyWithGeometry(
|
|
2103
|
-
geometry,
|
|
2104
|
-
{},
|
|
2105
|
-
storedOperation('chamfer', [{model: this, role: 'source', index: 0}], {
|
|
2106
|
-
selections: [
|
|
2107
|
-
{
|
|
2108
|
-
kind: 'edge',
|
|
2109
|
-
input: this,
|
|
2110
|
-
ids: selectedEdgeIds,
|
|
2111
|
-
transform: identityRigidTransform,
|
|
2112
|
-
},
|
|
2113
|
-
],
|
|
2114
|
-
}),
|
|
2115
|
-
) as unknown as SolidModel<Elements>;
|
|
2116
|
-
}
|
|
2117
|
-
|
|
2118
|
-
shell(
|
|
2119
|
-
this: ModelObject<Elements, 'solid'>,
|
|
2120
|
-
thickness: number,
|
|
2121
|
-
removedSurfaceIds: readonly SurfaceId[] = [],
|
|
2122
|
-
): SolidModel<Elements> {
|
|
2123
|
-
if (!Number.isFinite(thickness) || thickness === 0) {
|
|
2124
|
-
throw new Error('thickness must be a nonzero finite number.');
|
|
2125
|
-
}
|
|
2126
|
-
const source = this.requireSolidGeometry();
|
|
2127
|
-
const selectedIds = resolveTopologySelection(
|
|
2128
|
-
'surface',
|
|
2129
|
-
source.value.topology.surfaces,
|
|
2130
|
-
removedSurfaceIds,
|
|
2131
|
-
);
|
|
2132
|
-
const geometry = evaluateSolidGeometry(
|
|
2133
|
-
'shell',
|
|
2134
|
-
[thickness, selectedIds],
|
|
2135
|
-
[source],
|
|
2136
|
-
() =>
|
|
2137
|
-
shellWithTopology(
|
|
2138
|
-
source.value.shape,
|
|
2139
|
-
source.value.topology,
|
|
2140
|
-
thickness,
|
|
2141
|
-
selectedIds,
|
|
2142
|
-
),
|
|
2143
|
-
);
|
|
2144
|
-
return this.copyWithGeometry(
|
|
2145
|
-
geometry,
|
|
2146
|
-
{},
|
|
2147
|
-
storedOperation('shell', [{model: this, role: 'source', index: 0}], {
|
|
2148
|
-
selections: [
|
|
2149
|
-
{
|
|
2150
|
-
kind: 'surface',
|
|
2151
|
-
input: this,
|
|
2152
|
-
ids: selectedIds,
|
|
2153
|
-
transform: identityRigidTransform,
|
|
2154
|
-
},
|
|
2155
|
-
],
|
|
2156
|
-
}),
|
|
2157
|
-
) as unknown as SolidModel<Elements>;
|
|
2158
|
-
}
|
|
2159
|
-
|
|
2160
|
-
/** @internal */
|
|
2161
|
-
attachSource(sourceRef: SourceRef): void {
|
|
2162
|
-
const previous = this.sourceRefs.at(-1);
|
|
2163
|
-
if (
|
|
2164
|
-
previous?.file !== sourceRef.file ||
|
|
2165
|
-
previous.start !== sourceRef.start ||
|
|
2166
|
-
previous.end !== sourceRef.end
|
|
2167
|
-
) {
|
|
2168
|
-
this.sourceRefs.push(sourceRef);
|
|
2169
|
-
}
|
|
2000
|
+
protected copyRelations(init: RelationObjectInit): SketchFrame {
|
|
2001
|
+
return new SketchFrame(this, init);
|
|
2170
2002
|
}
|
|
2171
2003
|
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2004
|
+
relatedObjects(): readonly RelationObject[] {
|
|
2005
|
+
return [
|
|
2006
|
+
...(this.source ? [this.source] : []),
|
|
2007
|
+
...this.constraints.flatMap(constraintReferences),
|
|
2008
|
+
];
|
|
2175
2009
|
}
|
|
2176
2010
|
|
|
2177
|
-
/** @internal */
|
|
2178
2011
|
attachOperationTrace(
|
|
2179
2012
|
siteId: string,
|
|
2180
2013
|
execution: number,
|
|
2181
2014
|
order: number,
|
|
2182
2015
|
sourceRef: SourceRef,
|
|
2016
|
+
outputIndex = 0,
|
|
2183
2017
|
): void {
|
|
2184
|
-
if (operationTraces.has(this.operation))
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
return [
|
|
2193
|
-
...this.children,
|
|
2194
|
-
...this.operation.inputs.map(input => input.model),
|
|
2195
|
-
...this.constraints.flatMap(constraintReferences),
|
|
2196
|
-
];
|
|
2197
|
-
}
|
|
2198
|
-
|
|
2199
|
-
/** @internal */
|
|
2200
|
-
toSnapshot(
|
|
2201
|
-
meshCache: Map<AnyShape, RenderMesh> = new Map(),
|
|
2202
|
-
): ModelSnapshotObject {
|
|
2203
|
-
const solveContext = ModelObject.createSolveContext([this]);
|
|
2204
|
-
return this.snapshotNode(meshCache, solveContext);
|
|
2018
|
+
if (!operationTraces.has(this.operation))
|
|
2019
|
+
operationTraces.set(this.operation, {
|
|
2020
|
+
siteId,
|
|
2021
|
+
execution,
|
|
2022
|
+
order,
|
|
2023
|
+
sourceRef,
|
|
2024
|
+
outputIndex,
|
|
2025
|
+
});
|
|
2205
2026
|
}
|
|
2206
2027
|
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
const material = overrideMaterial ?? this.materialSnapshot;
|
|
2214
|
-
const pose = this.solvePose(solveContext);
|
|
2215
|
-
const constraints = this.constraints.map(constraint =>
|
|
2216
|
-
this.constraintSnapshot(constraint, solveContext),
|
|
2217
|
-
);
|
|
2218
|
-
const parameters = uniqueParameters([
|
|
2219
|
-
...this.parameters,
|
|
2220
|
-
...this.constraints.flatMap(
|
|
2221
|
-
constraint => valueTrace(constraint).parameters,
|
|
2222
|
-
),
|
|
2223
|
-
]);
|
|
2224
|
-
const common = {
|
|
2225
|
-
nodeId: this.nodeId,
|
|
2226
|
-
kind: this.kind,
|
|
2028
|
+
toSnapshot(): ModelSnapshotObject {
|
|
2029
|
+
const {siteId, execution, order, sourceRef} =
|
|
2030
|
+
operationTraces.get(this.operation) ?? {};
|
|
2031
|
+
return {
|
|
2032
|
+
...this.snapshot(),
|
|
2033
|
+
kind: 'reference',
|
|
2227
2034
|
name: this.name,
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
transform: toTransform(inComposition ? pose : identityRigidTransform),
|
|
2231
|
-
constraints,
|
|
2232
|
-
origin,
|
|
2035
|
+
children: [],
|
|
2036
|
+
transform: toTransform(identityRigidTransform),
|
|
2233
2037
|
elements: snapshotElements({
|
|
2234
|
-
|
|
2235
|
-
...(this.geometry || this.children.length
|
|
2236
|
-
? Object.fromEntries(
|
|
2237
|
-
Object.keys(boundDirections).map(direction => [
|
|
2238
|
-
direction,
|
|
2239
|
-
anchorReference(this[direction as keyof DirectionalBounds]),
|
|
2240
|
-
]),
|
|
2241
|
-
)
|
|
2242
|
-
: {}),
|
|
2038
|
+
plane: {kind: 'face', transform: identityRigidTransform},
|
|
2243
2039
|
}),
|
|
2040
|
+
origin,
|
|
2244
2041
|
sourceRefs: [...this.sourceRefs],
|
|
2245
|
-
parameters,
|
|
2246
|
-
operation:
|
|
2247
|
-
|
|
2042
|
+
parameters: [...this.parameters],
|
|
2043
|
+
operation: {
|
|
2044
|
+
id: storedOperationId(this.operation),
|
|
2045
|
+
siteId,
|
|
2046
|
+
execution,
|
|
2047
|
+
order,
|
|
2048
|
+
sourceRef,
|
|
2049
|
+
kind: 'sketch',
|
|
2050
|
+
outputNodeId: this.nodeId,
|
|
2051
|
+
inputs: this.relatedObjects().map((model, index) => ({
|
|
2052
|
+
nodeId: model.nodeId,
|
|
2053
|
+
role: 'reference',
|
|
2054
|
+
index,
|
|
2055
|
+
})),
|
|
2056
|
+
regions: [],
|
|
2057
|
+
selections: [],
|
|
2058
|
+
},
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
2248
2061
|
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
};
|
|
2257
|
-
}
|
|
2062
|
+
relate(
|
|
2063
|
+
build: (frame: SketchFrame) => Constraint | readonly Constraint[],
|
|
2064
|
+
): SketchFrame {
|
|
2065
|
+
const related = new SketchFrame(this);
|
|
2066
|
+
related.addRelations(this, () => build(related));
|
|
2067
|
+
return related;
|
|
2068
|
+
}
|
|
2258
2069
|
|
|
2259
|
-
|
|
2070
|
+
face(region: SketchRegion): FaceModel {
|
|
2071
|
+
return sketchFaceModel(region, this.constraints, this);
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
snapshot(): ConstraintPreview['object'] {
|
|
2075
|
+
const context = RelationObject.createSolveContext([this]);
|
|
2260
2076
|
return {
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
geometry.value.shape,
|
|
2266
|
-
meshCache,
|
|
2267
|
-
this.meshTolerance,
|
|
2268
|
-
geometry.value.topology,
|
|
2077
|
+
nodeId: this.nodeId,
|
|
2078
|
+
compositionTransform: toTransform(this.solvePose(context)),
|
|
2079
|
+
constraints: this.constraints.map(constraint =>
|
|
2080
|
+
this.constraintSnapshot(constraint, context),
|
|
2269
2081
|
),
|
|
2270
2082
|
};
|
|
2271
2083
|
}
|
|
2272
2084
|
|
|
2273
|
-
/**
|
|
2274
|
-
|
|
2275
|
-
const
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
}
|
|
2288
|
-
this.children.forEach(child => child.disposeShape(disposed));
|
|
2085
|
+
/** Related finite models, expressed in this sketch's local editing plane. */
|
|
2086
|
+
context(): readonly Readonly<{nodeId: string; transform: Transform}>[] {
|
|
2087
|
+
const context = RelationObject.createSolveContext([this]);
|
|
2088
|
+
const frame = this.solvePose(context);
|
|
2089
|
+
return [...context.poses].flatMap(([object, pose]) =>
|
|
2090
|
+
object instanceof ModelObject
|
|
2091
|
+
? [
|
|
2092
|
+
{
|
|
2093
|
+
nodeId: object.nodeId,
|
|
2094
|
+
transform: toTransform(relativeTransform(pose, frame)),
|
|
2095
|
+
},
|
|
2096
|
+
]
|
|
2097
|
+
: [],
|
|
2098
|
+
);
|
|
2289
2099
|
}
|
|
2100
|
+
}
|
|
2290
2101
|
|
|
2102
|
+
export class ModelObject<
|
|
2103
|
+
Elements extends NamedElements = {},
|
|
2104
|
+
Kind extends ModelKind = ModelKind,
|
|
2105
|
+
>
|
|
2106
|
+
extends RelationObject
|
|
2107
|
+
implements Anchor<ModelElementKind<Kind>>
|
|
2108
|
+
{
|
|
2109
|
+
declare readonly [anchorKind]: ModelElementKind<Kind>;
|
|
2110
|
+
declare readonly [modelKind]: Kind;
|
|
2111
|
+
declare readonly [modelNamedElements]: Elements;
|
|
2291
2112
|
/** @internal */
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
transform: relativeTransform(section.solvePose(solveContext), resultPose),
|
|
2308
|
-
}));
|
|
2309
|
-
const spineInput = spine
|
|
2310
|
-
? {
|
|
2311
|
-
model: spine,
|
|
2312
|
-
geometry: spine.requireGeometry(),
|
|
2313
|
-
transform: relativeTransform(
|
|
2314
|
-
spine.solvePose(solveContext),
|
|
2315
|
-
resultPose,
|
|
2316
|
-
),
|
|
2317
|
-
}
|
|
2318
|
-
: undefined;
|
|
2319
|
-
const geometry = evaluateSolidGeometry(
|
|
2320
|
-
spine ? 'spine-loft' : 'loft',
|
|
2321
|
-
[
|
|
2322
|
-
ruled,
|
|
2323
|
-
sectionInputs.map(input => [
|
|
2324
|
-
input.transform.position,
|
|
2325
|
-
input.transform.quaternion,
|
|
2326
|
-
]),
|
|
2327
|
-
spineInput
|
|
2328
|
-
? [spineInput.transform.position, spineInput.transform.quaternion]
|
|
2329
|
-
: null,
|
|
2330
|
-
],
|
|
2331
|
-
[
|
|
2332
|
-
...sectionInputs.map(input => input.geometry),
|
|
2333
|
-
...(spineInput ? [spineInput.geometry] : []),
|
|
2334
|
-
],
|
|
2335
|
-
() => buildLoftGeometry(sectionInputs, spineInput, ruled),
|
|
2336
|
-
);
|
|
2337
|
-
const inputs = [...sections, ...(spine ? [spine] : [])];
|
|
2338
|
-
return ModelObject.create<CanonicalElements, 'solid'>({
|
|
2339
|
-
kind: 'solid',
|
|
2340
|
-
name: 'Loft',
|
|
2341
|
-
geometry,
|
|
2342
|
-
material: this.materialSnapshot,
|
|
2343
|
-
constraints: this.constraints,
|
|
2344
|
-
sourceRefs: inputs.flatMap(input => input.sourceRefs),
|
|
2345
|
-
parameters: uniqueParameters(
|
|
2346
|
-
inputs.flatMap(input => input.allParameters()),
|
|
2347
|
-
),
|
|
2348
|
-
meshTolerance: Math.min(...inputs.map(input => input.meshTolerance)),
|
|
2349
|
-
operation: storedOperation('loft', [
|
|
2350
|
-
{model: this, role: 'receiver', index: 0},
|
|
2351
|
-
...others.map((model, index) => ({
|
|
2352
|
-
model,
|
|
2353
|
-
role: 'section' as const,
|
|
2354
|
-
index: index + 1,
|
|
2355
|
-
})),
|
|
2356
|
-
...(spine
|
|
2357
|
-
? [{model: spine, role: 'spine' as const, index: sections.length}]
|
|
2358
|
-
: []),
|
|
2359
|
-
]),
|
|
2360
|
-
}) as unknown as SolidModel;
|
|
2361
|
-
}
|
|
2113
|
+
readonly elementKind: ModelElementKind<Kind>;
|
|
2114
|
+
/** @internal */
|
|
2115
|
+
readonly kind: Kind;
|
|
2116
|
+
/** @internal */
|
|
2117
|
+
readonly name: string;
|
|
2118
|
+
/** @internal */
|
|
2119
|
+
private readonly materialSnapshot?: ModelMaterialSnapshot;
|
|
2120
|
+
/** @internal */
|
|
2121
|
+
readonly children: readonly ModelObject[];
|
|
2122
|
+
private readonly geometry?: ModelGeometry;
|
|
2123
|
+
private readonly assembly?: SolveContext;
|
|
2124
|
+
private readonly meshTolerance: number;
|
|
2125
|
+
private readonly geometryAnchor: StoredElement;
|
|
2126
|
+
private readonly elements: StoredElements;
|
|
2127
|
+
private readonly operation: StoredOperation;
|
|
2362
2128
|
|
|
2363
2129
|
/** @internal */
|
|
2364
|
-
[
|
|
2365
|
-
this
|
|
2366
|
-
operation: BooleanOperation,
|
|
2367
|
-
others: readonly ModelObject<{}, 'solid'>[],
|
|
2368
|
-
): SolidModel {
|
|
2369
|
-
const evaluation = this.evaluateBoolean(operation, others);
|
|
2370
|
-
let transferred = false;
|
|
2371
|
-
try {
|
|
2372
|
-
const combined = ModelObject.create<CanonicalElements, 'solid'>({
|
|
2373
|
-
kind: 'solid',
|
|
2374
|
-
geometryAnchor: this.geometryAnchor,
|
|
2375
|
-
geometry: evaluation.geometry,
|
|
2376
|
-
name: this.name,
|
|
2377
|
-
material: this.materialSnapshot,
|
|
2378
|
-
constraints: this.constraints,
|
|
2379
|
-
sourceRefs: [this, ...others].flatMap(model => model.sourceRefs),
|
|
2380
|
-
parameters: uniqueParameters(
|
|
2381
|
-
[this, ...others].flatMap(model => model.allParameters()),
|
|
2382
|
-
),
|
|
2383
|
-
meshTolerance: Math.min(
|
|
2384
|
-
this.meshTolerance,
|
|
2385
|
-
...others.map(model => model.meshTolerance),
|
|
2386
|
-
),
|
|
2387
|
-
operation: storedOperation(
|
|
2388
|
-
operation === 'fuse' ? 'union' : operation,
|
|
2389
|
-
[
|
|
2390
|
-
{model: this, role: 'receiver', index: 0},
|
|
2391
|
-
...others.map((model, index) => ({
|
|
2392
|
-
model,
|
|
2393
|
-
role:
|
|
2394
|
-
operation === 'cut' ? ('tool' as const) : ('operand' as const),
|
|
2395
|
-
index: index + 1,
|
|
2396
|
-
})),
|
|
2397
|
-
],
|
|
2398
|
-
{regions: evaluation.regions},
|
|
2399
|
-
),
|
|
2400
|
-
});
|
|
2401
|
-
transferred = true;
|
|
2402
|
-
return combined as unknown as SolidModel;
|
|
2403
|
-
} finally {
|
|
2404
|
-
if (!transferred) {
|
|
2405
|
-
disposeModelGeometryValue(evaluation.geometry.value);
|
|
2406
|
-
evaluation.regions.forEach(region => region.artifact.value.delete());
|
|
2407
|
-
}
|
|
2408
|
-
}
|
|
2130
|
+
[modelGeometry](): ModelGeometry | undefined {
|
|
2131
|
+
return this.geometry;
|
|
2409
2132
|
}
|
|
2410
2133
|
|
|
2411
|
-
private
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
)
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
kind: 'section',
|
|
2452
|
-
input: other,
|
|
2453
|
-
frame: this,
|
|
2454
|
-
artifact: evaluateKernelShape(
|
|
2455
|
-
'boolean-section-region',
|
|
2456
|
-
[],
|
|
2457
|
-
[geometry, operand],
|
|
2458
|
-
() => unionSectionShape(geometry.value.shape, operand.value),
|
|
2459
|
-
),
|
|
2460
|
-
});
|
|
2461
|
-
}
|
|
2462
|
-
const nextGeometry = evaluateSolidGeometry(
|
|
2463
|
-
`boolean-${operation}`,
|
|
2464
|
-
[otherIndex],
|
|
2465
|
-
[geometry, operand],
|
|
2466
|
-
() => {
|
|
2467
|
-
const result = booleanWithTopology(
|
|
2468
|
-
{
|
|
2469
|
-
shape: geometry.value.shape,
|
|
2470
|
-
topology: geometry.value.topology,
|
|
2471
|
-
index: otherIndex === 0 ? 1 : undefined,
|
|
2472
|
-
},
|
|
2473
|
-
{
|
|
2474
|
-
shape: operand.value,
|
|
2475
|
-
topology: otherGeometry.value.topology,
|
|
2476
|
-
index: otherIndex + 2,
|
|
2477
|
-
},
|
|
2478
|
-
operation,
|
|
2479
|
-
);
|
|
2480
|
-
return {shape: result.shape, topology: result.topology};
|
|
2481
|
-
},
|
|
2482
|
-
);
|
|
2483
|
-
temporaryGeometry?.value.shape.delete();
|
|
2484
|
-
temporaryGeometry = nextGeometry;
|
|
2485
|
-
geometry = nextGeometry;
|
|
2486
|
-
} finally {
|
|
2487
|
-
operand.value.delete();
|
|
2134
|
+
private constructor(init: ModelObjectInit<Kind>) {
|
|
2135
|
+
super(init);
|
|
2136
|
+
if (init.kind !== 'group' && !init.geometry) {
|
|
2137
|
+
throw new Error(
|
|
2138
|
+
'A geometric model object must contain an OpenCascade shape.',
|
|
2139
|
+
);
|
|
2140
|
+
}
|
|
2141
|
+
this.kind = init.kind;
|
|
2142
|
+
this.elementKind = modelElementKinds[
|
|
2143
|
+
init.kind
|
|
2144
|
+
] as unknown as ModelElementKind<Kind>;
|
|
2145
|
+
this.geometry = init.geometry;
|
|
2146
|
+
this.geometryAnchor = init.geometryAnchor ?? {
|
|
2147
|
+
kind: this.elementKind,
|
|
2148
|
+
transform: identityRigidTransform,
|
|
2149
|
+
};
|
|
2150
|
+
this.meshTolerance = init.meshTolerance ?? 0.2;
|
|
2151
|
+
this.name = init.name ?? defaultModelNames[init.kind];
|
|
2152
|
+
this.materialSnapshot = init.material;
|
|
2153
|
+
this.children = init.children ?? [];
|
|
2154
|
+
this.assembly =
|
|
2155
|
+
init.assembly ??
|
|
2156
|
+
(this.kind === 'group'
|
|
2157
|
+
? ModelObject.createAssembly(this.children)
|
|
2158
|
+
: undefined);
|
|
2159
|
+
this.operation = init.operation;
|
|
2160
|
+
const elements =
|
|
2161
|
+
init.elements ??
|
|
2162
|
+
(this.kind === 'solid' && this.geometry
|
|
2163
|
+
? solidElements(this.geometry.value.localBounds)
|
|
2164
|
+
: {});
|
|
2165
|
+
this.elements = this.geometry
|
|
2166
|
+
? {
|
|
2167
|
+
center: {
|
|
2168
|
+
kind: 'point',
|
|
2169
|
+
transform: translation(
|
|
2170
|
+
boundsCenter(this.geometry.value.localBounds),
|
|
2171
|
+
),
|
|
2172
|
+
},
|
|
2173
|
+
...elements,
|
|
2488
2174
|
}
|
|
2175
|
+
: elements;
|
|
2176
|
+
for (const [name, element] of Object.entries(this.elements)) {
|
|
2177
|
+
if (name in this) {
|
|
2178
|
+
throw new Error(
|
|
2179
|
+
`The element name ${name} conflicts with the model API.`,
|
|
2180
|
+
);
|
|
2489
2181
|
}
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
if (!evaluated) {
|
|
2494
|
-
temporaryGeometry?.value.shape.delete();
|
|
2495
|
-
regions.forEach(region => region.artifact.value.delete());
|
|
2496
|
-
}
|
|
2182
|
+
Object.defineProperty(this, name, {
|
|
2183
|
+
value: modelAnchor(this, name, element),
|
|
2184
|
+
});
|
|
2497
2185
|
}
|
|
2498
2186
|
}
|
|
2499
2187
|
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
]);
|
|
2188
|
+
/** @internal */
|
|
2189
|
+
static create<
|
|
2190
|
+
Elements extends NamedElements = {},
|
|
2191
|
+
Kind extends ModelKind = ModelKind,
|
|
2192
|
+
>(init: ModelObjectInit<Kind>): ModelObject<Elements, Kind> {
|
|
2193
|
+
return new ModelObject<Elements, Kind>(init);
|
|
2507
2194
|
}
|
|
2508
2195
|
|
|
2509
|
-
|
|
2510
|
-
return
|
|
2196
|
+
get up(): Bound {
|
|
2197
|
+
return directionalBound(this.relationAnchorReference(), 'up');
|
|
2511
2198
|
}
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
const members = new Map<ModelObject, RigidTransform | null>([
|
|
2515
|
-
[this, identityRigidTransform],
|
|
2516
|
-
]);
|
|
2517
|
-
if (this.children.length) {
|
|
2518
|
-
const context = this.assembly!;
|
|
2519
|
-
for (const child of this.children) {
|
|
2520
|
-
const pose = child.solvePose(context);
|
|
2521
|
-
for (const [member, localPose] of child.memberPoses()) {
|
|
2522
|
-
members.set(
|
|
2523
|
-
member,
|
|
2524
|
-
members.has(member) || localPose === null
|
|
2525
|
-
? null
|
|
2526
|
-
: composeTransforms(pose, localPose),
|
|
2527
|
-
);
|
|
2528
|
-
}
|
|
2529
|
-
}
|
|
2530
|
-
}
|
|
2531
|
-
return members;
|
|
2199
|
+
get down(): Bound {
|
|
2200
|
+
return directionalBound(this.relationAnchorReference(), 'down');
|
|
2532
2201
|
}
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
bounds: pointBounds(
|
|
2545
|
-
[-1, 1].flatMap(a =>
|
|
2546
|
-
[-1, 1].map(
|
|
2547
|
-
b =>
|
|
2548
|
-
composeTransforms(
|
|
2549
|
-
frame,
|
|
2550
|
-
translation([(a * x) / 2, 0, (b * z) / 2]),
|
|
2551
|
-
).position,
|
|
2552
|
-
),
|
|
2553
|
-
),
|
|
2554
|
-
),
|
|
2555
|
-
},
|
|
2556
|
-
];
|
|
2557
|
-
}
|
|
2558
|
-
if (reference.whole) {
|
|
2559
|
-
if (this.geometry) return [boundsQuery(this.geometry, transform)];
|
|
2560
|
-
const context = this.assembly!;
|
|
2561
|
-
return this.children.flatMap(child =>
|
|
2562
|
-
child[referenceBoundsParts](
|
|
2563
|
-
child.relationAnchorReference(),
|
|
2564
|
-
composeTransforms(transform, child.solvePose(context)),
|
|
2565
|
-
),
|
|
2566
|
-
);
|
|
2567
|
-
}
|
|
2568
|
-
if (reference.topology) {
|
|
2569
|
-
const topology = reference.topology;
|
|
2570
|
-
return [
|
|
2571
|
-
boundsQuery(
|
|
2572
|
-
topology.source.requireGeometry(),
|
|
2573
|
-
composeTransforms(transform, topology.transform),
|
|
2574
|
-
topology.selection,
|
|
2575
|
-
topology.scale,
|
|
2576
|
-
),
|
|
2577
|
-
];
|
|
2578
|
-
}
|
|
2579
|
-
if (reference.kind === 'point') {
|
|
2580
|
-
const point = composeTransforms(transform, reference.transform).position;
|
|
2581
|
-
return [{bounds: [point, point]}];
|
|
2582
|
-
}
|
|
2583
|
-
throw new Error(
|
|
2584
|
-
`The reference ${reference.name} has no finite geometry. Select a model, vertex, edge, or surface for on().`,
|
|
2585
|
-
);
|
|
2202
|
+
get left(): Bound {
|
|
2203
|
+
return directionalBound(this.relationAnchorReference(), 'left');
|
|
2204
|
+
}
|
|
2205
|
+
get right(): Bound {
|
|
2206
|
+
return directionalBound(this.relationAnchorReference(), 'right');
|
|
2207
|
+
}
|
|
2208
|
+
get front(): Bound {
|
|
2209
|
+
return directionalBound(this.relationAnchorReference(), 'front');
|
|
2210
|
+
}
|
|
2211
|
+
get back(): Bound {
|
|
2212
|
+
return directionalBound(this.relationAnchorReference(), 'back');
|
|
2586
2213
|
}
|
|
2587
2214
|
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
): LocalBounds {
|
|
2593
|
-
const bounds = this[referenceBoundsParts](reference, transform).map(part =>
|
|
2594
|
-
'bounds' in part
|
|
2595
|
-
? part.bounds
|
|
2596
|
-
: (evaluateSnapshotQuery(part) as LocalBounds),
|
|
2215
|
+
on(target: Bound): Constraint {
|
|
2216
|
+
return Constraint.create(
|
|
2217
|
+
this.relationAnchorReference(),
|
|
2218
|
+
boundReference(target),
|
|
2597
2219
|
);
|
|
2598
|
-
return bounds.length === 1 ? bounds[0] : combineBounds(bounds);
|
|
2599
2220
|
}
|
|
2600
2221
|
|
|
2601
|
-
/**
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
if (this.geometry || this.children.length) {
|
|
2605
|
-
for (const direction of Object.values(boundDirections)) {
|
|
2606
|
-
const transform = invertTransform(
|
|
2607
|
-
rotation(frameFromYAxis(origin, direction).quaternion),
|
|
2608
|
-
);
|
|
2609
|
-
for (const part of this[referenceBoundsParts](
|
|
2610
|
-
this.relationAnchorReference(),
|
|
2611
|
-
transform,
|
|
2612
|
-
))
|
|
2613
|
-
if (!('bounds' in part)) result.push(part);
|
|
2614
|
-
}
|
|
2615
|
-
}
|
|
2616
|
-
if (this.geometry)
|
|
2617
|
-
result.push(
|
|
2618
|
-
meshQuery(
|
|
2619
|
-
this.geometry,
|
|
2620
|
-
this.geometry.value.shape,
|
|
2621
|
-
this.meshTolerance,
|
|
2622
|
-
this.geometry.value.topology,
|
|
2623
|
-
),
|
|
2624
|
-
);
|
|
2625
|
-
for (const region of this.operation.regions)
|
|
2626
|
-
result.push(
|
|
2627
|
-
meshQuery(region.artifact, region.artifact.value, this.meshTolerance),
|
|
2628
|
-
);
|
|
2629
|
-
return result;
|
|
2222
|
+
/** @internal */
|
|
2223
|
+
relationAnchorReference(): AnchorReference {
|
|
2224
|
+
return {model: this, name: 'geometry', ...this.geometryAnchor, whole: true};
|
|
2630
2225
|
}
|
|
2631
2226
|
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
? (this.requireGeometry().value.shape as ReplicadVertex).asTuple()
|
|
2638
|
-
: reference.transform.position,
|
|
2639
|
-
};
|
|
2640
|
-
const read = (shape: AnyShape) =>
|
|
2641
|
-
reference.kind === 'line'
|
|
2642
|
-
? edgeGeometry(shape as ReplicadEdge, reference.direction ?? 1)
|
|
2643
|
-
: faceGeometry(
|
|
2644
|
-
shape as ReplicadFace,
|
|
2645
|
-
reference.facing ?? reference.bound?.facing ?? 1,
|
|
2646
|
-
);
|
|
2647
|
-
if (reference.topology) {
|
|
2648
|
-
const topology = reference.topology,
|
|
2649
|
-
geometry = topology.source.requireGeometry().value;
|
|
2650
|
-
return withTopologyShape(
|
|
2651
|
-
geometry.shape,
|
|
2652
|
-
geometry.topology,
|
|
2653
|
-
topology.selection,
|
|
2654
|
-
shape => {
|
|
2655
|
-
const scaled =
|
|
2656
|
-
topology.scale === 1
|
|
2657
|
-
? shape
|
|
2658
|
-
: shapeWithScale(shape, topology.scale);
|
|
2659
|
-
try {
|
|
2660
|
-
return transformGeometry(read(scaled), topology.transform);
|
|
2661
|
-
} finally {
|
|
2662
|
-
if (scaled !== shape) scaled.delete();
|
|
2663
|
-
}
|
|
2664
|
-
},
|
|
2665
|
-
);
|
|
2666
|
-
}
|
|
2667
|
-
if (reference.whole) return read(this.requireGeometry().value.shape);
|
|
2668
|
-
const direction = rotateVector(
|
|
2669
|
-
[
|
|
2670
|
-
0,
|
|
2671
|
-
reference.kind === 'line'
|
|
2672
|
-
? (reference.direction ?? 1)
|
|
2673
|
-
: (reference.facing ?? reference.bound?.facing ?? 1),
|
|
2674
|
-
0,
|
|
2675
|
-
],
|
|
2676
|
-
reference.transform.quaternion,
|
|
2227
|
+
align(target: Anchor<'point' | 'line' | 'face'>): Constraint {
|
|
2228
|
+
return Constraint.create(
|
|
2229
|
+
this.relationAnchorReference(),
|
|
2230
|
+
anchorReference(target),
|
|
2231
|
+
'align',
|
|
2677
2232
|
);
|
|
2678
|
-
return reference.kind === 'line'
|
|
2679
|
-
? {kind: 'line', point: reference.transform.position, direction}
|
|
2680
|
-
: {kind: 'plane', point: reference.transform.position, normal: direction};
|
|
2681
2233
|
}
|
|
2682
2234
|
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
? {
|
|
2686
|
-
...this.exposedElement(),
|
|
2687
|
-
name: reference.name,
|
|
2688
|
-
direction: reference.direction,
|
|
2689
|
-
facing: reference.facing,
|
|
2690
|
-
}
|
|
2691
|
-
: reference;
|
|
2692
|
-
const topology = element.topology;
|
|
2693
|
-
let arrow: RigidTransform | undefined;
|
|
2694
|
-
if (reference.kind === 'line' && topology) {
|
|
2695
|
-
const geometry = topology.source.requireGeometry().value;
|
|
2696
|
-
arrow = withTopologyShape(
|
|
2697
|
-
geometry.shape,
|
|
2698
|
-
geometry.topology,
|
|
2699
|
-
topology.selection,
|
|
2700
|
-
shape => {
|
|
2701
|
-
const edge = shape as ReplicadEdge;
|
|
2702
|
-
const position = (reference.direction ?? 1) === 1 ? 1 : 0;
|
|
2703
|
-
const point = edge.pointAt(position),
|
|
2704
|
-
tangent = edge.tangentAt(position);
|
|
2705
|
-
try {
|
|
2706
|
-
return topologyTransform(
|
|
2707
|
-
topology,
|
|
2708
|
-
frameFromYAxis(
|
|
2709
|
-
point.toTuple(),
|
|
2710
|
-
tangent
|
|
2711
|
-
.toTuple()
|
|
2712
|
-
.map(v => v * (reference.direction ?? 1)) as unknown as Vec3,
|
|
2713
|
-
),
|
|
2714
|
-
);
|
|
2715
|
-
} finally {
|
|
2716
|
-
point.delete();
|
|
2717
|
-
tangent.delete();
|
|
2718
|
-
}
|
|
2719
|
-
},
|
|
2720
|
-
);
|
|
2721
|
-
}
|
|
2722
|
-
return {
|
|
2723
|
-
...snapshotElements({[reference.name]: element})[0],
|
|
2724
|
-
arrow,
|
|
2725
|
-
};
|
|
2235
|
+
reverse(): Edge {
|
|
2236
|
+
return this.edges()[0].reverse();
|
|
2726
2237
|
}
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
constraint: StoredConstraint,
|
|
2730
|
-
indices: Map<ModelObject, number>,
|
|
2731
|
-
): BodyRotation[] {
|
|
2732
|
-
return constraint.rotations.map(({pivot, angles}) => {
|
|
2733
|
-
if (pivot.kind === 'around') {
|
|
2734
|
-
return pivot.axis.model
|
|
2735
|
-
? {
|
|
2736
|
-
body: indices.get(pivot.axis.model)!,
|
|
2737
|
-
axis: pivot.axis.transform,
|
|
2738
|
-
angle: (angles as number) * (pivot.axis.direction ?? 1),
|
|
2739
|
-
}
|
|
2740
|
-
: {
|
|
2741
|
-
local: axisRotation(
|
|
2742
|
-
pivot.axis.transform,
|
|
2743
|
-
(angles as number) * (pivot.axis.direction ?? 1),
|
|
2744
|
-
),
|
|
2745
|
-
};
|
|
2746
|
-
}
|
|
2747
|
-
const frame = translation(
|
|
2748
|
-
pivot.kind === 'pivot'
|
|
2749
|
-
? pivot.point
|
|
2750
|
-
: anchorReference(this.vertex(pivot.id)).transform.position,
|
|
2751
|
-
);
|
|
2752
|
-
return {
|
|
2753
|
-
local: composeTransforms(
|
|
2754
|
-
composeTransforms(frame, rotationAround(origin, angles as Vec3)),
|
|
2755
|
-
invertTransform(frame),
|
|
2756
|
-
),
|
|
2757
|
-
};
|
|
2758
|
-
});
|
|
2238
|
+
flip(): Surface {
|
|
2239
|
+
return this.surfaces()[0].flip();
|
|
2759
2240
|
}
|
|
2760
2241
|
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
// are returned and committed to self. Geometry and node identity stay shared.
|
|
2769
|
-
const owner = this.copy(
|
|
2770
|
-
{nodeId, constraints: [...this.constraints, constraint]},
|
|
2771
|
-
this.operation,
|
|
2772
|
-
);
|
|
2773
|
-
const context = ModelObject.createSolveContext([
|
|
2774
|
-
owner,
|
|
2775
|
-
...(selection?.pivot.kind === 'around' && selection.pivot.axis.model
|
|
2776
|
-
? [selection.pivot.axis.model]
|
|
2777
|
-
: []),
|
|
2242
|
+
relate(
|
|
2243
|
+
build: (
|
|
2244
|
+
self: RuntimeModel<Elements, Kind>,
|
|
2245
|
+
) => Constraint | readonly Constraint[],
|
|
2246
|
+
): RuntimeModel<Elements, Kind> {
|
|
2247
|
+
const operation = storedOperation('relate', [
|
|
2248
|
+
{model: this, role: 'source', index: 0},
|
|
2778
2249
|
]);
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
},
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
: undefined,
|
|
2790
|
-
};
|
|
2250
|
+
const related = this.copy({}, operation);
|
|
2251
|
+
const references = related.addRelations(this, () => build(related));
|
|
2252
|
+
operation.inputs.push(
|
|
2253
|
+
...references.map((model, index) => ({
|
|
2254
|
+
model,
|
|
2255
|
+
role: 'reference' as const,
|
|
2256
|
+
index,
|
|
2257
|
+
})),
|
|
2258
|
+
);
|
|
2259
|
+
return related;
|
|
2791
2260
|
}
|
|
2792
2261
|
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
const
|
|
2799
|
-
|
|
2800
|
-
constraint,
|
|
2801
|
-
new Map(models.map((model, i) => [model, i])),
|
|
2262
|
+
expose<const Sources extends ElementSources>(
|
|
2263
|
+
sources: Sources,
|
|
2264
|
+
): RuntimeModel<MergedElements<Elements, ExposedElements<Sources>>, Kind> {
|
|
2265
|
+
const entries = Object.entries(sources);
|
|
2266
|
+
const elements = this.localElements(entries.map(([, source]) => source));
|
|
2267
|
+
const exposed = Object.fromEntries(
|
|
2268
|
+
entries.map(([name], index) => [name, elements[index]]),
|
|
2802
2269
|
);
|
|
2803
|
-
const
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
);
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2270
|
+
const references = entries.map(
|
|
2271
|
+
([, source]) => anchorReference(source).model,
|
|
2272
|
+
);
|
|
2273
|
+
const operation = storedOperation('expose', [
|
|
2274
|
+
{model: this, role: 'source', index: 0},
|
|
2275
|
+
...uniqueModels(references)
|
|
2276
|
+
.filter(model => model !== this)
|
|
2277
|
+
.map((model, index) => ({
|
|
2278
|
+
model,
|
|
2279
|
+
role: 'reference' as const,
|
|
2280
|
+
index,
|
|
2281
|
+
})),
|
|
2282
|
+
]);
|
|
2283
|
+
return this.copy(
|
|
2284
|
+
{elements: {...this.elements, ...exposed}},
|
|
2285
|
+
operation,
|
|
2286
|
+
) as unknown as RuntimeModel<
|
|
2287
|
+
MergedElements<Elements, ExposedElements<Sources>>,
|
|
2288
|
+
Kind
|
|
2289
|
+
>;
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
/** Resolve both own and occurrence references into this model's local frame. */
|
|
2293
|
+
private localElements(sources: readonly Anchor[]): StoredElement[] {
|
|
2294
|
+
const members = this.memberPoses();
|
|
2295
|
+
const external = sources
|
|
2296
|
+
.map(source => anchorReference(source).model)
|
|
2297
|
+
.filter(model => !members.has(model));
|
|
2298
|
+
const context = external.length
|
|
2299
|
+
? ModelObject.createSolveContext([this, ...external])
|
|
2300
|
+
: undefined;
|
|
2301
|
+
return sources.map(source => {
|
|
2302
|
+
const reference = anchorReference(source);
|
|
2303
|
+
const {
|
|
2304
|
+
kind,
|
|
2305
|
+
transform: frame,
|
|
2306
|
+
topology,
|
|
2307
|
+
members: nested,
|
|
2308
|
+
bound,
|
|
2309
|
+
facing,
|
|
2310
|
+
} = source instanceof ModelObject ? source.exposedElement() : reference;
|
|
2311
|
+
const memberPose = members.get(reference.model);
|
|
2312
|
+
if (memberPose === null)
|
|
2313
|
+
throw new Error(
|
|
2314
|
+
"The point or element belongs to multiple occurrences. Expose it through the intended child model's named reference.",
|
|
2315
|
+
);
|
|
2316
|
+
const transform =
|
|
2317
|
+
memberPose ??
|
|
2318
|
+
relativeTransform(
|
|
2319
|
+
reference.model.solvePose(context!),
|
|
2320
|
+
this.solvePose(context!),
|
|
2321
|
+
);
|
|
2322
|
+
return transformElement(
|
|
2323
|
+
{kind, transform: frame, topology, members: nested, bound, facing},
|
|
2324
|
+
transform,
|
|
2836
2325
|
);
|
|
2837
|
-
|
|
2838
|
-
}
|
|
2839
|
-
const angles = constraint.rotations[stage]?.angles ?? origin;
|
|
2840
|
-
const rotationVector: Vec3 =
|
|
2841
|
-
typeof angles === 'number' ? [0, angles, 0] : angles;
|
|
2842
|
-
const localFrame = relativeTransform(frame, finalPose);
|
|
2843
|
-
return {
|
|
2844
|
-
nodeId: this.nodeId,
|
|
2845
|
-
kind: selection.kind,
|
|
2846
|
-
spatial: {
|
|
2847
|
-
origin: localFrame.position,
|
|
2848
|
-
vector:
|
|
2849
|
-
selection.kind === 'rotate'
|
|
2850
|
-
? rotationVector
|
|
2851
|
-
: pivot.kind === 'pivot'
|
|
2852
|
-
? pivot.point
|
|
2853
|
-
: origin,
|
|
2854
|
-
frame: localFrame,
|
|
2855
|
-
rotation: rotationVector,
|
|
2856
|
-
axisOnly: pivot.kind === 'around',
|
|
2857
|
-
},
|
|
2858
|
-
};
|
|
2326
|
+
});
|
|
2859
2327
|
}
|
|
2860
2328
|
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
):
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
return
|
|
2329
|
+
vertex(id: VertexId): Vertex {
|
|
2330
|
+
return this.vertices([id])[0];
|
|
2331
|
+
}
|
|
2332
|
+
vertices(ids?: readonly VertexId[]): readonly Vertex[] {
|
|
2333
|
+
return this.selectTopology('vertex', ids) as unknown as readonly Vertex[];
|
|
2334
|
+
}
|
|
2335
|
+
edge(id: EdgeId): Edge {
|
|
2336
|
+
return this.edges([id])[0];
|
|
2337
|
+
}
|
|
2338
|
+
edges(ids?: readonly EdgeId[]): readonly Edge[] {
|
|
2339
|
+
return this.selectTopology('edge', ids) as unknown as readonly Edge[];
|
|
2340
|
+
}
|
|
2341
|
+
surface(id: SurfaceId): Surface {
|
|
2342
|
+
return this.surfaces([id])[0];
|
|
2343
|
+
}
|
|
2344
|
+
surfaces(ids?: readonly SurfaceId[]): readonly Surface[] {
|
|
2345
|
+
return this.selectTopology('surface', ids) as unknown as readonly Surface[];
|
|
2872
2346
|
}
|
|
2873
2347
|
|
|
2874
|
-
private
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
const
|
|
2879
|
-
const
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
kind: 'align',
|
|
2893
|
-
id: constraint.id,
|
|
2894
|
-
source: {
|
|
2895
|
-
body: indices.get(constraint.source.model ?? model)!,
|
|
2896
|
-
geometry: (
|
|
2897
|
-
constraint.source.model ?? model
|
|
2898
|
-
).alignmentGeometry(constraint.source),
|
|
2899
|
-
transform: constraint.source.transform,
|
|
2900
|
-
},
|
|
2901
|
-
target: {
|
|
2902
|
-
body: indices.get(constraint.target.model ?? model)!,
|
|
2903
|
-
geometry: (
|
|
2904
|
-
constraint.target.model ?? model
|
|
2905
|
-
).alignmentGeometry(constraint.target),
|
|
2906
|
-
transform: constraint.target.transform,
|
|
2907
|
-
},
|
|
2908
|
-
offset: constraint.offset,
|
|
2909
|
-
rotations: model.bodyRotations(constraint, indices),
|
|
2910
|
-
}
|
|
2911
|
-
: {
|
|
2912
|
-
kind: 'on',
|
|
2913
|
-
id: constraint.id,
|
|
2914
|
-
source: {
|
|
2915
|
-
body: indices.get(constraint.source.model ?? model)!,
|
|
2916
|
-
key: constraint.source.name,
|
|
2917
|
-
bounds: (orientation: Quaternion) =>
|
|
2918
|
-
(constraint.source.model ?? model)[referenceBounds](
|
|
2919
|
-
constraint.source,
|
|
2920
|
-
rotation(orientation),
|
|
2921
|
-
),
|
|
2922
|
-
},
|
|
2923
|
-
target: {
|
|
2924
|
-
body: indices.get(constraint.target.model ?? model)!,
|
|
2925
|
-
transform: constraint.target.transform,
|
|
2926
|
-
facing: constraint.target.bound!.facing,
|
|
2927
|
-
},
|
|
2928
|
-
offset: constraint.offset,
|
|
2929
|
-
rotations: model.bodyRotations(constraint, indices),
|
|
2930
|
-
},
|
|
2931
|
-
),
|
|
2932
|
-
})),
|
|
2348
|
+
private selectTopology(
|
|
2349
|
+
kind: TopologyKind,
|
|
2350
|
+
ids?: readonly TopologyId[],
|
|
2351
|
+
): readonly ModelTopologyElement[] {
|
|
2352
|
+
const geometry = this.requireGeometry().value;
|
|
2353
|
+
const selected = topologyChildren(
|
|
2354
|
+
geometry.shape,
|
|
2355
|
+
geometry.topology,
|
|
2356
|
+
{kind: 'solid'},
|
|
2357
|
+
kind,
|
|
2358
|
+
ids,
|
|
2359
|
+
);
|
|
2360
|
+
return topologyReferences(
|
|
2361
|
+
this,
|
|
2362
|
+
'',
|
|
2363
|
+
{source: this, transform: identityRigidTransform, scale: 1},
|
|
2364
|
+
kind,
|
|
2365
|
+
selected,
|
|
2933
2366
|
);
|
|
2934
|
-
return {
|
|
2935
|
-
poses: new Map(models.map((model, index) => [model, poses[index]])),
|
|
2936
|
-
};
|
|
2937
2367
|
}
|
|
2938
2368
|
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
const
|
|
2944
|
-
const
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2369
|
+
/** @internal */
|
|
2370
|
+
exposedElement(): StoredElement {
|
|
2371
|
+
if (this.kind === 'group')
|
|
2372
|
+
return {...this.geometryAnchor, members: this.elements};
|
|
2373
|
+
const geometry = this.requireGeometry().value;
|
|
2374
|
+
const kind = (
|
|
2375
|
+
this.kind === 'face' ? 'surface' : this.kind
|
|
2376
|
+
) as TopologySelection['kind'];
|
|
2377
|
+
const selection: TopologySelection =
|
|
2378
|
+
kind === 'solid'
|
|
2379
|
+
? {kind}
|
|
2380
|
+
: {
|
|
2381
|
+
kind,
|
|
2382
|
+
id: geometry.topology[
|
|
2383
|
+
kind === 'surface'
|
|
2384
|
+
? 'surfaces'
|
|
2385
|
+
: kind === 'edge'
|
|
2386
|
+
? 'edges'
|
|
2387
|
+
: 'vertices'
|
|
2388
|
+
].ids[0],
|
|
2389
|
+
};
|
|
2390
|
+
const context = {
|
|
2391
|
+
source: this,
|
|
2392
|
+
transform: identityRigidTransform,
|
|
2393
|
+
scale: 1,
|
|
2394
|
+
};
|
|
2395
|
+
const anchor =
|
|
2396
|
+
selection.kind === 'solid'
|
|
2397
|
+
? this.geometryAnchor
|
|
2398
|
+
: topologyReferences(this, '', context, selection.kind, [
|
|
2399
|
+
selection.id,
|
|
2400
|
+
])[0][anchorReferenceValue];
|
|
2401
|
+
return {
|
|
2402
|
+
kind: anchor.kind,
|
|
2403
|
+
transform: anchor.transform,
|
|
2404
|
+
members: this.elements,
|
|
2405
|
+
topology: {
|
|
2406
|
+
...context,
|
|
2407
|
+
selection,
|
|
2408
|
+
},
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
material(material: Material | string): RuntimeModel<Elements, Kind> {
|
|
2413
|
+
return this.copy(
|
|
2414
|
+
{material: captureModelMaterial(material)},
|
|
2415
|
+
storedOperation('material', [{model: this, role: 'source', index: 0}]),
|
|
2416
|
+
);
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
originOffset(
|
|
2420
|
+
dx: number,
|
|
2421
|
+
dy: number,
|
|
2422
|
+
dz: number,
|
|
2423
|
+
): RuntimeModel<Elements, Kind>;
|
|
2424
|
+
originOffset(dx = 0, dy = 0, dz = 0): RuntimeModel<Elements, Kind> {
|
|
2425
|
+
const offset: Vec3 = [dx, dy, dz];
|
|
2426
|
+
assertFiniteVector('originOffset', offset);
|
|
2427
|
+
return this.withOrigin(offset, {kind: 'originOffset'});
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
originPoint(point: PointAnchor): RuntimeModel<Elements, Kind> {
|
|
2431
|
+
const reference = anchorReference(point);
|
|
2432
|
+
if (reference.kind !== 'point')
|
|
2433
|
+
throw new Error('originPoint() requires a point reference.');
|
|
2434
|
+
const [element] = this.localElements([point]);
|
|
2435
|
+
return this.withOrigin(element.transform.position, {
|
|
2436
|
+
kind: 'originPoint',
|
|
2437
|
+
model: reference.model,
|
|
2438
|
+
});
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
originVertex(id: VertexId): RuntimeModel<Elements, Kind> {
|
|
2442
|
+
const geometry = this.requireGeometry().value;
|
|
2443
|
+
const [{position}] = topologyVertexPoints(
|
|
2444
|
+
geometry.shape,
|
|
2445
|
+
geometry.topology.vertices,
|
|
2446
|
+
[id],
|
|
2447
|
+
);
|
|
2448
|
+
return this.withOrigin(position, {kind: 'originVertex', id});
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
originCenter(): RuntimeModel<Elements, Kind> {
|
|
2452
|
+
this.requireGeometry();
|
|
2453
|
+
const position = this.elements.center.transform.position;
|
|
2454
|
+
return this.withOrigin(position, {kind: 'originCenter'});
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
private withOrigin(
|
|
2458
|
+
offset: Vec3,
|
|
2459
|
+
selection:
|
|
2460
|
+
| {kind: 'originOffset' | 'originCenter'}
|
|
2461
|
+
| {kind: 'originVertex'; id: VertexId}
|
|
2462
|
+
| {kind: 'originPoint'; model: RelationObject},
|
|
2463
|
+
): RuntimeModel<Elements, Kind> {
|
|
2464
|
+
const transform = translation(negateVector(offset));
|
|
2465
|
+
const operation = storedOperation(selection.kind, [
|
|
2466
|
+
{model: this, role: 'source', index: 0},
|
|
2467
|
+
]);
|
|
2468
|
+
if (selection.kind === 'originPoint' && selection.model !== this)
|
|
2469
|
+
operation.inputs.push({
|
|
2470
|
+
model: selection.model,
|
|
2471
|
+
role: 'reference',
|
|
2472
|
+
index: 0,
|
|
2473
|
+
});
|
|
2474
|
+
operation.spatial = {origin, vector: offset};
|
|
2475
|
+
if (selection.kind === 'originVertex') {
|
|
2476
|
+
operation.selections.push({
|
|
2477
|
+
kind: 'vertex',
|
|
2478
|
+
input: this,
|
|
2479
|
+
ids: [selection.id],
|
|
2480
|
+
transform,
|
|
2481
|
+
});
|
|
2482
|
+
}
|
|
2483
|
+
return this.transformed(transform, operation);
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
rotate(x: number, y: number, z: number): RuntimeModel<Elements, Kind>;
|
|
2487
|
+
rotate(x = 0, y = 0, z = 0): RuntimeModel<Elements, Kind> {
|
|
2488
|
+
const angles: Vec3 = [x, y, z];
|
|
2489
|
+
assertFiniteVector('rotate', angles);
|
|
2490
|
+
const operation = storedOperation('rotate', [
|
|
2491
|
+
{model: this, role: 'source', index: 0},
|
|
2492
|
+
]);
|
|
2493
|
+
operation.spatial = {origin, vector: angles};
|
|
2494
|
+
return this.transformed(rotationAround(origin, angles), operation);
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
private transformed(
|
|
2498
|
+
transform: RigidTransform,
|
|
2499
|
+
operation: StoredOperation,
|
|
2500
|
+
): RuntimeModel<Elements, Kind> {
|
|
2501
|
+
const overrides: Partial<ModelObjectInit<Kind>> = {
|
|
2502
|
+
geometryAnchor: transformElement(this.geometryAnchor, transform),
|
|
2503
|
+
elements: Object.fromEntries(
|
|
2504
|
+
Object.entries(this.elements).map(([name, element]) => [
|
|
2505
|
+
name,
|
|
2506
|
+
transformElement(element, transform),
|
|
2507
|
+
]),
|
|
2508
|
+
),
|
|
2509
|
+
constraints: this.mapConstraintGeometry(
|
|
2510
|
+
element => transformElement(element, transform),
|
|
2511
|
+
point => composeTransforms(transform, translation(point)).position,
|
|
2512
|
+
),
|
|
2513
|
+
};
|
|
2514
|
+
if (this.assembly)
|
|
2515
|
+
return this.copy(
|
|
2516
|
+
{
|
|
2517
|
+
...overrides,
|
|
2518
|
+
assembly: transformSolveContext(this.assembly, transform),
|
|
2519
|
+
},
|
|
2520
|
+
operation,
|
|
2521
|
+
);
|
|
2522
|
+
const source = this.requireGeometry();
|
|
2523
|
+
const geometry = evaluateModelGeometry(
|
|
2524
|
+
'transform',
|
|
2525
|
+
[transform.position, transform.quaternion],
|
|
2526
|
+
[source],
|
|
2527
|
+
() => {
|
|
2528
|
+
const shape = shapeWithTransform(source.value.shape, transform);
|
|
2529
|
+
try {
|
|
2530
|
+
return {
|
|
2531
|
+
shape,
|
|
2532
|
+
topology: preserveShapeTopology(shape, source.value.topology),
|
|
2533
|
+
referenceBasis: transformedReferenceBasis(source, transform, 1),
|
|
2534
|
+
};
|
|
2535
|
+
} catch (error) {
|
|
2536
|
+
shape.delete();
|
|
2537
|
+
throw error;
|
|
2538
|
+
}
|
|
2539
|
+
},
|
|
2540
|
+
);
|
|
2541
|
+
return this.copyWithGeometry(geometry, overrides, operation);
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
private mapConstraintGeometry(
|
|
2545
|
+
mapElement: (element: StoredElement) => StoredElement,
|
|
2546
|
+
mapPoint: (point: Vec3) => Vec3,
|
|
2547
|
+
): StoredConstraint[] {
|
|
2548
|
+
const mapReference = (reference: RelationReference): RelationReference =>
|
|
2549
|
+
reference.model ? reference : {...reference, ...mapElement(reference)};
|
|
2550
|
+
return this.constraints.map(constraint => {
|
|
2551
|
+
const mapped: StoredConstraint = {
|
|
2552
|
+
...constraint,
|
|
2553
|
+
source: mapReference(constraint.source),
|
|
2554
|
+
target: mapReference(constraint.target),
|
|
2555
|
+
rotations: constraint.rotations.map(action => ({
|
|
2556
|
+
...action,
|
|
2557
|
+
pivot:
|
|
2558
|
+
action.pivot.kind === 'around'
|
|
2559
|
+
? {...action.pivot, axis: mapReference(action.pivot.axis)}
|
|
2560
|
+
: action.pivot.kind === 'pivot'
|
|
2561
|
+
? {...action.pivot, point: mapPoint(action.pivot.point)}
|
|
2562
|
+
: action.pivot,
|
|
2563
|
+
})),
|
|
2564
|
+
};
|
|
2565
|
+
valueTraces.set(mapped, valueTrace(constraint));
|
|
2566
|
+
return mapped;
|
|
2567
|
+
});
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
scaled(factor: number): RuntimeModel<Elements, Kind>;
|
|
2571
|
+
scaled(factor = 1): RuntimeModel<Elements, Kind> {
|
|
2572
|
+
assertPositive('scale', factor);
|
|
2573
|
+
const source = this.requireGeometry();
|
|
2574
|
+
const geometry = evaluateModelGeometry('scaled', [factor], [source], () => {
|
|
2575
|
+
const shape = shapeWithScale(source.value.shape, factor);
|
|
2576
|
+
try {
|
|
2577
|
+
return {
|
|
2578
|
+
shape,
|
|
2579
|
+
topology: preserveShapeTopology(shape, source.value.topology),
|
|
2580
|
+
referenceBasis: transformedReferenceBasis(
|
|
2581
|
+
source,
|
|
2582
|
+
identityRigidTransform,
|
|
2583
|
+
factor,
|
|
2584
|
+
),
|
|
2585
|
+
};
|
|
2586
|
+
} catch (error) {
|
|
2587
|
+
shape.delete();
|
|
2588
|
+
throw error;
|
|
2589
|
+
}
|
|
2590
|
+
});
|
|
2591
|
+
return this.copyWithGeometry(
|
|
2592
|
+
geometry,
|
|
2593
|
+
{
|
|
2594
|
+
geometryAnchor: scaleElement(this.geometryAnchor, factor),
|
|
2595
|
+
elements: scaleElements(this.elements, factor),
|
|
2596
|
+
constraints: this.mapConstraintGeometry(
|
|
2597
|
+
element => scaleElement(element, factor),
|
|
2598
|
+
point => scaleFrame(translation(point), factor).position,
|
|
2599
|
+
),
|
|
2600
|
+
},
|
|
2601
|
+
storedOperation('scaled', [{model: this, role: 'source', index: 0}]),
|
|
2602
|
+
);
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
extrude(this: ModelObject<Elements, 'face'>, distance: number): SolidModel;
|
|
2606
|
+
extrude(this: ModelObject<Elements, 'face'>, distance = 10): SolidModel {
|
|
2607
|
+
if (this.kind !== 'face')
|
|
2608
|
+
throw new Error('extrude requires a single face model.');
|
|
2609
|
+
if (!Number.isFinite(distance) || distance === 0)
|
|
2610
|
+
throw new Error('Extrusion distance must be finite and non-zero.');
|
|
2611
|
+
const source = this.requireGeometry();
|
|
2612
|
+
const direction = rotateVector(
|
|
2613
|
+
[0, distance, 0],
|
|
2614
|
+
this.geometryAnchor.transform.quaternion,
|
|
2615
|
+
);
|
|
2616
|
+
const geometry = evaluateSolidGeometry(
|
|
2617
|
+
'extrude',
|
|
2618
|
+
[direction],
|
|
2619
|
+
[source],
|
|
2620
|
+
() =>
|
|
2621
|
+
extrudeWithTopology(
|
|
2622
|
+
{
|
|
2623
|
+
shape: source.value.shape,
|
|
2624
|
+
topology: source.value.topology,
|
|
2625
|
+
index: 1,
|
|
2626
|
+
},
|
|
2627
|
+
direction,
|
|
2628
|
+
),
|
|
2629
|
+
);
|
|
2630
|
+
return ModelObject.create<CanonicalElements, 'solid'>({
|
|
2631
|
+
kind: 'solid',
|
|
2632
|
+
name: 'Extrude',
|
|
2633
|
+
geometry,
|
|
2634
|
+
material: this.materialSnapshot,
|
|
2635
|
+
constraints: this.constraints,
|
|
2636
|
+
sourceRefs: this.sourceRefs,
|
|
2637
|
+
parameters: this.allParameters(),
|
|
2638
|
+
meshTolerance: this.meshTolerance,
|
|
2639
|
+
operation: storedOperation(
|
|
2640
|
+
'extrude',
|
|
2641
|
+
[{model: this, role: 'receiver', index: 0}],
|
|
2642
|
+
{
|
|
2643
|
+
dimensions: {
|
|
2644
|
+
distance: {
|
|
2645
|
+
origin: this.elements.center.transform.position,
|
|
2646
|
+
vector: direction,
|
|
2647
|
+
},
|
|
2648
|
+
},
|
|
2649
|
+
},
|
|
2650
|
+
),
|
|
2651
|
+
}) as unknown as SolidModel;
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
cut(
|
|
2655
|
+
this: ModelObject<Elements, 'solid'>,
|
|
2656
|
+
tools: readonly SolidModel<{}>[],
|
|
2657
|
+
): SolidModel {
|
|
2658
|
+
return cut(this as unknown as SolidModel<{}>, tools);
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
fillet(
|
|
2662
|
+
this: ModelObject<Elements, 'solid'>,
|
|
2663
|
+
radius: number,
|
|
2664
|
+
edgeIds?: readonly EdgeId[],
|
|
2665
|
+
): SolidModel<Elements>;
|
|
2666
|
+
fillet(
|
|
2667
|
+
this: ModelObject<Elements, 'solid'>,
|
|
2668
|
+
radius = 1,
|
|
2669
|
+
edgeIds?: readonly EdgeId[],
|
|
2670
|
+
): SolidModel<Elements> {
|
|
2671
|
+
assertPositive('radius', radius);
|
|
2672
|
+
const source = this.requireSolidGeometry();
|
|
2673
|
+
const selectedEdgeIds = resolveEdgeSelection(
|
|
2674
|
+
source.value.topology.edges,
|
|
2675
|
+
edgeIds,
|
|
2676
|
+
);
|
|
2677
|
+
const geometry = evaluateSolidGeometry(
|
|
2678
|
+
'fillet',
|
|
2679
|
+
[radius, selectedEdgeIds],
|
|
2680
|
+
[source],
|
|
2681
|
+
() => {
|
|
2682
|
+
const result = filletEdges(
|
|
2683
|
+
source.value.shape,
|
|
2684
|
+
source.value.topology,
|
|
2685
|
+
radius,
|
|
2686
|
+
selectedEdgeIds,
|
|
2687
|
+
);
|
|
2688
|
+
return {shape: result.shape, topology: result.topology};
|
|
2689
|
+
},
|
|
2690
|
+
);
|
|
2691
|
+
return this.copyWithGeometry(
|
|
2692
|
+
geometry,
|
|
2693
|
+
{},
|
|
2694
|
+
storedOperation('fillet', [{model: this, role: 'source', index: 0}], {
|
|
2695
|
+
selections: [
|
|
2696
|
+
{
|
|
2697
|
+
kind: 'edge',
|
|
2698
|
+
input: this,
|
|
2699
|
+
ids: selectedEdgeIds,
|
|
2700
|
+
transform: identityRigidTransform,
|
|
2701
|
+
},
|
|
2702
|
+
],
|
|
2703
|
+
}),
|
|
2704
|
+
) as unknown as SolidModel<Elements>;
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
chamfer(
|
|
2708
|
+
this: ModelObject<Elements, 'solid'>,
|
|
2709
|
+
distance: number,
|
|
2710
|
+
edgeIds?: readonly EdgeId[],
|
|
2711
|
+
): SolidModel<Elements>;
|
|
2712
|
+
chamfer(
|
|
2713
|
+
this: ModelObject<Elements, 'solid'>,
|
|
2714
|
+
distance = 1,
|
|
2715
|
+
edgeIds?: readonly EdgeId[],
|
|
2716
|
+
): SolidModel<Elements> {
|
|
2717
|
+
assertPositive('distance', distance);
|
|
2718
|
+
const source = this.requireSolidGeometry();
|
|
2719
|
+
const selectedEdgeIds = resolveEdgeSelection(
|
|
2720
|
+
source.value.topology.edges,
|
|
2721
|
+
edgeIds,
|
|
2722
|
+
);
|
|
2723
|
+
const geometry = evaluateSolidGeometry(
|
|
2724
|
+
'chamfer',
|
|
2725
|
+
[distance, selectedEdgeIds],
|
|
2726
|
+
[source],
|
|
2727
|
+
() => {
|
|
2728
|
+
const result = chamferEdges(
|
|
2729
|
+
source.value.shape,
|
|
2730
|
+
source.value.topology,
|
|
2731
|
+
distance,
|
|
2732
|
+
selectedEdgeIds,
|
|
2733
|
+
);
|
|
2734
|
+
return {shape: result.shape, topology: result.topology};
|
|
2735
|
+
},
|
|
2736
|
+
);
|
|
2737
|
+
return this.copyWithGeometry(
|
|
2738
|
+
geometry,
|
|
2739
|
+
{},
|
|
2740
|
+
storedOperation('chamfer', [{model: this, role: 'source', index: 0}], {
|
|
2741
|
+
selections: [
|
|
2742
|
+
{
|
|
2743
|
+
kind: 'edge',
|
|
2744
|
+
input: this,
|
|
2745
|
+
ids: selectedEdgeIds,
|
|
2746
|
+
transform: identityRigidTransform,
|
|
2747
|
+
},
|
|
2748
|
+
],
|
|
2749
|
+
}),
|
|
2750
|
+
) as unknown as SolidModel<Elements>;
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
shell(
|
|
2754
|
+
this: ModelObject<Elements, 'solid'>,
|
|
2755
|
+
thickness: number,
|
|
2756
|
+
removedSurfaceIds?: readonly SurfaceId[],
|
|
2757
|
+
): SolidModel<Elements>;
|
|
2758
|
+
shell(
|
|
2759
|
+
this: ModelObject<Elements, 'solid'>,
|
|
2760
|
+
thickness = 1,
|
|
2761
|
+
removedSurfaceIds: readonly SurfaceId[] = [],
|
|
2762
|
+
): SolidModel<Elements> {
|
|
2763
|
+
if (!Number.isFinite(thickness) || thickness === 0) {
|
|
2764
|
+
throw new Error('thickness must be a nonzero finite number.');
|
|
2965
2765
|
}
|
|
2966
|
-
const
|
|
2967
|
-
const
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2766
|
+
const source = this.requireSolidGeometry();
|
|
2767
|
+
const selectedIds = resolveTopologySelection(
|
|
2768
|
+
'surface',
|
|
2769
|
+
source.value.topology.surfaces,
|
|
2770
|
+
removedSurfaceIds,
|
|
2771
|
+
);
|
|
2772
|
+
const geometry = evaluateSolidGeometry(
|
|
2773
|
+
'shell',
|
|
2774
|
+
[thickness, selectedIds],
|
|
2775
|
+
[source],
|
|
2776
|
+
() =>
|
|
2777
|
+
shellWithTopology(
|
|
2778
|
+
source.value.shape,
|
|
2779
|
+
source.value.topology,
|
|
2780
|
+
thickness,
|
|
2781
|
+
selectedIds,
|
|
2782
|
+
),
|
|
2783
|
+
);
|
|
2784
|
+
return this.copyWithGeometry(
|
|
2785
|
+
geometry,
|
|
2786
|
+
{},
|
|
2787
|
+
storedOperation('shell', [{model: this, role: 'source', index: 0}], {
|
|
2788
|
+
selections: [
|
|
2789
|
+
{
|
|
2790
|
+
kind: 'surface',
|
|
2791
|
+
input: this,
|
|
2792
|
+
ids: selectedIds,
|
|
2793
|
+
transform: identityRigidTransform,
|
|
2794
|
+
},
|
|
2795
|
+
],
|
|
2796
|
+
}),
|
|
2797
|
+
) as unknown as SolidModel<Elements>;
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2800
|
+
/** @internal */
|
|
2801
|
+
attachOperationTrace(
|
|
2802
|
+
siteId: string,
|
|
2803
|
+
execution: number,
|
|
2804
|
+
order: number,
|
|
2805
|
+
sourceRef: SourceRef,
|
|
2806
|
+
outputIndex = 0,
|
|
2807
|
+
): void {
|
|
2808
|
+
if (operationTraces.has(this.operation)) {
|
|
2809
|
+
return;
|
|
2810
|
+
}
|
|
2811
|
+
operationTraces.set(this.operation, {
|
|
2812
|
+
siteId,
|
|
2813
|
+
execution,
|
|
2814
|
+
order,
|
|
2815
|
+
sourceRef,
|
|
2816
|
+
outputIndex,
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2820
|
+
/** @internal */
|
|
2821
|
+
relatedObjects(): readonly RelationObject[] {
|
|
2822
|
+
return [
|
|
2823
|
+
...this.children,
|
|
2824
|
+
...this.operation.inputs.map(input => input.model),
|
|
2825
|
+
...this.constraints.flatMap(constraintReferences),
|
|
2826
|
+
];
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
/** @internal */
|
|
2830
|
+
toSnapshot(
|
|
2831
|
+
meshCache: Map<AnyShape, RenderMesh> = new Map(),
|
|
2832
|
+
): ModelSnapshotObject {
|
|
2833
|
+
const solveContext = ModelObject.createSolveContext([this]);
|
|
2834
|
+
return this.snapshotNode(meshCache, solveContext);
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
private snapshotNode(
|
|
2838
|
+
meshCache: Map<AnyShape, RenderMesh>,
|
|
2839
|
+
solveContext: SolveContext,
|
|
2840
|
+
inComposition = false,
|
|
2841
|
+
overrideMaterial?: ModelMaterialSnapshot,
|
|
2842
|
+
): ModelSnapshotObject {
|
|
2843
|
+
const material = overrideMaterial ?? this.materialSnapshot;
|
|
2844
|
+
const pose = this.solvePose(solveContext);
|
|
2845
|
+
const constraints = this.constraints.map(constraint =>
|
|
2846
|
+
this.constraintSnapshot(constraint, solveContext),
|
|
2847
|
+
);
|
|
2848
|
+
const parameters = uniqueParameters([
|
|
2849
|
+
...this.parameters,
|
|
2850
|
+
...this.constraints.flatMap(
|
|
2851
|
+
constraint => valueTrace(constraint).parameters,
|
|
2852
|
+
),
|
|
2853
|
+
]);
|
|
2854
|
+
const common = {
|
|
2855
|
+
nodeId: this.nodeId,
|
|
2856
|
+
kind: this.kind,
|
|
2857
|
+
name: this.name,
|
|
2858
|
+
material,
|
|
2859
|
+
compositionTransform: toTransform(pose),
|
|
2860
|
+
transform: toTransform(inComposition ? pose : identityRigidTransform),
|
|
2861
|
+
constraints,
|
|
2862
|
+
origin,
|
|
2863
|
+
elements: snapshotElements({
|
|
2864
|
+
...this.elements,
|
|
2865
|
+
...(this.geometry || this.children.length
|
|
2866
|
+
? Object.fromEntries(
|
|
2867
|
+
Object.keys(boundDirections).map(direction => [
|
|
2868
|
+
direction,
|
|
2869
|
+
anchorReference(this[direction as keyof DirectionalBounds]),
|
|
2870
|
+
]),
|
|
2871
|
+
)
|
|
2872
|
+
: {}),
|
|
2873
|
+
}),
|
|
2874
|
+
sourceRefs: [...this.sourceRefs],
|
|
2875
|
+
parameters,
|
|
2876
|
+
operation: this.operationSnapshot(meshCache),
|
|
2877
|
+
} as const;
|
|
2878
|
+
|
|
2879
|
+
if (this.kind === 'group') {
|
|
2880
|
+
const childContext = this.assembly!;
|
|
2881
|
+
return {
|
|
2882
|
+
...common,
|
|
2883
|
+
children: this.children.map(child =>
|
|
2884
|
+
child.snapshotNode(meshCache, childContext, true, material),
|
|
2885
|
+
),
|
|
2886
|
+
};
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
const geometry = this.requireGeometry();
|
|
2890
|
+
return {
|
|
2891
|
+
...common,
|
|
2892
|
+
children: [],
|
|
2893
|
+
mesh: renderMesh(
|
|
2894
|
+
geometry,
|
|
2895
|
+
geometry.value.shape,
|
|
2896
|
+
meshCache,
|
|
2897
|
+
this.meshTolerance,
|
|
2898
|
+
geometry.value.topology,
|
|
2899
|
+
),
|
|
2900
|
+
};
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
/** @internal */
|
|
2904
|
+
disposeShape(disposed: Set<AnyShape>): void {
|
|
2905
|
+
const shapes = [
|
|
2906
|
+
...(this.geometry ? [this.geometry.value.shape] : []),
|
|
2907
|
+
...(this.geometry?.value.referenceBasis
|
|
2908
|
+
? [this.geometry.value.referenceBasis.shape]
|
|
2909
|
+
: []),
|
|
2910
|
+
...this.operation.regions.map(region => region.artifact.value),
|
|
2911
|
+
];
|
|
2912
|
+
for (const shape of shapes) {
|
|
2913
|
+
if (!disposed.has(shape)) {
|
|
2914
|
+
disposed.add(shape);
|
|
2915
|
+
shape.delete();
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
this.children.forEach(child => child.disposeShape(disposed));
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2921
|
+
/** @internal */
|
|
2922
|
+
[loftModels](
|
|
2923
|
+
this: ModelObject<Elements, 'face'>,
|
|
2924
|
+
others: readonly ModelObject<{}, 'face'>[],
|
|
2925
|
+
spine: ModelObject<{}, 'edge'> | undefined,
|
|
2926
|
+
ruled: boolean,
|
|
2927
|
+
): SolidModel {
|
|
2928
|
+
const sections: readonly ModelObject<{}, 'face'>[] = [this, ...others];
|
|
2929
|
+
const solveContext = ModelObject.createSolveContext([
|
|
2930
|
+
...sections,
|
|
2931
|
+
...(spine ? [spine] : []),
|
|
2932
|
+
]);
|
|
2933
|
+
const resultPose = this.solvePose(solveContext);
|
|
2934
|
+
const sectionInputs = sections.map(section => ({
|
|
2935
|
+
model: section,
|
|
2936
|
+
geometry: section.requireGeometry(),
|
|
2937
|
+
transform: relativeTransform(section.solvePose(solveContext), resultPose),
|
|
2938
|
+
}));
|
|
2939
|
+
const spineInput = spine
|
|
2940
|
+
? {
|
|
2941
|
+
model: spine,
|
|
2942
|
+
geometry: spine.requireGeometry(),
|
|
2943
|
+
transform: relativeTransform(
|
|
2944
|
+
spine.solvePose(solveContext),
|
|
2945
|
+
resultPose,
|
|
2946
|
+
),
|
|
2947
|
+
}
|
|
2948
|
+
: undefined;
|
|
2949
|
+
const geometry = evaluateSolidGeometry(
|
|
2950
|
+
spine ? 'spine-loft' : 'loft',
|
|
2951
|
+
[
|
|
2952
|
+
ruled,
|
|
2953
|
+
sectionInputs.map(input => [
|
|
2954
|
+
input.transform.position,
|
|
2955
|
+
input.transform.quaternion,
|
|
2956
|
+
]),
|
|
2957
|
+
spineInput
|
|
2958
|
+
? [spineInput.transform.position, spineInput.transform.quaternion]
|
|
2959
|
+
: null,
|
|
2960
|
+
],
|
|
2961
|
+
[
|
|
2962
|
+
...sectionInputs.map(input => input.geometry),
|
|
2963
|
+
...(spineInput ? [spineInput.geometry] : []),
|
|
2964
|
+
],
|
|
2965
|
+
() => buildLoftGeometry(sectionInputs, spineInput, ruled),
|
|
2971
2966
|
);
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2967
|
+
const inputs = [...sections, ...(spine ? [spine] : [])];
|
|
2968
|
+
return ModelObject.create<CanonicalElements, 'solid'>({
|
|
2969
|
+
kind: 'solid',
|
|
2970
|
+
name: 'Loft',
|
|
2971
|
+
geometry,
|
|
2972
|
+
material: this.materialSnapshot,
|
|
2973
|
+
constraints: this.constraints,
|
|
2974
|
+
sourceRefs: inputs.flatMap(input => input.sourceRefs),
|
|
2975
|
+
parameters: uniqueParameters(
|
|
2976
|
+
inputs.flatMap(input => input.allParameters()),
|
|
2977
|
+
),
|
|
2978
|
+
meshTolerance: Math.min(...inputs.map(input => input.meshTolerance)),
|
|
2979
|
+
operation: storedOperation('loft', [
|
|
2980
|
+
{model: this, role: 'receiver', index: 0},
|
|
2981
|
+
...others.map((model, index) => ({
|
|
2982
|
+
model,
|
|
2983
|
+
role: 'section' as const,
|
|
2984
|
+
index: index + 1,
|
|
2985
|
+
})),
|
|
2986
|
+
...(spine
|
|
2987
|
+
? [{model: spine, role: 'spine' as const, index: sections.length}]
|
|
2988
|
+
: []),
|
|
2989
|
+
]),
|
|
2990
|
+
}) as unknown as SolidModel;
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
/** @internal */
|
|
2994
|
+
[combineModels](
|
|
2995
|
+
this: ModelObject<Elements, 'solid'>,
|
|
2996
|
+
operation: BooleanOperation,
|
|
2997
|
+
others: readonly ModelObject<{}, 'solid'>[],
|
|
2998
|
+
): SolidModel {
|
|
2999
|
+
const evaluation = this.evaluateBoolean(operation, others);
|
|
3000
|
+
let transferred = false;
|
|
3001
|
+
try {
|
|
3002
|
+
const combined = ModelObject.create<CanonicalElements, 'solid'>({
|
|
3003
|
+
kind: 'solid',
|
|
3004
|
+
geometryAnchor: this.geometryAnchor,
|
|
3005
|
+
geometry: evaluation.geometry,
|
|
3006
|
+
name: this.name,
|
|
3007
|
+
material: this.materialSnapshot,
|
|
3008
|
+
constraints: this.constraints,
|
|
3009
|
+
sourceRefs: [this, ...others].flatMap(model => model.sourceRefs),
|
|
3010
|
+
parameters: uniqueParameters(
|
|
3011
|
+
[this, ...others].flatMap(model => model.allParameters()),
|
|
3012
|
+
),
|
|
3013
|
+
meshTolerance: Math.min(
|
|
3014
|
+
this.meshTolerance,
|
|
3015
|
+
...others.map(model => model.meshTolerance),
|
|
3016
|
+
),
|
|
3017
|
+
operation: storedOperation(
|
|
3018
|
+
operation === 'fuse' ? 'union' : operation,
|
|
3019
|
+
[
|
|
3020
|
+
{model: this, role: 'receiver', index: 0},
|
|
3021
|
+
...others.map((model, index) => ({
|
|
3022
|
+
model,
|
|
3023
|
+
role:
|
|
3024
|
+
operation === 'cut' ? ('tool' as const) : ('operand' as const),
|
|
3025
|
+
index: index + 1,
|
|
3026
|
+
})),
|
|
3027
|
+
],
|
|
3028
|
+
{regions: evaluation.regions},
|
|
3029
|
+
),
|
|
3030
|
+
});
|
|
3031
|
+
transferred = true;
|
|
3032
|
+
return combined as unknown as SolidModel;
|
|
3033
|
+
} finally {
|
|
3034
|
+
if (!transferred) {
|
|
3035
|
+
disposeModelGeometryValue(evaluation.geometry.value);
|
|
3036
|
+
evaluation.regions.forEach(region => region.artifact.value.delete());
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
private evaluateBoolean(
|
|
3042
|
+
this: ModelObject<Elements, 'solid'>,
|
|
3043
|
+
operation: BooleanOperation,
|
|
3044
|
+
others: readonly ModelObject<{}, 'solid'>[],
|
|
3045
|
+
): BooleanEvaluation {
|
|
3046
|
+
const solveContext = ModelObject.createSolveContext([this, ...others]);
|
|
3047
|
+
const targetPose = this.solvePose(solveContext);
|
|
3048
|
+
let geometry = this.requireSolidGeometry();
|
|
3049
|
+
let temporaryGeometry: SolidGeometry | undefined;
|
|
3050
|
+
const regions: StoredOperationRegion[] = [];
|
|
3051
|
+
let evaluated = false;
|
|
3052
|
+
try {
|
|
3053
|
+
for (const [otherIndex, other] of others.entries()) {
|
|
3054
|
+
const otherGeometry = other.requireSolidGeometry();
|
|
3055
|
+
const transform = relativeTransform(
|
|
3056
|
+
other.solvePose(solveContext),
|
|
3057
|
+
targetPose,
|
|
3058
|
+
);
|
|
3059
|
+
const operand = evaluateKernelShape(
|
|
3060
|
+
'transform',
|
|
3061
|
+
[transform.position, transform.quaternion],
|
|
3062
|
+
[otherGeometry],
|
|
3063
|
+
() => shapeWithTransform(otherGeometry.value.shape, transform),
|
|
3064
|
+
);
|
|
3065
|
+
try {
|
|
3066
|
+
if (operation === 'cut' || operation === 'fuse') {
|
|
3067
|
+
regions.push({
|
|
3068
|
+
kind: 'intersection',
|
|
3069
|
+
input: other,
|
|
3070
|
+
frame: this,
|
|
3071
|
+
artifact: evaluateKernelShape(
|
|
3072
|
+
'boolean-intersection-region',
|
|
3073
|
+
[],
|
|
3074
|
+
[geometry, operand],
|
|
3075
|
+
() => geometry.value.shape.intersect(operand.value),
|
|
2980
3076
|
),
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
3077
|
+
});
|
|
3078
|
+
}
|
|
3079
|
+
if (operation === 'fuse') {
|
|
3080
|
+
regions.push({
|
|
3081
|
+
kind: 'section',
|
|
3082
|
+
input: other,
|
|
3083
|
+
frame: this,
|
|
3084
|
+
artifact: evaluateKernelShape(
|
|
3085
|
+
'boolean-section-region',
|
|
3086
|
+
[],
|
|
3087
|
+
[geometry, operand],
|
|
3088
|
+
() => unionSectionShape(geometry.value.shape, operand.value),
|
|
3089
|
+
),
|
|
3090
|
+
});
|
|
3091
|
+
}
|
|
3092
|
+
const nextGeometry = evaluateSolidGeometry(
|
|
3093
|
+
`boolean-${operation}`,
|
|
3094
|
+
[otherIndex],
|
|
3095
|
+
[geometry, operand],
|
|
3096
|
+
() => {
|
|
3097
|
+
const result = booleanWithTopology(
|
|
3098
|
+
{
|
|
3099
|
+
shape: geometry.value.shape,
|
|
3100
|
+
topology: geometry.value.topology,
|
|
3101
|
+
index: otherIndex === 0 ? 1 : undefined,
|
|
3102
|
+
},
|
|
3103
|
+
{
|
|
3104
|
+
shape: operand.value,
|
|
3105
|
+
topology: otherGeometry.value.topology,
|
|
3106
|
+
index: otherIndex + 2,
|
|
3107
|
+
},
|
|
3108
|
+
operation,
|
|
3109
|
+
);
|
|
3110
|
+
return {shape: result.shape, topology: result.topology};
|
|
3111
|
+
},
|
|
2984
3112
|
);
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
3113
|
+
temporaryGeometry?.value.shape.delete();
|
|
3114
|
+
temporaryGeometry = nextGeometry;
|
|
3115
|
+
geometry = nextGeometry;
|
|
3116
|
+
} finally {
|
|
3117
|
+
operand.value.delete();
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
evaluated = true;
|
|
3121
|
+
return {geometry, regions};
|
|
3122
|
+
} finally {
|
|
3123
|
+
if (!evaluated) {
|
|
3124
|
+
temporaryGeometry?.value.shape.delete();
|
|
3125
|
+
regions.forEach(region => region.artifact.value.delete());
|
|
3126
|
+
}
|
|
2998
3127
|
}
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3128
|
+
}
|
|
3129
|
+
|
|
3130
|
+
private allParameters(): ParameterUsage[] {
|
|
3131
|
+
return uniqueParameters([
|
|
3132
|
+
...this.parameters,
|
|
3133
|
+
...this.constraints.flatMap(
|
|
3134
|
+
constraint => valueTrace(constraint).parameters,
|
|
3135
|
+
),
|
|
3136
|
+
]);
|
|
3137
|
+
}
|
|
3138
|
+
|
|
3139
|
+
private memberPoses(): Map<RelationObject, RigidTransform | null> {
|
|
3140
|
+
const members = new Map<RelationObject, RigidTransform | null>([
|
|
3141
|
+
[this, identityRigidTransform],
|
|
3142
|
+
]);
|
|
3143
|
+
if (this.children.length) {
|
|
3144
|
+
const context = this.assembly!;
|
|
3145
|
+
for (const child of this.children) {
|
|
3146
|
+
const pose = child.solvePose(context);
|
|
3147
|
+
for (const [member, localPose] of child.memberPoses()) {
|
|
3148
|
+
members.set(
|
|
3149
|
+
member,
|
|
3150
|
+
members.has(member) || localPose === null
|
|
3151
|
+
? null
|
|
3152
|
+
: composeTransforms(pose, localPose),
|
|
3153
|
+
);
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
return members;
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3160
|
+
/** Bounds of the selected finite geometry after a rigid transform. */
|
|
3161
|
+
private [referenceBoundsParts](
|
|
3162
|
+
reference: StoredAnchor,
|
|
3163
|
+
transform: RigidTransform,
|
|
3164
|
+
): ({bounds: LocalBounds} | SnapshotQueryInput)[] {
|
|
3165
|
+
if (reference.bound)
|
|
3166
|
+
return [{bounds: super[referenceBounds](reference, transform)}];
|
|
3167
|
+
if (reference.whole) {
|
|
3168
|
+
if (this.geometry) {
|
|
3169
|
+
const bounds = axisAlignedBounds(
|
|
3170
|
+
this.geometry.value.localBounds,
|
|
3171
|
+
transform,
|
|
3172
|
+
);
|
|
3173
|
+
return [bounds ? {bounds} : boundsQuery(this.geometry, transform)];
|
|
3174
|
+
}
|
|
3175
|
+
const context = this.assembly!;
|
|
3176
|
+
return this.children.flatMap(child =>
|
|
3177
|
+
child[referenceBoundsParts](
|
|
3178
|
+
child.relationAnchorReference(),
|
|
3179
|
+
composeTransforms(transform, child.solvePose(context)),
|
|
3017
3180
|
),
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3181
|
+
);
|
|
3182
|
+
}
|
|
3183
|
+
if (reference.topology) {
|
|
3184
|
+
const topology = reference.topology;
|
|
3185
|
+
return [
|
|
3186
|
+
boundsQuery(
|
|
3187
|
+
topology.source.requireGeometry(),
|
|
3188
|
+
composeTransforms(transform, topology.transform),
|
|
3189
|
+
topology.selection,
|
|
3190
|
+
topology.scale,
|
|
3191
|
+
),
|
|
3192
|
+
];
|
|
3193
|
+
}
|
|
3194
|
+
return [{bounds: super[referenceBounds](reference, transform)}];
|
|
3195
|
+
}
|
|
3196
|
+
|
|
3197
|
+
/** Bounds of the selected finite geometry after a rigid transform. */
|
|
3198
|
+
override [referenceBounds](
|
|
3199
|
+
reference: StoredAnchor,
|
|
3200
|
+
transform: RigidTransform,
|
|
3201
|
+
): LocalBounds {
|
|
3202
|
+
const bounds = this[referenceBoundsParts](reference, transform).map(part =>
|
|
3203
|
+
'bounds' in part
|
|
3204
|
+
? part.bounds
|
|
3205
|
+
: (evaluateSnapshotQuery(part) as LocalBounds),
|
|
3021
3206
|
);
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
models[action.body].solvePose(context),
|
|
3033
|
-
action.axis,
|
|
3034
|
-
),
|
|
3035
|
-
action.angle,
|
|
3036
|
-
),
|
|
3037
|
-
offsetFrame,
|
|
3207
|
+
return bounds.length === 1 ? bounds[0] : combineBounds(bounds);
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
/** Pure query collection: it never substitutes geometry or runs author code. */
|
|
3211
|
+
[modelSnapshotQueries](): SnapshotQueryInput[] {
|
|
3212
|
+
const result: SnapshotQueryInput[] = [];
|
|
3213
|
+
if (this.geometry || this.children.length) {
|
|
3214
|
+
for (const direction of Object.values(boundDirections)) {
|
|
3215
|
+
const transform = invertTransform(
|
|
3216
|
+
rotation(frameFromYAxis(origin, direction).quaternion),
|
|
3038
3217
|
);
|
|
3218
|
+
for (const part of this[referenceBoundsParts](
|
|
3219
|
+
this.relationAnchorReference(),
|
|
3220
|
+
transform,
|
|
3221
|
+
))
|
|
3222
|
+
if (!('bounds' in part)) result.push(part);
|
|
3223
|
+
}
|
|
3039
3224
|
}
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
transform: toTransform(
|
|
3048
|
-
composeTransforms(sourceFrame, {
|
|
3049
|
-
position: [
|
|
3050
|
-
0,
|
|
3051
|
-
(constraint.target.bound!.facing * sourceSize[1]) / 2,
|
|
3052
|
-
0,
|
|
3053
|
-
],
|
|
3054
|
-
quaternion: [0, 0, 0, 1],
|
|
3055
|
-
}),
|
|
3225
|
+
if (this.geometry)
|
|
3226
|
+
result.push(
|
|
3227
|
+
meshQuery(
|
|
3228
|
+
this.geometry,
|
|
3229
|
+
this.geometry.value.shape,
|
|
3230
|
+
this.meshTolerance,
|
|
3231
|
+
this.geometry.value.topology,
|
|
3056
3232
|
),
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3233
|
+
);
|
|
3234
|
+
for (const region of this.operation.regions)
|
|
3235
|
+
result.push(
|
|
3236
|
+
meshQuery(region.artifact, region.artifact.value, this.meshTolerance),
|
|
3237
|
+
);
|
|
3238
|
+
return result;
|
|
3239
|
+
}
|
|
3240
|
+
|
|
3241
|
+
protected override alignmentGeometry(
|
|
3242
|
+
reference: StoredAnchor,
|
|
3243
|
+
): AlignmentGeometry {
|
|
3244
|
+
if (reference.kind === 'point')
|
|
3245
|
+
return {
|
|
3246
|
+
kind: 'point',
|
|
3247
|
+
point: reference.whole
|
|
3248
|
+
? (this.requireGeometry().value.shape as ReplicadVertex).asTuple()
|
|
3249
|
+
: reference.transform.position,
|
|
3250
|
+
};
|
|
3251
|
+
const read = (shape: AnyShape) =>
|
|
3252
|
+
reference.kind === 'line'
|
|
3253
|
+
? edgeGeometry(shape as ReplicadEdge, reference.direction ?? 1)
|
|
3254
|
+
: faceGeometry(
|
|
3255
|
+
shape as ReplicadFace,
|
|
3256
|
+
reference.facing ?? reference.bound?.facing ?? 1,
|
|
3257
|
+
);
|
|
3258
|
+
if (reference.topology) {
|
|
3259
|
+
const topology = reference.topology,
|
|
3260
|
+
geometry = topology.source.requireGeometry().value;
|
|
3261
|
+
return withTopologyShape(
|
|
3262
|
+
geometry.shape,
|
|
3263
|
+
geometry.topology,
|
|
3264
|
+
topology.selection,
|
|
3265
|
+
shape => {
|
|
3266
|
+
const scaled =
|
|
3267
|
+
topology.scale === 1
|
|
3268
|
+
? shape
|
|
3269
|
+
: shapeWithScale(shape, topology.scale);
|
|
3270
|
+
try {
|
|
3271
|
+
return transformGeometry(read(scaled), topology.transform);
|
|
3272
|
+
} finally {
|
|
3273
|
+
if (scaled !== shape) scaled.delete();
|
|
3274
|
+
}
|
|
3065
3275
|
},
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3276
|
+
);
|
|
3277
|
+
}
|
|
3278
|
+
if (reference.whole) return read(this.requireGeometry().value.shape);
|
|
3279
|
+
return super.alignmentGeometry(reference);
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
protected override relationElement(reference: StoredAnchor): ElementSnapshot {
|
|
3283
|
+
return super.relationElement(
|
|
3284
|
+
reference.whole
|
|
3285
|
+
? {
|
|
3286
|
+
...this.exposedElement(),
|
|
3287
|
+
name: reference.name,
|
|
3288
|
+
direction: reference.direction,
|
|
3289
|
+
facing: reference.facing,
|
|
3290
|
+
}
|
|
3291
|
+
: reference,
|
|
3292
|
+
);
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
protected override vertexPosition(id: VertexId): Vec3 {
|
|
3296
|
+
return anchorReference(this.vertex(id)).transform.position;
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
protected copyRelations(init: RelationObjectInit): ModelObject {
|
|
3300
|
+
return this.copy(init, this.operation);
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
/** Fix the assembly frame once, using only its direct member origins. */
|
|
3304
|
+
private static createAssembly(
|
|
3305
|
+
children: readonly ModelObject[],
|
|
3306
|
+
): SolveContext {
|
|
3307
|
+
const context = ModelObject.createSolveContext(children);
|
|
3308
|
+
const center = children.length
|
|
3309
|
+
? boundsCenter(
|
|
3310
|
+
pointBounds(children.map(child => child.solvePose(context).position)),
|
|
3311
|
+
)
|
|
3312
|
+
: origin;
|
|
3313
|
+
return transformSolveContext(context, translation(negateVector(center)));
|
|
3079
3314
|
}
|
|
3080
3315
|
|
|
3081
3316
|
private requireGeometry(): ModelGeometry {
|
|
@@ -3659,39 +3894,34 @@ export function regularPrism(
|
|
|
3659
3894
|
}
|
|
3660
3895
|
|
|
3661
3896
|
/** @internal */
|
|
3662
|
-
export function
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
kind: 'solid',
|
|
3691
|
-
name: 'Custom primitive',
|
|
3692
|
-
geometry,
|
|
3693
|
-
operation: storedOperation('primitive'),
|
|
3694
|
-
}) as unknown as SolidModel;
|
|
3897
|
+
export function primitiveConstructor<Args extends unknown[]>(
|
|
3898
|
+
build: (...args: Args) => Shape3D,
|
|
3899
|
+
): (...args: Args) => SolidModel {
|
|
3900
|
+
const geometry = cachedArtifact(
|
|
3901
|
+
(...args: Args) => {
|
|
3902
|
+
const shape = build(...args);
|
|
3903
|
+
let solid: Shape3D;
|
|
3904
|
+
try {
|
|
3905
|
+
solid = normalizeReplicadSolid(shape);
|
|
3906
|
+
} catch (error) {
|
|
3907
|
+
shape.delete();
|
|
3908
|
+
throw error;
|
|
3909
|
+
}
|
|
3910
|
+
return createModelGeometryValue(solid);
|
|
3911
|
+
},
|
|
3912
|
+
{
|
|
3913
|
+
identity: build,
|
|
3914
|
+
namespace: 'primitive',
|
|
3915
|
+
lifecycle: modelGeometryLifecycle,
|
|
3916
|
+
},
|
|
3917
|
+
);
|
|
3918
|
+
return (...args) =>
|
|
3919
|
+
ModelObject.create<CanonicalElements, 'solid'>({
|
|
3920
|
+
kind: 'solid',
|
|
3921
|
+
name: 'Custom primitive',
|
|
3922
|
+
geometry: geometry(...args) as SolidGeometry,
|
|
3923
|
+
operation: storedOperation('primitive'),
|
|
3924
|
+
}) as unknown as SolidModel;
|
|
3695
3925
|
}
|
|
3696
3926
|
|
|
3697
3927
|
function normalizeReplicadSolid(shape: Shape3D): Shape3D {
|
|
@@ -3780,15 +4010,55 @@ export function union(operands: readonly SolidModel<{}>[]): SolidModel {
|
|
|
3780
4010
|
}
|
|
3781
4011
|
|
|
3782
4012
|
/**
|
|
3783
|
-
* Extrudes
|
|
3784
|
-
* @code3d.param distance {kind: 'length', label: 'Extrusion distance'}
|
|
4013
|
+
* Extrudes each face independently, preserving array order and placement.
|
|
4014
|
+
* @code3d.param distance {kind: 'length', default: 10, label: 'Extrusion distance'}
|
|
4015
|
+
*/
|
|
4016
|
+
export function extrude(face: FaceModel<{}>, distance: number): SolidModel;
|
|
4017
|
+
/**
|
|
4018
|
+
* Extrudes each face independently.
|
|
4019
|
+
* @code3d.param distance {kind: 'length', default: 10, label: 'Extrusion distance'}
|
|
4020
|
+
*/
|
|
4021
|
+
export function extrude(
|
|
4022
|
+
faces: readonly FaceModel<{}>[],
|
|
4023
|
+
distance: number,
|
|
4024
|
+
): readonly SolidModel[];
|
|
4025
|
+
export function extrude(
|
|
4026
|
+
face: FaceModel<{}> | readonly FaceModel<{}>[],
|
|
4027
|
+
distance = 10,
|
|
4028
|
+
): SolidModel | readonly SolidModel[] {
|
|
4029
|
+
const faces = (Array.isArray(face) ? face : [face]).map(value =>
|
|
4030
|
+
requireModelKind(
|
|
4031
|
+
value,
|
|
4032
|
+
'face',
|
|
4033
|
+
'extrude requires a face model or an array of face models.',
|
|
4034
|
+
),
|
|
4035
|
+
);
|
|
4036
|
+
const solids = faces.map(value => value.extrude(distance));
|
|
4037
|
+
return Array.isArray(face) ? solids : solids[0];
|
|
4038
|
+
}
|
|
4039
|
+
|
|
4040
|
+
/**
|
|
4041
|
+
* Creates connected text faces on the XZ plane: +X right, -Z up, normal +Y.
|
|
4042
|
+
* All faces share the baseline origin. Size is the font em in model units.
|
|
4043
|
+
* @code3d.param size {kind: 'length', label: 'Text size'}
|
|
3785
4044
|
*/
|
|
3786
|
-
export function
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
4045
|
+
export function text(
|
|
4046
|
+
content: string,
|
|
4047
|
+
font: Font,
|
|
4048
|
+
size: number,
|
|
4049
|
+
options?: TextOptions,
|
|
4050
|
+
): readonly FaceModel[] {
|
|
4051
|
+
return textGlyphs(content, font, size, options).flatMap(({regions, x, y}) =>
|
|
4052
|
+
regions.value.map((region, index) => {
|
|
4053
|
+
const geometry = evaluateModelGeometry(
|
|
4054
|
+
'text',
|
|
4055
|
+
[x, y, index],
|
|
4056
|
+
[regions],
|
|
4057
|
+
() => ({shape: textRegionFace(region, x, y)}),
|
|
4058
|
+
);
|
|
4059
|
+
return faceModel('text', 'Text face', geometry);
|
|
4060
|
+
}),
|
|
4061
|
+
);
|
|
3792
4062
|
}
|
|
3793
4063
|
|
|
3794
4064
|
export function cut(
|
|
@@ -3844,7 +4114,7 @@ export function instrumentConstraint(
|
|
|
3844
4114
|
}
|
|
3845
4115
|
|
|
3846
4116
|
export function instrumentModelOperation(
|
|
3847
|
-
object:
|
|
4117
|
+
object: RelationObject,
|
|
3848
4118
|
instrumentation: ModelOperationInstrumentation,
|
|
3849
4119
|
): void {
|
|
3850
4120
|
object.attachSource(instrumentation.sourceRef);
|
|
@@ -3854,6 +4124,7 @@ export function instrumentModelOperation(
|
|
|
3854
4124
|
instrumentation.execution,
|
|
3855
4125
|
instrumentation.order,
|
|
3856
4126
|
instrumentation.sourceRef,
|
|
4127
|
+
instrumentation.outputIndex,
|
|
3857
4128
|
);
|
|
3858
4129
|
}
|
|
3859
4130
|
|
|
@@ -3864,7 +4135,7 @@ export function constraintTraceReference(
|
|
|
3864
4135
|
}
|
|
3865
4136
|
|
|
3866
4137
|
export function modelObjectRuntimeInfo(
|
|
3867
|
-
object:
|
|
4138
|
+
object: RelationObject,
|
|
3868
4139
|
): ModelObjectRuntimeInfo {
|
|
3869
4140
|
return {
|
|
3870
4141
|
nodeId: object.nodeId,
|
|
@@ -3874,8 +4145,8 @@ export function modelObjectRuntimeInfo(
|
|
|
3874
4145
|
}
|
|
3875
4146
|
|
|
3876
4147
|
export function relatedModelObjects(
|
|
3877
|
-
object:
|
|
3878
|
-
): readonly
|
|
4148
|
+
object: RelationObject,
|
|
4149
|
+
): readonly RelationObject[] {
|
|
3879
4150
|
return object.relatedObjects();
|
|
3880
4151
|
}
|
|
3881
4152
|
|
|
@@ -3947,13 +4218,6 @@ function meshQuery(
|
|
|
3947
4218
|
};
|
|
3948
4219
|
}
|
|
3949
4220
|
|
|
3950
|
-
const queryLifecycle: KernelValueLifecycle<SnapshotQueryResult> = {
|
|
3951
|
-
estimateBytes: estimateRetainedBytes,
|
|
3952
|
-
retain: value => value,
|
|
3953
|
-
instantiate: value => value,
|
|
3954
|
-
release: () => {},
|
|
3955
|
-
};
|
|
3956
|
-
|
|
3957
4221
|
function computeSnapshotQuery(
|
|
3958
4222
|
geometry: SnapshotQueryInput['geometry'],
|
|
3959
4223
|
query: SnapshotQuery,
|
|
@@ -3967,14 +4231,13 @@ function computeSnapshotQuery(
|
|
|
3967
4231
|
);
|
|
3968
4232
|
}
|
|
3969
4233
|
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
//
|
|
3976
|
-
|
|
3977
|
-
// A local query and a remote query must therefore consume the same BREP.
|
|
4234
|
+
const snapshotQuery = cachedArtifact(
|
|
4235
|
+
(input: SnapshotQueryInput): SnapshotQueryResult => {
|
|
4236
|
+
if (input.query.kind !== 'mesh')
|
|
4237
|
+
return computeSnapshotQuery(input.geometry, input.query);
|
|
4238
|
+
// Local and remote meshes consume the same binary input: BinTools can
|
|
4239
|
+
// renormalize plane axes by a few ULPs and change Delaunay diagonal ties.
|
|
4240
|
+
let value!: SnapshotQueryResult;
|
|
3978
4241
|
executeSnapshotQueryBatch(
|
|
3979
4242
|
input.inputId,
|
|
3980
4243
|
encodeKernelArtifact(input.inputId, input.geometry),
|
|
@@ -3984,9 +4247,13 @@ function evaluateSnapshotQuery(input: SnapshotQueryInput): SnapshotQueryResult {
|
|
|
3984
4247
|
value = result;
|
|
3985
4248
|
},
|
|
3986
4249
|
);
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
4250
|
+
return value;
|
|
4251
|
+
},
|
|
4252
|
+
{key: input => input.query.key},
|
|
4253
|
+
);
|
|
4254
|
+
|
|
4255
|
+
function evaluateSnapshotQuery(input: SnapshotQueryInput): SnapshotQueryResult {
|
|
4256
|
+
return snapshotQuery(input).value;
|
|
3990
4257
|
}
|
|
3991
4258
|
|
|
3992
4259
|
/** Host-owned native input and result admission. Only encode() bytes cross runtimes. */
|
|
@@ -4000,10 +4267,13 @@ export type SnapshotQueryBatch = Readonly<{
|
|
|
4000
4267
|
}>;
|
|
4001
4268
|
|
|
4002
4269
|
export function planModelSnapshotQueries(
|
|
4003
|
-
objects: readonly
|
|
4270
|
+
objects: readonly RelationObject[],
|
|
4004
4271
|
): SnapshotQueryBatch[] {
|
|
4005
4272
|
const visited = new Set<ModelObject>();
|
|
4006
|
-
const
|
|
4273
|
+
const inputs = new Map<
|
|
4274
|
+
string,
|
|
4275
|
+
{input: SnapshotQueryInput; sourceRef?: SourceRef}
|
|
4276
|
+
>();
|
|
4007
4277
|
const batches = new Map<
|
|
4008
4278
|
string,
|
|
4009
4279
|
{input: SnapshotQueryInput; queries: SnapshotQuery[]; sourceRef?: SourceRef}
|
|
@@ -4012,27 +4282,33 @@ export function planModelSnapshotQueries(
|
|
|
4012
4282
|
if (visited.has(object)) return;
|
|
4013
4283
|
visited.add(object);
|
|
4014
4284
|
for (const input of object[modelSnapshotQueries]()) {
|
|
4015
|
-
const {query
|
|
4016
|
-
const
|
|
4017
|
-
if (
|
|
4018
|
-
if (signature !== query.key.signature)
|
|
4285
|
+
const {query} = input;
|
|
4286
|
+
const existing = inputs.get(query.key.id);
|
|
4287
|
+
if (existing) {
|
|
4288
|
+
if (existing.input.query.key.signature !== query.key.signature)
|
|
4019
4289
|
throw new Error(
|
|
4020
4290
|
`Kernel operation cache identity collision: ${query.key.id}`,
|
|
4021
4291
|
);
|
|
4022
4292
|
continue;
|
|
4023
4293
|
}
|
|
4024
|
-
|
|
4025
|
-
if (findKernelOperation(query.key, queryLifecycle)) continue;
|
|
4026
|
-
let batch = batches.get(inputId);
|
|
4027
|
-
if (!batch) {
|
|
4028
|
-
batch = {input, queries: [], sourceRef: object.sourceRefs.at(-1)};
|
|
4029
|
-
batches.set(inputId, batch);
|
|
4030
|
-
}
|
|
4031
|
-
batch.queries.push(query);
|
|
4294
|
+
inputs.set(query.key.id, {input, sourceRef: object.sourceRefs.at(-1)});
|
|
4032
4295
|
}
|
|
4033
4296
|
object.children.forEach(collect);
|
|
4034
4297
|
}
|
|
4035
|
-
objects.forEach(collect);
|
|
4298
|
+
objects.filter(isModelObject).forEach(collect);
|
|
4299
|
+
const pending = [...inputs.values()];
|
|
4300
|
+
const hits = snapshotQuery.findMany(
|
|
4301
|
+
pending.map(({input}) => input.query.key),
|
|
4302
|
+
);
|
|
4303
|
+
pending.forEach(({input, sourceRef}, index) => {
|
|
4304
|
+
if (hits[index]) return;
|
|
4305
|
+
let batch = batches.get(input.inputId);
|
|
4306
|
+
if (!batch) {
|
|
4307
|
+
batch = {input, queries: [], sourceRef};
|
|
4308
|
+
batches.set(input.inputId, batch);
|
|
4309
|
+
}
|
|
4310
|
+
batch.queries.push(input.query);
|
|
4311
|
+
});
|
|
4036
4312
|
return [...batches].map(([id, {input, queries, sourceRef}]) => ({
|
|
4037
4313
|
id,
|
|
4038
4314
|
queries,
|
|
@@ -4043,8 +4319,7 @@ export function planModelSnapshotQueries(
|
|
|
4043
4319
|
(input.geometry.topology?.surfaces.ids.length ?? 0)) *
|
|
4044
4320
|
queries.length,
|
|
4045
4321
|
encode: () => encodeKernelArtifact(id, input.geometry),
|
|
4046
|
-
accept: (query, value) =>
|
|
4047
|
-
acceptKernelOperation(query.key, queryLifecycle, value),
|
|
4322
|
+
accept: (query, value) => snapshotQuery.accept(query.key, value),
|
|
4048
4323
|
}));
|
|
4049
4324
|
}
|
|
4050
4325
|
|
|
@@ -4074,16 +4349,16 @@ export function executeSnapshotQueryBatch(
|
|
|
4074
4349
|
}
|
|
4075
4350
|
|
|
4076
4351
|
export function createModelSnapshotter(): (
|
|
4077
|
-
object:
|
|
4352
|
+
object: RelationObject,
|
|
4078
4353
|
) => ModelSnapshotObject {
|
|
4079
4354
|
const meshCache = new Map<AnyShape, RenderMesh>();
|
|
4080
4355
|
return object => object.toSnapshot(meshCache);
|
|
4081
4356
|
}
|
|
4082
4357
|
|
|
4083
|
-
export function disposeModelObjects(objects: Iterable<
|
|
4358
|
+
export function disposeModelObjects(objects: Iterable<RelationObject>): void {
|
|
4084
4359
|
const disposed = new Set<AnyShape>();
|
|
4085
4360
|
for (const object of objects) {
|
|
4086
|
-
object.disposeShape(disposed);
|
|
4361
|
+
if (isModelObject(object)) object.disposeShape(disposed);
|
|
4087
4362
|
}
|
|
4088
4363
|
}
|
|
4089
4364
|
|
|
@@ -4099,7 +4374,7 @@ export type ModelGeometrySnapshot = Readonly<{
|
|
|
4099
4374
|
}>;
|
|
4100
4375
|
|
|
4101
4376
|
export function retainModelGeometry(
|
|
4102
|
-
objects: Iterable<
|
|
4377
|
+
objects: Iterable<RelationObject>,
|
|
4103
4378
|
): ModelGeometrySnapshot {
|
|
4104
4379
|
const retained = new Map<AnyShape, AnyShape>();
|
|
4105
4380
|
const shapes = new Map<string, AnyShape>();
|
|
@@ -4112,6 +4387,7 @@ export function retainModelGeometry(
|
|
|
4112
4387
|
};
|
|
4113
4388
|
try {
|
|
4114
4389
|
for (const object of objects) {
|
|
4390
|
+
if (!isModelObject(object)) continue;
|
|
4115
4391
|
const geometry = object[modelGeometry]()?.value;
|
|
4116
4392
|
const original = geometry?.shape;
|
|
4117
4393
|
if (!original) continue;
|
|
@@ -4141,6 +4417,10 @@ export function retainModelGeometry(
|
|
|
4141
4417
|
}
|
|
4142
4418
|
|
|
4143
4419
|
export const authoringApi = Object.freeze({
|
|
4420
|
+
cached,
|
|
4421
|
+
font,
|
|
4422
|
+
googleFont,
|
|
4423
|
+
text,
|
|
4144
4424
|
sketch,
|
|
4145
4425
|
circle,
|
|
4146
4426
|
ellipse,
|
|
@@ -4188,7 +4468,7 @@ function storedOperation(
|
|
|
4188
4468
|
function storedOperationId(operation: StoredOperation): string {
|
|
4189
4469
|
const trace = operationTraces.get(operation);
|
|
4190
4470
|
return trace
|
|
4191
|
-
? `${trace.siteId}:execution:${trace.execution}`
|
|
4471
|
+
? `${trace.siteId}:execution:${trace.execution}:output:${trace.outputIndex ?? 0}`
|
|
4192
4472
|
: operation.runtimeId;
|
|
4193
4473
|
}
|
|
4194
4474
|
|
|
@@ -4233,45 +4513,49 @@ function disposeModelGeometryValue(geometry: ModelGeometryValue): void {
|
|
|
4233
4513
|
geometry.referenceBasis?.shape.delete();
|
|
4234
4514
|
}
|
|
4235
4515
|
|
|
4516
|
+
const kernelShape = cachedArtifact(
|
|
4517
|
+
(
|
|
4518
|
+
_operation: string,
|
|
4519
|
+
_arguments: readonly KernelKeyPart[],
|
|
4520
|
+
_inputs: readonly KernelArtifact<unknown>[],
|
|
4521
|
+
compute: () => AnyShape,
|
|
4522
|
+
) => compute(),
|
|
4523
|
+
{key: kernelOperationKey, lifecycle: shapeLifecycle},
|
|
4524
|
+
);
|
|
4525
|
+
|
|
4236
4526
|
function evaluateKernelShape<Shape extends AnyShape>(
|
|
4237
4527
|
operation: string,
|
|
4238
4528
|
arguments_: readonly KernelKeyPart[],
|
|
4239
4529
|
inputs: readonly KernelArtifact<unknown>[],
|
|
4240
4530
|
compute: () => Shape,
|
|
4241
4531
|
): KernelArtifact<Shape> {
|
|
4242
|
-
return
|
|
4532
|
+
return kernelShape(
|
|
4243
4533
|
operation,
|
|
4244
4534
|
arguments_,
|
|
4245
4535
|
inputs,
|
|
4246
|
-
shapeLifecycle as KernelValueLifecycle<Shape>,
|
|
4247
4536
|
compute,
|
|
4248
|
-
)
|
|
4537
|
+
) as KernelArtifact<Shape>;
|
|
4249
4538
|
}
|
|
4250
4539
|
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
referenceBasis: result.referenceBasis,
|
|
4271
|
-
};
|
|
4272
|
-
},
|
|
4273
|
-
);
|
|
4274
|
-
}
|
|
4540
|
+
const evaluateModelGeometry = cachedArtifact(
|
|
4541
|
+
(
|
|
4542
|
+
_operation: string,
|
|
4543
|
+
_arguments: readonly KernelKeyPart[],
|
|
4544
|
+
_inputs: readonly KernelArtifact<unknown>[],
|
|
4545
|
+
compute: () => Readonly<{
|
|
4546
|
+
shape: AnyShape;
|
|
4547
|
+
topology?: ShapeTopology;
|
|
4548
|
+
referenceBasis?: ModelGeometryValue['referenceBasis'];
|
|
4549
|
+
}>,
|
|
4550
|
+
): ModelGeometryValue => {
|
|
4551
|
+
const result = compute();
|
|
4552
|
+
return {
|
|
4553
|
+
...createModelGeometryValue(result.shape, result.topology),
|
|
4554
|
+
referenceBasis: result.referenceBasis,
|
|
4555
|
+
};
|
|
4556
|
+
},
|
|
4557
|
+
{key: kernelOperationKey, lifecycle: modelGeometryLifecycle},
|
|
4558
|
+
);
|
|
4275
4559
|
|
|
4276
4560
|
function createModelGeometryValue(
|
|
4277
4561
|
shape: AnyShape,
|
|
@@ -4378,6 +4662,8 @@ function meshUVs(
|
|
|
4378
4662
|
location,
|
|
4379
4663
|
0,
|
|
4380
4664
|
);
|
|
4665
|
+
// Native meshing can omit a face below its geometric tolerance.
|
|
4666
|
+
if (!triangulation) continue;
|
|
4381
4667
|
try {
|
|
4382
4668
|
if (triangulation.isNull()) continue;
|
|
4383
4669
|
if (!triangulation.HasUVNodes()) return undefined;
|
|
@@ -4425,7 +4711,11 @@ type PlanarSketch = Readonly<{
|
|
|
4425
4711
|
}>;
|
|
4426
4712
|
|
|
4427
4713
|
/** Internal bridge from the kernel-independent sketch definition to model geometry. */
|
|
4428
|
-
|
|
4714
|
+
function sketchFaceModel(
|
|
4715
|
+
region: SketchRegion,
|
|
4716
|
+
constraints: readonly StoredConstraint[],
|
|
4717
|
+
source: SketchFrame,
|
|
4718
|
+
): FaceModel {
|
|
4429
4719
|
const curves = [region.outer, ...region.holes].map(loop =>
|
|
4430
4720
|
loop.map(curve =>
|
|
4431
4721
|
curve.kind === 'line'
|
|
@@ -4445,10 +4735,13 @@ export function sketchFaceModel(region: SketchRegion): FaceModel {
|
|
|
4445
4735
|
return ModelObject.create<PlanarElements, 'face'>({
|
|
4446
4736
|
kind: 'face',
|
|
4447
4737
|
name: 'Sketch face',
|
|
4738
|
+
constraints,
|
|
4448
4739
|
geometry,
|
|
4449
4740
|
geometryAnchor: plane,
|
|
4450
4741
|
elements: {plane},
|
|
4451
|
-
operation: storedOperation('sketchFace'
|
|
4742
|
+
operation: storedOperation('sketchFace', [
|
|
4743
|
+
{model: source, role: 'source', index: 0},
|
|
4744
|
+
]),
|
|
4452
4745
|
}) as unknown as FaceModel;
|
|
4453
4746
|
}
|
|
4454
4747
|
|
|
@@ -4471,6 +4764,14 @@ function planarFaceModel(
|
|
|
4471
4764
|
sketch.delete();
|
|
4472
4765
|
}
|
|
4473
4766
|
});
|
|
4767
|
+
return faceModel(operation, name, geometry);
|
|
4768
|
+
}
|
|
4769
|
+
|
|
4770
|
+
function faceModel(
|
|
4771
|
+
operation: ModelOperationKind,
|
|
4772
|
+
name: string,
|
|
4773
|
+
geometry: ModelGeometry,
|
|
4774
|
+
): FaceModel {
|
|
4474
4775
|
const plane: StoredElement = {
|
|
4475
4776
|
kind: 'face',
|
|
4476
4777
|
transform: identityRigidTransform,
|
|
@@ -4607,14 +4908,47 @@ function solidElements(bounds: LocalBounds): StoredElements {
|
|
|
4607
4908
|
return {axis: {kind: 'line', transform: translation(boundsCenter(bounds))}};
|
|
4608
4909
|
}
|
|
4609
4910
|
|
|
4610
|
-
function constraintReferences(constraint: StoredConstraint):
|
|
4911
|
+
function constraintReferences(constraint: StoredConstraint): RelationObject[] {
|
|
4611
4912
|
return [
|
|
4612
4913
|
constraint.source.model,
|
|
4613
4914
|
constraint.target.model,
|
|
4614
4915
|
...constraint.rotations.map(action =>
|
|
4615
4916
|
action.pivot.kind === 'around' ? action.pivot.axis.model : undefined,
|
|
4616
4917
|
),
|
|
4617
|
-
].filter((model): model is
|
|
4918
|
+
].filter((model): model is RelationObject => !!model);
|
|
4919
|
+
}
|
|
4920
|
+
|
|
4921
|
+
/** Signed axis permutations preserve tight bounds; arbitrary rotations do not. */
|
|
4922
|
+
function axisAlignedBounds(
|
|
4923
|
+
bounds: LocalBounds,
|
|
4924
|
+
transform: RigidTransform,
|
|
4925
|
+
): LocalBounds | undefined {
|
|
4926
|
+
const axes = (
|
|
4927
|
+
[
|
|
4928
|
+
[1, 0, 0],
|
|
4929
|
+
[0, 1, 0],
|
|
4930
|
+
[0, 0, 1],
|
|
4931
|
+
] as const
|
|
4932
|
+
).map(axis => rotateVector(axis, transform.quaternion));
|
|
4933
|
+
// Account only for floating-point roundoff in quarter-turn quaternion products.
|
|
4934
|
+
if (
|
|
4935
|
+
axes.some(axis =>
|
|
4936
|
+
axis.some(
|
|
4937
|
+
value => Math.abs(value - Math.round(value)) > 8 * Number.EPSILON,
|
|
4938
|
+
),
|
|
4939
|
+
)
|
|
4940
|
+
)
|
|
4941
|
+
return;
|
|
4942
|
+
return [0, 1].map(side =>
|
|
4943
|
+
axes[0].map((_, output) => {
|
|
4944
|
+
const input = axes.findIndex(axis => Math.abs(axis[output]) > 0.5);
|
|
4945
|
+
const sign = Math.round(axes[input][output]);
|
|
4946
|
+
return (
|
|
4947
|
+
transform.position[output] +
|
|
4948
|
+
sign * bounds[sign > 0 ? side : 1 - side][input]
|
|
4949
|
+
);
|
|
4950
|
+
}),
|
|
4951
|
+
) as unknown as LocalBounds;
|
|
4618
4952
|
}
|
|
4619
4953
|
|
|
4620
4954
|
function computeTransformedBounds(
|
|
@@ -4699,7 +5033,7 @@ function directionalBound(
|
|
|
4699
5033
|
}
|
|
4700
5034
|
|
|
4701
5035
|
function modelAnchor<Kind extends ElementKind>(
|
|
4702
|
-
model:
|
|
5036
|
+
model: RelationObject,
|
|
4703
5037
|
name: string,
|
|
4704
5038
|
element: StoredElement & Readonly<{kind: Kind}>,
|
|
4705
5039
|
): Anchor<Kind> {
|
|
@@ -4875,7 +5209,7 @@ function topologyName(selection: TopologySelection): string {
|
|
|
4875
5209
|
}
|
|
4876
5210
|
|
|
4877
5211
|
function topologyReferences(
|
|
4878
|
-
model:
|
|
5212
|
+
model: RelationObject,
|
|
4879
5213
|
prefix: string,
|
|
4880
5214
|
context: Omit<StoredTopology, 'selection'>,
|
|
4881
5215
|
kind: TopologyKind,
|
|
@@ -4957,14 +5291,14 @@ function anchorReference(anchor: Anchor): AnchorReference {
|
|
|
4957
5291
|
|
|
4958
5292
|
function anchorSnapshot(reference: AnchorReference): ConstraintAnchorSnapshot;
|
|
4959
5293
|
function anchorSnapshot(
|
|
4960
|
-
model:
|
|
5294
|
+
model: RelationObject,
|
|
4961
5295
|
anchor: StoredAnchor,
|
|
4962
5296
|
): ConstraintAnchorSnapshot;
|
|
4963
5297
|
function anchorSnapshot(
|
|
4964
|
-
modelOrReference:
|
|
5298
|
+
modelOrReference: RelationObject | AnchorReference,
|
|
4965
5299
|
stored?: StoredAnchor,
|
|
4966
5300
|
): ConstraintAnchorSnapshot {
|
|
4967
|
-
const model = stored ? (modelOrReference as
|
|
5301
|
+
const model = stored ? (modelOrReference as RelationObject) : undefined;
|
|
4968
5302
|
const reference = stored ?? (modelOrReference as AnchorReference);
|
|
4969
5303
|
return {
|
|
4970
5304
|
nodeId: (model ?? (modelOrReference as AnchorReference).model).nodeId,
|
|
@@ -4973,7 +5307,7 @@ function anchorSnapshot(
|
|
|
4973
5307
|
};
|
|
4974
5308
|
}
|
|
4975
5309
|
|
|
4976
|
-
function uniqueModels(models: readonly
|
|
5310
|
+
function uniqueModels<T extends RelationObject>(models: readonly T[]): T[] {
|
|
4977
5311
|
return [...new Set(models)];
|
|
4978
5312
|
}
|
|
4979
5313
|
|