@tscircuit/3d-viewer 0.0.348 → 0.0.350

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,12 +1,11 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { AnyCircuitElement, PcbBoard } from 'circuit-json';
3
- import * as React from 'react';
1
+ import * as react from 'react';
4
2
  import * as THREE from 'three';
3
+ import { AnyCircuitElement, PcbBoard } from 'circuit-json';
5
4
  import { GLTFExporterOptions } from 'three-stdlib';
6
5
  import { ManifoldToplevel } from 'manifold-3d/manifold.d.ts';
7
6
  import { JSDOM } from 'jsdom';
8
7
 
9
- declare const CadViewer: (props: any) => react_jsx_runtime.JSX.Element;
8
+ declare const CadViewer: react.ForwardRefExoticComponent<Omit<any, "ref"> & react.RefAttributes<THREE.Object3D<THREE.Object3DEventMap>>>;
10
9
 
11
10
  interface CircuitToSvgOptions {
12
11
  width?: number;
@@ -34,11 +33,11 @@ type Options = Omit<GLTFExporterOptions, "animations" | "includeCustomExtensions
34
33
  declare function useSaveGltfAs(options?: Options & {
35
34
  filename?: string;
36
35
  }): [
37
- ref3D: React.ForwardedRef<THREE.Object3D>,
36
+ ref3D: react.ForwardedRef<THREE.Object3D>,
38
37
  saveAs: (filename?: string) => Promise<void>
39
38
  ];
40
39
  declare function useExportGltfUrl(options?: Options): [
41
- ref3D: React.ForwardedRef<THREE.Object3D>,
40
+ ref3D: react.ForwardedRef<THREE.Object3D>,
42
41
  url: string | undefined,
43
42
  error: ErrorEvent | undefined
44
43
  ];
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 useState15, useCallback as useCallback7, useRef as useRef9, useEffect as useEffect22 } from "react";
14231
+ import { useState as useState16, useCallback as useCallback8, useRef as useRef10, useEffect as useEffect23, forwardRef as forwardRef5 } 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.347",
25586
+ version: "0.0.349",
25587
25587
  main: "./dist/index.js",
25588
25588
  module: "./dist/index.js",
25589
25589
  type: "module",
