@code3d/core 0.0.1-alpha.3 → 0.0.1-alpha.6
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 +31 -3
- package/bld/chunks/{chunk-U5G2VU2I.js → chunk-3RIMOCPP.js} +2 -2
- package/bld/chunks/{chunk-P5VGKAEM.js → chunk-KYW4AWKO.js} +2 -2
- package/bld/chunks/{chunk-RPY2WVCB.js → chunk-T3RAUNSF.js} +941 -506
- package/bld/chunks/chunk-T3RAUNSF.js.map +7 -0
- package/bld/chunks/{chunk-BF55AIEQ.js → chunk-T6COR2SG.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/index.d.ts +2 -2
- package/bld/library/index.d.ts.map +1 -1
- package/bld/library/index.js +15 -1
- package/bld/library/kernel-cache.d.ts +2 -1
- 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 +214 -76
- 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 +17 -3
- 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 +16 -10
- package/package.json +1 -1
- package/src/library/bound-solver.ts +103 -73
- package/src/library/index.ts +13 -2
- package/src/library/kernel-cache.ts +8 -0
- package/src/library/relation-solver.ts +194 -110
- package/src/library/runtime.ts +1029 -424
- package/src/library/sketch.ts +3 -3
- package/src/library/topology.ts +13 -2
- package/src/tooling/index.ts +14 -7
- package/bld/chunks/chunk-RPY2WVCB.js.map +0 -7
- /package/bld/chunks/{chunk-U5G2VU2I.js.map → chunk-3RIMOCPP.js.map} +0 -0
- /package/bld/chunks/{chunk-P5VGKAEM.js.map → chunk-KYW4AWKO.js.map} +0 -0
- /package/bld/chunks/{chunk-BF55AIEQ.js.map → chunk-T6COR2SG.js.map} +0 -0
package/src/library/runtime.ts
CHANGED
|
@@ -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,9 +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
186
|
sourceRefs: readonly SourceRef[];
|
|
186
187
|
parameters: readonly ParameterUsage[];
|
|
187
188
|
}> &
|
|
@@ -290,6 +291,26 @@ export type ModelParameterDimension = Readonly<{
|
|
|
290
291
|
vector: Vec3;
|
|
291
292
|
}>;
|
|
292
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
|
+
|
|
293
314
|
/** Local geometry coordinates; vector is the authored coordinates, offset, or angles. */
|
|
294
315
|
export type ModelSpatialOperation = Readonly<{
|
|
295
316
|
origin: Vec3;
|
|
@@ -298,20 +319,34 @@ export type ModelSpatialOperation = Readonly<{
|
|
|
298
319
|
frame?: RigidTransform;
|
|
299
320
|
rotation?: Vec3;
|
|
300
321
|
axisOnly?: boolean;
|
|
322
|
+
reference?: RotationReferenceSnapshot;
|
|
301
323
|
}>;
|
|
302
324
|
|
|
303
|
-
export type
|
|
325
|
+
export type RelationSpatialReference = Readonly<{
|
|
304
326
|
nodeId: string;
|
|
305
|
-
kind:
|
|
327
|
+
kind:
|
|
328
|
+
| 'pivot'
|
|
329
|
+
| 'pivotVertex'
|
|
330
|
+
| 'pivotPoint'
|
|
331
|
+
| 'axisEdge'
|
|
332
|
+
| 'pivotOffset'
|
|
333
|
+
| 'axisLine'
|
|
334
|
+
| 'axisOffset'
|
|
335
|
+
| 'rotate'
|
|
336
|
+
| 'offset';
|
|
306
337
|
spatial: ModelSpatialOperation;
|
|
307
338
|
}>;
|
|
308
339
|
|
|
309
|
-
export type
|
|
340
|
+
export type RelationPreview = Readonly<{
|
|
310
341
|
object: Pick<
|
|
311
342
|
ModelSnapshotObject,
|
|
312
|
-
|
|
343
|
+
| 'nodeId'
|
|
344
|
+
| 'compositionTransform'
|
|
345
|
+
| 'constraints'
|
|
346
|
+
| 'transformations'
|
|
347
|
+
| 'relationStages'
|
|
313
348
|
>;
|
|
314
|
-
spatial?:
|
|
349
|
+
spatial?: RelationSpatialReference;
|
|
315
350
|
}>;
|
|
316
351
|
|
|
317
352
|
export type RenderMesh = Readonly<{
|
|
@@ -334,6 +369,8 @@ export type RenderMesh = Readonly<{
|
|
|
334
369
|
start: number;
|
|
335
370
|
count: number;
|
|
336
371
|
edgeId: EdgeId;
|
|
372
|
+
/** True for native straight edges, suitable as rotation references. */
|
|
373
|
+
linear?: boolean;
|
|
337
374
|
}>[];
|
|
338
375
|
}>;
|
|
339
376
|
|
|
@@ -348,6 +385,8 @@ export type ModelSnapshotObject = Readonly<{
|
|
|
348
385
|
/** Placement in this snapshot tree; a root value uses model-local coordinates. */
|
|
349
386
|
transform: Transform;
|
|
350
387
|
constraints: readonly ConstraintSnapshot[];
|
|
388
|
+
transformations?: readonly TransformationSnapshot[];
|
|
389
|
+
relationStages?: readonly RelationStageSnapshot[];
|
|
351
390
|
elements: readonly ElementSnapshot[];
|
|
352
391
|
origin: Vec3;
|
|
353
392
|
sourceRefs: readonly SourceRef[];
|
|
@@ -401,12 +440,21 @@ export type ModelTopologyReference = Readonly<{
|
|
|
401
440
|
TopologySelection;
|
|
402
441
|
|
|
403
442
|
export type ConstraintTraceReference = Readonly<{
|
|
443
|
+
kind: 'constraint';
|
|
404
444
|
constraintId: string;
|
|
405
445
|
source: RelationObject;
|
|
406
446
|
target: RelationObject;
|
|
407
447
|
self?: RelationObject;
|
|
408
448
|
}>;
|
|
409
449
|
|
|
450
|
+
export type RelationTraceReference =
|
|
451
|
+
| ConstraintTraceReference
|
|
452
|
+
| Readonly<{
|
|
453
|
+
kind: 'transformation';
|
|
454
|
+
transformationId: string;
|
|
455
|
+
self?: RelationObject;
|
|
456
|
+
}>;
|
|
457
|
+
|
|
410
458
|
export type ModelOperationInstrumentation = Readonly<{
|
|
411
459
|
siteId: string;
|
|
412
460
|
execution: number;
|
|
@@ -423,27 +471,93 @@ export type ModelObjectRuntimeInfo = Readonly<{
|
|
|
423
471
|
sourceRefs: readonly SourceRef[];
|
|
424
472
|
}>;
|
|
425
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
|
+
|
|
426
512
|
type StoredConstraint = Readonly<{
|
|
427
513
|
id: string;
|
|
428
514
|
kind: 'on' | 'align';
|
|
429
515
|
source: RelationReference;
|
|
430
516
|
target: RelationReference;
|
|
431
|
-
rotations: readonly ConstraintRotation[];
|
|
432
|
-
offset: Vec3 | undefined;
|
|
433
517
|
}>;
|
|
434
518
|
|
|
435
519
|
// An omitted model refers to relate's self, including after immutable copies.
|
|
436
520
|
type RelationReference = StoredAnchor & Readonly<{model?: RelationObject}>;
|
|
437
|
-
type PivotSelection =
|
|
521
|
+
type PivotSelection = (
|
|
438
522
|
| Readonly<{kind: 'pivot'; point: Vec3}>
|
|
439
523
|
| Readonly<{kind: 'pivotVertex'; id: VertexId}>
|
|
440
|
-
| Readonly<{kind: '
|
|
441
|
-
|
|
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<{
|
|
442
554
|
pivot: PivotSelection;
|
|
443
555
|
angles: Vec3 | number;
|
|
444
556
|
}>;
|
|
445
|
-
type
|
|
446
|
-
|
|
557
|
+
type TransformationAction =
|
|
558
|
+
TransformationRotationAction | Readonly<{offset: Vec3}>;
|
|
559
|
+
type TransformationSpatialSelection = Readonly<{
|
|
560
|
+
kind: RelationSpatialReference['kind'];
|
|
447
561
|
pivot: PivotSelection;
|
|
448
562
|
}>;
|
|
449
563
|
type RelateContext = Readonly<{self: RelationObject; original: RelationObject}>;
|
|
@@ -527,7 +641,7 @@ type ModelObjectInit<Kind extends ModelKind = ModelKind> = Readonly<{
|
|
|
527
641
|
material?: ModelMaterialSnapshot;
|
|
528
642
|
children?: readonly ModelObject[];
|
|
529
643
|
assembly?: SolveContext;
|
|
530
|
-
|
|
644
|
+
placements?: readonly StoredPlacement[];
|
|
531
645
|
elements?: StoredElements;
|
|
532
646
|
sourceRefs?: readonly SourceRef[];
|
|
533
647
|
parameters?: readonly ParameterUsage[];
|
|
@@ -558,6 +672,7 @@ type SolidGeometry = KernelArtifact<
|
|
|
558
672
|
|
|
559
673
|
type SolveContext = {
|
|
560
674
|
poses: ReadonlyMap<RelationObject, RigidTransform>;
|
|
675
|
+
frame: RigidTransform;
|
|
561
676
|
};
|
|
562
677
|
|
|
563
678
|
function transformSolveContext(
|
|
@@ -565,6 +680,7 @@ function transformSolveContext(
|
|
|
565
680
|
transform: RigidTransform,
|
|
566
681
|
): SolveContext {
|
|
567
682
|
return {
|
|
683
|
+
frame: composeTransforms(transform, context.frame),
|
|
568
684
|
poses: new Map(
|
|
569
685
|
[...context.poses].map(([model, pose]) => [
|
|
570
686
|
model,
|
|
@@ -738,7 +854,7 @@ export interface ModelCapabilities<
|
|
|
738
854
|
relate(
|
|
739
855
|
build: (
|
|
740
856
|
self: ModelForKind<Elements, Kind>,
|
|
741
|
-
) =>
|
|
857
|
+
) => Relation | readonly Relation[],
|
|
742
858
|
): ModelForKind<Elements, Kind>;
|
|
743
859
|
expose<const Sources extends ElementSources>(
|
|
744
860
|
sources: Sources,
|
|
@@ -1125,7 +1241,7 @@ export function modelTopologyIds(
|
|
|
1125
1241
|
: undefined;
|
|
1126
1242
|
const source = isModelObject(value)
|
|
1127
1243
|
? value
|
|
1128
|
-
: value instanceof
|
|
1244
|
+
: value instanceof RelationExpression
|
|
1129
1245
|
? value.self()
|
|
1130
1246
|
: topology?.source;
|
|
1131
1247
|
const geometry = isModelObject(source)
|
|
@@ -1140,24 +1256,14 @@ export function modelTopologyIds(
|
|
|
1140
1256
|
);
|
|
1141
1257
|
}
|
|
1142
1258
|
|
|
1143
|
-
/** Traceable
|
|
1144
|
-
export abstract class
|
|
1145
|
-
private get sourceRefs(): SourceRef[] {
|
|
1146
|
-
return valueTrace(this).sourceRefs;
|
|
1147
|
-
}
|
|
1148
|
-
private get parameters(): ParameterUsage[] {
|
|
1149
|
-
return valueTrace(this).parameters;
|
|
1150
|
-
}
|
|
1151
|
-
protected spatialOperation?: ConstraintSpatialSelection;
|
|
1259
|
+
/** Traceable placement values, including unfinished pivot and axis selections. */
|
|
1260
|
+
export abstract class RelationExpression {
|
|
1152
1261
|
protected constructor(
|
|
1153
|
-
protected readonly
|
|
1154
|
-
protected readonly source: AnchorReference,
|
|
1155
|
-
protected readonly target: AnchorReference,
|
|
1156
|
-
protected readonly displacement: Vec3 | undefined,
|
|
1157
|
-
protected readonly rotations: readonly ConstraintRotation[],
|
|
1262
|
+
protected readonly definition: RelationDefinition,
|
|
1158
1263
|
protected readonly context: RelateContext | undefined,
|
|
1159
|
-
protected readonly
|
|
1160
|
-
previous?:
|
|
1264
|
+
protected readonly relationId: string,
|
|
1265
|
+
previous?: RelationExpression,
|
|
1266
|
+
protected readonly spatialOperation?: TransformationSpatialSelection,
|
|
1161
1267
|
) {
|
|
1162
1268
|
valueTraces.set(this, {
|
|
1163
1269
|
sourceRefs: [...(previous ? valueTrace(previous).sourceRefs : [])],
|
|
@@ -1166,7 +1272,7 @@ export abstract class ConstraintExpression {
|
|
|
1166
1272
|
}
|
|
1167
1273
|
/** @internal */
|
|
1168
1274
|
attachSource(sourceRef: SourceRef): void {
|
|
1169
|
-
const refs = this.sourceRefs;
|
|
1275
|
+
const refs = valueTrace(this).sourceRefs;
|
|
1170
1276
|
if (
|
|
1171
1277
|
!refs.some(
|
|
1172
1278
|
ref =>
|
|
@@ -1179,107 +1285,125 @@ export abstract class ConstraintExpression {
|
|
|
1179
1285
|
}
|
|
1180
1286
|
/** @internal */
|
|
1181
1287
|
attachParameters(parameters: readonly ParameterUsage[]): void {
|
|
1182
|
-
appendUniqueParameters(this.parameters, parameters);
|
|
1288
|
+
appendUniqueParameters(valueTrace(this).parameters, parameters);
|
|
1183
1289
|
}
|
|
1184
1290
|
/** @internal */
|
|
1185
1291
|
self(): RelationObject | undefined {
|
|
1186
1292
|
return this.context?.self;
|
|
1187
1293
|
}
|
|
1188
1294
|
/** @internal */
|
|
1189
|
-
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
|
+
};
|
|
1190
1302
|
const rebind = (model: RelationObject) =>
|
|
1191
1303
|
model === this.context?.original ? this.context.self : model;
|
|
1192
1304
|
return {
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1305
|
+
kind: 'constraint',
|
|
1306
|
+
constraintId: this.relationId,
|
|
1307
|
+
source: rebind(this.definition.source.model),
|
|
1308
|
+
target: rebind(this.definition.target.model),
|
|
1196
1309
|
self: this.context?.self,
|
|
1197
1310
|
};
|
|
1198
1311
|
}
|
|
1199
1312
|
/** @internal */
|
|
1200
|
-
storeFor(model: RelationObject, original: RelationObject):
|
|
1313
|
+
storeFor(model: RelationObject, original: RelationObject): StoredPlacement {
|
|
1201
1314
|
const isSelf = (candidate: RelationObject | undefined) =>
|
|
1202
1315
|
candidate === model || candidate === original;
|
|
1203
|
-
if (!isSelf(this.source.model) && !isSelf(this.target.model))
|
|
1204
|
-
throw new Error(
|
|
1205
|
-
'The constraint returned by relate() must involve self or the original receiver.',
|
|
1206
|
-
);
|
|
1207
1316
|
const bind = (reference: RelationReference): RelationReference => ({
|
|
1208
1317
|
...reference,
|
|
1209
1318
|
model: isSelf(reference.model) ? undefined : reference.model,
|
|
1210
1319
|
});
|
|
1211
|
-
const
|
|
1212
|
-
|
|
1213
|
-
kind
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
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
|
+
};
|
|
1225
1356
|
valueTraces.set(stored, {
|
|
1226
|
-
sourceRefs: [...this.sourceRefs],
|
|
1227
|
-
parameters: [...this.parameters],
|
|
1357
|
+
sourceRefs: [...valueTrace(this).sourceRefs],
|
|
1358
|
+
parameters: [...valueTrace(this).parameters],
|
|
1228
1359
|
});
|
|
1229
1360
|
return stored;
|
|
1230
1361
|
}
|
|
1231
1362
|
/** @internal */
|
|
1232
|
-
preview(
|
|
1363
|
+
preview(
|
|
1364
|
+
preceding: readonly RelationExpression[] = [],
|
|
1365
|
+
): RelationPreview | undefined {
|
|
1233
1366
|
if (!this.context) return undefined;
|
|
1234
1367
|
const pivot = this.spatialOperation?.pivot;
|
|
1235
|
-
const selection =
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
},
|
|
1250
|
-
}
|
|
1251
|
-
: this.spatialOperation;
|
|
1252
|
-
return this.context.original[relationPreview](
|
|
1253
|
-
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](
|
|
1254
1382
|
this.storeFor(this.context.self, this.context.original),
|
|
1255
1383
|
selection,
|
|
1384
|
+
preceding,
|
|
1256
1385
|
);
|
|
1257
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
|
+
}
|
|
1258
1396
|
}
|
|
1259
1397
|
|
|
1260
|
-
export class Constraint extends
|
|
1398
|
+
export class Constraint extends RelationExpression {
|
|
1399
|
+
declare protected readonly definition: Exclude<
|
|
1400
|
+
RelationDefinition,
|
|
1401
|
+
{kind: 'transformation'}
|
|
1402
|
+
>;
|
|
1261
1403
|
private constructor(
|
|
1262
|
-
kind:
|
|
1263
|
-
source: AnchorReference,
|
|
1264
|
-
target: AnchorReference,
|
|
1265
|
-
displacement: Vec3 | undefined = undefined,
|
|
1266
|
-
rotations: readonly ConstraintRotation[] = [],
|
|
1267
|
-
context = activeRelate,
|
|
1268
|
-
constraintId = `constraint-${nextConstraintId++}`,
|
|
1269
|
-
previous?: ConstraintExpression,
|
|
1270
|
-
spatialOperation?: ConstraintSpatialSelection,
|
|
1404
|
+
definition: Exclude<RelationDefinition, {kind: 'transformation'}>,
|
|
1271
1405
|
) {
|
|
1272
|
-
super(
|
|
1273
|
-
kind,
|
|
1274
|
-
source,
|
|
1275
|
-
target,
|
|
1276
|
-
displacement,
|
|
1277
|
-
rotations,
|
|
1278
|
-
context,
|
|
1279
|
-
constraintId,
|
|
1280
|
-
previous,
|
|
1281
|
-
);
|
|
1282
|
-
this.spatialOperation = spatialOperation;
|
|
1406
|
+
super(definition, activeRelate, `constraint-${nextConstraintId++}`);
|
|
1283
1407
|
}
|
|
1284
1408
|
/** @internal */
|
|
1285
1409
|
static create(
|
|
@@ -1293,171 +1417,254 @@ export class Constraint extends ConstraintExpression {
|
|
|
1293
1417
|
throw new Error(
|
|
1294
1418
|
'align() requires points, curves, or surfaces; select geometry on a solid or group.',
|
|
1295
1419
|
);
|
|
1296
|
-
return new Constraint(kind, source, target);
|
|
1420
|
+
return new Constraint({kind, source, target});
|
|
1297
1421
|
}
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
this.context,
|
|
1316
|
-
this.constraintId,
|
|
1317
|
-
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,
|
|
1318
1439
|
);
|
|
1440
|
+
valueTraces.set(action, valueTrace(this));
|
|
1319
1441
|
}
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
}
|
|
1331
|
-
/** @code3d.param id {kind: 'vertex', label: 'Pivot vertex'} */
|
|
1332
|
-
pivotVertex(id: VertexId): ConstraintPivotChain {
|
|
1333
|
-
return new ConstraintPivotChain(this, {kind: 'pivotVertex', id});
|
|
1334
|
-
}
|
|
1335
|
-
/** Select a positioned axis in the composition. */
|
|
1336
|
-
around(axis: LineAnchor): ConstraintAroundChain {
|
|
1337
|
-
const reference = anchorReference(axis);
|
|
1338
|
-
if (reference.kind !== 'line')
|
|
1339
|
-
throw new Error('around() requires an axis or straight edge.');
|
|
1340
|
-
const geometry =
|
|
1341
|
-
reference.topology?.source[modelGeometry]()?.value ??
|
|
1342
|
-
(reference.whole && isModelObject(reference.model)
|
|
1343
|
-
? reference.model[modelGeometry]()?.value
|
|
1344
|
-
: undefined);
|
|
1345
|
-
if (geometry) {
|
|
1346
|
-
const straight = reference.topology
|
|
1347
|
-
? withTopologyShape(
|
|
1348
|
-
geometry.shape,
|
|
1349
|
-
geometry.topology,
|
|
1350
|
-
reference.topology.selection,
|
|
1351
|
-
shape => (shape as ReplicadEdge).geomType === 'LINE',
|
|
1352
|
-
)
|
|
1353
|
-
: (geometry.shape as ReplicadEdge).geomType === 'LINE';
|
|
1354
|
-
if (!straight)
|
|
1355
|
-
throw new Error(
|
|
1356
|
-
'around() requires a straight axis; curved edges do not define one rotation axis.',
|
|
1357
|
-
);
|
|
1358
|
-
}
|
|
1359
|
-
return new ConstraintAroundChain(this, {kind: 'around', axis: reference});
|
|
1360
|
-
}
|
|
1361
|
-
/**
|
|
1362
|
-
* Rotate about self's origin and local X, Y, then Z axes, in degrees.
|
|
1363
|
-
* @code3d.param x {kind: 'angle', default: 0, label: 'Rotate X'}
|
|
1364
|
-
* @code3d.param y {kind: 'angle', default: 0, label: 'Rotate Y'}
|
|
1365
|
-
* @code3d.param z {kind: 'angle', default: 0, label: 'Rotate Z'}
|
|
1366
|
-
*/
|
|
1367
|
-
rotate(x: number, y: number, z: number): Constraint;
|
|
1368
|
-
rotate(x = 0, y = 0, z = 0): Constraint {
|
|
1369
|
-
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
|
+
);
|
|
1370
1452
|
}
|
|
1371
|
-
/** @internal */
|
|
1372
1453
|
withRotation(
|
|
1373
1454
|
pivot: PivotSelection,
|
|
1374
1455
|
angles: Vec3 | number,
|
|
1375
|
-
previous:
|
|
1376
|
-
):
|
|
1456
|
+
previous: RelationExpression,
|
|
1457
|
+
): Transformation {
|
|
1377
1458
|
assertFiniteVector(
|
|
1378
1459
|
'rotate',
|
|
1379
1460
|
typeof angles === 'number' ? [angles, 0, 0] : angles,
|
|
1380
1461
|
);
|
|
1381
|
-
return new
|
|
1382
|
-
this.kind,
|
|
1383
|
-
this.source,
|
|
1384
|
-
this.target,
|
|
1385
|
-
this.displacement,
|
|
1386
|
-
[...this.rotations, {pivot, angles}],
|
|
1387
|
-
this.context,
|
|
1388
|
-
this.constraintId,
|
|
1389
|
-
previous,
|
|
1390
|
-
{kind: 'rotate', pivot},
|
|
1391
|
-
);
|
|
1462
|
+
return new Transformation({pivot, angles}, previous);
|
|
1392
1463
|
}
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
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
|
+
);
|
|
1414
1495
|
}
|
|
1496
|
+
return reference;
|
|
1415
1497
|
}
|
|
1416
1498
|
|
|
1417
|
-
export
|
|
1499
|
+
export type Relation = Constraint | Transformation;
|
|
1500
|
+
|
|
1501
|
+
export class PivotRotation extends RelationExpression {
|
|
1418
1502
|
/** @internal */
|
|
1419
1503
|
constructor(
|
|
1420
|
-
|
|
1421
|
-
|
|
1504
|
+
protected readonly chain: TransformationRotation,
|
|
1505
|
+
protected readonly selection: PointSelection,
|
|
1506
|
+
previous?: RelationExpression,
|
|
1422
1507
|
) {
|
|
1423
|
-
super(...
|
|
1424
|
-
|
|
1508
|
+
super(...(previous ?? chain).continuationArguments(), {
|
|
1509
|
+
kind: selection.offset ? 'pivotOffset' : selection.kind,
|
|
1510
|
+
pivot: selection,
|
|
1511
|
+
});
|
|
1425
1512
|
}
|
|
1426
1513
|
/**
|
|
1427
1514
|
* @code3d.param x {kind: 'angle', default: 0, label: 'Rotate X'}
|
|
1428
1515
|
* @code3d.param y {kind: 'angle', default: 0, label: 'Rotate Y'}
|
|
1429
1516
|
* @code3d.param z {kind: 'angle', default: 0, label: 'Rotate Z'}
|
|
1430
1517
|
*/
|
|
1431
|
-
rotate(x: number, y: number, z: number):
|
|
1432
|
-
rotate(x = 0, y = 0, z = 0):
|
|
1433
|
-
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);
|
|
1434
1521
|
}
|
|
1435
1522
|
}
|
|
1436
|
-
|
|
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
|
+
);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
export class AxisRotation extends RelationExpression {
|
|
1437
1543
|
/** @internal */
|
|
1438
1544
|
constructor(
|
|
1439
|
-
|
|
1440
|
-
|
|
1545
|
+
protected readonly chain: TransformationRotation,
|
|
1546
|
+
protected readonly selection: AxisSelection,
|
|
1547
|
+
previous?: RelationExpression,
|
|
1441
1548
|
) {
|
|
1442
|
-
super(...
|
|
1443
|
-
|
|
1549
|
+
super(...(previous ?? chain).continuationArguments(), {
|
|
1550
|
+
kind: selection.offset ? 'axisOffset' : selection.kind,
|
|
1551
|
+
pivot: selection,
|
|
1552
|
+
});
|
|
1444
1553
|
}
|
|
1445
1554
|
/** @code3d.param angle {kind: 'angle', default: 0, label: 'Rotate'} */
|
|
1446
|
-
rotate(angle: number):
|
|
1447
|
-
rotate(angle = 0):
|
|
1448
|
-
return this.
|
|
1555
|
+
rotate(angle: number): Transformation;
|
|
1556
|
+
rotate(angle = 0): Transformation {
|
|
1557
|
+
return this.chain.withRotation(this.selection, angle, this);
|
|
1449
1558
|
}
|
|
1450
1559
|
}
|
|
1451
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
|
+
);
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
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
|
+
|
|
1452
1659
|
const modelGeometry = Symbol('modelGeometry');
|
|
1453
1660
|
const referenceBounds = Symbol('referenceBounds');
|
|
1454
1661
|
const referenceBoundsParts = Symbol('referenceBoundsParts');
|
|
1455
1662
|
const modelSnapshotQueries = Symbol('modelSnapshotQueries');
|
|
1456
|
-
const
|
|
1663
|
+
const previewRelation = Symbol('previewRelation');
|
|
1457
1664
|
|
|
1458
1665
|
type RelationObjectInit = Readonly<{
|
|
1459
1666
|
nodeId?: string;
|
|
1460
|
-
|
|
1667
|
+
placements?: readonly StoredPlacement[];
|
|
1461
1668
|
sourceRefs?: readonly SourceRef[];
|
|
1462
1669
|
parameters?: readonly ParameterUsage[];
|
|
1463
1670
|
}>;
|
|
@@ -1467,11 +1674,17 @@ export abstract class RelationObject {
|
|
|
1467
1674
|
readonly nodeId: string;
|
|
1468
1675
|
/** @internal */
|
|
1469
1676
|
abstract readonly name: string;
|
|
1470
|
-
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
|
+
}
|
|
1471
1684
|
|
|
1472
1685
|
protected constructor(init: RelationObjectInit = {}) {
|
|
1473
1686
|
this.nodeId = init.nodeId ?? `node-${nextNodeId++}`;
|
|
1474
|
-
this.
|
|
1687
|
+
this.placements = [...(init.placements ?? [])];
|
|
1475
1688
|
valueTraces.set(this, {
|
|
1476
1689
|
sourceRefs: [...(init.sourceRefs ?? [])],
|
|
1477
1690
|
parameters: [...(init.parameters ?? [])],
|
|
@@ -1488,6 +1701,24 @@ export abstract class RelationObject {
|
|
|
1488
1701
|
|
|
1489
1702
|
protected abstract copyRelations(init: RelationObjectInit): RelationObject;
|
|
1490
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
|
+
|
|
1491
1722
|
protected vertexPosition(_id: VertexId): Vec3 {
|
|
1492
1723
|
throw new Error(
|
|
1493
1724
|
'pivotVertex() requires model topology, not a sketch reference frame. Use pivot([x, y, z]) instead.',
|
|
@@ -1512,27 +1743,30 @@ export abstract class RelationObject {
|
|
|
1512
1743
|
/** Store relations only after the complete callback has returned. */
|
|
1513
1744
|
protected addRelations(
|
|
1514
1745
|
original: RelationObject,
|
|
1515
|
-
build: () =>
|
|
1746
|
+
build: () => Relation | readonly Relation[],
|
|
1516
1747
|
): readonly RelationObject[] {
|
|
1517
1748
|
const previous = activeRelate;
|
|
1518
1749
|
activeRelate = {self: this, original};
|
|
1519
|
-
let built:
|
|
1750
|
+
let built: Relation | readonly Relation[];
|
|
1520
1751
|
try {
|
|
1521
1752
|
built = build();
|
|
1522
1753
|
} finally {
|
|
1523
1754
|
activeRelate = previous;
|
|
1524
1755
|
}
|
|
1525
|
-
const
|
|
1756
|
+
const relations: readonly Relation[] = Array.isArray(built)
|
|
1526
1757
|
? built
|
|
1527
|
-
: [built as
|
|
1528
|
-
const stored =
|
|
1529
|
-
if (
|
|
1758
|
+
: [built as Relation];
|
|
1759
|
+
const stored = relations.map(constraint => {
|
|
1760
|
+
if (
|
|
1761
|
+
!(constraint instanceof Constraint) &&
|
|
1762
|
+
!(constraint instanceof Transformation)
|
|
1763
|
+
)
|
|
1530
1764
|
throw new Error(
|
|
1531
|
-
'relate() requires a completed Constraint; finish pivot
|
|
1765
|
+
'relate() requires a completed Constraint or Transformation; finish the pivot or axis selector with rotate().',
|
|
1532
1766
|
);
|
|
1533
1767
|
return constraint.storeFor(this, original);
|
|
1534
1768
|
});
|
|
1535
|
-
this.
|
|
1769
|
+
this.placements.push(...stored);
|
|
1536
1770
|
return uniqueModels(stored.flatMap(constraintReferences));
|
|
1537
1771
|
}
|
|
1538
1772
|
|
|
@@ -1641,27 +1875,37 @@ export abstract class RelationObject {
|
|
|
1641
1875
|
};
|
|
1642
1876
|
}
|
|
1643
1877
|
|
|
1644
|
-
private
|
|
1645
|
-
constraint:
|
|
1878
|
+
private bodyActions(
|
|
1879
|
+
constraint: StoredTransformation,
|
|
1646
1880
|
indices: Map<RelationObject, number>,
|
|
1647
|
-
):
|
|
1648
|
-
return constraint.
|
|
1649
|
-
if (
|
|
1650
|
-
|
|
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
|
|
1651
1888
|
? {
|
|
1652
|
-
body: indices.get(
|
|
1653
|
-
axis: pivot.
|
|
1654
|
-
angle: (angles as number) * (
|
|
1889
|
+
body: indices.get(axis.model)!,
|
|
1890
|
+
axis: selectedAxis(axis, pivot.offset),
|
|
1891
|
+
angle: (angles as number) * (axis.direction ?? 1),
|
|
1655
1892
|
}
|
|
1656
1893
|
: {
|
|
1657
1894
|
local: axisRotation(
|
|
1658
|
-
pivot.
|
|
1659
|
-
(angles as number) * (
|
|
1895
|
+
selectedAxis(axis, pivot.offset),
|
|
1896
|
+
(angles as number) * (axis.direction ?? 1),
|
|
1660
1897
|
),
|
|
1661
1898
|
};
|
|
1662
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
|
+
};
|
|
1663
1907
|
const frame = translation(
|
|
1664
|
-
|
|
1908
|
+
addVectors(this.rotationPoint(pivot), pivot.offset ?? origin),
|
|
1665
1909
|
);
|
|
1666
1910
|
return {
|
|
1667
1911
|
local: composeTransforms(
|
|
@@ -1673,84 +1917,167 @@ export abstract class RelationObject {
|
|
|
1673
1917
|
}
|
|
1674
1918
|
|
|
1675
1919
|
/** @internal */
|
|
1676
|
-
[
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
//
|
|
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
|
+
}
|
|
1683
1948
|
const owner = this.copyRelations({
|
|
1684
|
-
nodeId,
|
|
1685
|
-
|
|
1949
|
+
nodeId: this.nodeId,
|
|
1950
|
+
placements,
|
|
1686
1951
|
});
|
|
1687
1952
|
const context = RelationObject.createSolveContext([
|
|
1688
1953
|
owner,
|
|
1689
|
-
...(selection
|
|
1690
|
-
? [selection.pivot
|
|
1954
|
+
...(selection && selectionReference(selection.pivot)?.model
|
|
1955
|
+
? [selectionReference(selection.pivot)!.model!]
|
|
1691
1956
|
: []),
|
|
1692
1957
|
]);
|
|
1693
1958
|
return {
|
|
1694
1959
|
object: {
|
|
1695
|
-
nodeId,
|
|
1960
|
+
nodeId: this.nodeId,
|
|
1696
1961
|
compositionTransform: toTransform(owner.solvePose(context)),
|
|
1697
1962
|
constraints: owner.constraints.map(value =>
|
|
1698
1963
|
owner.constraintSnapshot(value, context),
|
|
1699
1964
|
),
|
|
1965
|
+
transformations: owner.transformationSnapshots(context),
|
|
1966
|
+
relationStages: owner.relationStageSnapshots(context),
|
|
1700
1967
|
},
|
|
1701
|
-
spatial:
|
|
1702
|
-
|
|
1703
|
-
|
|
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,
|
|
1704
1976
|
};
|
|
1705
1977
|
}
|
|
1706
1978
|
|
|
1707
|
-
private
|
|
1708
|
-
constraint:
|
|
1709
|
-
selection:
|
|
1979
|
+
private relationSpatial(
|
|
1980
|
+
constraint: StoredTransformation,
|
|
1981
|
+
selection: TransformationSpatialSelection,
|
|
1710
1982
|
context: SolveContext,
|
|
1711
|
-
|
|
1983
|
+
actionIndex?: number,
|
|
1984
|
+
): RelationSpatialReference {
|
|
1985
|
+
context = this.placementStageContext(constraint, context);
|
|
1712
1986
|
const models = [...context.poses.keys()];
|
|
1713
|
-
const actions = this.
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
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);
|
|
1718
1997
|
const finalPose = this.solvePose(context);
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
-action.angle,
|
|
1732
|
-
),
|
|
1733
|
-
before,
|
|
1734
|
-
);
|
|
1735
|
-
}
|
|
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
|
+
);
|
|
1736
2010
|
const pivot = selection.pivot;
|
|
2011
|
+
const axis = isAxisSelection(pivot) ? this.rotationAxis(pivot) : undefined;
|
|
1737
2012
|
let frame: RigidTransform;
|
|
1738
|
-
if (
|
|
2013
|
+
if (axis) {
|
|
1739
2014
|
frame = composeTransforms(
|
|
1740
|
-
|
|
1741
|
-
(
|
|
1742
|
-
? pivot.
|
|
1743
|
-
: 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
|
+
),
|
|
1744
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
|
+
};
|
|
1745
2035
|
} else {
|
|
1746
2036
|
const local = translation(
|
|
1747
|
-
|
|
2037
|
+
addVectors(
|
|
2038
|
+
this.rotationPoint(pivot as PointSelection),
|
|
2039
|
+
pivot.offset ?? origin,
|
|
2040
|
+
),
|
|
1748
2041
|
);
|
|
1749
2042
|
frame = composeTransforms(before, local);
|
|
1750
2043
|
}
|
|
1751
|
-
const
|
|
2044
|
+
const selected = constraint.actions[stage];
|
|
2045
|
+
const angles = selected && 'angles' in selected ? selected.angles : origin;
|
|
1752
2046
|
const rotationVector: Vec3 =
|
|
1753
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
|
+
}
|
|
1754
2081
|
const localFrame = relativeTransform(frame, finalPose);
|
|
1755
2082
|
return {
|
|
1756
2083
|
nodeId: this.nodeId,
|
|
@@ -1760,30 +2087,289 @@ export abstract class RelationObject {
|
|
|
1760
2087
|
vector:
|
|
1761
2088
|
selection.kind === 'rotate'
|
|
1762
2089
|
? rotationVector
|
|
1763
|
-
:
|
|
1764
|
-
|
|
1765
|
-
|
|
2090
|
+
: selection.kind === 'pivotOffset' ||
|
|
2091
|
+
selection.kind === 'axisOffset'
|
|
2092
|
+
? (pivot.offset ?? origin)
|
|
2093
|
+
: pivot.kind === 'pivot'
|
|
2094
|
+
? pivot.point
|
|
2095
|
+
: origin,
|
|
1766
2096
|
frame: localFrame,
|
|
1767
2097
|
rotation: rotationVector,
|
|
1768
|
-
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
|
+
},
|
|
1769
2122
|
},
|
|
1770
2123
|
};
|
|
1771
2124
|
}
|
|
1772
2125
|
|
|
1773
|
-
|
|
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 {
|
|
1774
2325
|
const models: RelationObject[] = [];
|
|
1775
2326
|
const indices = new Map<RelationObject, number>();
|
|
1776
2327
|
const collect = (model: RelationObject): void => {
|
|
1777
2328
|
if (indices.has(model)) return;
|
|
1778
2329
|
indices.set(model, models.length);
|
|
1779
2330
|
models.push(model);
|
|
1780
|
-
|
|
2331
|
+
(overrides.get(model) ?? model.placements)
|
|
2332
|
+
.flatMap(constraintReferences)
|
|
2333
|
+
.forEach(collect);
|
|
1781
2334
|
};
|
|
1782
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
|
+
});
|
|
1783
2367
|
const poses = solveBodies(
|
|
1784
|
-
models.map((model): Body => ({
|
|
2368
|
+
models.map((model, index): Body => ({
|
|
2369
|
+
initial: programs[index].initial,
|
|
2370
|
+
transformations: programs[index].transformations,
|
|
1785
2371
|
name: model.name,
|
|
1786
|
-
relations:
|
|
2372
|
+
relations: programs[index].constraints.map(constraint =>
|
|
1787
2373
|
constraint.kind === 'align'
|
|
1788
2374
|
? {
|
|
1789
2375
|
kind: 'align',
|
|
@@ -1802,8 +2388,6 @@ export abstract class RelationObject {
|
|
|
1802
2388
|
).alignmentGeometry(constraint.target),
|
|
1803
2389
|
transform: constraint.target.transform,
|
|
1804
2390
|
},
|
|
1805
|
-
offset: constraint.offset,
|
|
1806
|
-
rotations: model.bodyRotations(constraint, indices),
|
|
1807
2391
|
}
|
|
1808
2392
|
: {
|
|
1809
2393
|
kind: 'on',
|
|
@@ -1822,14 +2406,13 @@ export abstract class RelationObject {
|
|
|
1822
2406
|
transform: constraint.target.transform,
|
|
1823
2407
|
facing: constraint.target.bound!.facing,
|
|
1824
2408
|
},
|
|
1825
|
-
offset: constraint.offset,
|
|
1826
|
-
rotations: model.bodyRotations(constraint, indices),
|
|
1827
2409
|
},
|
|
1828
2410
|
),
|
|
1829
2411
|
})),
|
|
1830
2412
|
);
|
|
1831
2413
|
return {
|
|
1832
2414
|
poses: new Map(models.map((model, index) => [model, poses[index]])),
|
|
2415
|
+
frame: identityRigidTransform,
|
|
1833
2416
|
};
|
|
1834
2417
|
}
|
|
1835
2418
|
|
|
@@ -1839,46 +2422,12 @@ export abstract class RelationObject {
|
|
|
1839
2422
|
): ConstraintSnapshot {
|
|
1840
2423
|
const source = constraint.source.model ?? this;
|
|
1841
2424
|
const target = constraint.target.model ?? this;
|
|
1842
|
-
const
|
|
1843
|
-
const
|
|
1844
|
-
|
|
1845
|
-
new Map(models.map((model, i) => [model, i])),
|
|
1846
|
-
);
|
|
1847
|
-
let before = this.solvePose(context);
|
|
1848
|
-
for (const action of [...actions].reverse()) {
|
|
1849
|
-
before =
|
|
1850
|
-
'local' in action
|
|
1851
|
-
? composeTransforms(before, invertTransform(action.local))
|
|
1852
|
-
: composeTransforms(
|
|
1853
|
-
axisRotation(
|
|
1854
|
-
composeTransforms(
|
|
1855
|
-
models[action.body].solvePose(context),
|
|
1856
|
-
action.axis,
|
|
1857
|
-
),
|
|
1858
|
-
-action.angle,
|
|
1859
|
-
),
|
|
1860
|
-
before,
|
|
1861
|
-
);
|
|
1862
|
-
}
|
|
1863
|
-
const sourcePose = source === this ? before : source.solvePose(context);
|
|
1864
|
-
const targetPose = target === this ? before : target.solvePose(context);
|
|
1865
|
-
let offsetFrame = composeTransforms(
|
|
1866
|
-
targetPose,
|
|
2425
|
+
const sourcePose = source.solvePose(context);
|
|
2426
|
+
const contactFrame = composeTransforms(
|
|
2427
|
+
target.solvePose(context),
|
|
1867
2428
|
constraint.target.transform,
|
|
1868
2429
|
);
|
|
1869
2430
|
if (constraint.kind === 'align') {
|
|
1870
|
-
for (const action of actions)
|
|
1871
|
-
if ('body' in action)
|
|
1872
|
-
offsetFrame = composeTransforms(
|
|
1873
|
-
axisRotation(
|
|
1874
|
-
composeTransforms(
|
|
1875
|
-
models[action.body].solvePose(context),
|
|
1876
|
-
action.axis,
|
|
1877
|
-
),
|
|
1878
|
-
action.angle,
|
|
1879
|
-
),
|
|
1880
|
-
offsetFrame,
|
|
1881
|
-
);
|
|
1882
2431
|
return {
|
|
1883
2432
|
id: constraint.id,
|
|
1884
2433
|
kind: 'align',
|
|
@@ -1886,15 +2435,12 @@ export abstract class RelationObject {
|
|
|
1886
2435
|
target: anchorSnapshot(target, constraint.target),
|
|
1887
2436
|
sourceElement: source.relationElement(constraint.source),
|
|
1888
2437
|
targetElement: target.relationElement(constraint.target),
|
|
1889
|
-
offsetDirection: 1,
|
|
1890
|
-
offset: constraint.offset ?? origin,
|
|
1891
|
-
offsetFrame: toTransform(offsetFrame),
|
|
1892
2438
|
sourceRefs: [...valueTrace(constraint).sourceRefs],
|
|
1893
2439
|
parameters: [...valueTrace(constraint).parameters],
|
|
1894
2440
|
};
|
|
1895
2441
|
}
|
|
1896
2442
|
const orientation = composeTransforms(
|
|
1897
|
-
invertTransform(rotation(
|
|
2443
|
+
invertTransform(rotation(contactFrame.quaternion)),
|
|
1898
2444
|
rotation(sourcePose.quaternion),
|
|
1899
2445
|
).quaternion;
|
|
1900
2446
|
const bounds = source[referenceBounds](
|
|
@@ -1910,9 +2456,9 @@ export abstract class RelationObject {
|
|
|
1910
2456
|
{
|
|
1911
2457
|
position: addVectors(
|
|
1912
2458
|
sourcePose.position,
|
|
1913
|
-
rotateVector(center,
|
|
2459
|
+
rotateVector(center, contactFrame.quaternion),
|
|
1914
2460
|
),
|
|
1915
|
-
quaternion:
|
|
2461
|
+
quaternion: contactFrame.quaternion,
|
|
1916
2462
|
},
|
|
1917
2463
|
sourcePose,
|
|
1918
2464
|
);
|
|
@@ -1921,19 +2467,6 @@ export abstract class RelationObject {
|
|
|
1921
2467
|
bounds[1][1] - bounds[0][1],
|
|
1922
2468
|
bounds[1][2] - bounds[0][2],
|
|
1923
2469
|
];
|
|
1924
|
-
for (const action of actions) {
|
|
1925
|
-
if ('body' in action)
|
|
1926
|
-
offsetFrame = composeTransforms(
|
|
1927
|
-
axisRotation(
|
|
1928
|
-
composeTransforms(
|
|
1929
|
-
models[action.body].solvePose(context),
|
|
1930
|
-
action.axis,
|
|
1931
|
-
),
|
|
1932
|
-
action.angle,
|
|
1933
|
-
),
|
|
1934
|
-
offsetFrame,
|
|
1935
|
-
);
|
|
1936
|
-
}
|
|
1937
2470
|
return {
|
|
1938
2471
|
id: constraint.id,
|
|
1939
2472
|
kind: constraint.kind,
|
|
@@ -1967,9 +2500,6 @@ export abstract class RelationObject {
|
|
|
1967
2500
|
transform: toTransform(constraint.target.transform),
|
|
1968
2501
|
bound: constraint.target.bound,
|
|
1969
2502
|
},
|
|
1970
|
-
offsetDirection: source === this ? 1 : -1,
|
|
1971
|
-
offset: constraint.offset ?? origin,
|
|
1972
|
-
offsetFrame: toTransform(offsetFrame),
|
|
1973
2503
|
sourceRefs: [...valueTrace(constraint).sourceRefs],
|
|
1974
2504
|
parameters: [...valueTrace(constraint).parameters],
|
|
1975
2505
|
};
|
|
@@ -1985,7 +2515,7 @@ export class SketchFrame extends RelationObject {
|
|
|
1985
2515
|
|
|
1986
2516
|
constructor(source?: SketchFrame, init: RelationObjectInit = {}) {
|
|
1987
2517
|
super({
|
|
1988
|
-
|
|
2518
|
+
placements: source?.placements,
|
|
1989
2519
|
sourceRefs: source?.sourceRefs,
|
|
1990
2520
|
parameters: source?.parameters,
|
|
1991
2521
|
...init,
|
|
@@ -2004,7 +2534,7 @@ export class SketchFrame extends RelationObject {
|
|
|
2004
2534
|
relatedObjects(): readonly RelationObject[] {
|
|
2005
2535
|
return [
|
|
2006
2536
|
...(this.source ? [this.source] : []),
|
|
2007
|
-
...this.
|
|
2537
|
+
...this.placements.flatMap(constraintReferences),
|
|
2008
2538
|
];
|
|
2009
2539
|
}
|
|
2010
2540
|
|
|
@@ -2060,7 +2590,7 @@ export class SketchFrame extends RelationObject {
|
|
|
2060
2590
|
}
|
|
2061
2591
|
|
|
2062
2592
|
relate(
|
|
2063
|
-
build: (frame: SketchFrame) =>
|
|
2593
|
+
build: (frame: SketchFrame) => Relation | readonly Relation[],
|
|
2064
2594
|
): SketchFrame {
|
|
2065
2595
|
const related = new SketchFrame(this);
|
|
2066
2596
|
related.addRelations(this, () => build(related));
|
|
@@ -2068,10 +2598,10 @@ export class SketchFrame extends RelationObject {
|
|
|
2068
2598
|
}
|
|
2069
2599
|
|
|
2070
2600
|
face(region: SketchRegion): FaceModel {
|
|
2071
|
-
return sketchFaceModel(region, this.
|
|
2601
|
+
return sketchFaceModel(region, this.placements, this);
|
|
2072
2602
|
}
|
|
2073
2603
|
|
|
2074
|
-
snapshot():
|
|
2604
|
+
snapshot(): RelationPreview['object'] {
|
|
2075
2605
|
const context = RelationObject.createSolveContext([this]);
|
|
2076
2606
|
return {
|
|
2077
2607
|
nodeId: this.nodeId,
|
|
@@ -2079,6 +2609,8 @@ export class SketchFrame extends RelationObject {
|
|
|
2079
2609
|
constraints: this.constraints.map(constraint =>
|
|
2080
2610
|
this.constraintSnapshot(constraint, context),
|
|
2081
2611
|
),
|
|
2612
|
+
transformations: this.transformationSnapshots(context),
|
|
2613
|
+
relationStages: this.relationStageSnapshots(context),
|
|
2082
2614
|
};
|
|
2083
2615
|
}
|
|
2084
2616
|
|
|
@@ -2242,7 +2774,7 @@ export class ModelObject<
|
|
|
2242
2774
|
relate(
|
|
2243
2775
|
build: (
|
|
2244
2776
|
self: RuntimeModel<Elements, Kind>,
|
|
2245
|
-
) =>
|
|
2777
|
+
) => Relation | readonly Relation[],
|
|
2246
2778
|
): RuntimeModel<Elements, Kind> {
|
|
2247
2779
|
const operation = storedOperation('relate', [
|
|
2248
2780
|
{model: this, role: 'source', index: 0},
|
|
@@ -2506,7 +3038,7 @@ export class ModelObject<
|
|
|
2506
3038
|
transformElement(element, transform),
|
|
2507
3039
|
]),
|
|
2508
3040
|
),
|
|
2509
|
-
|
|
3041
|
+
placements: this.mapConstraintGeometry(
|
|
2510
3042
|
element => transformElement(element, transform),
|
|
2511
3043
|
point => composeTransforms(transform, translation(point)).position,
|
|
2512
3044
|
),
|
|
@@ -2544,23 +3076,52 @@ export class ModelObject<
|
|
|
2544
3076
|
private mapConstraintGeometry(
|
|
2545
3077
|
mapElement: (element: StoredElement) => StoredElement,
|
|
2546
3078
|
mapPoint: (point: Vec3) => Vec3,
|
|
2547
|
-
):
|
|
3079
|
+
): StoredPlacement[] {
|
|
2548
3080
|
const mapReference = (reference: RelationReference): RelationReference =>
|
|
2549
3081
|
reference.model ? reference : {...reference, ...mapElement(reference)};
|
|
2550
|
-
|
|
2551
|
-
|
|
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 = {
|
|
2552
3090
|
...constraint,
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
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
|
+
: {}),
|
|
2564
3125
|
};
|
|
2565
3126
|
valueTraces.set(mapped, valueTrace(constraint));
|
|
2566
3127
|
return mapped;
|
|
@@ -2593,7 +3154,7 @@ export class ModelObject<
|
|
|
2593
3154
|
{
|
|
2594
3155
|
geometryAnchor: scaleElement(this.geometryAnchor, factor),
|
|
2595
3156
|
elements: scaleElements(this.elements, factor),
|
|
2596
|
-
|
|
3157
|
+
placements: this.mapConstraintGeometry(
|
|
2597
3158
|
element => scaleElement(element, factor),
|
|
2598
3159
|
point => scaleFrame(translation(point), factor).position,
|
|
2599
3160
|
),
|
|
@@ -2632,7 +3193,7 @@ export class ModelObject<
|
|
|
2632
3193
|
name: 'Extrude',
|
|
2633
3194
|
geometry,
|
|
2634
3195
|
material: this.materialSnapshot,
|
|
2635
|
-
|
|
3196
|
+
placements: this.placements,
|
|
2636
3197
|
sourceRefs: this.sourceRefs,
|
|
2637
3198
|
parameters: this.allParameters(),
|
|
2638
3199
|
meshTolerance: this.meshTolerance,
|
|
@@ -2822,7 +3383,7 @@ export class ModelObject<
|
|
|
2822
3383
|
return [
|
|
2823
3384
|
...this.children,
|
|
2824
3385
|
...this.operation.inputs.map(input => input.model),
|
|
2825
|
-
...this.
|
|
3386
|
+
...this.placements.flatMap(constraintReferences),
|
|
2826
3387
|
];
|
|
2827
3388
|
}
|
|
2828
3389
|
|
|
@@ -2847,7 +3408,7 @@ export class ModelObject<
|
|
|
2847
3408
|
);
|
|
2848
3409
|
const parameters = uniqueParameters([
|
|
2849
3410
|
...this.parameters,
|
|
2850
|
-
...this.
|
|
3411
|
+
...this.placements.flatMap(
|
|
2851
3412
|
constraint => valueTrace(constraint).parameters,
|
|
2852
3413
|
),
|
|
2853
3414
|
]);
|
|
@@ -2859,6 +3420,8 @@ export class ModelObject<
|
|
|
2859
3420
|
compositionTransform: toTransform(pose),
|
|
2860
3421
|
transform: toTransform(inComposition ? pose : identityRigidTransform),
|
|
2861
3422
|
constraints,
|
|
3423
|
+
transformations: this.transformationSnapshots(solveContext),
|
|
3424
|
+
relationStages: this.relationStageSnapshots(solveContext),
|
|
2862
3425
|
origin,
|
|
2863
3426
|
elements: snapshotElements({
|
|
2864
3427
|
...this.elements,
|
|
@@ -2970,7 +3533,7 @@ export class ModelObject<
|
|
|
2970
3533
|
name: 'Loft',
|
|
2971
3534
|
geometry,
|
|
2972
3535
|
material: this.materialSnapshot,
|
|
2973
|
-
|
|
3536
|
+
placements: this.placements,
|
|
2974
3537
|
sourceRefs: inputs.flatMap(input => input.sourceRefs),
|
|
2975
3538
|
parameters: uniqueParameters(
|
|
2976
3539
|
inputs.flatMap(input => input.allParameters()),
|
|
@@ -3005,7 +3568,7 @@ export class ModelObject<
|
|
|
3005
3568
|
geometry: evaluation.geometry,
|
|
3006
3569
|
name: this.name,
|
|
3007
3570
|
material: this.materialSnapshot,
|
|
3008
|
-
|
|
3571
|
+
placements: this.placements,
|
|
3009
3572
|
sourceRefs: [this, ...others].flatMap(model => model.sourceRefs),
|
|
3010
3573
|
parameters: uniqueParameters(
|
|
3011
3574
|
[this, ...others].flatMap(model => model.allParameters()),
|
|
@@ -3130,7 +3693,7 @@ export class ModelObject<
|
|
|
3130
3693
|
private allParameters(): ParameterUsage[] {
|
|
3131
3694
|
return uniqueParameters([
|
|
3132
3695
|
...this.parameters,
|
|
3133
|
-
...this.
|
|
3696
|
+
...this.placements.flatMap(
|
|
3134
3697
|
constraint => valueTrace(constraint).parameters,
|
|
3135
3698
|
),
|
|
3136
3699
|
]);
|
|
@@ -3292,6 +3855,10 @@ export class ModelObject<
|
|
|
3292
3855
|
);
|
|
3293
3856
|
}
|
|
3294
3857
|
|
|
3858
|
+
protected override edgeReference(id: EdgeId): RelationReference {
|
|
3859
|
+
return {...rotationAxisReference(this.edge(id)), model: undefined};
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3295
3862
|
protected override vertexPosition(id: VertexId): Vec3 {
|
|
3296
3863
|
return anchorReference(this.vertex(id)).transform.position;
|
|
3297
3864
|
}
|
|
@@ -3300,17 +3867,17 @@ export class ModelObject<
|
|
|
3300
3867
|
return this.copy(init, this.operation);
|
|
3301
3868
|
}
|
|
3302
3869
|
|
|
3303
|
-
/** Fix the assembly frame once
|
|
3870
|
+
/** Fix the assembly frame once in the first member's solved local coordinates. */
|
|
3304
3871
|
private static createAssembly(
|
|
3305
3872
|
children: readonly ModelObject[],
|
|
3306
3873
|
): SolveContext {
|
|
3307
3874
|
const context = ModelObject.createSolveContext(children);
|
|
3308
|
-
|
|
3309
|
-
?
|
|
3310
|
-
|
|
3875
|
+
return children.length
|
|
3876
|
+
? transformSolveContext(
|
|
3877
|
+
context,
|
|
3878
|
+
invertTransform(children[0].solvePose(context)),
|
|
3311
3879
|
)
|
|
3312
|
-
:
|
|
3313
|
-
return transformSolveContext(context, translation(negateVector(center)));
|
|
3880
|
+
: context;
|
|
3314
3881
|
}
|
|
3315
3882
|
|
|
3316
3883
|
private requireGeometry(): ModelGeometry {
|
|
@@ -3395,7 +3962,7 @@ export class ModelObject<
|
|
|
3395
3962
|
material: this.materialSnapshot,
|
|
3396
3963
|
children: this.children,
|
|
3397
3964
|
assembly: this.assembly,
|
|
3398
|
-
|
|
3965
|
+
placements: this.placements,
|
|
3399
3966
|
elements: this.elements,
|
|
3400
3967
|
sourceRefs: this.sourceRefs,
|
|
3401
3968
|
parameters: this.parameters,
|
|
@@ -3985,6 +4552,7 @@ function normalizeReplicadSolid(shape: Shape3D): Shape3D {
|
|
|
3985
4552
|
return solid;
|
|
3986
4553
|
}
|
|
3987
4554
|
|
|
4555
|
+
/** Compose members in the first member's local frame; empty groups use the default frame. */
|
|
3988
4556
|
export function group(children: readonly Model[], name = 'Group'): GroupModel {
|
|
3989
4557
|
const runtimeChildren = children.map(child =>
|
|
3990
4558
|
requireModelObject(child, 'Every group child must be a model.'),
|
|
@@ -4092,20 +4660,40 @@ export function isConstraint(value: unknown): value is Constraint {
|
|
|
4092
4660
|
return value instanceof Constraint;
|
|
4093
4661
|
}
|
|
4094
4662
|
|
|
4095
|
-
export function
|
|
4663
|
+
export function isRelationExpression(
|
|
4096
4664
|
value: unknown,
|
|
4097
|
-
): value is
|
|
4098
|
-
return value instanceof
|
|
4665
|
+
): value is RelationExpression {
|
|
4666
|
+
return value instanceof RelationExpression;
|
|
4099
4667
|
}
|
|
4100
4668
|
|
|
4101
|
-
export function
|
|
4102
|
-
value:
|
|
4103
|
-
|
|
4104
|
-
|
|
4669
|
+
export function relationPreview(
|
|
4670
|
+
value: RelationExpression,
|
|
4671
|
+
preceding: readonly RelationExpression[] = [],
|
|
4672
|
+
): RelationPreview | undefined {
|
|
4673
|
+
return value.preview(preceding);
|
|
4105
4674
|
}
|
|
4106
4675
|
|
|
4107
|
-
|
|
4108
|
-
|
|
4676
|
+
/** The current authoring receiver remains available when a free selector throws. */
|
|
4677
|
+
export function currentRelationSelf(): RelationObject | undefined {
|
|
4678
|
+
return activeRelate?.self;
|
|
4679
|
+
}
|
|
4680
|
+
|
|
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,
|
|
4109
4697
|
sourceRef: SourceRef,
|
|
4110
4698
|
parameters: readonly ParameterUsage[],
|
|
4111
4699
|
): void {
|
|
@@ -4128,9 +4716,9 @@ export function instrumentModelOperation(
|
|
|
4128
4716
|
);
|
|
4129
4717
|
}
|
|
4130
4718
|
|
|
4131
|
-
export function
|
|
4132
|
-
constraint:
|
|
4133
|
-
):
|
|
4719
|
+
export function relationTraceReference(
|
|
4720
|
+
constraint: RelationExpression,
|
|
4721
|
+
): RelationTraceReference {
|
|
4134
4722
|
return constraint.traceReference();
|
|
4135
4723
|
}
|
|
4136
4724
|
|
|
@@ -4417,6 +5005,13 @@ export function retainModelGeometry(
|
|
|
4417
5005
|
}
|
|
4418
5006
|
|
|
4419
5007
|
export const authoringApi = Object.freeze({
|
|
5008
|
+
offset,
|
|
5009
|
+
rotate,
|
|
5010
|
+
pivot,
|
|
5011
|
+
pivotVertex,
|
|
5012
|
+
pivotPoint,
|
|
5013
|
+
axisEdge,
|
|
5014
|
+
axisLine,
|
|
4420
5015
|
cached,
|
|
4421
5016
|
font,
|
|
4422
5017
|
googleFont,
|
|
@@ -4520,7 +5115,11 @@ const kernelShape = cachedArtifact(
|
|
|
4520
5115
|
_inputs: readonly KernelArtifact<unknown>[],
|
|
4521
5116
|
compute: () => AnyShape,
|
|
4522
5117
|
) => compute(),
|
|
4523
|
-
{
|
|
5118
|
+
{
|
|
5119
|
+
key: (operation, arguments_, inputs) =>
|
|
5120
|
+
kernelOperationKey(`kernel-shape:${operation}`, arguments_, inputs),
|
|
5121
|
+
lifecycle: shapeLifecycle,
|
|
5122
|
+
},
|
|
4524
5123
|
);
|
|
4525
5124
|
|
|
4526
5125
|
function evaluateKernelShape<Shape extends AnyShape>(
|
|
@@ -4554,7 +5153,11 @@ const evaluateModelGeometry = cachedArtifact(
|
|
|
4554
5153
|
referenceBasis: result.referenceBasis,
|
|
4555
5154
|
};
|
|
4556
5155
|
},
|
|
4557
|
-
{
|
|
5156
|
+
{
|
|
5157
|
+
key: (operation, arguments_, inputs) =>
|
|
5158
|
+
kernelOperationKey(`model-geometry:${operation}`, arguments_, inputs),
|
|
5159
|
+
lifecycle: modelGeometryLifecycle,
|
|
5160
|
+
},
|
|
4558
5161
|
);
|
|
4559
5162
|
|
|
4560
5163
|
function createModelGeometryValue(
|
|
@@ -4713,7 +5316,7 @@ type PlanarSketch = Readonly<{
|
|
|
4713
5316
|
/** Internal bridge from the kernel-independent sketch definition to model geometry. */
|
|
4714
5317
|
function sketchFaceModel(
|
|
4715
5318
|
region: SketchRegion,
|
|
4716
|
-
|
|
5319
|
+
placements: readonly StoredPlacement[],
|
|
4717
5320
|
source: SketchFrame,
|
|
4718
5321
|
): FaceModel {
|
|
4719
5322
|
const curves = [region.outer, ...region.holes].map(loop =>
|
|
@@ -4735,7 +5338,7 @@ function sketchFaceModel(
|
|
|
4735
5338
|
return ModelObject.create<PlanarElements, 'face'>({
|
|
4736
5339
|
kind: 'face',
|
|
4737
5340
|
name: 'Sketch face',
|
|
4738
|
-
|
|
5341
|
+
placements,
|
|
4739
5342
|
geometry,
|
|
4740
5343
|
geometryAnchor: plane,
|
|
4741
5344
|
elements: {plane},
|
|
@@ -4908,12 +5511,14 @@ function solidElements(bounds: LocalBounds): StoredElements {
|
|
|
4908
5511
|
return {axis: {kind: 'line', transform: translation(boundsCenter(bounds))}};
|
|
4909
5512
|
}
|
|
4910
5513
|
|
|
4911
|
-
function constraintReferences(constraint:
|
|
5514
|
+
function constraintReferences(constraint: StoredPlacement): RelationObject[] {
|
|
4912
5515
|
return [
|
|
4913
|
-
constraint.
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
5516
|
+
...(constraint.kind === 'transformation'
|
|
5517
|
+
? []
|
|
5518
|
+
: [constraint.source.model, constraint.target.model]),
|
|
5519
|
+
...(constraint.kind === 'transformation' ? constraint.actions : []).map(
|
|
5520
|
+
action =>
|
|
5521
|
+
'pivot' in action ? selectionReference(action.pivot)?.model : undefined,
|
|
4917
5522
|
),
|
|
4918
5523
|
].filter((model): model is RelationObject => !!model);
|
|
4919
5524
|
}
|