@tscircuit/3d-viewer 0.0.231 → 0.0.232

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 (3) hide show
  1. package/dist/index.d.ts +24 -14
  2. package/dist/index.js +1197 -129
  3. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -11721,7 +11721,7 @@ var require_vectorText = __commonJS({
11721
11721
  }
11722
11722
  return line3;
11723
11723
  };
11724
- var vectorText2 = (options, text) => {
11724
+ var vectorText3 = (options, text) => {
11725
11725
  const {
11726
11726
  xOffset,
11727
11727
  yOffset,
@@ -11778,7 +11778,7 @@ var require_vectorText = __commonJS({
11778
11778
  }
11779
11779
  return output;
11780
11780
  };
11781
- module.exports = vectorText2;
11781
+ module.exports = vectorText3;
11782
11782
  }
11783
11783
  });
11784
11784
 
@@ -13979,12 +13979,15 @@ var require_browser = __commonJS({
13979
13979
  }
13980
13980
  });
13981
13981
 
13982
+ // src/CadViewer.tsx
13983
+ import { useState as useState9, useCallback as useCallback4, useRef as useRef7, useEffect as useEffect7 } from "react";
13984
+
13982
13985
  // src/hooks/use-convert-children-to-soup.ts
13983
13986
  import { Circuit } from "@tscircuit/core";
13984
13987
  import { useMemo } from "react";
