three-cad-viewer 5.1.0 → 5.1.2

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.
@@ -27,6 +27,11 @@ import { ViewerState } from "./viewer-state.js";
27
27
  import type { Display } from "../ui/display.js";
28
28
  import type { Vector3Tuple, QuaternionTuple } from "three";
29
29
  import { CollapseState, type ZebraColorScheme, type ZebraMappingMode, type StudioToneMapping, type StudioTextureMapping, type StudioBackground, type NotificationCallback, type RenderOptions, type ViewerOptions, type Shapes, type VisibilityState, type ActiveTab, type Axis, type ClipIndex, type ThemeInput, type BoundingBoxFlat, type Keymap } from "./types.js";
30
+ /**
31
+ * Why the viewer renders continuously (see {@link Viewer.setLoopReason}): an
32
+ * animation is playing, a measure/select tool is active, or a screenshot is taken.
33
+ */
34
+ export type LoopReason = "animation" | "tool" | "capture";
30
35
  /**
31
36
  * Material settings for the viewer.
32
37
  */
@@ -187,6 +192,8 @@ declare class Viewer {
187
192
  hasAnimationLoop: boolean;
188
193
  mixer: THREE.AnimationMixer | null;
189
194
  continueAnimation: boolean;
195
+ /** Reasons for continuous rendering; the render loop runs while any is set. */
196
+ private _loopReasons;
190
197
  clipAction: THREE.AnimationAction | null;
191
198
  shapeRenderer: ShapeRenderer | null;
192
199
  camera_distance: number;
@@ -219,7 +226,7 @@ declare class Viewer {
219
226
  /** True while the Studio (presentation) tab owns the render. PickHost member. */
220
227
  get studioActive(): boolean;
221
228
  zScale: number;
222
- /** Whether the attached animation has moved parts (Play pressed or time slider moved). */
229
+ /** Whether the attached animation has moved parts (Play or time slider, not stopped since). */
223
230
  private _animationStarted;
224
231
  clipNormal0: Vector3Tuple | null;
225
232
  clipNormal1: Vector3Tuple | null;
@@ -346,6 +353,24 @@ declare class Viewer {
346
353
  * Start the animation loop
347
354
  */
348
355
  animate: () => void;
356
+ /**
357
+ * Add or remove a reason for continuous rendering. The render loop runs while at
358
+ * least one reason is set; otherwise the viewer renders on demand (camera changes
359
+ * and explicit updates), so an idle viewer costs no CPU/GPU time.
360
+ * @param reason - why continuous rendering is needed.
361
+ * @param flag - whether the reason applies.
362
+ */
363
+ setLoopReason(reason: LoopReason, flag: boolean): void;
364
+ /**
365
+ * Run the render loop only while the animation plays. When it stops (pause,
366
+ * stop, slider), render once so the final pose is shown.
367
+ */
368
+ private _setAnimationPlaying;
369
+ /**
370
+ * Start or stop the render loop directly. Prefer {@link setLoopReason}, which keeps
371
+ * the loop running while any other reason still needs it.
372
+ * @param flag - whether the render loop should run.
373
+ */
349
374
  toggleAnimationLoop(flag: boolean): void;
350
375
  /**
351
376
  * Draw the outline of the clip caps over the frame (Clip tab active only). Skipped
@@ -567,8 +592,9 @@ declare class Viewer {
567
592
  * Copy the world-space point under the cursor as `"x, y, z"` to the clipboard and
568
593
  * send it to the host as a `copiedPoint` notification (for hosts that block the
569
594
  * browser clipboard). Does nothing while parts may be displaced from their model
570
- * positions — explode mode, an animation once started (Play or time slider), a
571
- * z-scale other than 1 — or when no visible component is under the cursor.
595
+ * positions — explode mode, an animation that was played or scrubbed and not
596
+ * stopped since (Stop puts the parts back), a z-scale other than 1 — or when no
597
+ * visible component is under the cursor.
572
598
  * @returns true when a point was copied (the caller then consumes the key event).
573
599
  */
574
600
  copyPointUnderCursor(): boolean;
@@ -116,6 +116,16 @@ declare class Animation {
116
116
  * Dispose of animation resources.
117
117
  */
118
118
  dispose(): void;
119
+ /**
120
+ * Apply the current animation time to the objects without advancing it, so a
121
+ * single on-demand render shows the pose (e.g. after {@link setRelativeTime}).
122
+ */
123
+ apply(): void;
124
+ /**
125
+ * Restart the frame-time measurement, so resuming playback after a pause does
126
+ * not advance the animation by the whole pause.
127
+ */
128
+ resetClock(): void;
119
129
  /**
120
130
  * Update the animation mixer (call each frame when animating).
121
131
  */
@@ -92263,6 +92263,20 @@ class Animation {
92263
92263
  this.tracks = [];
92264
92264
  this.root = null;
92265
92265
  }
92266
+ /**
92267
+ * Apply the current animation time to the objects without advancing it, so a
92268
+ * single on-demand render shows the pose (e.g. after {@link setRelativeTime}).
92269
+ */
92270
+ apply() {
92271
+ this.mixer?.update(0);
92272
+ }
92273
+ /**
92274
+ * Restart the frame-time measurement, so resuming playback after a pause does
92275
+ * not advance the animation by the whole pause.
92276
+ */
92277
+ resetClock() {
92278
+ this.clock.reset();
92279
+ }
92266
92280
  /**
92267
92281
  * Update the animation mixer (call each frame when animating).
92268
92282
  */
@@ -97834,7 +97848,7 @@ class Tools {
97834
97848
  }
97835
97849
  }
97836
97850
 
97837
- const version = "5.1.0";
97851
+ const version = "5.1.2";
97838
97852
 
97839
97853
  /**
97840
97854
  * `PickedComponent` over a GPU id-pick result. Drives the shader
@@ -109429,6 +109443,8 @@ class Viewer {
109429
109443
  * @param updateMarker - enforce to redraw orientation marker after every ui activity
109430
109444
  */
109431
109445
  constructor(display, options, notifyCallback, pinAsPngCallback = null, updateMarker = true) {
109446
+ /** Reasons for continuous rendering; the render loop runs while any is set. */
109447
+ this._loopReasons = new Set();
109432
109448
  // Hide-undo stack: each meta-double-click hide pushes the leaf id + its pre-hide
109433
109449
  // state; meta-double-click on empty space pops and restores the last one. Lets a
109434
109450
  // hidden object be brought back without the tree (e.g. in Studio, where it's hidden).
@@ -109455,7 +109471,7 @@ class Viewer {
109455
109471
  this._capOutlineCamera = [];
109456
109472
  /** Pending redraw that adds the outline once the camera has settled. */
109457
109473
  this._capOutlineSettle = null;
109458
- /** Whether the attached animation has moved parts (Play pressed or time slider moved). */
109474
+ /** Whether the attached animation has moved parts (Play or time slider, not stopped since). */
109459
109475
  this._animationStarted = false;
109460
109476
  // ---------------------------------------------------------------------------
109461
109477
  // Render Loop & Scene Updates
@@ -109864,12 +109880,19 @@ class Viewer {
109864
109880
  this.clipAction.paused = false;
109865
109881
  }
109866
109882
  this.clipAction.play();
109883
+ this._setAnimationPlaying(true);
109867
109884
  break;
109868
109885
  case "pause":
109869
109886
  this.clipAction.paused = !this.clipAction.paused;
109887
+ this._setAnimationPlaying(!this.clipAction.paused && this.clipAction.isRunning());
109870
109888
  break;
109871
109889
  case "stop":
109872
109890
  this.clipAction.stop();
109891
+ // stop() restores the parts' original transforms (three.js
109892
+ // AnimationMixer._deactivateAction -> restoreOriginalState), so they are
109893
+ // at their model positions again and points can be copied.
109894
+ this._animationStarted = false;
109895
+ this._setAnimationPlaying(false);
109873
109896
  break;
109874
109897
  }
109875
109898
  };
@@ -110759,10 +110782,7 @@ class Viewer {
110759
110782
  return Promise.resolve({ task: taskId, dataUrl: null });
110760
110783
  }
110761
110784
  // canvas.toBlob can be very slow when animation loop is off!
110762
- const animationLoop = this.hasAnimationLoop;
110763
- if (!animationLoop) {
110764
- this.toggleAnimationLoop(true);
110765
- }
110785
+ this.setLoopReason("capture", true);
110766
110786
  this.rendered.orientationMarker.setVisible(false);
110767
110787
  this.update(true);
110768
110788
  return this.display.captureCanvas({
@@ -110780,10 +110800,8 @@ class Viewer {
110780
110800
  }
110781
110801
  },
110782
110802
  onComplete: () => {
110783
- // Restore animation loop to original state
110784
- if (!animationLoop) {
110785
- this.toggleAnimationLoop(false);
110786
- }
110803
+ // Restore the loop to what the other reasons need
110804
+ this.setLoopReason("capture", false);
110787
110805
  this.rendered.orientationMarker.setVisible(true);
110788
110806
  this.update(true);
110789
110807
  },
@@ -111181,14 +111199,13 @@ class Viewer {
111181
111199
  return;
111182
111200
  }
111183
111201
  logger.debug("Animation initialized");
111184
- if (!this.hasAnimationLoop) {
111185
- this.toggleAnimationLoop(true);
111186
- }
111187
111202
  this.state.set("animationMode", label === "E" ? "explode" : "animation");
111188
111203
  this._animationStarted = false;
111189
111204
  this.clipAction = this.animation.animate(this.rendered.nestedGroup.rootGroup, duration, speed, repeat);
111190
111205
  // Reset animation slider to start
111191
111206
  this.state.set("animationSliderValue", 0);
111207
+ // Not playing yet: render on demand only (the loop starts with Play).
111208
+ this._setAnimationPlaying(false);
111192
111209
  }
111193
111210
  /**
111194
111211
  * Check whether animation object exists
@@ -111205,7 +111222,7 @@ class Viewer {
111205
111222
  }
111206
111223
  this.state.set("animationMode", "none");
111207
111224
  this._animationStarted = false;
111208
- this.toggleAnimationLoop(false);
111225
+ this.setLoopReason("animation", false);
111209
111226
  }
111210
111227
  /**
111211
111228
  * Set the animation to a specific relative time (0-1).
@@ -111217,6 +111234,8 @@ class Viewer {
111217
111234
  this._animationStarted = true;
111218
111235
  this.animation.setRelativeTime(fraction);
111219
111236
  this.state.set("animationSliderValue", fraction * 1000);
111237
+ // Setting a time pauses the animation: stop the loop and show the new pose.
111238
+ this._setAnimationPlaying(false);
111220
111239
  }
111221
111240
  /**
111222
111241
  * Get the current relative animation time (0-1).
@@ -111225,6 +111244,44 @@ class Viewer {
111225
111244
  getRelativeTime() {
111226
111245
  return this.animation.getRelativeTime();
111227
111246
  }
111247
+ /**
111248
+ * Add or remove a reason for continuous rendering. The render loop runs while at
111249
+ * least one reason is set; otherwise the viewer renders on demand (camera changes
111250
+ * and explicit updates), so an idle viewer costs no CPU/GPU time.
111251
+ * @param reason - why continuous rendering is needed.
111252
+ * @param flag - whether the reason applies.
111253
+ */
111254
+ setLoopReason(reason, flag) {
111255
+ if (flag) {
111256
+ this._loopReasons.add(reason);
111257
+ }
111258
+ else {
111259
+ this._loopReasons.delete(reason);
111260
+ }
111261
+ const run = this._loopReasons.size > 0;
111262
+ if (run !== this.hasAnimationLoop)
111263
+ this.toggleAnimationLoop(run);
111264
+ }
111265
+ /**
111266
+ * Run the render loop only while the animation plays. When it stops (pause,
111267
+ * stop, slider), render once so the final pose is shown.
111268
+ */
111269
+ _setAnimationPlaying(playing) {
111270
+ if (playing) {
111271
+ // Resume without jumping ahead by the time spent paused.
111272
+ this.animation.resetClock();
111273
+ }
111274
+ this.setLoopReason("animation", playing);
111275
+ if (!playing && this._rendered !== null) {
111276
+ this.animation.apply();
111277
+ this.update(true, false);
111278
+ }
111279
+ }
111280
+ /**
111281
+ * Start or stop the render loop directly. Prefer {@link setLoopReason}, which keeps
111282
+ * the loop running while any other reason still needs it.
111283
+ * @param flag - whether the render loop should run.
111284
+ */
111228
111285
  toggleAnimationLoop(flag) {
111229
111286
  if (flag) {
111230
111287
  this.continueAnimation = true;
@@ -111375,6 +111432,7 @@ class Viewer {
111375
111432
  // Hide the topo filter (+ detach its shortcuts) for a clean cleared canvas.
111376
111433
  this.display.shapeFilterDropDownMenu.show(false);
111377
111434
  // stop animation
111435
+ this._loopReasons.clear();
111378
111436
  this.hasAnimationLoop = false;
111379
111437
  this.continueAnimation = false;
111380
111438
  // remove change listener if exists
@@ -111957,8 +112015,9 @@ class Viewer {
111957
112015
  * Copy the world-space point under the cursor as `"x, y, z"` to the clipboard and
111958
112016
  * send it to the host as a `copiedPoint` notification (for hosts that block the
111959
112017
  * browser clipboard). Does nothing while parts may be displaced from their model
111960
- * positions — explode mode, an animation once started (Play or time slider), a
111961
- * z-scale other than 1 — or when no visible component is under the cursor.
112018
+ * positions — explode mode, an animation that was played or scrubbed and not
112019
+ * stopped since (Stop puts the parts back), a z-scale other than 1 — or when no
112020
+ * visible component is under the cursor.
111962
112021
  * @returns true when a point was copied (the caller then consumes the key event).
111963
112022
  */
111964
112023
  copyPointUnderCursor() {
@@ -114069,7 +114128,7 @@ class Display {
114069
114128
  if (flag && this.viewer.isStudioActive) {
114070
114129
  return;
114071
114130
  }
114072
- this.viewer.toggleAnimationLoop(flag);
114131
+ this.viewer.setLoopReason("tool", flag);
114073
114132
  if (flag) {
114074
114133
  // Delegate state mutations to Viewer
114075
114134
  this.viewer.activateTool(name, true);