@@ -25630,7 +25630,7 @@ var package_default = {
25630
25630
  "@storybook/blocks": "9.0.0-alpha.17",
25631
25631
  "@storybook/react-vite": "^9.1.2",
25632
25632
  "@tscircuit/circuit-json-util": "^0.0.66",
25633
- "@tscircuit/core": "^0.0.631",
25633
+ "@tscircuit/core": "^0.0.673",
25634
25634
  "@tscircuit/props": "^0.0.288",
25635
25635
  "@types/jsdom": "^21.1.7",
25636
25636
  "@types/react": "19",
@@ -25994,9 +25994,14 @@ var OrbitControls = ({
25994
25994
  ]);
25995
25995
  useEffect11(() => {
25996
25996
  if (!controls || !onStart) return;
25997
- controls.addEventListener("start", onStart);
25997
+ const handleStart = (event) => {
25998
+ if (event.button !== 2) {
25999
+ onStart();
26000
+ }
26001
+ };
26002
+ controls.addEventListener("start", handleStart);
25998
26003
  return () => {
25999
- controls.removeEventListener("start", onStart);
26004
+ controls.removeEventListener("start", handleStart);
26000
26005
  };
26001
26006
  }, [controls, onStart]);
26002
26007
  useEffect11(() => {
@@ -27763,7 +27768,7 @@ var CadViewerJscad = forwardRef3(
27763
27768
 
27764
27769
  // src/CadViewerManifold.tsx
27765
27770
  import { su as su13 } from "@tscircuit/circuit-json-util";
27766
- import { useEffect as useEffect20, useMemo as useMemo18, useState as useState13 } from "react";
27771
+ import { forwardRef as forwardRef4, useEffect as useEffect20, useMemo as useMemo18, useState as useState13 } from "react";
27767
27772
 
27768
27773
  // src/hooks/useManifoldBoardBuilder.ts
27769
27774
  import { useState as useState12, useEffect as useEffect19, useMemo as useMemo17, useRef as useRef7 } from "react";
@@ -28873,50 +28878,51 @@ var BoardMeshes = ({
28873
28878
  return null;
28874
28879
  };
28875
28880
  var MANIFOLD_CDN_BASE_URL = "https://cdn.jsdelivr.net/npm/manifold-3d@3.2.1";
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 = () => {
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
+ };
28908
28908
  if (window.ManifoldModule) {
28909
28909
  initManifold(window.ManifoldModule);
28910
- } else {
28911
- const errText = "ManifoldModule not found on window after script load.";
28912
- console.error(errText);
28913
- setManifoldLoadingError(errText);
28910
+ return;
28914
28911
  }
28915
- };
28916
- window.addEventListener(eventName, handleLoad, { once: true });
28917
- const script = document.createElement("script");
28918
- script.type = "module";
28919
- script.innerHTML = `
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 = `
28920
28926
  try {
28921
28927
  const { default: ManifoldModule } = await import('${MANIFOLD_CDN_BASE_URL}/manifold.js');
28922
28928
  window.ManifoldModule = ManifoldModule;
@@ -28926,123 +28932,125 @@ try {
28926
28932
  window.dispatchEvent(new CustomEvent('${eventName}'));
28927
28933
  }
28928
28934
  `.trim();
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
- }
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]
28987
28960
  );
28988
- }
28989
- if (!manifoldJSModule) {
28990
- return /* @__PURE__ */ jsx13("div", { style: { padding: "1em" }, children: "Loading Manifold module..." });
28991
- }
28992
- if (builderError) {
28961
+ const cadComponents = useMemo18(
28962
+ () => su13(circuitJson).cad_component.list(),
28963
+ [circuitJson]
28964
+ );
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
+ }
28993
29018
  return /* @__PURE__ */ jsxs7(
28994
- "div",
29019
+ CadViewerContainer,
28995
29020
  {
28996
- style: {
28997
- color: "red",
28998
- padding: "1em",
28999
- border: "1px solid red",
29000
- margin: "1em"
29001
- },
29021
+ ref,
29022
+ initialCameraPosition,
29023
+ autoRotateDisabled,
29024
+ clickToInteractEnabled,
29025
+ boardDimensions,
29026
+ onUserInteraction,
29002
29027
  children: [
29003
- "Error: ",
29004
- builderError
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
+ ))
29005
29049
  ]
29006
29050
  }
29007
29051
  );
29008
29052
  }
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
- };
29053
+ );
29046
29054
  var CadViewerManifold_default = CadViewerManifold;
29047
29055
 
29048
29056
  // src/hooks/useContextMenu.ts
@@ -29147,13 +29155,87 @@ var useContextMenu = ({ containerRef }) => {
29147
29155
  };
29148
29156
  };
29149
29157
 
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
+
29150
29232
  // src/CadViewer.tsx
29151
29233
  import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
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);
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);
29157
29239
  const {
29158
29240
  menuVisible,
29159
29241
  menuPos,
@@ -29161,14 +29243,28 @@ var CadViewer = (props) => {
29161
29243
  contextMenuEventHandlers,
29162
29244
  setMenuVisible
29163
29245
  } = useContextMenu({ containerRef });
29164
- const autoRotateUserToggledRef = useRef9(autoRotateUserToggled);
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);
29165
29261
  autoRotateUserToggledRef.current = autoRotateUserToggled;
29166
- const handleUserInteraction = useCallback7(() => {
29262
+ const handleUserInteraction = useCallback8(() => {
29167
29263
  if (!autoRotateUserToggledRef.current) {
29168
29264
  setAutoRotate(false);
29169
29265
  }
29170
29266
  }, []);
29171
- const toggleAutoRotate = useCallback7(() => {
29267
+ const toggleAutoRotate = useCallback8(() => {
29172
29268
  setAutoRotate((prev) => !prev);
29173
29269
  setAutoRotateUserToggled(true);
29174
29270
  }, []);
@@ -29176,13 +29272,17 @@ var CadViewer = (props) => {
29176
29272
  setEngine(newEngine);
29177
29273
  setMenuVisible(false);
29178
29274
  };
29179
- useEffect22(() => {
29275
+ const handleDownloadGltf = useCallback8(() => {
29276
+ saveGltfAs("pcb.glb");
29277
+ setMenuVisible(false);
29278
+ }, [saveGltfAs, setMenuVisible]);
29279
+ useEffect23(() => {
29180
29280
  const stored = window.localStorage.getItem("cadViewerEngine");
29181
29281
  if (stored === "jscad" || stored === "manifold") {
29182
29282
  setEngine(stored);
29183
29283
  }
29184
29284
  }, []);
29185
- useEffect22(() => {
29285
+ useEffect23(() => {
29186
29286
  window.localStorage.setItem("cadViewerEngine", engine);
29187
29287
  }, [engine]);
29188
29288
  const viewerKey = props.circuitJson ? JSON.stringify(props.circuitJson) : void 0;
@@ -29197,15 +29297,19 @@ var CadViewer = (props) => {
29197
29297
  CadViewerJscad,
29198
29298
  {
29199
29299
  ...props,
29300
+ ref: mergedRef,
29200
29301
  autoRotateDisabled: props.autoRotateDisabled || !autoRotate,
29201
- onUserInteraction: handleUserInteraction
29302
+ onUserInteraction: handleUserInteraction,
29303
+ children: props.children
29202
29304
  }
29203
29305
  ) : /* @__PURE__ */ jsx14(
29204
29306
  CadViewerManifold_default,
29205
29307
  {
29206
29308
  ...props,
29309
+ ref: mergedRef,
29207
29310
  autoRotateDisabled: props.autoRotateDisabled || !autoRotate,
29208
- onUserInteraction: handleUserInteraction
29311
+ onUserInteraction: handleUserInteraction,
29312
+ children: props.children
29209
29313
  }
29210
29314
  ),
29211
29315
  /* @__PURE__ */ jsxs8(
@@ -29312,6 +29416,26 @@ var CadViewer = (props) => {
29312
29416
  ]
29313
29417
  }
29314
29418
  ),
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
+ ),
29315
29439
  /* @__PURE__ */ jsx14(
29316
29440
  "div",
29317
29441
  {
@@ -29346,7 +29470,7 @@ var CadViewer = (props) => {
29346
29470
  },
29347
29471
  viewerKey
29348
29472
  );
29349
- };
29473
+ });
29350
29474
 
29351
29475
  // src/convert-circuit-json-to-3d-svg.ts
29352
29476
  var import_debug = __toESM(require_browser(), 1);
@@ -29615,66 +29739,6 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
29615
29739
  return serialized;
29616
29740
  }
29617
29741
 
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
-
29678
29742
  // src/utils/jsdom-shim.ts
29679
29743
  function applyJsdomShim(jsdom) {
29680
29744
  global.window = jsdom.window;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/3d-viewer",
3
- "version": "0.0.348",
3
+ "version": "0.0.350",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.js",
6
6
  "type": "module",
@@ -47,7 +47,7 @@
47
47
  "@storybook/blocks": "9.0.0-alpha.17",
48
48
  "@storybook/react-vite": "^9.1.2",
49
49
  "@tscircuit/circuit-json-util": "^0.0.66",
50
- "@tscircuit/core": "^0.0.631",
50
+ "@tscircuit/core": "^0.0.673",
51
51
  "@tscircuit/props": "^0.0.288",
52
52
  "@types/jsdom": "^21.1.7",
53
53
  "@types/react": "19",