13985
- var useConvertChildrenToSoup = (children, defaultSoup) => {
13988
+ var useConvertChildrenToSoup = (children) => {
13986
13989
  return useMemo(() => {
13987
- if (!children) return;
13990
+ if (!children) return void 0;
13988
13991
  const circuit = new Circuit();
13989
13992
  circuit.add(children);
13990
13993
  circuit.render();
@@ -13992,7 +13995,7 @@ var useConvertChildrenToSoup = (children, defaultSoup) => {
13992
13995
  }, [children]);
13993
13996
  };
13994
13997
 
13995
- // src/CadViewer.tsx
13998
+ // src/CadViewerJscad.tsx
13996
13999
  import { su as su4 } from "@tscircuit/soup-util";
13997
14000
  import { useMemo as useMemo5, forwardRef as forwardRef2 } from "react";
13998
14001
 
@@ -16982,7 +16985,7 @@ import { Canvas, useFrame as useFrame2 } from "@react-three/fiber";
16982
16985
  // package.json
16983
16986
  var package_default = {
16984
16987
  name: "@tscircuit/3d-viewer",
16985
- version: "0.0.230",
16988
+ version: "0.0.231",
16986
16989
  main: "./dist/index.js",
16987
16990
  module: "./dist/index.js",
16988
16991
  type: "module",
@@ -17019,6 +17022,7 @@ var package_default = {
17019
17022
  "jscad-electronics": "^0.0.27",
17020
17023
  "jscad-fiber": "^0.0.79",
17021
17024
  "jscad-planner": "^0.0.13",
17025
+ "manifold-3d": "^3.1.0",
17022
17026
  react: "^18.3.1",
17023
17027
  "react-dom": "^18.3.1",
17024
17028
  "react-use-gesture": "^9.1.3"
@@ -17399,15 +17403,23 @@ var platedHole = (plated_hole, ctx) => {
17399
17403
  (0, import_primitives3.cylinder)({
17400
17404
  center: [plated_hole.x, plated_hole.y, 0],
17401
17405
  radius: plated_hole.hole_diameter / 2,
17402
- height: 1.2
17406
+ height: ctx.pcbThickness
17403
17407
  }),
17404
17408
  (0, import_primitives3.cylinder)({
17405
- center: [plated_hole.x, plated_hole.y, 1.2 / 2],
17409
+ center: [
17410
+ plated_hole.x,
17411
+ plated_hole.y,
17412
+ ctx.pcbThickness / 2 + platedHoleLipHeight / 2 + M
17413
+ ],
17406
17414
  radius: plated_hole.outer_diameter / 2,
17407
17415
  height: platedHoleLipHeight
17408
17416
  }),
17409
17417
  (0, import_primitives3.cylinder)({
17410
- center: [plated_hole.x, plated_hole.y, -1.2 / 2],
17418
+ center: [
17419
+ plated_hole.x,
17420
+ plated_hole.y,
17421
+ -ctx.pcbThickness / 2 - platedHoleLipHeight / 2 - M
17422
+ ],
17411
17423
  radius: plated_hole.outer_diameter / 2,
17412
17424
  height: platedHoleLipHeight
17413
17425
  })
@@ -17457,82 +17469,81 @@ var platedHole = (plated_hole, ctx) => {
17457
17469
  const shouldRotate = plated_hole.hole_height > plated_hole.hole_width;
17458
17470
  const holeWidth = shouldRotate ? plated_hole.hole_height : plated_hole.hole_width;
17459
17471
  const holeHeight = shouldRotate ? plated_hole.hole_width : plated_hole.hole_height;
17460
- const outerHeight = shouldRotate ? plated_hole.outer_width || holeWidth + 0.2 : plated_hole.outer_height || holeHeight + 0.2;
17472
+ const outerPillWidth = shouldRotate ? plated_hole.outer_height || holeHeight + 0.2 : plated_hole.outer_width || holeWidth + 0.2;
17473
+ const outerPillHeight = shouldRotate ? plated_hole.outer_width || holeWidth + 0.2 : plated_hole.outer_height || holeHeight + 0.2;
17461
17474
  const holeRadius = holeHeight / 2;
17475
+ const outerRadius = outerPillHeight / 2;
17462
17476
  const rectLength = Math.abs(holeWidth - holeHeight);
17463
- const mainRect = (0, import_primitives3.cuboid)({
17464
- center: shouldRotate ? [plated_hole.x, plated_hole.y, 0] : [plated_hole.x, plated_hole.y, 0],
17465
- size: shouldRotate ? [holeHeight, rectLength, 1.2] : [rectLength, holeHeight, 1.2]
17477
+ const outerRectLength = Math.abs(outerPillWidth - outerPillHeight);
17478
+ const mainRectBarrel = (0, import_primitives3.cuboid)({
17479
+ center: [plated_hole.x, plated_hole.y, 0],
17480
+ size: shouldRotate ? [holeHeight, rectLength, ctx.pcbThickness] : [rectLength, holeHeight, ctx.pcbThickness]
17466
17481
  });
17467
- const leftCap = (0, import_primitives3.cylinder)({
17482
+ const leftCapBarrel = (0, import_primitives3.cylinder)({
17468
17483
  center: shouldRotate ? [plated_hole.x, plated_hole.y - rectLength / 2, 0] : [plated_hole.x - rectLength / 2, plated_hole.y, 0],
17469
17484
  radius: holeRadius,
17470
- height: 1.2
17485
+ height: ctx.pcbThickness
17471
17486
  });
17472
- const rightCap = (0, import_primitives3.cylinder)({
17487
+ const rightCapBarrel = (0, import_primitives3.cylinder)({
17473
17488
  center: shouldRotate ? [plated_hole.x, plated_hole.y + rectLength / 2, 0] : [plated_hole.x + rectLength / 2, plated_hole.y, 0],
17474
17489
  radius: holeRadius,
17475
- height: 1.2
17490
+ height: ctx.pcbThickness
17476
17491
  });
17477
- const outerMainRect = (0, import_primitives3.cuboid)({
17478
- center: shouldRotate ? [plated_hole.x, plated_hole.y, 1.2 / 2] : [plated_hole.x, plated_hole.y, 1.2 / 2],
17479
- size: shouldRotate ? [outerHeight, rectLength, M] : [rectLength, outerHeight, M]
17492
+ const barrelUnion = (0, import_booleans.union)(mainRectBarrel, leftCapBarrel, rightCapBarrel);
17493
+ const topLipZ = ctx.pcbThickness / 2 + platedHoleLipHeight / 2 + M;
17494
+ const topLipRect = (0, import_primitives3.cuboid)({
17495
+ center: [plated_hole.x, plated_hole.y, topLipZ],
17496
+ size: shouldRotate ? [outerPillHeight, outerRectLength, platedHoleLipHeight] : [outerRectLength, outerPillHeight, platedHoleLipHeight]
17480
17497
  });
17481
- const outerLeftCap = (0, import_primitives3.cylinder)({
17482
- center: shouldRotate ? [plated_hole.x, plated_hole.y - rectLength / 2, 1.2 / 2] : [plated_hole.x - rectLength / 2, plated_hole.y, 1.2 / 2],
17483
- radius: outerHeight / 2,
17498
+ const topLipLeftCap = (0, import_primitives3.cylinder)({
17499
+ center: shouldRotate ? [plated_hole.x, plated_hole.y - outerRectLength / 2, topLipZ] : [plated_hole.x - outerRectLength / 2, plated_hole.y, topLipZ],
17500
+ radius: outerRadius,
17484
17501
  height: platedHoleLipHeight
17485
17502
  });
17486
- const outerRightCap = (0, import_primitives3.cylinder)({
17487
- center: shouldRotate ? [plated_hole.x, plated_hole.y + rectLength / 2, 1.2 / 2] : [plated_hole.x + rectLength / 2, plated_hole.y, 1.2 / 2],
17488
- radius: outerHeight / 2,
17503
+ const topLipRightCap = (0, import_primitives3.cylinder)({
17504
+ center: shouldRotate ? [plated_hole.x, plated_hole.y + outerRectLength / 2, topLipZ] : [plated_hole.x + outerRectLength / 2, plated_hole.y, topLipZ],
17505
+ radius: outerRadius,
17489
17506
  height: platedHoleLipHeight
17490
17507
  });
17491
- const bottomMainRect = (0, import_primitives3.cuboid)({
17492
- center: shouldRotate ? [plated_hole.x, plated_hole.y, -1.2 / 2] : [plated_hole.x, plated_hole.y, -1.2 / 2],
17493
- size: shouldRotate ? [outerHeight, rectLength, M] : [rectLength, outerHeight, M]
17508
+ const topLipUnion = (0, import_booleans.union)(topLipRect, topLipLeftCap, topLipRightCap);
17509
+ const bottomLipZ = -ctx.pcbThickness / 2 - platedHoleLipHeight / 2 - M;
17510
+ const bottomLipRect = (0, import_primitives3.cuboid)({
17511
+ center: [plated_hole.x, plated_hole.y, bottomLipZ],
17512
+ size: shouldRotate ? [outerPillHeight, outerRectLength, platedHoleLipHeight] : [outerRectLength, outerPillHeight, platedHoleLipHeight]
17494
17513
  });
17495
- const bottomLeftCap = (0, import_primitives3.cylinder)({
17496
- center: shouldRotate ? [plated_hole.x, plated_hole.y - rectLength / 2, -1.2 / 2] : [plated_hole.x - rectLength / 2, plated_hole.y, -1.2 / 2],
17497
- radius: outerHeight / 2,
17514
+ const bottomLipLeftCap = (0, import_primitives3.cylinder)({
17515
+ center: shouldRotate ? [plated_hole.x, plated_hole.y - outerRectLength / 2, bottomLipZ] : [plated_hole.x - outerRectLength / 2, plated_hole.y, bottomLipZ],
17516
+ radius: outerRadius,
17498
17517
  height: platedHoleLipHeight
17499
17518
  });
17500
- const bottomRightCap = (0, import_primitives3.cylinder)({
17501
- center: shouldRotate ? [plated_hole.x, plated_hole.y + rectLength / 2, -1.2 / 2] : [plated_hole.x + rectLength / 2, plated_hole.y, -1.2 / 2],
17502
- radius: outerHeight / 2,
17519
+ const bottomLipRightCap = (0, import_primitives3.cylinder)({
17520
+ center: shouldRotate ? [plated_hole.x, plated_hole.y + outerRectLength / 2, bottomLipZ] : [plated_hole.x + outerRectLength / 2, plated_hole.y, bottomLipZ],
17521
+ radius: outerRadius,
17503
17522
  height: platedHoleLipHeight
17504
17523
  });
17524
+ const bottomLipUnion = (0, import_booleans.union)(
17525
+ bottomLipRect,
17526
+ bottomLipLeftCap,
17527
+ bottomLipRightCap
17528
+ );
17529
+ const drillRect = (0, import_primitives3.cuboid)({
17530
+ center: [plated_hole.x, plated_hole.y, 0],
17531
+ size: shouldRotate ? [holeHeight - 2 * M, rectLength, ctx.pcbThickness + 2 * M] : [rectLength, holeHeight - 2 * M, ctx.pcbThickness + 2 * M]
17532
+ });
17533
+ const drillLeftCap = (0, import_primitives3.cylinder)({
17534
+ center: shouldRotate ? [plated_hole.x, plated_hole.y - rectLength / 2, 0] : [plated_hole.x - rectLength / 2, plated_hole.y, 0],
17535
+ radius: holeRadius - M,
17536
+ height: ctx.pcbThickness + 2 * M
17537
+ });
17538
+ const drillRightCap = (0, import_primitives3.cylinder)({
17539
+ center: shouldRotate ? [plated_hole.x, plated_hole.y + rectLength / 2, 0] : [plated_hole.x + rectLength / 2, plated_hole.y, 0],
17540
+ radius: holeRadius - M,
17541
+ height: ctx.pcbThickness + 2 * M
17542
+ });
17543
+ const drillUnion = (0, import_booleans.union)(drillRect, drillLeftCap, drillRightCap);
17505
17544
  return (0, import_colors2.colorize)(
17506
17545
  colors.copper,
17507
- (0, import_booleans.subtract)(
17508
- (0, import_booleans.union)(
17509
- mainRect,
17510
- leftCap,
17511
- rightCap,
17512
- outerMainRect,
17513
- outerLeftCap,
17514
- outerRightCap,
17515
- bottomMainRect,
17516
- bottomLeftCap,
17517
- bottomRightCap
17518
- ),
17519
- (0, import_booleans.union)(
17520
- (0, import_primitives3.cuboid)({
17521
- center: [plated_hole.x, plated_hole.y, 0],
17522
- size: shouldRotate ? [holeHeight - platedHoleLipHeight, rectLength, 1.5] : [rectLength, holeHeight - platedHoleLipHeight, 1.5]
17523
- }),
17524
- (0, import_primitives3.cylinder)({
17525
- center: shouldRotate ? [plated_hole.x, plated_hole.y - rectLength / 2, 0] : [plated_hole.x - rectLength / 2, plated_hole.y, 0],
17526
- radius: holeRadius - platedHoleLipHeight,
17527
- height: 1.5
17528
- }),
17529
- (0, import_primitives3.cylinder)({
17530
- center: shouldRotate ? [plated_hole.x, plated_hole.y + rectLength / 2, 0] : [plated_hole.x + rectLength / 2, plated_hole.y, 0],
17531
- radius: holeRadius - platedHoleLipHeight,
17532
- height: 1.5
17533
- })
17534
- )
17535
- )
17546
+ (0, import_booleans.subtract)((0, import_booleans.union)(barrelUnion, topLipUnion, bottomLipUnion), drillUnion)
17536
17547
  );
17537
17548
  }
17538
17549
  if (plated_hole.shape === "pill_hole_with_rect_pad") {
@@ -18893,14 +18904,15 @@ var Error3d = ({
18893
18904
  );
18894
18905
  };
18895
18906
 
18896
- // src/CadViewer.tsx
18907
+ // src/CadViewerJscad.tsx
18897
18908
  import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
18898
- var CadViewer = forwardRef2(
18909
+ var CadViewerJscad = forwardRef2(
18899
18910
  ({ soup, circuitJson, children, autoRotateDisabled, clickToInteractEnabled }, ref) => {
18911
+ const childrenSoup = useConvertChildrenToSoup(children);
18900
18912
  const internalCircuitJson = useMemo5(() => {
18901
18913
  const cj = soup ?? circuitJson;
18902
- return cj ?? useConvertChildrenToSoup(children, cj);
18903
- }, [soup, circuitJson, children]);
18914
+ return cj ?? childrenSoup;
18915
+ }, [soup, circuitJson, childrenSoup]);
18904
18916
  const boardGeom = useBoardGeomBuilder(internalCircuitJson);
18905
18917
  const initialCameraPosition = useMemo5(() => {
18906
18918
  if (!internalCircuitJson) return [5, 5, 5];
@@ -18962,17 +18974,1072 @@ var CadViewer = forwardRef2(
18962
18974
  }
18963
18975
  );
18964
18976
 
18965
- // src/convert-circuit-json-to-3d-svg.ts
18966
- var import_debug = __toESM(require_browser(), 1);
18977
+ // src/CadViewerManifold.tsx
18978
+ import { useEffect as useEffect6, useState as useState8, useMemo as useMemo7 } from "react";
18979
+ import { su as su6 } from "@tscircuit/soup-util";
18980
+ import * as THREE8 from "three";
18981
+ import ManifoldModule from "manifold-3d";
18982
+
18983
+ // src/hooks/useManifoldBoardBuilder.ts
18984
+ import { useState as useState7, useEffect as useEffect5, useMemo as useMemo6, useRef as useRef6 } from "react";
18967
18985
  import { su as su5 } from "@tscircuit/soup-util";
18968
18986
  import * as THREE7 from "three";
18987
+
18988
+ // src/utils/manifold-mesh-to-three-geometry.ts
18989
+ import * as THREE4 from "three";
18990
+ function manifoldMeshToThreeGeometry(manifoldMesh) {
18991
+ const geometry = new THREE4.BufferGeometry();
18992
+ geometry.setAttribute(
18993
+ "position",
18994
+ new THREE4.Float32BufferAttribute(manifoldMesh.vertProperties, 3)
18995
+ );
18996
+ geometry.setIndex(new THREE4.Uint32BufferAttribute(manifoldMesh.triVerts, 1));
18997
+ if (manifoldMesh.runIndex && manifoldMesh.runIndex.length > 1 && manifoldMesh.runOriginalID) {
18998
+ for (let i = 0; i < manifoldMesh.runIndex.length - 1; i++) {
18999
+ const start = manifoldMesh.runIndex[i];
19000
+ const count = manifoldMesh.runIndex[i + 1] - start;
19001
+ geometry.addGroup(start, count, 0);
19002
+ }
19003
+ } else {
19004
+ geometry.addGroup(0, manifoldMesh.triVerts.length, 0);
19005
+ }
19006
+ return geometry;
19007
+ }
19008
+
19009
+ // src/utils/trace-texture.ts
19010
+ import * as THREE5 from "three";
19011
+ function isWireRoutePoint(point) {
19012
+ return point && point.route_type === "wire" && typeof point.layer === "string" && typeof point.width === "number";
19013
+ }
19014
+ function createTraceTextureForLayer({
19015
+ layer,
19016
+ pcbTraces,
19017
+ boardData,
19018
+ traceColor,
19019
+ traceTextureResolution,
19020
+ allPcbVias,
19021
+ allPcbPlatedHoles
19022
+ }) {
19023
+ const tracesOnLayer = pcbTraces.filter(
19024
+ (t) => t.route.some((p) => isWireRoutePoint(p) && p.layer === layer)
19025
+ );
19026
+ if (tracesOnLayer.length === 0) return null;
19027
+ const canvas = document.createElement("canvas");
19028
+ const canvasWidth = Math.floor(boardData.width * traceTextureResolution);
19029
+ const canvasHeight = Math.floor(boardData.height * traceTextureResolution);
19030
+ canvas.width = canvasWidth;
19031
+ canvas.height = canvasHeight;
19032
+ const ctx = canvas.getContext("2d");
19033
+ if (!ctx) return null;
19034
+ if (layer === "bottom") {
19035
+ ctx.translate(0, canvasHeight);
19036
+ ctx.scale(1, -1);
19037
+ }
19038
+ tracesOnLayer.forEach((trace) => {
19039
+ let firstPoint = true;
19040
+ ctx.beginPath();
19041
+ ctx.strokeStyle = traceColor;
19042
+ ctx.lineCap = "round";
19043
+ ctx.lineJoin = "round";
19044
+ let currentLineWidth = 0;
19045
+ for (const point of trace.route) {
19046
+ if (!isWireRoutePoint(point) || point.layer !== layer) {
19047
+ if (!firstPoint) ctx.stroke();
19048
+ firstPoint = true;
19049
+ continue;
19050
+ }
19051
+ const pcbX = point.x;
19052
+ const pcbY = point.y;
19053
+ currentLineWidth = point.width * traceTextureResolution;
19054
+ ctx.lineWidth = currentLineWidth;
19055
+ const canvasX = (pcbX - boardData.center.x + boardData.width / 2) * traceTextureResolution;
19056
+ const canvasY = (-(pcbY - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
19057
+ if (firstPoint) {
19058
+ ctx.moveTo(canvasX, canvasY);
19059
+ firstPoint = false;
19060
+ } else {
19061
+ ctx.lineTo(canvasX, canvasY);
19062
+ }
19063
+ }
19064
+ if (!firstPoint) {
19065
+ ctx.stroke();
19066
+ }
19067
+ });
19068
+ ctx.globalCompositeOperation = "destination-out";
19069
+ ctx.fillStyle = "black";
19070
+ allPcbVias.forEach((via) => {
19071
+ const canvasX = (via.x - boardData.center.x + boardData.width / 2) * traceTextureResolution;
19072
+ const canvasY = (-(via.y - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
19073
+ const canvasRadius = via.outer_diameter / 2 * traceTextureResolution;
19074
+ ctx.beginPath();
19075
+ ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI, false);
19076
+ ctx.fill();
19077
+ });
19078
+ allPcbPlatedHoles.forEach((ph) => {
19079
+ if (ph.layers.includes(layer) && ph.shape === "circle") {
19080
+ const canvasX = (ph.x - boardData.center.x + boardData.width / 2) * traceTextureResolution;
19081
+ const canvasY = (-(ph.y - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
19082
+ const canvasRadius = ph.outer_diameter / 2 * traceTextureResolution;
19083
+ ctx.beginPath();
19084
+ ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI, false);
19085
+ ctx.fill();
19086
+ }
19087
+ });
19088
+ ctx.globalCompositeOperation = "source-over";
19089
+ const texture = new THREE5.CanvasTexture(canvas);
19090
+ texture.generateMipmaps = true;
19091
+ texture.minFilter = THREE5.LinearMipmapLinearFilter;
19092
+ texture.magFilter = THREE5.LinearFilter;
19093
+ texture.anisotropy = 16;
19094
+ texture.needsUpdate = true;
19095
+ return texture;
19096
+ }
19097
+
19098
+ // src/utils/silkscreen-texture.ts
19099
+ var import_text2 = __toESM(require_text(), 1);
19100
+ import * as THREE6 from "three";
19101
+ function createSilkscreenTextureForLayer({
19102
+ layer,
19103
+ pcbSilkscreenTexts,
19104
+ pcbSilkscreenPaths,
19105
+ boardData,
19106
+ silkscreenColor = "rgb(255,255,255)",
19107
+ traceTextureResolution
19108
+ }) {
19109
+ const textsOnLayer = pcbSilkscreenTexts.filter((t) => t.layer === layer);
19110
+ const pathsOnLayer = pcbSilkscreenPaths.filter((p) => p.layer === layer);
19111
+ if (textsOnLayer.length === 0 && pathsOnLayer.length === 0) return null;
19112
+ const canvas = document.createElement("canvas");
19113
+ const canvasWidth = Math.floor(boardData.width * traceTextureResolution);
19114
+ const canvasHeight = Math.floor(boardData.height * traceTextureResolution);
19115
+ canvas.width = canvasWidth;
19116
+ canvas.height = canvasHeight;
19117
+ const ctx = canvas.getContext("2d");
19118
+ if (!ctx) return null;
19119
+ if (layer === "bottom") {
19120
+ ctx.translate(0, canvasHeight);
19121
+ ctx.scale(1, -1);
19122
+ }
19123
+ ctx.strokeStyle = silkscreenColor;
19124
+ ctx.fillStyle = silkscreenColor;
19125
+ pathsOnLayer.forEach((path) => {
19126
+ if (path.route.length < 2) return;
19127
+ ctx.beginPath();
19128
+ ctx.lineWidth = (path.stroke_width || 0.1) * traceTextureResolution;
19129
+ ctx.lineCap = "round";
19130
+ ctx.lineJoin = "round";
19131
+ path.route.forEach((point, index) => {
19132
+ const canvasX = (point.x - boardData.center.x + boardData.width / 2) * traceTextureResolution;
19133
+ const canvasY = (-(point.y - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
19134
+ if (index === 0) ctx.moveTo(canvasX, canvasY);
19135
+ else ctx.lineTo(canvasX, canvasY);
19136
+ });
19137
+ ctx.stroke();
19138
+ });
19139
+ textsOnLayer.forEach((textS) => {
19140
+ const fontSize = textS.font_size || 0.25;
19141
+ const textStrokeWidth = Math.min(Math.max(0.01, fontSize * 0.1), fontSize * 0.05) * traceTextureResolution;
19142
+ ctx.lineWidth = textStrokeWidth;
19143
+ ctx.lineCap = "butt";
19144
+ ctx.lineJoin = "miter";
19145
+ const rawTextOutlines = (0, import_text2.vectorText)({
19146
+ height: fontSize * 0.57,
19147
+ input: textS.text
19148
+ });
19149
+ const processedTextOutlines = [];
19150
+ rawTextOutlines.forEach((outline) => {
19151
+ if (outline.length === 29) {
19152
+ processedTextOutlines.push(
19153
+ outline.slice(0, 15)
19154
+ );
19155
+ processedTextOutlines.push(
19156
+ outline.slice(14, 29)
19157
+ );
19158
+ } else if (outline.length === 17) {
19159
+ processedTextOutlines.push(
19160
+ outline.slice(0, 10)
19161
+ );
19162
+ processedTextOutlines.push(
19163
+ outline.slice(9, 17)
19164
+ );
19165
+ } else {
19166
+ processedTextOutlines.push(outline);
19167
+ }
19168
+ });
19169
+ const points = processedTextOutlines.flat();
19170
+ const textBounds = {
19171
+ minX: points.length > 0 ? Math.min(...points.map((p) => p[0])) : 0,
19172
+ maxX: points.length > 0 ? Math.max(...points.map((p) => p[0])) : 0,
19173
+ minY: points.length > 0 ? Math.min(...points.map((p) => p[1])) : 0,
19174
+ maxY: points.length > 0 ? Math.max(...points.map((p) => p[1])) : 0
19175
+ };
19176
+ const textCenterX = (textBounds.minX + textBounds.maxX) / 2;
19177
+ const textCenterY = (textBounds.minY + textBounds.maxY) / 2;
19178
+ let xOff = -textCenterX;
19179
+ let yOff = -textCenterY;
19180
+ if (textS.anchor_alignment?.includes("right")) xOff = -textBounds.maxX;
19181
+ else if (textS.anchor_alignment?.includes("left")) xOff = -textBounds.minX;
19182
+ if (textS.anchor_alignment?.includes("top")) yOff = -textBounds.maxY;
19183
+ else if (textS.anchor_alignment?.includes("bottom")) yOff = -textBounds.minY;
19184
+ const transformMatrices = [];
19185
+ let rotationDeg = textS.ccw_rotation ?? 0;
19186
+ if (textS.layer === "bottom") {
19187
+ transformMatrices.push(
19188
+ translate2(textCenterX, textCenterY),
19189
+ { a: -1, b: 0, c: 0, d: 1, e: 0, f: 0 },
19190
+ translate2(-textCenterX, -textCenterY)
19191
+ );
19192
+ rotationDeg = -rotationDeg;
19193
+ }
19194
+ if (rotationDeg) {
19195
+ const rad = rotationDeg * Math.PI / 180;
19196
+ transformMatrices.push(
19197
+ translate2(textCenterX, textCenterY),
19198
+ rotate(rad),
19199
+ translate2(-textCenterX, -textCenterY)
19200
+ );
19201
+ }
19202
+ const finalTransformMatrix = transformMatrices.length > 0 ? compose(...transformMatrices) : void 0;
19203
+ processedTextOutlines.forEach((segment) => {
19204
+ ctx.beginPath();
19205
+ segment.forEach((p, index) => {
19206
+ let transformedP = { x: p[0], y: p[1] };
19207
+ if (finalTransformMatrix) {
19208
+ transformedP = applyToPoint(finalTransformMatrix, transformedP);
19209
+ }
19210
+ const pcbX = transformedP.x + xOff + textS.anchor_position.x;
19211
+ const pcbY = transformedP.y + yOff + textS.anchor_position.y;
19212
+ const canvasX = (pcbX - boardData.center.x + boardData.width / 2) * traceTextureResolution;
19213
+ const canvasY = (-(pcbY - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
19214
+ if (index === 0) ctx.moveTo(canvasX, canvasY);
19215
+ else ctx.lineTo(canvasX, canvasY);
19216
+ });
19217
+ ctx.stroke();
19218
+ });
19219
+ });
19220
+ const texture = new THREE6.CanvasTexture(canvas);
19221
+ texture.generateMipmaps = true;
19222
+ texture.minFilter = THREE6.LinearMipmapLinearFilter;
19223
+ texture.magFilter = THREE6.LinearFilter;
19224
+ texture.anisotropy = 16;
19225
+ texture.needsUpdate = true;
19226
+ return texture;
19227
+ }
19228
+
19229
+ // src/utils/pad-geoms.ts
19230
+ function createPadManifoldOp({
19231
+ Manifold,
19232
+ pad,
19233
+ padBaseThickness
19234
+ }) {
19235
+ if (pad.shape === "rect") {
19236
+ return Manifold.cube([pad.width, pad.height, padBaseThickness], true);
19237
+ } else if (pad.shape === "circle" && pad.radius) {
19238
+ return Manifold.cylinder(padBaseThickness, pad.radius, -1, 32, true);
19239
+ }
19240
+ return null;
19241
+ }
19242
+
19243
+ // src/utils/hole-geoms.ts
19244
+ function createCircleHoleDrill({
19245
+ Manifold,
19246
+ x,
19247
+ y,
19248
+ diameter,
19249
+ thickness,
19250
+ segments = 32
19251
+ }) {
19252
+ const drill = Manifold.cylinder(
19253
+ thickness * 1.2,
19254
+ diameter / 2,
19255
+ diameter / 2,
19256
+ segments,
19257
+ true
19258
+ );
19259
+ return drill.translate([x, y, 0]);
19260
+ }
19261
+ function createPlatedHoleDrill({
19262
+ Manifold,
19263
+ x,
19264
+ y,
19265
+ outerDiameter,
19266
+ thickness,
19267
+ zOffset = 1e-3,
19268
+ segments = 32
19269
+ }) {
19270
+ const boardHoleRadius = outerDiameter / 2 + zOffset;
19271
+ const drill = Manifold.cylinder(
19272
+ thickness * 1.2,
19273
+ boardHoleRadius,
19274
+ boardHoleRadius,
19275
+ segments,
19276
+ true
19277
+ );
19278
+ return drill.translate([x, y, 0]);
19279
+ }
19280
+
19281
+ // src/utils/via-geoms.ts
19282
+ function createViaCopper({
19283
+ Manifold,
19284
+ x,
19285
+ y,
19286
+ outerDiameter,
19287
+ holeDiameter,
19288
+ thickness,
19289
+ zOffset = 1e-3,
19290
+ segments = 32
19291
+ }) {
19292
+ const copperPartThickness = thickness + 2 * zOffset;
19293
+ let viaCopper = Manifold.cylinder(
19294
+ copperPartThickness,
19295
+ outerDiameter / 2,
19296
+ -1,
19297
+ segments,
19298
+ true
19299
+ );
19300
+ const drill = Manifold.cylinder(
19301
+ copperPartThickness * 1.05,
19302
+ holeDiameter / 2,
19303
+ -1,
19304
+ segments,
19305
+ true
19306
+ );
19307
+ const finalViaCopperOp = viaCopper.subtract(drill);
19308
+ return finalViaCopperOp.translate([x, y, 0]);
19309
+ }
19310
+
19311
+ // src/hooks/useManifoldBoardBuilder.ts
19312
+ var arePointsClockwise2 = (points) => {
19313
+ let area = 0;
19314
+ for (let i = 0; i < points.length; i++) {
19315
+ const j = (i + 1) % points.length;
19316
+ if (points[i] && points[j]) {
19317
+ area += points[i][0] * points[j][1];
19318
+ area -= points[j][0] * points[i][1];
19319
+ }
19320
+ }
19321
+ const signedArea = area / 2;
19322
+ return signedArea <= 0;
19323
+ };
19324
+ var COPPER_COLOR = new THREE7.Color(
19325
+ colors.copper[0],
19326
+ colors.copper[1],
19327
+ colors.copper[2]
19328
+ );
19329
+ var DEFAULT_SMT_PAD_THICKNESS = 0.035;
19330
+ var SMOOTH_CIRCLE_SEGMENTS = 32;
19331
+ var MANIFOLD_Z_OFFSET = 1e-3;
19332
+ var TRACE_TEXTURE_RESOLUTION = 50;
19333
+ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
19334
+ const [boardThreeGeom, setBoardThreeGeom] = useState7(null);
19335
+ const [boardColor, setBoardColor] = useState7(
19336
+ new THREE7.Color(
19337
+ colors.fr4Green[0],
19338
+ colors.fr4Green[1],
19339
+ colors.fr4Green[2]
19340
+ )
19341
+ );
19342
+ const [otherComponentGeoms, setOtherComponentGeoms] = useState7([]);
19343
+ const [topTraceTexture, setTopTraceTexture] = useState7(null);
19344
+ const [bottomTraceTexture, setBottomTraceTexture] = useState7(null);
19345
+ const [topSilkscreenTexture, setTopSilkscreenTexture] = useState7(null);
19346
+ const [bottomSilkscreenTexture, setBottomSilkscreenTexture] = useState7(null);
19347
+ const [pcbThickness, setPcbThickness] = useState7(null);
19348
+ const [error, setError] = useState7(null);
19349
+ const [isLoading, setIsLoading] = useState7(true);
19350
+ const manifoldInstancesForCleanup = useRef6([]);
19351
+ const boardData = useMemo6(() => {
19352
+ if (!circuitJson) return null;
19353
+ const boards = su5(circuitJson).pcb_board.list();
19354
+ if (boards.length === 0) {
19355
+ return null;
19356
+ }
19357
+ return boards[0];
19358
+ }, [circuitJson]);
19359
+ function isCircleHole(hole) {
19360
+ return (hole.shape === "circle" || hole.hole_shape === "circle") && typeof hole.hole_diameter === "number";
19361
+ }
19362
+ useEffect5(() => {
19363
+ if (!manifoldJSModule || !circuitJson || !boardData) {
19364
+ setBoardThreeGeom(null);
19365
+ setOtherComponentGeoms([]);
19366
+ setTopTraceTexture(null);
19367
+ setBottomTraceTexture(null);
19368
+ setTopSilkscreenTexture(null);
19369
+ setBottomSilkscreenTexture(null);
19370
+ setPcbThickness(null);
19371
+ if (circuitJson && su5(circuitJson).pcb_board.list().length === 0) {
19372
+ setError("No pcb_board found in circuitJson.");
19373
+ }
19374
+ setIsLoading(false);
19375
+ return;
19376
+ }
19377
+ setIsLoading(true);
19378
+ setError(null);
19379
+ const Manifold = manifoldJSModule.Manifold;
19380
+ const CrossSection = manifoldJSModule.CrossSection;
19381
+ manifoldInstancesForCleanup.current.forEach((inst) => inst.delete());
19382
+ manifoldInstancesForCleanup.current = [];
19383
+ let boardManifold = null;
19384
+ const newOtherGeoms = [];
19385
+ try {
19386
+ const currentPcbThickness = boardData.thickness || 1.6;
19387
+ setPcbThickness(currentPcbThickness);
19388
+ let currentBoardOp;
19389
+ if (boardData.outline && boardData.outline.length >= 3) {
19390
+ let outlineVec2 = boardData.outline.map(
19391
+ (p) => [p.x, p.y]
19392
+ );
19393
+ if (arePointsClockwise2(outlineVec2)) {
19394
+ outlineVec2 = outlineVec2.reverse();
19395
+ }
19396
+ const crossSection = CrossSection.ofPolygons([outlineVec2]);
19397
+ manifoldInstancesForCleanup.current.push(crossSection);
19398
+ currentBoardOp = Manifold.extrude(
19399
+ crossSection,
19400
+ currentPcbThickness,
19401
+ void 0,
19402
+ // nDivisions
19403
+ void 0,
19404
+ // twistDegrees
19405
+ void 0,
19406
+ // scaleTop
19407
+ true
19408
+ // center (for Z-axis)
19409
+ );
19410
+ } else {
19411
+ if (boardData.outline && boardData.outline.length > 0) {
19412
+ console.warn(
19413
+ "Board outline has fewer than 3 points, falling back to rectangular board."
19414
+ );
19415
+ }
19416
+ currentBoardOp = Manifold.cube(
19417
+ [boardData.width, boardData.height, currentPcbThickness],
19418
+ true
19419
+ // center (for all axes)
19420
+ );
19421
+ }
19422
+ manifoldInstancesForCleanup.current.push(currentBoardOp);
19423
+ currentBoardOp = currentBoardOp.translate([
19424
+ boardData.center.x,
19425
+ boardData.center.y,
19426
+ 0
19427
+ ]);
19428
+ const pcbHoles = su5(circuitJson).pcb_hole.list();
19429
+ const pcbPlatedHoles = su5(circuitJson).pcb_plated_hole.list();
19430
+ const pcbVias = su5(circuitJson).pcb_via.list();
19431
+ const allHoleDrills = [];
19432
+ pcbHoles.forEach((hole) => {
19433
+ if (isCircleHole(hole)) {
19434
+ const translatedDrill = createCircleHoleDrill({
19435
+ Manifold,
19436
+ x: hole.x,
19437
+ y: hole.y,
19438
+ diameter: hole.hole_diameter,
19439
+ thickness: currentPcbThickness,
19440
+ segments: SMOOTH_CIRCLE_SEGMENTS
19441
+ });
19442
+ manifoldInstancesForCleanup.current.push(translatedDrill);
19443
+ allHoleDrills.push(translatedDrill);
19444
+ }
19445
+ });
19446
+ pcbPlatedHoles.forEach((ph) => {
19447
+ if (ph.shape === "circle") {
19448
+ const translatedDrill = createPlatedHoleDrill({
19449
+ Manifold,
19450
+ x: ph.x,
19451
+ y: ph.y,
19452
+ outerDiameter: ph.outer_diameter,
19453
+ thickness: currentPcbThickness,
19454
+ zOffset: MANIFOLD_Z_OFFSET,
19455
+ segments: SMOOTH_CIRCLE_SEGMENTS
19456
+ });
19457
+ manifoldInstancesForCleanup.current.push(translatedDrill);
19458
+ allHoleDrills.push(translatedDrill);
19459
+ }
19460
+ });
19461
+ pcbVias.forEach((via) => {
19462
+ if (typeof via.outer_diameter === "number") {
19463
+ const translatedDrill = createPlatedHoleDrill({
19464
+ Manifold,
19465
+ x: via.x,
19466
+ y: via.y,
19467
+ outerDiameter: via.outer_diameter,
19468
+ thickness: currentPcbThickness,
19469
+ zOffset: MANIFOLD_Z_OFFSET,
19470
+ segments: SMOOTH_CIRCLE_SEGMENTS
19471
+ });
19472
+ manifoldInstancesForCleanup.current.push(translatedDrill);
19473
+ allHoleDrills.push(translatedDrill);
19474
+ }
19475
+ });
19476
+ if (allHoleDrills.length > 0) {
19477
+ const unionedDrills = Manifold.union(allHoleDrills);
19478
+ manifoldInstancesForCleanup.current.push(unionedDrills);
19479
+ const nextBoard = currentBoardOp.subtract(unionedDrills);
19480
+ manifoldInstancesForCleanup.current.push(nextBoard);
19481
+ currentBoardOp = nextBoard;
19482
+ }
19483
+ boardManifold = currentBoardOp;
19484
+ if (boardManifold) {
19485
+ const boardThreeMesh = boardManifold.getMesh();
19486
+ const finalBoardGeom = manifoldMeshToThreeGeometry(boardThreeMesh);
19487
+ setBoardThreeGeom(finalBoardGeom);
19488
+ } else {
19489
+ setBoardThreeGeom(null);
19490
+ }
19491
+ const matColorArray = boardMaterialColors[boardData.material] ?? colors.fr4Green;
19492
+ setBoardColor(
19493
+ new THREE7.Color(matColorArray[0], matColorArray[1], matColorArray[2])
19494
+ );
19495
+ pcbPlatedHoles.forEach((ph, index) => {
19496
+ if (ph.shape === "circle") {
19497
+ const copperPartThickness = currentPcbThickness + 2 * MANIFOLD_Z_OFFSET;
19498
+ let platedPart = Manifold.cylinder(
19499
+ copperPartThickness,
19500
+ ph.outer_diameter / 2,
19501
+ -1,
19502
+ SMOOTH_CIRCLE_SEGMENTS,
19503
+ true
19504
+ );
19505
+ manifoldInstancesForCleanup.current.push(platedPart);
19506
+ const drill = Manifold.cylinder(
19507
+ copperPartThickness * 1.05,
19508
+ ph.hole_diameter / 2,
19509
+ -1,
19510
+ SMOOTH_CIRCLE_SEGMENTS,
19511
+ true
19512
+ );
19513
+ manifoldInstancesForCleanup.current.push(drill);
19514
+ const finalPlatedPartOp = platedPart.subtract(drill);
19515
+ manifoldInstancesForCleanup.current.push(finalPlatedPartOp);
19516
+ const translatedPlatedPart = finalPlatedPartOp.translate([
19517
+ ph.x,
19518
+ ph.y,
19519
+ 0
19520
+ ]);
19521
+ manifoldInstancesForCleanup.current.push(translatedPlatedPart);
19522
+ const copperMesh = translatedPlatedPart.getMesh();
19523
+ const copperGeom = manifoldMeshToThreeGeometry(copperMesh);
19524
+ newOtherGeoms.push({
19525
+ key: `ph-${ph.pcb_plated_hole_id || index}`,
19526
+ geometry: copperGeom,
19527
+ color: COPPER_COLOR,
19528
+ manifoldSource: translatedPlatedPart
19529
+ });
19530
+ }
19531
+ });
19532
+ const smtPads = su5(circuitJson).pcb_smtpad.list();
19533
+ smtPads.forEach((pad, index) => {
19534
+ const padBaseThickness = DEFAULT_SMT_PAD_THICKNESS;
19535
+ const zPos = pad.layer === "bottom" ? -currentPcbThickness / 2 - padBaseThickness / 2 - MANIFOLD_Z_OFFSET : currentPcbThickness / 2 + padBaseThickness / 2 + MANIFOLD_Z_OFFSET;
19536
+ let padManifoldOp = createPadManifoldOp({
19537
+ Manifold,
19538
+ pad,
19539
+ padBaseThickness
19540
+ });
19541
+ if (padManifoldOp) {
19542
+ manifoldInstancesForCleanup.current.push(padManifoldOp);
19543
+ const translatedPad = padManifoldOp.translate([pad.x, pad.y, zPos]);
19544
+ manifoldInstancesForCleanup.current.push(translatedPad);
19545
+ const padMesh = translatedPad.getMesh();
19546
+ const padGeom = manifoldMeshToThreeGeometry(padMesh);
19547
+ newOtherGeoms.push({
19548
+ key: `pad-${pad.pcb_smtpad_id || index}`,
19549
+ geometry: padGeom,
19550
+ color: COPPER_COLOR,
19551
+ manifoldSource: translatedPad
19552
+ });
19553
+ }
19554
+ });
19555
+ pcbVias.forEach((via, index) => {
19556
+ if (typeof via.outer_diameter === "number" && typeof via.hole_diameter === "number") {
19557
+ const translatedViaCopper = createViaCopper({
19558
+ Manifold,
19559
+ x: via.x,
19560
+ y: via.y,
19561
+ outerDiameter: via.outer_diameter,
19562
+ holeDiameter: via.hole_diameter,
19563
+ thickness: currentPcbThickness,
19564
+ zOffset: MANIFOLD_Z_OFFSET,
19565
+ segments: SMOOTH_CIRCLE_SEGMENTS
19566
+ });
19567
+ manifoldInstancesForCleanup.current.push(translatedViaCopper);
19568
+ const viaMesh = translatedViaCopper.getMesh();
19569
+ const viaGeom = manifoldMeshToThreeGeometry(viaMesh);
19570
+ newOtherGeoms.push({
19571
+ key: `via-${via.pcb_via_id || index}`,
19572
+ geometry: viaGeom,
19573
+ color: COPPER_COLOR,
19574
+ manifoldSource: translatedViaCopper
19575
+ });
19576
+ }
19577
+ });
19578
+ const pcbTraces = su5(circuitJson).pcb_trace.list();
19579
+ const traceColorArr = tracesMaterialColors[boardData.material] ?? colors.fr4GreenSolderWithMask;
19580
+ const traceColor = `rgb(${Math.round(traceColorArr[0] * 255)}, ${Math.round(traceColorArr[1] * 255)}, ${Math.round(traceColorArr[2] * 255)})`;
19581
+ const allPcbVias = su5(circuitJson).pcb_via.list();
19582
+ const allPcbPlatedHoles = su5(circuitJson).pcb_plated_hole.list();
19583
+ setTopTraceTexture(
19584
+ createTraceTextureForLayer({
19585
+ layer: "top",
19586
+ pcbTraces,
19587
+ boardData,
19588
+ traceColor,
19589
+ traceTextureResolution: TRACE_TEXTURE_RESOLUTION,
19590
+ allPcbVias,
19591
+ allPcbPlatedHoles
19592
+ })
19593
+ );
19594
+ setBottomTraceTexture(
19595
+ createTraceTextureForLayer({
19596
+ layer: "bottom",
19597
+ pcbTraces,
19598
+ boardData,
19599
+ traceColor,
19600
+ traceTextureResolution: TRACE_TEXTURE_RESOLUTION,
19601
+ allPcbVias,
19602
+ allPcbPlatedHoles
19603
+ })
19604
+ );
19605
+ const pcbSilkscreenTexts = su5(circuitJson).pcb_silkscreen_text.list();
19606
+ const pcbSilkscreenPaths = su5(circuitJson).pcb_silkscreen_path.list();
19607
+ const silkscreenColor = "rgb(255,255,255)";
19608
+ setTopSilkscreenTexture(
19609
+ createSilkscreenTextureForLayer({
19610
+ layer: "top",
19611
+ pcbSilkscreenTexts,
19612
+ pcbSilkscreenPaths,
19613
+ boardData,
19614
+ silkscreenColor,
19615
+ traceTextureResolution: TRACE_TEXTURE_RESOLUTION
19616
+ })
19617
+ );
19618
+ setBottomSilkscreenTexture(
19619
+ createSilkscreenTextureForLayer({
19620
+ layer: "bottom",
19621
+ pcbSilkscreenTexts,
19622
+ pcbSilkscreenPaths,
19623
+ boardData,
19624
+ silkscreenColor,
19625
+ traceTextureResolution: TRACE_TEXTURE_RESOLUTION
19626
+ })
19627
+ );
19628
+ setOtherComponentGeoms(
19629
+ newOtherGeoms.map((g) => ({
19630
+ key: g.key,
19631
+ geometry: g.geometry,
19632
+ color: g.color
19633
+ }))
19634
+ );
19635
+ } catch (e) {
19636
+ console.error("Error processing geometry with Manifold in hook:", e);
19637
+ setError(
19638
+ e.message || "An unknown error occurred while processing geometry in hook."
19639
+ );
19640
+ setBoardThreeGeom(null);
19641
+ setOtherComponentGeoms([]);
19642
+ setTopTraceTexture(null);
19643
+ setBottomTraceTexture(null);
19644
+ setTopSilkscreenTexture(null);
19645
+ setBottomSilkscreenTexture(null);
19646
+ } finally {
19647
+ setIsLoading(false);
19648
+ }
19649
+ return () => {
19650
+ manifoldInstancesForCleanup.current.forEach((inst) => inst.delete());
19651
+ manifoldInstancesForCleanup.current = [];
19652
+ };
19653
+ }, [manifoldJSModule, circuitJson, boardData]);
19654
+ return {
19655
+ boardThreeGeom,
19656
+ boardColor,
19657
+ otherComponentGeoms,
19658
+ topTraceTexture,
19659
+ bottomTraceTexture,
19660
+ topSilkscreenTexture,
19661
+ bottomSilkscreenTexture,
19662
+ pcbThickness,
19663
+ error,
19664
+ isLoading,
19665
+ boardData
19666
+ };
19667
+ };
19668
+
19669
+ // src/CadViewerManifold.tsx
19670
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
19671
+ var CadViewerManifold = ({
19672
+ circuitJson,
19673
+ autoRotateDisabled,
19674
+ clickToInteractEnabled
19675
+ }) => {
19676
+ const [manifoldJSModule, setManifoldJSModule] = useState8(null);
19677
+ const [manifoldLoadingError, setManifoldLoadingError] = useState8(null);
19678
+ useEffect6(() => {
19679
+ const manifoldConfig = {
19680
+ locateFile: (path, scriptDirectory) => path === "manifold.wasm" ? "/manifold.wasm" : scriptDirectory + path
19681
+ };
19682
+ ManifoldModule(manifoldConfig).then((loadedModule) => {
19683
+ loadedModule.setup();
19684
+ setManifoldJSModule(loadedModule);
19685
+ }).catch(() => {
19686
+ setManifoldLoadingError(
19687
+ "Failed to load Manifold module. Check console for details."
19688
+ );
19689
+ });
19690
+ }, []);
19691
+ const {
19692
+ boardThreeGeom,
19693
+ boardColor,
19694
+ otherComponentGeoms,
19695
+ topTraceTexture,
19696
+ bottomTraceTexture,
19697
+ topSilkscreenTexture,
19698
+ bottomSilkscreenTexture,
19699
+ pcbThickness,
19700
+ error: builderError,
19701
+ isLoading: builderIsLoading,
19702
+ boardData
19703
+ } = useManifoldBoardBuilder(manifoldJSModule, circuitJson);
19704
+ const cadComponents = useMemo7(
19705
+ () => circuitJson ? su6(circuitJson).cad_component.list() : [],
19706
+ [circuitJson]
19707
+ );
19708
+ const initialCameraPosition = useMemo7(() => {
19709
+ if (!boardData) return [5, 5, 5];
19710
+ const { width = 0, height = 0 } = boardData;
19711
+ const safeWidth = Math.max(width, 1);
19712
+ const safeHeight = Math.max(height, 1);
19713
+ const largestDim = Math.max(safeWidth, safeHeight, 5);
19714
+ return [largestDim * 0.75, largestDim * 0.75, largestDim * 0.75];
19715
+ }, [boardData]);
19716
+ if (manifoldLoadingError) {
19717
+ return /* @__PURE__ */ jsxs8(
19718
+ "div",
19719
+ {
19720
+ style: {
19721
+ color: "red",
19722
+ padding: "1em",
19723
+ border: "1px solid red",
19724
+ margin: "1em"
19725
+ },
19726
+ children: [
19727
+ "Error: ",
19728
+ manifoldLoadingError
19729
+ ]
19730
+ }
19731
+ );
19732
+ }
19733
+ if (!manifoldJSModule) {
19734
+ return /* @__PURE__ */ jsx11("div", { style: { padding: "1em" }, children: "Loading Manifold module..." });
19735
+ }
19736
+ if (builderError) {
19737
+ return /* @__PURE__ */ jsxs8(
19738
+ "div",
19739
+ {
19740
+ style: {
19741
+ color: "red",
19742
+ padding: "1em",
19743
+ border: "1px solid red",
19744
+ margin: "1em"
19745
+ },
19746
+ children: [
19747
+ "Error: ",
19748
+ builderError
19749
+ ]
19750
+ }
19751
+ );
19752
+ }
19753
+ if (builderIsLoading || !boardData) {
19754
+ return /* @__PURE__ */ jsx11("div", { style: { padding: "1em" }, children: "Processing board geometry..." });
19755
+ }
19756
+ if (!boardThreeGeom) {
19757
+ return /* @__PURE__ */ jsx11("div", { style: { padding: "1em" }, children: "Preparing board display..." });
19758
+ }
19759
+ return /* @__PURE__ */ jsxs8(
19760
+ CadViewerContainer,
19761
+ {
19762
+ initialCameraPosition,
19763
+ autoRotateDisabled,
19764
+ clickToInteractEnabled,
19765
+ children: [
19766
+ /* @__PURE__ */ jsx11("mesh", { geometry: boardThreeGeom, children: /* @__PURE__ */ jsx11(
19767
+ "meshStandardMaterial",
19768
+ {
19769
+ color: boardColor,
19770
+ side: THREE8.DoubleSide,
19771
+ flatShading: true
19772
+ }
19773
+ ) }),
19774
+ topTraceTexture && boardData && pcbThickness !== null && /* @__PURE__ */ jsxs8(
19775
+ "mesh",
19776
+ {
19777
+ position: [
19778
+ boardData.center.x,
19779
+ boardData.center.y,
19780
+ pcbThickness / 2 + 0.015
19781
+ ],
19782
+ children: [
19783
+ /* @__PURE__ */ jsx11("planeGeometry", { args: [boardData.width, boardData.height] }),
19784
+ /* @__PURE__ */ jsx11(
19785
+ "meshBasicMaterial",
19786
+ {
19787
+ map: topTraceTexture,
19788
+ transparent: true,
19789
+ side: THREE8.DoubleSide,
19790
+ depthWrite: false
19791
+ }
19792
+ )
19793
+ ]
19794
+ }
19795
+ ),
19796
+ topSilkscreenTexture && boardData && pcbThickness !== null && /* @__PURE__ */ jsxs8(
19797
+ "mesh",
19798
+ {
19799
+ position: [
19800
+ boardData.center.x,
19801
+ boardData.center.y,
19802
+ pcbThickness / 2 + 0.017
19803
+ ],
19804
+ children: [
19805
+ /* @__PURE__ */ jsx11("planeGeometry", { args: [boardData.width, boardData.height] }),
19806
+ /* @__PURE__ */ jsx11(
19807
+ "meshBasicMaterial",
19808
+ {
19809
+ map: topSilkscreenTexture,
19810
+ transparent: true,
19811
+ side: THREE8.DoubleSide,
19812
+ depthWrite: false
19813
+ }
19814
+ )
19815
+ ]
19816
+ }
19817
+ ),
19818
+ bottomTraceTexture && boardData && pcbThickness !== null && /* @__PURE__ */ jsxs8(
19819
+ "mesh",
19820
+ {
19821
+ position: [
19822
+ boardData.center.x,
19823
+ boardData.center.y,
19824
+ -pcbThickness / 2 - 0.015
19825
+ ],
19826
+ rotation: [Math.PI, 0, 0],
19827
+ children: [
19828
+ /* @__PURE__ */ jsx11("planeGeometry", { args: [boardData.width, boardData.height] }),
19829
+ /* @__PURE__ */ jsx11(
19830
+ "meshBasicMaterial",
19831
+ {
19832
+ map: bottomTraceTexture,
19833
+ transparent: true,
19834
+ side: THREE8.DoubleSide,
19835
+ depthWrite: false
19836
+ }
19837
+ )
19838
+ ]
19839
+ }
19840
+ ),
19841
+ bottomSilkscreenTexture && boardData && pcbThickness !== null && /* @__PURE__ */ jsxs8(
19842
+ "mesh",
19843
+ {
19844
+ position: [
19845
+ boardData.center.x,
19846
+ boardData.center.y,
19847
+ -pcbThickness / 2 - 0.017
19848
+ ],
19849
+ rotation: [Math.PI, 0, 0],
19850
+ children: [
19851
+ /* @__PURE__ */ jsx11("planeGeometry", { args: [boardData.width, boardData.height] }),
19852
+ /* @__PURE__ */ jsx11(
19853
+ "meshBasicMaterial",
19854
+ {
19855
+ map: bottomSilkscreenTexture,
19856
+ transparent: true,
19857
+ side: THREE8.DoubleSide,
19858
+ depthWrite: false
19859
+ }
19860
+ )
19861
+ ]
19862
+ }
19863
+ ),
19864
+ otherComponentGeoms.map((comp) => /* @__PURE__ */ jsx11("mesh", { geometry: comp.geometry, children: /* @__PURE__ */ jsx11(
19865
+ "meshStandardMaterial",
19866
+ {
19867
+ color: comp.color,
19868
+ side: THREE8.DoubleSide,
19869
+ flatShading: true
19870
+ }
19871
+ ) }, comp.key)),
19872
+ cadComponents.map((cad_component) => /* @__PURE__ */ jsx11(
19873
+ ThreeErrorBoundary,
19874
+ {
19875
+ fallback: ({ error }) => /* @__PURE__ */ jsx11(Error3d, { cad_component, error }),
19876
+ children: /* @__PURE__ */ jsx11(
19877
+ AnyCadComponent,
19878
+ {
19879
+ cad_component,
19880
+ circuitJson
19881
+ }
19882
+ )
19883
+ },
19884
+ cad_component.cad_component_id
19885
+ ))
19886
+ ]
19887
+ }
19888
+ );
19889
+ };
19890
+ var CadViewerManifold_default = CadViewerManifold;
19891
+
19892
+ // src/CadViewer.tsx
19893
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
19894
+ var CadViewer = (props) => {
19895
+ const [engine, setEngine] = useState9("jscad");
19896
+ const [menuVisible, setMenuVisible] = useState9(false);
19897
+ const [menuPos, setMenuPos] = useState9({
19898
+ x: 0,
19899
+ y: 0
19900
+ });
19901
+ const containerRef = useRef7(null);
19902
+ const menuRef = useRef7(null);
19903
+ const handleContextMenu = useCallback4((e) => {
19904
+ e.preventDefault();
19905
+ setMenuPos({ x: e.clientX, y: e.clientY });
19906
+ setMenuVisible(true);
19907
+ }, []);
19908
+ const handleMenuClick = (newEngine) => {
19909
+ setEngine(newEngine);
19910
+ setMenuVisible(false);
19911
+ };
19912
+ const handleClickAway = useCallback4((e) => {
19913
+ const target = e.target;
19914
+ if (!menuRef.current || !menuRef.current.contains(target)) {
19915
+ setMenuVisible(false);
19916
+ }
19917
+ }, []);
19918
+ useEffect7(() => {
19919
+ if (menuVisible) {
19920
+ document.addEventListener("mousedown", handleClickAway);
19921
+ return () => document.removeEventListener("mousedown", handleClickAway);
19922
+ }
19923
+ }, [menuVisible, handleClickAway]);
19924
+ useEffect7(() => {
19925
+ const stored = window.localStorage.getItem("cadViewerEngine");
19926
+ if (stored === "jscad" || stored === "manifold") {
19927
+ setEngine(stored);
19928
+ }
19929
+ }, []);
19930
+ useEffect7(() => {
19931
+ window.localStorage.setItem("cadViewerEngine", engine);
19932
+ }, [engine]);
19933
+ const viewerKey = props.circuitJson ? JSON.stringify(props.circuitJson) : void 0;
19934
+ return /* @__PURE__ */ jsxs9(
19935
+ "div",
19936
+ {
19937
+ ref: containerRef,
19938
+ style: { width: "100%", height: "100%", position: "relative" },
19939
+ onContextMenu: handleContextMenu,
19940
+ children: [
19941
+ engine === "jscad" ? /* @__PURE__ */ jsx12(CadViewerJscad, { ...props }) : /* @__PURE__ */ jsx12(CadViewerManifold_default, { ...props }),
19942
+ /* @__PURE__ */ jsxs9(
19943
+ "div",
19944
+ {
19945
+ style: {
19946
+ position: "absolute",
19947
+ right: 8,
19948
+ top: 8,
19949
+ background: "#222",
19950
+ color: "#fff",
19951
+ padding: "2px 8px",
19952
+ borderRadius: 4,
19953
+ fontSize: 12,
19954
+ opacity: 0.7,
19955
+ userSelect: "none"
19956
+ },
19957
+ onClick: () => {
19958
+ if ("ontouchstart" in window) {
19959
+ setEngine(engine === "jscad" ? "manifold" : "jscad");
19960
+ }
19961
+ },
19962
+ children: [
19963
+ "Engine: ",
19964
+ /* @__PURE__ */ jsx12("b", { children: engine === "jscad" ? "JSCAD" : "Manifold" })
19965
+ ]
19966
+ }
19967
+ ),
19968
+ menuVisible && /* @__PURE__ */ jsx12(
19969
+ "div",
19970
+ {
19971
+ ref: menuRef,
19972
+ style: {
19973
+ position: "fixed",
19974
+ top: menuPos.y,
19975
+ left: menuPos.x,
19976
+ background: "#23272f",
19977
+ color: "#f5f6fa",
19978
+ borderRadius: 6,
19979
+ boxShadow: "0 6px 24px 0 rgba(0,0,0,0.18)",
19980
+ zIndex: 1e3,
19981
+ minWidth: 200,
19982
+ border: "1px solid #353945",
19983
+ padding: 0,
19984
+ fontSize: 15,
19985
+ fontWeight: 500,
19986
+ transition: "opacity 0.1s"
19987
+ },
19988
+ children: /* @__PURE__ */ jsxs9(
19989
+ "div",
19990
+ {
19991
+ style: {
19992
+ padding: "12px 18px",
19993
+ cursor: "pointer",
19994
+ display: "flex",
19995
+ alignItems: "center",
19996
+ gap: 10,
19997
+ color: "#f5f6fa",
19998
+ fontWeight: 500,
19999
+ borderRadius: 6,
20000
+ transition: "background 0.1s"
20001
+ },
20002
+ onClick: () => handleMenuClick(engine === "jscad" ? "manifold" : "jscad"),
20003
+ onMouseOver: (e) => e.currentTarget.style.background = "#2d313a",
20004
+ onMouseOut: (e) => e.currentTarget.style.background = "transparent",
20005
+ children: [
20006
+ "Switch to ",
20007
+ engine === "jscad" ? "Manifold" : "JSCAD",
20008
+ " Engine",
20009
+ /* @__PURE__ */ jsx12(
20010
+ "span",
20011
+ {
20012
+ style: {
20013
+ fontSize: 12,
20014
+ marginLeft: "auto",
20015
+ opacity: 0.5,
20016
+ fontWeight: 400
20017
+ },
20018
+ children: engine === "jscad" ? "experimental" : "default"
20019
+ }
20020
+ )
20021
+ ]
20022
+ }
20023
+ )
20024
+ }
20025
+ )
20026
+ ]
20027
+ },
20028
+ viewerKey
20029
+ );
20030
+ };
20031
+
20032
+ // src/convert-circuit-json-to-3d-svg.ts
20033
+ var import_debug = __toESM(require_browser(), 1);
20034
+ import { su as su7 } from "@tscircuit/soup-util";
20035
+ import * as THREE12 from "three";
18969
20036
  import { SVGRenderer } from "three/examples/jsm/renderers/SVGRenderer.js";
18970
20037
 
18971
20038
  // src/utils/create-geometry-from-polygons.ts
18972
- import * as THREE4 from "three";
18973
- import { BufferGeometry as BufferGeometry3, Float32BufferAttribute as Float32BufferAttribute3 } from "three";
20039
+ import * as THREE9 from "three";
20040
+ import { BufferGeometry as BufferGeometry4, Float32BufferAttribute as Float32BufferAttribute4 } from "three";
18974
20041
  function createGeometryFromPolygons(polygons) {
18975
- const geometry = new BufferGeometry3();
20042
+ const geometry = new BufferGeometry4();
18976
20043
  const vertices = [];
18977
20044
  const normals = [];
18978
20045
  for (const polygon2 of polygons) {
@@ -18983,12 +20050,12 @@ function createGeometryFromPolygons(polygons) {
18983
20050
  ...polygon2.vertices[i + 1]
18984
20051
  // Third vertex
18985
20052
  );
18986
- const v1 = new THREE4.Vector3(...polygon2.vertices[0]);
18987
- const v2 = new THREE4.Vector3(...polygon2.vertices[i]);
18988
- const v3 = new THREE4.Vector3(...polygon2.vertices[i + 1]);
18989
- const normal = new THREE4.Vector3().crossVectors(
18990
- new THREE4.Vector3().subVectors(v2, v1),
18991
- new THREE4.Vector3().subVectors(v3, v1)
20053
+ const v1 = new THREE9.Vector3(...polygon2.vertices[0]);
20054
+ const v2 = new THREE9.Vector3(...polygon2.vertices[i]);
20055
+ const v3 = new THREE9.Vector3(...polygon2.vertices[i + 1]);
20056
+ const normal = new THREE9.Vector3().crossVectors(
20057
+ new THREE9.Vector3().subVectors(v2, v1),
20058
+ new THREE9.Vector3().subVectors(v3, v1)
18992
20059
  ).normalize();
18993
20060
  normals.push(
18994
20061
  normal.x,
@@ -19003,8 +20070,8 @@ function createGeometryFromPolygons(polygons) {
19003
20070
  );
19004
20071
  }
19005
20072
  }
19006
- geometry.setAttribute("position", new Float32BufferAttribute3(vertices, 3));
19007
- geometry.setAttribute("normal", new Float32BufferAttribute3(normals, 3));
20073
+ geometry.setAttribute("position", new Float32BufferAttribute4(vertices, 3));
20074
+ geometry.setAttribute("normal", new Float32BufferAttribute4(normals, 3));
19008
20075
  return geometry;
19009
20076
  }
19010
20077
 
@@ -19014,22 +20081,22 @@ import { Footprinter3d as Footprinter3d2 } from "jscad-electronics";
19014
20081
  import { convertCSGToThreeGeom as convertCSGToThreeGeom2 } from "jscad-fiber/three";
19015
20082
  import { createJSCADRenderer as createJSCADRenderer2 } from "jscad-fiber";
19016
20083
  import { executeJscadOperations as executeJscadOperations2, jscadPlanner as jscadPlanner2 } from "jscad-planner";
19017
- import * as THREE6 from "three";
20084
+ import * as THREE11 from "three";
19018
20085
 
19019
20086
  // src/utils/load-model.ts
19020
- import * as THREE5 from "three";
20087
+ import * as THREE10 from "three";
19021
20088
  import { OBJLoader as OBJLoader3 } from "three/examples/jsm/loaders/OBJLoader.js";
19022
20089
  import { STLLoader as STLLoader2 } from "three/examples/jsm/loaders/STLLoader.js";
19023
20090
  async function load3DModel(url) {
19024
20091
  if (url.endsWith(".stl")) {
19025
20092
  const loader = new STLLoader2();
19026
20093
  const geometry = await loader.loadAsync(url);
19027
- const material = new THREE5.MeshStandardMaterial({
20094
+ const material = new THREE10.MeshStandardMaterial({
19028
20095
  color: 8947848,
19029
20096
  metalness: 0.5,
19030
20097
  roughness: 0.5
19031
20098
  });
19032
- return new THREE5.Mesh(geometry, material);
20099
+ return new THREE10.Mesh(geometry, material);
19033
20100
  }
19034
20101
  if (url.endsWith(".obj")) {
19035
20102
  const loader = new OBJLoader3();
@@ -19040,7 +20107,7 @@ async function load3DModel(url) {
19040
20107
  }
19041
20108
 
19042
20109
  // src/utils/render-component.tsx
19043
- import { jsx as jsx11 } from "react/jsx-runtime";
20110
+ import { jsx as jsx13 } from "react/jsx-runtime";
19044
20111
  var { createJSCADRoot: createJSCADRoot2 } = createJSCADRenderer2(jscadPlanner2);
19045
20112
  async function renderComponent(component, scene) {
19046
20113
  const url = component.model_obj_url ?? component.model_stl_url;
@@ -19056,9 +20123,9 @@ async function renderComponent(component, scene) {
19056
20123
  }
19057
20124
  if (component.rotation) {
19058
20125
  model.rotation.set(
19059
- THREE6.MathUtils.degToRad(component.rotation.x ?? 0),
19060
- THREE6.MathUtils.degToRad(component.rotation.y ?? 0),
19061
- THREE6.MathUtils.degToRad(component.rotation.z ?? 0)
20126
+ THREE11.MathUtils.degToRad(component.rotation.x ?? 0),
20127
+ THREE11.MathUtils.degToRad(component.rotation.y ?? 0),
20128
+ THREE11.MathUtils.degToRad(component.rotation.z ?? 0)
19062
20129
  );
19063
20130
  }
19064
20131
  scene.add(model);
@@ -19071,13 +20138,13 @@ async function renderComponent(component, scene) {
19071
20138
  component.model_jscad
19072
20139
  );
19073
20140
  const threeGeom = convertCSGToThreeGeom2(jscadObject);
19074
- const material2 = new THREE6.MeshStandardMaterial({
20141
+ const material2 = new THREE11.MeshStandardMaterial({
19075
20142
  color: 8947848,
19076
20143
  metalness: 0.5,
19077
20144
  roughness: 0.5,
19078
- side: THREE6.DoubleSide
20145
+ side: THREE11.DoubleSide
19079
20146
  });
19080
- const mesh2 = new THREE6.Mesh(threeGeom, material2);
20147
+ const mesh2 = new THREE11.Mesh(threeGeom, material2);
19081
20148
  if (component.position) {
19082
20149
  mesh2.position.set(
19083
20150
  component.position.x ?? 0,
@@ -19087,9 +20154,9 @@ async function renderComponent(component, scene) {
19087
20154
  }
19088
20155
  if (component.rotation) {
19089
20156
  mesh2.rotation.set(
19090
- THREE6.MathUtils.degToRad(component.rotation.x ?? 0),
19091
- THREE6.MathUtils.degToRad(component.rotation.y ?? 0),
19092
- THREE6.MathUtils.degToRad(component.rotation.z ?? 0)
20157
+ THREE11.MathUtils.degToRad(component.rotation.x ?? 0),
20158
+ THREE11.MathUtils.degToRad(component.rotation.y ?? 0),
20159
+ THREE11.MathUtils.degToRad(component.rotation.z ?? 0)
19093
20160
  );
19094
20161
  }
19095
20162
  scene.add(mesh2);
@@ -19098,17 +20165,17 @@ async function renderComponent(component, scene) {
19098
20165
  if (component.footprinter_string) {
19099
20166
  const jscadOperations = [];
19100
20167
  const root = createJSCADRoot2(jscadOperations);
19101
- root.render(/* @__PURE__ */ jsx11(Footprinter3d2, { footprint: component.footprinter_string }));
20168
+ root.render(/* @__PURE__ */ jsx13(Footprinter3d2, { footprint: component.footprinter_string }));
19102
20169
  for (const operation of jscadOperations) {
19103
20170
  const jscadObject = executeJscadOperations2(import_modeling2.default, operation);
19104
20171
  const threeGeom = convertCSGToThreeGeom2(jscadObject);
19105
- const material2 = new THREE6.MeshStandardMaterial({
20172
+ const material2 = new THREE11.MeshStandardMaterial({
19106
20173
  color: 4473924,
19107
20174
  metalness: 0.2,
19108
20175
  roughness: 0.8,
19109
- side: THREE6.DoubleSide
20176
+ side: THREE11.DoubleSide
19110
20177
  });
19111
- const mesh2 = new THREE6.Mesh(threeGeom, material2);
20178
+ const mesh2 = new THREE11.Mesh(threeGeom, material2);
19112
20179
  if (component.position) {
19113
20180
  mesh2.position.set(
19114
20181
  component.position.x ?? 0,
@@ -19118,22 +20185,22 @@ async function renderComponent(component, scene) {
19118
20185
  }
19119
20186
  if (component.rotation) {
19120
20187
  mesh2.rotation.set(
19121
- THREE6.MathUtils.degToRad(component.rotation.x ?? 0),
19122
- THREE6.MathUtils.degToRad(component.rotation.y ?? 0),
19123
- THREE6.MathUtils.degToRad(component.rotation.z ?? 0)
20188
+ THREE11.MathUtils.degToRad(component.rotation.x ?? 0),
20189
+ THREE11.MathUtils.degToRad(component.rotation.y ?? 0),
20190
+ THREE11.MathUtils.degToRad(component.rotation.z ?? 0)
19124
20191
  );
19125
20192
  }
19126
20193
  scene.add(mesh2);
19127
20194
  }
19128
20195
  return;
19129
20196
  }
19130
- const geometry = new THREE6.BoxGeometry(0.5, 0.5, 0.5);
19131
- const material = new THREE6.MeshStandardMaterial({
20197
+ const geometry = new THREE11.BoxGeometry(0.5, 0.5, 0.5);
20198
+ const material = new THREE11.MeshStandardMaterial({
19132
20199
  color: 16711680,
19133
20200
  transparent: true,
19134
20201
  opacity: 0.25
19135
20202
  });
19136
- const mesh = new THREE6.Mesh(geometry, material);
20203
+ const mesh = new THREE11.Mesh(geometry, material);
19137
20204
  if (component.position) {
19138
20205
  mesh.position.set(
19139
20206
  component.position.x ?? 0,
@@ -19154,11 +20221,11 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
19154
20221
  padding = 20,
19155
20222
  zoom = 1.5
19156
20223
  } = options;
19157
- const scene = new THREE7.Scene();
20224
+ const scene = new THREE12.Scene();
19158
20225
  const renderer = new SVGRenderer();
19159
20226
  renderer.setSize(width, height);
19160
- renderer.setClearColor(new THREE7.Color(backgroundColor), 1);
19161
- const camera = new THREE7.OrthographicCamera();
20227
+ renderer.setClearColor(new THREE12.Color(backgroundColor), 1);
20228
+ const camera = new THREE12.OrthographicCamera();
19162
20229
  const aspect = width / height;
19163
20230
  const frustumSize = 100;
19164
20231
  const halfFrustumSize = frustumSize / 2 / zoom;
@@ -19172,14 +20239,14 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
19172
20239
  camera.position.set(position.x, position.y, position.z);
19173
20240
  camera.up.set(0, 1, 0);
19174
20241
  const lookAt = options.camera?.lookAt ?? { x: 0, y: 0, z: 0 };
19175
- camera.lookAt(new THREE7.Vector3(lookAt.x, lookAt.y, lookAt.z));
20242
+ camera.lookAt(new THREE12.Vector3(lookAt.x, lookAt.y, lookAt.z));
19176
20243
  camera.updateProjectionMatrix();
19177
- const ambientLight = new THREE7.AmbientLight(16777215, Math.PI / 2);
20244
+ const ambientLight = new THREE12.AmbientLight(16777215, Math.PI / 2);
19178
20245
  scene.add(ambientLight);
19179
- const pointLight = new THREE7.PointLight(16777215, Math.PI / 4);
20246
+ const pointLight = new THREE12.PointLight(16777215, Math.PI / 4);
19180
20247
  pointLight.position.set(-10, -10, 10);
19181
20248
  scene.add(pointLight);
19182
- const components = su5(circuitJson).cad_component.list();
20249
+ const components = su7(circuitJson).cad_component.list();
19183
20250
  for (const component of components) {
19184
20251
  await renderComponent(component, scene);
19185
20252
  }
@@ -19187,8 +20254,8 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
19187
20254
  if (boardGeom) {
19188
20255
  for (const geom of boardGeom) {
19189
20256
  const geometry = createGeometryFromPolygons(geom.polygons);
19190
- const material = new THREE7.MeshStandardMaterial({
19191
- color: new THREE7.Color(
20257
+ const material = new THREE12.MeshStandardMaterial({
20258
+ color: new THREE12.Color(
19192
20259
  geom.color?.[0] ?? 0,
19193
20260
  geom.color?.[1] ?? 0,
19194
20261
  geom.color?.[2] ?? 0
@@ -19197,18 +20264,18 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
19197
20264
  roughness: 0.8,
19198
20265
  opacity: 0.9,
19199
20266
  transparent: true,
19200
- side: THREE7.DoubleSide
20267
+ side: THREE12.DoubleSide
19201
20268
  });
19202
- const mesh = new THREE7.Mesh(geometry, material);
20269
+ const mesh = new THREE12.Mesh(geometry, material);
19203
20270
  scene.add(mesh);
19204
20271
  }
19205
20272
  }
19206
- const gridHelper = new THREE7.GridHelper(100, 100);
20273
+ const gridHelper = new THREE12.GridHelper(100, 100);
19207
20274
  gridHelper.rotation.x = Math.PI / 2;
19208
20275
  scene.add(gridHelper);
19209
- const box = new THREE7.Box3().setFromObject(scene);
19210
- const center = box.getCenter(new THREE7.Vector3());
19211
- const size = box.getSize(new THREE7.Vector3());
20276
+ const box = new THREE12.Box3().setFromObject(scene);
20277
+ const center = box.getCenter(new THREE12.Vector3());
20278
+ const size = box.getSize(new THREE12.Vector3());
19212
20279
  scene.position.sub(center);
19213
20280
  const maxDim = Math.max(size.x, size.y, size.z);
19214
20281
  if (maxDim > 0) {
@@ -19225,10 +20292,10 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
19225
20292
  }
19226
20293
 
19227
20294
  // src/hooks/exporter/gltf.ts
19228
- import { useEffect as useEffect5, useState as useState7, useMemo as useMemo6, useCallback as useCallback4 } from "react";
20295
+ import { useEffect as useEffect8, useState as useState10, useMemo as useMemo8, useCallback as useCallback5 } from "react";
19229
20296
  function useSaveGltfAs(options = {}) {
19230
20297
  const parse = useParser(options);
19231
- const link = useMemo6(() => document.createElement("a"), []);
20298
+ const link = useMemo8(() => document.createElement("a"), []);
19232
20299
  const saveAs = async (filename) => {
19233
20300
  const name = filename ?? options.filename ?? "";
19234
20301
  if (options.binary == null) options.binary = name.endsWith(".glb");
@@ -19238,7 +20305,7 @@ function useSaveGltfAs(options = {}) {
19238
20305
  link.dispatchEvent(new MouseEvent("click"));
19239
20306
  URL.revokeObjectURL(url);
19240
20307
  };
19241
- useEffect5(
20308
+ useEffect8(
19242
20309
  () => () => {
19243
20310
  link.remove();
19244
20311
  instance = null;
@@ -19246,24 +20313,24 @@ function useSaveGltfAs(options = {}) {
19246
20313
  []
19247
20314
  );
19248
20315
  let instance;
19249
- const ref = useCallback4((obj3D) => {
20316
+ const ref = useCallback5((obj3D) => {
19250
20317
  instance = obj3D;
19251
20318
  }, []);
19252
20319
  return [ref, saveAs];
19253
20320
  }
19254
20321
  function useExportGltfUrl(options = {}) {
19255
20322
  const parse = useParser(options);
19256
- const [url, setUrl] = useState7();
19257
- const [error, setError] = useState7();
19258
- const ref = useCallback4(
20323
+ const [url, setUrl] = useState10();
20324
+ const [error, setError] = useState10();
20325
+ const ref = useCallback5(
19259
20326
  (instance) => parse(instance).then(setUrl).catch(setError),
19260
20327
  []
19261
20328
  );
19262
- useEffect5(() => () => URL.revokeObjectURL(url), [url]);
20329
+ useEffect8(() => () => URL.revokeObjectURL(url), [url]);
19263
20330
  return [ref, url, error];
19264
20331
  }
19265
20332
  function useParser(options = {}) {
19266
- const exporter = useMemo6(() => new GLTFExporter(), []);
20333
+ const exporter = useMemo8(() => new GLTFExporter(), []);
19267
20334
  return (instance) => {
19268
20335
  const { promise, resolve, reject } = Promise.withResolvers();
19269
20336
  exporter.parse(
@@ -19293,5 +20360,6 @@ export {
19293
20360
  applyJsdomShim,
19294
20361
  convertCircuitJsonTo3dSvg,
19295
20362
  useExportGltfUrl,
20363
+ useManifoldBoardBuilder,
19296
20364
  useSaveGltfAs
19297
20365
  };