@tscircuit/3d-viewer 0.0.418 → 0.0.419

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.
Files changed (2) hide show
  1. package/dist/index.js +739 -297
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -14228,11 +14228,11 @@ var require_browser = __commonJS({
14228
14228
  });
14229
14229
 
14230
14230
  // src/CadViewer.tsx
14231
- import { useState as useState18, useCallback as useCallback9, useRef as useRef9, useEffect as useEffect24 } from "react";
14231
+ import { useState as useState19, useCallback as useCallback11, useRef as useRef9, useEffect as useEffect24 } from "react";
14232
14232
 
14233
14233
  // src/CadViewerJscad.tsx
14234
14234
  import { su as su4 } from "@tscircuit/circuit-json-util";
14235
- import { forwardRef as forwardRef3, useMemo as useMemo18 } from "react";
14235
+ import { forwardRef as forwardRef3, useMemo as useMemo19 } from "react";
14236
14236
 
14237
14237
  // src/AnyCadComponent.tsx
14238
14238
  import { su } from "@tscircuit/circuit-json-util";
@@ -26641,17 +26641,13 @@ var AnyCadComponent = ({
26641
26641
  };
26642
26642
 
26643
26643
  // src/CadViewerContainer.tsx
26644
- import {
26645
- forwardRef as forwardRef2,
26646
- useMemo as useMemo14,
26647
- useState as useState8
26648
- } from "react";
26649
- import * as THREE13 from "three";
26644
+ import { forwardRef as forwardRef2, useMemo as useMemo15, useState as useState8 } from "react";
26645
+ import * as THREE14 from "three";
26650
26646
 
26651
26647
  // package.json
26652
26648
  var package_default = {
26653
26649
  name: "@tscircuit/3d-viewer",
26654
- version: "0.0.417",
26650
+ version: "0.0.418",
26655
26651
  main: "./dist/index.js",
26656
26652
  module: "./dist/index.js",
26657
26653
  type: "module",
@@ -27052,13 +27048,21 @@ var OrbitControls = ({
27052
27048
  zoomSpeed,
27053
27049
  enableDamping,
27054
27050
  dampingFactor,
27055
- target
27051
+ target,
27052
+ onControlsChange
27056
27053
  }) => {
27057
27054
  const { camera, renderer } = useThree();
27058
27055
  const controls = useMemo11(() => {
27059
27056
  if (!camera || !renderer) return null;
27060
27057
  return new ThreeOrbitControls(camera, renderer.domElement);
27061
27058
  }, [camera, renderer]);
27059
+ useEffect13(() => {
27060
+ if (!onControlsChange) return;
27061
+ onControlsChange(controls ?? null);
27062
+ return () => {
27063
+ onControlsChange(null);
27064
+ };
27065
+ }, [controls, onControlsChange]);
27062
27066
  useEffect13(() => {
27063
27067
  if (!controls) return;
27064
27068
  controls.autoRotate = autoRotate || false;
@@ -27222,6 +27226,272 @@ var Lights = () => {
27222
27226
  return null;
27223
27227
  };
27224
27228
 
27229
+ // src/hooks/useCameraController.ts
27230
+ import { useCallback as useCallback5, useEffect as useEffect16, useMemo as useMemo14, useRef as useRef5 } from "react";
27231
+ import * as THREE13 from "three";
27232
+ var easeInOutCubic = (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
27233
+ var CameraAnimator = ({
27234
+ defaultTarget,
27235
+ controlsRef,
27236
+ onReady
27237
+ }) => {
27238
+ const { camera } = useThree();
27239
+ const animationRef = useRef5(null);
27240
+ const tempQuaternion = useRef5(new THREE13.Quaternion());
27241
+ const tempTarget = useRef5(new THREE13.Vector3());
27242
+ const tempUp = useRef5(new THREE13.Vector3());
27243
+ const tempRoll = useRef5(new THREE13.Quaternion());
27244
+ const tempRollTarget = useRef5(new THREE13.Quaternion());
27245
+ const baseOrientationHelper = useRef5(new THREE13.Object3D());
27246
+ const orientationHelper = useRef5(new THREE13.Object3D());
27247
+ const animateTo = useCallback5(
27248
+ ({ position, target, up, durationMs = 600 }) => {
27249
+ if (!camera) return;
27250
+ const currentTarget = controlsRef.current?.target ?? defaultTarget;
27251
+ const toPosition = new THREE13.Vector3(
27252
+ position[0],
27253
+ position[1],
27254
+ position[2]
27255
+ );
27256
+ const resolvedTarget = target ? new THREE13.Vector3(target[0], target[1], target[2]) : defaultTarget.clone();
27257
+ const resolvedUp = new THREE13.Vector3(...up ?? [0, 0, 1]).normalize();
27258
+ const toOrientationHelper = orientationHelper.current;
27259
+ toOrientationHelper.position.copy(toPosition);
27260
+ toOrientationHelper.up.copy(resolvedUp);
27261
+ toOrientationHelper.lookAt(resolvedTarget);
27262
+ const toQuaternion = toOrientationHelper.quaternion.clone();
27263
+ const fromQuaternion = camera.quaternion.clone();
27264
+ const fromPosition = camera.position.clone();
27265
+ const fromTarget = currentTarget.clone();
27266
+ const baseHelper = baseOrientationHelper.current;
27267
+ baseHelper.up.set(0, 0, 1);
27268
+ baseHelper.position.copy(fromPosition);
27269
+ baseHelper.lookAt(fromTarget);
27270
+ const baseFromQuaternion = baseHelper.quaternion.clone();
27271
+ baseHelper.up.set(0, 0, 1);
27272
+ baseHelper.position.copy(toPosition);
27273
+ baseHelper.lookAt(resolvedTarget);
27274
+ const baseToQuaternion = baseHelper.quaternion.clone();
27275
+ const rollFrom = baseFromQuaternion.clone().invert().multiply(fromQuaternion).normalize();
27276
+ const rollTo = baseToQuaternion.clone().invert().multiply(toQuaternion).normalize();
27277
+ animationRef.current = {
27278
+ fromPosition,
27279
+ toPosition,
27280
+ fromTarget,
27281
+ toTarget: resolvedTarget,
27282
+ toQuaternion,
27283
+ rollFrom,
27284
+ rollTo,
27285
+ startTime: performance.now(),
27286
+ duration: durationMs
27287
+ };
27288
+ },
27289
+ [camera, controlsRef, defaultTarget]
27290
+ );
27291
+ useEffect16(() => {
27292
+ if (!onReady || !camera) return;
27293
+ onReady({ animateTo });
27294
+ return () => {
27295
+ onReady(null);
27296
+ };
27297
+ }, [animateTo, camera, onReady]);
27298
+ useFrame(() => {
27299
+ if (!camera || !animationRef.current) return;
27300
+ const {
27301
+ fromPosition,
27302
+ toPosition,
27303
+ fromTarget,
27304
+ toTarget,
27305
+ toQuaternion,
27306
+ rollFrom,
27307
+ rollTo,
27308
+ startTime,
27309
+ duration
27310
+ } = animationRef.current;
27311
+ const elapsed = performance.now() - startTime;
27312
+ const progress = duration <= 0 ? 1 : Math.min(elapsed / duration, 1);
27313
+ const eased = easeInOutCubic(progress);
27314
+ camera.position.lerpVectors(fromPosition, toPosition, eased);
27315
+ const nextTarget = tempTarget.current;
27316
+ nextTarget.copy(fromTarget).lerp(toTarget, eased);
27317
+ const baseHelper = baseOrientationHelper.current;
27318
+ baseHelper.up.set(0, 0, 1);
27319
+ baseHelper.position.copy(camera.position);
27320
+ baseHelper.lookAt(nextTarget);
27321
+ const baseQuaternion = tempQuaternion.current;
27322
+ baseQuaternion.copy(baseHelper.quaternion);
27323
+ const interpolatedRoll = tempRoll.current;
27324
+ interpolatedRoll.copy(rollFrom);
27325
+ const rollTarget = tempRollTarget.current;
27326
+ rollTarget.copy(rollTo);
27327
+ if (rollFrom.dot(rollTo) < 0) {
27328
+ rollTarget.x *= -1;
27329
+ rollTarget.y *= -1;
27330
+ rollTarget.z *= -1;
27331
+ rollTarget.w *= -1;
27332
+ }
27333
+ rollTarget.normalize();
27334
+ interpolatedRoll.slerp(rollTarget, eased);
27335
+ camera.quaternion.copy(baseQuaternion).multiply(interpolatedRoll).normalize();
27336
+ const upVector = tempUp.current;
27337
+ upVector.set(0, 1, 0).applyQuaternion(camera.quaternion).normalize();
27338
+ camera.up.copy(upVector);
27339
+ controlsRef.current?.target.copy(nextTarget);
27340
+ camera.updateMatrixWorld();
27341
+ controlsRef.current?.update();
27342
+ if (progress >= 1) {
27343
+ camera.position.copy(toPosition);
27344
+ camera.quaternion.copy(toQuaternion);
27345
+ camera.up.set(0, 0, 1);
27346
+ camera.updateMatrixWorld();
27347
+ controlsRef.current?.target.copy(toTarget);
27348
+ controlsRef.current?.update();
27349
+ animationRef.current = null;
27350
+ }
27351
+ });
27352
+ return null;
27353
+ };
27354
+ var useCameraController = ({
27355
+ defaultTarget,
27356
+ initialCameraPosition,
27357
+ onCameraControllerReady
27358
+ }) => {
27359
+ const controlsRef = useRef5(null);
27360
+ const baseDistance = useMemo14(() => {
27361
+ const [x, y, z126] = initialCameraPosition ?? [5, 5, 5];
27362
+ const distance2 = Math.hypot(
27363
+ x - defaultTarget.x,
27364
+ y - defaultTarget.y,
27365
+ z126 - defaultTarget.z
27366
+ );
27367
+ return distance2 > 0 ? distance2 : 5;
27368
+ }, [initialCameraPosition, defaultTarget]);
27369
+ const getPresetConfig = useCallback5(
27370
+ (preset) => {
27371
+ const targetVector = [
27372
+ defaultTarget.x,
27373
+ defaultTarget.y,
27374
+ defaultTarget.z
27375
+ ];
27376
+ const distance2 = baseDistance;
27377
+ const heightOffset = distance2 * 0.3;
27378
+ switch (preset) {
27379
+ case "Top Centered": {
27380
+ const angledOffset = distance2 / Math.sqrt(2);
27381
+ return {
27382
+ position: [
27383
+ defaultTarget.x,
27384
+ defaultTarget.y - angledOffset,
27385
+ defaultTarget.z + angledOffset
27386
+ ],
27387
+ target: targetVector,
27388
+ up: [0, 0, 1]
27389
+ };
27390
+ }
27391
+ case "Top Down":
27392
+ return {
27393
+ position: [
27394
+ defaultTarget.x,
27395
+ defaultTarget.y,
27396
+ defaultTarget.z + distance2
27397
+ ],
27398
+ target: targetVector,
27399
+ up: [0, 0, 1]
27400
+ };
27401
+ case "Top Left Corner":
27402
+ return {
27403
+ position: [
27404
+ defaultTarget.x - distance2 * 0.6,
27405
+ defaultTarget.y - distance2 * 0.6,
27406
+ defaultTarget.z + distance2 * 0.6
27407
+ ],
27408
+ target: targetVector,
27409
+ up: [0, 0, 1]
27410
+ };
27411
+ case "Top Right Corner":
27412
+ return {
27413
+ position: [
27414
+ defaultTarget.x + distance2 * 0.6,
27415
+ defaultTarget.y - distance2 * 0.6,
27416
+ defaultTarget.z + distance2 * 0.6
27417
+ ],
27418
+ target: targetVector,
27419
+ up: [0, 0, 1]
27420
+ };
27421
+ case "Left Sideview":
27422
+ return {
27423
+ position: [
27424
+ defaultTarget.x - distance2,
27425
+ defaultTarget.y,
27426
+ defaultTarget.z + heightOffset
27427
+ ],
27428
+ target: targetVector,
27429
+ up: [0, 0, 1]
27430
+ };
27431
+ case "Right Sideview":
27432
+ return {
27433
+ position: [
27434
+ defaultTarget.x + distance2,
27435
+ defaultTarget.y,
27436
+ defaultTarget.z + heightOffset
27437
+ ],
27438
+ target: targetVector,
27439
+ up: [0, 0, 1]
27440
+ };
27441
+ case "Front":
27442
+ return {
27443
+ position: [
27444
+ defaultTarget.x,
27445
+ defaultTarget.y - distance2,
27446
+ defaultTarget.z + heightOffset
27447
+ ],
27448
+ target: targetVector,
27449
+ up: [0, 0, 1]
27450
+ };
27451
+ case "Custom":
27452
+ default:
27453
+ return null;
27454
+ }
27455
+ },
27456
+ [baseDistance, defaultTarget]
27457
+ );
27458
+ const handleControllerReady = useCallback5(
27459
+ (controller) => {
27460
+ if (!onCameraControllerReady) return;
27461
+ if (!controller) {
27462
+ onCameraControllerReady(null);
27463
+ return;
27464
+ }
27465
+ const enhancedController = {
27466
+ animateTo: controller.animateTo,
27467
+ animateToPreset: (preset) => {
27468
+ if (preset === "Custom") return;
27469
+ const config = getPresetConfig(preset);
27470
+ if (!config) return;
27471
+ controller.animateTo(config);
27472
+ }
27473
+ };
27474
+ onCameraControllerReady(enhancedController);
27475
+ },
27476
+ [getPresetConfig, onCameraControllerReady]
27477
+ );
27478
+ const handleControlsChange = useCallback5(
27479
+ (controls) => {
27480
+ controlsRef.current = controls;
27481
+ },
27482
+ []
27483
+ );
27484
+ const cameraAnimatorProps = useMemo14(
27485
+ () => ({
27486
+ defaultTarget,
27487
+ controlsRef,
27488
+ onReady: handleControllerReady
27489
+ }),
27490
+ [defaultTarget, handleControllerReady]
27491
+ );
27492
+ return { cameraAnimatorProps, handleControlsChange };
27493
+ };
27494
+
27225
27495
  // src/CadViewerContainer.tsx
27226
27496
  import { jsx as jsx12, jsxs as jsxs4 } from "react/jsx-runtime";
27227
27497
  var RotationTracker = () => {
@@ -27241,12 +27511,13 @@ var CadViewerContainer = forwardRef2(
27241
27511
  clickToInteractEnabled = false,
27242
27512
  boardDimensions,
27243
27513
  boardCenter,
27244
- onUserInteraction
27514
+ onUserInteraction,
27515
+ onCameraControllerReady
27245
27516
  }, ref) => {
27246
27517
  const [isInteractionEnabled, setIsInteractionEnabled] = useState8(
27247
27518
  !clickToInteractEnabled
27248
27519
  );
27249
- const gridSectionSize = useMemo14(() => {
27520
+ const gridSectionSize = useMemo15(() => {
27250
27521
  if (!boardDimensions) return 10;
27251
27522
  const width10 = boardDimensions.width ?? 0;
27252
27523
  const height10 = boardDimensions.height ?? 0;
@@ -27254,10 +27525,21 @@ var CadViewerContainer = forwardRef2(
27254
27525
  const desired = largest * 1.5;
27255
27526
  return desired > 10 ? desired : 10;
27256
27527
  }, [boardDimensions]);
27257
- const orbitTarget = useMemo14(() => {
27528
+ const orbitTarget = useMemo15(() => {
27258
27529
  if (!boardCenter) return void 0;
27259
27530
  return [boardCenter.x, boardCenter.y, 0];
27260
27531
  }, [boardCenter]);
27532
+ const defaultTarget = useMemo15(() => {
27533
+ if (orbitTarget) {
27534
+ return new THREE14.Vector3(orbitTarget[0], orbitTarget[1], orbitTarget[2]);
27535
+ }
27536
+ return new THREE14.Vector3(0, 0, 0);
27537
+ }, [orbitTarget]);
27538
+ const { cameraAnimatorProps, handleControlsChange } = useCameraController({
27539
+ defaultTarget,
27540
+ initialCameraPosition,
27541
+ onCameraControllerReady
27542
+ });
27261
27543
  return /* @__PURE__ */ jsxs4("div", { style: { position: "relative", width: "100%", height: "100%" }, children: [
27262
27544
  /* @__PURE__ */ jsx12(
27263
27545
  "div",
@@ -27286,9 +27568,10 @@ var CadViewerContainer = forwardRef2(
27286
27568
  Canvas,
27287
27569
  {
27288
27570
  ref,
27289
- scene: { up: new THREE13.Vector3(0, 0, 1) },
27571
+ scene: { up: new THREE14.Vector3(0, 0, 1) },
27290
27572
  camera: { up: [0, 0, 1], position: initialCameraPosition },
27291
27573
  children: [
27574
+ /* @__PURE__ */ jsx12(CameraAnimator, { ...cameraAnimatorProps }),
27292
27575
  /* @__PURE__ */ jsx12(RotationTracker, {}),
27293
27576
  isInteractionEnabled && /* @__PURE__ */ jsx12(
27294
27577
  OrbitControls,
@@ -27301,7 +27584,8 @@ var CadViewerContainer = forwardRef2(
27301
27584
  zoomSpeed: 0.5,
27302
27585
  enableDamping: true,
27303
27586
  dampingFactor: 0.1,
27304
- target: orbitTarget
27587
+ target: orbitTarget,
27588
+ onControlsChange: handleControlsChange
27305
27589
  }
27306
27590
  ),
27307
27591
  /* @__PURE__ */ jsx12(Lights, {}),
@@ -27379,9 +27663,9 @@ var CadViewerContainer = forwardRef2(
27379
27663
 
27380
27664
  // src/hooks/use-convert-children-to-soup.ts
27381
27665
  import { Circuit } from "@tscircuit/core";
27382
- import { useMemo as useMemo15 } from "react";
27666
+ import { useMemo as useMemo16 } from "react";
27383
27667
  var useConvertChildrenToCircuitJson = (children) => {
27384
- return useMemo15(() => {
27668
+ return useMemo16(() => {
27385
27669
  if (!children) return [];
27386
27670
  const circuit = new Circuit();
27387
27671
  circuit.add(children);
@@ -29089,8 +29373,8 @@ var useBoardGeomBuilder = (circuitJson) => {
29089
29373
  };
29090
29374
 
29091
29375
  // src/three-components/Error3d.tsx
29092
- import { useState as useState11, useCallback as useCallback5, useEffect as useEffect19, useMemo as useMemo16 } from "react";
29093
- import * as THREE14 from "three";
29376
+ import { useState as useState11, useCallback as useCallback6, useEffect as useEffect19, useMemo as useMemo17 } from "react";
29377
+ import * as THREE15 from "three";
29094
29378
  import { Fragment as Fragment5, jsx as jsx13, jsxs as jsxs5 } from "react/jsx-runtime";
29095
29379
  var Error3d = ({
29096
29380
  error,
@@ -29099,7 +29383,7 @@ var Error3d = ({
29099
29383
  const { rootObject } = useThree();
29100
29384
  const [isHovered, setIsHovered] = useState11(false);
29101
29385
  const [hoverPosition, setHoverPosition] = useState11(null);
29102
- const handleHover = useCallback5((e) => {
29386
+ const handleHover = useCallback6((e) => {
29103
29387
  if (e?.mousePosition) {
29104
29388
  setIsHovered(true);
29105
29389
  setHoverPosition(e.mousePosition);
@@ -29108,11 +29392,11 @@ var Error3d = ({
29108
29392
  setHoverPosition(null);
29109
29393
  }
29110
29394
  }, []);
29111
- const handleUnhover = useCallback5(() => {
29395
+ const handleUnhover = useCallback6(() => {
29112
29396
  setIsHovered(false);
29113
29397
  setHoverPosition(null);
29114
29398
  }, []);
29115
- const position = useMemo16(() => {
29399
+ const position = useMemo17(() => {
29116
29400
  if (cad_component2?.position) {
29117
29401
  const p = [
29118
29402
  cad_component2.position.x,
@@ -29123,8 +29407,8 @@ var Error3d = ({
29123
29407
  }
29124
29408
  return [0, 0, 0];
29125
29409
  }, [cad_component2]);
29126
- const group = useMemo16(() => {
29127
- const g = new THREE14.Group();
29410
+ const group = useMemo17(() => {
29411
+ const g = new THREE15.Group();
29128
29412
  g.position.fromArray(position);
29129
29413
  return g;
29130
29414
  }, [position]);
@@ -29183,10 +29467,10 @@ var Error3d = ({
29183
29467
  ] });
29184
29468
  };
29185
29469
  var ErrorBox = ({ parent }) => {
29186
- const mesh = useMemo16(() => {
29187
- const m = new THREE14.Mesh(
29188
- new THREE14.BoxGeometry(0.5, 0.5, 0.5),
29189
- new THREE14.MeshStandardMaterial({
29470
+ const mesh = useMemo17(() => {
29471
+ const m = new THREE15.Mesh(
29472
+ new THREE15.BoxGeometry(0.5, 0.5, 0.5),
29473
+ new THREE15.MeshStandardMaterial({
29190
29474
  depthTest: false,
29191
29475
  transparent: true,
29192
29476
  color: "red",
@@ -29207,8 +29491,8 @@ var ErrorBox = ({ parent }) => {
29207
29491
  };
29208
29492
 
29209
29493
  // src/three-components/STLModel.tsx
29210
- import { useState as useState12, useEffect as useEffect20, useMemo as useMemo17 } from "react";
29211
- import * as THREE15 from "three";
29494
+ import { useState as useState12, useEffect as useEffect20, useMemo as useMemo18 } from "react";
29495
+ import * as THREE16 from "three";
29212
29496
  import { STLLoader } from "three-stdlib";
29213
29497
  function STLModel({
29214
29498
  stlUrl,
@@ -29237,14 +29521,14 @@ function STLModel({
29237
29521
  });
29238
29522
  }
29239
29523
  }, [stlUrl, stlData]);
29240
- const mesh = useMemo17(() => {
29524
+ const mesh = useMemo18(() => {
29241
29525
  if (!geom) return null;
29242
- const material = new THREE15.MeshStandardMaterial({
29243
- color: Array.isArray(color) ? new THREE15.Color(color[0], color[1], color[2]) : color,
29526
+ const material = new THREE16.MeshStandardMaterial({
29527
+ color: Array.isArray(color) ? new THREE16.Color(color[0], color[1], color[2]) : color,
29244
29528
  transparent: opacity !== 1,
29245
29529
  opacity
29246
29530
  });
29247
- return new THREE15.Mesh(geom, material);
29531
+ return new THREE16.Mesh(geom, material);
29248
29532
  }, [geom, color, opacity]);
29249
29533
  useEffect20(() => {
29250
29534
  if (!rootObject || !mesh) return;
@@ -29316,15 +29600,16 @@ var CadViewerJscad = forwardRef3(
29316
29600
  children,
29317
29601
  autoRotateDisabled,
29318
29602
  clickToInteractEnabled,
29319
- onUserInteraction
29603
+ onUserInteraction,
29604
+ onCameraControllerReady
29320
29605
  }, ref) => {
29321
29606
  const childrenSoup = useConvertChildrenToCircuitJson(children);
29322
- const internalCircuitJson = useMemo18(() => {
29607
+ const internalCircuitJson = useMemo19(() => {
29323
29608
  const cj = soup ?? circuitJson;
29324
29609
  return cj ?? childrenSoup;
29325
29610
  }, [soup, circuitJson, childrenSoup]);
29326
29611
  const boardGeom = useBoardGeomBuilder(internalCircuitJson);
29327
- const initialCameraPosition = useMemo18(() => {
29612
+ const initialCameraPosition = useMemo19(() => {
29328
29613
  if (!internalCircuitJson) return [5, 5, 5];
29329
29614
  try {
29330
29615
  const board = su4(internalCircuitJson).pcb_board.list()[0];
@@ -29343,7 +29628,7 @@ var CadViewerJscad = forwardRef3(
29343
29628
  return [5, 5, 5];
29344
29629
  }
29345
29630
  }, [internalCircuitJson]);
29346
- const boardDimensions = useMemo18(() => {
29631
+ const boardDimensions = useMemo19(() => {
29347
29632
  if (!internalCircuitJson) return void 0;
29348
29633
  try {
29349
29634
  const board = su4(internalCircuitJson).pcb_board.list()[0];
@@ -29354,7 +29639,7 @@ var CadViewerJscad = forwardRef3(
29354
29639
  return void 0;
29355
29640
  }
29356
29641
  }, [internalCircuitJson]);
29357
- const boardCenter = useMemo18(() => {
29642
+ const boardCenter = useMemo19(() => {
29358
29643
  if (!internalCircuitJson) return void 0;
29359
29644
  try {
29360
29645
  const board = su4(internalCircuitJson).pcb_board.list()[0];
@@ -29377,6 +29662,7 @@ var CadViewerJscad = forwardRef3(
29377
29662
  boardDimensions,
29378
29663
  boardCenter,
29379
29664
  onUserInteraction,
29665
+ onCameraControllerReady,
29380
29666
  children: [
29381
29667
  boardStls.map(({ stlData, color, layerType }, index) => /* @__PURE__ */ jsx15(
29382
29668
  VisibleSTLModel,
@@ -29411,22 +29697,22 @@ var CadViewerJscad = forwardRef3(
29411
29697
 
29412
29698
  // src/CadViewerManifold.tsx
29413
29699
  import { su as su13 } from "@tscircuit/circuit-json-util";
29414
- import { useEffect as useEffect22, useMemo as useMemo20, useState as useState15 } from "react";
29700
+ import { useEffect as useEffect22, useMemo as useMemo21, useState as useState15 } from "react";
29415
29701
 
29416
29702
  // src/hooks/useManifoldBoardBuilder.ts
29417
- import { useState as useState14, useEffect as useEffect21, useMemo as useMemo19, useRef as useRef7 } from "react";
29703
+ import { useState as useState14, useEffect as useEffect21, useMemo as useMemo20, useRef as useRef7 } from "react";
29418
29704
  import { su as su12 } from "@tscircuit/circuit-json-util";
29419
- import * as THREE23 from "three";
29705
+ import * as THREE24 from "three";
29420
29706
 
29421
29707
  // src/utils/manifold-mesh-to-three-geometry.ts
29422
- import * as THREE16 from "three";
29708
+ import * as THREE17 from "three";
29423
29709
  function manifoldMeshToThreeGeometry(manifoldMesh) {
29424
- const geometry = new THREE16.BufferGeometry();
29710
+ const geometry = new THREE17.BufferGeometry();
29425
29711
  geometry.setAttribute(
29426
29712
  "position",
29427
- new THREE16.Float32BufferAttribute(manifoldMesh.vertProperties, 3)
29713
+ new THREE17.Float32BufferAttribute(manifoldMesh.vertProperties, 3)
29428
29714
  );
29429
- geometry.setIndex(new THREE16.Uint32BufferAttribute(manifoldMesh.triVerts, 1));
29715
+ geometry.setIndex(new THREE17.Uint32BufferAttribute(manifoldMesh.triVerts, 1));
29430
29716
  if (manifoldMesh.runIndex && manifoldMesh.runIndex.length > 1 && manifoldMesh.runOriginalID) {
29431
29717
  for (let i = 0; i < manifoldMesh.runIndex.length - 1; i++) {
29432
29718
  const start = manifoldMesh.runIndex[i];
@@ -29440,7 +29726,7 @@ function manifoldMeshToThreeGeometry(manifoldMesh) {
29440
29726
  }
29441
29727
 
29442
29728
  // src/utils/trace-texture.ts
29443
- import * as THREE17 from "three";
29729
+ import * as THREE18 from "three";
29444
29730
  import { su as su5 } from "@tscircuit/circuit-json-util";
29445
29731
  function isWireRoutePoint(point2) {
29446
29732
  return point2 && point2.route_type === "wire" && typeof point2.layer === "string" && typeof point2.width === "number";
@@ -29523,10 +29809,10 @@ function createTraceTextureForLayer({
29523
29809
  }
29524
29810
  });
29525
29811
  ctx.globalCompositeOperation = "source-over";
29526
- const texture = new THREE17.CanvasTexture(canvas);
29812
+ const texture = new THREE18.CanvasTexture(canvas);
29527
29813
  texture.generateMipmaps = true;
29528
- texture.minFilter = THREE17.LinearMipmapLinearFilter;
29529
- texture.magFilter = THREE17.LinearFilter;
29814
+ texture.minFilter = THREE18.LinearMipmapLinearFilter;
29815
+ texture.magFilter = THREE18.LinearFilter;
29530
29816
  texture.anisotropy = 16;
29531
29817
  texture.needsUpdate = true;
29532
29818
  return texture;
@@ -29534,7 +29820,7 @@ function createTraceTextureForLayer({
29534
29820
 
29535
29821
  // src/utils/silkscreen-texture.ts
29536
29822
  var import_text2 = __toESM(require_text(), 1);
29537
- import * as THREE18 from "three";
29823
+ import * as THREE19 from "three";
29538
29824
  import { su as su6 } from "@tscircuit/circuit-json-util";
29539
29825
  function createSilkscreenTextureForLayer({
29540
29826
  layer,
@@ -29663,10 +29949,10 @@ function createSilkscreenTextureForLayer({
29663
29949
  ctx.stroke();
29664
29950
  });
29665
29951
  });
29666
- const texture = new THREE18.CanvasTexture(canvas);
29952
+ const texture = new THREE19.CanvasTexture(canvas);
29667
29953
  texture.generateMipmaps = true;
29668
- texture.minFilter = THREE18.LinearMipmapLinearFilter;
29669
- texture.magFilter = THREE18.LinearFilter;
29954
+ texture.minFilter = THREE19.LinearMipmapLinearFilter;
29955
+ texture.magFilter = THREE19.LinearFilter;
29670
29956
  texture.anisotropy = 16;
29671
29957
  texture.needsUpdate = true;
29672
29958
  return texture;
@@ -29850,8 +30136,8 @@ function processNonPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, m
29850
30136
 
29851
30137
  // src/utils/manifold/process-plated-holes.ts
29852
30138
  import { su as su8 } from "@tscircuit/circuit-json-util";
29853
- import * as THREE19 from "three";
29854
- var COPPER_COLOR = new THREE19.Color(...colors.copper);
30139
+ import * as THREE20 from "three";
30140
+ var COPPER_COLOR = new THREE20.Color(...colors.copper);
29855
30141
  var PLATED_HOLE_LIP_HEIGHT = 0.05;
29856
30142
  function processPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup, boardClipVolume) {
29857
30143
  const platedHoleBoardDrills = [];
@@ -30169,7 +30455,7 @@ function processPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, mani
30169
30455
 
30170
30456
  // src/utils/manifold/process-vias.ts
30171
30457
  import { su as su9 } from "@tscircuit/circuit-json-util";
30172
- import * as THREE20 from "three";
30458
+ import * as THREE21 from "three";
30173
30459
 
30174
30460
  // src/utils/via-geoms.ts
30175
30461
  function createViaCopper({
@@ -30202,7 +30488,7 @@ function createViaCopper({
30202
30488
  }
30203
30489
 
30204
30490
  // src/utils/manifold/process-vias.ts
30205
- var COPPER_COLOR2 = new THREE20.Color(...colors.copper);
30491
+ var COPPER_COLOR2 = new THREE21.Color(...colors.copper);
30206
30492
  function processViasForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup, boardClipVolume) {
30207
30493
  const viaBoardDrills = [];
30208
30494
  const pcbVias = su9(circuitJson).pcb_via.list();
@@ -30255,8 +30541,8 @@ function processViasForManifold(Manifold, circuitJson, pcbThickness, manifoldIns
30255
30541
 
30256
30542
  // src/utils/manifold/process-smt-pads.ts
30257
30543
  import { su as su10 } from "@tscircuit/circuit-json-util";
30258
- import * as THREE21 from "three";
30259
- var COPPER_COLOR3 = new THREE21.Color(...colors.copper);
30544
+ import * as THREE22 from "three";
30545
+ var COPPER_COLOR3 = new THREE22.Color(...colors.copper);
30260
30546
  function processSmtPadsForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup, holeUnion, boardClipVolume) {
30261
30547
  const smtPadGeoms = [];
30262
30548
  const smtPads = su10(circuitJson).pcb_smtpad.list();
@@ -30352,8 +30638,8 @@ function createManifoldBoard(Manifold, CrossSection, boardData, pcbThickness, ma
30352
30638
  }
30353
30639
 
30354
30640
  // src/utils/manifold/process-copper-pours.ts
30355
- import * as THREE22 from "three";
30356
- var COPPER_COLOR4 = new THREE22.Color(...colors.copper);
30641
+ import * as THREE23 from "three";
30642
+ var COPPER_COLOR4 = new THREE23.Color(...colors.copper);
30357
30643
  var arePointsClockwise4 = (points) => {
30358
30644
  let area = 0;
30359
30645
  for (let i = 0; i < points.length; i++) {
@@ -30629,7 +30915,7 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
30629
30915
  const [error, setError] = useState14(null);
30630
30916
  const [isLoading, setIsLoading] = useState14(true);
30631
30917
  const manifoldInstancesForCleanup = useRef7([]);
30632
- const boardData = useMemo19(() => {
30918
+ const boardData = useMemo20(() => {
30633
30919
  const boards = su12(circuitJson).pcb_board.list();
30634
30920
  if (boards.length === 0) {
30635
30921
  return null;
@@ -30757,7 +31043,7 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
30757
31043
  {
30758
31044
  key: "plated-holes-union",
30759
31045
  geometry: cutPlatedGeom,
30760
- color: new THREE23.Color(
31046
+ color: new THREE24.Color(
30761
31047
  colors.copper[0],
30762
31048
  colors.copper[1],
30763
31049
  colors.copper[2]
@@ -30787,7 +31073,7 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
30787
31073
  const matColorArray = boardMaterialColors[boardData.material] ?? colors.fr4Green;
30788
31074
  currentGeoms.board = {
30789
31075
  geometry: finalBoardGeom,
30790
- color: new THREE23.Color(
31076
+ color: new THREE24.Color(
30791
31077
  matColorArray[0],
30792
31078
  matColorArray[1],
30793
31079
  matColorArray[2]
@@ -30873,18 +31159,18 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
30873
31159
  };
30874
31160
 
30875
31161
  // src/utils/manifold/create-three-geometry-meshes.ts
30876
- import * as THREE25 from "three";
31162
+ import * as THREE26 from "three";
30877
31163
 
30878
31164
  // src/utils/create-board-material.ts
30879
- import * as THREE24 from "three";
30880
- var DEFAULT_SIDE = THREE24.DoubleSide;
31165
+ import * as THREE25 from "three";
31166
+ var DEFAULT_SIDE = THREE25.DoubleSide;
30881
31167
  var createBoardMaterial = ({
30882
31168
  material,
30883
31169
  color,
30884
31170
  side = DEFAULT_SIDE
30885
31171
  }) => {
30886
31172
  if (material === "fr4") {
30887
- return new THREE24.MeshPhysicalMaterial({
31173
+ return new THREE25.MeshPhysicalMaterial({
30888
31174
  color,
30889
31175
  side,
30890
31176
  metalness: 0,
@@ -30898,7 +31184,7 @@ var createBoardMaterial = ({
30898
31184
  flatShading: true
30899
31185
  });
30900
31186
  }
30901
- return new THREE24.MeshStandardMaterial({
31187
+ return new THREE25.MeshStandardMaterial({
30902
31188
  color,
30903
31189
  side,
30904
31190
  flatShading: true,
@@ -30914,12 +31200,12 @@ function createGeometryMeshes(geoms) {
30914
31200
  const meshes = [];
30915
31201
  if (!geoms) return meshes;
30916
31202
  if (geoms.board && geoms.board.geometry) {
30917
- const mesh = new THREE25.Mesh(
31203
+ const mesh = new THREE26.Mesh(
30918
31204
  geoms.board.geometry,
30919
31205
  createBoardMaterial({
30920
31206
  material: geoms.board.material,
30921
31207
  color: geoms.board.color,
30922
- side: THREE25.DoubleSide
31208
+ side: THREE26.DoubleSide
30923
31209
  })
30924
31210
  );
30925
31211
  mesh.name = "board-geom";
@@ -30928,11 +31214,11 @@ function createGeometryMeshes(geoms) {
30928
31214
  const createMeshesFromArray = (geomArray) => {
30929
31215
  if (geomArray) {
30930
31216
  geomArray.forEach((comp) => {
30931
- const mesh = new THREE25.Mesh(
31217
+ const mesh = new THREE26.Mesh(
30932
31218
  comp.geometry,
30933
- new THREE25.MeshStandardMaterial({
31219
+ new THREE26.MeshStandardMaterial({
30934
31220
  color: comp.color,
30935
- side: THREE25.DoubleSide,
31221
+ side: THREE26.DoubleSide,
30936
31222
  flatShading: true
30937
31223
  // Consistent with board
30938
31224
  })
@@ -30950,21 +31236,21 @@ function createGeometryMeshes(geoms) {
30950
31236
  }
30951
31237
 
30952
31238
  // src/utils/manifold/create-three-texture-meshes.ts
30953
- import * as THREE26 from "three";
31239
+ import * as THREE27 from "three";
30954
31240
  function createTextureMeshes(textures, boardData, pcbThickness) {
30955
31241
  const meshes = [];
30956
31242
  if (!textures || !boardData || pcbThickness === null) return meshes;
30957
31243
  const createTexturePlane = (texture, yOffset, isBottomLayer, keySuffix) => {
30958
31244
  if (!texture) return null;
30959
- const planeGeom = new THREE26.PlaneGeometry(boardData.width, boardData.height);
30960
- const material = new THREE26.MeshBasicMaterial({
31245
+ const planeGeom = new THREE27.PlaneGeometry(boardData.width, boardData.height);
31246
+ const material = new THREE27.MeshBasicMaterial({
30961
31247
  map: texture,
30962
31248
  transparent: true,
30963
- side: THREE26.DoubleSide,
31249
+ side: THREE27.DoubleSide,
30964
31250
  depthWrite: false
30965
31251
  // Important for layers to avoid z-fighting issues with board itself
30966
31252
  });
30967
- const mesh = new THREE26.Mesh(planeGeom, material);
31253
+ const mesh = new THREE27.Mesh(planeGeom, material);
30968
31254
  mesh.position.set(boardData.center.x, boardData.center.y, yOffset);
30969
31255
  if (isBottomLayer) {
30970
31256
  mesh.rotation.set(Math.PI, 0, 0);
@@ -31072,10 +31358,11 @@ var CadViewerManifold = ({
31072
31358
  autoRotateDisabled,
31073
31359
  clickToInteractEnabled,
31074
31360
  onUserInteraction,
31075
- children
31361
+ children,
31362
+ onCameraControllerReady
31076
31363
  }) => {
31077
31364
  const childrenCircuitJson = useConvertChildrenToCircuitJson(children);
31078
- const circuitJson = useMemo20(() => {
31365
+ const circuitJson = useMemo21(() => {
31079
31366
  return circuitJsonProp ?? childrenCircuitJson;
31080
31367
  }, [circuitJsonProp, childrenCircuitJson]);
31081
31368
  const [manifoldJSModule, setManifoldJSModule] = useState15(null);
@@ -31150,27 +31437,27 @@ try {
31150
31437
  isLoading: builderIsLoading,
31151
31438
  boardData
31152
31439
  } = useManifoldBoardBuilder(manifoldJSModule, circuitJson);
31153
- const geometryMeshes = useMemo20(() => createGeometryMeshes(geoms), [geoms]);
31154
- const textureMeshes = useMemo20(
31440
+ const geometryMeshes = useMemo21(() => createGeometryMeshes(geoms), [geoms]);
31441
+ const textureMeshes = useMemo21(
31155
31442
  () => createTextureMeshes(textures, boardData, pcbThickness),
31156
31443
  [textures, boardData, pcbThickness]
31157
31444
  );
31158
- const cadComponents = useMemo20(
31445
+ const cadComponents = useMemo21(
31159
31446
  () => su13(circuitJson).cad_component.list(),
31160
31447
  [circuitJson]
31161
31448
  );
31162
- const boardDimensions = useMemo20(() => {
31449
+ const boardDimensions = useMemo21(() => {
31163
31450
  if (!boardData) return void 0;
31164
31451
  const { width: width10 = 0, height: height10 = 0 } = boardData;
31165
31452
  return { width: width10, height: height10 };
31166
31453
  }, [boardData]);
31167
- const boardCenter = useMemo20(() => {
31454
+ const boardCenter = useMemo21(() => {
31168
31455
  if (!boardData) return void 0;
31169
31456
  const { center } = boardData;
31170
31457
  if (!center) return void 0;
31171
31458
  return { x: center.x, y: center.y };
31172
31459
  }, [boardData]);
31173
- const initialCameraPosition = useMemo20(() => {
31460
+ const initialCameraPosition = useMemo21(() => {
31174
31461
  if (!boardData) return [5, 5, 5];
31175
31462
  const { width: width10 = 0, height: height10 = 0 } = boardData;
31176
31463
  const safeWidth = Math.max(width10, 1);
@@ -31227,6 +31514,7 @@ try {
31227
31514
  boardDimensions,
31228
31515
  boardCenter,
31229
31516
  onUserInteraction,
31517
+ onCameraControllerReady,
31230
31518
  children: [
31231
31519
  /* @__PURE__ */ jsx16(
31232
31520
  BoardMeshes,
@@ -31256,7 +31544,7 @@ try {
31256
31544
  var CadViewerManifold_default = CadViewerManifold;
31257
31545
 
31258
31546
  // src/hooks/useContextMenu.ts
31259
- import { useState as useState16, useCallback as useCallback7, useRef as useRef8, useEffect as useEffect23 } from "react";
31547
+ import { useState as useState16, useCallback as useCallback8, useRef as useRef8, useEffect as useEffect23 } from "react";
31260
31548
  var useContextMenu = ({ containerRef }) => {
31261
31549
  const [menuVisible, setMenuVisible] = useState16(false);
31262
31550
  const [menuPos, setMenuPos] = useState16({
@@ -31273,7 +31561,7 @@ var useContextMenu = ({ containerRef }) => {
31273
31561
  longPressTimeoutRef.current = null;
31274
31562
  }
31275
31563
  };
31276
- const handleContextMenu = useCallback7(
31564
+ const handleContextMenu = useCallback8(
31277
31565
  (e) => {
31278
31566
  e.preventDefault();
31279
31567
  const eventX = typeof e.clientX === "number" ? e.clientX : 0;
@@ -31299,7 +31587,7 @@ var useContextMenu = ({ containerRef }) => {
31299
31587
  },
31300
31588
  [setMenuPos, setMenuVisible]
31301
31589
  );
31302
- const handleTouchStart = useCallback7(
31590
+ const handleTouchStart = useCallback8(
31303
31591
  (e) => {
31304
31592
  if (e.touches.length === 1) {
31305
31593
  const touch = e.touches[0];
@@ -31332,7 +31620,7 @@ var useContextMenu = ({ containerRef }) => {
31332
31620
  },
31333
31621
  [containerRef]
31334
31622
  );
31335
- const handleTouchMove = useCallback7((e) => {
31623
+ const handleTouchMove = useCallback8((e) => {
31336
31624
  if (!interactionOriginPosRef.current || e.touches.length !== 1) {
31337
31625
  return;
31338
31626
  }
@@ -31350,7 +31638,7 @@ var useContextMenu = ({ containerRef }) => {
31350
31638
  clearLongPressTimeout();
31351
31639
  }
31352
31640
  }, []);
31353
- const handleTouchEnd = useCallback7(() => {
31641
+ const handleTouchEnd = useCallback8(() => {
31354
31642
  clearLongPressTimeout();
31355
31643
  setTimeout(() => {
31356
31644
  if (interactionOriginPosRef.current) {
@@ -31358,7 +31646,7 @@ var useContextMenu = ({ containerRef }) => {
31358
31646
  }
31359
31647
  }, 0);
31360
31648
  }, []);
31361
- const handleClickAway = useCallback7((e) => {
31649
+ const handleClickAway = useCallback8((e) => {
31362
31650
  const target = e.target;
31363
31651
  if (menuRef.current && !menuRef.current.contains(target)) {
31364
31652
  setMenuVisible(false);
@@ -31398,10 +31686,10 @@ var useContextMenu = ({ containerRef }) => {
31398
31686
  };
31399
31687
 
31400
31688
  // src/hooks/useGlobalDownloadGltf.ts
31401
- import { useCallback as useCallback8 } from "react";
31689
+ import { useCallback as useCallback9 } from "react";
31402
31690
  import { GLTFExporter } from "three-stdlib";
31403
31691
  var useGlobalDownloadGltf = () => {
31404
- return useCallback8(() => {
31692
+ return useCallback9(() => {
31405
31693
  const root = window.__TSCIRCUIT_THREE_OBJECT;
31406
31694
  if (!root) return;
31407
31695
  const exporter = new GLTFExporter();
@@ -31426,6 +31714,9 @@ var useGlobalDownloadGltf = () => {
31426
31714
  }, []);
31427
31715
  };
31428
31716
 
31717
+ // src/components/ContextMenu.tsx
31718
+ import { useState as useState18, useCallback as useCallback10 } from "react";
31719
+
31429
31720
  // src/components/AppearanceMenu.tsx
31430
31721
  import { useState as useState17 } from "react";
31431
31722
  import { Fragment as Fragment6, jsx as jsx17, jsxs as jsxs8 } from "react/jsx-runtime";
@@ -31602,14 +31893,258 @@ var AppearanceMenu = () => {
31602
31893
  ] });
31603
31894
  };
31604
31895
 
31605
- // src/CadViewer.tsx
31896
+ // src/components/ContextMenu.tsx
31606
31897
  import { jsx as jsx18, jsxs as jsxs9 } from "react/jsx-runtime";
31898
+ var cameraOptions = [
31899
+ "Custom",
31900
+ "Top Centered",
31901
+ "Top Down",
31902
+ "Top Left Corner",
31903
+ "Top Right Corner",
31904
+ "Left Sideview",
31905
+ "Right Sideview",
31906
+ "Front"
31907
+ ];
31908
+ var ContextMenu = ({
31909
+ menuRef,
31910
+ menuPos,
31911
+ engine,
31912
+ cameraPreset,
31913
+ autoRotate,
31914
+ onEngineSwitch,
31915
+ onCameraPresetSelect,
31916
+ onAutoRotateToggle,
31917
+ onDownloadGltf
31918
+ }) => {
31919
+ const [activeSubmenu, setActiveSubmenu] = useState18(null);
31920
+ const handleMenuItemHover = useCallback10(
31921
+ (event, hovered) => {
31922
+ event.currentTarget.style.background = hovered ? "#2d313a" : "transparent";
31923
+ },
31924
+ []
31925
+ );
31926
+ return /* @__PURE__ */ jsxs9(
31927
+ "div",
31928
+ {
31929
+ ref: menuRef,
31930
+ style: {
31931
+ position: "fixed",
31932
+ top: menuPos.y,
31933
+ left: menuPos.x,
31934
+ background: "#23272f",
31935
+ color: "#f5f6fa",
31936
+ borderRadius: 6,
31937
+ boxShadow: "0 6px 24px 0 rgba(0,0,0,0.18)",
31938
+ zIndex: 1e3,
31939
+ minWidth: 200,
31940
+ border: "1px solid #353945",
31941
+ padding: 0,
31942
+ fontSize: 15,
31943
+ fontWeight: 500,
31944
+ transition: "opacity 0.1s"
31945
+ },
31946
+ children: [
31947
+ /* @__PURE__ */ jsxs9(
31948
+ "div",
31949
+ {
31950
+ style: {
31951
+ padding: "12px 18px",
31952
+ cursor: "pointer",
31953
+ display: "flex",
31954
+ alignItems: "center",
31955
+ gap: 10,
31956
+ color: "#f5f6fa",
31957
+ fontWeight: 500,
31958
+ borderRadius: 6,
31959
+ transition: "background 0.1s"
31960
+ },
31961
+ onClick: () => onEngineSwitch(engine === "jscad" ? "manifold" : "jscad"),
31962
+ onMouseOver: (event) => handleMenuItemHover(event, true),
31963
+ onMouseOut: (event) => handleMenuItemHover(event, false),
31964
+ children: [
31965
+ "Switch to ",
31966
+ engine === "jscad" ? "Manifold" : "JSCAD",
31967
+ " Engine",
31968
+ /* @__PURE__ */ jsx18(
31969
+ "span",
31970
+ {
31971
+ style: {
31972
+ fontSize: 12,
31973
+ marginLeft: "auto",
31974
+ opacity: 0.5,
31975
+ fontWeight: 400
31976
+ },
31977
+ children: engine === "jscad" ? "experimental" : "default"
31978
+ }
31979
+ )
31980
+ ]
31981
+ }
31982
+ ),
31983
+ /* @__PURE__ */ jsxs9(
31984
+ "div",
31985
+ {
31986
+ style: { position: "relative" },
31987
+ onMouseEnter: () => setActiveSubmenu("camera"),
31988
+ onMouseLeave: () => setActiveSubmenu(null),
31989
+ children: [
31990
+ /* @__PURE__ */ jsxs9(
31991
+ "div",
31992
+ {
31993
+ style: {
31994
+ padding: "10px 18px",
31995
+ cursor: "pointer",
31996
+ display: "flex",
31997
+ alignItems: "center",
31998
+ gap: 10,
31999
+ color: "#f5f6fa",
32000
+ fontWeight: 500,
32001
+ borderRadius: 6,
32002
+ transition: "background 0.1s",
32003
+ background: activeSubmenu === "camera" ? "#2d313a" : "transparent"
32004
+ },
32005
+ onClick: () => setActiveSubmenu(
32006
+ (current2) => current2 === "camera" ? null : "camera"
32007
+ ),
32008
+ children: [
32009
+ "Camera Position",
32010
+ /* @__PURE__ */ jsx18("span", { style: { marginLeft: "auto", opacity: 0.75 }, children: cameraPreset }),
32011
+ /* @__PURE__ */ jsx18("span", { style: { marginLeft: 4, opacity: 0.5 }, children: "\u203A" })
32012
+ ]
32013
+ }
32014
+ ),
32015
+ activeSubmenu === "camera" && /* @__PURE__ */ jsx18(
32016
+ "div",
32017
+ {
32018
+ style: {
32019
+ position: "absolute",
32020
+ top: 0,
32021
+ left: "100%",
32022
+ marginLeft: -2,
32023
+ background: "#23272f",
32024
+ color: "#f5f6fa",
32025
+ borderRadius: 6,
32026
+ boxShadow: "0 6px 24px 0 rgba(0,0,0,0.18)",
32027
+ border: "1px solid #353945",
32028
+ minWidth: 200,
32029
+ padding: "6px 0",
32030
+ zIndex: 1001
32031
+ },
32032
+ children: cameraOptions.map((option) => /* @__PURE__ */ jsxs9(
32033
+ "div",
32034
+ {
32035
+ style: {
32036
+ padding: "10px 18px",
32037
+ cursor: "pointer",
32038
+ display: "flex",
32039
+ alignItems: "center",
32040
+ gap: 10,
32041
+ color: "#f5f6fa",
32042
+ fontWeight: 500,
32043
+ borderRadius: 6,
32044
+ transition: "background 0.1s"
32045
+ },
32046
+ onClick: () => onCameraPresetSelect(option),
32047
+ onMouseOver: (event) => handleMenuItemHover(event, true),
32048
+ onMouseOut: (event) => handleMenuItemHover(event, false),
32049
+ children: [
32050
+ /* @__PURE__ */ jsx18("span", { style: { width: 18 }, children: cameraPreset === option ? "\u2714" : "" }),
32051
+ option
32052
+ ]
32053
+ },
32054
+ option
32055
+ ))
32056
+ }
32057
+ )
32058
+ ]
32059
+ }
32060
+ ),
32061
+ /* @__PURE__ */ jsxs9(
32062
+ "div",
32063
+ {
32064
+ style: {
32065
+ padding: "12px 18px",
32066
+ cursor: "pointer",
32067
+ display: "flex",
32068
+ alignItems: "center",
32069
+ gap: 10,
32070
+ color: "#f5f6fa",
32071
+ fontWeight: 500,
32072
+ borderRadius: 6,
32073
+ transition: "background 0.1s"
32074
+ },
32075
+ onClick: onAutoRotateToggle,
32076
+ onMouseOver: (event) => handleMenuItemHover(event, true),
32077
+ onMouseOut: (event) => handleMenuItemHover(event, false),
32078
+ children: [
32079
+ /* @__PURE__ */ jsx18("span", { style: { marginRight: 8 }, children: autoRotate ? "\u2714" : "" }),
32080
+ "Auto rotate"
32081
+ ]
32082
+ }
32083
+ ),
32084
+ /* @__PURE__ */ jsx18(
32085
+ "div",
32086
+ {
32087
+ style: {
32088
+ padding: "12px 18px",
32089
+ cursor: "pointer",
32090
+ display: "flex",
32091
+ alignItems: "center",
32092
+ gap: 10,
32093
+ color: "#f5f6fa",
32094
+ fontWeight: 500,
32095
+ borderRadius: 6,
32096
+ transition: "background 0.1s"
32097
+ },
32098
+ onClick: onDownloadGltf,
32099
+ onMouseOver: (event) => handleMenuItemHover(event, true),
32100
+ onMouseOut: (event) => handleMenuItemHover(event, false),
32101
+ children: "Download GLTF"
32102
+ }
32103
+ ),
32104
+ /* @__PURE__ */ jsx18(AppearanceMenu, {}),
32105
+ /* @__PURE__ */ jsx18(
32106
+ "div",
32107
+ {
32108
+ style: {
32109
+ display: "flex",
32110
+ justifyContent: "center",
32111
+ padding: "8px 0",
32112
+ borderTop: "1px solid rgba(255, 255, 255, 0.1)",
32113
+ marginTop: "8px"
32114
+ },
32115
+ children: /* @__PURE__ */ jsxs9(
32116
+ "span",
32117
+ {
32118
+ style: {
32119
+ fontSize: 10,
32120
+ opacity: 0.6,
32121
+ fontWeight: 300,
32122
+ color: "#c0c0c0"
32123
+ },
32124
+ children: [
32125
+ "@tscircuit/3d-viewer@",
32126
+ package_default.version
32127
+ ]
32128
+ }
32129
+ )
32130
+ }
32131
+ )
32132
+ ]
32133
+ }
32134
+ );
32135
+ };
32136
+
32137
+ // src/CadViewer.tsx
32138
+ import { jsx as jsx19, jsxs as jsxs10 } from "react/jsx-runtime";
31607
32139
  var CadViewerInner = (props) => {
31608
- const [engine, setEngine] = useState18("manifold");
32140
+ const [engine, setEngine] = useState19("manifold");
31609
32141
  const containerRef = useRef9(null);
31610
- const [autoRotate, setAutoRotate] = useState18(true);
31611
- const [autoRotateUserToggled, setAutoRotateUserToggled] = useState18(false);
32142
+ const [autoRotate, setAutoRotate] = useState19(true);
32143
+ const [autoRotateUserToggled, setAutoRotateUserToggled] = useState19(false);
32144
+ const [cameraPreset, setCameraPreset] = useState19("Custom");
31612
32145
  const { visibility, toggleLayer } = useLayerVisibility();
32146
+ const cameraControllerRef = useRef9(null);
32147
+ const externalCameraControllerReady = props.onCameraControllerReady;
31613
32148
  const {
31614
32149
  menuVisible,
31615
32150
  menuPos,
@@ -31619,20 +32154,39 @@ var CadViewerInner = (props) => {
31619
32154
  } = useContextMenu({ containerRef });
31620
32155
  const autoRotateUserToggledRef = useRef9(autoRotateUserToggled);
31621
32156
  autoRotateUserToggledRef.current = autoRotateUserToggled;
31622
- const handleUserInteraction = useCallback9(() => {
32157
+ const handleUserInteraction = useCallback11(() => {
31623
32158
  if (!autoRotateUserToggledRef.current) {
31624
32159
  setAutoRotate(false);
31625
32160
  }
32161
+ setCameraPreset("Custom");
31626
32162
  }, []);
31627
- const toggleAutoRotate = useCallback9(() => {
32163
+ const toggleAutoRotate = useCallback11(() => {
31628
32164
  setAutoRotate((prev) => !prev);
31629
32165
  setAutoRotateUserToggled(true);
31630
32166
  }, []);
31631
32167
  const downloadGltf = useGlobalDownloadGltf();
31632
- const handleMenuClick = (newEngine) => {
31633
- setEngine(newEngine);
32168
+ const closeMenu = useCallback11(() => {
31634
32169
  setMenuVisible(false);
31635
- };
32170
+ }, [setMenuVisible]);
32171
+ const handleCameraControllerReady = useCallback11(
32172
+ (controller) => {
32173
+ cameraControllerRef.current = controller;
32174
+ externalCameraControllerReady?.(controller);
32175
+ if (controller && cameraPreset !== "Custom") {
32176
+ controller.animateToPreset(cameraPreset);
32177
+ }
32178
+ },
32179
+ [cameraPreset, externalCameraControllerReady]
32180
+ );
32181
+ const handleCameraPresetSelect = useCallback11(
32182
+ (preset) => {
32183
+ setCameraPreset(preset);
32184
+ closeMenu();
32185
+ if (preset === "Custom") return;
32186
+ cameraControllerRef.current?.animateToPreset(preset);
32187
+ },
32188
+ [closeMenu]
32189
+ );
31636
32190
  useEffect24(() => {
31637
32191
  const stored = window.localStorage.getItem("cadViewerEngine");
31638
32192
  if (stored === "jscad" || stored === "manifold") {
@@ -31643,7 +32197,7 @@ var CadViewerInner = (props) => {
31643
32197
  window.localStorage.setItem("cadViewerEngine", engine);
31644
32198
  }, [engine]);
31645
32199
  const viewerKey = props.circuitJson ? JSON.stringify(props.circuitJson) : void 0;
31646
- return /* @__PURE__ */ jsxs9(
32200
+ return /* @__PURE__ */ jsxs10(
31647
32201
  "div",
31648
32202
  {
31649
32203
  ref: containerRef,
@@ -31659,22 +32213,24 @@ var CadViewerInner = (props) => {
31659
32213
  },
31660
32214
  ...contextMenuEventHandlers,
31661
32215
  children: [
31662
- engine === "jscad" ? /* @__PURE__ */ jsx18(
32216
+ engine === "jscad" ? /* @__PURE__ */ jsx19(
31663
32217
  CadViewerJscad,
31664
32218
  {
31665
32219
  ...props,
31666
32220
  autoRotateDisabled: props.autoRotateDisabled || !autoRotate,
31667
- onUserInteraction: handleUserInteraction
32221
+ onUserInteraction: handleUserInteraction,
32222
+ onCameraControllerReady: handleCameraControllerReady
31668
32223
  }
31669
- ) : /* @__PURE__ */ jsx18(
32224
+ ) : /* @__PURE__ */ jsx19(
31670
32225
  CadViewerManifold_default,
31671
32226
  {
31672
32227
  ...props,
31673
32228
  autoRotateDisabled: props.autoRotateDisabled || !autoRotate,
31674
- onUserInteraction: handleUserInteraction
32229
+ onUserInteraction: handleUserInteraction,
32230
+ onCameraControllerReady: handleCameraControllerReady
31675
32231
  }
31676
32232
  ),
31677
- /* @__PURE__ */ jsxs9(
32233
+ /* @__PURE__ */ jsxs10(
31678
32234
  "div",
31679
32235
  {
31680
32236
  style: {
@@ -31691,145 +32247,31 @@ var CadViewerInner = (props) => {
31691
32247
  },
31692
32248
  children: [
31693
32249
  "Engine: ",
31694
- /* @__PURE__ */ jsx18("b", { children: engine === "jscad" ? "JSCAD" : "Manifold" })
32250
+ /* @__PURE__ */ jsx19("b", { children: engine === "jscad" ? "JSCAD" : "Manifold" })
31695
32251
  ]
31696
32252
  }
31697
32253
  ),
31698
- menuVisible && /* @__PURE__ */ jsxs9(
31699
- "div",
32254
+ menuVisible && /* @__PURE__ */ jsx19(
32255
+ ContextMenu,
31700
32256
  {
31701
- ref: menuRef,
31702
- style: {
31703
- position: "fixed",
31704
- top: menuPos.y,
31705
- left: menuPos.x,
31706
- background: "#23272f",
31707
- color: "#f5f6fa",
31708
- borderRadius: 6,
31709
- boxShadow: "0 6px 24px 0 rgba(0,0,0,0.18)",
31710
- zIndex: 1e3,
31711
- minWidth: 200,
31712
- border: "1px solid #353945",
31713
- padding: 0,
31714
- fontSize: 15,
31715
- fontWeight: 500,
31716
- transition: "opacity 0.1s"
32257
+ menuRef,
32258
+ menuPos,
32259
+ engine,
32260
+ cameraPreset,
32261
+ autoRotate,
32262
+ onEngineSwitch: (newEngine) => {
32263
+ setEngine(newEngine);
32264
+ closeMenu();
31717
32265
  },
31718
- children: [
31719
- /* @__PURE__ */ jsxs9(
31720
- "div",
31721
- {
31722
- style: {
31723
- padding: "12px 18px",
31724
- cursor: "pointer",
31725
- display: "flex",
31726
- alignItems: "center",
31727
- gap: 10,
31728
- color: "#f5f6fa",
31729
- fontWeight: 500,
31730
- borderRadius: 6,
31731
- transition: "background 0.1s"
31732
- },
31733
- onClick: () => handleMenuClick(engine === "jscad" ? "manifold" : "jscad"),
31734
- onMouseOver: (e) => e.currentTarget.style.background = "#2d313a",
31735
- onMouseOut: (e) => e.currentTarget.style.background = "transparent",
31736
- children: [
31737
- "Switch to ",
31738
- engine === "jscad" ? "Manifold" : "JSCAD",
31739
- " Engine",
31740
- /* @__PURE__ */ jsx18(
31741
- "span",
31742
- {
31743
- style: {
31744
- fontSize: 12,
31745
- marginLeft: "auto",
31746
- opacity: 0.5,
31747
- fontWeight: 400
31748
- },
31749
- children: engine === "jscad" ? "experimental" : "default"
31750
- }
31751
- )
31752
- ]
31753
- }
31754
- ),
31755
- /* @__PURE__ */ jsxs9(
31756
- "div",
31757
- {
31758
- style: {
31759
- padding: "12px 18px",
31760
- cursor: "pointer",
31761
- display: "flex",
31762
- alignItems: "center",
31763
- gap: 10,
31764
- color: "#f5f6fa",
31765
- fontWeight: 500,
31766
- borderRadius: 6,
31767
- transition: "background 0.1s"
31768
- },
31769
- onClick: () => {
31770
- toggleAutoRotate();
31771
- setMenuVisible(false);
31772
- },
31773
- onMouseOver: (e) => e.currentTarget.style.background = "#2d313a",
31774
- onMouseOut: (e) => e.currentTarget.style.background = "transparent",
31775
- children: [
31776
- /* @__PURE__ */ jsx18("span", { style: { marginRight: 8 }, children: autoRotate ? "\u2714" : "" }),
31777
- "Auto rotate"
31778
- ]
31779
- }
31780
- ),
31781
- /* @__PURE__ */ jsx18(
31782
- "div",
31783
- {
31784
- style: {
31785
- padding: "12px 18px",
31786
- cursor: "pointer",
31787
- display: "flex",
31788
- alignItems: "center",
31789
- gap: 10,
31790
- color: "#f5f6fa",
31791
- fontWeight: 500,
31792
- borderRadius: 6,
31793
- transition: "background 0.1s"
31794
- },
31795
- onClick: () => {
31796
- downloadGltf();
31797
- setMenuVisible(false);
31798
- },
31799
- onMouseOver: (e) => e.currentTarget.style.background = "#2d313a",
31800
- onMouseOut: (e) => e.currentTarget.style.background = "transparent",
31801
- children: "Download GLTF"
31802
- }
31803
- ),
31804
- /* @__PURE__ */ jsx18(AppearanceMenu, {}),
31805
- /* @__PURE__ */ jsx18(
31806
- "div",
31807
- {
31808
- style: {
31809
- display: "flex",
31810
- justifyContent: "center",
31811
- padding: "8px 0",
31812
- borderTop: "1px solid rgba(255, 255, 255, 0.1)",
31813
- marginTop: "8px"
31814
- },
31815
- children: /* @__PURE__ */ jsxs9(
31816
- "span",
31817
- {
31818
- style: {
31819
- fontSize: 10,
31820
- opacity: 0.6,
31821
- fontWeight: 300,
31822
- color: "#c0c0c0"
31823
- },
31824
- children: [
31825
- "@tscircuit/3d-viewer@",
31826
- package_default.version
31827
- ]
31828
- }
31829
- )
31830
- }
31831
- )
31832
- ]
32266
+ onCameraPresetSelect: handleCameraPresetSelect,
32267
+ onAutoRotateToggle: () => {
32268
+ toggleAutoRotate();
32269
+ closeMenu();
32270
+ },
32271
+ onDownloadGltf: () => {
32272
+ downloadGltf();
32273
+ closeMenu();
32274
+ }
31833
32275
  }
31834
32276
  )
31835
32277
  ]
@@ -31838,17 +32280,17 @@ var CadViewerInner = (props) => {
31838
32280
  );
31839
32281
  };
31840
32282
  var CadViewer = (props) => {
31841
- return /* @__PURE__ */ jsx18(LayerVisibilityProvider, { children: /* @__PURE__ */ jsx18(CadViewerInner, { ...props }) });
32283
+ return /* @__PURE__ */ jsx19(LayerVisibilityProvider, { children: /* @__PURE__ */ jsx19(CadViewerInner, { ...props }) });
31842
32284
  };
31843
32285
 
31844
32286
  // src/convert-circuit-json-to-3d-svg.ts
31845
32287
  var import_debug = __toESM(require_browser(), 1);
31846
32288
  import { su as su14 } from "@tscircuit/circuit-json-util";
31847
- import * as THREE30 from "three";
32289
+ import * as THREE31 from "three";
31848
32290
  import { SVGRenderer } from "three/examples/jsm/renderers/SVGRenderer.js";
31849
32291
 
31850
32292
  // src/utils/create-geometry-from-polygons.ts
31851
- import * as THREE27 from "three";
32293
+ import * as THREE28 from "three";
31852
32294
  import { BufferGeometry as BufferGeometry3, Float32BufferAttribute as Float32BufferAttribute2 } from "three";
31853
32295
  function createGeometryFromPolygons(polygons) {
31854
32296
  const geometry = new BufferGeometry3();
@@ -31862,12 +32304,12 @@ function createGeometryFromPolygons(polygons) {
31862
32304
  ...polygon3.vertices[i + 1]
31863
32305
  // Third vertex
31864
32306
  );
31865
- const v1 = new THREE27.Vector3(...polygon3.vertices[0]);
31866
- const v2 = new THREE27.Vector3(...polygon3.vertices[i]);
31867
- const v3 = new THREE27.Vector3(...polygon3.vertices[i + 1]);
31868
- const normal = new THREE27.Vector3().crossVectors(
31869
- new THREE27.Vector3().subVectors(v2, v1),
31870
- new THREE27.Vector3().subVectors(v3, v1)
32307
+ const v1 = new THREE28.Vector3(...polygon3.vertices[0]);
32308
+ const v2 = new THREE28.Vector3(...polygon3.vertices[i]);
32309
+ const v3 = new THREE28.Vector3(...polygon3.vertices[i + 1]);
32310
+ const normal = new THREE28.Vector3().crossVectors(
32311
+ new THREE28.Vector3().subVectors(v2, v1),
32312
+ new THREE28.Vector3().subVectors(v3, v1)
31871
32313
  ).normalize();
31872
32314
  normals.push(
31873
32315
  normal.x,
@@ -31891,10 +32333,10 @@ function createGeometryFromPolygons(polygons) {
31891
32333
  var import_modeling2 = __toESM(require_src(), 1);
31892
32334
  var import_jscad_planner2 = __toESM(require_dist(), 1);
31893
32335
  var jscadModeling2 = __toESM(require_src(), 1);
31894
- import * as THREE29 from "three";
32336
+ import * as THREE30 from "three";
31895
32337
 
31896
32338
  // src/utils/load-model.ts
31897
- import * as THREE28 from "three";
32339
+ import * as THREE29 from "three";
31898
32340
  import { GLTFLoader as GLTFLoader2 } from "three/examples/jsm/loaders/GLTFLoader.js";
31899
32341
  import { OBJLoader as OBJLoader2 } from "three/examples/jsm/loaders/OBJLoader.js";
31900
32342
  import { STLLoader as STLLoader2 } from "three/examples/jsm/loaders/STLLoader.js";
@@ -31902,12 +32344,12 @@ async function load3DModel(url) {
31902
32344
  if (url.endsWith(".stl")) {
31903
32345
  const loader = new STLLoader2();
31904
32346
  const geometry = await loader.loadAsync(url);
31905
- const material = new THREE28.MeshStandardMaterial({
32347
+ const material = new THREE29.MeshStandardMaterial({
31906
32348
  color: 8947848,
31907
32349
  metalness: 0.5,
31908
32350
  roughness: 0.5
31909
32351
  });
31910
- return new THREE28.Mesh(geometry, material);
32352
+ return new THREE29.Mesh(geometry, material);
31911
32353
  }
31912
32354
  if (url.endsWith(".obj")) {
31913
32355
  const loader = new OBJLoader2();
@@ -31940,9 +32382,9 @@ async function renderComponent(component, scene) {
31940
32382
  }
31941
32383
  if (component.rotation) {
31942
32384
  model.rotation.set(
31943
- THREE29.MathUtils.degToRad(component.rotation.x ?? 0),
31944
- THREE29.MathUtils.degToRad(component.rotation.y ?? 0),
31945
- THREE29.MathUtils.degToRad(component.rotation.z ?? 0)
32385
+ THREE30.MathUtils.degToRad(component.rotation.x ?? 0),
32386
+ THREE30.MathUtils.degToRad(component.rotation.y ?? 0),
32387
+ THREE30.MathUtils.degToRad(component.rotation.z ?? 0)
31946
32388
  );
31947
32389
  }
31948
32390
  scene.add(model);
@@ -31956,13 +32398,13 @@ async function renderComponent(component, scene) {
31956
32398
  );
31957
32399
  if (jscadObject && (jscadObject.polygons || jscadObject.sides)) {
31958
32400
  const threeGeom = convertCSGToThreeGeom(jscadObject);
31959
- const material2 = new THREE29.MeshStandardMaterial({
32401
+ const material2 = new THREE30.MeshStandardMaterial({
31960
32402
  color: 8947848,
31961
32403
  metalness: 0.5,
31962
32404
  roughness: 0.5,
31963
- side: THREE29.DoubleSide
32405
+ side: THREE30.DoubleSide
31964
32406
  });
31965
- const mesh2 = new THREE29.Mesh(threeGeom, material2);
32407
+ const mesh2 = new THREE30.Mesh(threeGeom, material2);
31966
32408
  if (component.position) {
31967
32409
  mesh2.position.set(
31968
32410
  component.position.x ?? 0,
@@ -31972,9 +32414,9 @@ async function renderComponent(component, scene) {
31972
32414
  }
31973
32415
  if (component.rotation) {
31974
32416
  mesh2.rotation.set(
31975
- THREE29.MathUtils.degToRad(component.rotation.x ?? 0),
31976
- THREE29.MathUtils.degToRad(component.rotation.y ?? 0),
31977
- THREE29.MathUtils.degToRad(component.rotation.z ?? 0)
32417
+ THREE30.MathUtils.degToRad(component.rotation.x ?? 0),
32418
+ THREE30.MathUtils.degToRad(component.rotation.y ?? 0),
32419
+ THREE30.MathUtils.degToRad(component.rotation.z ?? 0)
31978
32420
  );
31979
32421
  }
31980
32422
  scene.add(mesh2);
@@ -31991,17 +32433,17 @@ async function renderComponent(component, scene) {
31991
32433
  if (!geom || !geom.polygons && !geom.sides) {
31992
32434
  continue;
31993
32435
  }
31994
- const color = new THREE29.Color(geomInfo.color);
32436
+ const color = new THREE30.Color(geomInfo.color);
31995
32437
  color.convertLinearToSRGB();
31996
32438
  const geomWithColor = { ...geom, color: [color.r, color.g, color.b] };
31997
32439
  const threeGeom = convertCSGToThreeGeom(geomWithColor);
31998
- const material2 = new THREE29.MeshStandardMaterial({
32440
+ const material2 = new THREE30.MeshStandardMaterial({
31999
32441
  vertexColors: true,
32000
32442
  metalness: 0.2,
32001
32443
  roughness: 0.8,
32002
- side: THREE29.DoubleSide
32444
+ side: THREE30.DoubleSide
32003
32445
  });
32004
- const mesh2 = new THREE29.Mesh(threeGeom, material2);
32446
+ const mesh2 = new THREE30.Mesh(threeGeom, material2);
32005
32447
  if (component.position) {
32006
32448
  mesh2.position.set(
32007
32449
  component.position.x ?? 0,
@@ -32011,22 +32453,22 @@ async function renderComponent(component, scene) {
32011
32453
  }
32012
32454
  if (component.rotation) {
32013
32455
  mesh2.rotation.set(
32014
- THREE29.MathUtils.degToRad(component.rotation.x ?? 0),
32015
- THREE29.MathUtils.degToRad(component.rotation.y ?? 0),
32016
- THREE29.MathUtils.degToRad(component.rotation.z ?? 0)
32456
+ THREE30.MathUtils.degToRad(component.rotation.x ?? 0),
32457
+ THREE30.MathUtils.degToRad(component.rotation.y ?? 0),
32458
+ THREE30.MathUtils.degToRad(component.rotation.z ?? 0)
32017
32459
  );
32018
32460
  }
32019
32461
  scene.add(mesh2);
32020
32462
  }
32021
32463
  return;
32022
32464
  }
32023
- const geometry = new THREE29.BoxGeometry(0.5, 0.5, 0.5);
32024
- const material = new THREE29.MeshStandardMaterial({
32465
+ const geometry = new THREE30.BoxGeometry(0.5, 0.5, 0.5);
32466
+ const material = new THREE30.MeshStandardMaterial({
32025
32467
  color: 16711680,
32026
32468
  transparent: true,
32027
32469
  opacity: 0.25
32028
32470
  });
32029
- const mesh = new THREE29.Mesh(geometry, material);
32471
+ const mesh = new THREE30.Mesh(geometry, material);
32030
32472
  if (component.position) {
32031
32473
  mesh.position.set(
32032
32474
  component.position.x ?? 0,
@@ -32047,11 +32489,11 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
32047
32489
  padding = 20,
32048
32490
  zoom = 1.5
32049
32491
  } = options;
32050
- const scene = new THREE30.Scene();
32492
+ const scene = new THREE31.Scene();
32051
32493
  const renderer = new SVGRenderer();
32052
32494
  renderer.setSize(width10, height10);
32053
- renderer.setClearColor(new THREE30.Color(backgroundColor), 1);
32054
- const camera = new THREE30.OrthographicCamera();
32495
+ renderer.setClearColor(new THREE31.Color(backgroundColor), 1);
32496
+ const camera = new THREE31.OrthographicCamera();
32055
32497
  const aspect = width10 / height10;
32056
32498
  const frustumSize = 100;
32057
32499
  const halfFrustumSize = frustumSize / 2 / zoom;
@@ -32065,11 +32507,11 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
32065
32507
  camera.position.set(position.x, position.y, position.z);
32066
32508
  camera.up.set(0, 1, 0);
32067
32509
  const lookAt = options.camera?.lookAt ?? { x: 0, y: 0, z: 0 };
32068
- camera.lookAt(new THREE30.Vector3(lookAt.x, lookAt.y, lookAt.z));
32510
+ camera.lookAt(new THREE31.Vector3(lookAt.x, lookAt.y, lookAt.z));
32069
32511
  camera.updateProjectionMatrix();
32070
- const ambientLight = new THREE30.AmbientLight(16777215, Math.PI / 2);
32512
+ const ambientLight = new THREE31.AmbientLight(16777215, Math.PI / 2);
32071
32513
  scene.add(ambientLight);
32072
- const pointLight = new THREE30.PointLight(16777215, Math.PI / 4);
32514
+ const pointLight = new THREE31.PointLight(16777215, Math.PI / 4);
32073
32515
  pointLight.position.set(-10, -10, 10);
32074
32516
  scene.add(pointLight);
32075
32517
  const components = su14(circuitJson).cad_component.list();
@@ -32083,7 +32525,7 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
32083
32525
  const g = geom;
32084
32526
  if (!g.polygons || g.polygons.length === 0) continue;
32085
32527
  const geometry = createGeometryFromPolygons(g.polygons);
32086
- const baseColor = new THREE30.Color(
32528
+ const baseColor = new THREE31.Color(
32087
32529
  g.color?.[0] ?? 0,
32088
32530
  g.color?.[1] ?? 0,
32089
32531
  g.color?.[2] ?? 0
@@ -32091,18 +32533,18 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
32091
32533
  const material = createBoardMaterial({
32092
32534
  material: boardData?.material,
32093
32535
  color: baseColor,
32094
- side: THREE30.DoubleSide
32536
+ side: THREE31.DoubleSide
32095
32537
  });
32096
- const mesh = new THREE30.Mesh(geometry, material);
32538
+ const mesh = new THREE31.Mesh(geometry, material);
32097
32539
  scene.add(mesh);
32098
32540
  }
32099
32541
  }
32100
- const gridHelper = new THREE30.GridHelper(100, 100);
32542
+ const gridHelper = new THREE31.GridHelper(100, 100);
32101
32543
  gridHelper.rotation.x = Math.PI / 2;
32102
32544
  scene.add(gridHelper);
32103
- const box = new THREE30.Box3().setFromObject(scene);
32104
- const center = box.getCenter(new THREE30.Vector3());
32105
- const size2 = box.getSize(new THREE30.Vector3());
32545
+ const box = new THREE31.Box3().setFromObject(scene);
32546
+ const center = box.getCenter(new THREE31.Vector3());
32547
+ const size2 = box.getSize(new THREE31.Vector3());
32106
32548
  scene.position.sub(center);
32107
32549
  const maxDim = Math.max(size2.x, size2.y, size2.z);
32108
32550
  if (maxDim > 0) {
@@ -32120,10 +32562,10 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
32120
32562
 
32121
32563
  // src/hooks/exporter/gltf.ts
32122
32564
  import { GLTFExporter as GLTFExporter2 } from "three-stdlib";
32123
- import { useEffect as useEffect25, useState as useState19, useMemo as useMemo21, useCallback as useCallback10 } from "react";
32565
+ import { useEffect as useEffect25, useState as useState20, useMemo as useMemo22, useCallback as useCallback12 } from "react";
32124
32566
  function useSaveGltfAs(options = {}) {
32125
32567
  const parse = useParser(options);
32126
- const link = useMemo21(() => document.createElement("a"), []);
32568
+ const link = useMemo22(() => document.createElement("a"), []);
32127
32569
  const saveAs = async (filename) => {
32128
32570
  const name = filename ?? options.filename ?? "";
32129
32571
  if (options.binary == null) options.binary = name.endsWith(".glb");
@@ -32141,16 +32583,16 @@ function useSaveGltfAs(options = {}) {
32141
32583
  []
32142
32584
  );
32143
32585
  let instance;
32144
- const ref = useCallback10((obj3D) => {
32586
+ const ref = useCallback12((obj3D) => {
32145
32587
  instance = obj3D;
32146
32588
  }, []);
32147
32589
  return [ref, saveAs];
32148
32590
  }
32149
32591
  function useExportGltfUrl(options = {}) {
32150
32592
  const parse = useParser(options);
32151
- const [url, setUrl] = useState19();
32152
- const [error, setError] = useState19();
32153
- const ref = useCallback10(
32593
+ const [url, setUrl] = useState20();
32594
+ const [error, setError] = useState20();
32595
+ const ref = useCallback12(
32154
32596
  (instance) => parse(instance).then(setUrl).catch(setError),
32155
32597
  []
32156
32598
  );
@@ -32158,7 +32600,7 @@ function useExportGltfUrl(options = {}) {
32158
32600
  return [ref, url, error];
32159
32601
  }
32160
32602
  function useParser(options = {}) {
32161
- const exporter = useMemo21(() => new GLTFExporter2(), []);
32603
+ const exporter = useMemo22(() => new GLTFExporter2(), []);
32162
32604
  return (instance) => {
32163
32605
  const { promise, resolve, reject } = Promise.withResolvers();
32164
32606
  exporter.parse(