@tscircuit/3d-viewer 0.0.450 → 0.0.452
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 +2 -0
- package/dist/index.js +1137 -857
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14229,7 +14229,7 @@ var require_browser = __commonJS({
|
|
|
14229
14229
|
|
|
14230
14230
|
// src/CadViewer.tsx
|
|
14231
14231
|
import { useState as useState36, useCallback as useCallback22, useRef as useRef26, useEffect as useEffect42, useMemo as useMemo28 } from "react";
|
|
14232
|
-
import * as
|
|
14232
|
+
import * as THREE30 from "three";
|
|
14233
14233
|
|
|
14234
14234
|
// src/CadViewerJscad.tsx
|
|
14235
14235
|
import { su as su6 } from "@tscircuit/circuit-json-util";
|
|
@@ -14530,12 +14530,28 @@ function MixedStlModel({
|
|
|
14530
14530
|
onHover,
|
|
14531
14531
|
onUnhover,
|
|
14532
14532
|
isHovered,
|
|
14533
|
-
scale: scale3
|
|
14533
|
+
scale: scale3,
|
|
14534
|
+
isTranslucent = false
|
|
14534
14535
|
}) {
|
|
14535
14536
|
const obj = useGlobalObjLoader(url);
|
|
14536
14537
|
const { rootObject } = useThree();
|
|
14537
14538
|
const model = useMemo2(() => {
|
|
14538
14539
|
if (obj && !(obj instanceof Error)) {
|
|
14540
|
+
obj.traverse((child) => {
|
|
14541
|
+
if (child instanceof THREE2.Mesh && child.material) {
|
|
14542
|
+
const setMaterialTransparency = (mat) => {
|
|
14543
|
+
mat.transparent = isTranslucent;
|
|
14544
|
+
mat.opacity = isTranslucent ? 0.5 : 1;
|
|
14545
|
+
mat.depthWrite = !isTranslucent;
|
|
14546
|
+
mat.needsUpdate = true;
|
|
14547
|
+
};
|
|
14548
|
+
if (Array.isArray(child.material)) {
|
|
14549
|
+
child.material.forEach(setMaterialTransparency);
|
|
14550
|
+
} else {
|
|
14551
|
+
setMaterialTransparency(child.material);
|
|
14552
|
+
}
|
|
14553
|
+
}
|
|
14554
|
+
});
|
|
14539
14555
|
return obj;
|
|
14540
14556
|
}
|
|
14541
14557
|
return new THREE2.Mesh(
|
|
@@ -14546,7 +14562,7 @@ function MixedStlModel({
|
|
|
14546
14562
|
opacity: 0.25
|
|
14547
14563
|
})
|
|
14548
14564
|
);
|
|
14549
|
-
}, [obj]);
|
|
14565
|
+
}, [obj, isTranslucent]);
|
|
14550
14566
|
useEffect4(() => {
|
|
14551
14567
|
if (!rootObject || !model) return;
|
|
14552
14568
|
rootObject.add(model);
|
|
@@ -14639,7 +14655,8 @@ function GltfModel({
|
|
|
14639
14655
|
onHover,
|
|
14640
14656
|
onUnhover,
|
|
14641
14657
|
isHovered,
|
|
14642
|
-
scale: scale3
|
|
14658
|
+
scale: scale3,
|
|
14659
|
+
isTranslucent = false
|
|
14643
14660
|
}) {
|
|
14644
14661
|
const { renderer, rootObject } = useThree();
|
|
14645
14662
|
const [model, setModel] = useState3(null);
|
|
@@ -14651,7 +14668,24 @@ function GltfModel({
|
|
|
14651
14668
|
loader.load(
|
|
14652
14669
|
gltfUrl,
|
|
14653
14670
|
(gltf) => {
|
|
14654
|
-
if (isMounted)
|
|
14671
|
+
if (!isMounted) return;
|
|
14672
|
+
const scene = gltf.scene;
|
|
14673
|
+
scene.traverse((child) => {
|
|
14674
|
+
if (child instanceof THREE4.Mesh && child.material) {
|
|
14675
|
+
const setMaterialTransparency = (mat) => {
|
|
14676
|
+
mat.transparent = isTranslucent;
|
|
14677
|
+
mat.opacity = isTranslucent ? 0.5 : 1;
|
|
14678
|
+
mat.depthWrite = !isTranslucent;
|
|
14679
|
+
mat.needsUpdate = true;
|
|
14680
|
+
};
|
|
14681
|
+
if (Array.isArray(child.material)) {
|
|
14682
|
+
child.material.forEach(setMaterialTransparency);
|
|
14683
|
+
} else {
|
|
14684
|
+
setMaterialTransparency(child.material);
|
|
14685
|
+
}
|
|
14686
|
+
}
|
|
14687
|
+
});
|
|
14688
|
+
setModel(scene);
|
|
14655
14689
|
},
|
|
14656
14690
|
void 0,
|
|
14657
14691
|
(error) => {
|
|
@@ -14664,7 +14698,7 @@ function GltfModel({
|
|
|
14664
14698
|
return () => {
|
|
14665
14699
|
isMounted = false;
|
|
14666
14700
|
};
|
|
14667
|
-
}, [gltfUrl]);
|
|
14701
|
+
}, [gltfUrl, isTranslucent]);
|
|
14668
14702
|
useEffect5(() => {
|
|
14669
14703
|
if (!model) return;
|
|
14670
14704
|
if (position) model.position.fromArray(position);
|
|
@@ -28045,7 +28079,8 @@ var JscadModel = ({
|
|
|
28045
28079
|
onHover,
|
|
28046
28080
|
onUnhover,
|
|
28047
28081
|
isHovered,
|
|
28048
|
-
scale: scale3
|
|
28082
|
+
scale: scale3,
|
|
28083
|
+
isTranslucent = false
|
|
28049
28084
|
}) => {
|
|
28050
28085
|
const { rootObject } = useThree();
|
|
28051
28086
|
const { threeGeom, material } = useMemo4(() => {
|
|
@@ -28056,11 +28091,13 @@ var JscadModel = ({
|
|
|
28056
28091
|
const threeGeom2 = convertCSGToThreeGeom(jscadObject);
|
|
28057
28092
|
const material2 = new THREE5.MeshStandardMaterial({
|
|
28058
28093
|
vertexColors: true,
|
|
28059
|
-
side: THREE5.DoubleSide
|
|
28060
|
-
|
|
28094
|
+
side: THREE5.DoubleSide,
|
|
28095
|
+
transparent: isTranslucent,
|
|
28096
|
+
opacity: isTranslucent ? 0.5 : 1,
|
|
28097
|
+
depthWrite: !isTranslucent
|
|
28061
28098
|
});
|
|
28062
28099
|
return { threeGeom: threeGeom2, material: material2 };
|
|
28063
|
-
}, [jscadPlan]);
|
|
28100
|
+
}, [jscadPlan, isTranslucent]);
|
|
28064
28101
|
const mesh = useMemo4(() => {
|
|
28065
28102
|
if (!threeGeom) return null;
|
|
28066
28103
|
return new THREE5.Mesh(threeGeom, material);
|
|
@@ -28122,7 +28159,8 @@ var FootprinterModel = ({
|
|
|
28122
28159
|
onHover,
|
|
28123
28160
|
onUnhover,
|
|
28124
28161
|
isHovered,
|
|
28125
|
-
scale: scale3
|
|
28162
|
+
scale: scale3,
|
|
28163
|
+
isTranslucent = false
|
|
28126
28164
|
}) => {
|
|
28127
28165
|
const { rootObject } = useThree();
|
|
28128
28166
|
const group = useMemo5(() => {
|
|
@@ -28140,13 +28178,16 @@ var FootprinterModel = ({
|
|
|
28140
28178
|
const threeGeom = convertCSGToThreeGeom(geomWithColor);
|
|
28141
28179
|
const material = new THREE6.MeshStandardMaterial({
|
|
28142
28180
|
vertexColors: true,
|
|
28143
|
-
side: THREE6.DoubleSide
|
|
28181
|
+
side: THREE6.DoubleSide,
|
|
28182
|
+
transparent: isTranslucent,
|
|
28183
|
+
opacity: isTranslucent ? 0.5 : 1,
|
|
28184
|
+
depthWrite: !isTranslucent
|
|
28144
28185
|
});
|
|
28145
28186
|
const mesh = new THREE6.Mesh(threeGeom, material);
|
|
28146
28187
|
group2.add(mesh);
|
|
28147
28188
|
}
|
|
28148
28189
|
return group2;
|
|
28149
|
-
}, [footprint]);
|
|
28190
|
+
}, [footprint, isTranslucent]);
|
|
28150
28191
|
useEffect7(() => {
|
|
28151
28192
|
if (!group || !rootObject) return;
|
|
28152
28193
|
rootObject.add(group);
|
|
@@ -28368,7 +28409,8 @@ var AnyCadComponent = ({
|
|
|
28368
28409
|
scale: cad_component2.model_unit_to_mm_scale_factor,
|
|
28369
28410
|
onHover: handleHover,
|
|
28370
28411
|
onUnhover: handleUnhover,
|
|
28371
|
-
isHovered
|
|
28412
|
+
isHovered,
|
|
28413
|
+
isTranslucent: cad_component2.show_as_translucent_model
|
|
28372
28414
|
},
|
|
28373
28415
|
cad_component2.cad_component_id
|
|
28374
28416
|
);
|
|
@@ -28386,7 +28428,8 @@ var AnyCadComponent = ({
|
|
|
28386
28428
|
scale: cad_component2.model_unit_to_mm_scale_factor,
|
|
28387
28429
|
onHover: handleHover,
|
|
28388
28430
|
onUnhover: handleUnhover,
|
|
28389
|
-
isHovered
|
|
28431
|
+
isHovered,
|
|
28432
|
+
isTranslucent: cad_component2.show_as_translucent_model
|
|
28390
28433
|
},
|
|
28391
28434
|
cad_component2.cad_component_id
|
|
28392
28435
|
);
|
|
@@ -28399,7 +28442,8 @@ var AnyCadComponent = ({
|
|
|
28399
28442
|
scale: cad_component2.model_unit_to_mm_scale_factor,
|
|
28400
28443
|
onHover: handleHover,
|
|
28401
28444
|
onUnhover: handleUnhover,
|
|
28402
|
-
isHovered
|
|
28445
|
+
isHovered,
|
|
28446
|
+
isTranslucent: cad_component2.show_as_translucent_model
|
|
28403
28447
|
},
|
|
28404
28448
|
cad_component2.cad_component_id
|
|
28405
28449
|
);
|
|
@@ -28417,7 +28461,8 @@ var AnyCadComponent = ({
|
|
|
28417
28461
|
scale: cad_component2.model_unit_to_mm_scale_factor,
|
|
28418
28462
|
onHover: handleHover,
|
|
28419
28463
|
onUnhover: handleUnhover,
|
|
28420
|
-
isHovered
|
|
28464
|
+
isHovered,
|
|
28465
|
+
isTranslucent: cad_component2.show_as_translucent_model
|
|
28421
28466
|
}
|
|
28422
28467
|
);
|
|
28423
28468
|
}
|
|
@@ -28464,7 +28509,7 @@ import * as THREE15 from "three";
|
|
|
28464
28509
|
// package.json
|
|
28465
28510
|
var package_default = {
|
|
28466
28511
|
name: "@tscircuit/3d-viewer",
|
|
28467
|
-
version: "0.0.
|
|
28512
|
+
version: "0.0.451",
|
|
28468
28513
|
main: "./dist/index.js",
|
|
28469
28514
|
module: "./dist/index.js",
|
|
28470
28515
|
type: "module",
|
|
@@ -32311,13 +32356,74 @@ var CadViewerJscad = forwardRef3(
|
|
|
32311
32356
|
);
|
|
32312
32357
|
|
|
32313
32358
|
// src/CadViewerManifold.tsx
|
|
32314
|
-
import { su as
|
|
32359
|
+
import { su as su16 } from "@tscircuit/circuit-json-util";
|
|
32315
32360
|
import { useEffect as useEffect23, useMemo as useMemo20, useState as useState16 } from "react";
|
|
32316
32361
|
|
|
32317
32362
|
// src/hooks/useManifoldBoardBuilder.ts
|
|
32318
32363
|
import { useState as useState15, useEffect as useEffect22, useMemo as useMemo19, useRef as useRef9 } from "react";
|
|
32319
|
-
import { su as
|
|
32320
|
-
import * as
|
|
32364
|
+
import { su as su15 } from "@tscircuit/circuit-json-util";
|
|
32365
|
+
import * as THREE26 from "three";
|
|
32366
|
+
|
|
32367
|
+
// src/utils/manifold/create-manifold-board.ts
|
|
32368
|
+
var arePointsClockwise3 = (points) => {
|
|
32369
|
+
let area = 0;
|
|
32370
|
+
for (let i = 0; i < points.length; i++) {
|
|
32371
|
+
const j = (i + 1) % points.length;
|
|
32372
|
+
if (points[i] && points[j]) {
|
|
32373
|
+
area += points[i][0] * points[j][1];
|
|
32374
|
+
area -= points[j][0] * points[i][1];
|
|
32375
|
+
}
|
|
32376
|
+
}
|
|
32377
|
+
const signedArea = area / 2;
|
|
32378
|
+
return signedArea <= 0;
|
|
32379
|
+
};
|
|
32380
|
+
function createManifoldBoard(Manifold, CrossSection, boardData, pcbThickness, manifoldInstancesForCleanup) {
|
|
32381
|
+
let boardOp;
|
|
32382
|
+
let outlineCrossSection = null;
|
|
32383
|
+
if (boardData.outline && boardData.outline.length >= 3) {
|
|
32384
|
+
let outlineVec2 = boardData.outline.map((p) => [
|
|
32385
|
+
p.x,
|
|
32386
|
+
p.y
|
|
32387
|
+
]);
|
|
32388
|
+
if (arePointsClockwise3(outlineVec2)) {
|
|
32389
|
+
outlineVec2 = outlineVec2.reverse();
|
|
32390
|
+
}
|
|
32391
|
+
const crossSection = CrossSection.ofPolygons([outlineVec2]);
|
|
32392
|
+
manifoldInstancesForCleanup.push(crossSection);
|
|
32393
|
+
outlineCrossSection = crossSection;
|
|
32394
|
+
boardOp = Manifold.extrude(
|
|
32395
|
+
crossSection,
|
|
32396
|
+
pcbThickness,
|
|
32397
|
+
void 0,
|
|
32398
|
+
// nDivisions
|
|
32399
|
+
void 0,
|
|
32400
|
+
// twistDegrees
|
|
32401
|
+
void 0,
|
|
32402
|
+
// scaleTop
|
|
32403
|
+
true
|
|
32404
|
+
// center (for Z-axis)
|
|
32405
|
+
);
|
|
32406
|
+
manifoldInstancesForCleanup.push(boardOp);
|
|
32407
|
+
} else {
|
|
32408
|
+
if (boardData.outline && boardData.outline.length > 0) {
|
|
32409
|
+
console.warn(
|
|
32410
|
+
"Board outline has fewer than 3 points, falling back to rectangular board."
|
|
32411
|
+
);
|
|
32412
|
+
}
|
|
32413
|
+
boardOp = Manifold.cube(
|
|
32414
|
+
[boardData.width, boardData.height, pcbThickness],
|
|
32415
|
+
true
|
|
32416
|
+
// center (for all axes)
|
|
32417
|
+
);
|
|
32418
|
+
manifoldInstancesForCleanup.push(boardOp);
|
|
32419
|
+
boardOp = boardOp.translate([boardData.center.x, boardData.center.y, 0]);
|
|
32420
|
+
manifoldInstancesForCleanup.push(boardOp);
|
|
32421
|
+
}
|
|
32422
|
+
return { boardOp, outlineCrossSection };
|
|
32423
|
+
}
|
|
32424
|
+
|
|
32425
|
+
// src/utils/manifold/process-copper-pours.ts
|
|
32426
|
+
import * as THREE19 from "three";
|
|
32321
32427
|
|
|
32322
32428
|
// src/utils/manifold-mesh-to-three-geometry.ts
|
|
32323
32429
|
import * as THREE18 from "three";
|
|
@@ -32340,409 +32446,181 @@ function manifoldMeshToThreeGeometry(manifoldMesh) {
|
|
|
32340
32446
|
return geometry;
|
|
32341
32447
|
}
|
|
32342
32448
|
|
|
32343
|
-
// src/utils/
|
|
32344
|
-
|
|
32345
|
-
|
|
32346
|
-
|
|
32347
|
-
|
|
32348
|
-
|
|
32349
|
-
|
|
32350
|
-
|
|
32351
|
-
|
|
32352
|
-
|
|
32353
|
-
|
|
32354
|
-
|
|
32355
|
-
}
|
|
32356
|
-
|
|
32357
|
-
|
|
32358
|
-
|
|
32359
|
-
|
|
32360
|
-
|
|
32361
|
-
const
|
|
32362
|
-
|
|
32449
|
+
// src/utils/manifold/process-copper-pours.ts
|
|
32450
|
+
var arePointsClockwise4 = (points) => {
|
|
32451
|
+
let area = 0;
|
|
32452
|
+
for (let i = 0; i < points.length; i++) {
|
|
32453
|
+
const j = (i + 1) % points.length;
|
|
32454
|
+
if (points[i] && points[j]) {
|
|
32455
|
+
area += points[i][0] * points[j][1];
|
|
32456
|
+
area -= points[j][0] * points[i][1];
|
|
32457
|
+
}
|
|
32458
|
+
}
|
|
32459
|
+
const signedArea = area / 2;
|
|
32460
|
+
return signedArea <= 0;
|
|
32461
|
+
};
|
|
32462
|
+
function segmentToPoints2(p1, p2, bulge, arcSegments) {
|
|
32463
|
+
if (!bulge || Math.abs(bulge) < 1e-9) {
|
|
32464
|
+
return [];
|
|
32465
|
+
}
|
|
32466
|
+
const theta = 4 * Math.atan(bulge);
|
|
32467
|
+
const dx = p2[0] - p1[0];
|
|
32468
|
+
const dy = p2[1] - p1[1];
|
|
32469
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
32470
|
+
if (dist < 1e-9) return [];
|
|
32471
|
+
const radius = Math.abs(dist / (2 * Math.sin(theta / 2)));
|
|
32472
|
+
const m = Math.sqrt(Math.max(0, radius * radius - dist / 2 * (dist / 2)));
|
|
32473
|
+
const midPoint = [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2];
|
|
32474
|
+
const ux = dx / dist;
|
|
32475
|
+
const uy = dy / dist;
|
|
32476
|
+
const nx = -uy;
|
|
32477
|
+
const ny = ux;
|
|
32478
|
+
const centerX = midPoint[0] + nx * m * Math.sign(bulge);
|
|
32479
|
+
const centerY = midPoint[1] + ny * m * Math.sign(bulge);
|
|
32480
|
+
const startAngle = Math.atan2(p1[1] - centerY, p1[0] - centerX);
|
|
32481
|
+
const points = [];
|
|
32482
|
+
const numSteps = Math.max(
|
|
32483
|
+
2,
|
|
32484
|
+
Math.ceil(arcSegments * Math.abs(theta) / (Math.PI * 2) * 4)
|
|
32363
32485
|
);
|
|
32364
|
-
|
|
32365
|
-
|
|
32366
|
-
|
|
32367
|
-
|
|
32368
|
-
|
|
32369
|
-
|
|
32370
|
-
|
|
32371
|
-
if (!ctx) return null;
|
|
32372
|
-
if (layer === "bottom") {
|
|
32373
|
-
ctx.translate(0, canvasHeight);
|
|
32374
|
-
ctx.scale(1, -1);
|
|
32486
|
+
const angleStep = theta / numSteps;
|
|
32487
|
+
for (let i = 1; i < numSteps; i++) {
|
|
32488
|
+
const angle = startAngle + angleStep * i;
|
|
32489
|
+
points.push([
|
|
32490
|
+
centerX + radius * Math.cos(angle),
|
|
32491
|
+
centerY + radius * Math.sin(angle)
|
|
32492
|
+
]);
|
|
32375
32493
|
}
|
|
32376
|
-
|
|
32377
|
-
let firstPoint = true;
|
|
32378
|
-
ctx.beginPath();
|
|
32379
|
-
ctx.strokeStyle = traceColor;
|
|
32380
|
-
ctx.lineCap = "round";
|
|
32381
|
-
ctx.lineJoin = "round";
|
|
32382
|
-
let currentLineWidth = 0;
|
|
32383
|
-
for (const point2 of trace.route) {
|
|
32384
|
-
if (!isWireRoutePoint(point2) || point2.layer !== layer) {
|
|
32385
|
-
if (!firstPoint) ctx.stroke();
|
|
32386
|
-
firstPoint = true;
|
|
32387
|
-
continue;
|
|
32388
|
-
}
|
|
32389
|
-
const pcbX = point2.x;
|
|
32390
|
-
const pcbY = point2.y;
|
|
32391
|
-
currentLineWidth = point2.width * traceTextureResolution;
|
|
32392
|
-
ctx.lineWidth = currentLineWidth;
|
|
32393
|
-
const canvasX = (pcbX - boardData.center.x + boardData.width / 2) * traceTextureResolution;
|
|
32394
|
-
const canvasY = (-(pcbY - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
|
|
32395
|
-
if (firstPoint) {
|
|
32396
|
-
ctx.moveTo(canvasX, canvasY);
|
|
32397
|
-
firstPoint = false;
|
|
32398
|
-
} else {
|
|
32399
|
-
ctx.lineTo(canvasX, canvasY);
|
|
32400
|
-
}
|
|
32401
|
-
}
|
|
32402
|
-
if (!firstPoint) {
|
|
32403
|
-
ctx.stroke();
|
|
32404
|
-
}
|
|
32405
|
-
});
|
|
32406
|
-
ctx.globalCompositeOperation = "destination-out";
|
|
32407
|
-
ctx.fillStyle = "black";
|
|
32408
|
-
allPcbVias.forEach((via) => {
|
|
32409
|
-
const canvasX = (via.x - boardData.center.x + boardData.width / 2) * traceTextureResolution;
|
|
32410
|
-
const canvasY = (-(via.y - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
|
|
32411
|
-
const canvasRadius = via.outer_diameter / 2 * traceTextureResolution;
|
|
32412
|
-
ctx.beginPath();
|
|
32413
|
-
ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI, false);
|
|
32414
|
-
ctx.fill();
|
|
32415
|
-
});
|
|
32416
|
-
allPcbPlatedHoles.forEach((ph) => {
|
|
32417
|
-
if (ph.layers.includes(layer) && ph.shape === "circle") {
|
|
32418
|
-
const canvasX = (ph.x - boardData.center.x + boardData.width / 2) * traceTextureResolution;
|
|
32419
|
-
const canvasY = (-(ph.y - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
|
|
32420
|
-
const canvasRadius = ph.outer_diameter / 2 * traceTextureResolution;
|
|
32421
|
-
ctx.beginPath();
|
|
32422
|
-
ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI, false);
|
|
32423
|
-
ctx.fill();
|
|
32424
|
-
}
|
|
32425
|
-
});
|
|
32426
|
-
ctx.globalCompositeOperation = "source-over";
|
|
32427
|
-
const texture = new THREE19.CanvasTexture(canvas);
|
|
32428
|
-
texture.generateMipmaps = true;
|
|
32429
|
-
texture.minFilter = THREE19.LinearMipmapLinearFilter;
|
|
32430
|
-
texture.magFilter = THREE19.LinearFilter;
|
|
32431
|
-
texture.anisotropy = 16;
|
|
32432
|
-
texture.needsUpdate = true;
|
|
32433
|
-
return texture;
|
|
32494
|
+
return points;
|
|
32434
32495
|
}
|
|
32435
|
-
|
|
32436
|
-
|
|
32437
|
-
|
|
32438
|
-
|
|
32439
|
-
|
|
32440
|
-
|
|
32441
|
-
|
|
32442
|
-
|
|
32443
|
-
|
|
32444
|
-
|
|
32445
|
-
|
|
32446
|
-
|
|
32447
|
-
|
|
32448
|
-
|
|
32449
|
-
|
|
32450
|
-
|
|
32451
|
-
const pcbSilkscreenCircles = su8(circuitJson).pcb_silkscreen_circle.list();
|
|
32452
|
-
const textsOnLayer = pcbSilkscreenTexts.filter((t) => t.layer === layer);
|
|
32453
|
-
const pathsOnLayer = pcbSilkscreenPaths.filter((p) => p.layer === layer);
|
|
32454
|
-
const linesOnLayer = pcbSilkscreenLines.filter((l) => l.layer === layer);
|
|
32455
|
-
const rectsOnLayer = pcbSilkscreenRects.filter((r) => r.layer === layer);
|
|
32456
|
-
const circlesOnLayer = pcbSilkscreenCircles.filter((c) => c.layer === layer);
|
|
32457
|
-
if (textsOnLayer.length === 0 && pathsOnLayer.length === 0 && linesOnLayer.length === 0 && rectsOnLayer.length === 0 && circlesOnLayer.length === 0) {
|
|
32458
|
-
return null;
|
|
32496
|
+
function ringToPoints2(ring2, arcSegments) {
|
|
32497
|
+
const allPoints = [];
|
|
32498
|
+
const vertices = ring2.vertices;
|
|
32499
|
+
for (let i = 0; i < vertices.length; i++) {
|
|
32500
|
+
const p1 = vertices[i];
|
|
32501
|
+
const p2 = vertices[(i + 1) % vertices.length];
|
|
32502
|
+
allPoints.push([p1.x, p1.y]);
|
|
32503
|
+
if (p1.bulge) {
|
|
32504
|
+
const arcPoints = segmentToPoints2(
|
|
32505
|
+
[p1.x, p1.y],
|
|
32506
|
+
[p2.x, p2.y],
|
|
32507
|
+
p1.bulge,
|
|
32508
|
+
arcSegments
|
|
32509
|
+
);
|
|
32510
|
+
allPoints.push(...arcPoints);
|
|
32511
|
+
}
|
|
32459
32512
|
}
|
|
32460
|
-
|
|
32461
|
-
|
|
32462
|
-
|
|
32463
|
-
|
|
32464
|
-
|
|
32465
|
-
|
|
32466
|
-
|
|
32467
|
-
|
|
32468
|
-
|
|
32469
|
-
|
|
32470
|
-
|
|
32471
|
-
|
|
32472
|
-
|
|
32473
|
-
|
|
32474
|
-
|
|
32475
|
-
|
|
32476
|
-
|
|
32477
|
-
|
|
32478
|
-
|
|
32479
|
-
const endYmm = parseDimensionToMm(lineEl.y2) ?? 0;
|
|
32480
|
-
if (startXmm === endXmm && startYmm === endYmm) return;
|
|
32481
|
-
ctx.beginPath();
|
|
32482
|
-
ctx.lineWidth = coerceDimensionToMm(lineEl.stroke_width, 0.1) * traceTextureResolution;
|
|
32483
|
-
ctx.lineCap = "round";
|
|
32484
|
-
ctx.moveTo(canvasXFromPcb(startXmm), canvasYFromPcb(startYmm));
|
|
32485
|
-
ctx.lineTo(canvasXFromPcb(endXmm), canvasYFromPcb(endYmm));
|
|
32486
|
-
ctx.stroke();
|
|
32487
|
-
});
|
|
32488
|
-
pathsOnLayer.forEach((path) => {
|
|
32489
|
-
if (path.route.length < 2) return;
|
|
32490
|
-
ctx.beginPath();
|
|
32491
|
-
ctx.lineWidth = coerceDimensionToMm(path.stroke_width, 0.1) * traceTextureResolution;
|
|
32492
|
-
ctx.lineCap = "round";
|
|
32493
|
-
ctx.lineJoin = "round";
|
|
32494
|
-
path.route.forEach((point2, index2) => {
|
|
32495
|
-
const canvasX = canvasXFromPcb(parseDimensionToMm(point2.x) ?? 0);
|
|
32496
|
-
const canvasY = canvasYFromPcb(parseDimensionToMm(point2.y) ?? 0);
|
|
32497
|
-
if (index2 === 0) ctx.moveTo(canvasX, canvasY);
|
|
32498
|
-
else ctx.lineTo(canvasX, canvasY);
|
|
32499
|
-
});
|
|
32500
|
-
ctx.stroke();
|
|
32501
|
-
});
|
|
32502
|
-
circlesOnLayer.forEach((circleEl) => {
|
|
32503
|
-
const radius = coerceDimensionToMm(circleEl.radius, 0);
|
|
32504
|
-
if (radius <= 0) return;
|
|
32505
|
-
const strokeWidth = coerceDimensionToMm(circleEl.stroke_width, 0.12);
|
|
32506
|
-
const hasStroke = strokeWidth > 0;
|
|
32507
|
-
const centerXmm = parseDimensionToMm(circleEl.center?.x) ?? 0;
|
|
32508
|
-
const centerYmm = parseDimensionToMm(circleEl.center?.y) ?? 0;
|
|
32509
|
-
const canvasCenterX = canvasXFromPcb(centerXmm);
|
|
32510
|
-
const canvasCenterY = canvasYFromPcb(centerYmm);
|
|
32511
|
-
const radiusPx = radius * traceTextureResolution;
|
|
32512
|
-
ctx.save();
|
|
32513
|
-
ctx.translate(canvasCenterX, canvasCenterY);
|
|
32514
|
-
if (hasStroke) {
|
|
32515
|
-
const outerRadiusPx = radiusPx + strokeWidth / 2 * traceTextureResolution;
|
|
32516
|
-
const innerRadiusPx = Math.max(
|
|
32517
|
-
0,
|
|
32518
|
-
radiusPx - strokeWidth / 2 * traceTextureResolution
|
|
32519
|
-
);
|
|
32520
|
-
if (innerRadiusPx > 0) {
|
|
32521
|
-
ctx.beginPath();
|
|
32522
|
-
ctx.arc(0, 0, outerRadiusPx, 0, 2 * Math.PI);
|
|
32523
|
-
ctx.arc(0, 0, innerRadiusPx, 0, 2 * Math.PI, true);
|
|
32524
|
-
ctx.fill("evenodd");
|
|
32525
|
-
} else {
|
|
32526
|
-
ctx.beginPath();
|
|
32527
|
-
ctx.arc(0, 0, outerRadiusPx, 0, 2 * Math.PI);
|
|
32528
|
-
ctx.fill();
|
|
32529
|
-
}
|
|
32530
|
-
} else {
|
|
32531
|
-
ctx.beginPath();
|
|
32532
|
-
ctx.arc(0, 0, radiusPx, 0, 2 * Math.PI);
|
|
32533
|
-
ctx.fill();
|
|
32534
|
-
}
|
|
32535
|
-
ctx.restore();
|
|
32536
|
-
});
|
|
32537
|
-
rectsOnLayer.forEach((rect) => {
|
|
32538
|
-
const width10 = coerceDimensionToMm(rect.width, 0);
|
|
32539
|
-
const height10 = coerceDimensionToMm(rect.height, 0);
|
|
32540
|
-
if (width10 <= 0 || height10 <= 0) return;
|
|
32541
|
-
const centerXmm = parseDimensionToMm(rect.center?.x) ?? 0;
|
|
32542
|
-
const centerYmm = parseDimensionToMm(rect.center?.y) ?? 0;
|
|
32543
|
-
const canvasCenterX = canvasXFromPcb(centerXmm);
|
|
32544
|
-
const canvasCenterY = canvasYFromPcb(centerYmm);
|
|
32545
|
-
const rawRadius = extractRectBorderRadius(rect);
|
|
32546
|
-
const borderRadiusInput = typeof rawRadius === "string" ? parseDimensionToMm(rawRadius) : rawRadius;
|
|
32547
|
-
const borderRadiusMm = clampRectBorderRadius(
|
|
32548
|
-
width10,
|
|
32549
|
-
height10,
|
|
32550
|
-
borderRadiusInput
|
|
32551
|
-
);
|
|
32552
|
-
ctx.save();
|
|
32553
|
-
ctx.translate(canvasCenterX, canvasCenterY);
|
|
32554
|
-
const halfWidthPx = width10 / 2 * traceTextureResolution;
|
|
32555
|
-
const halfHeightPx = height10 / 2 * traceTextureResolution;
|
|
32556
|
-
const borderRadiusPx = Math.min(
|
|
32557
|
-
borderRadiusMm * traceTextureResolution,
|
|
32558
|
-
halfWidthPx,
|
|
32559
|
-
halfHeightPx
|
|
32560
|
-
);
|
|
32561
|
-
const hasStroke = rect.has_stroke ?? false;
|
|
32562
|
-
const isFilled = rect.is_filled ?? true;
|
|
32563
|
-
const isDashed = rect.is_stroke_dashed ?? false;
|
|
32564
|
-
const strokeWidthPx = hasStroke ? coerceDimensionToMm(rect.stroke_width, 0.1) * traceTextureResolution : 0;
|
|
32565
|
-
const drawRoundedRectPath = (x, y, rectWidth, rectHeight, radius) => {
|
|
32566
|
-
ctx.beginPath();
|
|
32567
|
-
if (radius <= 0) {
|
|
32568
|
-
ctx.rect(x, y, rectWidth, rectHeight);
|
|
32569
|
-
} else {
|
|
32570
|
-
const r = radius;
|
|
32571
|
-
const right = x + rectWidth;
|
|
32572
|
-
const bottom = y + rectHeight;
|
|
32573
|
-
ctx.moveTo(x + r, y);
|
|
32574
|
-
ctx.lineTo(right - r, y);
|
|
32575
|
-
ctx.quadraticCurveTo(right, y, right, y + r);
|
|
32576
|
-
ctx.lineTo(right, bottom - r);
|
|
32577
|
-
ctx.quadraticCurveTo(right, bottom, right - r, bottom);
|
|
32578
|
-
ctx.lineTo(x + r, bottom);
|
|
32579
|
-
ctx.quadraticCurveTo(x, bottom, x, bottom - r);
|
|
32580
|
-
ctx.lineTo(x, y + r);
|
|
32581
|
-
ctx.quadraticCurveTo(x, y, x + r, y);
|
|
32582
|
-
ctx.closePath();
|
|
32513
|
+
return allPoints;
|
|
32514
|
+
}
|
|
32515
|
+
function processCopperPoursForManifold(Manifold, CrossSection, circuitJson, pcbThickness, manifoldInstancesForCleanup, boardMaterial, holeUnion, boardClipVolume) {
|
|
32516
|
+
const copperPourGeoms = [];
|
|
32517
|
+
const copperPours = circuitJson.filter(
|
|
32518
|
+
(e) => e.type === "pcb_copper_pour"
|
|
32519
|
+
);
|
|
32520
|
+
for (const pour of copperPours) {
|
|
32521
|
+
const pourThickness = DEFAULT_SMT_PAD_THICKNESS;
|
|
32522
|
+
const layerSign = pour.layer === "bottom" ? -1 : 1;
|
|
32523
|
+
const zPos = layerSign * (pcbThickness / 2 + pourThickness / 2 + MANIFOLD_Z_OFFSET);
|
|
32524
|
+
let pourOp;
|
|
32525
|
+
if (pour.shape === "rect") {
|
|
32526
|
+
pourOp = Manifold.cube([pour.width, pour.height, pourThickness], true);
|
|
32527
|
+
manifoldInstancesForCleanup.push(pourOp);
|
|
32528
|
+
if (pour.rotation) {
|
|
32529
|
+
const rotatedOp = pourOp.rotate([0, 0, pour.rotation]);
|
|
32530
|
+
manifoldInstancesForCleanup.push(rotatedOp);
|
|
32531
|
+
pourOp = rotatedOp;
|
|
32583
32532
|
}
|
|
32584
|
-
|
|
32585
|
-
|
|
32586
|
-
|
|
32587
|
-
|
|
32588
|
-
|
|
32589
|
-
|
|
32590
|
-
|
|
32591
|
-
|
|
32592
|
-
|
|
32593
|
-
|
|
32594
|
-
}
|
|
32595
|
-
if (hasStroke && strokeWidthPx > 0) {
|
|
32596
|
-
ctx.lineWidth = strokeWidthPx;
|
|
32597
|
-
if (isDashed) {
|
|
32598
|
-
const dashLength = Math.max(strokeWidthPx * 2, 1);
|
|
32599
|
-
ctx.setLineDash([dashLength, dashLength]);
|
|
32533
|
+
pourOp = pourOp.translate([pour.center.x, pour.center.y, zPos]);
|
|
32534
|
+
manifoldInstancesForCleanup.push(pourOp);
|
|
32535
|
+
} else if (pour.shape === "polygon") {
|
|
32536
|
+
if (pour.points.length < 3) continue;
|
|
32537
|
+
let pointsVec2 = pour.points.map((p) => [
|
|
32538
|
+
p.x,
|
|
32539
|
+
p.y
|
|
32540
|
+
]);
|
|
32541
|
+
if (arePointsClockwise4(pointsVec2)) {
|
|
32542
|
+
pointsVec2 = pointsVec2.reverse();
|
|
32600
32543
|
}
|
|
32601
|
-
|
|
32602
|
-
|
|
32603
|
-
|
|
32544
|
+
const crossSection = CrossSection.ofPolygons([pointsVec2]);
|
|
32545
|
+
manifoldInstancesForCleanup.push(crossSection);
|
|
32546
|
+
pourOp = Manifold.extrude(
|
|
32547
|
+
crossSection,
|
|
32548
|
+
pourThickness,
|
|
32549
|
+
0,
|
|
32550
|
+
// nDivisions
|
|
32551
|
+
0,
|
|
32552
|
+
// twistDegrees
|
|
32553
|
+
[1, 1],
|
|
32554
|
+
// scaleTop
|
|
32555
|
+
true
|
|
32556
|
+
// center extrusion
|
|
32557
|
+
).translate([0, 0, zPos]);
|
|
32558
|
+
manifoldInstancesForCleanup.push(pourOp);
|
|
32559
|
+
} else if (pour.shape === "brep") {
|
|
32560
|
+
const brepShape = pour.brep_shape;
|
|
32561
|
+
if (!brepShape || !brepShape.outer_ring) continue;
|
|
32562
|
+
let outerRingPoints = ringToPoints2(
|
|
32563
|
+
brepShape.outer_ring,
|
|
32564
|
+
SMOOTH_CIRCLE_SEGMENTS
|
|
32565
|
+
);
|
|
32566
|
+
if (arePointsClockwise4(outerRingPoints)) {
|
|
32567
|
+
outerRingPoints = outerRingPoints.reverse();
|
|
32604
32568
|
}
|
|
32605
|
-
|
|
32606
|
-
|
|
32607
|
-
|
|
32608
|
-
|
|
32609
|
-
|
|
32610
|
-
|
|
32611
|
-
|
|
32612
|
-
|
|
32613
|
-
|
|
32614
|
-
|
|
32615
|
-
height: fontSize * 0.45,
|
|
32616
|
-
input: textS.text
|
|
32617
|
-
});
|
|
32618
|
-
const processedTextOutlines = [];
|
|
32619
|
-
rawTextOutlines.forEach((outline) => {
|
|
32620
|
-
if (outline.length === 29) {
|
|
32621
|
-
processedTextOutlines.push(
|
|
32622
|
-
outline.slice(0, 15)
|
|
32623
|
-
);
|
|
32624
|
-
processedTextOutlines.push(
|
|
32625
|
-
outline.slice(14, 29)
|
|
32626
|
-
);
|
|
32627
|
-
} else if (outline.length === 17) {
|
|
32628
|
-
processedTextOutlines.push(
|
|
32629
|
-
outline.slice(0, 10)
|
|
32630
|
-
);
|
|
32631
|
-
processedTextOutlines.push(
|
|
32632
|
-
outline.slice(9, 17)
|
|
32633
|
-
);
|
|
32634
|
-
} else {
|
|
32635
|
-
processedTextOutlines.push(outline);
|
|
32569
|
+
const polygons = [outerRingPoints];
|
|
32570
|
+
if (brepShape.inner_rings) {
|
|
32571
|
+
const innerRingsPoints = brepShape.inner_rings.map((ring2) => {
|
|
32572
|
+
let points = ringToPoints2(ring2, SMOOTH_CIRCLE_SEGMENTS);
|
|
32573
|
+
if (!arePointsClockwise4(points)) {
|
|
32574
|
+
points = points.reverse();
|
|
32575
|
+
}
|
|
32576
|
+
return points;
|
|
32577
|
+
});
|
|
32578
|
+
polygons.push(...innerRingsPoints);
|
|
32636
32579
|
}
|
|
32637
|
-
|
|
32638
|
-
|
|
32639
|
-
|
|
32640
|
-
|
|
32641
|
-
|
|
32642
|
-
|
|
32643
|
-
|
|
32644
|
-
|
|
32645
|
-
|
|
32646
|
-
|
|
32647
|
-
|
|
32648
|
-
|
|
32649
|
-
|
|
32650
|
-
|
|
32651
|
-
|
|
32652
|
-
} else if (alignment.includes("right")) {
|
|
32653
|
-
xOff = -textBounds.maxX;
|
|
32654
|
-
}
|
|
32655
|
-
if (alignment.includes("top")) {
|
|
32656
|
-
yOff = -textBounds.maxY;
|
|
32657
|
-
} else if (alignment.includes("bottom")) {
|
|
32658
|
-
yOff = -textBounds.minY;
|
|
32659
|
-
}
|
|
32660
|
-
const transformMatrices = [];
|
|
32661
|
-
let rotationDeg = textS.ccw_rotation ?? 0;
|
|
32662
|
-
if (textS.layer === "bottom") {
|
|
32663
|
-
transformMatrices.push(
|
|
32664
|
-
translate4(textCenterX, textCenterY),
|
|
32665
|
-
{ a: -1, b: 0, c: 0, d: 1, e: 0, f: 0 },
|
|
32666
|
-
translate4(-textCenterX, -textCenterY)
|
|
32667
|
-
);
|
|
32668
|
-
rotationDeg = -rotationDeg;
|
|
32669
|
-
}
|
|
32670
|
-
if (rotationDeg) {
|
|
32671
|
-
const rad = rotationDeg * Math.PI / 180;
|
|
32672
|
-
transformMatrices.push(
|
|
32673
|
-
translate4(textCenterX, textCenterY),
|
|
32674
|
-
rotate2(rad),
|
|
32675
|
-
translate4(-textCenterX, -textCenterY)
|
|
32676
|
-
);
|
|
32580
|
+
const crossSection = CrossSection.ofPolygons(polygons);
|
|
32581
|
+
manifoldInstancesForCleanup.push(crossSection);
|
|
32582
|
+
pourOp = Manifold.extrude(
|
|
32583
|
+
crossSection,
|
|
32584
|
+
pourThickness,
|
|
32585
|
+
0,
|
|
32586
|
+
// nDivisions
|
|
32587
|
+
0,
|
|
32588
|
+
// twistDegrees
|
|
32589
|
+
[1, 1],
|
|
32590
|
+
// scaleTop
|
|
32591
|
+
true
|
|
32592
|
+
// center extrusion
|
|
32593
|
+
).translate([0, 0, zPos]);
|
|
32594
|
+
manifoldInstancesForCleanup.push(pourOp);
|
|
32677
32595
|
}
|
|
32678
|
-
|
|
32679
|
-
|
|
32680
|
-
|
|
32681
|
-
|
|
32682
|
-
|
|
32683
|
-
|
|
32684
|
-
|
|
32685
|
-
|
|
32686
|
-
|
|
32687
|
-
|
|
32688
|
-
|
|
32689
|
-
|
|
32690
|
-
|
|
32691
|
-
|
|
32692
|
-
|
|
32693
|
-
|
|
32694
|
-
|
|
32695
|
-
|
|
32696
|
-
|
|
32697
|
-
|
|
32698
|
-
|
|
32699
|
-
|
|
32700
|
-
|
|
32701
|
-
texture.needsUpdate = true;
|
|
32702
|
-
return texture;
|
|
32703
|
-
}
|
|
32704
|
-
|
|
32705
|
-
// src/utils/manifold/process-non-plated-holes.ts
|
|
32706
|
-
import { su as su9 } from "@tscircuit/circuit-json-util";
|
|
32707
|
-
|
|
32708
|
-
// src/utils/hole-geoms.ts
|
|
32709
|
-
function createCircleHoleDrill({
|
|
32710
|
-
Manifold,
|
|
32711
|
-
x,
|
|
32712
|
-
y,
|
|
32713
|
-
diameter,
|
|
32714
|
-
thickness,
|
|
32715
|
-
segments = 32
|
|
32716
|
-
}) {
|
|
32717
|
-
const drill = Manifold.cylinder(
|
|
32718
|
-
thickness * 1.2,
|
|
32719
|
-
diameter / 2,
|
|
32720
|
-
diameter / 2,
|
|
32721
|
-
segments,
|
|
32722
|
-
true
|
|
32723
|
-
);
|
|
32724
|
-
return drill.translate([x, y, 0]);
|
|
32725
|
-
}
|
|
32726
|
-
function createPlatedHoleDrill({
|
|
32727
|
-
Manifold,
|
|
32728
|
-
x,
|
|
32729
|
-
y,
|
|
32730
|
-
holeDiameter,
|
|
32731
|
-
thickness,
|
|
32732
|
-
zOffset = 1e-3,
|
|
32733
|
-
segments = 32
|
|
32734
|
-
}) {
|
|
32735
|
-
const boardHoleRadius = holeDiameter / 2 + zOffset;
|
|
32736
|
-
const drill = Manifold.cylinder(
|
|
32737
|
-
thickness * 1.2,
|
|
32738
|
-
boardHoleRadius,
|
|
32739
|
-
boardHoleRadius,
|
|
32740
|
-
segments,
|
|
32741
|
-
true
|
|
32742
|
-
);
|
|
32743
|
-
return drill.translate([x, y, 0]);
|
|
32596
|
+
if (pourOp) {
|
|
32597
|
+
if (holeUnion) {
|
|
32598
|
+
const withHoles = pourOp.subtract(holeUnion);
|
|
32599
|
+
manifoldInstancesForCleanup.push(withHoles);
|
|
32600
|
+
pourOp = withHoles;
|
|
32601
|
+
}
|
|
32602
|
+
if (boardClipVolume) {
|
|
32603
|
+
const clipped = Manifold.intersection([pourOp, boardClipVolume]);
|
|
32604
|
+
manifoldInstancesForCleanup.push(clipped);
|
|
32605
|
+
pourOp = clipped;
|
|
32606
|
+
}
|
|
32607
|
+
const covered = pour.covered_with_solder_mask !== false;
|
|
32608
|
+
const pourColorArr = covered ? tracesMaterialColors[boardMaterial] ?? colors.fr4GreenSolderWithMask : colors.copper;
|
|
32609
|
+
const pourColor = new THREE19.Color(...pourColorArr);
|
|
32610
|
+
const threeGeom = manifoldMeshToThreeGeometry(pourOp.getMesh());
|
|
32611
|
+
copperPourGeoms.push({
|
|
32612
|
+
key: `coppour-${pour.pcb_copper_pour_id}`,
|
|
32613
|
+
geometry: threeGeom,
|
|
32614
|
+
color: pourColor
|
|
32615
|
+
});
|
|
32616
|
+
}
|
|
32617
|
+
}
|
|
32618
|
+
return { copperPourGeoms };
|
|
32744
32619
|
}
|
|
32745
32620
|
|
|
32621
|
+
// src/utils/manifold/process-cutouts.ts
|
|
32622
|
+
import { su as su7 } from "@tscircuit/circuit-json-util";
|
|
32623
|
+
|
|
32746
32624
|
// src/utils/pad-geoms.ts
|
|
32747
32625
|
var RECT_PAD_SEGMENTS2 = 64;
|
|
32748
32626
|
function createRoundedRectPrism({
|
|
@@ -32818,6 +32696,156 @@ function createPadManifoldOp({
|
|
|
32818
32696
|
return null;
|
|
32819
32697
|
}
|
|
32820
32698
|
|
|
32699
|
+
// src/utils/manifold/process-cutouts.ts
|
|
32700
|
+
var arePointsClockwise5 = (points) => {
|
|
32701
|
+
let area = 0;
|
|
32702
|
+
for (let i = 0; i < points.length; i++) {
|
|
32703
|
+
const j = (i + 1) % points.length;
|
|
32704
|
+
if (points[i] && points[j]) {
|
|
32705
|
+
area += points[i][0] * points[j][1];
|
|
32706
|
+
area -= points[j][0] * points[i][1];
|
|
32707
|
+
}
|
|
32708
|
+
}
|
|
32709
|
+
const signedArea = area / 2;
|
|
32710
|
+
return signedArea <= 0;
|
|
32711
|
+
};
|
|
32712
|
+
function processCutoutsForManifold(Manifold, CrossSection, circuitJson, pcbThickness, manifoldInstancesForCleanup) {
|
|
32713
|
+
const cutoutOps = [];
|
|
32714
|
+
const pcbCutouts = su7(circuitJson).pcb_cutout.list();
|
|
32715
|
+
for (const cutout of pcbCutouts) {
|
|
32716
|
+
let cutoutOp;
|
|
32717
|
+
const cutoutHeight = pcbThickness * 1.5;
|
|
32718
|
+
switch (cutout.shape) {
|
|
32719
|
+
case "rect": {
|
|
32720
|
+
const rectCornerRadius = extractRectBorderRadius(cutout);
|
|
32721
|
+
if (typeof rectCornerRadius === "number" && rectCornerRadius > 0) {
|
|
32722
|
+
cutoutOp = createRoundedRectPrism({
|
|
32723
|
+
Manifold,
|
|
32724
|
+
width: cutout.width,
|
|
32725
|
+
height: cutout.height,
|
|
32726
|
+
thickness: cutoutHeight,
|
|
32727
|
+
borderRadius: rectCornerRadius
|
|
32728
|
+
});
|
|
32729
|
+
} else {
|
|
32730
|
+
cutoutOp = Manifold.cube(
|
|
32731
|
+
[cutout.width, cutout.height, cutoutHeight],
|
|
32732
|
+
true
|
|
32733
|
+
// centered
|
|
32734
|
+
);
|
|
32735
|
+
}
|
|
32736
|
+
manifoldInstancesForCleanup.push(cutoutOp);
|
|
32737
|
+
if (cutout.rotation) {
|
|
32738
|
+
const rotatedOp = cutoutOp.rotate([0, 0, cutout.rotation]);
|
|
32739
|
+
manifoldInstancesForCleanup.push(rotatedOp);
|
|
32740
|
+
cutoutOp = rotatedOp;
|
|
32741
|
+
}
|
|
32742
|
+
cutoutOp = cutoutOp.translate([
|
|
32743
|
+
cutout.center.x,
|
|
32744
|
+
cutout.center.y,
|
|
32745
|
+
0
|
|
32746
|
+
// Centered vertically by Manifold.cube, so Z is 0 for board plane
|
|
32747
|
+
]);
|
|
32748
|
+
manifoldInstancesForCleanup.push(cutoutOp);
|
|
32749
|
+
break;
|
|
32750
|
+
}
|
|
32751
|
+
case "circle":
|
|
32752
|
+
cutoutOp = Manifold.cylinder(
|
|
32753
|
+
cutoutHeight,
|
|
32754
|
+
cutout.radius,
|
|
32755
|
+
-1,
|
|
32756
|
+
// default for radiusHigh
|
|
32757
|
+
SMOOTH_CIRCLE_SEGMENTS,
|
|
32758
|
+
true
|
|
32759
|
+
// centered
|
|
32760
|
+
);
|
|
32761
|
+
manifoldInstancesForCleanup.push(cutoutOp);
|
|
32762
|
+
cutoutOp = cutoutOp.translate([cutout.center.x, cutout.center.y, 0]);
|
|
32763
|
+
manifoldInstancesForCleanup.push(cutoutOp);
|
|
32764
|
+
break;
|
|
32765
|
+
case "polygon":
|
|
32766
|
+
if (cutout.points.length < 3) {
|
|
32767
|
+
console.warn(
|
|
32768
|
+
`PCB Cutout [${cutout.pcb_cutout_id}] polygon has fewer than 3 points, skipping.`
|
|
32769
|
+
);
|
|
32770
|
+
continue;
|
|
32771
|
+
}
|
|
32772
|
+
let pointsVec2 = cutout.points.map((p) => [
|
|
32773
|
+
p.x,
|
|
32774
|
+
p.y
|
|
32775
|
+
]);
|
|
32776
|
+
if (arePointsClockwise5(pointsVec2)) {
|
|
32777
|
+
pointsVec2 = pointsVec2.reverse();
|
|
32778
|
+
}
|
|
32779
|
+
const crossSection = CrossSection.ofPolygons([pointsVec2]);
|
|
32780
|
+
manifoldInstancesForCleanup.push(crossSection);
|
|
32781
|
+
cutoutOp = Manifold.extrude(
|
|
32782
|
+
crossSection,
|
|
32783
|
+
cutoutHeight,
|
|
32784
|
+
0,
|
|
32785
|
+
// nDivisions
|
|
32786
|
+
0,
|
|
32787
|
+
// twistDegrees
|
|
32788
|
+
[1, 1],
|
|
32789
|
+
// scaleTop
|
|
32790
|
+
true
|
|
32791
|
+
// center extrusion
|
|
32792
|
+
);
|
|
32793
|
+
manifoldInstancesForCleanup.push(cutoutOp);
|
|
32794
|
+
break;
|
|
32795
|
+
default:
|
|
32796
|
+
console.warn(
|
|
32797
|
+
`Unsupported cutout shape: ${cutout.shape} for cutout ${cutout.pcb_cutout_id}`
|
|
32798
|
+
);
|
|
32799
|
+
continue;
|
|
32800
|
+
}
|
|
32801
|
+
if (cutoutOp) {
|
|
32802
|
+
cutoutOps.push(cutoutOp);
|
|
32803
|
+
}
|
|
32804
|
+
}
|
|
32805
|
+
return { cutoutOps };
|
|
32806
|
+
}
|
|
32807
|
+
|
|
32808
|
+
// src/utils/manifold/process-non-plated-holes.ts
|
|
32809
|
+
import { su as su8 } from "@tscircuit/circuit-json-util";
|
|
32810
|
+
|
|
32811
|
+
// src/utils/hole-geoms.ts
|
|
32812
|
+
function createCircleHoleDrill({
|
|
32813
|
+
Manifold,
|
|
32814
|
+
x,
|
|
32815
|
+
y,
|
|
32816
|
+
diameter,
|
|
32817
|
+
thickness,
|
|
32818
|
+
segments = 32
|
|
32819
|
+
}) {
|
|
32820
|
+
const drill = Manifold.cylinder(
|
|
32821
|
+
thickness * 1.2,
|
|
32822
|
+
diameter / 2,
|
|
32823
|
+
diameter / 2,
|
|
32824
|
+
segments,
|
|
32825
|
+
true
|
|
32826
|
+
);
|
|
32827
|
+
return drill.translate([x, y, 0]);
|
|
32828
|
+
}
|
|
32829
|
+
function createPlatedHoleDrill({
|
|
32830
|
+
Manifold,
|
|
32831
|
+
x,
|
|
32832
|
+
y,
|
|
32833
|
+
holeDiameter,
|
|
32834
|
+
thickness,
|
|
32835
|
+
zOffset = 1e-3,
|
|
32836
|
+
segments = 32
|
|
32837
|
+
}) {
|
|
32838
|
+
const boardHoleRadius = holeDiameter / 2 + zOffset;
|
|
32839
|
+
const drill = Manifold.cylinder(
|
|
32840
|
+
thickness * 1.2,
|
|
32841
|
+
boardHoleRadius,
|
|
32842
|
+
boardHoleRadius,
|
|
32843
|
+
segments,
|
|
32844
|
+
true
|
|
32845
|
+
);
|
|
32846
|
+
return drill.translate([x, y, 0]);
|
|
32847
|
+
}
|
|
32848
|
+
|
|
32821
32849
|
// src/utils/manifold/process-non-plated-holes.ts
|
|
32822
32850
|
function isCircleHole(hole) {
|
|
32823
32851
|
return (hole.shape === "circle" || hole.hole_shape === "circle") && typeof hole.hole_diameter === "number";
|
|
@@ -32830,7 +32858,7 @@ function isRotatedPillHole(hole) {
|
|
|
32830
32858
|
}
|
|
32831
32859
|
function processNonPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup) {
|
|
32832
32860
|
const nonPlatedHoleBoardDrills = [];
|
|
32833
|
-
const pcbHoles =
|
|
32861
|
+
const pcbHoles = su8(circuitJson).pcb_hole.list();
|
|
32834
32862
|
const createPillOp = (width10, height10, depth) => {
|
|
32835
32863
|
const pillOp = createRoundedRectPrism({
|
|
32836
32864
|
Manifold,
|
|
@@ -32879,9 +32907,9 @@ function processNonPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, m
|
|
|
32879
32907
|
}
|
|
32880
32908
|
|
|
32881
32909
|
// src/utils/manifold/process-plated-holes.ts
|
|
32882
|
-
import { su as
|
|
32883
|
-
import * as
|
|
32884
|
-
var
|
|
32910
|
+
import { su as su9 } from "@tscircuit/circuit-json-util";
|
|
32911
|
+
import * as THREE20 from "three";
|
|
32912
|
+
var arePointsClockwise6 = (points) => {
|
|
32885
32913
|
let area = 0;
|
|
32886
32914
|
for (let i = 0; i < points.length; i++) {
|
|
32887
32915
|
const j = (i + 1) % points.length;
|
|
@@ -32901,11 +32929,11 @@ var createEllipsePoints = (width10, height10, segments) => {
|
|
|
32901
32929
|
}
|
|
32902
32930
|
return points;
|
|
32903
32931
|
};
|
|
32904
|
-
var COPPER_COLOR = new
|
|
32932
|
+
var COPPER_COLOR = new THREE20.Color(...colors.copper);
|
|
32905
32933
|
var PLATED_HOLE_LIP_HEIGHT = 0.05;
|
|
32906
32934
|
function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbThickness, manifoldInstancesForCleanup, boardClipVolume) {
|
|
32907
32935
|
const platedHoleBoardDrills = [];
|
|
32908
|
-
const pcbPlatedHoles =
|
|
32936
|
+
const pcbPlatedHoles = su9(circuitJson).pcb_plated_hole.list();
|
|
32909
32937
|
const platedHoleCopperGeoms = [];
|
|
32910
32938
|
const platedHoleCopperOpsForSubtract = [];
|
|
32911
32939
|
const createPillOp = (width10, height10, depth) => {
|
|
@@ -32928,7 +32956,7 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
|
|
|
32928
32956
|
point2.x,
|
|
32929
32957
|
point2.y
|
|
32930
32958
|
]);
|
|
32931
|
-
if (
|
|
32959
|
+
if (arePointsClockwise6(points)) {
|
|
32932
32960
|
points = points.reverse();
|
|
32933
32961
|
}
|
|
32934
32962
|
const crossSection = CrossSection.ofPolygons([points]);
|
|
@@ -32973,7 +33001,7 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
|
|
|
32973
33001
|
const height10 = Math.max(baseHeight + sizeDelta, M);
|
|
32974
33002
|
if (holeShape === "oval") {
|
|
32975
33003
|
let points = createEllipsePoints(width10, height10, SMOOTH_CIRCLE_SEGMENTS);
|
|
32976
|
-
if (
|
|
33004
|
+
if (arePointsClockwise6(points)) {
|
|
32977
33005
|
points = points.reverse();
|
|
32978
33006
|
}
|
|
32979
33007
|
const crossSection = CrossSection.ofPolygons([points]);
|
|
@@ -33288,7 +33316,7 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
|
|
|
33288
33316
|
drillH,
|
|
33289
33317
|
SMOOTH_CIRCLE_SEGMENTS
|
|
33290
33318
|
);
|
|
33291
|
-
if (
|
|
33319
|
+
if (arePointsClockwise6(boardDrillPoints)) {
|
|
33292
33320
|
boardDrillPoints = boardDrillPoints.reverse();
|
|
33293
33321
|
}
|
|
33294
33322
|
const boardDrillCrossSection = CrossSection.ofPolygons([boardDrillPoints]);
|
|
@@ -33316,7 +33344,7 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
|
|
|
33316
33344
|
outerH,
|
|
33317
33345
|
SMOOTH_CIRCLE_SEGMENTS
|
|
33318
33346
|
);
|
|
33319
|
-
if (
|
|
33347
|
+
if (arePointsClockwise6(outerPoints)) {
|
|
33320
33348
|
outerPoints = outerPoints.reverse();
|
|
33321
33349
|
}
|
|
33322
33350
|
const outerCrossSection = CrossSection.ofPolygons([outerPoints]);
|
|
@@ -33335,7 +33363,7 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
|
|
|
33335
33363
|
holeH,
|
|
33336
33364
|
SMOOTH_CIRCLE_SEGMENTS
|
|
33337
33365
|
);
|
|
33338
|
-
if (
|
|
33366
|
+
if (arePointsClockwise6(innerPoints)) {
|
|
33339
33367
|
innerPoints = innerPoints.reverse();
|
|
33340
33368
|
}
|
|
33341
33369
|
const innerCrossSection = CrossSection.ofPolygons([innerPoints]);
|
|
@@ -33473,6 +33501,46 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
|
|
|
33473
33501
|
return { platedHoleBoardDrills, platedHoleCopperGeoms, platedHoleSubtractOp };
|
|
33474
33502
|
}
|
|
33475
33503
|
|
|
33504
|
+
// src/utils/manifold/process-smt-pads.ts
|
|
33505
|
+
import { su as su10 } from "@tscircuit/circuit-json-util";
|
|
33506
|
+
import * as THREE21 from "three";
|
|
33507
|
+
var COPPER_COLOR2 = new THREE21.Color(...colors.copper);
|
|
33508
|
+
function processSmtPadsForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup, holeUnion, boardClipVolume) {
|
|
33509
|
+
const smtPadGeoms = [];
|
|
33510
|
+
const smtPads = su10(circuitJson).pcb_smtpad.list();
|
|
33511
|
+
smtPads.forEach((pad2, index2) => {
|
|
33512
|
+
const padBaseThickness = DEFAULT_SMT_PAD_THICKNESS;
|
|
33513
|
+
const zPos = pad2.layer === "bottom" ? -pcbThickness / 2 - BOARD_SURFACE_OFFSET.copper : pcbThickness / 2 + BOARD_SURFACE_OFFSET.copper;
|
|
33514
|
+
let padManifoldOp = createPadManifoldOp({
|
|
33515
|
+
Manifold,
|
|
33516
|
+
pad: pad2,
|
|
33517
|
+
padBaseThickness
|
|
33518
|
+
});
|
|
33519
|
+
if (padManifoldOp) {
|
|
33520
|
+
manifoldInstancesForCleanup.push(padManifoldOp);
|
|
33521
|
+
const translatedPad = padManifoldOp.translate([pad2.x, pad2.y, zPos]);
|
|
33522
|
+
manifoldInstancesForCleanup.push(translatedPad);
|
|
33523
|
+
let finalPadOp = translatedPad;
|
|
33524
|
+
if (holeUnion) {
|
|
33525
|
+
finalPadOp = translatedPad.subtract(holeUnion);
|
|
33526
|
+
manifoldInstancesForCleanup.push(finalPadOp);
|
|
33527
|
+
}
|
|
33528
|
+
if (boardClipVolume) {
|
|
33529
|
+
const clipped = Manifold.intersection([finalPadOp, boardClipVolume]);
|
|
33530
|
+
manifoldInstancesForCleanup.push(clipped);
|
|
33531
|
+
finalPadOp = clipped;
|
|
33532
|
+
}
|
|
33533
|
+
const threeGeom = manifoldMeshToThreeGeometry(finalPadOp.getMesh());
|
|
33534
|
+
smtPadGeoms.push({
|
|
33535
|
+
key: `smt_pad-${pad2.layer || "top"}-${pad2.pcb_smtpad_id || index2}`,
|
|
33536
|
+
geometry: threeGeom,
|
|
33537
|
+
color: COPPER_COLOR2
|
|
33538
|
+
});
|
|
33539
|
+
}
|
|
33540
|
+
});
|
|
33541
|
+
return { smtPadGeoms };
|
|
33542
|
+
}
|
|
33543
|
+
|
|
33476
33544
|
// src/utils/manifold/process-vias.ts
|
|
33477
33545
|
import { su as su11 } from "@tscircuit/circuit-json-util";
|
|
33478
33546
|
import * as THREE22 from "three";
|
|
@@ -33528,7 +33596,7 @@ function createViaCopper({
|
|
|
33528
33596
|
}
|
|
33529
33597
|
|
|
33530
33598
|
// src/utils/manifold/process-vias.ts
|
|
33531
|
-
var
|
|
33599
|
+
var COPPER_COLOR3 = new THREE22.Color(...colors.copper);
|
|
33532
33600
|
function processViasForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup, boardClipVolume) {
|
|
33533
33601
|
const viaBoardDrills = [];
|
|
33534
33602
|
const pcbVias = su11(circuitJson).pcb_via.list();
|
|
@@ -33568,396 +33636,523 @@ function processViasForManifold(Manifold, circuitJson, pcbThickness, manifoldIns
|
|
|
33568
33636
|
manifoldInstancesForCleanup.push(clipped);
|
|
33569
33637
|
finalCopperOp = clipped;
|
|
33570
33638
|
}
|
|
33571
|
-
const threeGeom = manifoldMeshToThreeGeometry(finalCopperOp.getMesh());
|
|
33572
|
-
viaCopperGeoms.push({
|
|
33573
|
-
key: `via-${via.pcb_via_id || index2}`,
|
|
33574
|
-
geometry: threeGeom,
|
|
33575
|
-
color: COPPER_COLOR2
|
|
33576
|
-
});
|
|
33577
|
-
}
|
|
33578
|
-
});
|
|
33579
|
-
return { viaBoardDrills, viaCopperGeoms };
|
|
33580
|
-
}
|
|
33581
|
-
|
|
33582
|
-
// src/utils/manifold/process-smt-pads.ts
|
|
33583
|
-
import { su as su12 } from "@tscircuit/circuit-json-util";
|
|
33584
|
-
import * as THREE23 from "three";
|
|
33585
|
-
var COPPER_COLOR3 = new THREE23.Color(...colors.copper);
|
|
33586
|
-
function processSmtPadsForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup, holeUnion, boardClipVolume) {
|
|
33587
|
-
const smtPadGeoms = [];
|
|
33588
|
-
const smtPads = su12(circuitJson).pcb_smtpad.list();
|
|
33589
|
-
smtPads.forEach((pad2, index2) => {
|
|
33590
|
-
const padBaseThickness = DEFAULT_SMT_PAD_THICKNESS;
|
|
33591
|
-
const zPos = pad2.layer === "bottom" ? -pcbThickness / 2 - BOARD_SURFACE_OFFSET.copper : pcbThickness / 2 + BOARD_SURFACE_OFFSET.copper;
|
|
33592
|
-
let padManifoldOp = createPadManifoldOp({
|
|
33593
|
-
Manifold,
|
|
33594
|
-
pad: pad2,
|
|
33595
|
-
padBaseThickness
|
|
33596
|
-
});
|
|
33597
|
-
if (padManifoldOp) {
|
|
33598
|
-
manifoldInstancesForCleanup.push(padManifoldOp);
|
|
33599
|
-
const translatedPad = padManifoldOp.translate([pad2.x, pad2.y, zPos]);
|
|
33600
|
-
manifoldInstancesForCleanup.push(translatedPad);
|
|
33601
|
-
let finalPadOp = translatedPad;
|
|
33602
|
-
if (holeUnion) {
|
|
33603
|
-
finalPadOp = translatedPad.subtract(holeUnion);
|
|
33604
|
-
manifoldInstancesForCleanup.push(finalPadOp);
|
|
33605
|
-
}
|
|
33606
|
-
if (boardClipVolume) {
|
|
33607
|
-
const clipped = Manifold.intersection([finalPadOp, boardClipVolume]);
|
|
33608
|
-
manifoldInstancesForCleanup.push(clipped);
|
|
33609
|
-
finalPadOp = clipped;
|
|
33610
|
-
}
|
|
33611
|
-
const threeGeom = manifoldMeshToThreeGeometry(finalPadOp.getMesh());
|
|
33612
|
-
smtPadGeoms.push({
|
|
33613
|
-
key: `smt_pad-${pad2.layer || "top"}-${pad2.pcb_smtpad_id || index2}`,
|
|
33639
|
+
const threeGeom = manifoldMeshToThreeGeometry(finalCopperOp.getMesh());
|
|
33640
|
+
viaCopperGeoms.push({
|
|
33641
|
+
key: `via-${via.pcb_via_id || index2}`,
|
|
33614
33642
|
geometry: threeGeom,
|
|
33615
33643
|
color: COPPER_COLOR3
|
|
33616
33644
|
});
|
|
33617
33645
|
}
|
|
33618
33646
|
});
|
|
33619
|
-
return {
|
|
33647
|
+
return { viaBoardDrills, viaCopperGeoms };
|
|
33620
33648
|
}
|
|
33621
33649
|
|
|
33622
|
-
// src/utils/
|
|
33623
|
-
var
|
|
33624
|
-
|
|
33625
|
-
|
|
33626
|
-
|
|
33627
|
-
|
|
33628
|
-
|
|
33629
|
-
|
|
33630
|
-
|
|
33650
|
+
// src/utils/silkscreen-texture.ts
|
|
33651
|
+
var import_text2 = __toESM(require_text(), 1);
|
|
33652
|
+
import * as THREE23 from "three";
|
|
33653
|
+
import { su as su12 } from "@tscircuit/circuit-json-util";
|
|
33654
|
+
function createSilkscreenTextureForLayer({
|
|
33655
|
+
layer,
|
|
33656
|
+
circuitJson,
|
|
33657
|
+
boardData,
|
|
33658
|
+
silkscreenColor = "rgb(255,255,255)",
|
|
33659
|
+
traceTextureResolution
|
|
33660
|
+
}) {
|
|
33661
|
+
const pcbSilkscreenTexts = su12(circuitJson).pcb_silkscreen_text.list();
|
|
33662
|
+
const pcbSilkscreenPaths = su12(circuitJson).pcb_silkscreen_path.list();
|
|
33663
|
+
const pcbSilkscreenLines = su12(circuitJson).pcb_silkscreen_line.list();
|
|
33664
|
+
const pcbSilkscreenRects = su12(circuitJson).pcb_silkscreen_rect.list();
|
|
33665
|
+
const pcbSilkscreenCircles = su12(circuitJson).pcb_silkscreen_circle.list();
|
|
33666
|
+
const textsOnLayer = pcbSilkscreenTexts.filter((t) => t.layer === layer);
|
|
33667
|
+
const pathsOnLayer = pcbSilkscreenPaths.filter((p) => p.layer === layer);
|
|
33668
|
+
const linesOnLayer = pcbSilkscreenLines.filter((l) => l.layer === layer);
|
|
33669
|
+
const rectsOnLayer = pcbSilkscreenRects.filter((r) => r.layer === layer);
|
|
33670
|
+
const circlesOnLayer = pcbSilkscreenCircles.filter((c) => c.layer === layer);
|
|
33671
|
+
if (textsOnLayer.length === 0 && pathsOnLayer.length === 0 && linesOnLayer.length === 0 && rectsOnLayer.length === 0 && circlesOnLayer.length === 0) {
|
|
33672
|
+
return null;
|
|
33631
33673
|
}
|
|
33632
|
-
const
|
|
33633
|
-
|
|
33634
|
-
|
|
33635
|
-
|
|
33636
|
-
|
|
33637
|
-
|
|
33638
|
-
if (
|
|
33639
|
-
|
|
33640
|
-
|
|
33641
|
-
|
|
33642
|
-
|
|
33643
|
-
|
|
33644
|
-
|
|
33674
|
+
const canvas = document.createElement("canvas");
|
|
33675
|
+
const canvasWidth = Math.floor(boardData.width * traceTextureResolution);
|
|
33676
|
+
const canvasHeight = Math.floor(boardData.height * traceTextureResolution);
|
|
33677
|
+
canvas.width = canvasWidth;
|
|
33678
|
+
canvas.height = canvasHeight;
|
|
33679
|
+
const ctx = canvas.getContext("2d");
|
|
33680
|
+
if (!ctx) return null;
|
|
33681
|
+
if (layer === "bottom") {
|
|
33682
|
+
ctx.translate(0, canvasHeight);
|
|
33683
|
+
ctx.scale(1, -1);
|
|
33684
|
+
}
|
|
33685
|
+
ctx.strokeStyle = silkscreenColor;
|
|
33686
|
+
ctx.fillStyle = silkscreenColor;
|
|
33687
|
+
const canvasXFromPcb = (pcbX) => (pcbX - boardData.center.x + boardData.width / 2) * traceTextureResolution;
|
|
33688
|
+
const canvasYFromPcb = (pcbY) => (-(pcbY - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
|
|
33689
|
+
linesOnLayer.forEach((lineEl) => {
|
|
33690
|
+
const startXmm = parseDimensionToMm(lineEl.x1) ?? 0;
|
|
33691
|
+
const startYmm = parseDimensionToMm(lineEl.y1) ?? 0;
|
|
33692
|
+
const endXmm = parseDimensionToMm(lineEl.x2) ?? 0;
|
|
33693
|
+
const endYmm = parseDimensionToMm(lineEl.y2) ?? 0;
|
|
33694
|
+
if (startXmm === endXmm && startYmm === endYmm) return;
|
|
33695
|
+
ctx.beginPath();
|
|
33696
|
+
ctx.lineWidth = coerceDimensionToMm(lineEl.stroke_width, 0.1) * traceTextureResolution;
|
|
33697
|
+
ctx.lineCap = "round";
|
|
33698
|
+
ctx.moveTo(canvasXFromPcb(startXmm), canvasYFromPcb(startYmm));
|
|
33699
|
+
ctx.lineTo(canvasXFromPcb(endXmm), canvasYFromPcb(endYmm));
|
|
33700
|
+
ctx.stroke();
|
|
33701
|
+
});
|
|
33702
|
+
pathsOnLayer.forEach((path) => {
|
|
33703
|
+
if (path.route.length < 2) return;
|
|
33704
|
+
ctx.beginPath();
|
|
33705
|
+
ctx.lineWidth = coerceDimensionToMm(path.stroke_width, 0.1) * traceTextureResolution;
|
|
33706
|
+
ctx.lineCap = "round";
|
|
33707
|
+
ctx.lineJoin = "round";
|
|
33708
|
+
path.route.forEach((point2, index2) => {
|
|
33709
|
+
const canvasX = canvasXFromPcb(parseDimensionToMm(point2.x) ?? 0);
|
|
33710
|
+
const canvasY = canvasYFromPcb(parseDimensionToMm(point2.y) ?? 0);
|
|
33711
|
+
if (index2 === 0) ctx.moveTo(canvasX, canvasY);
|
|
33712
|
+
else ctx.lineTo(canvasX, canvasY);
|
|
33713
|
+
});
|
|
33714
|
+
ctx.stroke();
|
|
33715
|
+
});
|
|
33716
|
+
circlesOnLayer.forEach((circleEl) => {
|
|
33717
|
+
const radius = coerceDimensionToMm(circleEl.radius, 0);
|
|
33718
|
+
if (radius <= 0) return;
|
|
33719
|
+
const strokeWidth = coerceDimensionToMm(circleEl.stroke_width, 0.12);
|
|
33720
|
+
const hasStroke = strokeWidth > 0;
|
|
33721
|
+
const centerXmm = parseDimensionToMm(circleEl.center?.x) ?? 0;
|
|
33722
|
+
const centerYmm = parseDimensionToMm(circleEl.center?.y) ?? 0;
|
|
33723
|
+
const canvasCenterX = canvasXFromPcb(centerXmm);
|
|
33724
|
+
const canvasCenterY = canvasYFromPcb(centerYmm);
|
|
33725
|
+
const radiusPx = radius * traceTextureResolution;
|
|
33726
|
+
ctx.save();
|
|
33727
|
+
ctx.translate(canvasCenterX, canvasCenterY);
|
|
33728
|
+
if (hasStroke) {
|
|
33729
|
+
const outerRadiusPx = radiusPx + strokeWidth / 2 * traceTextureResolution;
|
|
33730
|
+
const innerRadiusPx = Math.max(
|
|
33731
|
+
0,
|
|
33732
|
+
radiusPx - strokeWidth / 2 * traceTextureResolution
|
|
33733
|
+
);
|
|
33734
|
+
if (innerRadiusPx > 0) {
|
|
33735
|
+
ctx.beginPath();
|
|
33736
|
+
ctx.arc(0, 0, outerRadiusPx, 0, 2 * Math.PI);
|
|
33737
|
+
ctx.arc(0, 0, innerRadiusPx, 0, 2 * Math.PI, true);
|
|
33738
|
+
ctx.fill("evenodd");
|
|
33739
|
+
} else {
|
|
33740
|
+
ctx.beginPath();
|
|
33741
|
+
ctx.arc(0, 0, outerRadiusPx, 0, 2 * Math.PI);
|
|
33742
|
+
ctx.fill();
|
|
33743
|
+
}
|
|
33744
|
+
} else {
|
|
33745
|
+
ctx.beginPath();
|
|
33746
|
+
ctx.arc(0, 0, radiusPx, 0, 2 * Math.PI);
|
|
33747
|
+
ctx.fill();
|
|
33645
33748
|
}
|
|
33646
|
-
|
|
33647
|
-
|
|
33648
|
-
|
|
33649
|
-
|
|
33650
|
-
|
|
33651
|
-
|
|
33652
|
-
|
|
33653
|
-
|
|
33654
|
-
|
|
33655
|
-
|
|
33656
|
-
|
|
33657
|
-
|
|
33658
|
-
|
|
33659
|
-
|
|
33749
|
+
ctx.restore();
|
|
33750
|
+
});
|
|
33751
|
+
rectsOnLayer.forEach((rect) => {
|
|
33752
|
+
const width10 = coerceDimensionToMm(rect.width, 0);
|
|
33753
|
+
const height10 = coerceDimensionToMm(rect.height, 0);
|
|
33754
|
+
if (width10 <= 0 || height10 <= 0) return;
|
|
33755
|
+
const centerXmm = parseDimensionToMm(rect.center?.x) ?? 0;
|
|
33756
|
+
const centerYmm = parseDimensionToMm(rect.center?.y) ?? 0;
|
|
33757
|
+
const canvasCenterX = canvasXFromPcb(centerXmm);
|
|
33758
|
+
const canvasCenterY = canvasYFromPcb(centerYmm);
|
|
33759
|
+
const rawRadius = extractRectBorderRadius(rect);
|
|
33760
|
+
const borderRadiusInput = typeof rawRadius === "string" ? parseDimensionToMm(rawRadius) : rawRadius;
|
|
33761
|
+
const borderRadiusMm = clampRectBorderRadius(
|
|
33762
|
+
width10,
|
|
33763
|
+
height10,
|
|
33764
|
+
borderRadiusInput
|
|
33660
33765
|
);
|
|
33661
|
-
|
|
33662
|
-
|
|
33663
|
-
|
|
33664
|
-
|
|
33665
|
-
|
|
33766
|
+
ctx.save();
|
|
33767
|
+
ctx.translate(canvasCenterX, canvasCenterY);
|
|
33768
|
+
const halfWidthPx = width10 / 2 * traceTextureResolution;
|
|
33769
|
+
const halfHeightPx = height10 / 2 * traceTextureResolution;
|
|
33770
|
+
const borderRadiusPx = Math.min(
|
|
33771
|
+
borderRadiusMm * traceTextureResolution,
|
|
33772
|
+
halfWidthPx,
|
|
33773
|
+
halfHeightPx
|
|
33774
|
+
);
|
|
33775
|
+
const hasStroke = rect.has_stroke ?? false;
|
|
33776
|
+
const isFilled = rect.is_filled ?? true;
|
|
33777
|
+
const isDashed = rect.is_stroke_dashed ?? false;
|
|
33778
|
+
const strokeWidthPx = hasStroke ? coerceDimensionToMm(rect.stroke_width, 0.1) * traceTextureResolution : 0;
|
|
33779
|
+
const drawRoundedRectPath = (x, y, rectWidth, rectHeight, radius) => {
|
|
33780
|
+
ctx.beginPath();
|
|
33781
|
+
if (radius <= 0) {
|
|
33782
|
+
ctx.rect(x, y, rectWidth, rectHeight);
|
|
33783
|
+
} else {
|
|
33784
|
+
const r = radius;
|
|
33785
|
+
const right = x + rectWidth;
|
|
33786
|
+
const bottom = y + rectHeight;
|
|
33787
|
+
ctx.moveTo(x + r, y);
|
|
33788
|
+
ctx.lineTo(right - r, y);
|
|
33789
|
+
ctx.quadraticCurveTo(right, y, right, y + r);
|
|
33790
|
+
ctx.lineTo(right, bottom - r);
|
|
33791
|
+
ctx.quadraticCurveTo(right, bottom, right - r, bottom);
|
|
33792
|
+
ctx.lineTo(x + r, bottom);
|
|
33793
|
+
ctx.quadraticCurveTo(x, bottom, x, bottom - r);
|
|
33794
|
+
ctx.lineTo(x, y + r);
|
|
33795
|
+
ctx.quadraticCurveTo(x, y, x + r, y);
|
|
33796
|
+
ctx.closePath();
|
|
33797
|
+
}
|
|
33798
|
+
};
|
|
33799
|
+
drawRoundedRectPath(
|
|
33800
|
+
-halfWidthPx,
|
|
33801
|
+
-halfHeightPx,
|
|
33802
|
+
halfWidthPx * 2,
|
|
33803
|
+
halfHeightPx * 2,
|
|
33804
|
+
borderRadiusPx
|
|
33805
|
+
);
|
|
33806
|
+
if (isFilled) {
|
|
33807
|
+
ctx.fill();
|
|
33808
|
+
}
|
|
33809
|
+
if (hasStroke && strokeWidthPx > 0) {
|
|
33810
|
+
ctx.lineWidth = strokeWidthPx;
|
|
33811
|
+
if (isDashed) {
|
|
33812
|
+
const dashLength = Math.max(strokeWidthPx * 2, 1);
|
|
33813
|
+
ctx.setLineDash([dashLength, dashLength]);
|
|
33814
|
+
}
|
|
33815
|
+
ctx.stroke();
|
|
33816
|
+
if (isDashed) {
|
|
33817
|
+
ctx.setLineDash([]);
|
|
33818
|
+
}
|
|
33819
|
+
}
|
|
33820
|
+
ctx.restore();
|
|
33821
|
+
});
|
|
33822
|
+
textsOnLayer.forEach((textS) => {
|
|
33823
|
+
const fontSize = textS.font_size || 0.25;
|
|
33824
|
+
const textStrokeWidth = Math.min(Math.max(0.01, fontSize * 0.1), fontSize * 0.05) * traceTextureResolution;
|
|
33825
|
+
ctx.lineWidth = textStrokeWidth;
|
|
33826
|
+
ctx.lineCap = "butt";
|
|
33827
|
+
ctx.lineJoin = "miter";
|
|
33828
|
+
const rawTextOutlines = (0, import_text2.vectorText)({
|
|
33829
|
+
height: fontSize * 0.45,
|
|
33830
|
+
input: textS.text
|
|
33831
|
+
});
|
|
33832
|
+
const processedTextOutlines = [];
|
|
33833
|
+
rawTextOutlines.forEach((outline) => {
|
|
33834
|
+
if (outline.length === 29) {
|
|
33835
|
+
processedTextOutlines.push(
|
|
33836
|
+
outline.slice(0, 15)
|
|
33837
|
+
);
|
|
33838
|
+
processedTextOutlines.push(
|
|
33839
|
+
outline.slice(14, 29)
|
|
33840
|
+
);
|
|
33841
|
+
} else if (outline.length === 17) {
|
|
33842
|
+
processedTextOutlines.push(
|
|
33843
|
+
outline.slice(0, 10)
|
|
33844
|
+
);
|
|
33845
|
+
processedTextOutlines.push(
|
|
33846
|
+
outline.slice(9, 17)
|
|
33847
|
+
);
|
|
33848
|
+
} else {
|
|
33849
|
+
processedTextOutlines.push(outline);
|
|
33850
|
+
}
|
|
33851
|
+
});
|
|
33852
|
+
const points = processedTextOutlines.flat();
|
|
33853
|
+
const textBounds = {
|
|
33854
|
+
minX: points.length > 0 ? Math.min(...points.map((p) => p[0])) : 0,
|
|
33855
|
+
maxX: points.length > 0 ? Math.max(...points.map((p) => p[0])) : 0,
|
|
33856
|
+
minY: points.length > 0 ? Math.min(...points.map((p) => p[1])) : 0,
|
|
33857
|
+
maxY: points.length > 0 ? Math.max(...points.map((p) => p[1])) : 0
|
|
33858
|
+
};
|
|
33859
|
+
const textCenterX = (textBounds.minX + textBounds.maxX) / 2;
|
|
33860
|
+
const textCenterY = (textBounds.minY + textBounds.maxY) / 2;
|
|
33861
|
+
let xOff = -textCenterX;
|
|
33862
|
+
let yOff = -textCenterY;
|
|
33863
|
+
const alignment = textS.anchor_alignment || "center";
|
|
33864
|
+
if (alignment.includes("left")) {
|
|
33865
|
+
xOff = -textBounds.minX;
|
|
33866
|
+
} else if (alignment.includes("right")) {
|
|
33867
|
+
xOff = -textBounds.maxX;
|
|
33868
|
+
}
|
|
33869
|
+
if (alignment.includes("top")) {
|
|
33870
|
+
yOff = -textBounds.maxY;
|
|
33871
|
+
} else if (alignment.includes("bottom")) {
|
|
33872
|
+
yOff = -textBounds.minY;
|
|
33873
|
+
}
|
|
33874
|
+
const transformMatrices = [];
|
|
33875
|
+
let rotationDeg = textS.ccw_rotation ?? 0;
|
|
33876
|
+
if (textS.layer === "bottom") {
|
|
33877
|
+
transformMatrices.push(
|
|
33878
|
+
translate4(textCenterX, textCenterY),
|
|
33879
|
+
{ a: -1, b: 0, c: 0, d: 1, e: 0, f: 0 },
|
|
33880
|
+
translate4(-textCenterX, -textCenterY)
|
|
33666
33881
|
);
|
|
33882
|
+
rotationDeg = -rotationDeg;
|
|
33667
33883
|
}
|
|
33668
|
-
|
|
33669
|
-
|
|
33670
|
-
|
|
33671
|
-
|
|
33672
|
-
|
|
33673
|
-
|
|
33674
|
-
|
|
33675
|
-
|
|
33676
|
-
|
|
33677
|
-
|
|
33884
|
+
if (rotationDeg) {
|
|
33885
|
+
const rad = rotationDeg * Math.PI / 180;
|
|
33886
|
+
transformMatrices.push(
|
|
33887
|
+
translate4(textCenterX, textCenterY),
|
|
33888
|
+
rotate2(rad),
|
|
33889
|
+
translate4(-textCenterX, -textCenterY)
|
|
33890
|
+
);
|
|
33891
|
+
}
|
|
33892
|
+
const finalTransformMatrix = transformMatrices.length > 0 ? compose(...transformMatrices) : void 0;
|
|
33893
|
+
processedTextOutlines.forEach((segment) => {
|
|
33894
|
+
ctx.beginPath();
|
|
33895
|
+
segment.forEach((p, index2) => {
|
|
33896
|
+
let transformedP = { x: p[0], y: p[1] };
|
|
33897
|
+
if (finalTransformMatrix) {
|
|
33898
|
+
transformedP = applyToPoint(finalTransformMatrix, transformedP);
|
|
33899
|
+
}
|
|
33900
|
+
const pcbX = transformedP.x + xOff + textS.anchor_position.x;
|
|
33901
|
+
const pcbY = transformedP.y + yOff + textS.anchor_position.y;
|
|
33902
|
+
const canvasX = canvasXFromPcb(pcbX);
|
|
33903
|
+
const canvasY = canvasYFromPcb(pcbY);
|
|
33904
|
+
if (index2 === 0) ctx.moveTo(canvasX, canvasY);
|
|
33905
|
+
else ctx.lineTo(canvasX, canvasY);
|
|
33906
|
+
});
|
|
33907
|
+
ctx.stroke();
|
|
33908
|
+
});
|
|
33909
|
+
});
|
|
33910
|
+
const texture = new THREE23.CanvasTexture(canvas);
|
|
33911
|
+
texture.generateMipmaps = true;
|
|
33912
|
+
texture.minFilter = THREE23.LinearMipmapLinearFilter;
|
|
33913
|
+
texture.magFilter = THREE23.LinearFilter;
|
|
33914
|
+
texture.anisotropy = 16;
|
|
33915
|
+
texture.needsUpdate = true;
|
|
33916
|
+
return texture;
|
|
33678
33917
|
}
|
|
33679
33918
|
|
|
33680
|
-
// src/utils/
|
|
33919
|
+
// src/utils/soldermask-texture.ts
|
|
33681
33920
|
import * as THREE24 from "three";
|
|
33682
|
-
|
|
33683
|
-
|
|
33684
|
-
|
|
33685
|
-
|
|
33686
|
-
|
|
33687
|
-
|
|
33688
|
-
|
|
33689
|
-
|
|
33690
|
-
|
|
33691
|
-
const
|
|
33692
|
-
|
|
33693
|
-
|
|
33694
|
-
|
|
33695
|
-
|
|
33696
|
-
|
|
33697
|
-
|
|
33698
|
-
|
|
33699
|
-
|
|
33700
|
-
const dy = p2[1] - p1[1];
|
|
33701
|
-
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
33702
|
-
if (dist < 1e-9) return [];
|
|
33703
|
-
const radius = Math.abs(dist / (2 * Math.sin(theta / 2)));
|
|
33704
|
-
const m = Math.sqrt(Math.max(0, radius * radius - dist / 2 * (dist / 2)));
|
|
33705
|
-
const midPoint = [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2];
|
|
33706
|
-
const ux = dx / dist;
|
|
33707
|
-
const uy = dy / dist;
|
|
33708
|
-
const nx = -uy;
|
|
33709
|
-
const ny = ux;
|
|
33710
|
-
const centerX = midPoint[0] + nx * m * Math.sign(bulge);
|
|
33711
|
-
const centerY = midPoint[1] + ny * m * Math.sign(bulge);
|
|
33712
|
-
const startAngle = Math.atan2(p1[1] - centerY, p1[0] - centerX);
|
|
33713
|
-
const points = [];
|
|
33714
|
-
const numSteps = Math.max(
|
|
33715
|
-
2,
|
|
33716
|
-
Math.ceil(arcSegments * Math.abs(theta) / (Math.PI * 2) * 4)
|
|
33717
|
-
);
|
|
33718
|
-
const angleStep = theta / numSteps;
|
|
33719
|
-
for (let i = 1; i < numSteps; i++) {
|
|
33720
|
-
const angle = startAngle + angleStep * i;
|
|
33721
|
-
points.push([
|
|
33722
|
-
centerX + radius * Math.cos(angle),
|
|
33723
|
-
centerY + radius * Math.sin(angle)
|
|
33724
|
-
]);
|
|
33921
|
+
import { su as su13 } from "@tscircuit/circuit-json-util";
|
|
33922
|
+
function createSoldermaskTextureForLayer({
|
|
33923
|
+
layer,
|
|
33924
|
+
circuitJson,
|
|
33925
|
+
boardData,
|
|
33926
|
+
soldermaskColor,
|
|
33927
|
+
traceTextureResolution
|
|
33928
|
+
}) {
|
|
33929
|
+
const canvas = document.createElement("canvas");
|
|
33930
|
+
const canvasWidth = Math.floor(boardData.width * traceTextureResolution);
|
|
33931
|
+
const canvasHeight = Math.floor(boardData.height * traceTextureResolution);
|
|
33932
|
+
canvas.width = canvasWidth;
|
|
33933
|
+
canvas.height = canvasHeight;
|
|
33934
|
+
const ctx = canvas.getContext("2d");
|
|
33935
|
+
if (!ctx) return null;
|
|
33936
|
+
if (layer === "bottom") {
|
|
33937
|
+
ctx.translate(0, canvasHeight);
|
|
33938
|
+
ctx.scale(1, -1);
|
|
33725
33939
|
}
|
|
33726
|
-
|
|
33727
|
-
|
|
33728
|
-
|
|
33729
|
-
const
|
|
33730
|
-
|
|
33731
|
-
|
|
33732
|
-
|
|
33733
|
-
|
|
33734
|
-
|
|
33735
|
-
if (
|
|
33736
|
-
|
|
33737
|
-
|
|
33738
|
-
|
|
33739
|
-
|
|
33740
|
-
|
|
33940
|
+
ctx.fillStyle = soldermaskColor;
|
|
33941
|
+
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
|
|
33942
|
+
const canvasXFromPcb = (pcbX) => (pcbX - boardData.center.x + boardData.width / 2) * traceTextureResolution;
|
|
33943
|
+
const canvasYFromPcb = (pcbY) => (-(pcbY - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
|
|
33944
|
+
ctx.globalCompositeOperation = "destination-out";
|
|
33945
|
+
ctx.fillStyle = "black";
|
|
33946
|
+
const pcbSmtPads = su13(circuitJson).pcb_smtpad.list();
|
|
33947
|
+
const smtPadsOnLayer = pcbSmtPads.filter((pad2) => pad2.layer === layer);
|
|
33948
|
+
smtPadsOnLayer.forEach((pad2) => {
|
|
33949
|
+
if (pad2.shape === "polygon" && pad2.points) {
|
|
33950
|
+
ctx.beginPath();
|
|
33951
|
+
pad2.points.forEach((point2, index2) => {
|
|
33952
|
+
const px = canvasXFromPcb(point2.x);
|
|
33953
|
+
const py = canvasYFromPcb(point2.y);
|
|
33954
|
+
if (index2 === 0) {
|
|
33955
|
+
ctx.moveTo(px, py);
|
|
33956
|
+
} else {
|
|
33957
|
+
ctx.lineTo(px, py);
|
|
33958
|
+
}
|
|
33959
|
+
});
|
|
33960
|
+
ctx.closePath();
|
|
33961
|
+
ctx.fill();
|
|
33962
|
+
return;
|
|
33963
|
+
}
|
|
33964
|
+
if (pad2.x === void 0 || pad2.y === void 0) return;
|
|
33965
|
+
const x = pad2.x;
|
|
33966
|
+
const y = pad2.y;
|
|
33967
|
+
const canvasX = canvasXFromPcb(x);
|
|
33968
|
+
const canvasY = canvasYFromPcb(y);
|
|
33969
|
+
if (pad2.shape === "rect") {
|
|
33970
|
+
const width10 = pad2.width * traceTextureResolution;
|
|
33971
|
+
const height10 = pad2.height * traceTextureResolution;
|
|
33972
|
+
ctx.fillRect(canvasX - width10 / 2, canvasY - height10 / 2, width10, height10);
|
|
33973
|
+
} else if (pad2.shape === "circle") {
|
|
33974
|
+
const radius = (pad2.radius ?? pad2.width / 2) * traceTextureResolution;
|
|
33975
|
+
ctx.beginPath();
|
|
33976
|
+
ctx.arc(canvasX, canvasY, radius, 0, 2 * Math.PI);
|
|
33977
|
+
ctx.fill();
|
|
33978
|
+
} else if (pad2.shape === "pill" || pad2.shape === "rotated_rect") {
|
|
33979
|
+
const width10 = pad2.width * traceTextureResolution;
|
|
33980
|
+
const height10 = pad2.height * traceTextureResolution;
|
|
33981
|
+
const radius = Math.min(width10, height10) / 2;
|
|
33982
|
+
ctx.beginPath();
|
|
33983
|
+
ctx.roundRect(
|
|
33984
|
+
canvasX - width10 / 2,
|
|
33985
|
+
canvasY - height10 / 2,
|
|
33986
|
+
width10,
|
|
33987
|
+
height10,
|
|
33988
|
+
radius
|
|
33741
33989
|
);
|
|
33742
|
-
|
|
33990
|
+
ctx.fill();
|
|
33743
33991
|
}
|
|
33744
|
-
}
|
|
33745
|
-
|
|
33746
|
-
|
|
33747
|
-
|
|
33748
|
-
|
|
33749
|
-
|
|
33750
|
-
(
|
|
33751
|
-
|
|
33752
|
-
|
|
33753
|
-
|
|
33754
|
-
|
|
33755
|
-
|
|
33756
|
-
|
|
33757
|
-
|
|
33758
|
-
|
|
33759
|
-
|
|
33760
|
-
|
|
33761
|
-
|
|
33762
|
-
|
|
33763
|
-
|
|
33764
|
-
|
|
33765
|
-
|
|
33766
|
-
|
|
33767
|
-
} else if (
|
|
33768
|
-
|
|
33769
|
-
|
|
33770
|
-
|
|
33771
|
-
|
|
33772
|
-
|
|
33773
|
-
|
|
33774
|
-
|
|
33775
|
-
|
|
33776
|
-
|
|
33777
|
-
|
|
33778
|
-
pourOp = Manifold.extrude(
|
|
33779
|
-
crossSection,
|
|
33780
|
-
pourThickness,
|
|
33781
|
-
0,
|
|
33782
|
-
// nDivisions
|
|
33783
|
-
0,
|
|
33784
|
-
// twistDegrees
|
|
33785
|
-
[1, 1],
|
|
33786
|
-
// scaleTop
|
|
33787
|
-
true
|
|
33788
|
-
// center extrusion
|
|
33789
|
-
).translate([0, 0, zPos]);
|
|
33790
|
-
manifoldInstancesForCleanup.push(pourOp);
|
|
33791
|
-
} else if (pour.shape === "brep") {
|
|
33792
|
-
const brepShape = pour.brep_shape;
|
|
33793
|
-
if (!brepShape || !brepShape.outer_ring) continue;
|
|
33794
|
-
let outerRingPoints = ringToPoints2(
|
|
33795
|
-
brepShape.outer_ring,
|
|
33796
|
-
SMOOTH_CIRCLE_SEGMENTS
|
|
33992
|
+
});
|
|
33993
|
+
const pcbVias = su13(circuitJson).pcb_via.list();
|
|
33994
|
+
pcbVias.forEach((via) => {
|
|
33995
|
+
const canvasX = canvasXFromPcb(via.x);
|
|
33996
|
+
const canvasY = canvasYFromPcb(via.y);
|
|
33997
|
+
const canvasRadius = via.outer_diameter / 2 * traceTextureResolution;
|
|
33998
|
+
ctx.beginPath();
|
|
33999
|
+
ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI);
|
|
34000
|
+
ctx.fill();
|
|
34001
|
+
});
|
|
34002
|
+
const pcbPlatedHoles = su13(circuitJson).pcb_plated_hole.list();
|
|
34003
|
+
pcbPlatedHoles.forEach((hole) => {
|
|
34004
|
+
if (!hole.layers?.includes(layer)) return;
|
|
34005
|
+
const x = hole.x;
|
|
34006
|
+
const y = hole.y;
|
|
34007
|
+
const canvasX = canvasXFromPcb(x);
|
|
34008
|
+
const canvasY = canvasYFromPcb(y);
|
|
34009
|
+
if (hole.shape === "circle") {
|
|
34010
|
+
const outerDiameter = hole.outer_diameter;
|
|
34011
|
+
const canvasRadius = outerDiameter / 2 * traceTextureResolution;
|
|
34012
|
+
ctx.beginPath();
|
|
34013
|
+
ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI);
|
|
34014
|
+
ctx.fill();
|
|
34015
|
+
} else if (hole.shape === "pill" || hole.shape === "oval") {
|
|
34016
|
+
const width10 = (hole.outer_width ?? hole.outer_diameter ?? hole.hole_width) * traceTextureResolution;
|
|
34017
|
+
const height10 = (hole.outer_height ?? hole.outer_diameter ?? hole.hole_height) * traceTextureResolution;
|
|
34018
|
+
const radius = Math.min(width10, height10) / 2;
|
|
34019
|
+
ctx.beginPath();
|
|
34020
|
+
ctx.roundRect(
|
|
34021
|
+
canvasX - width10 / 2,
|
|
34022
|
+
canvasY - height10 / 2,
|
|
34023
|
+
width10,
|
|
34024
|
+
height10,
|
|
34025
|
+
radius
|
|
33797
34026
|
);
|
|
33798
|
-
|
|
33799
|
-
outerRingPoints = outerRingPoints.reverse();
|
|
33800
|
-
}
|
|
33801
|
-
const polygons = [outerRingPoints];
|
|
33802
|
-
if (brepShape.inner_rings) {
|
|
33803
|
-
const innerRingsPoints = brepShape.inner_rings.map((ring2) => {
|
|
33804
|
-
let points = ringToPoints2(ring2, SMOOTH_CIRCLE_SEGMENTS);
|
|
33805
|
-
if (!arePointsClockwise5(points)) {
|
|
33806
|
-
points = points.reverse();
|
|
33807
|
-
}
|
|
33808
|
-
return points;
|
|
33809
|
-
});
|
|
33810
|
-
polygons.push(...innerRingsPoints);
|
|
33811
|
-
}
|
|
33812
|
-
const crossSection = CrossSection.ofPolygons(polygons);
|
|
33813
|
-
manifoldInstancesForCleanup.push(crossSection);
|
|
33814
|
-
pourOp = Manifold.extrude(
|
|
33815
|
-
crossSection,
|
|
33816
|
-
pourThickness,
|
|
33817
|
-
0,
|
|
33818
|
-
// nDivisions
|
|
33819
|
-
0,
|
|
33820
|
-
// twistDegrees
|
|
33821
|
-
[1, 1],
|
|
33822
|
-
// scaleTop
|
|
33823
|
-
true
|
|
33824
|
-
// center extrusion
|
|
33825
|
-
).translate([0, 0, zPos]);
|
|
33826
|
-
manifoldInstancesForCleanup.push(pourOp);
|
|
34027
|
+
ctx.fill();
|
|
33827
34028
|
}
|
|
33828
|
-
|
|
33829
|
-
|
|
33830
|
-
|
|
33831
|
-
|
|
33832
|
-
|
|
33833
|
-
|
|
33834
|
-
|
|
33835
|
-
|
|
33836
|
-
|
|
33837
|
-
|
|
33838
|
-
|
|
33839
|
-
|
|
33840
|
-
|
|
33841
|
-
|
|
33842
|
-
|
|
33843
|
-
|
|
33844
|
-
|
|
33845
|
-
|
|
33846
|
-
|
|
34029
|
+
});
|
|
34030
|
+
const pcbCopperPours = su13(circuitJson).pcb_copper_pour.list();
|
|
34031
|
+
pcbCopperPours.forEach((pour) => {
|
|
34032
|
+
if (pour.layer !== layer) return;
|
|
34033
|
+
if (pour.covered_with_solder_mask !== false) return;
|
|
34034
|
+
if (pour.shape === "rect") {
|
|
34035
|
+
const centerX = canvasXFromPcb(pour.center.x);
|
|
34036
|
+
const centerY = canvasYFromPcb(pour.center.y);
|
|
34037
|
+
const width10 = pour.width * traceTextureResolution;
|
|
34038
|
+
const height10 = pour.height * traceTextureResolution;
|
|
34039
|
+
ctx.fillRect(centerX - width10 / 2, centerY - height10 / 2, width10, height10);
|
|
34040
|
+
} else if (pour.shape === "polygon" && pour.points) {
|
|
34041
|
+
ctx.beginPath();
|
|
34042
|
+
pour.points.forEach((point2, index2) => {
|
|
34043
|
+
const px = canvasXFromPcb(point2.x);
|
|
34044
|
+
const py = canvasYFromPcb(point2.y);
|
|
34045
|
+
if (index2 === 0) {
|
|
34046
|
+
ctx.moveTo(px, py);
|
|
34047
|
+
} else {
|
|
34048
|
+
ctx.lineTo(px, py);
|
|
34049
|
+
}
|
|
33847
34050
|
});
|
|
34051
|
+
ctx.closePath();
|
|
34052
|
+
ctx.fill();
|
|
33848
34053
|
}
|
|
33849
|
-
}
|
|
33850
|
-
|
|
34054
|
+
});
|
|
34055
|
+
ctx.globalCompositeOperation = "source-over";
|
|
34056
|
+
const texture = new THREE24.CanvasTexture(canvas);
|
|
34057
|
+
texture.generateMipmaps = true;
|
|
34058
|
+
texture.minFilter = THREE24.LinearMipmapLinearFilter;
|
|
34059
|
+
texture.magFilter = THREE24.LinearFilter;
|
|
34060
|
+
texture.anisotropy = 16;
|
|
34061
|
+
texture.needsUpdate = true;
|
|
34062
|
+
return texture;
|
|
33851
34063
|
}
|
|
33852
34064
|
|
|
33853
|
-
// src/utils/
|
|
33854
|
-
import
|
|
33855
|
-
|
|
33856
|
-
|
|
33857
|
-
|
|
33858
|
-
|
|
33859
|
-
|
|
33860
|
-
|
|
33861
|
-
|
|
33862
|
-
|
|
34065
|
+
// src/utils/trace-texture.ts
|
|
34066
|
+
import * as THREE25 from "three";
|
|
34067
|
+
import { su as su14 } from "@tscircuit/circuit-json-util";
|
|
34068
|
+
function isWireRoutePoint(point2) {
|
|
34069
|
+
return point2 && point2.route_type === "wire" && typeof point2.layer === "string" && typeof point2.width === "number";
|
|
34070
|
+
}
|
|
34071
|
+
function createTraceTextureForLayer({
|
|
34072
|
+
layer,
|
|
34073
|
+
circuitJson,
|
|
34074
|
+
boardData,
|
|
34075
|
+
traceColor,
|
|
34076
|
+
traceTextureResolution
|
|
34077
|
+
}) {
|
|
34078
|
+
const pcbTraces = su14(circuitJson).pcb_trace.list();
|
|
34079
|
+
const allPcbVias = su14(circuitJson).pcb_via.list();
|
|
34080
|
+
const allPcbPlatedHoles = su14(
|
|
34081
|
+
circuitJson
|
|
34082
|
+
).pcb_plated_hole.list();
|
|
34083
|
+
const tracesOnLayer = pcbTraces.filter(
|
|
34084
|
+
(t) => t.route.some((p) => isWireRoutePoint(p) && p.layer === layer)
|
|
34085
|
+
);
|
|
34086
|
+
if (tracesOnLayer.length === 0) return null;
|
|
34087
|
+
const canvas = document.createElement("canvas");
|
|
34088
|
+
const canvasWidth = Math.floor(boardData.width * traceTextureResolution);
|
|
34089
|
+
const canvasHeight = Math.floor(boardData.height * traceTextureResolution);
|
|
34090
|
+
canvas.width = canvasWidth;
|
|
34091
|
+
canvas.height = canvasHeight;
|
|
34092
|
+
const ctx = canvas.getContext("2d");
|
|
34093
|
+
if (!ctx) return null;
|
|
34094
|
+
if (layer === "bottom") {
|
|
34095
|
+
ctx.translate(0, canvasHeight);
|
|
34096
|
+
ctx.scale(1, -1);
|
|
33863
34097
|
}
|
|
33864
|
-
|
|
33865
|
-
|
|
33866
|
-
|
|
33867
|
-
|
|
33868
|
-
|
|
33869
|
-
|
|
33870
|
-
|
|
33871
|
-
|
|
33872
|
-
|
|
33873
|
-
|
|
33874
|
-
|
|
33875
|
-
const rectCornerRadius = extractRectBorderRadius(cutout);
|
|
33876
|
-
if (typeof rectCornerRadius === "number" && rectCornerRadius > 0) {
|
|
33877
|
-
cutoutOp = createRoundedRectPrism({
|
|
33878
|
-
Manifold,
|
|
33879
|
-
width: cutout.width,
|
|
33880
|
-
height: cutout.height,
|
|
33881
|
-
thickness: cutoutHeight,
|
|
33882
|
-
borderRadius: rectCornerRadius
|
|
33883
|
-
});
|
|
33884
|
-
} else {
|
|
33885
|
-
cutoutOp = Manifold.cube(
|
|
33886
|
-
[cutout.width, cutout.height, cutoutHeight],
|
|
33887
|
-
true
|
|
33888
|
-
// centered
|
|
33889
|
-
);
|
|
33890
|
-
}
|
|
33891
|
-
manifoldInstancesForCleanup.push(cutoutOp);
|
|
33892
|
-
if (cutout.rotation) {
|
|
33893
|
-
const rotatedOp = cutoutOp.rotate([0, 0, cutout.rotation]);
|
|
33894
|
-
manifoldInstancesForCleanup.push(rotatedOp);
|
|
33895
|
-
cutoutOp = rotatedOp;
|
|
33896
|
-
}
|
|
33897
|
-
cutoutOp = cutoutOp.translate([
|
|
33898
|
-
cutout.center.x,
|
|
33899
|
-
cutout.center.y,
|
|
33900
|
-
0
|
|
33901
|
-
// Centered vertically by Manifold.cube, so Z is 0 for board plane
|
|
33902
|
-
]);
|
|
33903
|
-
manifoldInstancesForCleanup.push(cutoutOp);
|
|
33904
|
-
break;
|
|
33905
|
-
}
|
|
33906
|
-
case "circle":
|
|
33907
|
-
cutoutOp = Manifold.cylinder(
|
|
33908
|
-
cutoutHeight,
|
|
33909
|
-
cutout.radius,
|
|
33910
|
-
-1,
|
|
33911
|
-
// default for radiusHigh
|
|
33912
|
-
SMOOTH_CIRCLE_SEGMENTS,
|
|
33913
|
-
true
|
|
33914
|
-
// centered
|
|
33915
|
-
);
|
|
33916
|
-
manifoldInstancesForCleanup.push(cutoutOp);
|
|
33917
|
-
cutoutOp = cutoutOp.translate([cutout.center.x, cutout.center.y, 0]);
|
|
33918
|
-
manifoldInstancesForCleanup.push(cutoutOp);
|
|
33919
|
-
break;
|
|
33920
|
-
case "polygon":
|
|
33921
|
-
if (cutout.points.length < 3) {
|
|
33922
|
-
console.warn(
|
|
33923
|
-
`PCB Cutout [${cutout.pcb_cutout_id}] polygon has fewer than 3 points, skipping.`
|
|
33924
|
-
);
|
|
33925
|
-
continue;
|
|
33926
|
-
}
|
|
33927
|
-
let pointsVec2 = cutout.points.map((p) => [
|
|
33928
|
-
p.x,
|
|
33929
|
-
p.y
|
|
33930
|
-
]);
|
|
33931
|
-
if (arePointsClockwise6(pointsVec2)) {
|
|
33932
|
-
pointsVec2 = pointsVec2.reverse();
|
|
33933
|
-
}
|
|
33934
|
-
const crossSection = CrossSection.ofPolygons([pointsVec2]);
|
|
33935
|
-
manifoldInstancesForCleanup.push(crossSection);
|
|
33936
|
-
cutoutOp = Manifold.extrude(
|
|
33937
|
-
crossSection,
|
|
33938
|
-
cutoutHeight,
|
|
33939
|
-
0,
|
|
33940
|
-
// nDivisions
|
|
33941
|
-
0,
|
|
33942
|
-
// twistDegrees
|
|
33943
|
-
[1, 1],
|
|
33944
|
-
// scaleTop
|
|
33945
|
-
true
|
|
33946
|
-
// center extrusion
|
|
33947
|
-
);
|
|
33948
|
-
manifoldInstancesForCleanup.push(cutoutOp);
|
|
33949
|
-
break;
|
|
33950
|
-
default:
|
|
33951
|
-
console.warn(
|
|
33952
|
-
`Unsupported cutout shape: ${cutout.shape} for cutout ${cutout.pcb_cutout_id}`
|
|
33953
|
-
);
|
|
34098
|
+
tracesOnLayer.forEach((trace) => {
|
|
34099
|
+
let firstPoint = true;
|
|
34100
|
+
ctx.beginPath();
|
|
34101
|
+
ctx.strokeStyle = traceColor;
|
|
34102
|
+
ctx.lineCap = "round";
|
|
34103
|
+
ctx.lineJoin = "round";
|
|
34104
|
+
let currentLineWidth = 0;
|
|
34105
|
+
for (const point2 of trace.route) {
|
|
34106
|
+
if (!isWireRoutePoint(point2) || point2.layer !== layer) {
|
|
34107
|
+
if (!firstPoint) ctx.stroke();
|
|
34108
|
+
firstPoint = true;
|
|
33954
34109
|
continue;
|
|
34110
|
+
}
|
|
34111
|
+
const pcbX = point2.x;
|
|
34112
|
+
const pcbY = point2.y;
|
|
34113
|
+
currentLineWidth = point2.width * traceTextureResolution;
|
|
34114
|
+
ctx.lineWidth = currentLineWidth;
|
|
34115
|
+
const canvasX = (pcbX - boardData.center.x + boardData.width / 2) * traceTextureResolution;
|
|
34116
|
+
const canvasY = (-(pcbY - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
|
|
34117
|
+
if (firstPoint) {
|
|
34118
|
+
ctx.moveTo(canvasX, canvasY);
|
|
34119
|
+
firstPoint = false;
|
|
34120
|
+
} else {
|
|
34121
|
+
ctx.lineTo(canvasX, canvasY);
|
|
34122
|
+
}
|
|
33955
34123
|
}
|
|
33956
|
-
if (
|
|
33957
|
-
|
|
34124
|
+
if (!firstPoint) {
|
|
34125
|
+
ctx.stroke();
|
|
33958
34126
|
}
|
|
33959
|
-
}
|
|
33960
|
-
|
|
34127
|
+
});
|
|
34128
|
+
ctx.globalCompositeOperation = "destination-out";
|
|
34129
|
+
ctx.fillStyle = "black";
|
|
34130
|
+
allPcbVias.forEach((via) => {
|
|
34131
|
+
const canvasX = (via.x - boardData.center.x + boardData.width / 2) * traceTextureResolution;
|
|
34132
|
+
const canvasY = (-(via.y - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
|
|
34133
|
+
const canvasRadius = via.outer_diameter / 2 * traceTextureResolution;
|
|
34134
|
+
ctx.beginPath();
|
|
34135
|
+
ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI, false);
|
|
34136
|
+
ctx.fill();
|
|
34137
|
+
});
|
|
34138
|
+
allPcbPlatedHoles.forEach((ph) => {
|
|
34139
|
+
if (ph.layers.includes(layer) && ph.shape === "circle") {
|
|
34140
|
+
const canvasX = (ph.x - boardData.center.x + boardData.width / 2) * traceTextureResolution;
|
|
34141
|
+
const canvasY = (-(ph.y - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
|
|
34142
|
+
const canvasRadius = ph.outer_diameter / 2 * traceTextureResolution;
|
|
34143
|
+
ctx.beginPath();
|
|
34144
|
+
ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI, false);
|
|
34145
|
+
ctx.fill();
|
|
34146
|
+
}
|
|
34147
|
+
});
|
|
34148
|
+
ctx.globalCompositeOperation = "source-over";
|
|
34149
|
+
const texture = new THREE25.CanvasTexture(canvas);
|
|
34150
|
+
texture.generateMipmaps = true;
|
|
34151
|
+
texture.minFilter = THREE25.LinearMipmapLinearFilter;
|
|
34152
|
+
texture.magFilter = THREE25.LinearFilter;
|
|
34153
|
+
texture.anisotropy = 16;
|
|
34154
|
+
texture.needsUpdate = true;
|
|
34155
|
+
return texture;
|
|
33961
34156
|
}
|
|
33962
34157
|
|
|
33963
34158
|
// src/hooks/useManifoldBoardBuilder.ts
|
|
@@ -33972,7 +34167,7 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
|
|
|
33972
34167
|
const panels = circuitJson.filter(
|
|
33973
34168
|
(e) => e.type === "pcb_panel"
|
|
33974
34169
|
);
|
|
33975
|
-
const boards =
|
|
34170
|
+
const boards = su15(circuitJson).pcb_board.list();
|
|
33976
34171
|
if (panels.length > 0) {
|
|
33977
34172
|
const panel = panels[0];
|
|
33978
34173
|
return {
|
|
@@ -33991,7 +34186,7 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
|
|
|
33991
34186
|
return boardsNotInPanel.length > 0 ? boardsNotInPanel[0] : null;
|
|
33992
34187
|
}, [circuitJson]);
|
|
33993
34188
|
const isFauxBoard = useMemo19(() => {
|
|
33994
|
-
const boards =
|
|
34189
|
+
const boards = su15(circuitJson).pcb_board.list();
|
|
33995
34190
|
return boards.length > 0 && boards[0].pcb_board_id === "faux-board";
|
|
33996
34191
|
}, [circuitJson]);
|
|
33997
34192
|
useEffect22(() => {
|
|
@@ -34126,7 +34321,7 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
|
|
|
34126
34321
|
{
|
|
34127
34322
|
key: "plated-holes-union",
|
|
34128
34323
|
geometry: cutPlatedGeom,
|
|
34129
|
-
color: new
|
|
34324
|
+
color: new THREE26.Color(
|
|
34130
34325
|
colors.copper[0],
|
|
34131
34326
|
colors.copper[1],
|
|
34132
34327
|
colors.copper[2]
|
|
@@ -34156,7 +34351,7 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
|
|
|
34156
34351
|
const matColorArray = boardMaterialColors[boardData.material] ?? colors.fr4Green;
|
|
34157
34352
|
currentGeoms.board = {
|
|
34158
34353
|
geometry: finalBoardGeom,
|
|
34159
|
-
color: new
|
|
34354
|
+
color: new THREE26.Color(
|
|
34160
34355
|
matColorArray[0],
|
|
34161
34356
|
matColorArray[1],
|
|
34162
34357
|
matColorArray[2]
|
|
@@ -34217,6 +34412,22 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
|
|
|
34217
34412
|
silkscreenColor,
|
|
34218
34413
|
traceTextureResolution: TRACE_TEXTURE_RESOLUTION
|
|
34219
34414
|
});
|
|
34415
|
+
const soldermaskColorArr = tracesMaterialColors[boardData.material] ?? colors.fr4GreenSolderWithMask;
|
|
34416
|
+
const soldermaskColor = `rgb(${Math.round(soldermaskColorArr[0] * 255)}, ${Math.round(soldermaskColorArr[1] * 255)}, ${Math.round(soldermaskColorArr[2] * 255)})`;
|
|
34417
|
+
currentTextures.topSoldermask = createSoldermaskTextureForLayer({
|
|
34418
|
+
layer: "top",
|
|
34419
|
+
circuitJson,
|
|
34420
|
+
boardData,
|
|
34421
|
+
soldermaskColor,
|
|
34422
|
+
traceTextureResolution: TRACE_TEXTURE_RESOLUTION
|
|
34423
|
+
});
|
|
34424
|
+
currentTextures.bottomSoldermask = createSoldermaskTextureForLayer({
|
|
34425
|
+
layer: "bottom",
|
|
34426
|
+
circuitJson,
|
|
34427
|
+
boardData,
|
|
34428
|
+
soldermaskColor,
|
|
34429
|
+
traceTextureResolution: TRACE_TEXTURE_RESOLUTION
|
|
34430
|
+
});
|
|
34220
34431
|
setTextures(currentTextures);
|
|
34221
34432
|
} catch (e) {
|
|
34222
34433
|
console.error("Error processing geometry with Manifold in hook:", e);
|
|
@@ -34245,11 +34456,11 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
|
|
|
34245
34456
|
};
|
|
34246
34457
|
|
|
34247
34458
|
// src/utils/manifold/create-three-geometry-meshes.ts
|
|
34248
|
-
import * as
|
|
34459
|
+
import * as THREE28 from "three";
|
|
34249
34460
|
|
|
34250
34461
|
// src/utils/create-board-material.ts
|
|
34251
|
-
import * as
|
|
34252
|
-
var DEFAULT_SIDE =
|
|
34462
|
+
import * as THREE27 from "three";
|
|
34463
|
+
var DEFAULT_SIDE = THREE27.DoubleSide;
|
|
34253
34464
|
var createBoardMaterial = ({
|
|
34254
34465
|
material,
|
|
34255
34466
|
color,
|
|
@@ -34257,7 +34468,7 @@ var createBoardMaterial = ({
|
|
|
34257
34468
|
isFaux = false
|
|
34258
34469
|
}) => {
|
|
34259
34470
|
if (material === "fr4") {
|
|
34260
|
-
return new
|
|
34471
|
+
return new THREE27.MeshPhysicalMaterial({
|
|
34261
34472
|
color,
|
|
34262
34473
|
side,
|
|
34263
34474
|
metalness: 0,
|
|
@@ -34271,7 +34482,7 @@ var createBoardMaterial = ({
|
|
|
34271
34482
|
flatShading: true
|
|
34272
34483
|
});
|
|
34273
34484
|
}
|
|
34274
|
-
return new
|
|
34485
|
+
return new THREE27.MeshStandardMaterial({
|
|
34275
34486
|
color,
|
|
34276
34487
|
side,
|
|
34277
34488
|
flatShading: true,
|
|
@@ -34287,12 +34498,12 @@ function createGeometryMeshes(geoms) {
|
|
|
34287
34498
|
const meshes = [];
|
|
34288
34499
|
if (!geoms) return meshes;
|
|
34289
34500
|
if (geoms.board && geoms.board.geometry) {
|
|
34290
|
-
const mesh = new
|
|
34501
|
+
const mesh = new THREE28.Mesh(
|
|
34291
34502
|
geoms.board.geometry,
|
|
34292
34503
|
createBoardMaterial({
|
|
34293
34504
|
material: geoms.board.material,
|
|
34294
34505
|
color: geoms.board.color,
|
|
34295
|
-
side:
|
|
34506
|
+
side: THREE28.DoubleSide,
|
|
34296
34507
|
isFaux: geoms.board.isFaux
|
|
34297
34508
|
})
|
|
34298
34509
|
);
|
|
@@ -34302,11 +34513,11 @@ function createGeometryMeshes(geoms) {
|
|
|
34302
34513
|
const createMeshesFromArray = (geomArray) => {
|
|
34303
34514
|
if (geomArray) {
|
|
34304
34515
|
geomArray.forEach((comp) => {
|
|
34305
|
-
const mesh = new
|
|
34516
|
+
const mesh = new THREE28.Mesh(
|
|
34306
34517
|
comp.geometry,
|
|
34307
|
-
new
|
|
34518
|
+
new THREE28.MeshStandardMaterial({
|
|
34308
34519
|
color: comp.color,
|
|
34309
|
-
side:
|
|
34520
|
+
side: THREE28.DoubleSide,
|
|
34310
34521
|
flatShading: true,
|
|
34311
34522
|
// Consistent with board
|
|
34312
34523
|
polygonOffset: true,
|
|
@@ -34327,21 +34538,24 @@ function createGeometryMeshes(geoms) {
|
|
|
34327
34538
|
}
|
|
34328
34539
|
|
|
34329
34540
|
// src/utils/manifold/create-three-texture-meshes.ts
|
|
34330
|
-
import * as
|
|
34541
|
+
import * as THREE29 from "three";
|
|
34331
34542
|
function createTextureMeshes(textures, boardData, pcbThickness) {
|
|
34332
34543
|
const meshes = [];
|
|
34333
34544
|
if (!textures || !boardData || pcbThickness === null) return meshes;
|
|
34334
|
-
const createTexturePlane = (texture, yOffset, isBottomLayer, keySuffix) => {
|
|
34545
|
+
const createTexturePlane = (texture, yOffset, isBottomLayer, keySuffix, usePolygonOffset = false) => {
|
|
34335
34546
|
if (!texture) return null;
|
|
34336
|
-
const planeGeom = new
|
|
34337
|
-
const material = new
|
|
34547
|
+
const planeGeom = new THREE29.PlaneGeometry(boardData.width, boardData.height);
|
|
34548
|
+
const material = new THREE29.MeshBasicMaterial({
|
|
34338
34549
|
map: texture,
|
|
34339
34550
|
transparent: true,
|
|
34340
|
-
side:
|
|
34341
|
-
depthWrite: false
|
|
34551
|
+
side: THREE29.DoubleSide,
|
|
34552
|
+
depthWrite: false,
|
|
34342
34553
|
// Important for layers to avoid z-fighting issues with board itself
|
|
34554
|
+
polygonOffset: usePolygonOffset,
|
|
34555
|
+
polygonOffsetFactor: usePolygonOffset ? -1 : 0,
|
|
34556
|
+
polygonOffsetUnits: usePolygonOffset ? -1 : 0
|
|
34343
34557
|
});
|
|
34344
|
-
const mesh = new
|
|
34558
|
+
const mesh = new THREE29.Mesh(planeGeom, material);
|
|
34345
34559
|
mesh.position.set(boardData.center.x, boardData.center.y, yOffset);
|
|
34346
34560
|
if (isBottomLayer) {
|
|
34347
34561
|
mesh.rotation.set(Math.PI, 0, 0);
|
|
@@ -34380,6 +34594,26 @@ function createTextureMeshes(textures, boardData, pcbThickness) {
|
|
|
34380
34594
|
"silkscreen"
|
|
34381
34595
|
);
|
|
34382
34596
|
if (bottomSilkscreenMesh) meshes.push(bottomSilkscreenMesh);
|
|
34597
|
+
const topSoldermaskMesh = createTexturePlane(
|
|
34598
|
+
textures.topSoldermask,
|
|
34599
|
+
pcbThickness / 2 + 8e-4,
|
|
34600
|
+
// Just above board surface, below traces
|
|
34601
|
+
false,
|
|
34602
|
+
"soldermask",
|
|
34603
|
+
true
|
|
34604
|
+
// Enable polygon offset
|
|
34605
|
+
);
|
|
34606
|
+
if (topSoldermaskMesh) meshes.push(topSoldermaskMesh);
|
|
34607
|
+
const bottomSoldermaskMesh = createTexturePlane(
|
|
34608
|
+
textures.bottomSoldermask,
|
|
34609
|
+
-pcbThickness / 2 - 8e-4,
|
|
34610
|
+
// Just below board surface (bottom side)
|
|
34611
|
+
true,
|
|
34612
|
+
"soldermask",
|
|
34613
|
+
true
|
|
34614
|
+
// Enable polygon offset
|
|
34615
|
+
);
|
|
34616
|
+
if (bottomSoldermaskMesh) meshes.push(bottomSoldermaskMesh);
|
|
34383
34617
|
return meshes;
|
|
34384
34618
|
}
|
|
34385
34619
|
|
|
@@ -34424,6 +34658,10 @@ var BoardMeshes = ({
|
|
|
34424
34658
|
shouldShow = visibility.topSilkscreen;
|
|
34425
34659
|
} else if (mesh.name.includes("bottom-silkscreen")) {
|
|
34426
34660
|
shouldShow = visibility.bottomSilkscreen;
|
|
34661
|
+
} else if (mesh.name.includes("top-soldermask")) {
|
|
34662
|
+
shouldShow = visibility.topMask;
|
|
34663
|
+
} else if (mesh.name.includes("bottom-soldermask")) {
|
|
34664
|
+
shouldShow = visibility.bottomMask;
|
|
34427
34665
|
}
|
|
34428
34666
|
if (shouldShow) {
|
|
34429
34667
|
rootObject.add(mesh);
|
|
@@ -34536,7 +34774,7 @@ try {
|
|
|
34536
34774
|
[textures, boardData, pcbThickness]
|
|
34537
34775
|
);
|
|
34538
34776
|
const cadComponents = useMemo20(
|
|
34539
|
-
() =>
|
|
34777
|
+
() => su16(circuitJson).cad_component.list(),
|
|
34540
34778
|
[circuitJson]
|
|
34541
34779
|
);
|
|
34542
34780
|
const boardDimensions = useMemo20(() => {
|
|
@@ -40651,6 +40889,48 @@ var AppearanceMenu = () => {
|
|
|
40651
40889
|
]
|
|
40652
40890
|
}
|
|
40653
40891
|
),
|
|
40892
|
+
/* @__PURE__ */ jsxs8(
|
|
40893
|
+
Item22,
|
|
40894
|
+
{
|
|
40895
|
+
style: {
|
|
40896
|
+
...itemStyles,
|
|
40897
|
+
backgroundColor: hoveredItem === "topMask" ? "#404040" : "transparent"
|
|
40898
|
+
},
|
|
40899
|
+
onSelect: (e) => e.preventDefault(),
|
|
40900
|
+
onPointerDown: (e) => {
|
|
40901
|
+
e.preventDefault();
|
|
40902
|
+
setLayerVisibility("topMask", !visibility.topMask);
|
|
40903
|
+
},
|
|
40904
|
+
onMouseEnter: () => setHoveredItem("topMask"),
|
|
40905
|
+
onMouseLeave: () => setHoveredItem(null),
|
|
40906
|
+
onTouchStart: () => setHoveredItem("topMask"),
|
|
40907
|
+
children: [
|
|
40908
|
+
/* @__PURE__ */ jsx34("span", { style: iconContainerStyles, children: visibility.topMask && /* @__PURE__ */ jsx34(CheckIcon, {}) }),
|
|
40909
|
+
/* @__PURE__ */ jsx34("span", { style: { display: "flex", alignItems: "center" }, children: "Top Soldermask" })
|
|
40910
|
+
]
|
|
40911
|
+
}
|
|
40912
|
+
),
|
|
40913
|
+
/* @__PURE__ */ jsxs8(
|
|
40914
|
+
Item22,
|
|
40915
|
+
{
|
|
40916
|
+
style: {
|
|
40917
|
+
...itemStyles,
|
|
40918
|
+
backgroundColor: hoveredItem === "bottomMask" ? "#404040" : "transparent"
|
|
40919
|
+
},
|
|
40920
|
+
onSelect: (e) => e.preventDefault(),
|
|
40921
|
+
onPointerDown: (e) => {
|
|
40922
|
+
e.preventDefault();
|
|
40923
|
+
setLayerVisibility("bottomMask", !visibility.bottomMask);
|
|
40924
|
+
},
|
|
40925
|
+
onMouseEnter: () => setHoveredItem("bottomMask"),
|
|
40926
|
+
onMouseLeave: () => setHoveredItem(null),
|
|
40927
|
+
onTouchStart: () => setHoveredItem("bottomMask"),
|
|
40928
|
+
children: [
|
|
40929
|
+
/* @__PURE__ */ jsx34("span", { style: iconContainerStyles, children: visibility.bottomMask && /* @__PURE__ */ jsx34(CheckIcon, {}) }),
|
|
40930
|
+
/* @__PURE__ */ jsx34("span", { style: { display: "flex", alignItems: "center" }, children: "Bottom Soldermask" })
|
|
40931
|
+
]
|
|
40932
|
+
}
|
|
40933
|
+
),
|
|
40654
40934
|
/* @__PURE__ */ jsxs8(
|
|
40655
40935
|
Item22,
|
|
40656
40936
|
{
|
|
@@ -41490,7 +41770,7 @@ var CadViewerInner = (props) => {
|
|
|
41490
41770
|
);
|
|
41491
41771
|
};
|
|
41492
41772
|
var CadViewer = (props) => {
|
|
41493
|
-
const defaultTarget = useMemo28(() => new
|
|
41773
|
+
const defaultTarget = useMemo28(() => new THREE30.Vector3(0, 0, 0), []);
|
|
41494
41774
|
const initialCameraPosition = useMemo28(
|
|
41495
41775
|
() => [5, -5, 5],
|
|
41496
41776
|
[]
|
|
@@ -41507,12 +41787,12 @@ var CadViewer = (props) => {
|
|
|
41507
41787
|
|
|
41508
41788
|
// src/convert-circuit-json-to-3d-svg.ts
|
|
41509
41789
|
var import_debug = __toESM(require_browser(), 1);
|
|
41510
|
-
import { su as
|
|
41511
|
-
import * as
|
|
41790
|
+
import { su as su17 } from "@tscircuit/circuit-json-util";
|
|
41791
|
+
import * as THREE34 from "three";
|
|
41512
41792
|
import { SVGRenderer } from "three/examples/jsm/renderers/SVGRenderer.js";
|
|
41513
41793
|
|
|
41514
41794
|
// src/utils/create-geometry-from-polygons.ts
|
|
41515
|
-
import * as
|
|
41795
|
+
import * as THREE31 from "three";
|
|
41516
41796
|
import { BufferGeometry as BufferGeometry3, Float32BufferAttribute as Float32BufferAttribute2 } from "three";
|
|
41517
41797
|
function createGeometryFromPolygons(polygons) {
|
|
41518
41798
|
const geometry = new BufferGeometry3();
|
|
@@ -41526,12 +41806,12 @@ function createGeometryFromPolygons(polygons) {
|
|
|
41526
41806
|
...polygon3.vertices[i + 1]
|
|
41527
41807
|
// Third vertex
|
|
41528
41808
|
);
|
|
41529
|
-
const v1 = new
|
|
41530
|
-
const v2 = new
|
|
41531
|
-
const v3 = new
|
|
41532
|
-
const normal = new
|
|
41533
|
-
new
|
|
41534
|
-
new
|
|
41809
|
+
const v1 = new THREE31.Vector3(...polygon3.vertices[0]);
|
|
41810
|
+
const v2 = new THREE31.Vector3(...polygon3.vertices[i]);
|
|
41811
|
+
const v3 = new THREE31.Vector3(...polygon3.vertices[i + 1]);
|
|
41812
|
+
const normal = new THREE31.Vector3().crossVectors(
|
|
41813
|
+
new THREE31.Vector3().subVectors(v2, v1),
|
|
41814
|
+
new THREE31.Vector3().subVectors(v3, v1)
|
|
41535
41815
|
).normalize();
|
|
41536
41816
|
normals.push(
|
|
41537
41817
|
normal.x,
|
|
@@ -41555,10 +41835,10 @@ function createGeometryFromPolygons(polygons) {
|
|
|
41555
41835
|
var import_modeling2 = __toESM(require_src(), 1);
|
|
41556
41836
|
var import_jscad_planner2 = __toESM(require_dist(), 1);
|
|
41557
41837
|
var jscadModeling2 = __toESM(require_src(), 1);
|
|
41558
|
-
import * as
|
|
41838
|
+
import * as THREE33 from "three";
|
|
41559
41839
|
|
|
41560
41840
|
// src/utils/load-model.ts
|
|
41561
|
-
import * as
|
|
41841
|
+
import * as THREE32 from "three";
|
|
41562
41842
|
import { GLTFLoader as GLTFLoader2 } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
41563
41843
|
import { OBJLoader as OBJLoader2 } from "three/examples/jsm/loaders/OBJLoader.js";
|
|
41564
41844
|
import { STLLoader as STLLoader2 } from "three/examples/jsm/loaders/STLLoader.js";
|
|
@@ -41566,12 +41846,12 @@ async function load3DModel(url) {
|
|
|
41566
41846
|
if (url.endsWith(".stl")) {
|
|
41567
41847
|
const loader = new STLLoader2();
|
|
41568
41848
|
const geometry = await loader.loadAsync(url);
|
|
41569
|
-
const material = new
|
|
41849
|
+
const material = new THREE32.MeshStandardMaterial({
|
|
41570
41850
|
color: 8947848,
|
|
41571
41851
|
metalness: 0.5,
|
|
41572
41852
|
roughness: 0.5
|
|
41573
41853
|
});
|
|
41574
|
-
return new
|
|
41854
|
+
return new THREE32.Mesh(geometry, material);
|
|
41575
41855
|
}
|
|
41576
41856
|
if (url.endsWith(".obj")) {
|
|
41577
41857
|
const loader = new OBJLoader2();
|
|
@@ -41604,9 +41884,9 @@ async function renderComponent(component, scene) {
|
|
|
41604
41884
|
}
|
|
41605
41885
|
if (component.rotation) {
|
|
41606
41886
|
model.rotation.set(
|
|
41607
|
-
|
|
41608
|
-
|
|
41609
|
-
|
|
41887
|
+
THREE33.MathUtils.degToRad(component.rotation.x ?? 0),
|
|
41888
|
+
THREE33.MathUtils.degToRad(component.rotation.y ?? 0),
|
|
41889
|
+
THREE33.MathUtils.degToRad(component.rotation.z ?? 0)
|
|
41610
41890
|
);
|
|
41611
41891
|
}
|
|
41612
41892
|
scene.add(model);
|
|
@@ -41620,13 +41900,13 @@ async function renderComponent(component, scene) {
|
|
|
41620
41900
|
);
|
|
41621
41901
|
if (jscadObject && (jscadObject.polygons || jscadObject.sides)) {
|
|
41622
41902
|
const threeGeom = convertCSGToThreeGeom(jscadObject);
|
|
41623
|
-
const material2 = new
|
|
41903
|
+
const material2 = new THREE33.MeshStandardMaterial({
|
|
41624
41904
|
color: 8947848,
|
|
41625
41905
|
metalness: 0.5,
|
|
41626
41906
|
roughness: 0.5,
|
|
41627
|
-
side:
|
|
41907
|
+
side: THREE33.DoubleSide
|
|
41628
41908
|
});
|
|
41629
|
-
const mesh2 = new
|
|
41909
|
+
const mesh2 = new THREE33.Mesh(threeGeom, material2);
|
|
41630
41910
|
if (component.position) {
|
|
41631
41911
|
mesh2.position.set(
|
|
41632
41912
|
component.position.x ?? 0,
|
|
@@ -41636,9 +41916,9 @@ async function renderComponent(component, scene) {
|
|
|
41636
41916
|
}
|
|
41637
41917
|
if (component.rotation) {
|
|
41638
41918
|
mesh2.rotation.set(
|
|
41639
|
-
|
|
41640
|
-
|
|
41641
|
-
|
|
41919
|
+
THREE33.MathUtils.degToRad(component.rotation.x ?? 0),
|
|
41920
|
+
THREE33.MathUtils.degToRad(component.rotation.y ?? 0),
|
|
41921
|
+
THREE33.MathUtils.degToRad(component.rotation.z ?? 0)
|
|
41642
41922
|
);
|
|
41643
41923
|
}
|
|
41644
41924
|
scene.add(mesh2);
|
|
@@ -41655,17 +41935,17 @@ async function renderComponent(component, scene) {
|
|
|
41655
41935
|
if (!geom || !geom.polygons && !geom.sides) {
|
|
41656
41936
|
continue;
|
|
41657
41937
|
}
|
|
41658
|
-
const color = new
|
|
41938
|
+
const color = new THREE33.Color(geomInfo.color);
|
|
41659
41939
|
color.convertLinearToSRGB();
|
|
41660
41940
|
const geomWithColor = { ...geom, color: [color.r, color.g, color.b] };
|
|
41661
41941
|
const threeGeom = convertCSGToThreeGeom(geomWithColor);
|
|
41662
|
-
const material2 = new
|
|
41942
|
+
const material2 = new THREE33.MeshStandardMaterial({
|
|
41663
41943
|
vertexColors: true,
|
|
41664
41944
|
metalness: 0.2,
|
|
41665
41945
|
roughness: 0.8,
|
|
41666
|
-
side:
|
|
41946
|
+
side: THREE33.DoubleSide
|
|
41667
41947
|
});
|
|
41668
|
-
const mesh2 = new
|
|
41948
|
+
const mesh2 = new THREE33.Mesh(threeGeom, material2);
|
|
41669
41949
|
if (component.position) {
|
|
41670
41950
|
mesh2.position.set(
|
|
41671
41951
|
component.position.x ?? 0,
|
|
@@ -41675,22 +41955,22 @@ async function renderComponent(component, scene) {
|
|
|
41675
41955
|
}
|
|
41676
41956
|
if (component.rotation) {
|
|
41677
41957
|
mesh2.rotation.set(
|
|
41678
|
-
|
|
41679
|
-
|
|
41680
|
-
|
|
41958
|
+
THREE33.MathUtils.degToRad(component.rotation.x ?? 0),
|
|
41959
|
+
THREE33.MathUtils.degToRad(component.rotation.y ?? 0),
|
|
41960
|
+
THREE33.MathUtils.degToRad(component.rotation.z ?? 0)
|
|
41681
41961
|
);
|
|
41682
41962
|
}
|
|
41683
41963
|
scene.add(mesh2);
|
|
41684
41964
|
}
|
|
41685
41965
|
return;
|
|
41686
41966
|
}
|
|
41687
|
-
const geometry = new
|
|
41688
|
-
const material = new
|
|
41967
|
+
const geometry = new THREE33.BoxGeometry(0.5, 0.5, 0.5);
|
|
41968
|
+
const material = new THREE33.MeshStandardMaterial({
|
|
41689
41969
|
color: 16711680,
|
|
41690
41970
|
transparent: true,
|
|
41691
41971
|
opacity: 0.25
|
|
41692
41972
|
});
|
|
41693
|
-
const mesh = new
|
|
41973
|
+
const mesh = new THREE33.Mesh(geometry, material);
|
|
41694
41974
|
if (component.position) {
|
|
41695
41975
|
mesh.position.set(
|
|
41696
41976
|
component.position.x ?? 0,
|
|
@@ -41711,11 +41991,11 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
|
|
|
41711
41991
|
padding = 20,
|
|
41712
41992
|
zoom = 1.5
|
|
41713
41993
|
} = options;
|
|
41714
|
-
const scene = new
|
|
41994
|
+
const scene = new THREE34.Scene();
|
|
41715
41995
|
const renderer = new SVGRenderer();
|
|
41716
41996
|
renderer.setSize(width10, height10);
|
|
41717
|
-
renderer.setClearColor(new
|
|
41718
|
-
const camera = new
|
|
41997
|
+
renderer.setClearColor(new THREE34.Color(backgroundColor), 1);
|
|
41998
|
+
const camera = new THREE34.OrthographicCamera();
|
|
41719
41999
|
const aspect = width10 / height10;
|
|
41720
42000
|
const frustumSize = 100;
|
|
41721
42001
|
const halfFrustumSize = frustumSize / 2 / zoom;
|
|
@@ -41729,25 +42009,25 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
|
|
|
41729
42009
|
camera.position.set(position.x, position.y, position.z);
|
|
41730
42010
|
camera.up.set(0, 1, 0);
|
|
41731
42011
|
const lookAt = options.camera?.lookAt ?? { x: 0, y: 0, z: 0 };
|
|
41732
|
-
camera.lookAt(new
|
|
42012
|
+
camera.lookAt(new THREE34.Vector3(lookAt.x, lookAt.y, lookAt.z));
|
|
41733
42013
|
camera.updateProjectionMatrix();
|
|
41734
|
-
const ambientLight = new
|
|
42014
|
+
const ambientLight = new THREE34.AmbientLight(16777215, Math.PI / 2);
|
|
41735
42015
|
scene.add(ambientLight);
|
|
41736
|
-
const pointLight = new
|
|
42016
|
+
const pointLight = new THREE34.PointLight(16777215, Math.PI / 4);
|
|
41737
42017
|
pointLight.position.set(-10, -10, 10);
|
|
41738
42018
|
scene.add(pointLight);
|
|
41739
|
-
const components =
|
|
42019
|
+
const components = su17(circuitJson).cad_component.list();
|
|
41740
42020
|
for (const component of components) {
|
|
41741
42021
|
await renderComponent(component, scene);
|
|
41742
42022
|
}
|
|
41743
|
-
const boardData =
|
|
42023
|
+
const boardData = su17(circuitJson).pcb_board.list()[0];
|
|
41744
42024
|
const boardGeom = createBoardGeomFromCircuitJson(circuitJson);
|
|
41745
42025
|
if (boardGeom) {
|
|
41746
42026
|
for (const geom of boardGeom) {
|
|
41747
42027
|
const g = geom;
|
|
41748
42028
|
if (!g.polygons || g.polygons.length === 0) continue;
|
|
41749
42029
|
const geometry = createGeometryFromPolygons(g.polygons);
|
|
41750
|
-
const baseColor = new
|
|
42030
|
+
const baseColor = new THREE34.Color(
|
|
41751
42031
|
g.color?.[0] ?? 0,
|
|
41752
42032
|
g.color?.[1] ?? 0,
|
|
41753
42033
|
g.color?.[2] ?? 0
|
|
@@ -41755,18 +42035,18 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
|
|
|
41755
42035
|
const material = createBoardMaterial({
|
|
41756
42036
|
material: boardData?.material,
|
|
41757
42037
|
color: baseColor,
|
|
41758
|
-
side:
|
|
42038
|
+
side: THREE34.DoubleSide
|
|
41759
42039
|
});
|
|
41760
|
-
const mesh = new
|
|
42040
|
+
const mesh = new THREE34.Mesh(geometry, material);
|
|
41761
42041
|
scene.add(mesh);
|
|
41762
42042
|
}
|
|
41763
42043
|
}
|
|
41764
|
-
const gridHelper = new
|
|
42044
|
+
const gridHelper = new THREE34.GridHelper(100, 100);
|
|
41765
42045
|
gridHelper.rotation.x = Math.PI / 2;
|
|
41766
42046
|
scene.add(gridHelper);
|
|
41767
|
-
const box = new
|
|
41768
|
-
const center = box.getCenter(new
|
|
41769
|
-
const size5 = box.getSize(new
|
|
42047
|
+
const box = new THREE34.Box3().setFromObject(scene);
|
|
42048
|
+
const center = box.getCenter(new THREE34.Vector3());
|
|
42049
|
+
const size5 = box.getSize(new THREE34.Vector3());
|
|
41770
42050
|
scene.position.sub(center);
|
|
41771
42051
|
const maxDim = Math.max(size5.x, size5.y, size5.z);
|
|
41772
42052
|
if (maxDim > 0) {
|