@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.
- package/dist/index.d.ts +24 -14
- package/dist/index.js +1197 -129
- 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
|
|
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 =
|
|
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
|
|
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/
|
|
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.
|
|
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:
|
|
17406
|
+
height: ctx.pcbThickness
|
|
17403
17407
|
}),
|
|
17404
17408
|
(0, import_primitives3.cylinder)({
|
|
17405
|
-
center: [
|
|
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: [
|
|
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
|
|
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
|
|
17464
|
-
|
|
17465
|
-
|
|
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
|
|
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:
|
|
17485
|
+
height: ctx.pcbThickness
|
|
17471
17486
|
});
|
|
17472
|
-
const
|
|
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:
|
|
17490
|
+
height: ctx.pcbThickness
|
|
17476
17491
|
});
|
|
17477
|
-
const
|
|
17478
|
-
|
|
17479
|
-
|
|
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
|
|
17482
|
-
center: shouldRotate ? [plated_hole.x, plated_hole.y -
|
|
17483
|
-
radius:
|
|
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
|
|
17487
|
-
center: shouldRotate ? [plated_hole.x, plated_hole.y +
|
|
17488
|
-
radius:
|
|
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
|
|
17492
|
-
|
|
17493
|
-
|
|
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
|
|
17496
|
-
center: shouldRotate ? [plated_hole.x, plated_hole.y -
|
|
17497
|
-
radius:
|
|
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
|
|
17501
|
-
center: shouldRotate ? [plated_hole.x, plated_hole.y +
|
|
17502
|
-
radius:
|
|
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/
|
|
18907
|
+
// src/CadViewerJscad.tsx
|
|
18897
18908
|
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
18898
|
-
var
|
|
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 ??
|
|
18903
|
-
}, [soup, circuitJson,
|
|
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/
|
|
18966
|
-
|
|
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
|
|
18973
|
-
import { BufferGeometry as
|
|
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
|
|
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
|
|
18987
|
-
const v2 = new
|
|
18988
|
-
const v3 = new
|
|
18989
|
-
const normal = new
|
|
18990
|
-
new
|
|
18991
|
-
new
|
|
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
|
|
19007
|
-
geometry.setAttribute("normal", new
|
|
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
|
|
20084
|
+
import * as THREE11 from "three";
|
|
19018
20085
|
|
|
19019
20086
|
// src/utils/load-model.ts
|
|
19020
|
-
import * as
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
19060
|
-
|
|
19061
|
-
|
|
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
|
|
20141
|
+
const material2 = new THREE11.MeshStandardMaterial({
|
|
19075
20142
|
color: 8947848,
|
|
19076
20143
|
metalness: 0.5,
|
|
19077
20144
|
roughness: 0.5,
|
|
19078
|
-
side:
|
|
20145
|
+
side: THREE11.DoubleSide
|
|
19079
20146
|
});
|
|
19080
|
-
const mesh2 = new
|
|
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
|
-
|
|
19091
|
-
|
|
19092
|
-
|
|
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__ */
|
|
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
|
|
20172
|
+
const material2 = new THREE11.MeshStandardMaterial({
|
|
19106
20173
|
color: 4473924,
|
|
19107
20174
|
metalness: 0.2,
|
|
19108
20175
|
roughness: 0.8,
|
|
19109
|
-
side:
|
|
20176
|
+
side: THREE11.DoubleSide
|
|
19110
20177
|
});
|
|
19111
|
-
const mesh2 = new
|
|
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
|
-
|
|
19122
|
-
|
|
19123
|
-
|
|
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
|
|
19131
|
-
const material = new
|
|
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
|
|
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
|
|
20224
|
+
const scene = new THREE12.Scene();
|
|
19158
20225
|
const renderer = new SVGRenderer();
|
|
19159
20226
|
renderer.setSize(width, height);
|
|
19160
|
-
renderer.setClearColor(new
|
|
19161
|
-
const camera = new
|
|
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
|
|
20242
|
+
camera.lookAt(new THREE12.Vector3(lookAt.x, lookAt.y, lookAt.z));
|
|
19176
20243
|
camera.updateProjectionMatrix();
|
|
19177
|
-
const ambientLight = new
|
|
20244
|
+
const ambientLight = new THREE12.AmbientLight(16777215, Math.PI / 2);
|
|
19178
20245
|
scene.add(ambientLight);
|
|
19179
|
-
const pointLight = new
|
|
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 =
|
|
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
|
|
19191
|
-
color: new
|
|
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:
|
|
20267
|
+
side: THREE12.DoubleSide
|
|
19201
20268
|
});
|
|
19202
|
-
const mesh = new
|
|
20269
|
+
const mesh = new THREE12.Mesh(geometry, material);
|
|
19203
20270
|
scene.add(mesh);
|
|
19204
20271
|
}
|
|
19205
20272
|
}
|
|
19206
|
-
const gridHelper = new
|
|
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
|
|
19210
|
-
const center = box.getCenter(new
|
|
19211
|
-
const size = box.getSize(new
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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] =
|
|
19257
|
-
const [error, setError] =
|
|
19258
|
-
const ref =
|
|
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
|
-
|
|
20329
|
+
useEffect8(() => () => URL.revokeObjectURL(url), [url]);
|
|
19263
20330
|
return [ref, url, error];
|
|
19264
20331
|
}
|
|
19265
20332
|
function useParser(options = {}) {
|
|
19266
|
-
const exporter =
|
|
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
|
};
|