@code3d/core 0.0.1-alpha.4 → 0.0.1-alpha.7
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 +42 -5
- package/bld/chunks/{chunk-J77FMYFY.js → chunk-4WLVDYUJ.js} +992 -545
- package/bld/chunks/chunk-4WLVDYUJ.js.map +7 -0
- package/bld/chunks/{chunk-PAIFBNU4.js → chunk-C3AJ6WNB.js} +2 -2
- package/bld/chunks/{chunk-OQLJDJIF.js → chunk-TCMEEKWL.js} +2 -2
- package/bld/chunks/{chunk-ZMT6EFHL.js → chunk-VHSPHJ5F.js} +3 -3
- package/bld/library/bound-solver.d.ts +29 -5
- package/bld/library/bound-solver.d.ts.map +1 -1
- package/bld/library/cached.d.ts +5 -3
- package/bld/library/cached.d.ts.map +1 -1
- package/bld/library/index.d.ts +4 -4
- package/bld/library/index.d.ts.map +1 -1
- package/bld/library/index.js +17 -3
- package/bld/library/kernel-cache.d.ts +6 -3
- package/bld/library/kernel-cache.d.ts.map +1 -1
- package/bld/library/relation-solver.d.ts +12 -7
- package/bld/library/relation-solver.d.ts.map +1 -1
- package/bld/library/replicad.js +2 -2
- package/bld/library/runtime.d.ts +218 -82
- package/bld/library/runtime.d.ts.map +1 -1
- package/bld/library/sketch.d.ts +2 -2
- package/bld/library/sketch.d.ts.map +1 -1
- package/bld/library/topology.d.ts +2 -0
- package/bld/library/topology.d.ts.map +1 -1
- package/bld/node/index.js +19 -5
- package/bld/node/replicad.js +4 -4
- package/bld/tooling/index.d.ts +3 -3
- package/bld/tooling/index.d.ts.map +1 -1
- package/bld/tooling/index.js +18 -10
- package/package.json +1 -1
- package/src/library/bound-solver.ts +103 -73
- package/src/library/cached.ts +29 -8
- package/src/library/index.ts +15 -4
- package/src/library/kernel-cache.ts +37 -5
- package/src/library/relation-solver.ts +194 -110
- package/src/library/runtime.ts +1046 -442
- package/src/library/sketch.ts +3 -3
- package/src/library/topology.ts +13 -2
- package/src/tooling/index.ts +15 -7
- package/bld/chunks/chunk-J77FMYFY.js.map +0 -7
- /package/bld/chunks/{chunk-PAIFBNU4.js.map → chunk-C3AJ6WNB.js.map} +0 -0
- /package/bld/chunks/{chunk-OQLJDJIF.js.map → chunk-TCMEEKWL.js.map} +0 -0
- /package/bld/chunks/{chunk-ZMT6EFHL.js.map → chunk-VHSPHJ5F.js.map} +0 -0
package/src/library/runtime.ts
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
transformGeometry,
|
|
33
33
|
type AlignmentGeometry,
|
|
34
34
|
} from './alignment-geometry.js';
|
|
35
|
-
import {
|
|
35
|
+
import {cache, cachedArtifact} from './cached.js';
|
|
36
36
|
import {extrudeWithTopology} from './extrude.js';
|
|
37
37
|
import {font, googleFont, type Font} from './font.js';
|
|
38
38
|
import {
|
|
@@ -56,7 +56,9 @@ import {
|
|
|
56
56
|
axisRotation,
|
|
57
57
|
solveBodies,
|
|
58
58
|
type Body,
|
|
59
|
-
|
|
59
|
+
beforeTransformations,
|
|
60
|
+
afterTransformations,
|
|
61
|
+
type BodyAction,
|
|
60
62
|
} from './relation-solver.js';
|
|
61
63
|
import {estimateRetainedBytes} from './retained-memory.js';
|
|
62
64
|
import {shellWithTopology} from './shell.js';
|
|
@@ -76,6 +78,7 @@ import {
|
|
|
76
78
|
rotation,
|
|
77
79
|
rotationAround,
|
|
78
80
|
translation,
|
|
81
|
+
transformsAreEquivalent,
|
|
79
82
|
type Quaternion,
|
|
80
83
|
type RigidTransform,
|
|
81
84
|
type Vec3,
|
|
@@ -89,6 +92,7 @@ import {
|
|
|
89
92
|
type TopologyInspectionOptions,
|
|
90
93
|
} from './topology-inspection.js';
|
|
91
94
|
import {
|
|
95
|
+
assertTopologyId,
|
|
92
96
|
booleanWithTopology,
|
|
93
97
|
chamferEdges,
|
|
94
98
|
filletEdges,
|
|
@@ -179,11 +183,6 @@ export type ConstraintSnapshot = Readonly<{
|
|
|
179
183
|
target: ConstraintAnchorSnapshot;
|
|
180
184
|
sourceElement: ElementSnapshot;
|
|
181
185
|
targetElement: ElementSnapshot;
|
|
182
|
-
offsetDirection: 1 | -1;
|
|
183
|
-
offset: Vec3;
|
|
184
|
-
offsetFrame: Transform;
|
|
185
|
-
/** Latest authored rotation, expressed in the fully solved self frame. */
|
|
186
|
-
rotation?: ModelSpatialOperation;
|
|
187
186
|
sourceRefs: readonly SourceRef[];
|
|
188
187
|
parameters: readonly ParameterUsage[];
|
|
189
188
|
}> &
|
|
@@ -292,6 +291,26 @@ export type ModelParameterDimension = Readonly<{
|
|
|
292
291
|
vector: Vec3;
|
|
293
292
|
}>;
|
|
294
293
|
|
|
294
|
+
export type RotationReferenceSnapshot = Readonly<{
|
|
295
|
+
offset: Vec3;
|
|
296
|
+
offsetExplicit: boolean;
|
|
297
|
+
explicit: boolean;
|
|
298
|
+
/** Rotation in the displacement frame, including reversed axes. */
|
|
299
|
+
rotation: Vec3;
|
|
300
|
+
/** Reference displacement axes, in the result model's local coordinates. */
|
|
301
|
+
frame: RigidTransform;
|
|
302
|
+
}> &
|
|
303
|
+
(
|
|
304
|
+
| Readonly<{kind: 'pivot'; point: Vec3}>
|
|
305
|
+
| Readonly<{kind: 'pivotVertex'; id: VertexId}>
|
|
306
|
+
| Readonly<{kind: 'axisEdge'; id: EdgeId}>
|
|
307
|
+
| Readonly<{
|
|
308
|
+
kind: 'pivotPoint' | 'axisLine';
|
|
309
|
+
nodeId: string;
|
|
310
|
+
name: string;
|
|
311
|
+
}>
|
|
312
|
+
);
|
|
313
|
+
|
|
295
314
|
/** Local geometry coordinates; vector is the authored coordinates, offset, or angles. */
|
|
296
315
|
export type ModelSpatialOperation = Readonly<{
|
|
297
316
|
origin: Vec3;
|
|
@@ -300,20 +319,34 @@ export type ModelSpatialOperation = Readonly<{
|
|
|
300
319
|
frame?: RigidTransform;
|
|
301
320
|
rotation?: Vec3;
|
|
302
321
|
axisOnly?: boolean;
|
|
322
|
+
reference?: RotationReferenceSnapshot;
|
|
303
323
|
}>;
|
|
304
324
|
|
|
305
|
-
export type
|
|
325
|
+
export type RelationSpatialReference = Readonly<{
|
|
306
326
|
nodeId: string;
|
|
307
|
-
kind:
|
|
327
|
+
kind:
|
|
328
|
+
| 'pivot'
|
|
329
|
+
| 'pivotVertex'
|
|
330
|
+
| 'pivotPoint'
|
|
331
|
+
| 'axisEdge'
|
|
332
|
+
| 'pivotOffset'
|
|
333
|
+
| 'axisLine'
|
|
334
|
+
| 'axisOffset'
|
|
335
|
+
| 'rotate'
|
|
336
|
+
| 'offset';
|
|
308
337
|
spatial: ModelSpatialOperation;
|
|
309
338
|
}>;
|
|
310
339
|
|
|
311
|
-
export type
|
|
340
|
+
export type RelationPreview = Readonly<{
|
|
312
341
|
object: Pick<
|
|
313
342
|
ModelSnapshotObject,
|
|
314
|
-
|
|
343
|
+
| 'nodeId'
|
|
344
|
+
| 'compositionTransform'
|
|
345
|
+
| 'constraints'
|
|
346
|
+
| 'transformations'
|
|
347
|
+
| 'relationStages'
|
|
315
348
|
>;
|
|
316
|
-
spatial?:
|
|
349
|
+
spatial?: RelationSpatialReference;
|
|
317
350
|
}>;
|
|
318
351
|
|
|
319
352
|
export type RenderMesh = Readonly<{
|
|
@@ -336,6 +369,8 @@ export type RenderMesh = Readonly<{
|
|
|
336
369
|
start: number;
|
|
337
370
|
count: number;
|
|
338
371
|
edgeId: EdgeId;
|
|
372
|
+
/** True for native straight edges, suitable as rotation references. */
|
|
373
|
+
linear?: boolean;
|
|
339
374
|
}>[];
|
|
340
375
|
}>;
|
|
341
376
|
|
|
@@ -350,6 +385,8 @@ export type ModelSnapshotObject = Readonly<{
|
|
|
350
385
|
/** Placement in this snapshot tree; a root value uses model-local coordinates. */
|
|
351
386
|
transform: Transform;
|
|
352
387
|
constraints: readonly ConstraintSnapshot[];
|
|
388
|
+
transformations?: readonly TransformationSnapshot[];
|
|
389
|
+
relationStages?: readonly RelationStageSnapshot[];
|
|
353
390
|
elements: readonly ElementSnapshot[];
|
|
354
391
|
origin: Vec3;
|
|
355
392
|
sourceRefs: readonly SourceRef[];
|
|
@@ -403,12 +440,21 @@ export type ModelTopologyReference = Readonly<{
|
|
|
403
440
|
TopologySelection;
|
|
404
441
|
|
|
405
442
|
export type ConstraintTraceReference = Readonly<{
|
|
443
|
+
kind: 'constraint';
|
|
406
444
|
constraintId: string;
|
|
407
445
|
source: RelationObject;
|
|
408
446
|
target: RelationObject;
|
|
409
447
|
self?: RelationObject;
|
|
410
448
|
}>;
|
|
411
449
|
|
|
450
|
+
export type RelationTraceReference =
|
|
451
|
+
| ConstraintTraceReference
|
|
452
|
+
| Readonly<{
|
|
453
|
+
kind: 'transformation';
|
|
454
|
+
transformationId: string;
|
|
455
|
+
self?: RelationObject;
|
|
456
|
+
}>;
|
|
457
|
+
|
|
412
458
|
export type ModelOperationInstrumentation = Readonly<{
|
|
413
459
|
siteId: string;
|
|
414
460
|
execution: number;
|
|
@@ -425,27 +471,93 @@ export type ModelObjectRuntimeInfo = Readonly<{
|
|
|
425
471
|
sourceRefs: readonly SourceRef[];
|
|
426
472
|
}>;
|
|
427
473
|
|
|
474
|
+
type RelationDefinition =
|
|
475
|
+
| Readonly<{
|
|
476
|
+
kind: 'on' | 'align';
|
|
477
|
+
source: AnchorReference;
|
|
478
|
+
target: AnchorReference;
|
|
479
|
+
}>
|
|
480
|
+
| Readonly<{
|
|
481
|
+
kind: 'transformation';
|
|
482
|
+
actions: readonly TransformationAction[];
|
|
483
|
+
}>;
|
|
484
|
+
type StoredTransformation = Readonly<{
|
|
485
|
+
id: string;
|
|
486
|
+
kind: 'transformation';
|
|
487
|
+
actions: readonly TransformationAction[];
|
|
488
|
+
}>;
|
|
489
|
+
export type RelationStageSnapshot = Readonly<{
|
|
490
|
+
constraintIds: readonly string[];
|
|
491
|
+
transformationIds: readonly string[];
|
|
492
|
+
compositionTransform: Transform;
|
|
493
|
+
offsetFrame: Transform;
|
|
494
|
+
}>;
|
|
495
|
+
export type TransformationSnapshot = Readonly<{
|
|
496
|
+
id: string;
|
|
497
|
+
sourceRefs: readonly SourceRef[];
|
|
498
|
+
parameters: readonly ParameterUsage[];
|
|
499
|
+
offsets: readonly Readonly<{
|
|
500
|
+
value: Vec3;
|
|
501
|
+
frame: Transform;
|
|
502
|
+
sourceRefs: readonly SourceRef[];
|
|
503
|
+
}>[];
|
|
504
|
+
rotations: readonly Readonly<{
|
|
505
|
+
spatial: ModelSpatialOperation;
|
|
506
|
+
sourceRefs: readonly SourceRef[];
|
|
507
|
+
}>[];
|
|
508
|
+
offsetFrame: Transform;
|
|
509
|
+
}>;
|
|
510
|
+
type StoredPlacement = StoredConstraint | StoredTransformation;
|
|
511
|
+
|
|
428
512
|
type StoredConstraint = Readonly<{
|
|
429
513
|
id: string;
|
|
430
514
|
kind: 'on' | 'align';
|
|
431
515
|
source: RelationReference;
|
|
432
516
|
target: RelationReference;
|
|
433
|
-
rotations: readonly ConstraintRotation[];
|
|
434
|
-
offset: Vec3 | undefined;
|
|
435
517
|
}>;
|
|
436
518
|
|
|
437
519
|
// An omitted model refers to relate's self, including after immutable copies.
|
|
438
520
|
type RelationReference = StoredAnchor & Readonly<{model?: RelationObject}>;
|
|
439
|
-
type PivotSelection =
|
|
521
|
+
type PivotSelection = (
|
|
440
522
|
| Readonly<{kind: 'pivot'; point: Vec3}>
|
|
441
523
|
| Readonly<{kind: 'pivotVertex'; id: VertexId}>
|
|
442
|
-
| Readonly<{kind: '
|
|
443
|
-
|
|
524
|
+
| Readonly<{kind: 'pivotPoint'; point: RelationReference}>
|
|
525
|
+
| Readonly<{kind: 'axisEdge'; id: EdgeId}>
|
|
526
|
+
| Readonly<{kind: 'axisLine'; axis: RelationReference}>
|
|
527
|
+
) &
|
|
528
|
+
Readonly<{offset?: Vec3; implicit?: true}>;
|
|
529
|
+
type AxisSelection = Extract<PivotSelection, {kind: 'axisLine' | 'axisEdge'}>;
|
|
530
|
+
type PointSelection = Exclude<PivotSelection, AxisSelection>;
|
|
531
|
+
function isAxisSelection(pivot: PivotSelection): pivot is AxisSelection {
|
|
532
|
+
return pivot.kind === 'axisLine' || pivot.kind === 'axisEdge';
|
|
533
|
+
}
|
|
534
|
+
function selectionReference(
|
|
535
|
+
pivot: PivotSelection,
|
|
536
|
+
): RelationReference | undefined {
|
|
537
|
+
return pivot.kind === 'axisLine'
|
|
538
|
+
? pivot.axis
|
|
539
|
+
: pivot.kind === 'pivotPoint'
|
|
540
|
+
? pivot.point
|
|
541
|
+
: undefined;
|
|
542
|
+
}
|
|
543
|
+
function mapSelectionReference(
|
|
544
|
+
pivot: PivotSelection,
|
|
545
|
+
map: (reference: RelationReference) => RelationReference,
|
|
546
|
+
): PivotSelection {
|
|
547
|
+
return pivot.kind === 'axisLine'
|
|
548
|
+
? {...pivot, axis: map(pivot.axis)}
|
|
549
|
+
: pivot.kind === 'pivotPoint'
|
|
550
|
+
? {...pivot, point: map(pivot.point)}
|
|
551
|
+
: pivot;
|
|
552
|
+
}
|
|
553
|
+
type TransformationRotationAction = Readonly<{
|
|
444
554
|
pivot: PivotSelection;
|
|
445
555
|
angles: Vec3 | number;
|
|
446
556
|
}>;
|
|
447
|
-
type
|
|
448
|
-
|
|
557
|
+
type TransformationAction =
|
|
558
|
+
TransformationRotationAction | Readonly<{offset: Vec3}>;
|
|
559
|
+
type TransformationSpatialSelection = Readonly<{
|
|
560
|
+
kind: RelationSpatialReference['kind'];
|
|
449
561
|
pivot: PivotSelection;
|
|
450
562
|
}>;
|
|
451
563
|
type RelateContext = Readonly<{self: RelationObject; original: RelationObject}>;
|
|
@@ -529,7 +641,7 @@ type ModelObjectInit<Kind extends ModelKind = ModelKind> = Readonly<{
|
|
|
529
641
|
material?: ModelMaterialSnapshot;
|
|
530
642
|
children?: readonly ModelObject[];
|
|
531
643
|
assembly?: SolveContext;
|
|
532
|
-
|
|
644
|
+
placements?: readonly StoredPlacement[];
|
|
533
645
|
elements?: StoredElements;
|
|
534
646
|
sourceRefs?: readonly SourceRef[];
|
|
535
647
|
parameters?: readonly ParameterUsage[];
|
|
@@ -560,6 +672,7 @@ type SolidGeometry = KernelArtifact<
|
|
|
560
672
|
|
|
561
673
|
type SolveContext = {
|
|
562
674
|
poses: ReadonlyMap<RelationObject, RigidTransform>;
|
|
675
|
+
frame: RigidTransform;
|
|
563
676
|
};
|
|
564
677
|
|
|
565
678
|
function transformSolveContext(
|
|
@@ -567,6 +680,7 @@ function transformSolveContext(
|
|
|
567
680
|
transform: RigidTransform,
|
|
568
681
|
): SolveContext {
|
|
569
682
|
return {
|
|
683
|
+
frame: composeTransforms(transform, context.frame),
|
|
570
684
|
poses: new Map(
|
|
571
685
|
[...context.poses].map(([model, pose]) => [
|
|
572
686
|
model,
|
|
@@ -740,7 +854,7 @@ export interface ModelCapabilities<
|
|
|
740
854
|
relate(
|
|
741
855
|
build: (
|
|
742
856
|
self: ModelForKind<Elements, Kind>,
|
|
743
|
-
) =>
|
|
857
|
+
) => Relation | readonly Relation[],
|
|
744
858
|
): ModelForKind<Elements, Kind>;
|
|
745
859
|
expose<const Sources extends ElementSources>(
|
|
746
860
|
sources: Sources,
|
|
@@ -1127,7 +1241,7 @@ export function modelTopologyIds(
|
|
|
1127
1241
|
: undefined;
|
|
1128
1242
|
const source = isModelObject(value)
|
|
1129
1243
|
? value
|
|
1130
|
-
: value instanceof
|
|
1244
|
+
: value instanceof RelationExpression
|
|
1131
1245
|
? value.self()
|
|
1132
1246
|
: topology?.source;
|
|
1133
1247
|
const geometry = isModelObject(source)
|
|
@@ -1142,24 +1256,14 @@ export function modelTopologyIds(
|
|
|
1142
1256
|
);
|
|
1143
1257
|
}
|
|
1144
1258
|
|
|
1145
|
-
/** Traceable
|
|
1146
|
-
export abstract class
|
|
1147
|
-
private get sourceRefs(): SourceRef[] {
|
|
1148
|
-
return valueTrace(this).sourceRefs;
|
|
1149
|
-
}
|
|
1150
|
-
private get parameters(): ParameterUsage[] {
|
|
1151
|
-
return valueTrace(this).parameters;
|
|
1152
|
-
}
|
|
1153
|
-
protected spatialOperation?: ConstraintSpatialSelection;
|
|
1259
|
+
/** Traceable placement values, including unfinished pivot and axis selections. */
|
|
1260
|
+
export abstract class RelationExpression {
|
|
1154
1261
|
protected constructor(
|
|
1155
|
-
protected readonly
|
|
1156
|
-
protected readonly source: AnchorReference,
|
|
1157
|
-
protected readonly target: AnchorReference,
|
|
1158
|
-
protected readonly displacement: Vec3 | undefined,
|
|
1159
|
-
protected readonly rotations: readonly ConstraintRotation[],
|
|
1262
|
+
protected readonly definition: RelationDefinition,
|
|
1160
1263
|
protected readonly context: RelateContext | undefined,
|
|
1161
|
-
protected readonly
|
|
1162
|
-
previous?:
|
|
1264
|
+
protected readonly relationId: string,
|
|
1265
|
+
previous?: RelationExpression,
|
|
1266
|
+
protected readonly spatialOperation?: TransformationSpatialSelection,
|
|
1163
1267
|
) {
|
|
1164
1268
|
valueTraces.set(this, {
|
|
1165
1269
|
sourceRefs: [...(previous ? valueTrace(previous).sourceRefs : [])],
|
|
@@ -1168,7 +1272,7 @@ export abstract class ConstraintExpression {
|
|
|
1168
1272
|
}
|
|
1169
1273
|
/** @internal */
|
|
1170
1274
|
attachSource(sourceRef: SourceRef): void {
|
|
1171
|
-
const refs = this.sourceRefs;
|
|
1275
|
+
const refs = valueTrace(this).sourceRefs;
|
|
1172
1276
|
if (
|
|
1173
1277
|
!refs.some(
|
|
1174
1278
|
ref =>
|
|
@@ -1181,107 +1285,125 @@ export abstract class ConstraintExpression {
|
|
|
1181
1285
|
}
|
|
1182
1286
|
/** @internal */
|
|
1183
1287
|
attachParameters(parameters: readonly ParameterUsage[]): void {
|
|
1184
|
-
appendUniqueParameters(this.parameters, parameters);
|
|
1288
|
+
appendUniqueParameters(valueTrace(this).parameters, parameters);
|
|
1185
1289
|
}
|
|
1186
1290
|
/** @internal */
|
|
1187
1291
|
self(): RelationObject | undefined {
|
|
1188
1292
|
return this.context?.self;
|
|
1189
1293
|
}
|
|
1190
1294
|
/** @internal */
|
|
1191
|
-
traceReference():
|
|
1295
|
+
traceReference(): RelationTraceReference {
|
|
1296
|
+
if (this.definition.kind === 'transformation')
|
|
1297
|
+
return {
|
|
1298
|
+
kind: 'transformation',
|
|
1299
|
+
transformationId: this.relationId,
|
|
1300
|
+
self: this.context?.self,
|
|
1301
|
+
};
|
|
1192
1302
|
const rebind = (model: RelationObject) =>
|
|
1193
1303
|
model === this.context?.original ? this.context.self : model;
|
|
1194
1304
|
return {
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1305
|
+
kind: 'constraint',
|
|
1306
|
+
constraintId: this.relationId,
|
|
1307
|
+
source: rebind(this.definition.source.model),
|
|
1308
|
+
target: rebind(this.definition.target.model),
|
|
1198
1309
|
self: this.context?.self,
|
|
1199
1310
|
};
|
|
1200
1311
|
}
|
|
1201
1312
|
/** @internal */
|
|
1202
|
-
storeFor(model: RelationObject, original: RelationObject):
|
|
1313
|
+
storeFor(model: RelationObject, original: RelationObject): StoredPlacement {
|
|
1203
1314
|
const isSelf = (candidate: RelationObject | undefined) =>
|
|
1204
1315
|
candidate === model || candidate === original;
|
|
1205
|
-
if (!isSelf(this.source.model) && !isSelf(this.target.model))
|
|
1206
|
-
throw new Error(
|
|
1207
|
-
'The constraint returned by relate() must involve self or the original receiver.',
|
|
1208
|
-
);
|
|
1209
1316
|
const bind = (reference: RelationReference): RelationReference => ({
|
|
1210
1317
|
...reference,
|
|
1211
1318
|
model: isSelf(reference.model) ? undefined : reference.model,
|
|
1212
1319
|
});
|
|
1213
|
-
const
|
|
1214
|
-
|
|
1215
|
-
kind
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1320
|
+
const definition = this.definition;
|
|
1321
|
+
if (
|
|
1322
|
+
definition.kind !== 'transformation' &&
|
|
1323
|
+
!isSelf(definition.source.model) &&
|
|
1324
|
+
!isSelf(definition.target.model)
|
|
1325
|
+
)
|
|
1326
|
+
throw new Error(
|
|
1327
|
+
'The constraint returned by relate() must involve self or the original receiver.',
|
|
1328
|
+
);
|
|
1329
|
+
const stored: StoredPlacement =
|
|
1330
|
+
definition.kind === 'transformation'
|
|
1331
|
+
? {
|
|
1332
|
+
kind: 'transformation',
|
|
1333
|
+
id: this.relationId,
|
|
1334
|
+
actions: definition.actions.map(action => {
|
|
1335
|
+
const storedAction = {
|
|
1336
|
+
...action,
|
|
1337
|
+
...('pivot' in action
|
|
1338
|
+
? {
|
|
1339
|
+
pivot: mapSelectionReference(action.pivot, bind),
|
|
1340
|
+
}
|
|
1341
|
+
: {}),
|
|
1342
|
+
};
|
|
1343
|
+
valueTraces.set(storedAction, {
|
|
1344
|
+
sourceRefs: [...valueTrace(action).sourceRefs],
|
|
1345
|
+
parameters: [...valueTrace(action).parameters],
|
|
1346
|
+
});
|
|
1347
|
+
return storedAction;
|
|
1348
|
+
}),
|
|
1349
|
+
}
|
|
1350
|
+
: {
|
|
1351
|
+
kind: definition.kind,
|
|
1352
|
+
id: this.relationId,
|
|
1353
|
+
source: bind(definition.source),
|
|
1354
|
+
target: bind(definition.target),
|
|
1355
|
+
};
|
|
1227
1356
|
valueTraces.set(stored, {
|
|
1228
|
-
sourceRefs: [...this.sourceRefs],
|
|
1229
|
-
parameters: [...this.parameters],
|
|
1357
|
+
sourceRefs: [...valueTrace(this).sourceRefs],
|
|
1358
|
+
parameters: [...valueTrace(this).parameters],
|
|
1230
1359
|
});
|
|
1231
1360
|
return stored;
|
|
1232
1361
|
}
|
|
1233
1362
|
/** @internal */
|
|
1234
|
-
preview(
|
|
1363
|
+
preview(
|
|
1364
|
+
preceding: readonly RelationExpression[] = [],
|
|
1365
|
+
): RelationPreview | undefined {
|
|
1235
1366
|
if (!this.context) return undefined;
|
|
1236
1367
|
const pivot = this.spatialOperation?.pivot;
|
|
1237
|
-
const selection =
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
},
|
|
1252
|
-
}
|
|
1253
|
-
: this.spatialOperation;
|
|
1254
|
-
return this.context.original[relationPreview](
|
|
1255
|
-
this.context.self.nodeId,
|
|
1368
|
+
const selection = pivot
|
|
1369
|
+
? {
|
|
1370
|
+
...this.spatialOperation!,
|
|
1371
|
+
pivot: mapSelectionReference(pivot, reference => ({
|
|
1372
|
+
...reference,
|
|
1373
|
+
model:
|
|
1374
|
+
reference.model === this.context!.self ||
|
|
1375
|
+
reference.model === this.context!.original
|
|
1376
|
+
? undefined
|
|
1377
|
+
: reference.model,
|
|
1378
|
+
})),
|
|
1379
|
+
}
|
|
1380
|
+
: this.spatialOperation;
|
|
1381
|
+
return this.context.self[previewRelation](
|
|
1256
1382
|
this.storeFor(this.context.self, this.context.original),
|
|
1257
1383
|
selection,
|
|
1384
|
+
preceding,
|
|
1258
1385
|
);
|
|
1259
1386
|
}
|
|
1387
|
+
/** @internal */
|
|
1388
|
+
continuationArguments(): [
|
|
1389
|
+
RelationDefinition,
|
|
1390
|
+
RelateContext | undefined,
|
|
1391
|
+
string,
|
|
1392
|
+
RelationExpression,
|
|
1393
|
+
] {
|
|
1394
|
+
return [this.definition, this.context, this.relationId, this];
|
|
1395
|
+
}
|
|
1260
1396
|
}
|
|
1261
1397
|
|
|
1262
|
-
export class Constraint extends
|
|
1398
|
+
export class Constraint extends RelationExpression {
|
|
1399
|
+
declare protected readonly definition: Exclude<
|
|
1400
|
+
RelationDefinition,
|
|
1401
|
+
{kind: 'transformation'}
|
|
1402
|
+
>;
|
|
1263
1403
|
private constructor(
|
|
1264
|
-
kind:
|
|
1265
|
-
source: AnchorReference,
|
|
1266
|
-
target: AnchorReference,
|
|
1267
|
-
displacement: Vec3 | undefined = undefined,
|
|
1268
|
-
rotations: readonly ConstraintRotation[] = [],
|
|
1269
|
-
context = activeRelate,
|
|
1270
|
-
constraintId = `constraint-${nextConstraintId++}`,
|
|
1271
|
-
previous?: ConstraintExpression,
|
|
1272
|
-
spatialOperation?: ConstraintSpatialSelection,
|
|
1404
|
+
definition: Exclude<RelationDefinition, {kind: 'transformation'}>,
|
|
1273
1405
|
) {
|
|
1274
|
-
super(
|
|
1275
|
-
kind,
|
|
1276
|
-
source,
|
|
1277
|
-
target,
|
|
1278
|
-
displacement,
|
|
1279
|
-
rotations,
|
|
1280
|
-
context,
|
|
1281
|
-
constraintId,
|
|
1282
|
-
previous,
|
|
1283
|
-
);
|
|
1284
|
-
this.spatialOperation = spatialOperation;
|
|
1406
|
+
super(definition, activeRelate, `constraint-${nextConstraintId++}`);
|
|
1285
1407
|
}
|
|
1286
1408
|
/** @internal */
|
|
1287
1409
|
static create(
|
|
@@ -1295,171 +1417,254 @@ export class Constraint extends ConstraintExpression {
|
|
|
1295
1417
|
throw new Error(
|
|
1296
1418
|
'align() requires points, curves, or surfaces; select geometry on a solid or group.',
|
|
1297
1419
|
);
|
|
1298
|
-
return new Constraint(kind, source, target);
|
|
1420
|
+
return new Constraint({kind, source, target});
|
|
1299
1421
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
this.context,
|
|
1318
|
-
this.constraintId,
|
|
1319
|
-
this,
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
/** One completed relative placement step. Array order composes transformations. */
|
|
1425
|
+
export class Transformation extends RelationExpression {
|
|
1426
|
+
declare protected readonly definition: Extract<
|
|
1427
|
+
RelationDefinition,
|
|
1428
|
+
{kind: 'transformation'}
|
|
1429
|
+
>;
|
|
1430
|
+
/** @internal */
|
|
1431
|
+
constructor(action: TransformationAction, previous?: RelationExpression) {
|
|
1432
|
+
const args = previous?.continuationArguments();
|
|
1433
|
+
super(
|
|
1434
|
+
{kind: 'transformation', actions: [action]},
|
|
1435
|
+
args ? args[1] : activeRelate,
|
|
1436
|
+
args?.[2] ?? `transformation-${nextConstraintId++}`,
|
|
1437
|
+
previous,
|
|
1438
|
+
'pivot' in action ? {kind: 'rotate', pivot: action.pivot} : undefined,
|
|
1320
1439
|
);
|
|
1440
|
+
valueTraces.set(action, valueTrace(this));
|
|
1321
1441
|
}
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
}
|
|
1333
|
-
/** @code3d.param id {kind: 'vertex', label: 'Pivot vertex'} */
|
|
1334
|
-
pivotVertex(id: VertexId): ConstraintPivotChain {
|
|
1335
|
-
return new ConstraintPivotChain(this, {kind: 'pivotVertex', id});
|
|
1336
|
-
}
|
|
1337
|
-
/** Select a positioned axis in the composition. */
|
|
1338
|
-
around(axis: LineAnchor): ConstraintAroundChain {
|
|
1339
|
-
const reference = anchorReference(axis);
|
|
1340
|
-
if (reference.kind !== 'line')
|
|
1341
|
-
throw new Error('around() requires an axis or straight edge.');
|
|
1342
|
-
const geometry =
|
|
1343
|
-
reference.topology?.source[modelGeometry]()?.value ??
|
|
1344
|
-
(reference.whole && isModelObject(reference.model)
|
|
1345
|
-
? reference.model[modelGeometry]()?.value
|
|
1346
|
-
: undefined);
|
|
1347
|
-
if (geometry) {
|
|
1348
|
-
const straight = reference.topology
|
|
1349
|
-
? withTopologyShape(
|
|
1350
|
-
geometry.shape,
|
|
1351
|
-
geometry.topology,
|
|
1352
|
-
reference.topology.selection,
|
|
1353
|
-
shape => (shape as ReplicadEdge).geomType === 'LINE',
|
|
1354
|
-
)
|
|
1355
|
-
: (geometry.shape as ReplicadEdge).geomType === 'LINE';
|
|
1356
|
-
if (!straight)
|
|
1357
|
-
throw new Error(
|
|
1358
|
-
'around() requires a straight axis; curved edges do not define one rotation axis.',
|
|
1359
|
-
);
|
|
1360
|
-
}
|
|
1361
|
-
return new ConstraintAroundChain(this, {kind: 'around', axis: reference});
|
|
1362
|
-
}
|
|
1363
|
-
/**
|
|
1364
|
-
* Rotate about self's origin and local X, Y, then Z axes, in degrees.
|
|
1365
|
-
* @code3d.param x {kind: 'angle', default: 0, label: 'Rotate X'}
|
|
1366
|
-
* @code3d.param y {kind: 'angle', default: 0, label: 'Rotate Y'}
|
|
1367
|
-
* @code3d.param z {kind: 'angle', default: 0, label: 'Rotate Z'}
|
|
1368
|
-
*/
|
|
1369
|
-
rotate(x: number, y: number, z: number): Constraint;
|
|
1370
|
-
rotate(x = 0, y = 0, z = 0): Constraint {
|
|
1371
|
-
return this.withRotation({kind: 'pivot', point: origin}, [x, y, z], this);
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
/** Carries the reference selection until its independent rotation is complete. */
|
|
1445
|
+
class TransformationRotation extends RelationExpression {
|
|
1446
|
+
constructor() {
|
|
1447
|
+
super(
|
|
1448
|
+
{kind: 'transformation', actions: []},
|
|
1449
|
+
activeRelate,
|
|
1450
|
+
`transformation-${nextConstraintId++}`,
|
|
1451
|
+
);
|
|
1372
1452
|
}
|
|
1373
|
-
/** @internal */
|
|
1374
1453
|
withRotation(
|
|
1375
1454
|
pivot: PivotSelection,
|
|
1376
1455
|
angles: Vec3 | number,
|
|
1377
|
-
previous:
|
|
1378
|
-
):
|
|
1456
|
+
previous: RelationExpression,
|
|
1457
|
+
): Transformation {
|
|
1379
1458
|
assertFiniteVector(
|
|
1380
1459
|
'rotate',
|
|
1381
1460
|
typeof angles === 'number' ? [angles, 0, 0] : angles,
|
|
1382
1461
|
);
|
|
1383
|
-
return new
|
|
1384
|
-
this.kind,
|
|
1385
|
-
this.source,
|
|
1386
|
-
this.target,
|
|
1387
|
-
this.displacement,
|
|
1388
|
-
[...this.rotations, {pivot, angles}],
|
|
1389
|
-
this.context,
|
|
1390
|
-
this.constraintId,
|
|
1391
|
-
previous,
|
|
1392
|
-
{kind: 'rotate', pivot},
|
|
1393
|
-
);
|
|
1462
|
+
return new Transformation({pivot, angles}, previous);
|
|
1394
1463
|
}
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
function rotationPointReference(point: PointAnchor): AnchorReference {
|
|
1467
|
+
const reference = anchorReference(point);
|
|
1468
|
+
if (reference.kind !== 'point')
|
|
1469
|
+
throw new Error('pivotPoint() requires a point reference.');
|
|
1470
|
+
return reference;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
function rotationAxisReference(axis: LineAnchor): AnchorReference {
|
|
1474
|
+
const reference = anchorReference(axis);
|
|
1475
|
+
if (reference.kind !== 'line')
|
|
1476
|
+
throw new Error('axisLine() requires an axis or straight edge.');
|
|
1477
|
+
const geometry =
|
|
1478
|
+
reference.topology?.source[modelGeometry]()?.value ??
|
|
1479
|
+
(reference.whole && isModelObject(reference.model)
|
|
1480
|
+
? reference.model[modelGeometry]()?.value
|
|
1481
|
+
: undefined);
|
|
1482
|
+
if (geometry) {
|
|
1483
|
+
const straight = reference.topology
|
|
1484
|
+
? withTopologyShape(
|
|
1485
|
+
geometry.shape,
|
|
1486
|
+
geometry.topology,
|
|
1487
|
+
reference.topology.selection,
|
|
1488
|
+
shape => (shape as ReplicadEdge).geomType === 'LINE',
|
|
1489
|
+
)
|
|
1490
|
+
: (geometry.shape as ReplicadEdge).geomType === 'LINE';
|
|
1491
|
+
if (!straight)
|
|
1492
|
+
throw new Error(
|
|
1493
|
+
'axisLine() requires a straight axis; curved edges do not define one rotation axis.',
|
|
1494
|
+
);
|
|
1416
1495
|
}
|
|
1496
|
+
return reference;
|
|
1417
1497
|
}
|
|
1418
1498
|
|
|
1419
|
-
export
|
|
1499
|
+
export type Relation = Constraint | Transformation;
|
|
1500
|
+
|
|
1501
|
+
export class PivotRotation extends RelationExpression {
|
|
1420
1502
|
/** @internal */
|
|
1421
1503
|
constructor(
|
|
1422
|
-
|
|
1423
|
-
|
|
1504
|
+
protected readonly chain: TransformationRotation,
|
|
1505
|
+
protected readonly selection: PointSelection,
|
|
1506
|
+
previous?: RelationExpression,
|
|
1424
1507
|
) {
|
|
1425
|
-
super(...
|
|
1426
|
-
|
|
1508
|
+
super(...(previous ?? chain).continuationArguments(), {
|
|
1509
|
+
kind: selection.offset ? 'pivotOffset' : selection.kind,
|
|
1510
|
+
pivot: selection,
|
|
1511
|
+
});
|
|
1427
1512
|
}
|
|
1428
1513
|
/**
|
|
1429
1514
|
* @code3d.param x {kind: 'angle', default: 0, label: 'Rotate X'}
|
|
1430
1515
|
* @code3d.param y {kind: 'angle', default: 0, label: 'Rotate Y'}
|
|
1431
1516
|
* @code3d.param z {kind: 'angle', default: 0, label: 'Rotate Z'}
|
|
1432
1517
|
*/
|
|
1433
|
-
rotate(x: number, y: number, z: number):
|
|
1434
|
-
rotate(x = 0, y = 0, z = 0):
|
|
1435
|
-
return this.
|
|
1518
|
+
rotate(x: number, y: number, z: number): Transformation;
|
|
1519
|
+
rotate(x = 0, y = 0, z = 0): Transformation {
|
|
1520
|
+
return this.chain.withRotation(this.selection, [x, y, z], this);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
export class PivotChain extends PivotRotation {
|
|
1525
|
+
/**
|
|
1526
|
+
* Offset the selected pivot along self's local axes, retaining its reference.
|
|
1527
|
+
* @code3d.param x {kind: 'length', default: 0, label: 'Pivot ΔX'}
|
|
1528
|
+
* @code3d.param y {kind: 'length', default: 0, label: 'Pivot ΔY'}
|
|
1529
|
+
* @code3d.param z {kind: 'length', default: 0, label: 'Pivot ΔZ'}
|
|
1530
|
+
*/
|
|
1531
|
+
pivotOffset(x: number, y: number, z: number): PivotRotation;
|
|
1532
|
+
pivotOffset(x = 0, y = 0, z = 0): PivotRotation {
|
|
1533
|
+
assertFiniteVector('pivotOffset', [x, y, z]);
|
|
1534
|
+
return new PivotRotation(
|
|
1535
|
+
this.chain,
|
|
1536
|
+
{...this.selection, offset: [x, y, z]},
|
|
1537
|
+
this,
|
|
1538
|
+
);
|
|
1436
1539
|
}
|
|
1437
1540
|
}
|
|
1438
|
-
|
|
1541
|
+
|
|
1542
|
+
export class AxisRotation extends RelationExpression {
|
|
1439
1543
|
/** @internal */
|
|
1440
1544
|
constructor(
|
|
1441
|
-
|
|
1442
|
-
|
|
1545
|
+
protected readonly chain: TransformationRotation,
|
|
1546
|
+
protected readonly selection: AxisSelection,
|
|
1547
|
+
previous?: RelationExpression,
|
|
1443
1548
|
) {
|
|
1444
|
-
super(...
|
|
1445
|
-
|
|
1549
|
+
super(...(previous ?? chain).continuationArguments(), {
|
|
1550
|
+
kind: selection.offset ? 'axisOffset' : selection.kind,
|
|
1551
|
+
pivot: selection,
|
|
1552
|
+
});
|
|
1446
1553
|
}
|
|
1447
1554
|
/** @code3d.param angle {kind: 'angle', default: 0, label: 'Rotate'} */
|
|
1448
|
-
rotate(angle: number):
|
|
1449
|
-
rotate(angle = 0):
|
|
1450
|
-
return this.
|
|
1555
|
+
rotate(angle: number): Transformation;
|
|
1556
|
+
rotate(angle = 0): Transformation {
|
|
1557
|
+
return this.chain.withRotation(this.selection, angle, this);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
export class AxisChain extends AxisRotation {
|
|
1562
|
+
/**
|
|
1563
|
+
* Offset the selected axis in its reference frame, retaining its direction.
|
|
1564
|
+
* @code3d.param x {kind: 'length', default: 0, label: 'Axis ΔX'}
|
|
1565
|
+
* @code3d.param y {kind: 'length', default: 0, label: 'Axis ΔY'}
|
|
1566
|
+
* @code3d.param z {kind: 'length', default: 0, label: 'Axis ΔZ'}
|
|
1567
|
+
*/
|
|
1568
|
+
axisOffset(x: number, y: number, z: number): AxisRotation;
|
|
1569
|
+
axisOffset(x = 0, y = 0, z = 0): AxisRotation {
|
|
1570
|
+
assertFiniteVector('axisOffset', [x, y, z]);
|
|
1571
|
+
return new AxisRotation(
|
|
1572
|
+
this.chain,
|
|
1573
|
+
{...this.selection, offset: [x, y, z]},
|
|
1574
|
+
this,
|
|
1575
|
+
);
|
|
1451
1576
|
}
|
|
1452
1577
|
}
|
|
1453
1578
|
|
|
1579
|
+
function selectedAxis(
|
|
1580
|
+
axis: RelationReference,
|
|
1581
|
+
offset: Vec3 | undefined,
|
|
1582
|
+
): RigidTransform {
|
|
1583
|
+
return composeTransforms(axis.transform, translation(offset ?? origin));
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
/**
|
|
1587
|
+
* Move the joint result along the fixed axes of its composition.
|
|
1588
|
+
* @code3d.param x {kind: 'length', default: 0, label: 'ΔX'}
|
|
1589
|
+
* @code3d.param y {kind: 'length', default: 0, label: 'ΔY'}
|
|
1590
|
+
* @code3d.param z {kind: 'length', default: 0, label: 'ΔZ'}
|
|
1591
|
+
*/
|
|
1592
|
+
export function offset(x: number, y: number, z: number): Transformation;
|
|
1593
|
+
export function offset(x = 0, y = 0, z = 0): Transformation {
|
|
1594
|
+
assertFiniteVector('offset', [x, y, z]);
|
|
1595
|
+
return new Transformation({offset: [x, y, z]});
|
|
1596
|
+
}
|
|
1597
|
+
/**
|
|
1598
|
+
* Rotate about self's current origin and local X, Y, then Z axes.
|
|
1599
|
+
* @code3d.param x {kind: 'angle', default: 0, label: 'Rotate X'}
|
|
1600
|
+
* @code3d.param y {kind: 'angle', default: 0, label: 'Rotate Y'}
|
|
1601
|
+
* @code3d.param z {kind: 'angle', default: 0, label: 'Rotate Z'}
|
|
1602
|
+
*/
|
|
1603
|
+
export function rotate(x: number, y: number, z: number): Transformation;
|
|
1604
|
+
export function rotate(x = 0, y = 0, z = 0): Transformation {
|
|
1605
|
+
assertFiniteVector('rotate', [x, y, z]);
|
|
1606
|
+
return new Transformation({
|
|
1607
|
+
pivot: {kind: 'pivot', point: origin, implicit: true},
|
|
1608
|
+
angles: [x, y, z],
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
/**
|
|
1612
|
+
* Select a pivot in self's local coordinates for the next rotation.
|
|
1613
|
+
* @code3d.param x {kind: 'length', default: 0, label: 'Pivot X'}
|
|
1614
|
+
* @code3d.param y {kind: 'length', default: 0, label: 'Pivot Y'}
|
|
1615
|
+
* @code3d.param z {kind: 'length', default: 0, label: 'Pivot Z'}
|
|
1616
|
+
*/
|
|
1617
|
+
export function pivot([x, y, z]: Vec3): PivotChain;
|
|
1618
|
+
export function pivot([x = 0, y = 0, z = 0]: Vec3 = origin): PivotChain {
|
|
1619
|
+
assertFiniteVector('pivot', [x, y, z]);
|
|
1620
|
+
return new PivotChain(new TransformationRotation(), {
|
|
1621
|
+
kind: 'pivot',
|
|
1622
|
+
point: [x, y, z],
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
/** @code3d.param id {kind: 'vertex', label: 'Pivot vertex'} */
|
|
1626
|
+
export function pivotVertex(id: VertexId): PivotChain {
|
|
1627
|
+
assertTopologyId('vertex', id);
|
|
1628
|
+
return new PivotChain(new TransformationRotation(), {
|
|
1629
|
+
kind: 'pivotVertex',
|
|
1630
|
+
id,
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
/** Select a point reference as the center; rotation axes remain self local. @code3d.tool */
|
|
1634
|
+
export function pivotPoint(point: PointAnchor): PivotChain {
|
|
1635
|
+
return new PivotChain(new TransformationRotation(), {
|
|
1636
|
+
kind: 'pivotPoint',
|
|
1637
|
+
point: rotationPointReference(point),
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
/** @code3d.param id {kind: 'edge', label: 'Rotation edge'} */
|
|
1641
|
+
export function axisEdge(id: EdgeId): AxisChain {
|
|
1642
|
+
assertTopologyId('edge', id);
|
|
1643
|
+
return new AxisChain(new TransformationRotation(), {
|
|
1644
|
+
kind: 'axisEdge',
|
|
1645
|
+
id,
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Select a positioned axis in the composition for the next rotation.
|
|
1650
|
+
* @code3d.tool
|
|
1651
|
+
*/
|
|
1652
|
+
export function axisLine(axis: LineAnchor): AxisChain {
|
|
1653
|
+
return new AxisChain(new TransformationRotation(), {
|
|
1654
|
+
kind: 'axisLine',
|
|
1655
|
+
axis: rotationAxisReference(axis),
|
|
1656
|
+
});
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1454
1659
|
const modelGeometry = Symbol('modelGeometry');
|
|
1455
1660
|
const referenceBounds = Symbol('referenceBounds');
|
|
1456
1661
|
const referenceBoundsParts = Symbol('referenceBoundsParts');
|
|
1457
1662
|
const modelSnapshotQueries = Symbol('modelSnapshotQueries');
|
|
1458
|
-
const
|
|
1663
|
+
const previewRelation = Symbol('previewRelation');
|
|
1459
1664
|
|
|
1460
1665
|
type RelationObjectInit = Readonly<{
|
|
1461
1666
|
nodeId?: string;
|
|
1462
|
-
|
|
1667
|
+
placements?: readonly StoredPlacement[];
|
|
1463
1668
|
sourceRefs?: readonly SourceRef[];
|
|
1464
1669
|
parameters?: readonly ParameterUsage[];
|
|
1465
1670
|
}>;
|
|
@@ -1469,11 +1674,17 @@ export abstract class RelationObject {
|
|
|
1469
1674
|
readonly nodeId: string;
|
|
1470
1675
|
/** @internal */
|
|
1471
1676
|
abstract readonly name: string;
|
|
1472
|
-
protected
|
|
1677
|
+
protected placements: StoredPlacement[];
|
|
1678
|
+
|
|
1679
|
+
protected get constraints(): StoredConstraint[] {
|
|
1680
|
+
return this.placements.filter(
|
|
1681
|
+
(value): value is StoredConstraint => value.kind !== 'transformation',
|
|
1682
|
+
);
|
|
1683
|
+
}
|
|
1473
1684
|
|
|
1474
1685
|
protected constructor(init: RelationObjectInit = {}) {
|
|
1475
1686
|
this.nodeId = init.nodeId ?? `node-${nextNodeId++}`;
|
|
1476
|
-
this.
|
|
1687
|
+
this.placements = [...(init.placements ?? [])];
|
|
1477
1688
|
valueTraces.set(this, {
|
|
1478
1689
|
sourceRefs: [...(init.sourceRefs ?? [])],
|
|
1479
1690
|
parameters: [...(init.parameters ?? [])],
|
|
@@ -1490,6 +1701,24 @@ export abstract class RelationObject {
|
|
|
1490
1701
|
|
|
1491
1702
|
protected abstract copyRelations(init: RelationObjectInit): RelationObject;
|
|
1492
1703
|
|
|
1704
|
+
protected edgeReference(_id: EdgeId): RelationReference {
|
|
1705
|
+
throw new Error(
|
|
1706
|
+
'axisEdge() requires model topology. Use axisLine(lineRef) for a line reference.',
|
|
1707
|
+
);
|
|
1708
|
+
}
|
|
1709
|
+
private rotationAxis(pivot: AxisSelection): RelationReference {
|
|
1710
|
+
return pivot.kind === 'axisLine'
|
|
1711
|
+
? pivot.axis
|
|
1712
|
+
: this.edgeReference(pivot.id);
|
|
1713
|
+
}
|
|
1714
|
+
private rotationPoint(pivot: PointSelection): Vec3 {
|
|
1715
|
+
return pivot.kind === 'pivot'
|
|
1716
|
+
? pivot.point
|
|
1717
|
+
: pivot.kind === 'pivotVertex'
|
|
1718
|
+
? this.vertexPosition(pivot.id)
|
|
1719
|
+
: pivot.point.transform.position;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1493
1722
|
protected vertexPosition(_id: VertexId): Vec3 {
|
|
1494
1723
|
throw new Error(
|
|
1495
1724
|
'pivotVertex() requires model topology, not a sketch reference frame. Use pivot([x, y, z]) instead.',
|
|
@@ -1514,27 +1743,30 @@ export abstract class RelationObject {
|
|
|
1514
1743
|
/** Store relations only after the complete callback has returned. */
|
|
1515
1744
|
protected addRelations(
|
|
1516
1745
|
original: RelationObject,
|
|
1517
|
-
build: () =>
|
|
1746
|
+
build: () => Relation | readonly Relation[],
|
|
1518
1747
|
): readonly RelationObject[] {
|
|
1519
1748
|
const previous = activeRelate;
|
|
1520
1749
|
activeRelate = {self: this, original};
|
|
1521
|
-
let built:
|
|
1750
|
+
let built: Relation | readonly Relation[];
|
|
1522
1751
|
try {
|
|
1523
1752
|
built = build();
|
|
1524
1753
|
} finally {
|
|
1525
1754
|
activeRelate = previous;
|
|
1526
1755
|
}
|
|
1527
|
-
const
|
|
1756
|
+
const relations: readonly Relation[] = Array.isArray(built)
|
|
1528
1757
|
? built
|
|
1529
|
-
: [built as
|
|
1530
|
-
const stored =
|
|
1531
|
-
if (
|
|
1758
|
+
: [built as Relation];
|
|
1759
|
+
const stored = relations.map(constraint => {
|
|
1760
|
+
if (
|
|
1761
|
+
!(constraint instanceof Constraint) &&
|
|
1762
|
+
!(constraint instanceof Transformation)
|
|
1763
|
+
)
|
|
1532
1764
|
throw new Error(
|
|
1533
|
-
'relate() requires a completed Constraint; finish pivot
|
|
1765
|
+
'relate() requires a completed Constraint or Transformation; finish the pivot or axis selector with rotate().',
|
|
1534
1766
|
);
|
|
1535
1767
|
return constraint.storeFor(this, original);
|
|
1536
1768
|
});
|
|
1537
|
-
this.
|
|
1769
|
+
this.placements.push(...stored);
|
|
1538
1770
|
return uniqueModels(stored.flatMap(constraintReferences));
|
|
1539
1771
|
}
|
|
1540
1772
|
|
|
@@ -1643,27 +1875,37 @@ export abstract class RelationObject {
|
|
|
1643
1875
|
};
|
|
1644
1876
|
}
|
|
1645
1877
|
|
|
1646
|
-
private
|
|
1647
|
-
constraint:
|
|
1878
|
+
private bodyActions(
|
|
1879
|
+
constraint: StoredTransformation,
|
|
1648
1880
|
indices: Map<RelationObject, number>,
|
|
1649
|
-
):
|
|
1650
|
-
return constraint.
|
|
1651
|
-
if (
|
|
1652
|
-
|
|
1881
|
+
): BodyAction[] {
|
|
1882
|
+
return constraint.actions.map(action => {
|
|
1883
|
+
if ('offset' in action) return {offset: action.offset};
|
|
1884
|
+
const {pivot, angles} = action;
|
|
1885
|
+
if (isAxisSelection(pivot)) {
|
|
1886
|
+
const axis = this.rotationAxis(pivot);
|
|
1887
|
+
return axis.model
|
|
1653
1888
|
? {
|
|
1654
|
-
body: indices.get(
|
|
1655
|
-
axis: pivot.
|
|
1656
|
-
angle: (angles as number) * (
|
|
1889
|
+
body: indices.get(axis.model)!,
|
|
1890
|
+
axis: selectedAxis(axis, pivot.offset),
|
|
1891
|
+
angle: (angles as number) * (axis.direction ?? 1),
|
|
1657
1892
|
}
|
|
1658
1893
|
: {
|
|
1659
1894
|
local: axisRotation(
|
|
1660
|
-
pivot.
|
|
1661
|
-
(angles as number) * (
|
|
1895
|
+
selectedAxis(axis, pivot.offset),
|
|
1896
|
+
(angles as number) * (axis.direction ?? 1),
|
|
1662
1897
|
),
|
|
1663
1898
|
};
|
|
1664
1899
|
}
|
|
1900
|
+
if (pivot.kind === 'pivotPoint' && pivot.point.model)
|
|
1901
|
+
return {
|
|
1902
|
+
body: indices.get(pivot.point.model)!,
|
|
1903
|
+
point: pivot.point.transform.position,
|
|
1904
|
+
rotation: rotationAround(origin, angles as Vec3).quaternion,
|
|
1905
|
+
displacement: pivot.offset ?? origin,
|
|
1906
|
+
};
|
|
1665
1907
|
const frame = translation(
|
|
1666
|
-
|
|
1908
|
+
addVectors(this.rotationPoint(pivot), pivot.offset ?? origin),
|
|
1667
1909
|
);
|
|
1668
1910
|
return {
|
|
1669
1911
|
local: composeTransforms(
|
|
@@ -1675,84 +1917,167 @@ export abstract class RelationObject {
|
|
|
1675
1917
|
}
|
|
1676
1918
|
|
|
1677
1919
|
/** @internal */
|
|
1678
|
-
[
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
//
|
|
1920
|
+
[previewRelation](
|
|
1921
|
+
constraint: StoredPlacement | undefined,
|
|
1922
|
+
selection: TransformationSpatialSelection | undefined,
|
|
1923
|
+
preceding: readonly RelationExpression[] = [],
|
|
1924
|
+
original?: RelationObject,
|
|
1925
|
+
): RelationPreview {
|
|
1926
|
+
// Keep inherited and sibling relations in the solve. The selected step
|
|
1927
|
+
// limits the placement prefix; geometry and node identity stay shared.
|
|
1928
|
+
let placements = [...(original ?? this).placements];
|
|
1929
|
+
const index = placements.findIndex(value => value.id === constraint?.id);
|
|
1930
|
+
if (index < 0) {
|
|
1931
|
+
placements.push(
|
|
1932
|
+
...preceding.map(value =>
|
|
1933
|
+
value.storeFor(this, value.continuationArguments()[1]!.original),
|
|
1934
|
+
),
|
|
1935
|
+
);
|
|
1936
|
+
if (constraint) placements.push(constraint);
|
|
1937
|
+
} else if (constraint) {
|
|
1938
|
+
placements[index] = constraint;
|
|
1939
|
+
let end = index + 1;
|
|
1940
|
+
if (constraint.kind !== 'transformation')
|
|
1941
|
+
while (
|
|
1942
|
+
end < placements.length &&
|
|
1943
|
+
placements[end].kind !== 'transformation'
|
|
1944
|
+
)
|
|
1945
|
+
end++;
|
|
1946
|
+
placements = placements.slice(0, end);
|
|
1947
|
+
}
|
|
1685
1948
|
const owner = this.copyRelations({
|
|
1686
|
-
nodeId,
|
|
1687
|
-
|
|
1949
|
+
nodeId: this.nodeId,
|
|
1950
|
+
placements,
|
|
1688
1951
|
});
|
|
1689
1952
|
const context = RelationObject.createSolveContext([
|
|
1690
1953
|
owner,
|
|
1691
|
-
...(selection
|
|
1692
|
-
? [selection.pivot
|
|
1954
|
+
...(selection && selectionReference(selection.pivot)?.model
|
|
1955
|
+
? [selectionReference(selection.pivot)!.model!]
|
|
1693
1956
|
: []),
|
|
1694
1957
|
]);
|
|
1695
1958
|
return {
|
|
1696
1959
|
object: {
|
|
1697
|
-
nodeId,
|
|
1960
|
+
nodeId: this.nodeId,
|
|
1698
1961
|
compositionTransform: toTransform(owner.solvePose(context)),
|
|
1699
1962
|
constraints: owner.constraints.map(value =>
|
|
1700
1963
|
owner.constraintSnapshot(value, context),
|
|
1701
1964
|
),
|
|
1965
|
+
transformations: owner.transformationSnapshots(context),
|
|
1966
|
+
relationStages: owner.relationStageSnapshots(context),
|
|
1702
1967
|
},
|
|
1703
|
-
spatial:
|
|
1704
|
-
|
|
1705
|
-
|
|
1968
|
+
spatial:
|
|
1969
|
+
selection && constraint?.kind === 'transformation'
|
|
1970
|
+
? owner.relationSpatial(constraint, selection, context)
|
|
1971
|
+
: constraint?.kind === 'transformation' &&
|
|
1972
|
+
constraint.actions.at(-1) &&
|
|
1973
|
+
'offset' in constraint.actions.at(-1)!
|
|
1974
|
+
? owner.transformationOffsetSpatial(constraint, context)
|
|
1975
|
+
: undefined,
|
|
1706
1976
|
};
|
|
1707
1977
|
}
|
|
1708
1978
|
|
|
1709
|
-
private
|
|
1710
|
-
constraint:
|
|
1711
|
-
selection:
|
|
1979
|
+
private relationSpatial(
|
|
1980
|
+
constraint: StoredTransformation,
|
|
1981
|
+
selection: TransformationSpatialSelection,
|
|
1712
1982
|
context: SolveContext,
|
|
1713
|
-
|
|
1983
|
+
actionIndex?: number,
|
|
1984
|
+
): RelationSpatialReference {
|
|
1985
|
+
context = this.placementStageContext(constraint, context);
|
|
1714
1986
|
const models = [...context.poses.keys()];
|
|
1715
|
-
const actions = this.
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1987
|
+
const actions = this.placementActions(constraint, context);
|
|
1988
|
+
const stage =
|
|
1989
|
+
actionIndex ??
|
|
1990
|
+
(selection.kind === 'rotate'
|
|
1991
|
+
? constraint.actions.length -
|
|
1992
|
+
1 -
|
|
1993
|
+
[...constraint.actions]
|
|
1994
|
+
.reverse()
|
|
1995
|
+
.findIndex(action => 'angles' in action)
|
|
1996
|
+
: actions.length);
|
|
1720
1997
|
const finalPose = this.solvePose(context);
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
-action.angle,
|
|
1734
|
-
),
|
|
1735
|
-
before,
|
|
1736
|
-
);
|
|
1737
|
-
}
|
|
1998
|
+
const following = this.followingActions(constraint, context);
|
|
1999
|
+
const before = beforeTransformations(
|
|
2000
|
+
beforeTransformations(
|
|
2001
|
+
finalPose,
|
|
2002
|
+
{actions: following},
|
|
2003
|
+
models.map(model => model.solvePose(context)),
|
|
2004
|
+
),
|
|
2005
|
+
{
|
|
2006
|
+
actions: actions.slice(stage),
|
|
2007
|
+
},
|
|
2008
|
+
models.map(model => model.solvePose(context)),
|
|
2009
|
+
);
|
|
1738
2010
|
const pivot = selection.pivot;
|
|
2011
|
+
const axis = isAxisSelection(pivot) ? this.rotationAxis(pivot) : undefined;
|
|
1739
2012
|
let frame: RigidTransform;
|
|
1740
|
-
if (
|
|
2013
|
+
if (axis) {
|
|
1741
2014
|
frame = composeTransforms(
|
|
1742
|
-
|
|
1743
|
-
(
|
|
1744
|
-
? pivot.
|
|
1745
|
-
: composeTransforms(
|
|
2015
|
+
axis.model ? axis.model.solvePose(context) : before,
|
|
2016
|
+
(axis.direction ?? 1) === 1
|
|
2017
|
+
? selectedAxis(axis, pivot.offset)
|
|
2018
|
+
: composeTransforms(
|
|
2019
|
+
selectedAxis(axis, pivot.offset),
|
|
2020
|
+
rotation([1, 0, 0, 0]),
|
|
2021
|
+
),
|
|
1746
2022
|
);
|
|
2023
|
+
} else if (pivot.kind === 'pivotPoint' && pivot.point.model) {
|
|
2024
|
+
const center = composeTransforms(
|
|
2025
|
+
pivot.point.model.solvePose(context),
|
|
2026
|
+
pivot.point.transform,
|
|
2027
|
+
).position;
|
|
2028
|
+
frame = {
|
|
2029
|
+
position: addVectors(
|
|
2030
|
+
center,
|
|
2031
|
+
rotateVector(pivot.offset ?? origin, before.quaternion),
|
|
2032
|
+
),
|
|
2033
|
+
quaternion: before.quaternion,
|
|
2034
|
+
};
|
|
1747
2035
|
} else {
|
|
1748
2036
|
const local = translation(
|
|
1749
|
-
|
|
2037
|
+
addVectors(
|
|
2038
|
+
this.rotationPoint(pivot as PointSelection),
|
|
2039
|
+
pivot.offset ?? origin,
|
|
2040
|
+
),
|
|
1750
2041
|
);
|
|
1751
2042
|
frame = composeTransforms(before, local);
|
|
1752
2043
|
}
|
|
1753
|
-
const
|
|
2044
|
+
const selected = constraint.actions[stage];
|
|
2045
|
+
const angles = selected && 'angles' in selected ? selected.angles : origin;
|
|
1754
2046
|
const rotationVector: Vec3 =
|
|
1755
2047
|
typeof angles === 'number' ? [0, angles, 0] : angles;
|
|
2048
|
+
// Later world-space actions move the earlier rotation's effective frame.
|
|
2049
|
+
// Later self-local actions already follow that rotation.
|
|
2050
|
+
const poses = models.map(model => model.solvePose(context));
|
|
2051
|
+
let actionPose = afterTransformations(
|
|
2052
|
+
before,
|
|
2053
|
+
{actions: actions.slice(stage, stage + 1)},
|
|
2054
|
+
poses,
|
|
2055
|
+
);
|
|
2056
|
+
const remainder = [...actions.slice(stage + 1), ...following];
|
|
2057
|
+
for (const action of remainder) {
|
|
2058
|
+
const next = afterTransformations(
|
|
2059
|
+
actionPose,
|
|
2060
|
+
{
|
|
2061
|
+
actions: [action],
|
|
2062
|
+
},
|
|
2063
|
+
poses,
|
|
2064
|
+
);
|
|
2065
|
+
if ('body' in action)
|
|
2066
|
+
frame = composeTransforms(
|
|
2067
|
+
composeTransforms(next, invertTransform(actionPose)),
|
|
2068
|
+
frame,
|
|
2069
|
+
);
|
|
2070
|
+
else if ('offset' in action) {
|
|
2071
|
+
frame = {
|
|
2072
|
+
...frame,
|
|
2073
|
+
position: addVectors(
|
|
2074
|
+
frame.position,
|
|
2075
|
+
addVectors(next.position, negateVector(actionPose.position)),
|
|
2076
|
+
),
|
|
2077
|
+
};
|
|
2078
|
+
}
|
|
2079
|
+
actionPose = next;
|
|
2080
|
+
}
|
|
1756
2081
|
const localFrame = relativeTransform(frame, finalPose);
|
|
1757
2082
|
return {
|
|
1758
2083
|
nodeId: this.nodeId,
|
|
@@ -1762,30 +2087,289 @@ export abstract class RelationObject {
|
|
|
1762
2087
|
vector:
|
|
1763
2088
|
selection.kind === 'rotate'
|
|
1764
2089
|
? rotationVector
|
|
1765
|
-
:
|
|
1766
|
-
|
|
1767
|
-
|
|
2090
|
+
: selection.kind === 'pivotOffset' ||
|
|
2091
|
+
selection.kind === 'axisOffset'
|
|
2092
|
+
? (pivot.offset ?? origin)
|
|
2093
|
+
: pivot.kind === 'pivot'
|
|
2094
|
+
? pivot.point
|
|
2095
|
+
: origin,
|
|
1768
2096
|
frame: localFrame,
|
|
1769
2097
|
rotation: rotationVector,
|
|
1770
|
-
axisOnly: pivot
|
|
2098
|
+
axisOnly: isAxisSelection(pivot),
|
|
2099
|
+
reference: {
|
|
2100
|
+
...(pivot.kind === 'axisEdge' || pivot.kind === 'pivotVertex'
|
|
2101
|
+
? {kind: pivot.kind, id: pivot.id}
|
|
2102
|
+
: pivot.kind === 'axisLine' || pivot.kind === 'pivotPoint'
|
|
2103
|
+
? {
|
|
2104
|
+
kind: pivot.kind,
|
|
2105
|
+
nodeId:
|
|
2106
|
+
selectionReference(pivot)!.model?.nodeId ?? this.nodeId,
|
|
2107
|
+
name: selectionReference(pivot)!.name,
|
|
2108
|
+
}
|
|
2109
|
+
: {kind: pivot.kind, point: pivot.point}),
|
|
2110
|
+
offset: pivot.offset ?? origin,
|
|
2111
|
+
offsetExplicit: pivot.offset !== undefined,
|
|
2112
|
+
explicit: !pivot.implicit,
|
|
2113
|
+
rotation:
|
|
2114
|
+
axis && (axis.direction ?? 1) !== 1
|
|
2115
|
+
? [0, -rotationVector[1], 0]
|
|
2116
|
+
: rotationVector,
|
|
2117
|
+
frame:
|
|
2118
|
+
axis && (axis.direction ?? 1) !== 1
|
|
2119
|
+
? composeTransforms(localFrame, rotation([1, 0, 0, 0]))
|
|
2120
|
+
: localFrame,
|
|
2121
|
+
},
|
|
1771
2122
|
},
|
|
1772
2123
|
};
|
|
1773
2124
|
}
|
|
1774
2125
|
|
|
1775
|
-
|
|
2126
|
+
private placementActions(
|
|
2127
|
+
value: StoredTransformation,
|
|
2128
|
+
context: SolveContext,
|
|
2129
|
+
): BodyAction[] {
|
|
2130
|
+
const indices = new Map(
|
|
2131
|
+
[...context.poses.keys()].map((model, i) => [model, i]),
|
|
2132
|
+
);
|
|
2133
|
+
return this.bodyActions(value, indices).map(action =>
|
|
2134
|
+
'offset' in action
|
|
2135
|
+
? {offset: rotateVector(action.offset, context.frame.quaternion)}
|
|
2136
|
+
: action,
|
|
2137
|
+
);
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
/** Pose at the end of this segment; later constraints have their own input pose. */
|
|
2141
|
+
private placementStageContext(
|
|
2142
|
+
placement: StoredPlacement,
|
|
2143
|
+
context: SolveContext,
|
|
2144
|
+
): SolveContext {
|
|
2145
|
+
const index = this.placements.indexOf(placement);
|
|
2146
|
+
let end = index + 1;
|
|
2147
|
+
if (placement.kind !== 'transformation')
|
|
2148
|
+
while (
|
|
2149
|
+
end < this.placements.length &&
|
|
2150
|
+
this.placements[end].kind !== 'transformation'
|
|
2151
|
+
)
|
|
2152
|
+
end++;
|
|
2153
|
+
while (
|
|
2154
|
+
end < this.placements.length &&
|
|
2155
|
+
this.placements[end].kind === 'transformation'
|
|
2156
|
+
)
|
|
2157
|
+
end++;
|
|
2158
|
+
if (end >= this.placements.length) return context;
|
|
2159
|
+
return transformSolveContext(
|
|
2160
|
+
RelationObject.createSolveContext(
|
|
2161
|
+
[this],
|
|
2162
|
+
new Map([[this, this.placements.slice(0, end)]]),
|
|
2163
|
+
),
|
|
2164
|
+
context.frame,
|
|
2165
|
+
);
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
private followingActions(
|
|
2169
|
+
placement: StoredPlacement,
|
|
2170
|
+
context: SolveContext,
|
|
2171
|
+
): BodyAction[] {
|
|
2172
|
+
let start = this.placements.indexOf(placement) + 1;
|
|
2173
|
+
if (placement.kind !== 'transformation')
|
|
2174
|
+
while (
|
|
2175
|
+
start < this.placements.length &&
|
|
2176
|
+
this.placements[start].kind !== 'transformation'
|
|
2177
|
+
)
|
|
2178
|
+
start++;
|
|
2179
|
+
const values: StoredTransformation[] = [];
|
|
2180
|
+
while (
|
|
2181
|
+
start < this.placements.length &&
|
|
2182
|
+
this.placements[start].kind === 'transformation'
|
|
2183
|
+
)
|
|
2184
|
+
values.push(this.placements[start++] as StoredTransformation);
|
|
2185
|
+
return values.flatMap(value => this.placementActions(value, context));
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
protected relationStageSnapshots(
|
|
2189
|
+
context: SolveContext,
|
|
2190
|
+
): readonly RelationStageSnapshot[] | undefined {
|
|
2191
|
+
if (!this.placements.length) return undefined;
|
|
2192
|
+
const stages: RelationStageSnapshot[] = [];
|
|
2193
|
+
let start = 0;
|
|
2194
|
+
while (start < this.placements.length) {
|
|
2195
|
+
let end = start;
|
|
2196
|
+
while (
|
|
2197
|
+
end < this.placements.length &&
|
|
2198
|
+
this.placements[end].kind !== 'transformation'
|
|
2199
|
+
)
|
|
2200
|
+
end++;
|
|
2201
|
+
while (
|
|
2202
|
+
end < this.placements.length &&
|
|
2203
|
+
this.placements[end].kind === 'transformation'
|
|
2204
|
+
)
|
|
2205
|
+
end++;
|
|
2206
|
+
const values = this.placements.slice(start, end);
|
|
2207
|
+
const stage = this.placementStageContext(values[0], context);
|
|
2208
|
+
stages.push({
|
|
2209
|
+
constraintIds: values
|
|
2210
|
+
.filter(value => value.kind !== 'transformation')
|
|
2211
|
+
.map(value => value.id),
|
|
2212
|
+
transformationIds: values
|
|
2213
|
+
.filter(value => value.kind === 'transformation')
|
|
2214
|
+
.map(value => value.id),
|
|
2215
|
+
compositionTransform: toTransform(this.solvePose(stage)),
|
|
2216
|
+
offsetFrame: toTransform(stage.frame),
|
|
2217
|
+
});
|
|
2218
|
+
start = end;
|
|
2219
|
+
}
|
|
2220
|
+
return stages;
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
protected transformationSnapshots(
|
|
2224
|
+
context: SolveContext,
|
|
2225
|
+
): readonly TransformationSnapshot[] | undefined {
|
|
2226
|
+
const values = this.placements.filter(
|
|
2227
|
+
(value): value is StoredTransformation => value.kind === 'transformation',
|
|
2228
|
+
);
|
|
2229
|
+
if (!values.length) return undefined;
|
|
2230
|
+
return values.map(value =>
|
|
2231
|
+
this.transformationSnapshot(
|
|
2232
|
+
value,
|
|
2233
|
+
this.placementStageContext(value, context),
|
|
2234
|
+
),
|
|
2235
|
+
);
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
private followingReferenceFrame(
|
|
2239
|
+
frame: RigidTransform,
|
|
2240
|
+
actions: readonly BodyAction[],
|
|
2241
|
+
context: SolveContext,
|
|
2242
|
+
): RigidTransform {
|
|
2243
|
+
const models = [...context.poses.keys()];
|
|
2244
|
+
const poses = models.map(model => model.solvePose(context));
|
|
2245
|
+
let pose = beforeTransformations(this.solvePose(context), {actions}, poses);
|
|
2246
|
+
for (const action of actions) {
|
|
2247
|
+
const next = afterTransformations(pose, {actions: [action]}, poses);
|
|
2248
|
+
if ('body' in action)
|
|
2249
|
+
frame = composeTransforms(
|
|
2250
|
+
composeTransforms(next, invertTransform(pose)),
|
|
2251
|
+
frame,
|
|
2252
|
+
);
|
|
2253
|
+
pose = next;
|
|
2254
|
+
}
|
|
2255
|
+
return frame;
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
private transformationSnapshot(
|
|
2259
|
+
value: StoredTransformation,
|
|
2260
|
+
context: SolveContext,
|
|
2261
|
+
): TransformationSnapshot {
|
|
2262
|
+
const models = [...context.poses.keys()];
|
|
2263
|
+
const actions = this.placementActions(value, context);
|
|
2264
|
+
const following = this.followingActions(value, context);
|
|
2265
|
+
const offsets = value.actions.flatMap((action, index) => {
|
|
2266
|
+
if (!('offset' in action)) return [];
|
|
2267
|
+
const frame = this.followingReferenceFrame(
|
|
2268
|
+
context.frame,
|
|
2269
|
+
[...actions.slice(index + 1), ...following],
|
|
2270
|
+
context,
|
|
2271
|
+
);
|
|
2272
|
+
return [
|
|
2273
|
+
{
|
|
2274
|
+
value: action.offset,
|
|
2275
|
+
frame: toTransform(frame),
|
|
2276
|
+
sourceRefs: valueTrace(action).sourceRefs,
|
|
2277
|
+
},
|
|
2278
|
+
];
|
|
2279
|
+
});
|
|
2280
|
+
const rotations = value.actions.flatMap((action, index) =>
|
|
2281
|
+
'pivot' in action
|
|
2282
|
+
? [
|
|
2283
|
+
{
|
|
2284
|
+
spatial: this.relationSpatial(
|
|
2285
|
+
value,
|
|
2286
|
+
{kind: 'rotate', pivot: action.pivot},
|
|
2287
|
+
context,
|
|
2288
|
+
index,
|
|
2289
|
+
).spatial,
|
|
2290
|
+
sourceRefs: valueTrace(action).sourceRefs,
|
|
2291
|
+
},
|
|
2292
|
+
]
|
|
2293
|
+
: [],
|
|
2294
|
+
);
|
|
2295
|
+
return {
|
|
2296
|
+
id: value.id,
|
|
2297
|
+
offsets,
|
|
2298
|
+
rotations,
|
|
2299
|
+
offsetFrame: toTransform(context.frame),
|
|
2300
|
+
sourceRefs: [...valueTrace(value).sourceRefs],
|
|
2301
|
+
parameters: [...valueTrace(value).parameters],
|
|
2302
|
+
};
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
private transformationOffsetSpatial(
|
|
2306
|
+
value: StoredTransformation,
|
|
2307
|
+
context: SolveContext,
|
|
2308
|
+
): RelationSpatialReference {
|
|
2309
|
+
const offset = this.transformationSnapshot(value, context).offsets.at(-1)!;
|
|
2310
|
+
const local = relativeTransform(
|
|
2311
|
+
{...offset.frame, position: this.solvePose(context).position},
|
|
2312
|
+
this.solvePose(context),
|
|
2313
|
+
);
|
|
2314
|
+
return {
|
|
2315
|
+
nodeId: this.nodeId,
|
|
2316
|
+
kind: 'offset',
|
|
2317
|
+
spatial: {origin, vector: offset.value, frame: local},
|
|
2318
|
+
};
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
static createSolveContext(
|
|
2322
|
+
roots: readonly RelationObject[],
|
|
2323
|
+
overrides = new Map<RelationObject, readonly StoredPlacement[]>(),
|
|
2324
|
+
): SolveContext {
|
|
1776
2325
|
const models: RelationObject[] = [];
|
|
1777
2326
|
const indices = new Map<RelationObject, number>();
|
|
1778
2327
|
const collect = (model: RelationObject): void => {
|
|
1779
2328
|
if (indices.has(model)) return;
|
|
1780
2329
|
indices.set(model, models.length);
|
|
1781
2330
|
models.push(model);
|
|
1782
|
-
|
|
2331
|
+
(overrides.get(model) ?? model.placements)
|
|
2332
|
+
.flatMap(constraintReferences)
|
|
2333
|
+
.forEach(collect);
|
|
1783
2334
|
};
|
|
1784
2335
|
roots.forEach(collect);
|
|
2336
|
+
const programs = models.map(model => {
|
|
2337
|
+
const placements = overrides.get(model) ?? model.placements;
|
|
2338
|
+
let last = placements.length - 1;
|
|
2339
|
+
while (last >= 0 && placements[last].kind === 'transformation') last--;
|
|
2340
|
+
let start = last;
|
|
2341
|
+
while (start > 0 && placements[start - 1].kind !== 'transformation')
|
|
2342
|
+
start--;
|
|
2343
|
+
const initial =
|
|
2344
|
+
start > 0
|
|
2345
|
+
? RelationObject.createSolveContext(
|
|
2346
|
+
[model],
|
|
2347
|
+
new Map(overrides).set(model, placements.slice(0, start)),
|
|
2348
|
+
).poses.get(model)!
|
|
2349
|
+
: identityRigidTransform;
|
|
2350
|
+
return {
|
|
2351
|
+
initial,
|
|
2352
|
+
constraints: placements
|
|
2353
|
+
.slice(Math.max(0, start), last + 1)
|
|
2354
|
+
.filter(
|
|
2355
|
+
(value): value is StoredConstraint =>
|
|
2356
|
+
value.kind !== 'transformation',
|
|
2357
|
+
),
|
|
2358
|
+
transformations: placements
|
|
2359
|
+
.slice(last + 1)
|
|
2360
|
+
.filter(
|
|
2361
|
+
(value): value is StoredTransformation =>
|
|
2362
|
+
value.kind === 'transformation',
|
|
2363
|
+
)
|
|
2364
|
+
.flatMap(value => model.bodyActions(value, indices)),
|
|
2365
|
+
};
|
|
2366
|
+
});
|
|
1785
2367
|
const poses = solveBodies(
|
|
1786
|
-
models.map((model): Body => ({
|
|
2368
|
+
models.map((model, index): Body => ({
|
|
2369
|
+
initial: programs[index].initial,
|
|
2370
|
+
transformations: programs[index].transformations,
|
|
1787
2371
|
name: model.name,
|
|
1788
|
-
relations:
|
|
2372
|
+
relations: programs[index].constraints.map(constraint =>
|
|
1789
2373
|
constraint.kind === 'align'
|
|
1790
2374
|
? {
|
|
1791
2375
|
kind: 'align',
|
|
@@ -1804,8 +2388,6 @@ export abstract class RelationObject {
|
|
|
1804
2388
|
).alignmentGeometry(constraint.target),
|
|
1805
2389
|
transform: constraint.target.transform,
|
|
1806
2390
|
},
|
|
1807
|
-
offset: constraint.offset,
|
|
1808
|
-
rotations: model.bodyRotations(constraint, indices),
|
|
1809
2391
|
}
|
|
1810
2392
|
: {
|
|
1811
2393
|
kind: 'on',
|
|
@@ -1824,14 +2406,13 @@ export abstract class RelationObject {
|
|
|
1824
2406
|
transform: constraint.target.transform,
|
|
1825
2407
|
facing: constraint.target.bound!.facing,
|
|
1826
2408
|
},
|
|
1827
|
-
offset: constraint.offset,
|
|
1828
|
-
rotations: model.bodyRotations(constraint, indices),
|
|
1829
2409
|
},
|
|
1830
2410
|
),
|
|
1831
2411
|
})),
|
|
1832
2412
|
);
|
|
1833
2413
|
return {
|
|
1834
2414
|
poses: new Map(models.map((model, index) => [model, poses[index]])),
|
|
2415
|
+
frame: identityRigidTransform,
|
|
1835
2416
|
};
|
|
1836
2417
|
}
|
|
1837
2418
|
|
|
@@ -1839,56 +2420,14 @@ export abstract class RelationObject {
|
|
|
1839
2420
|
constraint: StoredConstraint,
|
|
1840
2421
|
context: SolveContext,
|
|
1841
2422
|
): ConstraintSnapshot {
|
|
1842
|
-
const lastRotation = constraint.rotations.at(-1);
|
|
1843
|
-
const rotationSpatial = lastRotation
|
|
1844
|
-
? this.constraintSpatial(
|
|
1845
|
-
constraint,
|
|
1846
|
-
{kind: 'rotate', pivot: lastRotation.pivot},
|
|
1847
|
-
context,
|
|
1848
|
-
).spatial
|
|
1849
|
-
: undefined;
|
|
1850
2423
|
const source = constraint.source.model ?? this;
|
|
1851
2424
|
const target = constraint.target.model ?? this;
|
|
1852
|
-
const
|
|
1853
|
-
const
|
|
1854
|
-
|
|
1855
|
-
new Map(models.map((model, i) => [model, i])),
|
|
1856
|
-
);
|
|
1857
|
-
let before = this.solvePose(context);
|
|
1858
|
-
for (const action of [...actions].reverse()) {
|
|
1859
|
-
before =
|
|
1860
|
-
'local' in action
|
|
1861
|
-
? composeTransforms(before, invertTransform(action.local))
|
|
1862
|
-
: composeTransforms(
|
|
1863
|
-
axisRotation(
|
|
1864
|
-
composeTransforms(
|
|
1865
|
-
models[action.body].solvePose(context),
|
|
1866
|
-
action.axis,
|
|
1867
|
-
),
|
|
1868
|
-
-action.angle,
|
|
1869
|
-
),
|
|
1870
|
-
before,
|
|
1871
|
-
);
|
|
1872
|
-
}
|
|
1873
|
-
const sourcePose = source === this ? before : source.solvePose(context);
|
|
1874
|
-
const targetPose = target === this ? before : target.solvePose(context);
|
|
1875
|
-
let offsetFrame = composeTransforms(
|
|
1876
|
-
targetPose,
|
|
2425
|
+
const sourcePose = source.solvePose(context);
|
|
2426
|
+
const contactFrame = composeTransforms(
|
|
2427
|
+
target.solvePose(context),
|
|
1877
2428
|
constraint.target.transform,
|
|
1878
2429
|
);
|
|
1879
2430
|
if (constraint.kind === 'align') {
|
|
1880
|
-
for (const action of actions)
|
|
1881
|
-
if ('body' in action)
|
|
1882
|
-
offsetFrame = composeTransforms(
|
|
1883
|
-
axisRotation(
|
|
1884
|
-
composeTransforms(
|
|
1885
|
-
models[action.body].solvePose(context),
|
|
1886
|
-
action.axis,
|
|
1887
|
-
),
|
|
1888
|
-
action.angle,
|
|
1889
|
-
),
|
|
1890
|
-
offsetFrame,
|
|
1891
|
-
);
|
|
1892
2431
|
return {
|
|
1893
2432
|
id: constraint.id,
|
|
1894
2433
|
kind: 'align',
|
|
@@ -1896,16 +2435,12 @@ export abstract class RelationObject {
|
|
|
1896
2435
|
target: anchorSnapshot(target, constraint.target),
|
|
1897
2436
|
sourceElement: source.relationElement(constraint.source),
|
|
1898
2437
|
targetElement: target.relationElement(constraint.target),
|
|
1899
|
-
offsetDirection: 1,
|
|
1900
|
-
offset: constraint.offset ?? origin,
|
|
1901
|
-
offsetFrame: toTransform(offsetFrame),
|
|
1902
|
-
rotation: rotationSpatial,
|
|
1903
2438
|
sourceRefs: [...valueTrace(constraint).sourceRefs],
|
|
1904
2439
|
parameters: [...valueTrace(constraint).parameters],
|
|
1905
2440
|
};
|
|
1906
2441
|
}
|
|
1907
2442
|
const orientation = composeTransforms(
|
|
1908
|
-
invertTransform(rotation(
|
|
2443
|
+
invertTransform(rotation(contactFrame.quaternion)),
|
|
1909
2444
|
rotation(sourcePose.quaternion),
|
|
1910
2445
|
).quaternion;
|
|
1911
2446
|
const bounds = source[referenceBounds](
|
|
@@ -1921,9 +2456,9 @@ export abstract class RelationObject {
|
|
|
1921
2456
|
{
|
|
1922
2457
|
position: addVectors(
|
|
1923
2458
|
sourcePose.position,
|
|
1924
|
-
rotateVector(center,
|
|
2459
|
+
rotateVector(center, contactFrame.quaternion),
|
|
1925
2460
|
),
|
|
1926
|
-
quaternion:
|
|
2461
|
+
quaternion: contactFrame.quaternion,
|
|
1927
2462
|
},
|
|
1928
2463
|
sourcePose,
|
|
1929
2464
|
);
|
|
@@ -1932,19 +2467,6 @@ export abstract class RelationObject {
|
|
|
1932
2467
|
bounds[1][1] - bounds[0][1],
|
|
1933
2468
|
bounds[1][2] - bounds[0][2],
|
|
1934
2469
|
];
|
|
1935
|
-
for (const action of actions) {
|
|
1936
|
-
if ('body' in action)
|
|
1937
|
-
offsetFrame = composeTransforms(
|
|
1938
|
-
axisRotation(
|
|
1939
|
-
composeTransforms(
|
|
1940
|
-
models[action.body].solvePose(context),
|
|
1941
|
-
action.axis,
|
|
1942
|
-
),
|
|
1943
|
-
action.angle,
|
|
1944
|
-
),
|
|
1945
|
-
offsetFrame,
|
|
1946
|
-
);
|
|
1947
|
-
}
|
|
1948
2470
|
return {
|
|
1949
2471
|
id: constraint.id,
|
|
1950
2472
|
kind: constraint.kind,
|
|
@@ -1978,10 +2500,6 @@ export abstract class RelationObject {
|
|
|
1978
2500
|
transform: toTransform(constraint.target.transform),
|
|
1979
2501
|
bound: constraint.target.bound,
|
|
1980
2502
|
},
|
|
1981
|
-
offsetDirection: source === this ? 1 : -1,
|
|
1982
|
-
offset: constraint.offset ?? origin,
|
|
1983
|
-
offsetFrame: toTransform(offsetFrame),
|
|
1984
|
-
rotation: rotationSpatial,
|
|
1985
2503
|
sourceRefs: [...valueTrace(constraint).sourceRefs],
|
|
1986
2504
|
parameters: [...valueTrace(constraint).parameters],
|
|
1987
2505
|
};
|
|
@@ -1997,7 +2515,7 @@ export class SketchFrame extends RelationObject {
|
|
|
1997
2515
|
|
|
1998
2516
|
constructor(source?: SketchFrame, init: RelationObjectInit = {}) {
|
|
1999
2517
|
super({
|
|
2000
|
-
|
|
2518
|
+
placements: source?.placements,
|
|
2001
2519
|
sourceRefs: source?.sourceRefs,
|
|
2002
2520
|
parameters: source?.parameters,
|
|
2003
2521
|
...init,
|
|
@@ -2016,7 +2534,7 @@ export class SketchFrame extends RelationObject {
|
|
|
2016
2534
|
relatedObjects(): readonly RelationObject[] {
|
|
2017
2535
|
return [
|
|
2018
2536
|
...(this.source ? [this.source] : []),
|
|
2019
|
-
...this.
|
|
2537
|
+
...this.placements.flatMap(constraintReferences),
|
|
2020
2538
|
];
|
|
2021
2539
|
}
|
|
2022
2540
|
|
|
@@ -2072,7 +2590,7 @@ export class SketchFrame extends RelationObject {
|
|
|
2072
2590
|
}
|
|
2073
2591
|
|
|
2074
2592
|
relate(
|
|
2075
|
-
build: (frame: SketchFrame) =>
|
|
2593
|
+
build: (frame: SketchFrame) => Relation | readonly Relation[],
|
|
2076
2594
|
): SketchFrame {
|
|
2077
2595
|
const related = new SketchFrame(this);
|
|
2078
2596
|
related.addRelations(this, () => build(related));
|
|
@@ -2080,10 +2598,10 @@ export class SketchFrame extends RelationObject {
|
|
|
2080
2598
|
}
|
|
2081
2599
|
|
|
2082
2600
|
face(region: SketchRegion): FaceModel {
|
|
2083
|
-
return sketchFaceModel(region, this.
|
|
2601
|
+
return sketchFaceModel(region, this.placements, this);
|
|
2084
2602
|
}
|
|
2085
2603
|
|
|
2086
|
-
snapshot():
|
|
2604
|
+
snapshot(): RelationPreview['object'] {
|
|
2087
2605
|
const context = RelationObject.createSolveContext([this]);
|
|
2088
2606
|
return {
|
|
2089
2607
|
nodeId: this.nodeId,
|
|
@@ -2091,6 +2609,8 @@ export class SketchFrame extends RelationObject {
|
|
|
2091
2609
|
constraints: this.constraints.map(constraint =>
|
|
2092
2610
|
this.constraintSnapshot(constraint, context),
|
|
2093
2611
|
),
|
|
2612
|
+
transformations: this.transformationSnapshots(context),
|
|
2613
|
+
relationStages: this.relationStageSnapshots(context),
|
|
2094
2614
|
};
|
|
2095
2615
|
}
|
|
2096
2616
|
|
|
@@ -2254,7 +2774,7 @@ export class ModelObject<
|
|
|
2254
2774
|
relate(
|
|
2255
2775
|
build: (
|
|
2256
2776
|
self: RuntimeModel<Elements, Kind>,
|
|
2257
|
-
) =>
|
|
2777
|
+
) => Relation | readonly Relation[],
|
|
2258
2778
|
): RuntimeModel<Elements, Kind> {
|
|
2259
2779
|
const operation = storedOperation('relate', [
|
|
2260
2780
|
{model: this, role: 'source', index: 0},
|
|
@@ -2518,7 +3038,7 @@ export class ModelObject<
|
|
|
2518
3038
|
transformElement(element, transform),
|
|
2519
3039
|
]),
|
|
2520
3040
|
),
|
|
2521
|
-
|
|
3041
|
+
placements: this.mapConstraintGeometry(
|
|
2522
3042
|
element => transformElement(element, transform),
|
|
2523
3043
|
point => composeTransforms(transform, translation(point)).position,
|
|
2524
3044
|
),
|
|
@@ -2556,23 +3076,52 @@ export class ModelObject<
|
|
|
2556
3076
|
private mapConstraintGeometry(
|
|
2557
3077
|
mapElement: (element: StoredElement) => StoredElement,
|
|
2558
3078
|
mapPoint: (point: Vec3) => Vec3,
|
|
2559
|
-
):
|
|
3079
|
+
): StoredPlacement[] {
|
|
2560
3080
|
const mapReference = (reference: RelationReference): RelationReference =>
|
|
2561
3081
|
reference.model ? reference : {...reference, ...mapElement(reference)};
|
|
2562
|
-
|
|
2563
|
-
|
|
3082
|
+
const zero = mapPoint(origin);
|
|
3083
|
+
const mapVector = (vector: Vec3): Vec3 =>
|
|
3084
|
+
mapPoint(vector).map(
|
|
3085
|
+
(value, index) => value - zero[index],
|
|
3086
|
+
) as unknown as Vec3;
|
|
3087
|
+
const scale = Math.hypot(...mapVector([1, 0, 0]));
|
|
3088
|
+
return this.placements.map(constraint => {
|
|
3089
|
+
const mapped: StoredPlacement = {
|
|
2564
3090
|
...constraint,
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
3091
|
+
...(constraint.kind === 'transformation'
|
|
3092
|
+
? {}
|
|
3093
|
+
: {
|
|
3094
|
+
source: mapReference(constraint.source),
|
|
3095
|
+
target: mapReference(constraint.target),
|
|
3096
|
+
}),
|
|
3097
|
+
...(constraint.kind === 'transformation'
|
|
3098
|
+
? {
|
|
3099
|
+
actions: constraint.actions.map(action => {
|
|
3100
|
+
if ('offset' in action) return action;
|
|
3101
|
+
const mappedAction = {
|
|
3102
|
+
...action,
|
|
3103
|
+
pivot:
|
|
3104
|
+
action.pivot.kind === 'pivot'
|
|
3105
|
+
? {...action.pivot, point: mapPoint(action.pivot.point)}
|
|
3106
|
+
: mapSelectionReference(action.pivot, mapReference),
|
|
3107
|
+
};
|
|
3108
|
+
if (action.pivot.offset) {
|
|
3109
|
+
mappedAction.pivot = {
|
|
3110
|
+
...mappedAction.pivot,
|
|
3111
|
+
offset: isAxisSelection(action.pivot)
|
|
3112
|
+
? selectionReference(action.pivot)?.model
|
|
3113
|
+
? action.pivot.offset
|
|
3114
|
+
: (action.pivot.offset.map(
|
|
3115
|
+
value => value * scale,
|
|
3116
|
+
) as unknown as Vec3)
|
|
3117
|
+
: mapVector(action.pivot.offset),
|
|
3118
|
+
};
|
|
3119
|
+
}
|
|
3120
|
+
valueTraces.set(mappedAction, valueTrace(action));
|
|
3121
|
+
return mappedAction;
|
|
3122
|
+
}),
|
|
3123
|
+
}
|
|
3124
|
+
: {}),
|
|
2576
3125
|
};
|
|
2577
3126
|
valueTraces.set(mapped, valueTrace(constraint));
|
|
2578
3127
|
return mapped;
|
|
@@ -2605,7 +3154,7 @@ export class ModelObject<
|
|
|
2605
3154
|
{
|
|
2606
3155
|
geometryAnchor: scaleElement(this.geometryAnchor, factor),
|
|
2607
3156
|
elements: scaleElements(this.elements, factor),
|
|
2608
|
-
|
|
3157
|
+
placements: this.mapConstraintGeometry(
|
|
2609
3158
|
element => scaleElement(element, factor),
|
|
2610
3159
|
point => scaleFrame(translation(point), factor).position,
|
|
2611
3160
|
),
|
|
@@ -2644,7 +3193,7 @@ export class ModelObject<
|
|
|
2644
3193
|
name: 'Extrude',
|
|
2645
3194
|
geometry,
|
|
2646
3195
|
material: this.materialSnapshot,
|
|
2647
|
-
|
|
3196
|
+
placements: this.placements,
|
|
2648
3197
|
sourceRefs: this.sourceRefs,
|
|
2649
3198
|
parameters: this.allParameters(),
|
|
2650
3199
|
meshTolerance: this.meshTolerance,
|
|
@@ -2834,7 +3383,7 @@ export class ModelObject<
|
|
|
2834
3383
|
return [
|
|
2835
3384
|
...this.children,
|
|
2836
3385
|
...this.operation.inputs.map(input => input.model),
|
|
2837
|
-
...this.
|
|
3386
|
+
...this.placements.flatMap(constraintReferences),
|
|
2838
3387
|
];
|
|
2839
3388
|
}
|
|
2840
3389
|
|
|
@@ -2859,7 +3408,7 @@ export class ModelObject<
|
|
|
2859
3408
|
);
|
|
2860
3409
|
const parameters = uniqueParameters([
|
|
2861
3410
|
...this.parameters,
|
|
2862
|
-
...this.
|
|
3411
|
+
...this.placements.flatMap(
|
|
2863
3412
|
constraint => valueTrace(constraint).parameters,
|
|
2864
3413
|
),
|
|
2865
3414
|
]);
|
|
@@ -2871,6 +3420,8 @@ export class ModelObject<
|
|
|
2871
3420
|
compositionTransform: toTransform(pose),
|
|
2872
3421
|
transform: toTransform(inComposition ? pose : identityRigidTransform),
|
|
2873
3422
|
constraints,
|
|
3423
|
+
transformations: this.transformationSnapshots(solveContext),
|
|
3424
|
+
relationStages: this.relationStageSnapshots(solveContext),
|
|
2874
3425
|
origin,
|
|
2875
3426
|
elements: snapshotElements({
|
|
2876
3427
|
...this.elements,
|
|
@@ -2982,7 +3533,7 @@ export class ModelObject<
|
|
|
2982
3533
|
name: 'Loft',
|
|
2983
3534
|
geometry,
|
|
2984
3535
|
material: this.materialSnapshot,
|
|
2985
|
-
|
|
3536
|
+
placements: this.placements,
|
|
2986
3537
|
sourceRefs: inputs.flatMap(input => input.sourceRefs),
|
|
2987
3538
|
parameters: uniqueParameters(
|
|
2988
3539
|
inputs.flatMap(input => input.allParameters()),
|
|
@@ -3017,7 +3568,7 @@ export class ModelObject<
|
|
|
3017
3568
|
geometry: evaluation.geometry,
|
|
3018
3569
|
name: this.name,
|
|
3019
3570
|
material: this.materialSnapshot,
|
|
3020
|
-
|
|
3571
|
+
placements: this.placements,
|
|
3021
3572
|
sourceRefs: [this, ...others].flatMap(model => model.sourceRefs),
|
|
3022
3573
|
parameters: uniqueParameters(
|
|
3023
3574
|
[this, ...others].flatMap(model => model.allParameters()),
|
|
@@ -3142,7 +3693,7 @@ export class ModelObject<
|
|
|
3142
3693
|
private allParameters(): ParameterUsage[] {
|
|
3143
3694
|
return uniqueParameters([
|
|
3144
3695
|
...this.parameters,
|
|
3145
|
-
...this.
|
|
3696
|
+
...this.placements.flatMap(
|
|
3146
3697
|
constraint => valueTrace(constraint).parameters,
|
|
3147
3698
|
),
|
|
3148
3699
|
]);
|
|
@@ -3304,6 +3855,10 @@ export class ModelObject<
|
|
|
3304
3855
|
);
|
|
3305
3856
|
}
|
|
3306
3857
|
|
|
3858
|
+
protected override edgeReference(id: EdgeId): RelationReference {
|
|
3859
|
+
return {...rotationAxisReference(this.edge(id)), model: undefined};
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3307
3862
|
protected override vertexPosition(id: VertexId): Vec3 {
|
|
3308
3863
|
return anchorReference(this.vertex(id)).transform.position;
|
|
3309
3864
|
}
|
|
@@ -3312,17 +3867,17 @@ export class ModelObject<
|
|
|
3312
3867
|
return this.copy(init, this.operation);
|
|
3313
3868
|
}
|
|
3314
3869
|
|
|
3315
|
-
/** Fix the assembly frame once
|
|
3870
|
+
/** Fix the assembly frame once in the first member's solved local coordinates. */
|
|
3316
3871
|
private static createAssembly(
|
|
3317
3872
|
children: readonly ModelObject[],
|
|
3318
3873
|
): SolveContext {
|
|
3319
3874
|
const context = ModelObject.createSolveContext(children);
|
|
3320
|
-
|
|
3321
|
-
?
|
|
3322
|
-
|
|
3875
|
+
return children.length
|
|
3876
|
+
? transformSolveContext(
|
|
3877
|
+
context,
|
|
3878
|
+
invertTransform(children[0].solvePose(context)),
|
|
3323
3879
|
)
|
|
3324
|
-
:
|
|
3325
|
-
return transformSolveContext(context, translation(negateVector(center)));
|
|
3880
|
+
: context;
|
|
3326
3881
|
}
|
|
3327
3882
|
|
|
3328
3883
|
private requireGeometry(): ModelGeometry {
|
|
@@ -3407,7 +3962,7 @@ export class ModelObject<
|
|
|
3407
3962
|
material: this.materialSnapshot,
|
|
3408
3963
|
children: this.children,
|
|
3409
3964
|
assembly: this.assembly,
|
|
3410
|
-
|
|
3965
|
+
placements: this.placements,
|
|
3411
3966
|
elements: this.elements,
|
|
3412
3967
|
sourceRefs: this.sourceRefs,
|
|
3413
3968
|
parameters: this.parameters,
|
|
@@ -3997,6 +4552,7 @@ function normalizeReplicadSolid(shape: Shape3D): Shape3D {
|
|
|
3997
4552
|
return solid;
|
|
3998
4553
|
}
|
|
3999
4554
|
|
|
4555
|
+
/** Compose members in the first member's local frame; empty groups use the default frame. */
|
|
4000
4556
|
export function group(children: readonly Model[], name = 'Group'): GroupModel {
|
|
4001
4557
|
const runtimeChildren = children.map(child =>
|
|
4002
4558
|
requireModelObject(child, 'Every group child must be a model.'),
|
|
@@ -4104,20 +4660,40 @@ export function isConstraint(value: unknown): value is Constraint {
|
|
|
4104
4660
|
return value instanceof Constraint;
|
|
4105
4661
|
}
|
|
4106
4662
|
|
|
4107
|
-
export function
|
|
4663
|
+
export function isRelationExpression(
|
|
4108
4664
|
value: unknown,
|
|
4109
|
-
): value is
|
|
4110
|
-
return value instanceof
|
|
4665
|
+
): value is RelationExpression {
|
|
4666
|
+
return value instanceof RelationExpression;
|
|
4667
|
+
}
|
|
4668
|
+
|
|
4669
|
+
export function relationPreview(
|
|
4670
|
+
value: RelationExpression,
|
|
4671
|
+
preceding: readonly RelationExpression[] = [],
|
|
4672
|
+
): RelationPreview | undefined {
|
|
4673
|
+
return value.preview(preceding);
|
|
4111
4674
|
}
|
|
4112
4675
|
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
return value.preview();
|
|
4676
|
+
/** The current authoring receiver remains available when a free selector throws. */
|
|
4677
|
+
export function currentRelationSelf(): RelationObject | undefined {
|
|
4678
|
+
return activeRelate?.self;
|
|
4117
4679
|
}
|
|
4118
4680
|
|
|
4119
|
-
|
|
4120
|
-
|
|
4681
|
+
/** Preview an insertion/selector prefix, retaining only placements inherited by its callback. */
|
|
4682
|
+
export function relationSelectionPreview(
|
|
4683
|
+
self: RelationObject,
|
|
4684
|
+
preceding: readonly RelationExpression[],
|
|
4685
|
+
context?: RelationExpression,
|
|
4686
|
+
): RelationPreview {
|
|
4687
|
+
return self[previewRelation](
|
|
4688
|
+
undefined,
|
|
4689
|
+
undefined,
|
|
4690
|
+
preceding,
|
|
4691
|
+
(preceding[0] ?? context)?.continuationArguments()[1]?.original,
|
|
4692
|
+
);
|
|
4693
|
+
}
|
|
4694
|
+
|
|
4695
|
+
export function instrumentRelation(
|
|
4696
|
+
constraint: RelationExpression,
|
|
4121
4697
|
sourceRef: SourceRef,
|
|
4122
4698
|
parameters: readonly ParameterUsage[],
|
|
4123
4699
|
): void {
|
|
@@ -4140,9 +4716,9 @@ export function instrumentModelOperation(
|
|
|
4140
4716
|
);
|
|
4141
4717
|
}
|
|
4142
4718
|
|
|
4143
|
-
export function
|
|
4144
|
-
constraint:
|
|
4145
|
-
):
|
|
4719
|
+
export function relationTraceReference(
|
|
4720
|
+
constraint: RelationExpression,
|
|
4721
|
+
): RelationTraceReference {
|
|
4146
4722
|
return constraint.traceReference();
|
|
4147
4723
|
}
|
|
4148
4724
|
|
|
@@ -4275,7 +4851,11 @@ export type SnapshotQueryBatch = Readonly<{
|
|
|
4275
4851
|
weight: number;
|
|
4276
4852
|
sourceRef?: SourceRef;
|
|
4277
4853
|
encode(): Uint8Array;
|
|
4278
|
-
accept(
|
|
4854
|
+
accept(
|
|
4855
|
+
query: SnapshotQuery,
|
|
4856
|
+
value: SnapshotQueryResult,
|
|
4857
|
+
milliseconds: number,
|
|
4858
|
+
): void;
|
|
4279
4859
|
}>;
|
|
4280
4860
|
|
|
4281
4861
|
export function planModelSnapshotQueries(
|
|
@@ -4331,7 +4911,8 @@ export function planModelSnapshotQueries(
|
|
|
4331
4911
|
(input.geometry.topology?.surfaces.ids.length ?? 0)) *
|
|
4332
4912
|
queries.length,
|
|
4333
4913
|
encode: () => encodeKernelArtifact(id, input.geometry),
|
|
4334
|
-
accept: (query, value) =>
|
|
4914
|
+
accept: (query, value, milliseconds) =>
|
|
4915
|
+
snapshotQuery.accept(query.key, value, milliseconds),
|
|
4335
4916
|
}));
|
|
4336
4917
|
}
|
|
4337
4918
|
|
|
@@ -4341,7 +4922,11 @@ export function executeSnapshotQueryBatch(
|
|
|
4341
4922
|
bytes: Uint8Array,
|
|
4342
4923
|
queries: readonly SnapshotQuery[],
|
|
4343
4924
|
checkCancelled: () => void,
|
|
4344
|
-
onResult: (
|
|
4925
|
+
onResult: (
|
|
4926
|
+
query: SnapshotQuery,
|
|
4927
|
+
value: SnapshotQueryResult,
|
|
4928
|
+
milliseconds: number,
|
|
4929
|
+
) => void,
|
|
4345
4930
|
onRestore?: (milliseconds: number) => void,
|
|
4346
4931
|
): void {
|
|
4347
4932
|
const started = performance.now();
|
|
@@ -4353,7 +4938,9 @@ export function executeSnapshotQueryBatch(
|
|
|
4353
4938
|
onRestore?.(performance.now() - started);
|
|
4354
4939
|
for (const query of queries) {
|
|
4355
4940
|
checkCancelled();
|
|
4356
|
-
|
|
4941
|
+
const started = performance.now();
|
|
4942
|
+
const value = computeSnapshotQuery(geometry, query);
|
|
4943
|
+
onResult(query, value, performance.now() - started);
|
|
4357
4944
|
}
|
|
4358
4945
|
} finally {
|
|
4359
4946
|
geometry.shape.delete();
|
|
@@ -4429,7 +5016,14 @@ export function retainModelGeometry(
|
|
|
4429
5016
|
}
|
|
4430
5017
|
|
|
4431
5018
|
export const authoringApi = Object.freeze({
|
|
4432
|
-
|
|
5019
|
+
offset,
|
|
5020
|
+
rotate,
|
|
5021
|
+
pivot,
|
|
5022
|
+
pivotVertex,
|
|
5023
|
+
pivotPoint,
|
|
5024
|
+
axisEdge,
|
|
5025
|
+
axisLine,
|
|
5026
|
+
cache,
|
|
4433
5027
|
font,
|
|
4434
5028
|
googleFont,
|
|
4435
5029
|
text,
|
|
@@ -4532,7 +5126,11 @@ const kernelShape = cachedArtifact(
|
|
|
4532
5126
|
_inputs: readonly KernelArtifact<unknown>[],
|
|
4533
5127
|
compute: () => AnyShape,
|
|
4534
5128
|
) => compute(),
|
|
4535
|
-
{
|
|
5129
|
+
{
|
|
5130
|
+
key: (operation, arguments_, inputs) =>
|
|
5131
|
+
kernelOperationKey(`kernel-shape:${operation}`, arguments_, inputs),
|
|
5132
|
+
lifecycle: shapeLifecycle,
|
|
5133
|
+
},
|
|
4536
5134
|
);
|
|
4537
5135
|
|
|
4538
5136
|
function evaluateKernelShape<Shape extends AnyShape>(
|
|
@@ -4566,7 +5164,11 @@ const evaluateModelGeometry = cachedArtifact(
|
|
|
4566
5164
|
referenceBasis: result.referenceBasis,
|
|
4567
5165
|
};
|
|
4568
5166
|
},
|
|
4569
|
-
{
|
|
5167
|
+
{
|
|
5168
|
+
key: (operation, arguments_, inputs) =>
|
|
5169
|
+
kernelOperationKey(`model-geometry:${operation}`, arguments_, inputs),
|
|
5170
|
+
lifecycle: modelGeometryLifecycle,
|
|
5171
|
+
},
|
|
4570
5172
|
);
|
|
4571
5173
|
|
|
4572
5174
|
function createModelGeometryValue(
|
|
@@ -4725,7 +5327,7 @@ type PlanarSketch = Readonly<{
|
|
|
4725
5327
|
/** Internal bridge from the kernel-independent sketch definition to model geometry. */
|
|
4726
5328
|
function sketchFaceModel(
|
|
4727
5329
|
region: SketchRegion,
|
|
4728
|
-
|
|
5330
|
+
placements: readonly StoredPlacement[],
|
|
4729
5331
|
source: SketchFrame,
|
|
4730
5332
|
): FaceModel {
|
|
4731
5333
|
const curves = [region.outer, ...region.holes].map(loop =>
|
|
@@ -4747,7 +5349,7 @@ function sketchFaceModel(
|
|
|
4747
5349
|
return ModelObject.create<PlanarElements, 'face'>({
|
|
4748
5350
|
kind: 'face',
|
|
4749
5351
|
name: 'Sketch face',
|
|
4750
|
-
|
|
5352
|
+
placements,
|
|
4751
5353
|
geometry,
|
|
4752
5354
|
geometryAnchor: plane,
|
|
4753
5355
|
elements: {plane},
|
|
@@ -4920,12 +5522,14 @@ function solidElements(bounds: LocalBounds): StoredElements {
|
|
|
4920
5522
|
return {axis: {kind: 'line', transform: translation(boundsCenter(bounds))}};
|
|
4921
5523
|
}
|
|
4922
5524
|
|
|
4923
|
-
function constraintReferences(constraint:
|
|
5525
|
+
function constraintReferences(constraint: StoredPlacement): RelationObject[] {
|
|
4924
5526
|
return [
|
|
4925
|
-
constraint.
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
5527
|
+
...(constraint.kind === 'transformation'
|
|
5528
|
+
? []
|
|
5529
|
+
: [constraint.source.model, constraint.target.model]),
|
|
5530
|
+
...(constraint.kind === 'transformation' ? constraint.actions : []).map(
|
|
5531
|
+
action =>
|
|
5532
|
+
'pivot' in action ? selectionReference(action.pivot)?.model : undefined,
|
|
4929
5533
|
),
|
|
4930
5534
|
].filter((model): model is RelationObject => !!model);
|
|
4931
5535
|
}
|