three-cad-viewer 5.1.0 → 5.1.1

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;
@@ -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
@@ -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.1";
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).
@@ -109864,12 +109880,15 @@ 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
+ this._setAnimationPlaying(false);
109873
109892
  break;
109874
109893
  }
109875
109894
  };
@@ -110759,10 +110778,7 @@ class Viewer {
110759
110778
  return Promise.resolve({ task: taskId, dataUrl: null });
110760
110779
  }
110761
110780
  // canvas.toBlob can be very slow when animation loop is off!
110762
- const animationLoop = this.hasAnimationLoop;
110763
- if (!animationLoop) {
110764
- this.toggleAnimationLoop(true);
110765
- }
110781
+ this.setLoopReason("capture", true);
110766
110782
  this.rendered.orientationMarker.setVisible(false);
110767
110783
  this.update(true);
110768
110784
  return this.display.captureCanvas({
@@ -110780,10 +110796,8 @@ class Viewer {
110780
110796
  }
110781
110797
  },
110782
110798
  onComplete: () => {
110783
- // Restore animation loop to original state
110784
- if (!animationLoop) {
110785
- this.toggleAnimationLoop(false);
110786
- }
110799
+ // Restore the loop to what the other reasons need
110800
+ this.setLoopReason("capture", false);
110787
110801
  this.rendered.orientationMarker.setVisible(true);
110788
110802
  this.update(true);
110789
110803
  },
@@ -111181,14 +111195,13 @@ class Viewer {
111181
111195
  return;
111182
111196
  }
111183
111197
  logger.debug("Animation initialized");
111184
- if (!this.hasAnimationLoop) {
111185
- this.toggleAnimationLoop(true);
111186
- }
111187
111198
  this.state.set("animationMode", label === "E" ? "explode" : "animation");
111188
111199
  this._animationStarted = false;
111189
111200
  this.clipAction = this.animation.animate(this.rendered.nestedGroup.rootGroup, duration, speed, repeat);
111190
111201
  // Reset animation slider to start
111191
111202
  this.state.set("animationSliderValue", 0);
111203
+ // Not playing yet: render on demand only (the loop starts with Play).
111204
+ this._setAnimationPlaying(false);
111192
111205
  }
111193
111206
  /**
111194
111207
  * Check whether animation object exists
@@ -111205,7 +111218,7 @@ class Viewer {
111205
111218
  }
111206
111219
  this.state.set("animationMode", "none");
111207
111220
  this._animationStarted = false;
111208
- this.toggleAnimationLoop(false);
111221
+ this.setLoopReason("animation", false);
111209
111222
  }
111210
111223
  /**
111211
111224
  * Set the animation to a specific relative time (0-1).
@@ -111217,6 +111230,8 @@ class Viewer {
111217
111230
  this._animationStarted = true;
111218
111231
  this.animation.setRelativeTime(fraction);
111219
111232
  this.state.set("animationSliderValue", fraction * 1000);
111233
+ // Setting a time pauses the animation: stop the loop and show the new pose.
111234
+ this._setAnimationPlaying(false);
111220
111235
  }
111221
111236
  /**
111222
111237
  * Get the current relative animation time (0-1).
@@ -111225,6 +111240,44 @@ class Viewer {
111225
111240
  getRelativeTime() {
111226
111241
  return this.animation.getRelativeTime();
111227
111242
  }
111243
+ /**
111244
+ * Add or remove a reason for continuous rendering. The render loop runs while at
111245
+ * least one reason is set; otherwise the viewer renders on demand (camera changes
111246
+ * and explicit updates), so an idle viewer costs no CPU/GPU time.
111247
+ * @param reason - why continuous rendering is needed.
111248
+ * @param flag - whether the reason applies.
111249
+ */
111250
+ setLoopReason(reason, flag) {
111251
+ if (flag) {
111252
+ this._loopReasons.add(reason);
111253
+ }
111254
+ else {
111255
+ this._loopReasons.delete(reason);
111256
+ }
111257
+ const run = this._loopReasons.size > 0;
111258
+ if (run !== this.hasAnimationLoop)
111259
+ this.toggleAnimationLoop(run);
111260
+ }
111261
+ /**
111262
+ * Run the render loop only while the animation plays. When it stops (pause,
111263
+ * stop, slider), render once so the final pose is shown.
111264
+ */
111265
+ _setAnimationPlaying(playing) {
111266
+ if (playing) {
111267
+ // Resume without jumping ahead by the time spent paused.
111268
+ this.animation.resetClock();
111269
+ }
111270
+ this.setLoopReason("animation", playing);
111271
+ if (!playing && this._rendered !== null) {
111272
+ this.animation.apply();
111273
+ this.update(true, false);
111274
+ }
111275
+ }
111276
+ /**
111277
+ * Start or stop the render loop directly. Prefer {@link setLoopReason}, which keeps
111278
+ * the loop running while any other reason still needs it.
111279
+ * @param flag - whether the render loop should run.
111280
+ */
111228
111281
  toggleAnimationLoop(flag) {
111229
111282
  if (flag) {
111230
111283
  this.continueAnimation = true;
@@ -111375,6 +111428,7 @@ class Viewer {
111375
111428
  // Hide the topo filter (+ detach its shortcuts) for a clean cleared canvas.
111376
111429
  this.display.shapeFilterDropDownMenu.show(false);
111377
111430
  // stop animation
111431
+ this._loopReasons.clear();
111378
111432
  this.hasAnimationLoop = false;
111379
111433
  this.continueAnimation = false;
111380
111434
  // remove change listener if exists
@@ -114069,7 +114123,7 @@ class Display {
114069
114123
  if (flag && this.viewer.isStudioActive) {
114070
114124
  return;
114071
114125
  }
114072
- this.viewer.toggleAnimationLoop(flag);
114126
+ this.viewer.setLoopReason("tool", flag);
114073
114127
  if (flag) {
114074
114128
  // Delegate state mutations to Viewer
114075
114129
  this.viewer.activateTool(name, true);