@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.
Files changed (43) hide show
  1. package/README.md +42 -5
  2. package/bld/chunks/{chunk-J77FMYFY.js → chunk-4WLVDYUJ.js} +992 -545
  3. package/bld/chunks/chunk-4WLVDYUJ.js.map +7 -0
  4. package/bld/chunks/{chunk-PAIFBNU4.js → chunk-C3AJ6WNB.js} +2 -2
  5. package/bld/chunks/{chunk-OQLJDJIF.js → chunk-TCMEEKWL.js} +2 -2
  6. package/bld/chunks/{chunk-ZMT6EFHL.js → chunk-VHSPHJ5F.js} +3 -3
  7. package/bld/library/bound-solver.d.ts +29 -5
  8. package/bld/library/bound-solver.d.ts.map +1 -1
  9. package/bld/library/cached.d.ts +5 -3
  10. package/bld/library/cached.d.ts.map +1 -1
  11. package/bld/library/index.d.ts +4 -4
  12. package/bld/library/index.d.ts.map +1 -1
  13. package/bld/library/index.js +17 -3
  14. package/bld/library/kernel-cache.d.ts +6 -3
  15. package/bld/library/kernel-cache.d.ts.map +1 -1
  16. package/bld/library/relation-solver.d.ts +12 -7
  17. package/bld/library/relation-solver.d.ts.map +1 -1
  18. package/bld/library/replicad.js +2 -2
  19. package/bld/library/runtime.d.ts +218 -82
  20. package/bld/library/runtime.d.ts.map +1 -1
  21. package/bld/library/sketch.d.ts +2 -2
  22. package/bld/library/sketch.d.ts.map +1 -1
  23. package/bld/library/topology.d.ts +2 -0
  24. package/bld/library/topology.d.ts.map +1 -1
  25. package/bld/node/index.js +19 -5
  26. package/bld/node/replicad.js +4 -4
  27. package/bld/tooling/index.d.ts +3 -3
  28. package/bld/tooling/index.d.ts.map +1 -1
  29. package/bld/tooling/index.js +18 -10
  30. package/package.json +1 -1
  31. package/src/library/bound-solver.ts +103 -73
  32. package/src/library/cached.ts +29 -8
  33. package/src/library/index.ts +15 -4
  34. package/src/library/kernel-cache.ts +37 -5
  35. package/src/library/relation-solver.ts +194 -110
  36. package/src/library/runtime.ts +1046 -442
  37. package/src/library/sketch.ts +3 -3
  38. package/src/library/topology.ts +13 -2
  39. package/src/tooling/index.ts +15 -7
  40. package/bld/chunks/chunk-J77FMYFY.js.map +0 -7
  41. /package/bld/chunks/{chunk-PAIFBNU4.js.map → chunk-C3AJ6WNB.js.map} +0 -0
  42. /package/bld/chunks/{chunk-OQLJDJIF.js.map → chunk-TCMEEKWL.js.map} +0 -0
  43. /package/bld/chunks/{chunk-ZMT6EFHL.js.map → chunk-VHSPHJ5F.js.map} +0 -0
@@ -132,12 +132,12 @@ var binaryTypes = {
132
132
  };
133
133
  function encodeKernelArtifact(signature, value) {
134
134
  const chunks = [];
135
- let offset = 0;
135
+ let offset2 = 0;
136
136
  const references2 = /* @__PURE__ */ new Map();
137
137
  function binary(kind, bytes2) {
138
- const node = [kind, [offset, bytes2.byteLength]];
138
+ const node = [kind, [offset2, bytes2.byteLength]];
139
139
  chunks.push(bytes2);
140
- offset += bytes2.byteLength;
140
+ offset2 += bytes2.byteLength;
141
141
  return node;
142
142
  }
143
143
  __name(binary, "binary");
@@ -202,13 +202,13 @@ function encodeKernelArtifact(signature, value) {
202
202
  }
203
203
  __name(encodeObject, "encodeObject");
204
204
  const metadata = encoder.encode(JSON.stringify([signature, encode(value)]));
205
- const bytes = new Uint8Array(4 + metadata.byteLength + offset);
205
+ const bytes = new Uint8Array(4 + metadata.byteLength + offset2);
206
206
  new DataView(bytes.buffer).setUint32(0, metadata.byteLength, true);
207
207
  bytes.set(metadata, 4);
208
- offset = 4 + metadata.byteLength;
208
+ offset2 = 4 + metadata.byteLength;
209
209
  for (const chunk of chunks) {
210
- bytes.set(chunk, offset);
211
- offset += chunk.byteLength;
210
+ bytes.set(chunk, offset2);
211
+ offset2 += chunk.byteLength;
212
212
  }
213
213
  return bytes;
214
214
  }
@@ -280,20 +280,20 @@ function decodeKernelArtifact(bytes, signature) {
280
280
  return object;
281
281
  }
282
282
  case "view": {
283
- const [name, buffer, offset, length] = data;
283
+ const [name, buffer, offset2, length] = data;
284
284
  if (!Object.hasOwn(binaryTypes, name))
285
285
  throw new Error("Invalid kernel artifact view");
286
286
  return register(
287
- new binaryTypes[name](decode(buffer), offset, length)
287
+ new binaryTypes[name](decode(buffer), offset2, length)
288
288
  );
289
289
  }
290
290
  case "shape":
