partforge 0.53.0 → 0.54.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.
@@ -39,13 +39,15 @@ export function srgbEncodeInPlace(data) {
39
39
  // `renderer.renderOffscreen(pose)` does the GL work (temp camera → offscreen
40
40
  // target → readback → JPEG data URL); injected so this is unit-testable without
41
41
  // a GL context. The grid is hidden for the whole synchronous pass and restored.
42
- export function captureViewsFromScene(viewNames, { renderer, liveCamera, grid, bounds }) {
42
+ export function captureViewsFromScene(viewNames, { renderer, liveCamera, grid, bounds, hidden = [] }) {
43
43
  const views = (viewNames?.length ? viewNames : ["iso", "front", "top"])
44
44
  .filter((v) => CANONICAL_VIEWS.includes(v))
45
45
  .slice(0, CANONICAL_VIEWS.length);
46
46
  const before = liveCamera.position.clone();
47
47
  const gridWasVisible = grid?.visible;
48
48
  if (grid) grid.visible = false;
49
+ const hiddenWas = hidden.map((o) => o.visible);
50
+ for (const o of hidden) o.visible = false;
49
51
  try {
50
52
  return views.map((view) => ({
51
53
  view,
@@ -53,6 +55,7 @@ export function captureViewsFromScene(viewNames, { renderer, liveCamera, grid, b
53
55
  }));
54
56
  } finally {
55
57
  if (grid) grid.visible = gridWasVisible;
58
+ hidden.forEach((o, i) => { o.visible = hiddenWas[i]; });
56
59
  liveCamera.position.copy(before); // belt-and-suspenders: never leak camera state
57
60
  }
58
61
  }
@@ -139,6 +142,10 @@ export function createViewer(container, part) {
139
142
  };
140
143
  scene.background = new THREE.Color(THEME.dark.bg);
141
144
 
145
+ let currentTheme = "dark";
146
+ const themeListeners = new Set();
147
+ function onThemeChange(cb) { themeListeners.add(cb); return () => themeListeners.delete(cb); }
148
+
142
149
  const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 1000);
143
150
  camera.position.set(18, 12, 18);
144
151
 
@@ -536,6 +543,8 @@ export function createViewer(container, part) {
536
543
  for (const m of fadeLineMats.values()) m.color.set(t.line); // clones follow the theme
537
544
  cutaway.setTheme(mode, t.line);
538
545
  reassertLiveFades(); // setTheme re-clones every section's materials and reassigns them
546
+ currentTheme = THEME[mode] ? mode : "dark";
547
+ for (const cb of [...themeListeners]) cb(currentTheme);
539
548
  }
540
549
 
541
550
  function hideAssembly() {
@@ -640,6 +649,15 @@ export function createViewer(container, part) {
640
649
  return canvas.toDataURL("image/jpeg", quality);
641
650
  }
642
651
 
652
+ // Objects excluded from CANONICAL captures only (agent renders must stay
653
+ // dimension-free); captureCurrent — the user-framed showcase capture —
654
+ // deliberately does NOT consult this set.
655
+ const canonicalCaptureHidden = new Set();
656
+ function registerCanonicalCaptureHidden(obj) {
657
+ canonicalCaptureHidden.add(obj);
658
+ return () => canonicalCaptureHidden.delete(obj);
659
+ }
660
+
643
661
  // Render the canonical camera angles offscreen, framed to whatever is visible,
644
662
  // without disturbing the user's live view. Returns [{ view, dataUrl }].
645
663
  function captureCanonicalViews(viewNames) {
@@ -653,6 +671,7 @@ export function createViewer(container, part) {
653
671
  renderer: { renderOffscreen },
654
672
  liveCamera: camera,
655
673
  grid,
674
+ hidden: [...canonicalCaptureHidden],
656
675
  bounds: { center, radius },
657
676
  });
658
677
  }
@@ -877,6 +896,8 @@ export function createViewer(container, part) {
877
896
  controls.removeEventListener("start", onControlsStart);
878
897
  cameraStartListeners.clear();
879
898
  frameListeners.clear();
899
+ themeListeners.clear();
900
+ canonicalCaptureHidden.clear();
880
901
  camTween.cancel();
881
902
  controls.dispose();
882
903
  for (const t of flashTimers) clearTimeout(t);
@@ -930,6 +951,8 @@ export function createViewer(container, part) {
930
951
  setActive,
931
952
  onContextLost,
932
953
  setTheme,
954
+ onThemeChange,
955
+ getTheme: () => currentTheme,
933
956
  getCameraState,
934
957
  setCameraState,
935
958
  onCameraEnd,
@@ -946,6 +969,7 @@ export function createViewer(container, part) {
946
969
  resetCutaway: cutaway.reset,
947
970
  isWorldPointVisible: cutaway.isPointVisible,
948
971
  registerCutawayMaterial: cutaway.registerClippableMaterial,
972
+ registerCanonicalCaptureHidden,
949
973
  onCutawayHandleHover: cutaway.onHandleHoverChange,
950
974
  dispose,
951
975
  };
package/types/index.d.ts CHANGED
@@ -87,6 +87,7 @@ export interface MountElements {
87
87
  reframe?: HTMLElement | null;
88
88
  theme?: HTMLElement | null;
89
89
  cutaway?: HTMLElement | null;
90
+ measure?: HTMLElement | null;
90
91
  railToggle?: HTMLElement | null;
91
92
  };
92
93
  }
@@ -142,6 +143,19 @@ export interface CaptureViewOptions {
142
143
  angle?: CanonicalView | string;
143
144
  }
144
145
 
146
+ /**
147
+ * Measurement mode runtime controls. Dimensioned captures come straight from
148
+ * `captureCurrent()` while the mode is enabled — in-scene dims render into
149
+ * the frame natively (canonical-view captures and thumbnails never include
150
+ * them).
151
+ */
152
+ export interface MeasureRuntime {
153
+ isEnabled(): boolean;
154
+ setEnabled(on: boolean): void;
155
+ clearPins(): void;
156
+ pinCount(): number;
157
+ }
158
+
145
159
  /** Where playback is: idle, swinging the camera to an intro cue, playing, or paused. */
146
160
  export type AnimationStatus = "idle" | "intro" | "playing" | "paused";
147
161
 
@@ -253,6 +267,8 @@ export interface PartRuntime {
253
267
  * active view has none, where `state().animation` reads `null`.
254
268
  */
255
269
  animation: AnimationRuntime | null;
270
+ /** Measurement mode's runtime-controls API — mode on/off, unit, and pin state; dimensioned captures come from `captureCurrent()` while enabled. Always present (a no-op stand-in outside `makeHandle` tests). */
271
+ measure: MeasureRuntime;
256
272
  }
257
273
 
258
274
  /** Mount a full parametric-part app from a `PartDefinition`. */