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() {
@@ -522,4 +936,4 @@ function thread(options) {
522
936
  }
523
937
  }
524
938
  //#endregion
525
- export { updateNode as C, linearPattern as D, gridPattern as E, exportAssemblySTEP as O, removeChild as S, circularPattern as T, addChild as _, deserializeHistory as a, createAssemblyNode as b, modifyStep as c, replayFrom as d, replayHistory as f, undoLast as g, stepsFrom as h, createRegistry as i, createAssembly as k, registerOperation as l, stepCount as m, addStep as n, findStep as o, serializeHistory as p, createHistory as r, getShape as s, thread as t, registerShape as u, collectShapes as v, walkAssembly as w, findNode as x, countNodes as y };
939
+ export { quatRotate as A, gridPattern as B, prismaticJoint as C, sphericalJoint as D, setJointValues as E, findNode as F, exportAssemblySTEP as H, removeChild as I, updateNode as L, collectShapes as M, countNodes as N, quatFromAxisAngle as O, createAssemblyNode as P, walkAssembly as R, planarJoint as S, setJointValue as T, createAssembly as U, linearPattern as V, addJoint as _, deserializeHistory as a, jointTransform as b, modifyStep as c, replayFrom as d, replayHistory as f, undoLast as g, stepsFrom as h, createRegistry as i, addChild as j, quatFromTo as k, registerOperation as l, stepCount as m, addStep as n, findStep as o, serializeHistory as p, createHistory as r, getShape as s, thread as t, registerShape as u, cylindricalJoint as v, revoluteJoint as w, mechanismDOF as x, forwardKinematics as y, circularPattern as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brepjs",
3
- "version": "18.78.1",
3
+ "version": "18.79.0",
4
4
  "description": "Web CAD library with pluggable geometry kernel",
5
5
  "keywords": [
6
6
  "cad",