@tscircuit/3d-viewer 0.0.351 → 0.0.353

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/dist/index.d.ts CHANGED
@@ -1,11 +1,12 @@
1
- import * as react from 'react';
2
- import * as THREE from 'three';
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
3
2
  import { AnyCircuitElement, PcbBoard } from 'circuit-json';
3
+ import * as React from 'react';
4
+ import * as THREE from 'three';
4
5
  import { GLTFExporterOptions } from 'three-stdlib';
5
6
  import { ManifoldToplevel } from 'manifold-3d/manifold.d.ts';
6
7
  import { JSDOM } from 'jsdom';
7
8
 
8
- declare const CadViewer: react.ForwardRefExoticComponent<Omit<any, "ref"> & react.RefAttributes<THREE.Object3D<THREE.Object3DEventMap>>>;
9
+ declare const CadViewer: (props: any) => react_jsx_runtime.JSX.Element;
9
10
 
10
11
  interface CircuitToSvgOptions {
11
12
  width?: number;
@@ -33,11 +34,11 @@ type Options = Omit<GLTFExporterOptions, "animations" | "includeCustomExtensions
33
34
  declare function useSaveGltfAs(options?: Options & {
34
35
  filename?: string;
35
36
  }): [
36
- ref3D: react.ForwardedRef<THREE.Object3D>,
37
+ ref3D: React.ForwardedRef<THREE.Object3D>,
37
38
  saveAs: (filename?: string) => Promise<void>
38
39
  ];
39
40
  declare function useExportGltfUrl(options?: Options): [
40
- ref3D: react.ForwardedRef<THREE.Object3D>,
41
+ ref3D: React.ForwardedRef<THREE.Object3D>,
41
42
  url: string | undefined,
42
43
  error: ErrorEvent | undefined
43
44
  ];
package/dist/index.js CHANGED
@@ -14228,7 +14228,7 @@ var require_browser = __commonJS({
14228
14228
  });
14229
14229
 
14230
14230
  // src/CadViewer.tsx
14231
- import { useState as useState16, useCallback as useCallback8, useRef as useRef10, useEffect as useEffect23, forwardRef as forwardRef5 } from "react";
14231
+ import { useState as useState15, useCallback as useCallback8, useRef as useRef9, useEffect as useEffect22 } from "react";
14232
14232
 
14233
14233
  // src/CadViewerJscad.tsx
14234
14234
  import { su as su4 } from "@tscircuit/circuit-json-util";
@@ -25583,7 +25583,7 @@ import * as THREE10 from "three";
25583
25583
  // package.json
25584
25584
  var package_default = {
25585
25585
  name: "@tscircuit/3d-viewer",
25586
- version: "0.0.350",
25586
+ version: "0.0.352",
25587
25587
  main: "./dist/index.js",
25588
25588
  module: "./dist/index.js",
25589
25589
  type: "module",
@@ -25912,6 +25912,7 @@ var Canvas = forwardRef(
25912
25912
  }
25913
25913
  camera.lookAt(0, 0, 0);
25914
25914
  scene.add(rootObject.current);
25915
+ window.__TSCIRCUIT_THREE_OBJECT = rootObject.current;
25915
25916
  setContextState({
25916
25917
  scene,
25917
25918
  camera,
@@ -25949,6 +25950,9 @@ var Canvas = forwardRef(
25949
25950
  }
25950
25951
  renderer.dispose();
25951
25952
  scene.remove(rootObject.current);
25953
+ if (window.__TSCIRCUIT_THREE_OBJECT === rootObject.current) {
25954
+ window.__TSCIRCUIT_THREE_OBJECT = void 0;
25955
+ }
25952
25956
  };
25953
25957
  }, [scene, addFrameListener, removeFrameListener]);
25954
25958
  return /* @__PURE__ */ jsx9("div", { ref: mountRef, style: { width: "100%", height: "100%", ...style }, children: contextState && /* @__PURE__ */ jsx9(ThreeContext.Provider, { value: contextState, children: /* @__PURE__ */ jsx9(HoverProvider, { children }) }) });
@@ -25994,14 +25998,9 @@ var OrbitControls = ({
25994
25998
  ]);
25995
25999
  useEffect11(() => {
25996
26000
  if (!controls || !onStart) return;
25997
- const handleStart = (event) => {
25998
- if (event.button !== 2) {
25999
- onStart();
26000
- }
26001
- };
26002
- controls.addEventListener("start", handleStart);
26001
+ controls.addEventListener("start", onStart);
26003
26002
  return () => {
26004
- controls.removeEventListener("start", handleStart);
26003
+ controls.removeEventListener("start", onStart);
26005
26004
  };
26006
26005
  }, [controls, onStart]);
26007
26006
  useEffect11(() => {
@@ -27768,7 +27767,7 @@ var CadViewerJscad = forwardRef3(
27768
27767
 
27769
27768
  // src/CadViewerManifold.tsx
27770
27769
  import { su as su13 } from "@tscircuit/circuit-json-util";
27771
- import { forwardRef as forwardRef4, useEffect as useEffect20, useMemo as useMemo18, useState as useState13 } from "react";
27770
+ import { useEffect as useEffect20, useMemo as useMemo18, useState as useState13 } from "react";
27772
27771
 
27773
27772
  // src/hooks/useManifoldBoardBuilder.ts
27774
27773
  import { useState as useState12, useEffect as useEffect19, useMemo as useMemo17, useRef as useRef7 } from "react";
@@ -28878,51 +28877,50 @@ var BoardMeshes = ({
28878
28877
  return null;
28879
28878
  };
28880
28879
  var MANIFOLD_CDN_BASE_URL = "https://cdn.jsdelivr.net/npm/manifold-3d@3.2.1";
28881
- var CadViewerManifold = forwardRef4(
28882
- ({
28883
- circuitJson: circuitJsonProp,
28884
- autoRotateDisabled,
28885
- clickToInteractEnabled,
28886
- onUserInteraction,
28887
- children
28888
- }, ref) => {
28889
- const childrenCircuitJson = useConvertChildrenToSoup(children);
28890
- const circuitJson = useMemo18(() => {
28891
- return circuitJsonProp ?? childrenCircuitJson;
28892
- }, [circuitJsonProp, childrenCircuitJson]);
28893
- const [manifoldJSModule, setManifoldJSModule] = useState13(null);
28894
- const [manifoldLoadingError, setManifoldLoadingError] = useState13(null);
28895
- useEffect20(() => {
28896
- const initManifold = async (ManifoldModule) => {
28897
- try {
28898
- const loadedModule = await ManifoldModule();
28899
- loadedModule.setup();
28900
- setManifoldJSModule(loadedModule);
28901
- } catch (error) {
28902
- console.error("Failed to initialize Manifold:", error);
28903
- setManifoldLoadingError(
28904
- `Failed to initialize Manifold: ${error instanceof Error ? error.message : "Unknown error"}`
28905
- );
28906
- }
28907
- };
28880
+ var CadViewerManifold = ({
28881
+ circuitJson: circuitJsonProp,
28882
+ autoRotateDisabled,
28883
+ clickToInteractEnabled,
28884
+ onUserInteraction,
28885
+ children
28886
+ }) => {
28887
+ const childrenCircuitJson = useConvertChildrenToSoup(children);
28888
+ const circuitJson = useMemo18(() => {
28889
+ return circuitJsonProp ?? childrenCircuitJson;
28890
+ }, [circuitJsonProp, childrenCircuitJson]);
28891
+ const [manifoldJSModule, setManifoldJSModule] = useState13(null);
28892
+ const [manifoldLoadingError, setManifoldLoadingError] = useState13(null);
28893
+ useEffect20(() => {
28894
+ const initManifold = async (ManifoldModule) => {
28895
+ try {
28896
+ const loadedModule = await ManifoldModule();
28897
+ loadedModule.setup();
28898
+ setManifoldJSModule(loadedModule);
28899
+ } catch (error) {
28900
+ console.error("Failed to initialize Manifold:", error);
28901
+ setManifoldLoadingError(
28902
+ `Failed to initialize Manifold: ${error instanceof Error ? error.message : "Unknown error"}`
28903
+ );
28904
+ }
28905
+ };
28906
+ if (window.ManifoldModule) {
28907
+ initManifold(window.ManifoldModule);
28908
+ return;
28909
+ }
28910
+ const eventName = "manifoldLoaded";
28911
+ const handleLoad = () => {
28908
28912
  if (window.ManifoldModule) {
28909
28913
  initManifold(window.ManifoldModule);
28910
- return;
28914
+ } else {
28915
+ const errText = "ManifoldModule not found on window after script load.";
28916
+ console.error(errText);
28917
+ setManifoldLoadingError(errText);
28911
28918
  }
28912
- const eventName = "manifoldLoaded";
28913
- const handleLoad = () => {
28914
- if (window.ManifoldModule) {
28915
- initManifold(window.ManifoldModule);
28916
- } else {
28917
- const errText = "ManifoldModule not found on window after script load.";
28918
- console.error(errText);
28919
- setManifoldLoadingError(errText);
28920
- }
28921
- };
28922
- window.addEventListener(eventName, handleLoad, { once: true });
28923
- const script = document.createElement("script");
28924
- script.type = "module";
28925
- script.innerHTML = `
28919
+ };
28920
+ window.addEventListener(eventName, handleLoad, { once: true });
28921
+ const script = document.createElement("script");
28922
+ script.type = "module";
28923
+ script.innerHTML = `
28926
28924
  try {
28927
28925
  const { default: ManifoldModule } = await import('${MANIFOLD_CDN_BASE_URL}/manifold.js');
28928
28926
  window.ManifoldModule = ManifoldModule;
@@ -28932,125 +28930,123 @@ try {
28932
28930
  window.dispatchEvent(new CustomEvent('${eventName}'));
28933
28931
  }
28934
28932
  `.trim();
28935
- const scriptError = (err) => {
28936
- const errText = "Failed to load Manifold loader script.";
28937
- console.error(errText, err);
28938
- setManifoldLoadingError(errText);
28939
- window.removeEventListener(eventName, handleLoad);
28940
- };
28941
- script.addEventListener("error", scriptError);
28942
- document.body.appendChild(script);
28943
- return () => {
28944
- window.removeEventListener(eventName, handleLoad);
28945
- script.removeEventListener("error", scriptError);
28946
- };
28947
- }, []);
28948
- const {
28949
- geoms,
28950
- textures,
28951
- pcbThickness,
28952
- error: builderError,
28953
- isLoading: builderIsLoading,
28954
- boardData
28955
- } = useManifoldBoardBuilder(manifoldJSModule, circuitJson);
28956
- const geometryMeshes = useMemo18(() => createGeometryMeshes(geoms), [geoms]);
28957
- const textureMeshes = useMemo18(
28958
- () => createTextureMeshes(textures, boardData, pcbThickness),
28959
- [textures, boardData, pcbThickness]
28960
- );
28961
- const cadComponents = useMemo18(
28962
- () => su13(circuitJson).cad_component.list(),
28963
- [circuitJson]
28933
+ const scriptError = (err) => {
28934
+ const errText = "Failed to load Manifold loader script.";
28935
+ console.error(errText, err);
28936
+ setManifoldLoadingError(errText);
28937
+ window.removeEventListener(eventName, handleLoad);
28938
+ };
28939
+ script.addEventListener("error", scriptError);
28940
+ document.body.appendChild(script);
28941
+ return () => {
28942
+ window.removeEventListener(eventName, handleLoad);
28943
+ script.removeEventListener("error", scriptError);
28944
+ };
28945
+ }, []);
28946
+ const {
28947
+ geoms,
28948
+ textures,
28949
+ pcbThickness,
28950
+ error: builderError,
28951
+ isLoading: builderIsLoading,
28952
+ boardData
28953
+ } = useManifoldBoardBuilder(manifoldJSModule, circuitJson);
28954
+ const geometryMeshes = useMemo18(() => createGeometryMeshes(geoms), [geoms]);
28955
+ const textureMeshes = useMemo18(
28956
+ () => createTextureMeshes(textures, boardData, pcbThickness),
28957
+ [textures, boardData, pcbThickness]
28958
+ );
28959
+ const cadComponents = useMemo18(
28960
+ () => su13(circuitJson).cad_component.list(),
28961
+ [circuitJson]
28962
+ );
28963
+ const boardDimensions = useMemo18(() => {
28964
+ if (!boardData) return void 0;
28965
+ const { width: width10 = 0, height: height10 = 0 } = boardData;
28966
+ return { width: width10, height: height10 };
28967
+ }, [boardData]);
28968
+ const initialCameraPosition = useMemo18(() => {
28969
+ if (!boardData) return [5, 5, 5];
28970
+ const { width: width10 = 0, height: height10 = 0 } = boardData;
28971
+ const safeWidth = Math.max(width10, 1);
28972
+ const safeHeight = Math.max(height10, 1);
28973
+ const largestDim = Math.max(safeWidth, safeHeight, 5);
28974
+ return [largestDim * 0.75, largestDim * 0.75, largestDim * 0.75];
28975
+ }, [boardData]);
28976
+ if (manifoldLoadingError) {
28977
+ return /* @__PURE__ */ jsxs7(
28978
+ "div",
28979
+ {
28980
+ style: {
28981
+ color: "red",
28982
+ padding: "1em",
28983
+ border: "1px solid red",
28984
+ margin: "1em"
28985
+ },
28986
+ children: [
28987
+ "Error: ",
28988
+ manifoldLoadingError
28989
+ ]
28990
+ }
28964
28991
  );
28965
- const boardDimensions = useMemo18(() => {
28966
- if (!boardData) return void 0;
28967
- const { width: width10 = 0, height: height10 = 0 } = boardData;
28968
- return { width: width10, height: height10 };
28969
- }, [boardData]);
28970
- const initialCameraPosition = useMemo18(() => {
28971
- if (!boardData) return [5, 5, 5];
28972
- const { width: width10 = 0, height: height10 = 0 } = boardData;
28973
- const safeWidth = Math.max(width10, 1);
28974
- const safeHeight = Math.max(height10, 1);
28975
- const largestDim = Math.max(safeWidth, safeHeight, 5);
28976
- return [largestDim * 0.75, largestDim * 0.75, largestDim * 0.75];
28977
- }, [boardData]);
28978
- if (manifoldLoadingError) {
28979
- return /* @__PURE__ */ jsxs7(
28980
- "div",
28981
- {
28982
- style: {
28983
- color: "red",
28984
- padding: "1em",
28985
- border: "1px solid red",
28986
- margin: "1em"
28987
- },
28988
- children: [
28989
- "Error: ",
28990
- manifoldLoadingError
28991
- ]
28992
- }
28993
- );
28994
- }
28995
- if (!manifoldJSModule) {
28996
- return /* @__PURE__ */ jsx13("div", { style: { padding: "1em" }, children: "Loading Manifold module..." });
28997
- }
28998
- if (builderError) {
28999
- return /* @__PURE__ */ jsxs7(
29000
- "div",
29001
- {
29002
- style: {
29003
- color: "red",
29004
- padding: "1em",
29005
- border: "1px solid red",
29006
- margin: "1em"
29007
- },
29008
- children: [
29009
- "Error: ",
29010
- builderError
29011
- ]
29012
- }
29013
- );
29014
- }
29015
- if (builderIsLoading) {
29016
- return /* @__PURE__ */ jsx13("div", { style: { padding: "1em" }, children: "Processing board geometry..." });
29017
- }
28992
+ }
28993
+ if (!manifoldJSModule) {
28994
+ return /* @__PURE__ */ jsx13("div", { style: { padding: "1em" }, children: "Loading Manifold module..." });
28995
+ }
28996
+ if (builderError) {
29018
28997
  return /* @__PURE__ */ jsxs7(
29019
- CadViewerContainer,
28998
+ "div",
29020
28999
  {
29021
- ref,
29022
- initialCameraPosition,
29023
- autoRotateDisabled,
29024
- clickToInteractEnabled,
29025
- boardDimensions,
29026
- onUserInteraction,
29000
+ style: {
29001
+ color: "red",
29002
+ padding: "1em",
29003
+ border: "1px solid red",
29004
+ margin: "1em"
29005
+ },
29027
29006
  children: [
29028
- /* @__PURE__ */ jsx13(
29029
- BoardMeshes,
29030
- {
29031
- geometryMeshes,
29032
- textureMeshes
29033
- }
29034
- ),
29035
- cadComponents.map((cad_component2) => /* @__PURE__ */ jsx13(
29036
- ThreeErrorBoundary,
29037
- {
29038
- fallback: ({ error }) => /* @__PURE__ */ jsx13(Error3d, { cad_component: cad_component2, error }),
29039
- children: /* @__PURE__ */ jsx13(
29040
- AnyCadComponent,
29041
- {
29042
- cad_component: cad_component2,
29043
- circuitJson
29044
- }
29045
- )
29046
- },
29047
- cad_component2.cad_component_id
29048
- ))
29007
+ "Error: ",
29008
+ builderError
29049
29009
  ]
29050
29010
  }
29051
29011
  );
29052
29012
  }
29053
- );
29013
+ if (builderIsLoading) {
29014
+ return /* @__PURE__ */ jsx13("div", { style: { padding: "1em" }, children: "Processing board geometry..." });
29015
+ }
29016
+ return /* @__PURE__ */ jsxs7(
29017
+ CadViewerContainer,
29018
+ {
29019
+ initialCameraPosition,
29020
+ autoRotateDisabled,
29021
+ clickToInteractEnabled,
29022
+ boardDimensions,
29023
+ onUserInteraction,
29024
+ children: [
29025
+ /* @__PURE__ */ jsx13(
29026
+ BoardMeshes,
29027
+ {
29028
+ geometryMeshes,
29029
+ textureMeshes
29030
+ }
29031
+ ),
29032
+ cadComponents.map((cad_component2) => /* @__PURE__ */ jsx13(
29033
+ ThreeErrorBoundary,
29034
+ {
29035
+ fallback: ({ error }) => /* @__PURE__ */ jsx13(Error3d, { cad_component: cad_component2, error }),
29036
+ children: /* @__PURE__ */ jsx13(
29037
+ AnyCadComponent,
29038
+ {
29039
+ cad_component: cad_component2,
29040
+ circuitJson
29041
+ }
29042
+ )
29043
+ },
29044
+ cad_component2.cad_component_id
29045
+ ))
29046
+ ]
29047
+ }
29048
+ );
29049
+ };
29054
29050
  var CadViewerManifold_default = CadViewerManifold;
29055
29051
 
29056
29052
  // src/hooks/useContextMenu.ts
@@ -29155,87 +29151,42 @@ var useContextMenu = ({ containerRef }) => {
29155
29151
  };
29156
29152
  };
29157
29153
 
29158
- // src/hooks/exporter/gltf.ts
29154
+ // src/hooks/useGlobalDownloadGltf.ts
29155
+ import { useCallback as useCallback7 } from "react";
29159
29156
  import { GLTFExporter } from "three-stdlib";
29160
- import { useEffect as useEffect22, useState as useState15, useMemo as useMemo19, useCallback as useCallback7, useRef as useRef9 } from "react";
29161
- function useSaveGltfAs(options = {}) {
29162
- const parse = useParser(options);
29163
- const link = useMemo19(() => document.createElement("a"), []);
29164
- const instanceRef = useRef9(null);
29165
- const saveAs = async (filename) => {
29166
- const name = filename ?? options.filename ?? "";
29167
- if (options.binary == null) options.binary = name.endsWith(".glb");
29168
- if (!instanceRef.current) {
29169
- console.error("No 3D object available for export");
29170
- return;
29171
- }
29172
- try {
29173
- const url = await parse(instanceRef.current);
29174
- link.download = name;
29175
- link.href = url;
29176
- link.dispatchEvent(new MouseEvent("click"));
29177
- URL.revokeObjectURL(url);
29178
- } catch (error) {
29179
- console.error("Failed to export GLTF:", error);
29180
- }
29181
- };
29182
- useEffect22(
29183
- () => () => {
29184
- link.remove();
29185
- instanceRef.current = null;
29186
- },
29187
- []
29188
- );
29189
- const ref = useCallback7((obj3D) => {
29190
- instanceRef.current = obj3D;
29191
- }, []);
29192
- return [ref, saveAs];
29193
- }
29194
- function useExportGltfUrl(options = {}) {
29195
- const parse = useParser(options);
29196
- const [url, setUrl] = useState15();
29197
- const [error, setError] = useState15();
29198
- const instanceRef = useRef9(null);
29199
- const ref = useCallback7(
29200
- (instance) => {
29201
- instanceRef.current = instance;
29202
- if (instance) {
29203
- parse(instance).then(setUrl).catch(setError);
29204
- }
29205
- },
29206
- [parse]
29207
- );
29208
- useEffect22(() => () => URL.revokeObjectURL(url), [url]);
29209
- return [ref, url, error];
29210
- }
29211
- function useParser(options = {}) {
29212
- const exporter = useMemo19(() => new GLTFExporter(), []);
29213
- return (instance) => {
29214
- const { promise, resolve, reject } = Promise.withResolvers();
29157
+ var useGlobalDownloadGltf = () => {
29158
+ return useCallback7(() => {
29159
+ const root = window.__TSCIRCUIT_THREE_OBJECT;
29160
+ if (!root) return;
29161
+ const exporter = new GLTFExporter();
29215
29162
  exporter.parse(
29216
- instance,
29163
+ root,
29217
29164
  (gltf) => {
29218
- const type = options.binary ? "gltf-binary" : "gltf+json";
29219
29165
  const blob = new Blob(
29220
29166
  [gltf instanceof ArrayBuffer ? gltf : JSON.stringify(gltf)],
29221
- { type: `model/${type}` }
29167
+ { type: "model/gltf+json" }
29222
29168
  );
29223
- resolve(URL.createObjectURL(blob));
29169
+ const url = URL.createObjectURL(blob);
29170
+ const link = document.createElement("a");
29171
+ link.href = url;
29172
+ link.download = "scene.gltf";
29173
+ link.click();
29174
+ URL.revokeObjectURL(url);
29224
29175
  },
29225
- reject,
29226
- options
29176
+ (err) => {
29177
+ console.error("Failed to export GLTF", err);
29178
+ }
29227
29179
  );
29228
- return promise;
29229
- };
29230
- }
29180
+ }, []);
29181
+ };
29231
29182
 
29232
29183
  // src/CadViewer.tsx
29233
29184
  import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
29234
- var CadViewer = forwardRef5((props, ref) => {
29235
- const [engine, setEngine] = useState16("manifold");
29236
- const containerRef = useRef10(null);
29237
- const [autoRotate, setAutoRotate] = useState16(true);
29238
- const [autoRotateUserToggled, setAutoRotateUserToggled] = useState16(false);
29185
+ var CadViewer = (props) => {
29186
+ const [engine, setEngine] = useState15("manifold");
29187
+ const containerRef = useRef9(null);
29188
+ const [autoRotate, setAutoRotate] = useState15(true);
29189
+ const [autoRotateUserToggled, setAutoRotateUserToggled] = useState15(false);
29239
29190
  const {
29240
29191
  menuVisible,
29241
29192
  menuPos,
@@ -29243,21 +29194,7 @@ var CadViewer = forwardRef5((props, ref) => {
29243
29194
  contextMenuEventHandlers,
29244
29195
  setMenuVisible
29245
29196
  } = useContextMenu({ containerRef });
29246
- const [sceneRef, saveGltfAs] = useSaveGltfAs();
29247
- function useCombinedRefs(...refs) {
29248
- return useCallback8(
29249
- (value) => {
29250
- refs.forEach((ref2) => {
29251
- if (!ref2) return;
29252
- if (typeof ref2 === "function") ref2(value);
29253
- else ref2.current = value;
29254
- });
29255
- },
29256
- [refs]
29257
- );
29258
- }
29259
- const mergedRef = useCombinedRefs(ref, sceneRef);
29260
- const autoRotateUserToggledRef = useRef10(autoRotateUserToggled);
29197
+ const autoRotateUserToggledRef = useRef9(autoRotateUserToggled);
29261
29198
  autoRotateUserToggledRef.current = autoRotateUserToggled;
29262
29199
  const handleUserInteraction = useCallback8(() => {
29263
29200
  if (!autoRotateUserToggledRef.current) {
@@ -29268,21 +29205,18 @@ var CadViewer = forwardRef5((props, ref) => {
29268
29205
  setAutoRotate((prev) => !prev);
29269
29206
  setAutoRotateUserToggled(true);
29270
29207
  }, []);
29208
+ const downloadGltf = useGlobalDownloadGltf();
29271
29209
  const handleMenuClick = (newEngine) => {
29272
29210
  setEngine(newEngine);
29273
29211
  setMenuVisible(false);
29274
29212
  };
29275
- const handleDownloadGltf = useCallback8(() => {
29276
- saveGltfAs("pcb.glb");
29277
- setMenuVisible(false);
29278
- }, [saveGltfAs, setMenuVisible]);
29279
- useEffect23(() => {
29213
+ useEffect22(() => {
29280
29214
  const stored = window.localStorage.getItem("cadViewerEngine");
29281
29215
  if (stored === "jscad" || stored === "manifold") {
29282
29216
  setEngine(stored);
29283
29217
  }
29284
29218
  }, []);
29285
- useEffect23(() => {
29219
+ useEffect22(() => {
29286
29220
  window.localStorage.setItem("cadViewerEngine", engine);
29287
29221
  }, [engine]);
29288
29222
  const viewerKey = props.circuitJson ? JSON.stringify(props.circuitJson) : void 0;
@@ -29297,19 +29231,15 @@ var CadViewer = forwardRef5((props, ref) => {
29297
29231
  CadViewerJscad,
29298
29232
  {
29299
29233
  ...props,
29300
- ref: mergedRef,
29301
29234
  autoRotateDisabled: props.autoRotateDisabled || !autoRotate,
29302
- onUserInteraction: handleUserInteraction,
29303
- children: props.children
29235
+ onUserInteraction: handleUserInteraction
29304
29236
  }
29305
29237
  ) : /* @__PURE__ */ jsx14(
29306
29238
  CadViewerManifold_default,
29307
29239
  {
29308
29240
  ...props,
29309
- ref: mergedRef,
29310
29241
  autoRotateDisabled: props.autoRotateDisabled || !autoRotate,
29311
- onUserInteraction: handleUserInteraction,
29312
- children: props.children
29242
+ onUserInteraction: handleUserInteraction
29313
29243
  }
29314
29244
  ),
29315
29245
  /* @__PURE__ */ jsxs8(
@@ -29430,7 +29360,10 @@ var CadViewer = forwardRef5((props, ref) => {
29430
29360
  borderRadius: 6,
29431
29361
  transition: "background 0.1s"
29432
29362
  },
29433
- onClick: handleDownloadGltf,
29363
+ onClick: () => {
29364
+ downloadGltf();
29365
+ setMenuVisible(false);
29366
+ },
29434
29367
  onMouseOver: (e) => e.currentTarget.style.background = "#2d313a",
29435
29368
  onMouseOut: (e) => e.currentTarget.style.background = "transparent",
29436
29369
  children: "Download GLTF"
@@ -29470,7 +29403,7 @@ var CadViewer = forwardRef5((props, ref) => {
29470
29403
  },
29471
29404
  viewerKey
29472
29405
  );
29473
- });
29406
+ };
29474
29407
 
29475
29408
  // src/convert-circuit-json-to-3d-svg.ts
29476
29409
  var import_debug = __toESM(require_browser(), 1);
@@ -29739,6 +29672,66 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
29739
29672
  return serialized;
29740
29673
  }
29741
29674
 
29675
+ // src/hooks/exporter/gltf.ts
29676
+ import { GLTFExporter as GLTFExporter2 } from "three-stdlib";
29677
+ import { useEffect as useEffect23, useState as useState16, useMemo as useMemo19, useCallback as useCallback9 } from "react";
29678
+ function useSaveGltfAs(options = {}) {
29679
+ const parse = useParser(options);
29680
+ const link = useMemo19(() => document.createElement("a"), []);
29681
+ const saveAs = async (filename) => {
29682
+ const name = filename ?? options.filename ?? "";
29683
+ if (options.binary == null) options.binary = name.endsWith(".glb");
29684
+ const url = await parse(instance);
29685
+ link.download = name;
29686
+ link.href = url;
29687
+ link.dispatchEvent(new MouseEvent("click"));
29688
+ URL.revokeObjectURL(url);
29689
+ };
29690
+ useEffect23(
29691
+ () => () => {
29692
+ link.remove();
29693
+ instance = null;
29694
+ },
29695
+ []
29696
+ );
29697
+ let instance;
29698
+ const ref = useCallback9((obj3D) => {
29699
+ instance = obj3D;
29700
+ }, []);
29701
+ return [ref, saveAs];
29702
+ }
29703
+ function useExportGltfUrl(options = {}) {
29704
+ const parse = useParser(options);
29705
+ const [url, setUrl] = useState16();
29706
+ const [error, setError] = useState16();
29707
+ const ref = useCallback9(
29708
+ (instance) => parse(instance).then(setUrl).catch(setError),
29709
+ []
29710
+ );
29711
+ useEffect23(() => () => URL.revokeObjectURL(url), [url]);
29712
+ return [ref, url, error];
29713
+ }
29714
+ function useParser(options = {}) {
29715
+ const exporter = useMemo19(() => new GLTFExporter2(), []);
29716
+ return (instance) => {
29717
+ const { promise, resolve, reject } = Promise.withResolvers();
29718
+ exporter.parse(
29719
+ instance,
29720
+ (gltf) => {
29721
+ const type = options.binary ? "gltf-binary" : "gltf+json";
29722
+ const blob = new Blob(
29723
+ [gltf instanceof ArrayBuffer ? gltf : JSON.stringify(gltf)],
29724
+ { type: `model/${type}` }
29725
+ );
29726
+ resolve(URL.createObjectURL(blob));
29727
+ },
29728
+ reject,
29729
+ options
29730
+ );
29731
+ return promise;
29732
+ };
29733
+ }
29734
+
29742
29735
  // src/utils/jsdom-shim.ts
29743
29736
  function applyJsdomShim(jsdom) {
29744
29737
  global.window = jsdom.window;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/3d-viewer",
3
- "version": "0.0.351",
3
+ "version": "0.0.353",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.js",
6
6
  "type": "module",