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.
- package/dist/brepjs.cjs +18 -287
- package/dist/brepjs.js +2 -275
- package/dist/index.d.ts +1 -1
- package/dist/operations/jointFns.d.ts +88 -14
- package/dist/operations.cjs +12 -1
- package/dist/operations.d.ts +1 -0
- package/dist/operations.js +2 -2
- package/dist/{threadFns-a_C2wGGt.cjs → threadFns-B7a1EVpS.cjs} +498 -0
- package/dist/{threadFns-DGEyXFGP.js → threadFns-Cra0yHSF.js} +415 -1
- package/package.json +1 -1
|
@@ -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 {
|
|
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 };
|