@tscircuit/3d-viewer 0.0.640 → 0.0.642

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/README.md CHANGED
@@ -121,6 +121,7 @@ const svgString = await convert3dCircuitToSvg(circuitJson, options)
121
121
  ```
122
122
 
123
123
  The `convert3dCircuitToSvg` function accepts the following options:
124
+
124
125
  - `width`: Width of the output SVG (default: 800)
125
126
  - `height`: Height of the output SVG (default: 600)
126
127
  - `backgroundColor`: Background color in hex format (default: "#ffffff")
@@ -137,6 +138,7 @@ The `convert3dCircuitToSvg` function accepts the following options:
137
138
  Main component for rendering the 3D PCB viewer.
138
139
 
139
140
  Props:
141
+
140
142
  - `circuit-json`: (optional) An array of AnyCircuitElement objects representing the PCB layout.
141
143
  - `children`: (optional) React children elements describing the PCB layout (alternative to using `circuit-json`).
142
144
  - `resolveStaticAsset`: (optional) Function that receives each component model URL (`obj`, `wrl`, `stl`, `gltf`, `glb`, `step`) and returns the resolved URL to load.
@@ -146,12 +148,14 @@ Props:
146
148
  Defines the PCB board dimensions.
147
149
 
148
150
  Props:
151
+
149
152
  - `width`: Width of the board (e.g., "20mm").
150
153
  - `height`: Height of the board (e.g., "20mm").
151
154
 
152
155
  ### Component Elements
153
156
 
154
157
  Various component elements can be used as children of the `<board>` element:
158
+
155
159
  - `<resistor>`
156
160
  - `<capacitor>`
157
161
  - `<chip>`
@@ -306,7 +310,6 @@ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) f
306
310
 
307
311
  This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
308
312
 
309
-
310
313
  ### Section views
311
314
 
312
315
  Right-click the viewer and choose **Advanced → Section View…**.
package/dist/index.d.ts CHANGED
@@ -60,7 +60,10 @@ type CadViewerProps = Omit<React.ComponentProps<typeof CadViewerJscad>, "circuit
60
60
  circuitJson?: any[];
61
61
  /** Settled circuit retaining authored assembly.explosion stages. */
62
62
  circuit?: RootCircuit;
63
- /** Pre-rendered circuit-json-to-gltf GLB URL. Bypasses geometry/model generation. */
63
+ /**
64
+ * Pre-rendered circuit-json-to-gltf GLB URL. Bypasses geometry/model generation.
65
+ * Changing the GLB preserves the camera. Change the viewer key to frame a different board.
66
+ */
64
67
  glbUrl?: string;
65
68
  /** In-memory alternative to glbUrl. Supply only one GLB source. */
66
69
  glbData?: ArrayBuffer;
package/dist/index.js CHANGED
@@ -22344,7 +22344,7 @@ import * as THREE31 from "three";
22344
22344
  // package.json
22345
22345
  var package_default = {
22346
22346
  name: "@tscircuit/3d-viewer",
22347
- version: "0.0.639",
22347
+ version: "0.0.641",
22348
22348
  repository: {
22349
22349
  type: "git",
22350
22350
  url: "https://github.com/tscircuit/3d-viewer"
@@ -22860,6 +22860,7 @@ var Canvas = forwardRef(
22860
22860
  mountRef.current.removeChild(renderer.domElement);
22861
22861
  }
22862
22862
  renderer.dispose();
22863
+ renderer.forceContextLoss();
22863
22864
  scene.remove(rootObject.current);
22864
22865
  if (window.__TSCIRCUIT_THREE_OBJECT === rootObject.current) {
22865
22866
  window.__TSCIRCUIT_THREE_OBJECT = void 0;
@@ -32041,33 +32042,51 @@ var CadViewerManifold = ({
32041
32042
  var CadViewerManifold_default = CadViewerManifold;
32042
32043
 
32043
32044
  // src/CadViewerGlb.tsx
32044
- import { useEffect as useEffect37, useMemo as useMemo31, useState as useState23 } from "react";
32045
+ import {
32046
+ useEffect as useEffect37,
32047
+ useLayoutEffect as useLayoutEffect2,
32048
+ useState as useState23
32049
+ } from "react";
32045
32050
 
32046
32051
  // src/hooks/useGlbModel.ts
32047
- import { useEffect as useEffect36, useMemo as useMemo30, useState as useState22 } from "react";
32052
+ import { useEffect as useEffect36, useLayoutEffect, useMemo as useMemo30, useState as useState22 } from "react";
32048
32053
  function useGlbModel(source) {
32049
32054
  const request = useMemo30(() => ({ source }), [source]);
32050
32055
  const [result, setResult] = useState22();
32056
+ const displayedGlb = result?.loadedGlb;
32051
32057
  useEffect36(() => {
32052
32058
  const controller = new AbortController();
32053
- let model;
32054
- loadGlbScene(request.source, controller.signal).then((loaded) => {
32055
- model = loaded;
32056
- if (controller.signal.aborted) disposeGlbScene(loaded);
32057
- else setResult({ request, model: loaded });
32059
+ let loadedGlb;
32060
+ loadGlbScene(request.source, controller.signal).then((model) => {
32061
+ if (controller.signal.aborted) {
32062
+ disposeGlbScene(model);
32063
+ return;
32064
+ }
32065
+ loadedGlb = { model, wasDisplayed: false };
32066
+ setResult({ request, loadedGlb });
32058
32067
  }).catch((error) => {
32059
- if (!controller.signal.aborted)
32060
- setResult({
32061
- request,
32062
- error: error instanceof Error ? error.message : String(error)
32063
- });
32068
+ if (controller.signal.aborted) return;
32069
+ setResult((previous) => ({
32070
+ request,
32071
+ loadedGlb: previous?.loadedGlb,
32072
+ error: error instanceof Error ? error.message : String(error)
32073
+ }));
32064
32074
  });
32065
32075
  return () => {
32066
32076
  controller.abort();
32067
- if (model) disposeGlbScene(model);
32077
+ if (loadedGlb && !loadedGlb.wasDisplayed) disposeGlbScene(loadedGlb.model);
32068
32078
  };
32069
32079
  }, [request]);
32070
- return result?.request === request ? result : void 0;
32080
+ useLayoutEffect(() => {
32081
+ if (!displayedGlb) return;
32082
+ displayedGlb.wasDisplayed = true;
32083
+ return () => disposeGlbScene(displayedGlb.model);
32084
+ }, [displayedGlb]);
32085
+ return {
32086
+ model: displayedGlb?.model,
32087
+ isLoading: result?.request !== request,
32088
+ error: result?.request === request ? result.error : void 0
32089
+ };
32071
32090
  }
32072
32091
 
32073
32092
  // src/CadViewerGlb.tsx
@@ -32080,19 +32099,7 @@ function GlbScene({
32080
32099
  const { rootObject } = useThree();
32081
32100
  const { addHoverable, removeHoverable } = useHover();
32082
32101
  const [hover, setHover] = useState23();
32083
- const bounds = useMemo31(() => {
32084
- const box = new THREE62.Box3().setFromObject(model);
32085
- if (box.isEmpty()) return;
32086
- return {
32087
- minX: box.min.x,
32088
- maxX: box.max.x,
32089
- minY: box.min.y,
32090
- maxY: box.max.y,
32091
- minZ: box.min.z,
32092
- maxZ: box.max.z
32093
- };
32094
- }, [model]);
32095
- useEffect37(() => {
32102
+ useLayoutEffect2(() => {
32096
32103
  rootObject.add(model);
32097
32104
  return () => {
32098
32105
  rootObject.remove(model);
@@ -32143,54 +32150,88 @@ function GlbScene({
32143
32150
  for (const mesh of meshes) removeHoverable(mesh);
32144
32151
  };
32145
32152
  }, [model, objectVisibility, addHoverable, removeHoverable]);
32153
+ return /* @__PURE__ */ jsx24(Fragment5, { children: hover && /* @__PURE__ */ jsx24(
32154
+ Html,
32155
+ {
32156
+ position: hover.position,
32157
+ style: {
32158
+ background: "white",
32159
+ color: "black",
32160
+ padding: "5px",
32161
+ borderRadius: "3px",
32162
+ fontFamily: "sans-serif",
32163
+ transform: "translate(1rem, 1rem)"
32164
+ },
32165
+ children: hover.name
32166
+ }
32167
+ ) });
32168
+ }
32169
+ function CadViewerGlb({
32170
+ source,
32171
+ objectVisibility,
32172
+ ...containerProps
32173
+ }) {
32174
+ const { model, isLoading, error } = useGlbModel(source);
32175
+ const message = error ? `Unable to load 3D view: ${error}` : "Loading 3D view...";
32176
+ const role = error ? "alert" : "status";
32177
+ if (!model) return /* @__PURE__ */ jsx24("div", { role, children: message });
32146
32178
  return /* @__PURE__ */ jsxs8(Fragment5, { children: [
32147
- bounds && /* @__PURE__ */ jsx24(FitCameraToComparison, { bounds }),
32148
- hover && /* @__PURE__ */ jsx24(
32149
- Html,
32179
+ /* @__PURE__ */ jsx24(
32180
+ GlbViewport,
32150
32181
  {
32151
- position: hover.position,
32152
- style: {
32153
- background: "white",
32154
- color: "black",
32155
- padding: "5px",
32156
- borderRadius: "3px",
32157
- fontFamily: "sans-serif",
32158
- transform: "translate(1rem, 1rem)"
32159
- },
32160
- children: hover.name
32182
+ model,
32183
+ objectVisibility,
32184
+ ...containerProps
32161
32185
  }
32162
- )
32186
+ ),
32187
+ (isLoading || error) && /* @__PURE__ */ jsx24("div", { role, style: { position: "absolute", top: 12, left: 12 }, children: message })
32163
32188
  ] });
32164
32189
  }
32165
- function CadViewerGlb({
32166
- source,
32190
+ function GlbViewport({
32191
+ model,
32167
32192
  objectVisibility,
32168
32193
  ...containerProps
32169
32194
  }) {
32170
- const result = useGlbModel(source);
32171
- const framing = useMemo31(() => {
32172
- if (!result?.model) return void 0;
32173
- const bounds = new THREE62.Box3().setFromObject(result.model);
32174
- if (bounds.isEmpty()) return void 0;
32175
- return {
32176
- boardDimensions: {
32177
- width: bounds.max.x - bounds.min.x,
32178
- height: bounds.max.y - bounds.min.y
32179
- },
32180
- boardCenter: {
32181
- x: (bounds.min.x + bounds.max.x) / 2,
32182
- y: (bounds.min.y + bounds.max.y) / 2
32183
- }
32184
- };
32185
- }, [result, source]);
32186
- if (!result) return /* @__PURE__ */ jsx24("div", { role: "status", children: "Loading 3D view..." });
32187
- if (result.error)
32188
- return /* @__PURE__ */ jsxs8("div", { role: "alert", children: [
32189
- "Unable to load 3D view: ",
32190
- result.error
32191
- ] });
32192
- if (!result.model) return null;
32193
- return /* @__PURE__ */ jsx24(CadViewerContainer, { ...containerProps, ...framing, children: /* @__PURE__ */ jsx24(GlbScene, { model: result.model, objectVisibility }) });
32195
+ const [framing, setFraming] = useState23(() => getGlbFraming(model));
32196
+ useEffect37(() => {
32197
+ if (framing) return;
32198
+ const nextFraming = getGlbFraming(model);
32199
+ if (nextFraming) setFraming(nextFraming);
32200
+ }, [framing, model]);
32201
+ return /* @__PURE__ */ jsxs8(
32202
+ CadViewerContainer,
32203
+ {
32204
+ ...containerProps,
32205
+ boardDimensions: framing?.boardDimensions,
32206
+ boardCenter: framing?.boardCenter,
32207
+ children: [
32208
+ framing && /* @__PURE__ */ jsx24(FitCameraToComparison, { bounds: framing.bounds }),
32209
+ /* @__PURE__ */ jsx24(GlbScene, { model, objectVisibility })
32210
+ ]
32211
+ }
32212
+ );
32213
+ }
32214
+ function getGlbFraming(model) {
32215
+ const box = new THREE62.Box3().setFromObject(model);
32216
+ if (box.isEmpty()) return void 0;
32217
+ return {
32218
+ bounds: {
32219
+ minX: box.min.x,
32220
+ maxX: box.max.x,
32221
+ minY: box.min.y,
32222
+ maxY: box.max.y,
32223
+ minZ: box.min.z,
32224
+ maxZ: box.max.z
32225
+ },
32226
+ boardDimensions: {
32227
+ width: box.max.x - box.min.x,
32228
+ height: box.max.y - box.min.y
32229
+ },
32230
+ boardCenter: {
32231
+ x: (box.min.x + box.max.x) / 2,
32232
+ y: (box.min.y + box.max.y) / 2
32233
+ }
32234
+ };
32194
32235
  }
32195
32236
 
32196
32237
  // node_modules/@radix-ui/react-dropdown-menu/dist/index.mjs
@@ -32391,7 +32432,7 @@ var IS_DEVELOPMENT = false;
32391
32432
 
32392
32433
  // node_modules/@radix-ui/react-use-layout-effect/dist/index.mjs
32393
32434
  import * as React13 from "react";
32394
- var useLayoutEffect2 = globalThis?.document ? React13.useLayoutEffect : () => {
32435
+ var useLayoutEffect22 = globalThis?.document ? React13.useLayoutEffect : () => {
32395
32436
  };
32396
32437
 
32397
32438
  // node_modules/@radix-ui/react-use-controllable-state/dist/index.mjs
@@ -32415,7 +32456,7 @@ function useEffectEvent(callback) {
32415
32456
  ref.current = callback;
32416
32457
  });
32417
32458
  } else {
32418
- useLayoutEffect2(() => {
32459
+ useLayoutEffect22(() => {
32419
32460
  ref.current = callback;
32420
32461
  });
32421
32462
  }
@@ -32426,7 +32467,7 @@ __name4(useEffectEvent, "useEffectEvent");
32426
32467
  // node_modules/@radix-ui/react-use-controllable-state/dist/index.mjs
32427
32468
  var __defProp6 = Object.defineProperty;
32428
32469
  var __name5 = (target, value) => __defProp6(target, "name", { value, configurable: true });
32429
- var useInsertionEffect = React15[" useInsertionEffect ".trim().toString()] || useLayoutEffect2;
32470
+ var useInsertionEffect = React15[" useInsertionEffect ".trim().toString()] || useLayoutEffect22;
32430
32471
  function useControllableState({
32431
32472
  prop,
32432
32473
  defaultProp,
@@ -33266,7 +33307,7 @@ function createCollection2(name) {
33266
33307
  [registryRef]
33267
33308
  );
33268
33309
  const composedRefs = useComposedRefs(forwardedRef, registerElement, ref);
33269
- useLayoutEffect2(() => {
33310
+ useLayoutEffect22(() => {
33270
33311
  const node = ref.current;
33271
33312
  const registry = registryRef.current;
33272
33313
  if (node && registry.has(node)) {
@@ -33419,7 +33460,7 @@ function useCallbackRef(callback) {
33419
33460
  callbackRef.current = callback;
33420
33461
  });
33421
33462
  } else {
33422
- useLayoutEffect2(() => {
33463
+ useLayoutEffect22(() => {
33423
33464
  callbackRef.current = callback;
33424
33465
  });
33425
33466
  }
@@ -34088,7 +34129,7 @@ var useReactId = React26[" useId ".trim().toString()] || (() => void 0);
34088
34129
  var count2 = 0;
34089
34130
  function useId2(deterministicId) {
34090
34131
  const [id, setId] = React26.useState(useReactId());
34091
- useLayoutEffect2(() => {
34132
+ useLayoutEffect22(() => {
34092
34133
  if (!deterministicId) setId((reactId) => reactId ?? String(count2++));
34093
34134
  }, [deterministicId]);
34094
34135
  return deterministicId || (id ? `radix-${id}` : "");
@@ -35692,12 +35733,12 @@ var computePosition2 = (reference, floating, options) => {
35692
35733
 
35693
35734
  // node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.mjs
35694
35735
  import * as React27 from "react";
35695
- import { useLayoutEffect as useLayoutEffect3 } from "react";
35736
+ import { useLayoutEffect as useLayoutEffect4 } from "react";
35696
35737
  import * as ReactDOM3 from "react-dom";
35697
35738
  var isClient = typeof document !== "undefined";
35698
35739
  var noop = function noop2() {
35699
35740
  };
35700
- var index = isClient ? useLayoutEffect3 : noop;
35741
+ var index = isClient ? useLayoutEffect4 : noop;
35701
35742
  function deepEqual(a, b) {
35702
35743
  if (a === b) {
35703
35744
  return true;
@@ -36007,7 +36048,7 @@ var __defProp16 = Object.defineProperty;
36007
36048
  var __name15 = (target, value) => __defProp16(target, "name", { value, configurable: true });
36008
36049
  function useSize(element) {
36009
36050
  const [size4, setSize] = React28.useState(void 0);
36010
- useLayoutEffect2(() => {
36051
+ useLayoutEffect22(() => {
36011
36052
  if (element) {
36012
36053
  setSize({ width: element.offsetWidth, height: element.offsetHeight });
36013
36054
  let rAF = 0;
@@ -36214,7 +36255,7 @@ var PopperContent = /* @__PURE__ */ React29.forwardRef(
36214
36255
  ]
36215
36256
  });
36216
36257
  const setPlacementState = context.setPlacementState;
36217
- useLayoutEffect2(() => {
36258
+ useLayoutEffect22(() => {
36218
36259
  setPlacementState(placement);
36219
36260
  return () => {
36220
36261
  setPlacementState(void 0);
@@ -36222,7 +36263,7 @@ var PopperContent = /* @__PURE__ */ React29.forwardRef(
36222
36263
  }, [placement, setPlacementState]);
36223
36264
  const [placedSide, placedAlign] = getSideAndAlignFromPlacement(placement);
36224
36265
  const handlePlaced = useCallbackRef(onPlaced);
36225
- useLayoutEffect2(() => {
36266
+ useLayoutEffect22(() => {
36226
36267
  if (isPositioned) {
36227
36268
  handlePlaced?.();
36228
36269
  }
@@ -36231,7 +36272,7 @@ var PopperContent = /* @__PURE__ */ React29.forwardRef(
36231
36272
  const arrowY = middlewareData.arrow?.y;
36232
36273
  const cannotCenterArrow = middlewareData.arrow?.centerOffset !== 0;
36233
36274
  const [contentZIndex, setContentZIndex] = React29.useState();
36234
- useLayoutEffect2(() => {
36275
+ useLayoutEffect22(() => {
36235
36276
  if (content) setContentZIndex(window.getComputedStyle(content).zIndex);
36236
36277
  }, [content]);
36237
36278
  return /* @__PURE__ */ jsx32(
@@ -36341,7 +36382,7 @@ var Portal = /* @__PURE__ */ React30.forwardRef(
36341
36382
  /* @__PURE__ */ __name17(function Portal2(props, forwardedRef) {
36342
36383
  const { container: containerProp, ...portalProps } = props;
36343
36384
  const [mounted, setMounted] = React30.useState(false);
36344
- useLayoutEffect2(() => setMounted(true), []);
36385
+ useLayoutEffect22(() => setMounted(true), []);
36345
36386
  const container = containerProp || mounted && globalThis?.document?.body;
36346
36387
  return container ? ReactDOM4.createPortal(/* @__PURE__ */ jsx33(Primitive.div, { ...portalProps, ref: forwardedRef }), container) : null;
36347
36388
  }, "Portal")
@@ -36395,7 +36436,7 @@ function usePresence(present) {
36395
36436
  prevAnimationNameRef.current = "none";
36396
36437
  }
36397
36438
  }, [state]);
36398
- useLayoutEffect2(() => {
36439
+ useLayoutEffect22(() => {
36399
36440
  const styles = stylesRef.current;
36400
36441
  const wasPresent = prevPresentRef.current;
36401
36442
  const hasPresentChanged = wasPresent !== present;
@@ -36418,7 +36459,7 @@ function usePresence(present) {
36418
36459
  prevPresentRef.current = present;
36419
36460
  }
36420
36461
  }, [present, send]);
36421
- useLayoutEffect2(() => {
36462
+ useLayoutEffect22(() => {
36422
36463
  if (node) {
36423
36464
  let timeoutId;
36424
36465
  const ownerWindow = node.ownerDocument.defaultView ?? window;
@@ -36698,7 +36739,7 @@ var RovingFocusGroupItem = /* @__PURE__ */ React33.forwardRef(
36698
36739
  const getItems = useCollection(__scopeRovingFocusGroup);
36699
36740
  const { onFocusableItemAdd, onFocusableItemRemove, currentTabStopId } = context;
36700
36741
  const isHydrated = useIsHydrated2();
36701
- useLayoutEffect2(() => {
36742
+ useLayoutEffect22(() => {
36702
36743
  if (!isHydrated || !focusable) {
36703
36744
  return;
36704
36745
  }
@@ -38592,12 +38633,12 @@ var DropdownMenuSubContent = /* @__PURE__ */ React42.forwardRef(/* @__PURE__ */
38592
38633
  }, "DropdownMenuSubContent"));
38593
38634
 
38594
38635
  // src/components/ViewportSubContent.tsx
38595
- import { useLayoutEffect as useLayoutEffect5, useState as useState40 } from "react";
38636
+ import { useLayoutEffect as useLayoutEffect6, useState as useState40 } from "react";
38596
38637
  import { jsx as jsx37 } from "react/jsx-runtime";
38597
38638
  var ViewportSubContent = (props) => {
38598
38639
  const [content, setContent] = useState40(null);
38599
38640
  const [overlapWidth, setOverlapWidth] = useState40();
38600
- useLayoutEffect5(() => {
38641
+ useLayoutEffect6(() => {
38601
38642
  if (!content) return;
38602
38643
  const trigger = document.getElementById(
38603
38644
  content.getAttribute("aria-labelledby") ?? ""
@@ -39740,10 +39781,10 @@ var ContextMenu = ({
39740
39781
  };
39741
39782
 
39742
39783
  // src/components/KeyboardShortcutsDialog.tsx
39743
- import { useEffect as useEffect55, useMemo as useMemo41, useRef as useRef28, useState as useState44 } from "react";
39784
+ import { useEffect as useEffect55, useMemo as useMemo40, useRef as useRef28, useState as useState44 } from "react";
39744
39785
 
39745
39786
  // src/hooks/useRegisteredHotkey.ts
39746
- import { useEffect as useEffect54, useMemo as useMemo40, useRef as useRef27, useState as useState43 } from "react";
39787
+ import { useEffect as useEffect54, useMemo as useMemo39, useRef as useRef27, useState as useState43 } from "react";
39747
39788
  var hotkeyRegistry = /* @__PURE__ */ new Map();
39748
39789
  var subscribers = /* @__PURE__ */ new Set();
39749
39790
  var isListenerAttached = false;
@@ -39867,7 +39908,7 @@ var subscribeToRegistry = (subscriber) => {
39867
39908
  var useRegisteredHotkey = (id, handler, metadata) => {
39868
39909
  const handlerRef = useRef27(handler);
39869
39910
  handlerRef.current = handler;
39870
- const normalizedMetadata = useMemo40(
39911
+ const normalizedMetadata = useMemo39(
39871
39912
  () => ({
39872
39913
  shortcut: metadata.shortcut,
39873
39914
  description: metadata.description
@@ -39925,7 +39966,7 @@ var KeyboardShortcutsDialog = ({
39925
39966
  }, 0);
39926
39967
  }
39927
39968
  }, [open]);
39928
- const filteredHotkeys = useMemo41(() => {
39969
+ const filteredHotkeys = useMemo40(() => {
39929
39970
  const normalizedQuery = query.trim().toLowerCase();
39930
39971
  if (!normalizedQuery) {
39931
39972
  return hotkeys;
@@ -41152,10 +41193,10 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
41152
41193
  import {
41153
41194
  GLTFExporter as GLTFExporter3
41154
41195
  } from "three/examples/jsm/exporters/GLTFExporter.js";
41155
- import { useEffect as useEffect58, useState as useState47, useMemo as useMemo42, useCallback as useCallback26 } from "react";
41196
+ import { useEffect as useEffect58, useState as useState47, useMemo as useMemo41, useCallback as useCallback26 } from "react";
41156
41197
  function useSaveGltfAs(options = {}) {
41157
41198
  const parse2 = useParser(options);
41158
- const link = useMemo42(() => document.createElement("a"), []);
41199
+ const link = useMemo41(() => document.createElement("a"), []);
41159
41200
  const saveAs = async (filename) => {
41160
41201
  const name = filename ?? options.filename ?? "";
41161
41202
  if (options.binary == null) options.binary = name.endsWith(".glb");
@@ -41190,7 +41231,7 @@ function useExportGltfUrl(options = {}) {
41190
41231
  return [ref, url, error];
41191
41232
  }
41192
41233
  function useParser(options = {}) {
41193
- const exporter = useMemo42(() => new GLTFExporter3(), []);
41234
+ const exporter = useMemo41(() => new GLTFExporter3(), []);
41194
41235
  return (instance) => {
41195
41236
  const { promise, resolve, reject } = Promise.withResolvers();
41196
41237
  exporter.parse(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/3d-viewer",
3
- "version": "0.0.640",
3
+ "version": "0.0.642",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/tscircuit/3d-viewer"