@tscircuit/3d-viewer 0.0.351 → 0.0.352

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 useCallback7, 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.351",
25587
25587
  main: "./dist/index.js",
25588
25588
  module: "./dist/index.js",
25589
25589
  type: "module",
@@ -25994,14 +25994,9 @@ var OrbitControls = ({
25994
25994
  ]);
25995
25995
  useEffect11(() => {
25996
25996
  if (!controls || !onStart) return;
25997
- const handleStart = (event) => {
25998
- if (event.button !== 2) {
25999
- onStart();
26000
- }
26001
- };
26002
- controls.addEventListener("start", handleStart);
25997
+ controls.addEventListener("start", onStart);
26003
25998
  return () => {
26004
- controls.removeEventListener("start", handleStart);
25999
+ controls.removeEventListener("start", onStart);
26005
26000
  };
26006
26001
  }, [controls, onStart]);
26007
26002
  useEffect11(() => {
@@ -27768,7 +27763,7 @@ var CadViewerJscad = forwardRef3(
27768
27763
 
27769
27764
  // src/CadViewerManifold.tsx
27770
27765
  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";
27766
+ import { useEffect as useEffect20, useMemo as useMemo18, useState as useState13 } from "react";
27772
27767
 
27773
27768
  // src/hooks/useManifoldBoardBuilder.ts
27774
27769
  import { useState as useState12, useEffect as useEffect19, useMemo as useMemo17, useRef as useRef7 } from "react";
@@ -28878,51 +28873,50 @@ var BoardMeshes = ({
28878
28873
  return null;
28879
28874
  };
28880
28875
  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
- };
28876
+ var CadViewerManifold = ({
28877
+ circuitJson: circuitJsonProp,
28878
+ autoRotateDisabled,
28879
+ clickToInteractEnabled,
28880
+ onUserInteraction,
28881
+ children
28882
+ }) => {
28883
+ const childrenCircuitJson = useConvertChildrenToSoup(children);
28884
+ const circuitJson = useMemo18(() => {
28885
+ return circuitJsonProp ?? childrenCircuitJson;
28886
+ }, [circuitJsonProp, childrenCircuitJson]);
28887
+ const [manifoldJSModule, setManifoldJSModule] = useState13(null);
28888
+ const [manifoldLoadingError, setManifoldLoadingError] = useState13(null);
28889
+ useEffect20(() => {
28890
+ const initManifold = async (ManifoldModule) => {
28891
+ try {
28892
+ const loadedModule = await ManifoldModule();
28893
+ loadedModule.setup();
28894
+ setManifoldJSModule(loadedModule);
28895
+ } catch (error) {
28896
+ console.error("Failed to initialize Manifold:", error);
28897
+ setManifoldLoadingError(
28898
+ `Failed to initialize Manifold: ${error instanceof Error ? error.message : "Unknown error"}`
28899
+ );
28900
+ }
28901
+ };
28902
+ if (window.ManifoldModule) {
28903
+ initManifold(window.ManifoldModule);
28904
+ return;
28905
+ }
28906
+ const eventName = "manifoldLoaded";
28907
+ const handleLoad = () => {
28908
28908
  if (window.ManifoldModule) {
28909
28909
  initManifold(window.ManifoldModule);
28910
- return;
28910
+ } else {
28911
+ const errText = "ManifoldModule not found on window after script load.";
28912
+ console.error(errText);
28913
+ setManifoldLoadingError(errText);
28911
28914
  }
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 = `
28915
+ };
28916
+ window.addEventListener(eventName, handleLoad, { once: true });
28917
+ const script = document.createElement("script");
28918
+ script.type = "module";
28919
+ script.innerHTML = `
28926
28920
  try {
28927
28921
  const { default: ManifoldModule } = await import('${MANIFOLD_CDN_BASE_URL}/manifold.js');
28928
28922
  window.ManifoldModule = ManifoldModule;
@@ -28932,125 +28926,123 @@ try {
28932
28926
  window.dispatchEvent(new CustomEvent('${eventName}'));
28933
28927
  }
28934
28928
  `.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]
28929
+ const scriptError = (err) => {
28930
+ const errText = "Failed to load Manifold loader script.";
28931
+ console.error(errText, err);
28932
+ setManifoldLoadingError(errText);
28933
+ window.removeEventListener(eventName, handleLoad);
28934
+ };
28935
+ script.addEventListener("error", scriptError);
28936
+ document.body.appendChild(script);
28937
+ return () => {
28938
+ window.removeEventListener(eventName, handleLoad);
28939
+ script.removeEventListener("error", scriptError);
28940
+ };
28941
+ }, []);
28942
+ const {
28943
+ geoms,
28944
+ textures,
28945
+ pcbThickness,
28946
+ error: builderError,
28947
+ isLoading: builderIsLoading,
28948
+ boardData
28949
+ } = useManifoldBoardBuilder(manifoldJSModule, circuitJson);
28950
+ const geometryMeshes = useMemo18(() => createGeometryMeshes(geoms), [geoms]);
28951
+ const textureMeshes = useMemo18(
28952
+ () => createTextureMeshes(textures, boardData, pcbThickness),
28953
+ [textures, boardData, pcbThickness]
28954
+ );
28955
+ const cadComponents = useMemo18(
28956
+ () => su13(circuitJson).cad_component.list(),
28957
+ [circuitJson]
28958
+ );
28959
+ const boardDimensions = useMemo18(() => {
28960
+ if (!boardData) return void 0;
28961
+ const { width: width10 = 0, height: height10 = 0 } = boardData;
28962
+ return { width: width10, height: height10 };
28963
+ }, [boardData]);
28964
+ const initialCameraPosition = useMemo18(() => {
28965
+ if (!boardData) return [5, 5, 5];
28966
+ const { width: width10 = 0, height: height10 = 0 } = boardData;
28967
+ const safeWidth = Math.max(width10, 1);
28968
+ const safeHeight = Math.max(height10, 1);
28969
+ const largestDim = Math.max(safeWidth, safeHeight, 5);
28970
+ return [largestDim * 0.75, largestDim * 0.75, largestDim * 0.75];
28971
+ }, [boardData]);
28972
+ if (manifoldLoadingError) {
28973
+ return /* @__PURE__ */ jsxs7(
28974
+ "div",
28975
+ {
28976
+ style: {
28977
+ color: "red",
28978
+ padding: "1em",
28979
+ border: "1px solid red",
28980
+ margin: "1em"
28981
+ },
28982
+ children: [
28983
+ "Error: ",
28984
+ manifoldLoadingError
28985
+ ]
28986
+ }
28964
28987
  );
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
- }
28988
+ }
28989
+ if (!manifoldJSModule) {
28990
+ return /* @__PURE__ */ jsx13("div", { style: { padding: "1em" }, children: "Loading Manifold module..." });
28991
+ }
28992
+ if (builderError) {
29018
28993
  return /* @__PURE__ */ jsxs7(
29019
- CadViewerContainer,
28994
+ "div",
29020
28995
  {
29021
- ref,
29022
- initialCameraPosition,
29023
- autoRotateDisabled,
29024
- clickToInteractEnabled,
29025
- boardDimensions,
29026
- onUserInteraction,
28996
+ style: {
28997
+ color: "red",
28998
+ padding: "1em",
28999
+ border: "1px solid red",
29000
+ margin: "1em"
29001
+ },
29027
29002
  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
- ))
29003
+ "Error: ",
29004
+ builderError
29049
29005
  ]
29050
29006
  }
29051
29007
  );
29052
29008
  }
29053
- );
29009
+ if (builderIsLoading) {
29010
+ return /* @__PURE__ */ jsx13("div", { style: { padding: "1em" }, children: "Processing board geometry..." });
29011
+ }
29012
+ return /* @__PURE__ */ jsxs7(
29013
+ CadViewerContainer,
29014
+ {
29015
+ initialCameraPosition,
29016
+ autoRotateDisabled,
29017
+ clickToInteractEnabled,
29018
+ boardDimensions,
29019
+ onUserInteraction,
29020
+ children: [
29021
+ /* @__PURE__ */ jsx13(
29022
+ BoardMeshes,
29023
+ {
29024
+ geometryMeshes,
29025
+ textureMeshes
29026
+ }
29027
+ ),
29028
+ cadComponents.map((cad_component2) => /* @__PURE__ */ jsx13(
29029
+ ThreeErrorBoundary,
29030
+ {
29031
+ fallback: ({ error }) => /* @__PURE__ */ jsx13(Error3d, { cad_component: cad_component2, error }),
29032
+ children: /* @__PURE__ */ jsx13(
29033
+ AnyCadComponent,
29034
+ {
29035
+ cad_component: cad_component2,
29036
+ circuitJson
29037
+ }
29038
+ )
29039
+ },
29040
+ cad_component2.cad_component_id
29041
+ ))
29042
+ ]
29043
+ }
29044
+ );
29045
+ };
29054
29046
  var CadViewerManifold_default = CadViewerManifold;
29055
29047
 
29056
29048
  // src/hooks/useContextMenu.ts
@@ -29155,87 +29147,13 @@ var useContextMenu = ({ containerRef }) => {
29155
29147
  };
29156
29148
  };
29157
29149
 
29158
- // src/hooks/exporter/gltf.ts
29159
- 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();
29215
- exporter.parse(
29216
- instance,
29217
- (gltf) => {
29218
- const type = options.binary ? "gltf-binary" : "gltf+json";
29219
- const blob = new Blob(
29220
- [gltf instanceof ArrayBuffer ? gltf : JSON.stringify(gltf)],
29221
- { type: `model/${type}` }
29222
- );
29223
- resolve(URL.createObjectURL(blob));
29224
- },
29225
- reject,
29226
- options
29227
- );
29228
- return promise;
29229
- };
29230
- }
29231
-
29232
29150
  // src/CadViewer.tsx
29233
29151
  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);
29152
+ var CadViewer = (props) => {
29153
+ const [engine, setEngine] = useState15("manifold");
29154
+ const containerRef = useRef9(null);
29155
+ const [autoRotate, setAutoRotate] = useState15(true);
29156
+ const [autoRotateUserToggled, setAutoRotateUserToggled] = useState15(false);
29239
29157
  const {
29240
29158
  menuVisible,
29241
29159
  menuPos,
@@ -29243,28 +29161,14 @@ var CadViewer = forwardRef5((props, ref) => {
29243
29161
  contextMenuEventHandlers,
29244
29162
  setMenuVisible
29245
29163
  } = 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);
29164
+ const autoRotateUserToggledRef = useRef9(autoRotateUserToggled);
29261
29165
  autoRotateUserToggledRef.current = autoRotateUserToggled;
29262
- const handleUserInteraction = useCallback8(() => {
29166
+ const handleUserInteraction = useCallback7(() => {
29263
29167
  if (!autoRotateUserToggledRef.current) {
29264
29168
  setAutoRotate(false);
29265
29169
  }
29266
29170
  }, []);
29267
- const toggleAutoRotate = useCallback8(() => {
29171
+ const toggleAutoRotate = useCallback7(() => {
29268
29172
  setAutoRotate((prev) => !prev);
29269
29173
  setAutoRotateUserToggled(true);
29270
29174
  }, []);
@@ -29272,17 +29176,13 @@ var CadViewer = forwardRef5((props, ref) => {
29272
29176
  setEngine(newEngine);
29273
29177
  setMenuVisible(false);
29274
29178
  };
29275
- const handleDownloadGltf = useCallback8(() => {
29276
- saveGltfAs("pcb.glb");
29277
- setMenuVisible(false);
29278
- }, [saveGltfAs, setMenuVisible]);
29279
- useEffect23(() => {
29179
+ useEffect22(() => {
29280
29180
  const stored = window.localStorage.getItem("cadViewerEngine");
29281
29181
  if (stored === "jscad" || stored === "manifold") {
29282
29182
  setEngine(stored);
29283
29183
  }
29284
29184
  }, []);
29285
- useEffect23(() => {
29185
+ useEffect22(() => {
29286
29186
  window.localStorage.setItem("cadViewerEngine", engine);
29287
29187
  }, [engine]);
29288
29188
  const viewerKey = props.circuitJson ? JSON.stringify(props.circuitJson) : void 0;
@@ -29297,19 +29197,15 @@ var CadViewer = forwardRef5((props, ref) => {
29297
29197
  CadViewerJscad,
29298
29198
  {
29299
29199
  ...props,
29300
- ref: mergedRef,
29301
29200
  autoRotateDisabled: props.autoRotateDisabled || !autoRotate,
29302
- onUserInteraction: handleUserInteraction,
29303
- children: props.children
29201
+ onUserInteraction: handleUserInteraction
29304
29202
  }
29305
29203
  ) : /* @__PURE__ */ jsx14(
29306
29204
  CadViewerManifold_default,
29307
29205
  {
29308
29206
  ...props,
29309
- ref: mergedRef,
29310
29207
  autoRotateDisabled: props.autoRotateDisabled || !autoRotate,
29311
- onUserInteraction: handleUserInteraction,
29312
- children: props.children
29208
+ onUserInteraction: handleUserInteraction
29313
29209
  }
29314
29210
  ),
29315
29211
  /* @__PURE__ */ jsxs8(
@@ -29416,26 +29312,6 @@ var CadViewer = forwardRef5((props, ref) => {
29416
29312
  ]
29417
29313
  }
29418
29314
  ),
29419
- /* @__PURE__ */ jsx14(
29420
- "div",
29421
- {
29422
- style: {
29423
- padding: "12px 18px",
29424
- cursor: "pointer",
29425
- display: "flex",
29426
- alignItems: "center",
29427
- gap: 10,
29428
- color: "#f5f6fa",
29429
- fontWeight: 500,
29430
- borderRadius: 6,
29431
- transition: "background 0.1s"
29432
- },
29433
- onClick: handleDownloadGltf,
29434
- onMouseOver: (e) => e.currentTarget.style.background = "#2d313a",
29435
- onMouseOut: (e) => e.currentTarget.style.background = "transparent",
29436
- children: "Download GLTF"
29437
- }
29438
- ),
29439
29315
  /* @__PURE__ */ jsx14(
29440
29316
  "div",
29441
29317
  {
@@ -29470,7 +29346,7 @@ var CadViewer = forwardRef5((props, ref) => {
29470
29346
  },
29471
29347
  viewerKey
29472
29348
  );
29473
- });
29349
+ };
29474
29350
 
29475
29351
  // src/convert-circuit-json-to-3d-svg.ts
29476
29352
  var import_debug = __toESM(require_browser(), 1);
@@ -29739,6 +29615,66 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
29739
29615
  return serialized;
29740
29616
  }
29741
29617
 
29618
+ // src/hooks/exporter/gltf.ts
29619
+ import { GLTFExporter } from "three-stdlib";
29620
+ import { useEffect as useEffect23, useState as useState16, useMemo as useMemo19, useCallback as useCallback8 } from "react";
29621
+ function useSaveGltfAs(options = {}) {
29622
+ const parse = useParser(options);
29623
+ const link = useMemo19(() => document.createElement("a"), []);
29624
+ const saveAs = async (filename) => {
29625
+ const name = filename ?? options.filename ?? "";
29626
+ if (options.binary == null) options.binary = name.endsWith(".glb");
29627
+ const url = await parse(instance);
29628
+ link.download = name;
29629
+ link.href = url;
29630
+ link.dispatchEvent(new MouseEvent("click"));
29631
+ URL.revokeObjectURL(url);
29632
+ };
29633
+ useEffect23(
29634
+ () => () => {
29635
+ link.remove();
29636
+ instance = null;
29637
+ },
29638
+ []
29639
+ );
29640
+ let instance;
29641
+ const ref = useCallback8((obj3D) => {
29642
+ instance = obj3D;
29643
+ }, []);
29644
+ return [ref, saveAs];
29645
+ }
29646
+ function useExportGltfUrl(options = {}) {
29647
+ const parse = useParser(options);
29648
+ const [url, setUrl] = useState16();
29649
+ const [error, setError] = useState16();
29650
+ const ref = useCallback8(
29651
+ (instance) => parse(instance).then(setUrl).catch(setError),
29652
+ []
29653
+ );
29654
+ useEffect23(() => () => URL.revokeObjectURL(url), [url]);
29655
+ return [ref, url, error];
29656
+ }
29657
+ function useParser(options = {}) {
29658
+ const exporter = useMemo19(() => new GLTFExporter(), []);
29659
+ return (instance) => {
29660
+ const { promise, resolve, reject } = Promise.withResolvers();
29661
+ exporter.parse(
29662
+ instance,
29663
+ (gltf) => {
29664
+ const type = options.binary ? "gltf-binary" : "gltf+json";
29665
+ const blob = new Blob(
29666
+ [gltf instanceof ArrayBuffer ? gltf : JSON.stringify(gltf)],
29667
+ { type: `model/${type}` }
29668
+ );
29669
+ resolve(URL.createObjectURL(blob));
29670
+ },
29671
+ reject,
29672
+ options
29673
+ );
29674
+ return promise;
29675
+ };
29676
+ }
29677
+
29742
29678
  // src/utils/jsdom-shim.ts
29743
29679
  function applyJsdomShim(jsdom) {
29744
29680
  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.352",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.js",
6
6
  "type": "module",