brepjs 18.78.1 → 18.79.0

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.
@@ -255,6 +255,420 @@ function collectShapes(root) {
255
255
  return shapes;
256
256
  }
257
257
  //#endregion
258
+ //#region src/utils/quaternion.ts
259
+ function dot(a, b) {
260
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
261
+ }
262
+ function cross$1(a, b) {
263
+ return [
264
+ a[1] * b[2] - a[2] * b[1],
265
+ a[2] * b[0] - a[0] * b[2],
266
+ a[0] * b[1] - a[1] * b[0]
267
+ ];
268
+ }
269
+ function normalize(a) {
270
+ const l = Math.hypot(a[0], a[1], a[2]) || 1;
271
+ return [
272
+ a[0] / l,
273
+ a[1] / l,
274
+ a[2] / l
275
+ ];
276
+ }
277
+ /** A unit vector perpendicular to `v` (for the 180°/parallel degenerate cases). */
278
+ function anyPerpendicular$1(v) {
279
+ return normalize(cross$1(v, Math.abs(v[0]) < .9 ? [
280
+ 1,
281
+ 0,
282
+ 0
283
+ ] : [
284
+ 0,
285
+ 1,
286
+ 0
287
+ ]));
288
+ }
289
+ /** Rotate vector `v` by quaternion `q`. */
290
+ function quatRotate(q, v) {
291
+ const [w, x, y, z] = q;
292
+ const tx = 2 * (y * v[2] - z * v[1]);
293
+ const ty = 2 * (z * v[0] - x * v[2]);
294
+ const tz = 2 * (x * v[1] - y * v[0]);
295
+ return [
296
+ v[0] + w * tx + (y * tz - z * ty),
297
+ v[1] + w * ty + (z * tx - x * tz),
298
+ v[2] + w * tz + (x * ty - y * tx)
299
+ ];
300
+ }
301
+ /** Quaternion for a rotation of `angle` radians about (unit-normalized) `axis`. */
302
+ function quatFromAxisAngle(axis, angle) {
303
+ const h = angle / 2;
304
+ const s = Math.sin(h);
305
+ const u = normalize(axis);
306
+ return [
307
+ Math.cos(h),
308
+ u[0] * s,
309
+ u[1] * s,
310
+ u[2] * s
311
+ ];
312
+ }
313
+ /** Shortest-arc quaternion rotating unit vector `from` onto unit vector `to`. */
314
+ function quatFromTo(from, to) {
315
+ const a = normalize(from);
316
+ const b = normalize(to);
317
+ const d = dot(a, b);
318
+ if (d >= .999999999) return [
319
+ 1,
320
+ 0,
321
+ 0,
322
+ 0
323
+ ];
324
+ if (d <= -.999999999) return quatFromAxisAngle(anyPerpendicular$1(a), Math.PI);
325
+ const c = cross$1(a, b);
326
+ const len = Math.hypot(1 + d, c[0], c[1], c[2]) || 1;
327
+ return [
328
+ (1 + d) / len,
329
+ c[0] / len,
330
+ c[1] / len,
331
+ c[2] / len
332
+ ];
333
+ }
334
+ /** Hamilton product `a ⊗ b` — the rotation that applies `b` first, then `a`. */
335
+ function quatMultiply(a, b) {
336
+ const [aw, ax, ay, az] = a;
337
+ const [bw, bx, by, bz] = b;
338
+ return [
339
+ aw * bw - ax * bx - ay * by - az * bz,
340
+ aw * bx + ax * bw + ay * bz - az * by,
341
+ aw * by - ax * bz + ay * bw + az * bx,
342
+ aw * bz + ax * by - ay * bx + az * bw
343
+ ];
344
+ }
345
+ //#endregion
346
+ //#region src/operations/jointFns.ts
347
+ var DEG2RAD = Math.PI / 180;
348
+ function clamp(value, min, max) {
349
+ return Math.min(max, Math.max(min, value));
350
+ }
351
+ function unit(v) {
352
+ const l = Math.hypot(v[0], v[1], v[2]) || 1;
353
+ return [
354
+ v[0] / l,
355
+ v[1] / l,
356
+ v[2] / l
357
+ ];
358
+ }
359
+ function cross(a, b) {
360
+ return [
361
+ a[1] * b[2] - a[2] * b[1],
362
+ a[2] * b[0] - a[0] * b[2],
363
+ a[0] * b[1] - a[1] * b[0]
364
+ ];
365
+ }
366
+ /** A unit vector perpendicular to `v` (for the unspecified-reference case). */
367
+ function anyPerpendicular(v) {
368
+ return unit(cross(v, Math.abs(v[0]) < .9 ? [
369
+ 1,
370
+ 0,
371
+ 0
372
+ ] : [
373
+ 0,
374
+ 1,
375
+ 0
376
+ ]));
377
+ }
378
+ /** Build one DOF, normalizing an inverted range and clamping the initial value. */
379
+ function makeDof(kind, axis, opts, defMin, defMax) {
380
+ const a = opts.min ?? defMin;
381
+ const b = opts.max ?? defMax;
382
+ const min = Math.min(a, b);
383
+ const max = Math.max(a, b);
384
+ return {
385
+ kind,
386
+ axis: unit(axis),
387
+ min,
388
+ max,
389
+ value: clamp(opts.value ?? 0, min, max)
390
+ };
391
+ }
392
+ /** Assemble a Joint from its DOFs, mirroring the primary DOF into the top level. */
393
+ function buildJoint(type, parent, child, axis, dofs) {
394
+ const primary = dofs[0] ?? {
395
+ kind: "rotation",
396
+ axis: [
397
+ 0,
398
+ 0,
399
+ 1
400
+ ],
401
+ min: 0,
402
+ max: 0,
403
+ value: 0
404
+ };
405
+ return {
406
+ type,
407
+ parent,
408
+ child,
409
+ axis: {
410
+ origin: axis.origin,
411
+ direction: unit(axis.direction)
412
+ },
413
+ min: primary.min,
414
+ max: primary.max,
415
+ value: primary.value,
416
+ dofs
417
+ };
418
+ }
419
+ /** A revolute (hinge) joint — the child rotates about `axis` by `value` degrees. */
420
+ function revoluteJoint(parent, child, axis, opts = {}) {
421
+ return buildJoint("revolute", parent, child, axis, [makeDof("rotation", axis.direction, opts, -180, 180)]);
422
+ }
423
+ /**
424
+ * A prismatic (slider) joint — the child translates along `axis` by `value`
425
+ * units. Only `axis.direction` is used; `axis.origin` is ignored (a pure
426
+ * translation has no anchor point), unlike a revolute joint which rotates about
427
+ * the axis line through `origin`.
428
+ */
429
+ function prismaticJoint(parent, child, axis, opts = {}) {
430
+ return buildJoint("prismatic", parent, child, axis, [makeDof("translation", axis.direction, opts, 0, 100)]);
431
+ }
432
+ /**
433
+ * A cylindrical joint — the child both rotates about and slides along a single
434
+ * `axis` (2 DOF). DOF order: `[rotation, translation]`. The two motions share
435
+ * the axis, so they commute; rotation pivots about `axis.origin`.
436
+ */
437
+ function cylindricalJoint(parent, child, axis, opts = {}) {
438
+ return buildJoint("cylindrical", parent, child, axis, [makeDof("rotation", axis.direction, opts.rotation ?? {}, -180, 180), makeDof("translation", axis.direction, opts.translation ?? {}, 0, 100)]);
439
+ }
440
+ /**
441
+ * A planar joint — the child translates within a plane and rotates about its
442
+ * normal (3 DOF). `plane.direction` is the normal; `plane.origin` the rotation
443
+ * anchor. DOF order: `[u-translation, v-translation, rotation]`, where the
444
+ * translations are applied in the plane frame (independent of the rotation).
445
+ */
446
+ function planarJoint(parent, child, plane, opts = {}) {
447
+ const normal = unit(plane.direction);
448
+ let u;
449
+ if (opts.uDirection) {
450
+ const d = opts.uDirection;
451
+ const proj = d[0] * normal[0] + d[1] * normal[1] + d[2] * normal[2];
452
+ const inPlane = [
453
+ d[0] - proj * normal[0],
454
+ d[1] - proj * normal[1],
455
+ d[2] - proj * normal[2]
456
+ ];
457
+ u = Math.hypot(inPlane[0], inPlane[1], inPlane[2]) < 1e-9 ? anyPerpendicular(normal) : unit(inPlane);
458
+ } else u = anyPerpendicular(normal);
459
+ const v = unit(cross(normal, u));
460
+ return buildJoint("planar", parent, child, {
461
+ origin: plane.origin,
462
+ direction: normal
463
+ }, [
464
+ makeDof("translation", u, opts.u ?? {}, -100, 100),
465
+ makeDof("translation", v, opts.v ?? {}, -100, 100),
466
+ makeDof("rotation", normal, opts.rotation ?? {}, -180, 180)
467
+ ]);
468
+ }
469
+ /**
470
+ * A spherical (ball) joint — the child rotates freely about a pivot point
471
+ * (3 DOF). DOF order: `[x, y, z]` rotations about the local axes through
472
+ * `pivot`, composed as `Rx · Ry · Rz`.
473
+ */
474
+ function sphericalJoint(parent, child, pivot, opts = {}) {
475
+ return buildJoint("spherical", parent, child, {
476
+ origin: pivot,
477
+ direction: [
478
+ 0,
479
+ 0,
480
+ 1
481
+ ]
482
+ }, [
483
+ makeDof("rotation", [
484
+ 1,
485
+ 0,
486
+ 0
487
+ ], opts.x ?? {}, -180, 180),
488
+ makeDof("rotation", [
489
+ 0,
490
+ 1,
491
+ 0
492
+ ], opts.y ?? {}, -180, 180),
493
+ makeDof("rotation", [
494
+ 0,
495
+ 0,
496
+ 1
497
+ ], opts.z ?? {}, -180, 180)
498
+ ]);
499
+ }
500
+ /**
501
+ * Return a copy of `joint` with per-DOF values set (each clamped to its range).
502
+ * Values are positional, matching `joint.dofs`; omitted entries keep their
503
+ * stored value. The primary mirror (`value`) is kept in sync with `dofs[0]`.
504
+ */
505
+ function setJointValues(joint, values) {
506
+ const dofs = joint.dofs.map((d, i) => {
507
+ const v = values[i];
508
+ return v === void 0 ? d : {
509
+ ...d,
510
+ value: clamp(v, d.min, d.max)
511
+ };
512
+ });
513
+ const primary = dofs[0];
514
+ return primary ? {
515
+ ...joint,
516
+ dofs,
517
+ value: primary.value
518
+ } : {
519
+ ...joint,
520
+ dofs
521
+ };
522
+ }
523
+ /** Return a copy of `joint` with its primary DOF set (clamped to range). */
524
+ function setJointValue(joint, value) {
525
+ return setJointValues(joint, [value]);
526
+ }
527
+ /** The local rigid transform contributed by a single DOF at `value`. */
528
+ function dofPose(origin, dof, value) {
529
+ if (dof.kind === "translation") return {
530
+ position: [
531
+ dof.axis[0] * value,
532
+ dof.axis[1] * value,
533
+ dof.axis[2] * value
534
+ ],
535
+ rotation: [
536
+ 1,
537
+ 0,
538
+ 0,
539
+ 0
540
+ ]
541
+ };
542
+ const rotation = quatFromAxisAngle(dof.axis, value * DEG2RAD);
543
+ const ro = quatRotate(rotation, origin);
544
+ return {
545
+ position: [
546
+ origin[0] - ro[0],
547
+ origin[1] - ro[1],
548
+ origin[2] - ro[2]
549
+ ],
550
+ rotation
551
+ };
552
+ }
553
+ /**
554
+ * The child's local rigid transform (relative to the parent) for given DOF
555
+ * values. Defaults to each DOF's stored value. A single `number` overrides only
556
+ * the primary DOF (single-DOF ergonomics); an array overrides positionally,
557
+ * with omitted entries keeping their stored value. Each value is clamped to its
558
+ * DOF range.
559
+ *
560
+ * DOFs are folded in array order via frame composition. For same-anchor
561
+ * rotations (e.g. spherical) this composes to a single rotation about the pivot;
562
+ * for a cylindrical axis the rotation and slide commute.
563
+ */
564
+ function jointTransform(joint, value = joint.value) {
565
+ const overrides = Array.isArray(value) ? value : void 0;
566
+ const primary = overrides ? void 0 : value;
567
+ const origin = joint.axis.origin;
568
+ let pose = IDENTITY_POSE;
569
+ for (let i = 0; i < joint.dofs.length; i++) {
570
+ const dof = joint.dofs[i];
571
+ if (!dof) continue;
572
+ const raw = overrides ? overrides[i] ?? dof.value : i === 0 ? primary ?? dof.value : dof.value;
573
+ pose = composePose(pose, dofPose(origin, dof, clamp(raw, dof.min, dof.max)));
574
+ }
575
+ return pose;
576
+ }
577
+ /** Attach a joint to an assembly node. Returns a new node (immutable). */
578
+ function addJoint(assembly, joint) {
579
+ const existing = assembly.joints ?? [];
580
+ return {
581
+ ...assembly,
582
+ joints: [...existing, joint]
583
+ };
584
+ }
585
+ var IDENTITY_POSE = {
586
+ position: [
587
+ 0,
588
+ 0,
589
+ 0
590
+ ],
591
+ rotation: [
592
+ 1,
593
+ 0,
594
+ 0,
595
+ 0
596
+ ]
597
+ };
598
+ /** Compose two poses: the result applies `b` in `a`'s frame (`a ∘ b`). */
599
+ function composePose(a, b) {
600
+ const rb = quatRotate(a.rotation, b.position);
601
+ return {
602
+ position: [
603
+ a.position[0] + rb[0],
604
+ a.position[1] + rb[1],
605
+ a.position[2] + rb[2]
606
+ ],
607
+ rotation: quatMultiply(a.rotation, b.rotation)
608
+ };
609
+ }
610
+ /** Collect every joint attached anywhere in the assembly tree. */
611
+ function collectJoints(assembly) {
612
+ const joints = [];
613
+ walkAssembly(assembly, (node) => {
614
+ if (node.joints) joints.push(...node.joints);
615
+ });
616
+ return joints;
617
+ }
618
+ /**
619
+ * Forward kinematics: set joint values and propagate world poses down the
620
+ * kinematic chain. Each joint's axis is interpreted in its **parent's** frame,
621
+ * so a child's world pose is `parentWorld ∘ jointTransform(joint, value)`.
622
+ *
623
+ * Bodies not driven by a joint (chain roots) start at the origin. `jointValues`
624
+ * overrides a joint's stored value, keyed by the **child** node name; omitted
625
+ * joints use `joint.value`. Resolution is topological (reuses the Phase-0
626
+ * ordering), so chains of any depth compose. Returns a world pose for every node.
627
+ */
628
+ function forwardKinematics(assembly, jointValues = {}) {
629
+ const joints = [];
630
+ const names = /* @__PURE__ */ new Set();
631
+ walkAssembly(assembly, (node) => {
632
+ names.add(node.name);
633
+ if (node.joints) joints.push(...node.joints);
634
+ });
635
+ const byChild = /* @__PURE__ */ new Map();
636
+ for (const j of joints) {
637
+ byChild.set(j.child, j);
638
+ names.add(j.parent);
639
+ names.add(j.child);
640
+ }
641
+ const poses = /* @__PURE__ */ new Map();
642
+ for (const name of names) if (!byChild.has(name)) poses.set(name, IDENTITY_POSE);
643
+ const pending = [...joints];
644
+ let progress = true;
645
+ while (progress && pending.length > 0) {
646
+ progress = false;
647
+ for (let i = pending.length - 1; i >= 0; i--) {
648
+ const j = pending[i];
649
+ if (!j) continue;
650
+ const parentPose = poses.get(j.parent);
651
+ if (!parentPose) continue;
652
+ pending.splice(i, 1);
653
+ progress = true;
654
+ if (poses.has(j.child)) continue;
655
+ const value = jointValues[j.child] ?? j.value;
656
+ poses.set(j.child, composePose(parentPose, jointTransform(j, value)));
657
+ }
658
+ }
659
+ for (const j of pending) if (!poses.has(j.child)) poses.set(j.child, IDENTITY_POSE);
660
+ return poses;
661
+ }
662
+ /**
663
+ * Open-chain mobility — the number of independent degrees of freedom, summing
664
+ * each joint's DOF count (revolute/prismatic 1, cylindrical 2, planar/spherical
665
+ * 3). For a serial chain this equals the total DOF. (Closed-loop
666
+ * Grübler/Kutzbach analysis is future work.)
667
+ */
668
+ function mechanismDOF(assembly) {
669
+ return collectJoints(assembly).reduce((sum, j) => sum + j.dofs.length, 0);
670
+ }
671
+ //#endregion
258
672
  //#region src/operations/historyFns.ts