291
291
  case "ArrayBuffer": {
292
- const [offset, length] = data;
293
- if (!Number.isSafeInteger(offset) || !Number.isSafeInteger(length) || offset < 0 || length < 0 || start + offset + length > bytes.byteLength) {
292
+ const [offset2, length] = data;
293
+ if (!Number.isSafeInteger(offset2) || !Number.isSafeInteger(length) || offset2 < 0 || length < 0 || start + offset2 + length > bytes.byteLength) {
294
294
  throw new Error("Invalid kernel artifact binary range");
295
295
  }
296
- const chunk = bytes.slice(start + offset, start + offset + length);
296
+ const chunk = bytes.slice(start + offset2, start + offset2 + length);
297
297
  if (kind === "ArrayBuffer") return register(chunk.buffer);
298
298
  const shape = decodeShape(chunk);
299
299
  shapes.push(shape);
@@ -369,6 +369,7 @@ __name(estimateRetainedBytes, "estimateRetainedBytes");
369
369
  // packages/core/src/library/kernel-cache.ts
370
370
  function createComputationCache({
371
371
  maximumBytes = 2 * 1024 ** 3,
372
+ minimumPersistenceMilliseconds = 1,
372
373
  nativeAllocatedBytes
373
374
  }) {
374
375
  const entries = /* @__PURE__ */ new Map();
@@ -386,6 +387,15 @@ function createComputationCache({
386
387
  const persisted = /* @__PURE__ */ new Set();
387
388
  const pendingPersistence = /* @__PURE__ */ new Map();
388
389
  let externalBytes = 0;
390
+ function setKernelCacheBudget2(bytes) {
391
+ maximumBytes = bytes;
392
+ evictHistoricalEntries();
393
+ }
394
+ __name(setKernelCacheBudget2, "setKernelCacheBudget");
395
+ function setKernelCachePersistenceThreshold2(milliseconds) {
396
+ minimumPersistenceMilliseconds = milliseconds;
397
+ }
398
+ __name(setKernelCachePersistenceThreshold2, "setKernelCachePersistenceThreshold");
389
399
  function setKernelExternalBytes2(bytes) {
390
400
  externalBytes = bytes;
391
401
  evictHistoricalEntries();
@@ -426,8 +436,10 @@ function createComputationCache({
426
436
  function evaluateCachedArtifact2(key, lifecycle, compute, codec) {
427
437
  const hit = findKernelOperation2(key, lifecycle, codec);
428
438
  if (hit) return hit;
439
+ const started = performance.now();
429
440
  const value = compute();
430
- acceptKernelOperation2(key, lifecycle, value, codec);
441
+ const milliseconds = performance.now() - started;
442
+ acceptKernelOperation2(key, lifecycle, value, milliseconds, codec);
431
443
  return { id: key.id, value };
432
444
  }
433
445
  __name(evaluateCachedArtifact2, "evaluateCachedArtifact");
@@ -455,8 +467,8 @@ function createComputationCache({
455
467
  function lookup(key, lifecycle, codec, read) {
456
468
  currentEvaluation?.checkCancelled?.();
457
469
  const { id, signature } = key;
458
- let cached2 = entries.get(id);
459
- if (!cached2 && codec !== false) {
470
+ let cached = entries.get(id);
471
+ if (!cached && codec !== false) {
460
472
  const bytes = read();
461
473
  if (bytes) {
462
474
  let restored;
@@ -468,25 +480,26 @@ function createComputationCache({
468
480
  misses += 1;
469
481
  return void 0;
470
482
  }
471
- cached2 = retainEntry(key, lifecycle, restored);
483
+ cached = retainEntry(key, lifecycle, restored, true);
472
484
  persistentHits += 1;
473
485
  persisted.add(id);
474
486
  }
475
487
  }
476
- if (!cached2) {
488
+ if (!cached) {
477
489
  misses += 1;
478
490
  return void 0;
479
491
  }
480
- if (cached2.signature !== signature)
492
+ if (cached.signature !== signature)
481
493
  throw new Error(`Kernel operation cache identity collision: ${id}`);
482
494
  hits += 1;
483
- const value = cached2.instantiate(cached2.value);
484
- if (codec !== false) persist(key, cached2.value, codec, true);
485
- touchEntry(id, cached2);
495
+ const value = cached.instantiate(cached.value);
496
+ if (cached.persistenceEligible && codec !== false)
497
+ persist(key, cached.value, codec, true);
498
+ touchEntry(id, cached);
486
499
  return { id, value };
487
500
  }
488
501
  __name(lookup, "lookup");
489
- function acceptKernelOperation2(key, lifecycle, value, codec) {
502
+ function acceptKernelOperation2(key, lifecycle, value, milliseconds, codec) {
490
503
  const existing = entries.get(key.id);
491
504
  if (existing) {
492
505
  if (existing.signature !== key.signature)
@@ -501,8 +514,14 @@ function createComputationCache({
501
514
  lifecycle.release(value);
502
515
  throw error;
503
516
  }
504
- const entry = retainEntry(key, lifecycle, retained);
505
- if (codec !== false) persist(key, retained, codec);
517
+ const entry = retainEntry(
518
+ key,
519
+ lifecycle,
520
+ retained,
521
+ milliseconds >= minimumPersistenceMilliseconds
522
+ );
523
+ if (entry.persistenceEligible && codec !== false)
524
+ persist(key, retained, codec);
506
525
  touchEntry(key.id, entry);
507
526
  }
508
527
  __name(acceptKernelOperation2, "acceptKernelOperation");
@@ -548,8 +567,9 @@ function createComputationCache({
548
567
  pendingPersistence.clear();
549
568
  }
550
569
  __name(flushPendingPersistence, "flushPendingPersistence");
551
- function retainEntry(key, lifecycle, retained) {
570
+ function retainEntry(key, lifecycle, retained, persistenceEligible) {
552
571
  const entry = {
572
+ persistenceEligible,
553
573
  estimatedBytes: 256 + estimateRetainedBytes(key.signature) + lifecycle.estimateBytes(retained),
554
574
  signature: key.signature,
555
575
  value: retained,
@@ -624,6 +644,8 @@ function createComputationCache({
624
644
  beginKernelOperationEvaluation: beginKernelOperationEvaluation2,
625
645
  clearKernelOperationCache: clearKernelOperationCache2,
626
646
  kernelOperationCacheStats: kernelOperationCacheStats2,
647
+ setKernelCacheBudget: setKernelCacheBudget2,
648
+ setKernelCachePersistenceThreshold: setKernelCachePersistenceThreshold2,
627
649
  setKernelArtifactStore: setKernelArtifactStore2,
628
650
  findKernelOperation: findKernelOperation2,
629
651
  findKernelOperations: findKernelOperations2,
@@ -642,6 +664,8 @@ var {
642
664
  beginKernelOperationEvaluation,
643
665
  clearKernelOperationCache,
644
666
  kernelOperationCacheStats,
667
+ setKernelCacheBudget,
668
+ setKernelCachePersistenceThreshold,
645
669
  setKernelArtifactStore,
646
670
  findKernelOperation,
647
671
  findKernelOperations,
@@ -706,19 +730,19 @@ var dataLifecycle = {
706
730
  release() {
707
731
  }
708
732
  };
709
- function cached(compute, options) {
733
+ function cache(compute, args, options) {
710
734
  const operation = cachedArtifact(
711
- (...args) => {
712
- const value = compute(...args);
735
+ (...args2) => {
736
+ const value = compute(...args2);
713
737
  if (value && typeof value === "object" && "then" in value && typeof value.then === "function")
714
- throw new Error("cached() requires a synchronous computation.");
738
+ throw new Error("cache() requires a synchronous computation.");
715
739
  return value;
716
740
  },
717
741
  { identity: compute, codec: options }
718
742
  );
719
- return (...args) => operation(...args).value;
743
+ return args === void 0 ? (...args2) => operation(...args2).value : operation(...args).value;
720
744
  }
721
- __name(cached, "cached");
745
+ __name(cache, "cache");
722
746
  function cachedArtifact(compute, {
723
747
  key,
724
748
  lifecycle = dataLifecycle,
@@ -735,7 +759,7 @@ function cachedArtifact(compute, {
735
759
  ));
736
760
  const find = /* @__PURE__ */ __name((key2) => findKernelOperation(key2, lifecycle, persistence), "find");
737
761
  const findMany = /* @__PURE__ */ __name((keys) => findKernelOperations(keys, lifecycle, persistence), "findMany");
738
- const accept = /* @__PURE__ */ __name((key2, value) => acceptKernelOperation(key2, lifecycle, value, persistence), "accept");
762
+ const accept = /* @__PURE__ */ __name((key2, value, milliseconds) => acceptKernelOperation(key2, lifecycle, value, milliseconds, persistence), "accept");
739
763
  return Object.assign(
740
764
  (...args) => {
741
765
  return evaluateCachedArtifact(
@@ -1974,12 +1998,12 @@ function candidates(first, second, tolerance2) {
1974
1998
  const gap = Math.abs(perpendicular2);
1975
1999
  if (gap > second.radius + tolerance2) return [];
1976
2000
  const normalized = gap / second.radius;
1977
- const offset = Math.abs(gap - second.radius) <= tolerance2 ? 0 : second.radius * Math.sqrt(Math.max(0, (1 - normalized) * (1 + normalized)));
2001
+ const offset2 = Math.abs(gap - second.radius) <= tolerance2 ? 0 : second.radius * Math.sqrt(Math.max(0, (1 - normalized) * (1 + normalized)));
1978
2002
  const foot2 = [
1979
2003
  second.center[0] + perpendicular2 * unit3[1],
1980
2004
  second.center[1] - perpendicular2 * unit3[0]
1981
2005
  ];
1982
- return [-offset, offset].map((along2) => [
2006
+ return [-offset2, offset2].map((along2) => [
1983
2007
  foot2[0] + along2 * unit3[0],
1984
2008
  foot2[1] + along2 * unit3[1]
1985
2009
  ]);
@@ -2001,9 +2025,9 @@ function candidates(first, second, tolerance2) {
2001
2025
  first.center[0] + along * scale3 * unit2[0],
2002
2026
  first.center[1] + along * scale3 * unit2[1]
2003
2027
  ];
2004
- return [-height, height].map((offset) => [
2005
- foot[0] - offset * unit2[1],
2006
- foot[1] + offset * unit2[0]
2028
+ return [-height, height].map((offset2) => [
2029
+ foot[0] - offset2 * unit2[1],
2030
+ foot[1] + offset2 * unit2[0]
2007
2031
  ]);
2008
2032
  }
2009
2033
  __name(candidates, "candidates");
@@ -3333,7 +3357,17 @@ function stableEdgeGroups(shape, topology, groups) {
3333
3357
  topology,
3334
3358
  groups.map((group2) => group2.edgeId)
3335
3359
  );
3336
- return groups.map((group2, index) => ({ ...group2, edgeId: ids[index] }));
3360
+ const linear = new Map(
3361
+ edges.map((edge) => [
3362
+ edge.hashCode,
3363
+ edge.geomType === "LINE"
3364
+ ])
3365
+ );
3366
+ return groups.map((group2, index) => ({
3367
+ ...group2,
3368
+ edgeId: ids[index],
3369
+ linear: linear.get(group2.edgeId) ?? false
3370
+ }));
3337
3371
  } finally {
3338
3372
  deleteShapes(edges);
3339
3373
  }
@@ -5163,6 +5197,28 @@ function modelMaterialColor(snapshot) {
5163
5197
  __name(modelMaterialColor, "modelMaterialColor");
5164
5198
 
5165
5199
  // packages/core/src/library/bound-solver.ts
5200
+ function externalRotationFrame(action, self, reference, inverse = false) {
5201
+ if ("axis" in action) {
5202
+ const axis = composeTransforms(rotation(reference), action.axis);
5203
+ return {
5204
+ center: axis.position,
5205
+ quaternion: axisRotation(axis, inverse ? -action.angle : action.angle).quaternion
5206
+ };
5207
+ }
5208
+ const local = inverse ? invertTransform(rotation(action.rotation)) : rotation(action.rotation);
5209
+ const before = inverse ? composeTransforms(rotation(self), local) : rotation(self);
5210
+ return {
5211
+ center: addVectors(
5212
+ rotateVector(action.point, reference),
5213
+ rotateVector(action.displacement, before.quaternion)
5214
+ ),
5215
+ quaternion: composeTransforms(
5216
+ composeTransforms(before, local),
5217
+ invertTransform(before)
5218
+ ).quaternion
5219
+ };
5220
+ }
5221
+ __name(externalRotationFrame, "externalRotationFrame");
5166
5222
  var axes = [
5167
5223
  [1, 0, 0],
5168
5224
  [0, 1, 0],
@@ -5194,28 +5250,15 @@ function solveBodies(bodies) {
5194
5250
  "Cyclic external rotation axes cannot determine an authored orientation."
5195
5251
  );
5196
5252
  visiting.add(index);
5197
- const candidates2 = bodies[index].relations.filter((r) => r.rotations.length).map((relation) => {
5198
- let pose2 = identityRigidTransform;
5199
- for (const action of relation.rotations) {
5200
- pose2 = "local" in action ? composeTransforms(pose2, action.local) : composeTransforms(
5201
- axisRotation(
5202
- composeTransforms(seed2(action.body), action.axis),
5203
- action.angle
5204
- ),
5205
- pose2
5206
- );
5207
- }
5208
- return pose2;
5209
- });
5210
- const pose = candidates2[0] ?? identityRigidTransform;
5211
- if (candidates2.some(
5212
- (candidate) => !transformsAreEquivalent(
5213
- rotation(candidate.quaternion),
5214
- rotation(pose.quaternion)
5215
- )
5216
- )) {
5217
- throw new Error(
5218
- `Conflicting explicit rotations for ${bodies[index].name}.`
5253
+ let pose = bodies[index].initial ?? identityRigidTransform;
5254
+ for (const action of bodies[index].transformations ?? []) {
5255
+ if ("offset" in action) continue;
5256
+ pose = "local" in action ? composeTransforms(pose, action.local) : "point" in action ? composeTransforms(pose, rotation(action.rotation)) : composeTransforms(
5257
+ axisRotation(
5258
+ composeTransforms(seed2(action.body), action.axis),
5259
+ action.angle
5260
+ ),
5261
+ pose
5219
5262
  );
5220
5263
  }
5221
5264
  visiting.delete(index);
@@ -5232,17 +5275,18 @@ function solveBodies(bodies) {
5232
5275
  const known = positioned.get(index);
5233
5276
  if (known) return known;
5234
5277
  let pose = {
5235
- quaternion: identityRigidTransform.quaternion,
5278
+ quaternion: (bodies[index].initial ?? identityRigidTransform).quaternion,
5236
5279
  position: {
5237
- constant: origin,
5280
+ constant: (bodies[index].initial ?? identityRigidTransform).position,
5238
5281
  columns: variables.flatMap(
5239
5282
  (body) => axes.map((axis) => body === index ? axis : origin)
5240
5283
  )
5241
5284
  }
5242
5285
  };
5243
- const actions = bodies[index].relations.find((relation) => relation.rotations.length)?.rotations ?? [];
5244
- for (const action of actions) {
5245
- if ("local" in action) {
5286
+ for (const action of bodies[index].transformations ?? []) {
5287
+ if ("offset" in action) {
5288
+ pose = { ...pose, position: shiftPoint(pose.position, action.offset) };
5289
+ } else if ("local" in action) {
5246
5290
  pose = {
5247
5291
  position: shiftPoint(
5248
5292
  pose.position,
@@ -5255,12 +5299,12 @@ function solveBodies(bodies) {
5255
5299
  };
5256
5300
  } else {
5257
5301
  const axisPose = positionBody(action.body);
5258
- const axis = composeTransforms(
5259
- rotation(axisPose.quaternion),
5260
- action.axis
5302
+ const { center, quaternion: q } = externalRotationFrame(
5303
+ action,
5304
+ pose.quaternion,
5305
+ axisPose.quaternion
5261
5306
  );
5262
- const q = axisRotation(axis, action.angle).quaternion;
5263
- const axisPoint = shiftPoint(axisPose.position, axis.position);
5307
+ const axisPoint = shiftPoint(axisPose.position, center);
5264
5308
  const rotatedAxis = rotatePoint(axisPoint, q);
5265
5309
  pose = {
5266
5310
  position: addPoints(
@@ -5282,12 +5326,16 @@ function solveBodies(bodies) {
5282
5326
  const preferences = /* @__PURE__ */ new Map();
5283
5327
  bodies.forEach(
5284
5328
  (body, owner) => body.relations.forEach((relation) => {
5285
- const baseline = undoRotations(poses[owner], relation.rotations, poses);
5329
+ const baseline = undoActions(
5330
+ poses[owner],
5331
+ body.transformations ?? [],
5332
+ poses
5333
+ );
5286
5334
  for (let axis = 0; axis < 3; axis++) {
5287
5335
  const preference = {
5288
5336
  id: relation.id,
5289
5337
  coefficients: baseline.position.columns.map((column) => column[axis]),
5290
- value: -baseline.position.constant[axis]
5338
+ value: (body.initial?.position[axis] ?? 0) - baseline.position.constant[axis]
5291
5339
  };
5292
5340
  preferences.set(
5293
5341
  JSON.stringify([preference.coefficients, preference.value]),
@@ -5315,14 +5363,8 @@ function solveBodies(bodies) {
5315
5363
  source.position,
5316
5364
  rotateVector(center, targetFrame.quaternion)
5317
5365
  );
5318
- const targetPoint = shiftPoint(
5319
- target.position,
5320
- addVectors(
5321
- targetFrame.position,
5322
- rotateVector(relation.offset ?? origin, targetFrame.quaternion)
5323
- )
5324
- );
5325
- for (const localAxis of relation.offset ? axes : [axes[1]]) {
5366
+ const targetPoint = shiftPoint(target.position, targetFrame.position);
5367
+ for (const localAxis of [axes[1]]) {
5326
5368
  const normal = rotateVector(localAxis, targetFrame.quaternion);
5327
5369
  equations.push({
5328
5370
  id: relation.id,
@@ -5357,8 +5399,8 @@ function solveBodies(bodies) {
5357
5399
  }));
5358
5400
  }
5359
5401
  __name(solveBodies, "solveBodies");
5360
- function shiftPoint(point3, offset) {
5361
- return { ...point3, constant: addVectors(point3.constant, offset) };
5402
+ function shiftPoint(point3, offset2) {
5403
+ return { ...point3, constant: addVectors(point3.constant, offset2) };
5362
5404
  }
5363
5405
  __name(shiftPoint, "shiftPoint");
5364
5406
  function rotatePoint(point3, quaternion) {
@@ -5377,9 +5419,14 @@ function addPoints(left, right) {
5377
5419
  };
5378
5420
  }
5379
5421
  __name(addPoints, "addPoints");
5380
- function undoRotations(pose, actions, poses) {
5422
+ function undoActions(pose, actions, poses) {
5381
5423
  for (const action of [...actions].reverse()) {
5382
- if ("local" in action) {
5424
+ if ("offset" in action) {
5425
+ pose = {
5426
+ ...pose,
5427
+ position: shiftPoint(pose.position, negateVector(action.offset))
5428
+ };
5429
+ } else if ("local" in action) {
5383
5430
  const inverse = invertTransform(action.local);
5384
5431
  pose = {
5385
5432
  position: shiftPoint(
@@ -5393,12 +5440,13 @@ function undoRotations(pose, actions, poses) {
5393
5440
  };
5394
5441
  } else {
5395
5442
  const axisPose = poses[action.body];
5396
- const axis = composeTransforms(
5397
- rotation(axisPose.quaternion),
5398
- action.axis
5443
+ const { center, quaternion: q } = externalRotationFrame(
5444
+ action,
5445
+ pose.quaternion,
5446
+ axisPose.quaternion,
5447
+ true
5399
5448
  );
5400
- const q = axisRotation(axis, -action.angle).quaternion;
5401
- const axisPoint = shiftPoint(axisPose.position, axis.position);
5449
+ const axisPoint = shiftPoint(axisPose.position, center);
5402
5450
  const rotatedAxis = rotatePoint(axisPoint, q);
5403
5451
  pose = {
5404
5452
  position: addPoints(
@@ -5414,7 +5462,7 @@ function undoRotations(pose, actions, poses) {
5414
5462
  }
5415
5463
  return pose;
5416
5464
  }
5417
- __name(undoRotations, "undoRotations");
5465
+ __name(undoActions, "undoActions");
5418
5466
  function solveTranslations(equations, preferences, dimension) {
5419
5467
  const basis = [];
5420
5468
  const magnitude = Math.max(
@@ -5503,54 +5551,40 @@ function solveTranslations(equations, preferences, dimension) {
5503
5551
  __name(solveTranslations, "solveTranslations");
5504
5552
 
5505
5553
  // packages/core/src/library/relation-solver.ts
5506
- function beforeRelation(pose, relation, poses, owner) {
5507
- for (const action of [...relation.rotations].reverse())
5508
- pose = "local" in action ? composeTransforms(pose, invertTransform(action.local)) : composeTransforms(
5509
- axisRotation(
5510
- composeTransforms(poses[action.body], action.axis),
5511
- -action.angle
5512
- ),
5513
- pose
5514
- );
5515
- if (relation.kind === "align" && relation.offset) {
5516
- const target = relation.target.body === owner ? pose : poses[relation.target.body];
5517
- const frame = composeTransforms(target, relation.target.transform);
5518
- pose = {
5519
- ...pose,
5520
- position: subtract2(
5521
- pose.position,
5522
- rotateVector(relation.offset, frame.quaternion)
5523
- )
5524
- };
5554
+ function applyExternalRotation(pose, action, reference, inverse = false) {
5555
+ const { center, quaternion } = externalRotationFrame(
5556
+ action,
5557
+ pose.quaternion,
5558
+ reference.quaternion,
5559
+ inverse
5560
+ );
5561
+ const point3 = addVectors(reference.position, center);
5562
+ return composeTransforms(
5563
+ { quaternion, position: subtract2(point3, rotateVector(point3, quaternion)) },
5564
+ pose
5565
+ );
5566
+ }
5567
+ __name(applyExternalRotation, "applyExternalRotation");
5568
+ function beforeTransformations(pose, relation, poses) {
5569
+ for (const action of [...relation.actions].reverse()) {
5570
+ if ("offset" in action) {
5571
+ pose = { ...pose, position: subtract2(pose.position, action.offset) };
5572
+ } else
5573
+ pose = "local" in action ? composeTransforms(pose, invertTransform(action.local)) : applyExternalRotation(pose, action, poses[action.body], true);
5525
5574
  }
5526
5575
  return pose;
5527
5576
  }
5528
- __name(beforeRelation, "beforeRelation");
5529
- function afterRelation(pose, relation, poses, owner) {
5530
- if (relation.kind === "align" && relation.offset) {
5531
- const target = relation.target.body === owner ? pose : poses[relation.target.body];
5532
- pose = {
5533
- ...pose,
5534
- position: addVectors(
5535
- pose.position,
5536
- rotateVector(
5537
- relation.offset,
5538
- composeTransforms(target, relation.target.transform).quaternion
5539
- )
5540
- )
5541
- };
5577
+ __name(beforeTransformations, "beforeTransformations");
5578
+ function afterTransformations(pose, relation, poses) {
5579
+ for (const action of relation.actions) {
5580
+ if ("offset" in action) {
5581
+ pose = { ...pose, position: addVectors(pose.position, action.offset) };
5582
+ } else
5583
+ pose = "local" in action ? composeTransforms(pose, action.local) : applyExternalRotation(pose, action, poses[action.body]);
5542
5584
  }
5543
- for (const action of relation.rotations)
5544
- pose = "local" in action ? composeTransforms(pose, action.local) : composeTransforms(
5545
- axisRotation(
5546
- composeTransforms(poses[action.body], action.axis),
5547
- action.angle
5548
- ),
5549
- pose
5550
- );
5551
5585
  return pose;
5552
5586
  }
5553
- __name(afterRelation, "afterRelation");
5587
+ __name(afterTransformations, "afterTransformations");
5554
5588
  function directionRotation(from, to) {
5555
5589
  const product = dot(from, to);
5556
5590
  if (product < -1 + 1e-10) return [...perpendicular(from), 0];
@@ -5673,8 +5707,19 @@ function solveBodies2(bodies) {
5673
5707
  if (relation.kind === "align")
5674
5708
  validateAlignment(relation.source.geometry, relation.target.geometry);
5675
5709
  const active = bodies.flatMap(
5676
- (body, i) => body.relations.length ? [i] : []
5710
+ (body, i) => body.relations.length || body.transformations?.length ? [i] : []
5677
5711
  );
5712
+ const unplace = /* @__PURE__ */ __name((pose, owner, poses2) => beforeTransformations(
5713
+ pose,
5714
+ { actions: bodies[owner].transformations ?? [] },
5715
+ poses2
5716
+ ), "unplace");
5717
+ const place = /* @__PURE__ */ __name((pose, owner, poses2) => afterTransformations(
5718
+ pose,
5719
+ { actions: bodies[owner].transformations ?? [] },
5720
+ poses2
5721
+ ), "place");
5722
+ const baselinePose = /* @__PURE__ */ __name((pose, poses2, owner) => unplace(pose, owner, poses2), "baselinePose");
5678
5723
  const flexible = bodies.map(
5679
5724
  (body) => body.relations.some((r) => r.kind === "align")
5680
5725
  );
@@ -5685,26 +5730,13 @@ function solveBodies2(bodies) {
5685
5730
  const unique = [
5686
5731
  ...new Map(relations.map((entry) => [key(entry), entry])).entries()
5687
5732
  ].sort(([a], [b]) => a.localeCompare(b)).map(([, entry]) => entry);
5688
- let poses = bodies.map(() => identityRigidTransform);
5689
- for (const owner of active) {
5690
- const authored = unique.find(
5691
- (entry) => entry.owner === owner && entry.relation.rotations.length
5692
- )?.relation ?? unique.find(
5693
- (entry) => entry.owner === owner && entry.relation.kind === "align" && entry.relation.offset
5694
- )?.relation;
5695
- if (authored)
5696
- poses[owner] = afterRelation(
5697
- identityRigidTransform,
5698
- authored,
5699
- poses,
5700
- owner
5701
- );
5702
- }
5733
+ let poses = bodies.map((body) => body.initial ?? identityRigidTransform);
5734
+ for (const owner of active) poses[owner] = place(poses[owner], owner, poses);
5703
5735
  for (const { owner, relation } of unique) {
5704
5736
  if (relation.kind !== "align") continue;
5705
5737
  const sourceSelf = relation.source.body === owner;
5706
5738
  if (relation.target.body === relation.source.body) continue;
5707
- const baseline = beforeRelation(poses[owner], relation, poses, owner);
5739
+ const baseline = baselinePose(poses[owner], poses, owner);
5708
5740
  const source = transformGeometry(
5709
5741
  relation.source.geometry,
5710
5742
  relation.source.body === owner ? baseline : poses[relation.source.body]
@@ -5713,12 +5745,7 @@ function solveBodies2(bodies) {
5713
5745
  relation.target.body === owner ? baseline : poses[relation.target.body]
5714
5746
  );
5715
5747
  const seed2 = sourceSelf ? alignmentSeed(source, target) : alignmentSeed(target, source);
5716
- poses[owner] = afterRelation(
5717
- composeTransforms(seed2, baseline),
5718
- relation,
5719
- poses,
5720
- owner
5721
- );
5748
+ poses[owner] = place(composeTransforms(seed2, baseline), owner, poses);
5722
5749
  }
5723
5750
  const geometryScale = Math.max(
5724
5751
  1,
@@ -5728,12 +5755,23 @@ function solveBodies2(bodies) {
5728
5755
  ) : []
5729
5756
  )
5730
5757
  );
5731
- const variables = active.flatMap(
5732
- (body) => Array.from({ length: 6 }, (_, axis) => ({ body, axis }))
5758
+ const fullVariables = active.flatMap(
5759
+ (body) => (
5760
+ // Bound-only bodies without authored rotations have exactly identity
5761
+ // orientation. Do not numerically perturb those fixed axes: native bound
5762
+ // queries can introduce noise that prevents convergence at the identity.
5763
+ Array.from(
5764
+ {
5765
+ length: flexible[body] || bodies[body].transformations?.some((action) => !("offset" in action)) ? 6 : 3
5766
+ },
5767
+ (_, axis) => ({ body, axis })
5768
+ )
5769
+ )
5733
5770
  );
5771
+ let variables = fullVariables.filter((variable) => variable.axis < 3);
5734
5772
  const residual2 = /* @__PURE__ */ __name((candidate) => [
5735
5773
  ...unique.flatMap(({ owner, relation: r }) => {
5736
- const baseline = beforeRelation(candidate[owner], r, candidate, owner);
5774
+ const baseline = baselinePose(candidate[owner], candidate, owner);
5737
5775
  const source = r.source.body === owner ? baseline : candidate[r.source.body], target = r.target.body === owner ? baseline : candidate[r.target.body];
5738
5776
  if (r.kind === "align")
5739
5777
  return alignmentResidual(
@@ -5755,27 +5793,25 @@ function solveBodies2(bodies) {
5755
5793
  source.position,
5756
5794
  rotateVector(center, frame.quaternion)
5757
5795
  );
5758
- const b = composeTransforms(
5759
- frame,
5760
- translation(r.offset ?? origin)
5761
- ).position;
5796
+ const b = frame.position;
5762
5797
  const delta = rotateVector(
5763
5798
  subtract2(a, b),
5764
5799
  invertTransform(frame).quaternion
5765
5800
  );
5766
- return r.offset ? [...delta] : [delta[1]];
5801
+ return [delta[1]];
5767
5802
  }),
5768
5803
  ...active.flatMap((owner) => {
5769
5804
  if (flexible[owner]) return [];
5770
- const rotations = bodies[owner].relations.filter((r) => r.rotations.length);
5771
- return rotations.length ? rotations.flatMap(
5772
- (r) => beforeRelation(
5773
- candidate[owner],
5774
- r,
5775
- candidate,
5776
- owner
5777
- ).quaternion.slice(0, 3)
5778
- ) : candidate[owner].quaternion.slice(0, 3);
5805
+ const initial = bodies[owner].initial ?? identityRigidTransform;
5806
+ const unplaced = unplace(candidate[owner], owner, candidate);
5807
+ const orientationResidual = /* @__PURE__ */ __name((pose) => composeTransforms(
5808
+ invertTransform(rotation(initial.quaternion)),
5809
+ rotation(pose.quaternion)
5810
+ ).quaternion.slice(0, 3), "orientationResidual");
5811
+ return [
5812
+ ...bodies[owner].relations.length ? [] : subtract2(unplaced.position, initial.position),
5813
+ ...orientationResidual(unplaced)
5814
+ ];
5779
5815
  })
5780
5816
  ], "residual");
5781
5817
  const perturb = /* @__PURE__ */ __name((base, steps) => {
@@ -5795,10 +5831,61 @@ function solveBodies2(bodies) {
5795
5831
  };
5796
5832
  });
5797
5833
  }, "perturb");
5834
+ const settleBoundTranslations = /* @__PURE__ */ __name(() => {
5835
+ if (!active.some((owner) => !flexible[owner])) return poses;
5836
+ const free = active.flatMap((body) => [0, 1, 2].map((axis) => ({ body, axis })));
5837
+ if (!free.length) return poses;
5838
+ const shift = /* @__PURE__ */ __name((steps) => {
5839
+ const result = poses.map((pose) => ({
5840
+ ...pose,
5841
+ position: [...pose.position]
5842
+ }));
5843
+ free.forEach(({ body, axis }, i) => {
5844
+ result[body].position[axis] += steps[i];
5845
+ });
5846
+ return result;
5847
+ }, "shift");
5848
+ const preferences = /* @__PURE__ */ __name((candidate2) => unique.filter(({ owner }) => !flexible[owner]).flatMap(
5849
+ ({ owner }) => subtract2(
5850
+ baselinePose(candidate2[owner], candidate2, owner).position,
5851
+ bodies[owner].initial?.position ?? [0, 0, 0]
5852
+ )
5853
+ ), "preferences");
5854
+ const h = 1e-5;
5855
+ const initial = residual2(poses), preference = preferences(poses);
5856
+ const columns = free.map((_, i) => {
5857
+ const plus = shift(free.map((_2, j) => i === j ? h : 0));
5858
+ const minus = shift(free.map((_2, j) => i === j ? -h : 0));
5859
+ const a = residual2(plus), b = residual2(minus), p = preferences(plus), q = preferences(minus);
5860
+ return {
5861
+ constraints: a.map((v, j) => (v - b[j]) / (2 * h)),
5862
+ preferences: p.map((v, j) => (v - q[j]) / (2 * h))
5863
+ };
5864
+ });
5865
+ const step = solveTranslations(
5866
+ initial.map((value, i) => ({
5867
+ id: "bound translation",
5868
+ value: -value,
5869
+ coefficients: columns.map((column) => column.constraints[i])
5870
+ })),
5871
+ preference.map((value, i) => ({
5872
+ id: "pre-action position",
5873
+ value: -value,
5874
+ coefficients: columns.map((column) => column.preferences[i])
5875
+ })),
5876
+ free.length
5877
+ );
5878
+ const candidate = shift(step);
5879
+ return residual2(candidate).every((value) => Math.abs(value) < 1e-8) ? candidate : poses;
5880
+ }, "settleBoundTranslations");
5798
5881
  let values = residual2(poses), damping = 1e-5;
5799
5882
  const norm = /* @__PURE__ */ __name((values2) => values2.reduce((sum, x) => sum + x * x, 0), "norm");
5800
- for (let iteration = 0; iteration < 160; iteration++) {
5801
- if (values.every((v) => Math.abs(v) < 1e-8)) return poses;
5883
+ for (let iteration = 0; iteration < 168; iteration++) {
5884
+ if (iteration === 8) {
5885
+ variables = fullVariables;
5886
+ damping = 1e-5;
5887
+ }
5888
+ if (values.every((v) => Math.abs(v) < 1e-8)) return settleBoundTranslations();
5802
5889
  const h = 1e-6;
5803
5890
  const columns = variables.map((_, i) => {
5804
5891
  const shifted = residual2(
@@ -5807,7 +5894,13 @@ function solveBodies2(bodies) {
5807
5894
  variables.map((_2, j) => i === j ? h : 0)
5808
5895
  )
5809
5896
  );
5810
- return values.map((v, j) => (shifted[j] - v) / h);
5897
+ const opposite = residual2(
5898
+ perturb(
5899
+ poses,
5900
+ variables.map((_2, j) => i === j ? -h : 0)
5901
+ )
5902
+ );
5903
+ return values.map((_2, j) => (shifted[j] - opposite[j]) / (2 * h));
5811
5904
  });
5812
5905
  const multiply2 = /* @__PURE__ */ __name((a, b) => a.reduce((sum, x, i) => sum + x * b[i], 0), "multiply");
5813
5906
  const matrix = columns.map((a, i) => [
@@ -5830,10 +5923,10 @@ __name(solveBodies2, "solveBodies");
5830
5923
  function eliminate(matrix) {
5831
5924
  const n = matrix.length;
5832
5925
  for (let i = 0; i < n; i++) {
5833
- let pivot = i;
5926
+ let pivot2 = i;
5834
5927
  for (let j = i + 1; j < n; j++)
5835
- if (Math.abs(matrix[j][i]) > Math.abs(matrix[pivot][i])) pivot = j;
5836
- [matrix[i], matrix[pivot]] = [matrix[pivot], matrix[i]];
5928
+ if (Math.abs(matrix[j][i]) > Math.abs(matrix[pivot2][i])) pivot2 = j;
5929
+ [matrix[i], matrix[pivot2]] = [matrix[pivot2], matrix[i]];
5837
5930
  for (let j = i + 1; j < n; j++) {
5838
5931
  const factor = matrix[j][i] / matrix[i][i];
5839
5932
  for (let k = i; k <= n; k++) matrix[j][k] -= factor * matrix[i][k];
@@ -5977,10 +6070,10 @@ function openShell(shape, topology, thickness, removedIds, sourceVolume) {
5977
6070
  __name(openShell, "openShell");
5978
6071
  function closedShell(shape, topology, thickness, sourceVolume) {
5979
6072
  const oc = getOC8();
5980
- const offset = new oc.BRepOffsetAPI_MakeOffsetShape();
6073
+ const offset2 = new oc.BRepOffsetAPI_MakeOffsetShape();
5981
6074
  let parallel;
5982
6075
  try {
5983
- offset.PerformByJoin(
6076
+ offset2.PerformByJoin(
5984
6077
  shape.wrapped,
5985
6078
  -thickness,
5986
6079
  tolerance,
@@ -5990,9 +6083,9 @@ function closedShell(shape, topology, thickness, sourceVolume) {
5990
6083
  oc.GeomAbs_JoinType.GeomAbs_Arc,
5991
6084
  false
5992
6085
  );
5993
- if (!offset.IsDone())
6086
+ if (!offset2.IsDone())
5994
6087
  throw new Error("OpenCascade could not build the cavity offset.");
5995
- const raw = castOwnedShape3D(offset.Shape());
6088
+ const raw = castOwnedShape3D(offset2.Shape());
5996
6089
  try {
5997
6090
  parallel = singleSolid(raw);
5998
6091
  } finally {
@@ -6049,7 +6142,7 @@ function closedShell(shape, topology, thickness, sourceVolume) {
6049
6142
  }
6050
6143
  } finally {
6051
6144
  parallel?.delete();
6052
- offset.delete();
6145
+ offset2.delete();
6053
6146
  }
6054
6147
  }
6055
6148
  __name(closedShell, "closedShell");
@@ -17942,19 +18035,19 @@ function getAxisAlignedRayLoopIntersections(loop, p, dir) {
17942
18035
  }
17943
18036
  const ts7 = [];
17944
18037
  let f8;
17945
- let offset;
18038
+ let offset2;
17946
18039
  let axis;
17947
18040
  const dirIsDecreasing = dir === "left" || dir === "up";
17948
18041
  if (dir === "left" || dir === "right") {
17949
18042
  f8 = /* @__PURE__ */ __name((ps2) => toPowerBasis(ps2)[1], "f");
17950
- offset = [0, -y];
18043
+ offset2 = [0, -y];
17951
18044
  axis = 0;
17952
18045
  } else {
17953
18046
  f8 = /* @__PURE__ */ __name((ps2) => toPowerBasis(ps2)[0], "f");
17954
- offset = [-x, 0];
18047
+ offset2 = [-x, 0];
17955
18048
  axis = 1;
17956
18049
  }
17957
- const translatedPs = ps.map(translate(offset));
18050
+ const translatedPs = ps.map(translate(offset2));
17958
18051
  const poly = f8(translatedPs);
17959
18052
  const ts_ = roots(poly, 0 - DELTA, 1 + DELTA)?.map((r) => r.t) || [];
17960
18053
  for (let i2 = 0; i2 < ts_.length; i2++) {
@@ -19930,9 +20023,9 @@ import {
19930
20023
  measureShapeSurfaceProperties
19931
20024
  } from "replicad";
19932
20025
  function inspectShapeTopology(original, topology, options = {}) {
19933
- const offset = options.offset ?? 0;
20026
+ const offset2 = options.offset ?? 0;
19934
20027
  const limit = options.limit ?? 48;
19935
- if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(limit) || limit < 1 || limit > 200)
20028
+ if (!Number.isSafeInteger(offset2) || offset2 < 0 || !Number.isSafeInteger(limit) || limit < 1 || limit > 200)
19936
20029
  throw new Error(
19937
20030
  "Topology pages require a nonnegative offset and a limit from 1 to 200."
19938
20031
  );
@@ -19980,7 +20073,7 @@ function inspectShapeTopology(original, topology, options = {}) {
19980
20073
  (id, index) => !options.ids || options.ids.some((requested) => sameTopologyId(requested, id)) ? [{ kind, id, index }] : []
19981
20074
  )
19982
20075
  );
19983
- const page = all.slice(offset, offset + limit);
20076
+ const page = all.slice(offset2, offset2 + limit);
19984
20077
  const childIndices = /* @__PURE__ */ __name((parent, kind) => {
19985
20078
  const children = shapeSubshapes(parent, kind);
19986
20079
  try {
@@ -20059,8 +20152,8 @@ function inspectShapeTopology(original, topology, options = {}) {
20059
20152
  method: "kernel-enclosure"
20060
20153
  },
20061
20154
  total: all.length,
20062
- offset,
20063
- ...offset + page.length < all.length ? { nextOffset: offset + page.length } : {},
20155
+ offset: offset2,
20156
+ ...offset2 + page.length < all.length ? { nextOffset: offset2 + page.length } : {},
20064
20157
  items,
20065
20158
  units: "model",
20066
20159
  coordinates: "scene",
@@ -20188,6 +20281,18 @@ function inspectSurface(face) {
20188
20281
  __name(inspectSurface, "inspectSurface");
20189
20282
 
20190
20283
  // packages/core/src/library/runtime.ts
20284
+ function isAxisSelection(pivot2) {
20285
+ return pivot2.kind === "axisLine" || pivot2.kind === "axisEdge";
20286
+ }
20287
+ __name(isAxisSelection, "isAxisSelection");
20288
+ function selectionReference(pivot2) {
20289
+ return pivot2.kind === "axisLine" ? pivot2.axis : pivot2.kind === "pivotPoint" ? pivot2.point : void 0;
20290
+ }
20291
+ __name(selectionReference, "selectionReference");
20292
+ function mapSelectionReference(pivot2, map) {
20293
+ return pivot2.kind === "axisLine" ? { ...pivot2, axis: map(pivot2.axis) } : pivot2.kind === "pivotPoint" ? { ...pivot2, point: map(pivot2.point) } : pivot2;
20294
+ }
20295
+ __name(mapSelectionReference, "mapSelectionReference");
20191
20296
  var activeRelate;
20192
20297
  var valueTraces = /* @__PURE__ */ new WeakMap();
20193
20298
  var operationTraces = /* @__PURE__ */ new WeakMap();
@@ -20208,6 +20313,7 @@ function valueTrace(value) {
20208
20313
  __name(valueTrace, "valueTrace");
20209
20314
  function transformSolveContext(context, transform) {
20210
20315
  return {
20316
+ frame: composeTransforms(transform, context.frame),
20211
20317
  poses: new Map(
20212
20318
  [...context.poses].map(([model, pose]) => [
20213
20319
  model,
@@ -20436,7 +20542,7 @@ function modelTopologyReference(value) {
20436
20542
  __name(modelTopologyReference, "modelTopologyReference");
20437
20543
  function modelTopologyIds(value, kind) {
20438
20544
  const topology = value instanceof ModelTopologyElement ? value[anchorReferenceValue].topology : void 0;
20439
- const source = isModelObject(value) ? value : value instanceof ConstraintExpression ? value.self() : topology?.source;
20545
+ const source = isModelObject(value) ? value : value instanceof RelationExpression ? value.self() : topology?.source;
20440
20546
  const geometry = isModelObject(source) ? source[modelGeometry]()?.value : void 0;
20441
20547
  if (!geometry) return void 0;
20442
20548
  return topologyChildren(
@@ -20447,40 +20553,27 @@ function modelTopologyIds(value, kind) {
20447
20553
  );
20448
20554
  }
20449
20555
  __name(modelTopologyIds, "modelTopologyIds");
20450
- var ConstraintExpression = class {
20451
- constructor(kind, source, target, displacement, rotations, context, constraintId, previous) {
20452
- this.kind = kind;
20453
- this.source = source;
20454
- this.target = target;
20455
- this.displacement = displacement;
20456
- this.rotations = rotations;
20556
+ var RelationExpression = class {
20557
+ constructor(definition, context, relationId, previous, spatialOperation) {
20558
+ this.definition = definition;
20457
20559
  this.context = context;
20458
- this.constraintId = constraintId;
20560
+ this.relationId = relationId;
20561
+ this.spatialOperation = spatialOperation;
20459
20562
  valueTraces.set(this, {
20460
20563
  sourceRefs: [...previous ? valueTrace(previous).sourceRefs : []],
20461
20564
  parameters: [...previous ? valueTrace(previous).parameters : []]
20462
20565
  });
20463
20566
  }
20464
- kind;
20465
- source;
20466
- target;
20467
- displacement;
20468
- rotations;
20567
+ definition;
20469
20568
  context;
20470
- constraintId;
20569
+ relationId;
20570
+ spatialOperation;
20471
20571
  static {
20472
- __name(this, "ConstraintExpression");
20473
- }
20474
- get sourceRefs() {
20475
- return valueTrace(this).sourceRefs;
20476
- }
20477
- get parameters() {
20478
- return valueTrace(this).parameters;
20572
+ __name(this, "RelationExpression");
20479
20573
  }
20480
- spatialOperation;
20481
20574
  /** @internal */
20482
20575
  attachSource(sourceRef) {
20483
- const refs = this.sourceRefs;
20576
+ const refs = valueTrace(this).sourceRefs;
20484
20577
  if (!refs.some(
20485
20578
  (ref) => ref.file === sourceRef.file && ref.start === sourceRef.start && ref.end === sourceRef.end
20486
20579
  ))
@@ -20488,7 +20581,7 @@ var ConstraintExpression = class {
20488
20581
  }
20489
20582
  /** @internal */
20490
20583
  attachParameters(parameters) {
20491
- appendUniqueParameters(this.parameters, parameters);
20584
+ appendUniqueParameters(valueTrace(this).parameters, parameters);
20492
20585
  }
20493
20586
  /** @internal */
20494
20587
  self() {
@@ -20496,79 +20589,89 @@ var ConstraintExpression = class {
20496
20589
  }
20497
20590
  /** @internal */
20498
20591
  traceReference() {
20592
+ if (this.definition.kind === "transformation")
20593
+ return {
20594
+ kind: "transformation",
20595
+ transformationId: this.relationId,
20596
+ self: this.context?.self
20597
+ };
20499
20598
  const rebind = /* @__PURE__ */ __name((model) => model === this.context?.original ? this.context.self : model, "rebind");
20500
20599
  return {
20501
- constraintId: this.constraintId,
20502
- source: rebind(this.source.model),
20503
- target: rebind(this.target.model),
20600
+ kind: "constraint",
20601
+ constraintId: this.relationId,
20602
+ source: rebind(this.definition.source.model),
20603
+ target: rebind(this.definition.target.model),
20504
20604
  self: this.context?.self
20505
20605
  };
20506
20606
  }
20507
20607
  /** @internal */
20508
20608
  storeFor(model, original) {
20509
20609
  const isSelf = /* @__PURE__ */ __name((candidate) => candidate === model || candidate === original, "isSelf");
20510
- if (!isSelf(this.source.model) && !isSelf(this.target.model))
20511
- throw new Error(
20512
- "The constraint returned by relate() must involve self or the original receiver."
20513
- );
20514
20610
  const bind = /* @__PURE__ */ __name((reference) => ({
20515
20611
  ...reference,
20516
20612
  model: isSelf(reference.model) ? void 0 : reference.model
20517
20613
  }), "bind");
20518
- const stored = {
20519
- id: this.constraintId,
20520
- kind: this.kind,
20521
- source: bind(this.source),
20522
- target: bind(this.target),
20523
- offset: this.displacement,
20524
- rotations: this.rotations.map((action) => ({
20525
- ...action,
20526
- pivot: action.pivot.kind === "around" ? { ...action.pivot, axis: bind(action.pivot.axis) } : action.pivot
20527
- }))
20614
+ const definition = this.definition;
20615
+ if (definition.kind !== "transformation" && !isSelf(definition.source.model) && !isSelf(definition.target.model))
20616
+ throw new Error(
20617
+ "The constraint returned by relate() must involve self or the original receiver."
20618
+ );
20619
+ const stored = definition.kind === "transformation" ? {
20620
+ kind: "transformation",
20621
+ id: this.relationId,
20622
+ actions: definition.actions.map((action) => {
20623
+ const storedAction = {
20624
+ ...action,
20625
+ ..."pivot" in action ? {
20626
+ pivot: mapSelectionReference(action.pivot, bind)
20627
+ } : {}
20628
+ };
20629
+ valueTraces.set(storedAction, {
20630
+ sourceRefs: [...valueTrace(action).sourceRefs],
20631
+ parameters: [...valueTrace(action).parameters]
20632
+ });
20633
+ return storedAction;
20634
+ })
20635
+ } : {
20636
+ kind: definition.kind,
20637
+ id: this.relationId,
20638
+ source: bind(definition.source),
20639
+ target: bind(definition.target)
20528
20640
  };
20529
20641
  valueTraces.set(stored, {
20530
- sourceRefs: [...this.sourceRefs],
20531
- parameters: [...this.parameters]
20642
+ sourceRefs: [...valueTrace(this).sourceRefs],
20643
+ parameters: [...valueTrace(this).parameters]
20532
20644
  });
20533
20645
  return stored;
20534
20646
  }
20535
20647
  /** @internal */
20536
- preview() {
20648
+ preview(preceding = []) {
20537
20649
  if (!this.context) return void 0;
20538
- const pivot = this.spatialOperation?.pivot;
20539
- const selection = pivot?.kind === "around" ? {
20650
+ const pivot2 = this.spatialOperation?.pivot;
20651
+ const selection = pivot2 ? {
20540
20652
  ...this.spatialOperation,
20541
- pivot: {
20542
- ...pivot,
20543
- axis: {
20544
- ...pivot.axis,
20545
- model: pivot.axis.model === this.context.self || pivot.axis.model === this.context.original ? void 0 : pivot.axis.model
20546
- }
20547
- }
20653
+ pivot: mapSelectionReference(pivot2, (reference) => ({
20654
+ ...reference,
20655
+ model: reference.model === this.context.self || reference.model === this.context.original ? void 0 : reference.model
20656
+ }))
20548
20657
  } : this.spatialOperation;
20549
- return this.context.original[relationPreview](
20550
- this.context.self.nodeId,
20658
+ return this.context.self[previewRelation](
20551
20659
  this.storeFor(this.context.self, this.context.original),
20552
- selection
20660
+ selection,
20661
+ preceding
20553
20662
  );
20554
20663
  }
20664
+ /** @internal */
20665
+ continuationArguments() {
20666
+ return [this.definition, this.context, this.relationId, this];
20667
+ }
20555
20668
  };
20556
- var Constraint = class _Constraint extends ConstraintExpression {
20669
+ var Constraint = class _Constraint extends RelationExpression {
20557
20670
  static {
20558
20671
  __name(this, "Constraint");
20559
20672
  }
20560
- constructor(kind, source, target, displacement = void 0, rotations = [], context = activeRelate, constraintId = `constraint-${nextConstraintId++}`, previous, spatialOperation) {
20561
- super(
20562
- kind,
20563
- source,
20564
- target,
20565
- displacement,
20566
- rotations,
20567
- context,
20568
- constraintId,
20569
- previous
20570
- );
20571
- this.spatialOperation = spatialOperation;
20673
+ constructor(definition) {
20674
+ super(definition, activeRelate, `constraint-${nextConstraintId++}`);
20572
20675
  }
20573
20676
  /** @internal */
20574
20677
  static create(source, target, kind = "on") {
@@ -20578,132 +20681,210 @@ var Constraint = class _Constraint extends ConstraintExpression {
20578
20681
  throw new Error(
20579
20682
  "align() requires points, curves, or surfaces; select geometry on a solid or group."
20580
20683
  );
20581
- return new _Constraint(kind, source, target);
20582
- }
20583
- offset(x = 0, y = 0, z = 0) {
20584
- assertFiniteVector("offset", [x, y, z]);
20585
- return new _Constraint(
20586
- this.kind,
20587
- this.source,
20588
- this.target,
20589
- addVectors(this.displacement ?? origin, [x, y, z]),
20590
- this.rotations,
20591
- this.context,
20592
- this.constraintId,
20593
- this
20684
+ return new _Constraint({ kind, source, target });
20685
+ }
20686
+ };
20687
+ var Transformation = class extends RelationExpression {
20688
+ static {
20689
+ __name(this, "Transformation");
20690
+ }
20691
+ /** @internal */
20692
+ constructor(action, previous) {
20693
+ const args = previous?.continuationArguments();
20694
+ super(
20695
+ { kind: "transformation", actions: [action] },
20696
+ args ? args[1] : activeRelate,
20697
+ args?.[2] ?? `transformation-${nextConstraintId++}`,
20698
+ previous,
20699
+ "pivot" in action ? { kind: "rotate", pivot: action.pivot } : void 0
20594
20700
  );
20701
+ valueTraces.set(action, valueTrace(this));
20595
20702
  }
20596
- pivot([x = 0, y = 0, z = 0] = [0, 0, 0]) {
20597
- assertFiniteVector("pivot", [x, y, z]);
20598
- return new ConstraintPivotChain(this, { kind: "pivot", point: [x, y, z] });
20599
- }
20600
- /** @code3d.param id {kind: 'vertex', label: 'Pivot vertex'} */
20601
- pivotVertex(id) {
20602
- return new ConstraintPivotChain(this, { kind: "pivotVertex", id });
20603
- }
20604
- /** Select a positioned axis in the composition. */
20605
- around(axis) {
20606
- const reference = anchorReference(axis);
20607
- if (reference.kind !== "line")
20608
- throw new Error("around() requires an axis or straight edge.");
20609
- const geometry = reference.topology?.source[modelGeometry]()?.value ?? (reference.whole && isModelObject(reference.model) ? reference.model[modelGeometry]()?.value : void 0);
20610
- if (geometry) {
20611
- const straight = reference.topology ? withTopologyShape(
20612
- geometry.shape,
20613
- geometry.topology,
20614
- reference.topology.selection,
20615
- (shape) => shape.geomType === "LINE"
20616
- ) : geometry.shape.geomType === "LINE";
20617
- if (!straight)
20618
- throw new Error(
20619
- "around() requires a straight axis; curved edges do not define one rotation axis."
20620
- );
20621
- }
20622
- return new ConstraintAroundChain(this, { kind: "around", axis: reference });
20703
+ };
20704
+ var TransformationRotation = class extends RelationExpression {
20705
+ static {
20706
+ __name(this, "TransformationRotation");
20623
20707
  }
20624
- rotate(x = 0, y = 0, z = 0) {
20625
- return this.withRotation({ kind: "pivot", point: origin }, [x, y, z], this);
20708
+ constructor() {
20709
+ super(
20710
+ { kind: "transformation", actions: [] },
20711
+ activeRelate,
20712
+ `transformation-${nextConstraintId++}`
20713
+ );
20626
20714
  }
20627
- /** @internal */
20628
- withRotation(pivot, angles, previous) {
20715
+ withRotation(pivot2, angles, previous) {
20629
20716
  assertFiniteVector(
20630
20717
  "rotate",
20631
20718
  typeof angles === "number" ? [angles, 0, 0] : angles
20632
20719
  );
20633
- return new _Constraint(
20634
- this.kind,
20635
- this.source,
20636
- this.target,
20637
- this.displacement,
20638
- [...this.rotations, { pivot, angles }],
20639
- this.context,
20640
- this.constraintId,
20641
- previous,
20642
- { kind: "rotate", pivot }
20643
- );
20644
- }
20645
- /** @internal */
20646
- chainArguments() {
20647
- return [
20648
- this.kind,
20649
- this.source,
20650
- this.target,
20651
- this.displacement,
20652
- this.rotations,
20653
- this.context,
20654
- this.constraintId,
20655
- this
20656
- ];
20720
+ return new Transformation({ pivot: pivot2, angles }, previous);
20657
20721
  }
20658
20722
  };
20659
- var ConstraintPivotChain = class extends ConstraintExpression {
20723
+ function rotationPointReference(point3) {
20724
+ const reference = anchorReference(point3);
20725
+ if (reference.kind !== "point")
20726
+ throw new Error("pivotPoint() requires a point reference.");
20727
+ return reference;
20728
+ }
20729
+ __name(rotationPointReference, "rotationPointReference");
20730
+ function rotationAxisReference(axis) {
20731
+ const reference = anchorReference(axis);
20732
+ if (reference.kind !== "line")
20733
+ throw new Error("axisLine() requires an axis or straight edge.");
20734
+ const geometry = reference.topology?.source[modelGeometry]()?.value ?? (reference.whole && isModelObject(reference.model) ? reference.model[modelGeometry]()?.value : void 0);
20735
+ if (geometry) {
20736
+ const straight = reference.topology ? withTopologyShape(
20737
+ geometry.shape,
20738
+ geometry.topology,
20739
+ reference.topology.selection,
20740
+ (shape) => shape.geomType === "LINE"
20741
+ ) : geometry.shape.geomType === "LINE";
20742
+ if (!straight)
20743
+ throw new Error(
20744
+ "axisLine() requires a straight axis; curved edges do not define one rotation axis."
20745
+ );
20746
+ }
20747
+ return reference;
20748
+ }
20749
+ __name(rotationAxisReference, "rotationAxisReference");
20750
+ var PivotRotation = class extends RelationExpression {
20660
20751
  /** @internal */
20661
- constructor(constraint, selection) {
20662
- super(...constraint.chainArguments());
20663
- this.constraint = constraint;
20752
+ constructor(chain, selection, previous) {
20753
+ super(...(previous ?? chain).continuationArguments(), {
20754
+ kind: selection.offset ? "pivotOffset" : selection.kind,
20755
+ pivot: selection
20756
+ });
20757
+ this.chain = chain;
20664
20758
  this.selection = selection;
20665
- this.spatialOperation = { kind: selection.kind, pivot: selection };
20666
20759
  }
20667
- constraint;
20760
+ chain;
20668
20761
  selection;
20669
20762
  static {
20670
- __name(this, "ConstraintPivotChain");
20763
+ __name(this, "PivotRotation");
20671
20764
  }
20672
20765
  rotate(x = 0, y = 0, z = 0) {
20673
- return this.constraint.withRotation(this.selection, [x, y, z], this);
20766
+ return this.chain.withRotation(this.selection, [x, y, z], this);
20767
+ }
20768
+ };
20769
+ var PivotChain = class extends PivotRotation {
20770
+ static {
20771
+ __name(this, "PivotChain");
20772
+ }
20773
+ pivotOffset(x = 0, y = 0, z = 0) {
20774
+ assertFiniteVector("pivotOffset", [x, y, z]);
20775
+ return new PivotRotation(
20776
+ this.chain,
20777
+ { ...this.selection, offset: [x, y, z] },
20778
+ this
20779
+ );
20674
20780
  }
20675
20781
  };
20676
- var ConstraintAroundChain = class extends ConstraintExpression {
20782
+ var AxisRotation = class extends RelationExpression {
20677
20783
  /** @internal */
20678
- constructor(constraint, selection) {
20679
- super(...constraint.chainArguments());
20680
- this.constraint = constraint;
20784
+ constructor(chain, selection, previous) {
20785
+ super(...(previous ?? chain).continuationArguments(), {
20786
+ kind: selection.offset ? "axisOffset" : selection.kind,
20787
+ pivot: selection
20788
+ });
20789
+ this.chain = chain;
20681
20790
  this.selection = selection;
20682
- this.spatialOperation = { kind: selection.kind, pivot: selection };
20683
20791
  }
20684
- constraint;
20792
+ chain;
20685
20793
  selection;
20686
20794
  static {
20687
- __name(this, "ConstraintAroundChain");
20795
+ __name(this, "AxisRotation");
20688
20796
  }
20689
20797
  rotate(angle = 0) {
20690
- return this.constraint.withRotation(this.selection, angle, this);
20798
+ return this.chain.withRotation(this.selection, angle, this);
20799
+ }
20800
+ };
20801
+ var AxisChain = class extends AxisRotation {
20802
+ static {
20803
+ __name(this, "AxisChain");
20804
+ }
20805
+ axisOffset(x = 0, y = 0, z = 0) {
20806
+ assertFiniteVector("axisOffset", [x, y, z]);
20807
+ return new AxisRotation(
20808
+ this.chain,
20809
+ { ...this.selection, offset: [x, y, z] },
20810
+ this
20811
+ );
20691
20812
  }
20692
20813
  };
20814
+ function selectedAxis(axis, offset2) {
20815
+ return composeTransforms(axis.transform, translation(offset2 ?? origin));
20816
+ }
20817
+ __name(selectedAxis, "selectedAxis");
20818
+ function offset(x = 0, y = 0, z = 0) {
20819
+ assertFiniteVector("offset", [x, y, z]);
20820
+ return new Transformation({ offset: [x, y, z] });
20821
+ }
20822
+ __name(offset, "offset");
20823
+ function rotate2(x = 0, y = 0, z = 0) {
20824
+ assertFiniteVector("rotate", [x, y, z]);
20825
+ return new Transformation({
20826
+ pivot: { kind: "pivot", point: origin, implicit: true },
20827
+ angles: [x, y, z]
20828
+ });
20829
+ }
20830
+ __name(rotate2, "rotate");
20831
+ function pivot([x = 0, y = 0, z = 0] = origin) {
20832
+ assertFiniteVector("pivot", [x, y, z]);
20833
+ return new PivotChain(new TransformationRotation(), {
20834
+ kind: "pivot",
20835
+ point: [x, y, z]
20836
+ });
20837
+ }
20838
+ __name(pivot, "pivot");
20839
+ function pivotVertex(id) {
20840
+ assertTopologyId("vertex", id);
20841
+ return new PivotChain(new TransformationRotation(), {
20842
+ kind: "pivotVertex",
20843
+ id
20844
+ });
20845
+ }
20846
+ __name(pivotVertex, "pivotVertex");
20847
+ function pivotPoint(point3) {
20848
+ return new PivotChain(new TransformationRotation(), {
20849
+ kind: "pivotPoint",
20850
+ point: rotationPointReference(point3)
20851
+ });
20852
+ }
20853
+ __name(pivotPoint, "pivotPoint");
20854
+ function axisEdge(id) {
20855
+ assertTopologyId("edge", id);
20856
+ return new AxisChain(new TransformationRotation(), {
20857
+ kind: "axisEdge",
20858
+ id
20859
+ });
20860
+ }
20861
+ __name(axisEdge, "axisEdge");
20862
+ function axisLine(axis) {
20863
+ return new AxisChain(new TransformationRotation(), {
20864
+ kind: "axisLine",
20865
+ axis: rotationAxisReference(axis)
20866
+ });
20867
+ }
20868
+ __name(axisLine, "axisLine");
20693
20869
  var modelGeometry = /* @__PURE__ */ Symbol("modelGeometry");
20694
20870
  var referenceBounds = /* @__PURE__ */ Symbol("referenceBounds");
20695
20871
  var referenceBoundsParts = /* @__PURE__ */ Symbol("referenceBoundsParts");
20696
20872
  var modelSnapshotQueries = /* @__PURE__ */ Symbol("modelSnapshotQueries");
20697
- var relationPreview = /* @__PURE__ */ Symbol("relationPreview");
20873
+ var previewRelation = /* @__PURE__ */ Symbol("previewRelation");
20698
20874
  var RelationObject = class _RelationObject {
20699
20875
  static {
20700
20876
  __name(this, "RelationObject");
20701
20877
  }
20702
20878
  nodeId;
20703
- constraints;
20879
+ placements;
20880
+ get constraints() {
20881
+ return this.placements.filter(
20882
+ (value) => value.kind !== "transformation"
20883
+ );
20884
+ }
20704
20885
  constructor(init = {}) {
20705
20886
  this.nodeId = init.nodeId ?? `node-${nextNodeId++}`;
20706
- this.constraints = [...init.constraints ?? []];
20887
+ this.placements = [...init.placements ?? []];
20707
20888
  valueTraces.set(this, {
20708
20889
  sourceRefs: [...init.sourceRefs ?? []],
20709
20890
  parameters: [...init.parameters ?? []]
@@ -20715,6 +20896,17 @@ var RelationObject = class _RelationObject {
20715
20896
  get parameters() {
20716
20897
  return valueTrace(this).parameters;
20717
20898
  }
20899
+ edgeReference(_id) {
20900
+ throw new Error(
20901
+ "axisEdge() requires model topology. Use axisLine(lineRef) for a line reference."
20902
+ );
20903
+ }
20904
+ rotationAxis(pivot2) {
20905
+ return pivot2.kind === "axisLine" ? pivot2.axis : this.edgeReference(pivot2.id);
20906
+ }
20907
+ rotationPoint(pivot2) {
20908
+ return pivot2.kind === "pivot" ? pivot2.point : pivot2.kind === "pivotVertex" ? this.vertexPosition(pivot2.id) : pivot2.point.transform.position;
20909
+ }
20718
20910
  vertexPosition(_id) {
20719
20911
  throw new Error(
20720
20912
  "pivotVertex() requires model topology, not a sketch reference frame. Use pivot([x, y, z]) instead."
@@ -20730,15 +20922,15 @@ var RelationObject = class _RelationObject {
20730
20922
  } finally {
20731
20923
  activeRelate = previous;
20732
20924
  }
20733
- const constraints = Array.isArray(built) ? built : [built];
20734
- const stored = constraints.map((constraint) => {
20735
- if (!(constraint instanceof Constraint))
20925
+ const relations = Array.isArray(built) ? built : [built];
20926
+ const stored = relations.map((constraint) => {
20927
+ if (!(constraint instanceof Constraint) && !(constraint instanceof Transformation))
20736
20928
  throw new Error(
20737
- "relate() requires a completed Constraint; finish pivot/around with rotate()."
20929
+ "relate() requires a completed Constraint or Transformation; finish the pivot or axis selector with rotate()."
20738
20930
  );
20739
20931
  return constraint.storeFor(this, original);
20740
20932
  });
20741
- this.constraints.push(...stored);
20933
+ this.placements.push(...stored);
20742
20934
  return uniqueModels(stored.flatMap(constraintReferences));
20743
20935
  }
20744
20936
  [referenceBounds](reference, transform) {
@@ -20825,22 +21017,32 @@ var RelationObject = class _RelationObject {
20825
21017
  arrow
20826
21018
  };
20827
21019
  }
20828
- bodyRotations(constraint, indices) {
20829
- return constraint.rotations.map(({ pivot, angles }) => {
20830
- if (pivot.kind === "around") {
20831
- return pivot.axis.model ? {
20832
- body: indices.get(pivot.axis.model),
20833
- axis: pivot.axis.transform,
20834
- angle: angles * (pivot.axis.direction ?? 1)
21020
+ bodyActions(constraint, indices) {
21021
+ return constraint.actions.map((action) => {
21022
+ if ("offset" in action) return { offset: action.offset };
21023
+ const { pivot: pivot2, angles } = action;
21024
+ if (isAxisSelection(pivot2)) {
21025
+ const axis = this.rotationAxis(pivot2);
21026
+ return axis.model ? {
21027
+ body: indices.get(axis.model),
21028
+ axis: selectedAxis(axis, pivot2.offset),
21029
+ angle: angles * (axis.direction ?? 1)
20835
21030
  } : {
20836
21031
  local: axisRotation(
20837
- pivot.axis.transform,
20838
- angles * (pivot.axis.direction ?? 1)
21032
+ selectedAxis(axis, pivot2.offset),
21033
+ angles * (axis.direction ?? 1)
20839
21034
  )
20840
21035
  };
20841
21036
  }
21037
+ if (pivot2.kind === "pivotPoint" && pivot2.point.model)
21038
+ return {
21039
+ body: indices.get(pivot2.point.model),
21040
+ point: pivot2.point.transform.position,
21041
+ rotation: rotationAround(origin, angles).quaternion,
21042
+ displacement: pivot2.offset ?? origin
21043
+ };
20842
21044
  const frame = translation(
20843
- pivot.kind === "pivot" ? pivot.point : this.vertexPosition(pivot.id)
21045
+ addVectors(this.rotationPoint(pivot2), pivot2.offset ?? origin)
20844
21046
  );
20845
21047
  return {
20846
21048
  local: composeTransforms(
@@ -20851,90 +21053,329 @@ var RelationObject = class _RelationObject {
20851
21053
  });
20852
21054
  }
20853
21055
  /** @internal */
20854
- [relationPreview](nodeId, constraint, selection) {
21056
+ [previewRelation](constraint, selection, preceding = [], original) {
21057
+ let placements = [...(original ?? this).placements];
21058
+ const index = placements.findIndex((value) => value.id === constraint?.id);
21059
+ if (index < 0) {
21060
+ placements.push(
21061
+ ...preceding.map(
21062
+ (value) => value.storeFor(this, value.continuationArguments()[1].original)
21063
+ )
21064
+ );
21065
+ if (constraint) placements.push(constraint);
21066
+ } else if (constraint) {
21067
+ placements[index] = constraint;
21068
+ let end = index + 1;
21069
+ if (constraint.kind !== "transformation")
21070
+ while (end < placements.length && placements[end].kind !== "transformation")
21071
+ end++;
21072
+ placements = placements.slice(0, end);
21073
+ }
20855
21074
  const owner = this.copyRelations({
20856
- nodeId,
20857
- constraints: [...this.constraints, constraint]
21075
+ nodeId: this.nodeId,
21076
+ placements
20858
21077
  });
20859
21078
  const context = _RelationObject.createSolveContext([
20860
21079
  owner,
20861
- ...selection?.pivot.kind === "around" && selection.pivot.axis.model ? [selection.pivot.axis.model] : []
21080
+ ...selection && selectionReference(selection.pivot)?.model ? [selectionReference(selection.pivot).model] : []
20862
21081
  ]);
20863
21082
  return {
20864
21083
  object: {
20865
- nodeId,
21084
+ nodeId: this.nodeId,
20866
21085
  compositionTransform: toTransform(owner.solvePose(context)),
20867
21086
  constraints: owner.constraints.map(
20868
21087
  (value) => owner.constraintSnapshot(value, context)
20869
- )
21088
+ ),
21089
+ transformations: owner.transformationSnapshots(context),
21090
+ relationStages: owner.relationStageSnapshots(context)
20870
21091
  },
20871
- spatial: selection ? owner.constraintSpatial(constraint, selection, context) : void 0
21092
+ spatial: selection && constraint?.kind === "transformation" ? owner.relationSpatial(constraint, selection, context) : constraint?.kind === "transformation" && constraint.actions.at(-1) && "offset" in constraint.actions.at(-1) ? owner.transformationOffsetSpatial(constraint, context) : void 0
20872
21093
  };
20873
21094
  }
20874
- constraintSpatial(constraint, selection, context) {
21095
+ relationSpatial(constraint, selection, context, actionIndex) {
21096
+ context = this.placementStageContext(constraint, context);
20875
21097
  const models = [...context.poses.keys()];
20876
- const actions = this.bodyRotations(
20877
- constraint,
20878
- new Map(models.map((model, i) => [model, i]))
20879
- );
20880
- const stage = actions.length - (selection.kind === "rotate" ? 1 : 0);
21098
+ const actions = this.placementActions(constraint, context);
21099
+ const stage = actionIndex ?? (selection.kind === "rotate" ? constraint.actions.length - 1 - [...constraint.actions].reverse().findIndex((action) => "angles" in action) : actions.length);
20881
21100
  const finalPose = this.solvePose(context);
20882
- let before = finalPose;
20883
- for (let i = actions.length - 1; i >= stage; i--) {
20884
- const action = actions[i];
20885
- before = "local" in action ? composeTransforms(before, invertTransform(action.local)) : composeTransforms(
20886
- axisRotation(
20887
- composeTransforms(
20888
- models[action.body].solvePose(context),
20889
- action.axis
20890
- ),
20891
- -action.angle
20892
- ),
20893
- before
20894
- );
20895
- }
20896
- const pivot = selection.pivot;
21101
+ const following = this.followingActions(constraint, context);
21102
+ const before = beforeTransformations(
21103
+ beforeTransformations(
21104
+ finalPose,
21105
+ { actions: following },
21106
+ models.map((model) => model.solvePose(context))
21107
+ ),
21108
+ {
21109
+ actions: actions.slice(stage)
21110
+ },
21111
+ models.map((model) => model.solvePose(context))
21112
+ );
21113
+ const pivot2 = selection.pivot;
21114
+ const axis = isAxisSelection(pivot2) ? this.rotationAxis(pivot2) : void 0;
20897
21115
  let frame;
20898
- if (pivot.kind === "around") {
21116
+ if (axis) {
20899
21117
  frame = composeTransforms(
20900
- pivot.axis.model ? pivot.axis.model.solvePose(context) : before,
20901
- (pivot.axis.direction ?? 1) === 1 ? pivot.axis.transform : composeTransforms(pivot.axis.transform, rotation([1, 0, 0, 0]))
21118
+ axis.model ? axis.model.solvePose(context) : before,
21119
+ (axis.direction ?? 1) === 1 ? selectedAxis(axis, pivot2.offset) : composeTransforms(
21120
+ selectedAxis(axis, pivot2.offset),
21121
+ rotation([1, 0, 0, 0])
21122
+ )
20902
21123
  );
21124
+ } else if (pivot2.kind === "pivotPoint" && pivot2.point.model) {
21125
+ const center = composeTransforms(
21126
+ pivot2.point.model.solvePose(context),
21127
+ pivot2.point.transform
21128
+ ).position;
21129
+ frame = {
21130
+ position: addVectors(
21131
+ center,
21132
+ rotateVector(pivot2.offset ?? origin, before.quaternion)
21133
+ ),
21134
+ quaternion: before.quaternion
21135
+ };
20903
21136
  } else {
20904
21137
  const local = translation(
20905
- pivot.kind === "pivot" ? pivot.point : this.vertexPosition(pivot.id)
21138
+ addVectors(
21139
+ this.rotationPoint(pivot2),
21140
+ pivot2.offset ?? origin
21141
+ )
20906
21142
  );
20907
21143
  frame = composeTransforms(before, local);
20908
21144
  }
20909
- const angles = constraint.rotations[stage]?.angles ?? origin;
21145
+ const selected = constraint.actions[stage];
21146
+ const angles = selected && "angles" in selected ? selected.angles : origin;
20910
21147
  const rotationVector = typeof angles === "number" ? [0, angles, 0] : angles;
21148
+ const poses = models.map((model) => model.solvePose(context));
21149
+ let actionPose = afterTransformations(
21150
+ before,
21151
+ { actions: actions.slice(stage, stage + 1) },
21152
+ poses
21153
+ );
21154
+ const remainder = [...actions.slice(stage + 1), ...following];
21155
+ for (const action of remainder) {
21156
+ const next = afterTransformations(
21157
+ actionPose,
21158
+ {
21159
+ actions: [action]
21160
+ },
21161
+ poses
21162
+ );
21163
+ if ("body" in action)
21164
+ frame = composeTransforms(
21165
+ composeTransforms(next, invertTransform(actionPose)),
21166
+ frame
21167
+ );
21168
+ else if ("offset" in action) {
21169
+ frame = {
21170
+ ...frame,
21171
+ position: addVectors(
21172
+ frame.position,
21173
+ addVectors(next.position, negateVector(actionPose.position))
21174
+ )
21175
+ };
21176
+ }
21177
+ actionPose = next;
21178
+ }
20911
21179
  const localFrame = relativeTransform(frame, finalPose);
20912
21180
  return {
20913
21181
  nodeId: this.nodeId,
20914
21182
  kind: selection.kind,
20915
21183
  spatial: {
20916
21184
  origin: localFrame.position,
20917
- vector: selection.kind === "rotate" ? rotationVector : pivot.kind === "pivot" ? pivot.point : origin,
21185
+ vector: selection.kind === "rotate" ? rotationVector : selection.kind === "pivotOffset" || selection.kind === "axisOffset" ? pivot2.offset ?? origin : pivot2.kind === "pivot" ? pivot2.point : origin,
20918
21186
  frame: localFrame,
20919
21187
  rotation: rotationVector,
20920
- axisOnly: pivot.kind === "around"
21188
+ axisOnly: isAxisSelection(pivot2),
21189
+ reference: {
21190
+ ...pivot2.kind === "axisEdge" || pivot2.kind === "pivotVertex" ? { kind: pivot2.kind, id: pivot2.id } : pivot2.kind === "axisLine" || pivot2.kind === "pivotPoint" ? {
21191
+ kind: pivot2.kind,
21192
+ nodeId: selectionReference(pivot2).model?.nodeId ?? this.nodeId,
21193
+ name: selectionReference(pivot2).name
21194
+ } : { kind: pivot2.kind, point: pivot2.point },
21195
+ offset: pivot2.offset ?? origin,
21196
+ offsetExplicit: pivot2.offset !== void 0,
21197
+ explicit: !pivot2.implicit,
21198
+ rotation: axis && (axis.direction ?? 1) !== 1 ? [0, -rotationVector[1], 0] : rotationVector,
21199
+ frame: axis && (axis.direction ?? 1) !== 1 ? composeTransforms(localFrame, rotation([1, 0, 0, 0])) : localFrame
21200
+ }
20921
21201
  }
20922
21202
  };
20923
21203
  }
20924
- static createSolveContext(roots2) {
21204
+ placementActions(value, context) {
21205
+ const indices = new Map(
21206
+ [...context.poses.keys()].map((model, i) => [model, i])
21207
+ );
21208
+ return this.bodyActions(value, indices).map(
21209
+ (action) => "offset" in action ? { offset: rotateVector(action.offset, context.frame.quaternion) } : action
21210
+ );
21211
+ }
21212
+ /** Pose at the end of this segment; later constraints have their own input pose. */
21213
+ placementStageContext(placement, context) {
21214
+ const index = this.placements.indexOf(placement);
21215
+ let end = index + 1;
21216
+ if (placement.kind !== "transformation")
21217
+ while (end < this.placements.length && this.placements[end].kind !== "transformation")
21218
+ end++;
21219
+ while (end < this.placements.length && this.placements[end].kind === "transformation")
21220
+ end++;
21221
+ if (end >= this.placements.length) return context;
21222
+ return transformSolveContext(
21223
+ _RelationObject.createSolveContext(
21224
+ [this],
21225
+ /* @__PURE__ */ new Map([[this, this.placements.slice(0, end)]])
21226
+ ),
21227
+ context.frame
21228
+ );
21229
+ }
21230
+ followingActions(placement, context) {
21231
+ let start = this.placements.indexOf(placement) + 1;
21232
+ if (placement.kind !== "transformation")
21233
+ while (start < this.placements.length && this.placements[start].kind !== "transformation")
21234
+ start++;
21235
+ const values = [];
21236
+ while (start < this.placements.length && this.placements[start].kind === "transformation")
21237
+ values.push(this.placements[start++]);
21238
+ return values.flatMap((value) => this.placementActions(value, context));
21239
+ }
21240
+ relationStageSnapshots(context) {
21241
+ if (!this.placements.length) return void 0;
21242
+ const stages = [];
21243
+ let start = 0;
21244
+ while (start < this.placements.length) {
21245
+ let end = start;
21246
+ while (end < this.placements.length && this.placements[end].kind !== "transformation")
21247
+ end++;
21248
+ while (end < this.placements.length && this.placements[end].kind === "transformation")
21249
+ end++;
21250
+ const values = this.placements.slice(start, end);
21251
+ const stage = this.placementStageContext(values[0], context);
21252
+ stages.push({
21253
+ constraintIds: values.filter((value) => value.kind !== "transformation").map((value) => value.id),
21254
+ transformationIds: values.filter((value) => value.kind === "transformation").map((value) => value.id),
21255
+ compositionTransform: toTransform(this.solvePose(stage)),
21256
+ offsetFrame: toTransform(stage.frame)
21257
+ });
21258
+ start = end;
21259
+ }
21260
+ return stages;
21261
+ }
21262
+ transformationSnapshots(context) {
21263
+ const values = this.placements.filter(
21264
+ (value) => value.kind === "transformation"
21265
+ );
21266
+ if (!values.length) return void 0;
21267
+ return values.map(
21268
+ (value) => this.transformationSnapshot(
21269
+ value,
21270
+ this.placementStageContext(value, context)
21271
+ )
21272
+ );
21273
+ }
21274
+ followingReferenceFrame(frame, actions, context) {
21275
+ const models = [...context.poses.keys()];
21276
+ const poses = models.map((model) => model.solvePose(context));
21277
+ let pose = beforeTransformations(this.solvePose(context), { actions }, poses);
21278
+ for (const action of actions) {
21279
+ const next = afterTransformations(pose, { actions: [action] }, poses);
21280
+ if ("body" in action)
21281
+ frame = composeTransforms(
21282
+ composeTransforms(next, invertTransform(pose)),
21283
+ frame
21284
+ );
21285
+ pose = next;
21286
+ }
21287
+ return frame;
21288
+ }
21289
+ transformationSnapshot(value, context) {
21290
+ const models = [...context.poses.keys()];
21291
+ const actions = this.placementActions(value, context);
21292
+ const following = this.followingActions(value, context);
21293
+ const offsets = value.actions.flatMap((action, index) => {
21294
+ if (!("offset" in action)) return [];
21295
+ const frame = this.followingReferenceFrame(
21296
+ context.frame,
21297
+ [...actions.slice(index + 1), ...following],
21298
+ context
21299
+ );
21300
+ return [
21301
+ {
21302
+ value: action.offset,
21303
+ frame: toTransform(frame),
21304
+ sourceRefs: valueTrace(action).sourceRefs
21305
+ }
21306
+ ];
21307
+ });
21308
+ const rotations = value.actions.flatMap(
21309
+ (action, index) => "pivot" in action ? [
21310
+ {
21311
+ spatial: this.relationSpatial(
21312
+ value,
21313
+ { kind: "rotate", pivot: action.pivot },
21314
+ context,
21315
+ index
21316
+ ).spatial,
21317
+ sourceRefs: valueTrace(action).sourceRefs
21318
+ }
21319
+ ] : []
21320
+ );
21321
+ return {
21322
+ id: value.id,
21323
+ offsets,
21324
+ rotations,
21325
+ offsetFrame: toTransform(context.frame),
21326
+ sourceRefs: [...valueTrace(value).sourceRefs],
21327
+ parameters: [...valueTrace(value).parameters]
21328
+ };
21329
+ }
21330
+ transformationOffsetSpatial(value, context) {
21331
+ const offset2 = this.transformationSnapshot(value, context).offsets.at(-1);
21332
+ const local = relativeTransform(
21333
+ { ...offset2.frame, position: this.solvePose(context).position },
21334
+ this.solvePose(context)
21335
+ );
21336
+ return {
21337
+ nodeId: this.nodeId,
21338
+ kind: "offset",
21339
+ spatial: { origin, vector: offset2.value, frame: local }
21340
+ };
21341
+ }
21342
+ static createSolveContext(roots2, overrides = /* @__PURE__ */ new Map()) {
20925
21343
  const models = [];
20926
21344
  const indices = /* @__PURE__ */ new Map();
20927
21345
  const collect = /* @__PURE__ */ __name((model) => {
20928
21346
  if (indices.has(model)) return;
20929
21347
  indices.set(model, models.length);
20930
21348
  models.push(model);
20931
- model.constraints.flatMap(constraintReferences).forEach(collect);
21349
+ (overrides.get(model) ?? model.placements).flatMap(constraintReferences).forEach(collect);
20932
21350
  }, "collect");
20933
21351
  roots2.forEach(collect);
21352
+ const programs = models.map((model) => {
21353
+ const placements = overrides.get(model) ?? model.placements;
21354
+ let last = placements.length - 1;
21355
+ while (last >= 0 && placements[last].kind === "transformation") last--;
21356
+ let start = last;
21357
+ while (start > 0 && placements[start - 1].kind !== "transformation")
21358
+ start--;
21359
+ const initial = start > 0 ? _RelationObject.createSolveContext(
21360
+ [model],
21361
+ new Map(overrides).set(model, placements.slice(0, start))
21362
+ ).poses.get(model) : identityRigidTransform;
21363
+ return {
21364
+ initial,
21365
+ constraints: placements.slice(Math.max(0, start), last + 1).filter(
21366
+ (value) => value.kind !== "transformation"
21367
+ ),
21368
+ transformations: placements.slice(last + 1).filter(
21369
+ (value) => value.kind === "transformation"
21370
+ ).flatMap((value) => model.bodyActions(value, indices))
21371
+ };
21372
+ });
20934
21373
  const poses = solveBodies2(
20935
- models.map((model) => ({
21374
+ models.map((model, index) => ({
21375
+ initial: programs[index].initial,
21376
+ transformations: programs[index].transformations,
20936
21377
  name: model.name,
20937
- relations: model.constraints.map(
21378
+ relations: programs[index].constraints.map(
20938
21379
  (constraint) => constraint.kind === "align" ? {
20939
21380
  kind: "align",
20940
21381
  id: constraint.id,
@@ -20947,9 +21388,7 @@ var RelationObject = class _RelationObject {
20947
21388
  body: indices.get(constraint.target.model ?? model),
20948
21389
  geometry: (constraint.target.model ?? model).alignmentGeometry(constraint.target),
20949
21390
  transform: constraint.target.transform
20950
- },
20951
- offset: constraint.offset,
20952
- rotations: model.bodyRotations(constraint, indices)
21391
+ }
20953
21392
  } : {
20954
21393
  kind: "on",
20955
21394
  id: constraint.id,
@@ -20965,63 +21404,25 @@ var RelationObject = class _RelationObject {
20965
21404
  body: indices.get(constraint.target.model ?? model),
20966
21405
  transform: constraint.target.transform,
20967
21406
  facing: constraint.target.bound.facing
20968
- },
20969
- offset: constraint.offset,
20970
- rotations: model.bodyRotations(constraint, indices)
21407
+ }
20971
21408
  }
20972
21409
  )
20973
21410
  }))
20974
21411
  );
20975
21412
  return {
20976
- poses: new Map(models.map((model, index) => [model, poses[index]]))
21413
+ poses: new Map(models.map((model, index) => [model, poses[index]])),
21414
+ frame: identityRigidTransform
20977
21415
  };
20978
21416
  }
20979
21417
  constraintSnapshot(constraint, context) {
20980
- const lastRotation = constraint.rotations.at(-1);
20981
- const rotationSpatial = lastRotation ? this.constraintSpatial(
20982
- constraint,
20983
- { kind: "rotate", pivot: lastRotation.pivot },
20984
- context
20985
- ).spatial : void 0;
20986
21418
  const source = constraint.source.model ?? this;
20987
21419
  const target = constraint.target.model ?? this;
20988
- const models = [...context.poses.keys()];
20989
- const actions = this.bodyRotations(
20990
- constraint,
20991
- new Map(models.map((model, i) => [model, i]))
20992
- );
20993
- let before = this.solvePose(context);
20994
- for (const action of [...actions].reverse()) {
20995
- before = "local" in action ? composeTransforms(before, invertTransform(action.local)) : composeTransforms(
20996
- axisRotation(
20997
- composeTransforms(
20998
- models[action.body].solvePose(context),
20999
- action.axis
21000
- ),
21001
- -action.angle
21002
- ),
21003
- before
21004
- );
21005
- }
21006
- const sourcePose = source === this ? before : source.solvePose(context);
21007
- const targetPose = target === this ? before : target.solvePose(context);
21008
- let offsetFrame = composeTransforms(
21009
- targetPose,
21420
+ const sourcePose = source.solvePose(context);
21421
+ const contactFrame = composeTransforms(
21422
+ target.solvePose(context),
21010
21423
  constraint.target.transform
21011
21424
  );
21012
21425
  if (constraint.kind === "align") {
21013
- for (const action of actions)
21014
- if ("body" in action)
21015
- offsetFrame = composeTransforms(
21016
- axisRotation(
21017
- composeTransforms(
21018
- models[action.body].solvePose(context),
21019
- action.axis
21020
- ),
21021
- action.angle
21022
- ),
21023
- offsetFrame
21024
- );
21025
21426
  return {
21026
21427
  id: constraint.id,
21027
21428
  kind: "align",
@@ -21029,16 +21430,12 @@ var RelationObject = class _RelationObject {
21029
21430
  target: anchorSnapshot(target, constraint.target),
21030
21431
  sourceElement: source.relationElement(constraint.source),
21031
21432
  targetElement: target.relationElement(constraint.target),
21032
- offsetDirection: 1,
21033
- offset: constraint.offset ?? origin,
21034
- offsetFrame: toTransform(offsetFrame),
21035
- rotation: rotationSpatial,
21036
21433
  sourceRefs: [...valueTrace(constraint).sourceRefs],
21037
21434
  parameters: [...valueTrace(constraint).parameters]
21038
21435
  };
21039
21436
  }
21040
21437
  const orientation = composeTransforms(
21041
- invertTransform(rotation(offsetFrame.quaternion)),
21438
+ invertTransform(rotation(contactFrame.quaternion)),
21042
21439
  rotation(sourcePose.quaternion)
21043
21440
  ).quaternion;
21044
21441
  const bounds = source[referenceBounds](
@@ -21054,9 +21451,9 @@ var RelationObject = class _RelationObject {
21054
21451
  {
21055
21452
  position: addVectors(
21056
21453
  sourcePose.position,
21057
- rotateVector(center, offsetFrame.quaternion)
21454
+ rotateVector(center, contactFrame.quaternion)
21058
21455
  ),
21059
- quaternion: offsetFrame.quaternion
21456
+ quaternion: contactFrame.quaternion
21060
21457
  },
21061
21458
  sourcePose
21062
21459
  );
@@ -21065,19 +21462,6 @@ var RelationObject = class _RelationObject {
21065
21462
  bounds[1][1] - bounds[0][1],
21066
21463
  bounds[1][2] - bounds[0][2]
21067
21464
  ];
21068
- for (const action of actions) {
21069
- if ("body" in action)
21070
- offsetFrame = composeTransforms(
21071
- axisRotation(
21072
- composeTransforms(
21073
- models[action.body].solvePose(context),
21074
- action.axis
21075
- ),
21076
- action.angle
21077
- ),
21078
- offsetFrame
21079
- );
21080
- }
21081
21465
  return {
21082
21466
  id: constraint.id,
21083
21467
  kind: constraint.kind,
@@ -21111,10 +21495,6 @@ var RelationObject = class _RelationObject {
21111
21495
  transform: toTransform(constraint.target.transform),
21112
21496
  bound: constraint.target.bound
21113
21497
  },
21114
- offsetDirection: source === this ? 1 : -1,
21115
- offset: constraint.offset ?? origin,
21116
- offsetFrame: toTransform(offsetFrame),
21117
- rotation: rotationSpatial,
21118
21498
  sourceRefs: [...valueTrace(constraint).sourceRefs],
21119
21499
  parameters: [...valueTrace(constraint).parameters]
21120
21500
  };
@@ -21130,7 +21510,7 @@ var SketchFrame = class _SketchFrame extends RelationObject {
21130
21510
  operation = storedOperation("sketch");
21131
21511
  constructor(source, init = {}) {
21132
21512
  super({
21133
- constraints: source?.constraints,
21513
+ placements: source?.placements,
21134
21514
  sourceRefs: source?.sourceRefs,
21135
21515
  parameters: source?.parameters,
21136
21516
  ...init
@@ -21147,7 +21527,7 @@ var SketchFrame = class _SketchFrame extends RelationObject {
21147
21527
  relatedObjects() {
21148
21528
  return [
21149
21529
  ...this.source ? [this.source] : [],
21150
- ...this.constraints.flatMap(constraintReferences)
21530
+ ...this.placements.flatMap(constraintReferences)
21151
21531
  ];
21152
21532
  }
21153
21533
  attachOperationTrace(siteId, execution, order, sourceRef, outputIndex = 0) {
@@ -21198,7 +21578,7 @@ var SketchFrame = class _SketchFrame extends RelationObject {
21198
21578
  return related;
21199
21579
  }
21200
21580
  face(region) {
21201
- return sketchFaceModel(region, this.constraints, this);
21581
+ return sketchFaceModel(region, this.placements, this);
21202
21582
  }
21203
21583
  snapshot() {
21204
21584
  const context = RelationObject.createSolveContext([this]);
@@ -21207,7 +21587,9 @@ var SketchFrame = class _SketchFrame extends RelationObject {
21207
21587
  compositionTransform: toTransform(this.solvePose(context)),
21208
21588
  constraints: this.constraints.map(
21209
21589
  (constraint) => this.constraintSnapshot(constraint, context)
21210
- )
21590
+ ),
21591
+ transformations: this.transformationSnapshots(context),
21592
+ relationStages: this.relationStageSnapshots(context)
21211
21593
  };
21212
21594
  }
21213
21595
  /** Related finite models, expressed in this sketch's local editing plane. */
@@ -21471,9 +21853,9 @@ var ModelObject = class _ModelObject extends RelationObject {
21471
21853
  );
21472
21854
  }
21473
21855
  originOffset(dx = 0, dy = 0, dz = 0) {
21474
- const offset = [dx, dy, dz];
21475
- assertFiniteVector("originOffset", offset);
21476
- return this.withOrigin(offset, { kind: "originOffset" });
21856
+ const offset2 = [dx, dy, dz];
21857
+ assertFiniteVector("originOffset", offset2);
21858
+ return this.withOrigin(offset2, { kind: "originOffset" });
21477
21859
  }
21478
21860
  originPoint(point3) {
21479
21861
  const reference = anchorReference(point3);
@@ -21499,8 +21881,8 @@ var ModelObject = class _ModelObject extends RelationObject {
21499
21881
  const position2 = this.elements.center.transform.position;
21500
21882
  return this.withOrigin(position2, { kind: "originCenter" });
21501
21883
  }
21502
- withOrigin(offset, selection) {
21503
- const transform = translation(negateVector(offset));
21884
+ withOrigin(offset2, selection) {
21885
+ const transform = translation(negateVector(offset2));
21504
21886
  const operation = storedOperation(selection.kind, [
21505
21887
  { model: this, role: "source", index: 0 }
21506
21888
  ]);
@@ -21510,7 +21892,7 @@ var ModelObject = class _ModelObject extends RelationObject {
21510
21892
  role: "reference",
21511
21893
  index: 0
21512
21894
  });
21513
- operation.spatial = { origin, vector: offset };
21895
+ operation.spatial = { origin, vector: offset2 };
21514
21896
  if (selection.kind === "originVertex") {
21515
21897
  operation.selections.push({
21516
21898
  kind: "vertex",
@@ -21539,7 +21921,7 @@ var ModelObject = class _ModelObject extends RelationObject {
21539
21921
  transformElement(element, transform)
21540
21922
  ])
21541
21923
  ),
21542
- constraints: this.mapConstraintGeometry(
21924
+ placements: this.mapConstraintGeometry(
21543
21925
  (element) => transformElement(element, transform),
21544
21926
  (point3) => composeTransforms(transform, translation(point3)).position
21545
21927
  )
@@ -21575,15 +21957,37 @@ var ModelObject = class _ModelObject extends RelationObject {
21575
21957
  }
21576
21958
  mapConstraintGeometry(mapElement, mapPoint) {
21577
21959
  const mapReference = /* @__PURE__ */ __name((reference) => reference.model ? reference : { ...reference, ...mapElement(reference) }, "mapReference");
21578
- return this.constraints.map((constraint) => {
21960
+ const zero = mapPoint(origin);
21961
+ const mapVector = /* @__PURE__ */ __name((vector2) => mapPoint(vector2).map(
21962
+ (value, index) => value - zero[index]
21963
+ ), "mapVector");
21964
+ const scale3 = Math.hypot(...mapVector([1, 0, 0]));
21965
+ return this.placements.map((constraint) => {
21579
21966
  const mapped = {
21580
21967
  ...constraint,
21581
- source: mapReference(constraint.source),
21582
- target: mapReference(constraint.target),
21583
- rotations: constraint.rotations.map((action) => ({
21584
- ...action,
21585
- pivot: action.pivot.kind === "around" ? { ...action.pivot, axis: mapReference(action.pivot.axis) } : action.pivot.kind === "pivot" ? { ...action.pivot, point: mapPoint(action.pivot.point) } : action.pivot
21586
- }))
21968
+ ...constraint.kind === "transformation" ? {} : {
21969
+ source: mapReference(constraint.source),
21970
+ target: mapReference(constraint.target)
21971
+ },
21972
+ ...constraint.kind === "transformation" ? {
21973
+ actions: constraint.actions.map((action) => {
21974
+ if ("offset" in action) return action;
21975
+ const mappedAction = {
21976
+ ...action,
21977
+ pivot: action.pivot.kind === "pivot" ? { ...action.pivot, point: mapPoint(action.pivot.point) } : mapSelectionReference(action.pivot, mapReference)
21978
+ };
21979
+ if (action.pivot.offset) {
21980
+ mappedAction.pivot = {
21981
+ ...mappedAction.pivot,
21982
+ offset: isAxisSelection(action.pivot) ? selectionReference(action.pivot)?.model ? action.pivot.offset : action.pivot.offset.map(
21983
+ (value) => value * scale3
21984
+ ) : mapVector(action.pivot.offset)
21985
+ };
21986
+ }
21987
+ valueTraces.set(mappedAction, valueTrace(action));
21988
+ return mappedAction;
21989
+ })
21990
+ } : {}
21587
21991
  };
21588
21992
  valueTraces.set(mapped, valueTrace(constraint));
21589
21993
  return mapped;
@@ -21614,7 +22018,7 @@ var ModelObject = class _ModelObject extends RelationObject {
21614
22018
  {
21615
22019
  geometryAnchor: scaleElement(this.geometryAnchor, factor),
21616
22020
  elements: scaleElements(this.elements, factor),
21617
- constraints: this.mapConstraintGeometry(
22021
+ placements: this.mapConstraintGeometry(
21618
22022
  (element) => scaleElement(element, factor),
21619
22023
  (point3) => scaleFrame(translation(point3), factor).position
21620
22024
  )
@@ -21650,7 +22054,7 @@ var ModelObject = class _ModelObject extends RelationObject {
21650
22054
  name: "Extrude",
21651
22055
  geometry,
21652
22056
  material: this.materialSnapshot,
21653
- constraints: this.constraints,
22057
+ placements: this.placements,
21654
22058
  sourceRefs: this.sourceRefs,
21655
22059
  parameters: this.allParameters(),
21656
22060
  meshTolerance: this.meshTolerance,
@@ -21797,7 +22201,7 @@ var ModelObject = class _ModelObject extends RelationObject {
21797
22201
  return [
21798
22202
  ...this.children,
21799
22203
  ...this.operation.inputs.map((input) => input.model),
21800
- ...this.constraints.flatMap(constraintReferences)
22204
+ ...this.placements.flatMap(constraintReferences)
21801
22205
  ];
21802
22206
  }
21803
22207
  /** @internal */
@@ -21813,7 +22217,7 @@ var ModelObject = class _ModelObject extends RelationObject {
21813
22217
  );
21814
22218
  const parameters = uniqueParameters([
21815
22219
  ...this.parameters,
21816
- ...this.constraints.flatMap(
22220
+ ...this.placements.flatMap(
21817
22221
  (constraint) => valueTrace(constraint).parameters
21818
22222
  )
21819
22223
  ]);
@@ -21825,6 +22229,8 @@ var ModelObject = class _ModelObject extends RelationObject {
21825
22229
  compositionTransform: toTransform(pose),
21826
22230
  transform: toTransform(inComposition ? pose : identityRigidTransform),
21827
22231
  constraints,
22232
+ transformations: this.transformationSnapshots(solveContext),
22233
+ relationStages: this.relationStageSnapshots(solveContext),
21828
22234
  origin,
21829
22235
  elements: snapshotElements({
21830
22236
  ...this.elements,
@@ -21919,7 +22325,7 @@ var ModelObject = class _ModelObject extends RelationObject {
21919
22325
  name: "Loft",
21920
22326
  geometry,
21921
22327
  material: this.materialSnapshot,
21922
- constraints: this.constraints,
22328
+ placements: this.placements,
21923
22329
  sourceRefs: inputs.flatMap((input) => input.sourceRefs),
21924
22330
  parameters: uniqueParameters(
21925
22331
  inputs.flatMap((input) => input.allParameters())
@@ -21947,7 +22353,7 @@ var ModelObject = class _ModelObject extends RelationObject {
21947
22353
  geometry: evaluation.geometry,
21948
22354
  name: this.name,
21949
22355
  material: this.materialSnapshot,
21950
- constraints: this.constraints,
22356
+ placements: this.placements,
21951
22357
  sourceRefs: [this, ...others].flatMap((model) => model.sourceRefs),
21952
22358
  parameters: uniqueParameters(
21953
22359
  [this, ...others].flatMap((model) => model.allParameters())
@@ -22065,7 +22471,7 @@ var ModelObject = class _ModelObject extends RelationObject {
22065
22471
  allParameters() {
22066
22472
  return uniqueParameters([
22067
22473
  ...this.parameters,
22068
- ...this.constraints.flatMap(
22474
+ ...this.placements.flatMap(
22069
22475
  (constraint) => valueTrace(constraint).parameters
22070
22476
  )
22071
22477
  ]);
@@ -22197,19 +22603,22 @@ var ModelObject = class _ModelObject extends RelationObject {
22197
22603
  } : reference
22198
22604
  );
22199
22605
  }
22606
+ edgeReference(id) {
22607
+ return { ...rotationAxisReference(this.edge(id)), model: void 0 };
22608
+ }
22200
22609
  vertexPosition(id) {
22201
22610
  return anchorReference(this.vertex(id)).transform.position;
22202
22611
  }
22203
22612
  copyRelations(init) {
22204
22613
  return this.copy(init, this.operation);
22205
22614
  }
22206
- /** Fix the assembly frame once, using only its direct member origins. */
22615
+ /** Fix the assembly frame once in the first member's solved local coordinates. */
22207
22616
  static createAssembly(children) {
22208
22617
  const context = _ModelObject.createSolveContext(children);
22209
- const center = children.length ? boundsCenter(
22210
- pointBounds(children.map((child) => child.solvePose(context).position))
22211
- ) : origin;
22212
- return transformSolveContext(context, translation(negateVector(center)));
22618
+ return children.length ? transformSolveContext(
22619
+ context,
22620
+ invertTransform(children[0].solvePose(context))
22621
+ ) : context;
22213
22622
  }
22214
22623
  requireGeometry() {
22215
22624
  if (!this.geometry) {
@@ -22279,7 +22688,7 @@ var ModelObject = class _ModelObject extends RelationObject {
22279
22688
  material: this.materialSnapshot,
22280
22689
  children: this.children,
22281
22690
  assembly: this.assembly,
22282
- constraints: this.constraints,
22691
+ placements: this.placements,
22283
22692
  elements: this.elements,
22284
22693
  sourceRefs: this.sourceRefs,
22285
22694
  parameters: this.parameters,
@@ -22815,19 +23224,32 @@ function isConstraint(value) {
22815
23224
  return value instanceof Constraint;
22816
23225
  }
22817
23226
  __name(isConstraint, "isConstraint");
22818
- function isConstraintExpression(value) {
22819
- return value instanceof ConstraintExpression;
22820
- }
22821
- __name(isConstraintExpression, "isConstraintExpression");
22822
- function constraintPreview(value) {
22823
- return value.preview();
23227
+ function isRelationExpression(value) {
23228
+ return value instanceof RelationExpression;
23229
+ }
23230
+ __name(isRelationExpression, "isRelationExpression");
23231
+ function relationPreview(value, preceding = []) {
23232
+ return value.preview(preceding);
23233
+ }
23234
+ __name(relationPreview, "relationPreview");
23235
+ function currentRelationSelf() {
23236
+ return activeRelate?.self;
23237
+ }
23238
+ __name(currentRelationSelf, "currentRelationSelf");
23239
+ function relationSelectionPreview(self, preceding, context) {
23240
+ return self[previewRelation](
23241
+ void 0,
23242
+ void 0,
23243
+ preceding,
23244
+ (preceding[0] ?? context)?.continuationArguments()[1]?.original
23245
+ );
22824
23246
  }
22825
- __name(constraintPreview, "constraintPreview");
22826
- function instrumentConstraint(constraint, sourceRef, parameters) {
23247
+ __name(relationSelectionPreview, "relationSelectionPreview");
23248
+ function instrumentRelation(constraint, sourceRef, parameters) {
22827
23249
  constraint.attachSource(sourceRef);
22828
23250
  constraint.attachParameters(parameters);
22829
23251
  }
22830
- __name(instrumentConstraint, "instrumentConstraint");
23252
+ __name(instrumentRelation, "instrumentRelation");
22831
23253
  function instrumentModelOperation(object, instrumentation) {
22832
23254
  object.attachSource(instrumentation.sourceRef);
22833
23255
  object.attachParameters(instrumentation.parameters);
@@ -22840,10 +23262,10 @@ function instrumentModelOperation(object, instrumentation) {
22840
23262
  );
22841
23263
  }
22842
23264
  __name(instrumentModelOperation, "instrumentModelOperation");
22843
- function constraintTraceReference(constraint) {
23265
+ function relationTraceReference(constraint) {
22844
23266
  return constraint.traceReference();
22845
23267
  }
22846
- __name(constraintTraceReference, "constraintTraceReference");
23268
+ __name(relationTraceReference, "relationTraceReference");
22847
23269
  function modelObjectRuntimeInfo(object) {
22848
23270
  return {
22849
23271
  nodeId: object.nodeId,
@@ -22970,7 +23392,7 @@ function planModelSnapshotQueries(objects) {
22970
23392
  sourceRef,
22971
23393
  weight: (1 + (input.geometry.topology?.edges.ids.length ?? 0) + (input.geometry.topology?.surfaces.ids.length ?? 0)) * queries.length,
22972
23394
  encode: /* @__PURE__ */ __name(() => encodeKernelArtifact(id, input.geometry), "encode"),
22973
- accept: /* @__PURE__ */ __name((query, value) => snapshotQuery.accept(query.key, value), "accept")
23395
+ accept: /* @__PURE__ */ __name((query, value, milliseconds) => snapshotQuery.accept(query.key, value, milliseconds), "accept")
22974
23396
  }));
22975
23397
  }
22976
23398
  __name(planModelSnapshotQueries, "planModelSnapshotQueries");
@@ -22984,7 +23406,9 @@ function executeSnapshotQueryBatch(id, bytes, queries, checkCancelled, onResult,
22984
23406
  onRestore?.(performance.now() - started);
22985
23407
  for (const query of queries) {
22986
23408
  checkCancelled();
22987
- onResult(query, computeSnapshotQuery(geometry, query));
23409
+ const started2 = performance.now();
23410
+ const value = computeSnapshotQuery(geometry, query);
23411
+ onResult(query, value, performance.now() - started2);
22988
23412
  }
22989
23413
  } finally {
22990
23414
  geometry.shape.delete();
@@ -23045,7 +23469,14 @@ function retainModelGeometry(objects) {
23045
23469
  }
23046
23470
  __name(retainModelGeometry, "retainModelGeometry");
23047
23471
  var authoringApi = Object.freeze({
23048
- cached,
23472
+ offset,
23473
+ rotate: rotate2,
23474
+ pivot,
23475
+ pivotVertex,
23476
+ pivotPoint,
23477
+ axisEdge,
23478
+ axisLine,
23479
+ cache,
23049
23480
  font,
23050
23481
  googleFont,
23051
23482
  text,
@@ -23124,7 +23555,10 @@ function disposeModelGeometryValue(geometry) {
23124
23555
  __name(disposeModelGeometryValue, "disposeModelGeometryValue");
23125
23556
  var kernelShape = cachedArtifact(
23126
23557
  (_operation, _arguments, _inputs, compute) => compute(),
23127
- { key: kernelOperationKey, lifecycle: shapeLifecycle }
23558
+ {
23559
+ key: /* @__PURE__ */ __name((operation, arguments_, inputs) => kernelOperationKey(`kernel-shape:${operation}`, arguments_, inputs), "key"),
23560
+ lifecycle: shapeLifecycle
23561
+ }
23128
23562
  );
23129
23563
  function evaluateKernelShape(operation, arguments_, inputs, compute) {
23130
23564
  return kernelShape(
@@ -23143,7 +23577,10 @@ var evaluateModelGeometry = cachedArtifact(
23143
23577
  referenceBasis: result.referenceBasis
23144
23578
  };
23145
23579
  },
23146
- { key: kernelOperationKey, lifecycle: modelGeometryLifecycle }
23580
+ {
23581
+ key: /* @__PURE__ */ __name((operation, arguments_, inputs) => kernelOperationKey(`model-geometry:${operation}`, arguments_, inputs), "key"),
23582
+ lifecycle: modelGeometryLifecycle
23583
+ }
23147
23584
  );
23148
23585
  function createModelGeometryValue(shape, topology) {
23149
23586
  try {
@@ -23167,15 +23604,15 @@ function evaluateSolidGeometry(operation, arguments_, inputs, compute) {
23167
23604
  );
23168
23605
  }
23169
23606
  __name(evaluateSolidGeometry, "evaluateSolidGeometry");
23170
- function renderMesh(artifact, shape, cache, tolerance2, topology) {
23171
- const cached2 = cache.get(shape);
23172
- if (cached2) {
23173
- return cached2;
23607
+ function renderMesh(artifact, shape, cache2, tolerance2, topology) {
23608
+ const cached = cache2.get(shape);
23609
+ if (cached) {
23610
+ return cached;
23174
23611
  }
23175
23612
  const mesh = evaluateSnapshotQuery(
23176
23613
  meshQuery(artifact, shape, tolerance2, topology)
23177
23614
  );
23178
- cache.set(shape, mesh);
23615
+ cache2.set(shape, mesh);
23179
23616
  return mesh;
23180
23617
  }
23181
23618
  __name(renderMesh, "renderMesh");
@@ -23211,7 +23648,7 @@ function meshUVs(shape, vertexCount) {
23211
23648
  const faces = shape.faces;
23212
23649
  const location = new (getOC12()).TopLoc_Location();
23213
23650
  const uvs = new Float32Array(vertexCount * 2);
23214
- let offset = 0;
23651
+ let offset2 = 0;
23215
23652
  try {
23216
23653
  for (const face of faces) {
23217
23654
  const triangulation = getOC12().BRep_Tool.Triangulation(
@@ -23229,8 +23666,8 @@ function meshUVs(shape, vertexCount) {
23229
23666
  const uv = triangulation.UVNode(index + 1);
23230
23667
  try {
23231
23668
  const u8 = uv.X(), v = uv.Y();
23232
- uvs[(offset + index) * 2] = u8;
23233
- uvs[(offset + index) * 2 + 1] = v;
23669
+ uvs[(offset2 + index) * 2] = u8;
23670
+ uvs[(offset2 + index) * 2 + 1] = v;
23234
23671
  minU = Math.min(minU, u8);
23235
23672
  maxU = Math.max(maxU, u8);
23236
23673
  minV = Math.min(minV, v);
@@ -23240,11 +23677,11 @@ function meshUVs(shape, vertexCount) {
23240
23677
  }
23241
23678
  }
23242
23679
  for (let index = 0; index < count; index++) {
23243
- const base = (offset + index) * 2;
23680
+ const base = (offset2 + index) * 2;
23244
23681
  uvs[base] = maxU === minU ? 0 : (uvs[base] - minU) / (maxU - minU);
23245
23682
  uvs[base + 1] = maxV === minV ? 0 : (uvs[base + 1] - minV) / (maxV - minV);
23246
23683
  }
23247
- offset += count;
23684
+ offset2 += count;
23248
23685
  } finally {
23249
23686
  triangulation.delete();
23250
23687
  }
@@ -23256,7 +23693,7 @@ function meshUVs(shape, vertexCount) {
23256
23693
  }
23257
23694
  }
23258
23695
  __name(meshUVs, "meshUVs");
23259
- function sketchFaceModel(region, constraints, source) {
23696
+ function sketchFaceModel(region, placements, source) {
23260
23697
  const curves = [region.outer, ...region.holes].map(
23261
23698
  (loop) => loop.map(
23262
23699
  (curve) => curve.kind === "line" ? ["line", curve.points] : curve.kind === "circle" ? ["circle", curve.center, curve.radius] : ["arc", curve.center, curve.radius, curve.start, curve.sweep]
@@ -23272,7 +23709,7 @@ function sketchFaceModel(region, constraints, source) {
23272
23709
  return ModelObject.create({
23273
23710
  kind: "face",
23274
23711
  name: "Sketch face",
23275
- constraints,
23712
+ placements,
23276
23713
  geometry,
23277
23714
  geometryAnchor: plane,
23278
23715
  elements: { plane },
@@ -23411,10 +23848,9 @@ function solidElements(bounds) {
23411
23848
  __name(solidElements, "solidElements");
23412
23849
  function constraintReferences(constraint) {
23413
23850
  return [
23414
- constraint.source.model,
23415
- constraint.target.model,
23416
- ...constraint.rotations.map(
23417
- (action) => action.pivot.kind === "around" ? action.pivot.axis.model : void 0
23851
+ ...constraint.kind === "transformation" ? [] : [constraint.source.model, constraint.target.model],
23852
+ ...(constraint.kind === "transformation" ? constraint.actions : []).map(
23853
+ (action) => "pivot" in action ? selectionReference(action.pivot)?.model : void 0
23418
23854
  )
23419
23855
  ].filter((model) => !!model);
23420
23856
  }
@@ -24238,8 +24674,8 @@ function sketchPointResolver(layers) {
24238
24674
  const visiting = /* @__PURE__ */ new Set();
24239
24675
  const resolve = /* @__PURE__ */ __name((ref) => {
24240
24676
  const key = JSON.stringify([ref.layer, ref.id]);
24241
- const cached2 = resolved.get(key);
24242
- if (cached2) return cached2;
24677
+ const cached = resolved.get(key);
24678
+ if (cached) return cached;
24243
24679
  const point3 = points.get(key);
24244
24680
  if (!point3) throw new Error(`Missing sketch point ${ref.id}.`);
24245
24681
  if (visiting.has(key))
@@ -24743,10 +25179,12 @@ export {
24743
25179
  setKernelNativeMemoryCounter,
24744
25180
  clearKernelOperationCache,
24745
25181
  kernelOperationCacheStats,
25182
+ setKernelCacheBudget,
25183
+ setKernelCachePersistenceThreshold,
24746
25184
  setKernelArtifactStore,
24747
25185
  setKernelExternalBytes,
24748
25186
  identifyCachedFunction,
24749
- cached,
25187
+ cache,
24750
25188
  googleFontUrl,
24751
25189
  googleFontSources,
24752
25190
  installFontEngine,
@@ -24787,6 +25225,13 @@ export {
24787
25225
  modelElementReference,
24788
25226
  modelTopologyReference,
24789
25227
  modelTopologyIds,
25228
+ offset,
25229
+ rotate2 as rotate,
25230
+ pivot,
25231
+ pivotVertex,
25232
+ pivotPoint,
25233
+ axisEdge,
25234
+ axisLine,
24790
25235
  circle,
24791
25236
  ellipse,
24792
25237
  rectangle,
@@ -24813,11 +25258,13 @@ export {
24813
25258
  intersect,
24814
25259
  isModelObject,
24815
25260
  isConstraint,
24816
- isConstraintExpression,
24817
- constraintPreview,
24818
- instrumentConstraint,
25261
+ isRelationExpression,
25262
+ relationPreview,
25263
+ currentRelationSelf,
25264
+ relationSelectionPreview,
25265
+ instrumentRelation,
24819
25266
  instrumentModelOperation,
24820
- constraintTraceReference,
25267
+ relationTraceReference,
24821
25268
  modelObjectRuntimeInfo,
24822
25269
  relatedModelObjects,
24823
25270
  planModelSnapshotQueries,
@@ -24839,4 +25286,4 @@ export {
24839
25286
  assertSketchDragConnections,
24840
25287
  solveSketchSnapshot
24841
25288
  };
24842
- //# sourceMappingURL=chunk-J77FMYFY.js.map
25289
+ //# sourceMappingURL=chunk-4WLVDYUJ.js.map