partforge 0.129.0 → 0.130.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/docs/AUTHORING-PARTS.md
CHANGED
|
@@ -353,7 +353,10 @@ meta: { title: "…", units: "mm", environment: "studio" },
|
|
|
353
353
|
colour, flattened so dark materials stay readable. **Realistic** mode — the
|
|
354
354
|
viewer's toggle — shows the full material under environment lighting, with a
|
|
355
355
|
ground and soft shadow and no feature lines. The part never moves when the
|
|
356
|
-
mode changes.
|
|
356
|
+
mode changes. The ground is placed under the part when a view is shown and
|
|
357
|
+
after each edit; while an animation plays or is scrubbed it stays where it is
|
|
358
|
+
(dropping lower only if the part would otherwise sink into it), so a motion
|
|
359
|
+
that changes the part's footprint never slides the floor around under it.
|
|
357
360
|
|
|
358
361
|
**3D-print layer lines** (`pla-print`, `petg-print`) run perpendicular to the
|
|
359
362
|
**export** pose's +Z — the way the part will be printed, not the way it is
|
package/package.json
CHANGED
|
@@ -504,6 +504,12 @@ export function attachAnimationControls(viewer, part, {
|
|
|
504
504
|
// write params, or it would dirty the regen loop for a display-only fade.
|
|
505
505
|
// First write for this run also remembers what the user's params were, so
|
|
506
506
|
// Reset can put them back.
|
|
507
|
+
// Anything the animation moves holds the realistic floor where it is (see
|
|
508
|
+
// viewer.js placeGround): a track that rebuilds geometry re-shows the
|
|
509
|
+
// assembly every delivery, and the floor must not chase each frame's
|
|
510
|
+
// bounds. Released by a user edit, reset/stop, or a view switch — NOT by
|
|
511
|
+
// reaching the end, which would just move the jump to the last frame.
|
|
512
|
+
if (Object.keys(r.values).length || Object.keys(r.opacity ?? {}).length) viewer.holdGround?.(true);
|
|
507
513
|
if (Object.keys(r.values).length) {
|
|
508
514
|
if (snapshot == null) snapshot = getParamValues(current.trackedKeys);
|
|
509
515
|
applyValues(r.values);
|
|
@@ -539,6 +545,9 @@ export function attachAnimationControls(viewer, part, {
|
|
|
539
545
|
}
|
|
540
546
|
|
|
541
547
|
function doReset() {
|
|
548
|
+
// Before the snapshot is re-applied: the rebuild that restores the user's
|
|
549
|
+
// params is an ordinary placement, so the floor comes back to the part.
|
|
550
|
+
viewer.holdGround?.(false);
|
|
542
551
|
playback?.reset();
|
|
543
552
|
viewer.cancelCameraTween();
|
|
544
553
|
viewer.clearSubPartOpacities?.();
|
|
@@ -814,6 +823,7 @@ export function attachAnimationControls(viewer, part, {
|
|
|
814
823
|
// pause playback rather than fight over them.
|
|
815
824
|
notifyUserEdit() {
|
|
816
825
|
disarmAutoplay();
|
|
826
|
+
viewer.holdGround?.(false); // a user edit places the floor again, as without animation
|
|
817
827
|
viewer.cancelCameraTween();
|
|
818
828
|
playback?.userEdited();
|
|
819
829
|
syncUi();
|
|
@@ -854,6 +864,7 @@ export function attachAnimationControls(viewer, part, {
|
|
|
854
864
|
if (status !== "playing" && status !== "intro") guarded(() => playback.play());
|
|
855
865
|
},
|
|
856
866
|
detach() {
|
|
867
|
+
viewer.holdGround?.(false);
|
|
857
868
|
offFrame();
|
|
858
869
|
offOrbit();
|
|
859
870
|
viewer.clearSubPartOpacities?.();
|
package/src/framework/viewer.js
CHANGED
|
@@ -794,15 +794,28 @@ export function createViewer(container, part) {
|
|
|
794
794
|
// The ground comes to the part: it sits at the bottom of what is visible and
|
|
795
795
|
// is sized from it. Only on showAssembly (and on entering the mode) — an
|
|
796
796
|
// animated pose change moves the shadow, never the floor.
|
|
797
|
+
//
|
|
798
|
+
// An animation that REBUILDS geometry re-shows the assembly on every
|
|
799
|
+
// delivery, though, and re-deriving the floor from each frame's bounds slides
|
|
800
|
+
// it under the part as the bounds shift (feedback #138: a lid swinging open
|
|
801
|
+
// dragged the floor back with it). So while the animation driver holds the
|
|
802
|
+
// ground, a placement reuses the last one and may only LOWER it — never slide
|
|
803
|
+
// it sideways or resize it, and never lift it into the part. The hold is
|
|
804
|
+
// viewer-wide rather than per rig, so an environment switch mid-playback
|
|
805
|
+
// lands its floor in the same place.
|
|
806
|
+
let groundHeld = false;
|
|
807
|
+
let lastGround = null;
|
|
808
|
+
function holdGround(on) { groundHeld = !!on; }
|
|
797
809
|
function placeGround({ force = false } = {}) {
|
|
798
810
|
if (!realisticRig) return;
|
|
799
811
|
const b = getVisibleWorldBounds(); // shared Box3: read it out before anything else runs
|
|
800
812
|
if (b.isEmpty()) return;
|
|
801
813
|
const center = b.getCenter(new THREE.Vector3());
|
|
802
814
|
const size = b.getSize(new THREE.Vector3());
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
815
|
+
lastGround = groundHeld && lastGround
|
|
816
|
+
? { ...lastGround, y: Math.min(lastGround.y, b.min.y) }
|
|
817
|
+
: { y: b.min.y, centerX: center.x, centerZ: center.z, radius: size.length() / 2, footprintMm: Math.max(size.x, size.z) };
|
|
818
|
+
realisticRig.setGround(lastGround);
|
|
806
819
|
shadowMovedAt = null; // this render is the full-resolution one
|
|
807
820
|
renderShadow(undefined, { force });
|
|
808
821
|
}
|
|
@@ -2658,6 +2671,7 @@ export function createViewer(container, part) {
|
|
|
2658
2671
|
onFrame,
|
|
2659
2672
|
onAssemblyChange: (cb) => { assemblyListeners.add(cb); return () => assemblyListeners.delete(cb); },
|
|
2660
2673
|
tweenCameraTo,
|
|
2674
|
+
holdGround,
|
|
2661
2675
|
cancelCameraTween,
|
|
2662
2676
|
orbitBy,
|
|
2663
2677
|
onCameraStart,
|