259
673
  /** Create a new empty history. */
260
674
  function createHistory() {
@@ -528,6 +942,12 @@ Object.defineProperty(exports, "addChild", {
528
942
  return addChild;
529
943
  }
530
944
  });
945
+ Object.defineProperty(exports, "addJoint", {
946
+ enumerable: true,
947
+ get: function() {
948
+ return addJoint;
949
+ }
950
+ });
531
951
  Object.defineProperty(exports, "addStep", {
532
952
  enumerable: true,
533
953
  get: function() {
@@ -576,6 +996,12 @@ Object.defineProperty(exports, "createRegistry", {
576
996
  return createRegistry;
577
997
  }
578
998
  });
999
+ Object.defineProperty(exports, "cylindricalJoint", {
1000
+ enumerable: true,
1001
+ get: function() {
1002
+ return cylindricalJoint;
1003
+ }
1004
+ });
579
1005
  Object.defineProperty(exports, "deserializeHistory", {
580
1006
  enumerable: true,
581
1007
  get: function() {
@@ -600,6 +1026,12 @@ Object.defineProperty(exports, "findStep", {
600
1026
  return findStep;
601
1027
  }
602
1028
  });
1029
+ Object.defineProperty(exports, "forwardKinematics", {
1030
+ enumerable: true,
1031
+ get: function() {
1032
+ return forwardKinematics;
1033
+ }
1034
+ });
603
1035
  Object.defineProperty(exports, "getShape", {
604
1036
  enumerable: true,
605
1037
  get: function() {
@@ -612,18 +1044,60 @@ Object.defineProperty(exports, "gridPattern", {
612
1044
  return gridPattern;
613
1045
  }
614
1046
  });
1047
+ Object.defineProperty(exports, "jointTransform", {
1048
+ enumerable: true,
1049
+ get: function() {
1050
+ return jointTransform;
1051
+ }
1052
+ });
615
1053
  Object.defineProperty(exports, "linearPattern", {
616
1054
  enumerable: true,
617
1055
  get: function() {
618
1056
  return linearPattern;
619
1057
  }
620
1058
  });
1059
+ Object.defineProperty(exports, "mechanismDOF", {
1060
+ enumerable: true,
1061
+ get: function() {
1062
+ return mechanismDOF;
1063
+ }
1064
+ });
621
1065
  Object.defineProperty(exports, "modifyStep", {
622
1066
  enumerable: true,
623
1067
  get: function() {
624
1068
  return modifyStep;
625
1069
  }
626
1070
  });
1071
+ Object.defineProperty(exports, "planarJoint", {
1072
+ enumerable: true,
1073
+ get: function() {
1074
+ return planarJoint;
1075
+ }
1076
+ });
1077
+ Object.defineProperty(exports, "prismaticJoint", {
1078
+ enumerable: true,
1079
+ get: function() {
1080
+ return prismaticJoint;
1081
+ }
1082
+ });
1083
+ Object.defineProperty(exports, "quatFromAxisAngle", {
1084
+ enumerable: true,
1085
+ get: function() {
1086
+ return quatFromAxisAngle;
1087
+ }
1088
+ });
1089
+ Object.defineProperty(exports, "quatFromTo", {
1090
+ enumerable: true,
1091
+ get: function() {
1092
+ return quatFromTo;
1093
+ }
1094
+ });
1095
+ Object.defineProperty(exports, "quatRotate", {
1096
+ enumerable: true,
1097
+ get: function() {
1098
+ return quatRotate;
1099
+ }
1100
+ });
627
1101
  Object.defineProperty(exports, "registerOperation", {
628
1102
  enumerable: true,
629
1103
  get: function() {
@@ -654,12 +1128,36 @@ Object.defineProperty(exports, "replayHistory", {
654
1128
  return replayHistory;
655
1129
  }
656
1130
  });
1131
+ Object.defineProperty(exports, "revoluteJoint", {
1132
+ enumerable: true,
1133
+ get: function() {
1134
+ return revoluteJoint;
1135
+ }
1136
+ });
657
1137
  Object.defineProperty(exports, "serializeHistory", {
658
1138
  enumerable: true,
659
1139
  get: function() {
660
1140
  return serializeHistory;
661
1141
  }
662
1142
  });
1143
+ Object.defineProperty(exports, "setJointValue", {
1144
+ enumerable: true,
1145
+ get: function() {
1146
+ return setJointValue;
1147
+ }
1148
+ });
1149
+ Object.defineProperty(exports, "setJointValues", {
1150
+ enumerable: true,
1151
+ get: function() {
1152
+ return setJointValues;
1153
+ }
1154
+ });
1155
+ Object.defineProperty(exports, "sphericalJoint", {
1156
+ enumerable: true,
1157
+ get: function() {
1158
+ return sphericalJoint;
1159
+ }
1160
+ });
663
1161
  Object.defineProperty(exports, "stepCount", {
664
1162
  enumerable: true,
665
1163
  get: function() {