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