@tscircuit/3d-viewer 0.0.451 → 0.0.453
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 +1118 -840
- 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";
|
|
@@ -28509,7 +28509,7 @@ import * as THREE15 from "three";
|
|
|
28509
28509
|
// package.json
|
|
28510
28510
|
var package_default = {
|
|
28511
28511
|
name: "@tscircuit/3d-viewer",
|
|
28512
|
-
version: "0.0.
|
|
28512
|
+
version: "0.0.452",
|
|
28513
28513
|
main: "./dist/index.js",
|
|
28514
28514
|
module: "./dist/index.js",
|
|
28515
28515
|
type: "module",
|
|
@@ -32356,13 +32356,74 @@ var CadViewerJscad = forwardRef3(
|
|
|
32356
32356
|
);
|
|
32357
32357
|
|
|
32358
32358
|
// src/CadViewerManifold.tsx
|
|
32359
|
-
import { su as
|
|
32359
|
+
import { su as su16 } from "@tscircuit/circuit-json-util";
|
|
32360
32360
|
import { useEffect as useEffect23, useMemo as useMemo20, useState as useState16 } from "react";
|
|
32361
32361
|
|
|
32362
32362
|
// src/hooks/useManifoldBoardBuilder.ts
|
|
32363
32363
|
import { useState as useState15, useEffect as useEffect22, useMemo as useMemo19, useRef as useRef9 } from "react";
|
|
32364
|
-
import { su as
|
|
32365
|
-
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";
|
|
32366
32427
|
|
|
32367
32428
|
// src/utils/manifold-mesh-to-three-geometry.ts
|
|
32368
32429
|
import * as THREE18 from "three";
|
|
@@ -32385,409 +32446,181 @@ function manifoldMeshToThreeGeometry(manifoldMesh) {
|
|
|
32385
32446
|
return geometry;
|
|
32386
32447
|
}
|
|
32387
32448
|
|
|
32388
|
-
// src/utils/
|
|
32389
|
-
|
|
32390
|
-
|
|
32391
|
-
|
|
32392
|
-
|
|
32393
|
-
|
|
32394
|
-
|
|
32395
|
-
|
|
32396
|
-
|
|
32397
|
-
|
|
32398
|
-
|
|
32399
|
-
|
|
32400
|
-
}
|
|
32401
|
-
|
|
32402
|
-
|
|
32403
|
-
|
|
32404
|
-
|
|
32405
|
-
|
|
32406
|
-
const
|
|
32407
|
-
|
|
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)
|
|
32408
32485
|
);
|
|
32409
|
-
|
|
32410
|
-
|
|
32411
|
-
|
|
32412
|
-
|
|
32413
|
-
|
|
32414
|
-
|
|
32415
|
-
|
|
32416
|
-
if (!ctx) return null;
|
|
32417
|
-
if (layer === "bottom") {
|
|
32418
|
-
ctx.translate(0, canvasHeight);
|
|
32419
|
-
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
|
+
]);
|
|
32420
32493
|
}
|
|
32421
|
-
|
|
32422
|
-
let firstPoint = true;
|
|
32423
|
-
ctx.beginPath();
|
|
32424
|
-
ctx.strokeStyle = traceColor;
|
|
32425
|
-
ctx.lineCap = "round";
|
|
32426
|
-
ctx.lineJoin = "round";
|
|
32427
|
-
let currentLineWidth = 0;
|
|
32428
|
-
for (const point2 of trace.route) {
|
|
32429
|
-
if (!isWireRoutePoint(point2) || point2.layer !== layer) {
|
|
32430
|
-
if (!firstPoint) ctx.stroke();
|
|
32431
|
-
firstPoint = true;
|
|
32432
|
-
continue;
|
|
32433
|
-
}
|
|
32434
|
-
const pcbX = point2.x;
|
|
32435
|
-
const pcbY = point2.y;
|
|
32436
|
-
currentLineWidth = point2.width * traceTextureResolution;
|
|
32437
|
-
ctx.lineWidth = currentLineWidth;
|
|
32438
|
-
const canvasX = (pcbX - boardData.center.x + boardData.width / 2) * traceTextureResolution;
|
|
32439
|
-
const canvasY = (-(pcbY - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
|
|
32440
|
-
if (firstPoint) {
|
|
32441
|
-
ctx.moveTo(canvasX, canvasY);
|
|
32442
|
-
firstPoint = false;
|
|
32443
|
-
} else {
|
|
32444
|
-
ctx.lineTo(canvasX, canvasY);
|
|
32445
|
-
}
|
|
32446
|
-
}
|
|
32447
|
-
if (!firstPoint) {
|
|
32448
|
-
ctx.stroke();
|
|
32449
|
-
}
|
|
32450
|
-
});
|
|
32451
|
-
ctx.globalCompositeOperation = "destination-out";
|
|
32452
|
-
ctx.fillStyle = "black";
|
|
32453
|
-
allPcbVias.forEach((via) => {
|
|
32454
|
-
const canvasX = (via.x - boardData.center.x + boardData.width / 2) * traceTextureResolution;
|
|
32455
|
-
const canvasY = (-(via.y - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
|
|
32456
|
-
const canvasRadius = via.outer_diameter / 2 * traceTextureResolution;
|
|
32457
|
-
ctx.beginPath();
|
|
32458
|
-
ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI, false);
|
|
32459
|
-
ctx.fill();
|
|
32460
|
-
});
|
|
32461
|
-
allPcbPlatedHoles.forEach((ph) => {
|
|
32462
|
-
if (ph.layers.includes(layer) && ph.shape === "circle") {
|
|
32463
|
-
const canvasX = (ph.x - boardData.center.x + boardData.width / 2) * traceTextureResolution;
|
|
32464
|
-
const canvasY = (-(ph.y - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
|
|
32465
|
-
const canvasRadius = ph.outer_diameter / 2 * traceTextureResolution;
|
|
32466
|
-
ctx.beginPath();
|
|
32467
|
-
ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI, false);
|
|
32468
|
-
ctx.fill();
|
|
32469
|
-
}
|
|
32470
|
-
});
|
|
32471
|
-
ctx.globalCompositeOperation = "source-over";
|
|
32472
|
-
const texture = new THREE19.CanvasTexture(canvas);
|
|
32473
|
-
texture.generateMipmaps = true;
|
|
32474
|
-
texture.minFilter = THREE19.LinearMipmapLinearFilter;
|
|
32475
|
-
texture.magFilter = THREE19.LinearFilter;
|
|
32476
|
-
texture.anisotropy = 16;
|
|
32477
|
-
texture.needsUpdate = true;
|
|
32478
|
-
return texture;
|
|
32494
|
+
return points;
|
|
32479
32495
|
}
|
|
32480
|
-
|
|
32481
|
-
|
|
32482
|
-
|
|
32483
|
-
|
|
32484
|
-
|
|
32485
|
-
|
|
32486
|
-
|
|
32487
|
-
|
|
32488
|
-
|
|
32489
|
-
|
|
32490
|
-
|
|
32491
|
-
|
|
32492
|
-
|
|
32493
|
-
|
|
32494
|
-
|
|
32495
|
-
|
|
32496
|
-
const pcbSilkscreenCircles = su8(circuitJson).pcb_silkscreen_circle.list();
|
|
32497
|
-
const textsOnLayer = pcbSilkscreenTexts.filter((t) => t.layer === layer);
|
|
32498
|
-
const pathsOnLayer = pcbSilkscreenPaths.filter((p) => p.layer === layer);
|
|
32499
|
-
const linesOnLayer = pcbSilkscreenLines.filter((l) => l.layer === layer);
|
|
32500
|
-
const rectsOnLayer = pcbSilkscreenRects.filter((r) => r.layer === layer);
|
|
32501
|
-
const circlesOnLayer = pcbSilkscreenCircles.filter((c) => c.layer === layer);
|
|
32502
|
-
if (textsOnLayer.length === 0 && pathsOnLayer.length === 0 && linesOnLayer.length === 0 && rectsOnLayer.length === 0 && circlesOnLayer.length === 0) {
|
|
32503
|
-
return null;
|
|
32504
|
-
}
|
|
32505
|
-
const canvas = document.createElement("canvas");
|
|
32506
|
-
const canvasWidth = Math.floor(boardData.width * traceTextureResolution);
|
|
32507
|
-
const canvasHeight = Math.floor(boardData.height * traceTextureResolution);
|
|
32508
|
-
canvas.width = canvasWidth;
|
|
32509
|
-
canvas.height = canvasHeight;
|
|
32510
|
-
const ctx = canvas.getContext("2d");
|
|
32511
|
-
if (!ctx) return null;
|
|
32512
|
-
if (layer === "bottom") {
|
|
32513
|
-
ctx.translate(0, canvasHeight);
|
|
32514
|
-
ctx.scale(1, -1);
|
|
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
|
+
}
|
|
32515
32512
|
}
|
|
32516
|
-
|
|
32517
|
-
|
|
32518
|
-
|
|
32519
|
-
const
|
|
32520
|
-
|
|
32521
|
-
|
|
32522
|
-
|
|
32523
|
-
|
|
32524
|
-
const
|
|
32525
|
-
|
|
32526
|
-
|
|
32527
|
-
|
|
32528
|
-
|
|
32529
|
-
|
|
32530
|
-
|
|
32531
|
-
|
|
32532
|
-
|
|
32533
|
-
|
|
32534
|
-
|
|
32535
|
-
|
|
32536
|
-
|
|
32537
|
-
|
|
32538
|
-
|
|
32539
|
-
|
|
32540
|
-
|
|
32541
|
-
|
|
32542
|
-
|
|
32543
|
-
|
|
32544
|
-
|
|
32545
|
-
|
|
32546
|
-
|
|
32547
|
-
|
|
32548
|
-
|
|
32549
|
-
|
|
32550
|
-
|
|
32551
|
-
|
|
32552
|
-
const centerXmm = parseDimensionToMm(circleEl.center?.x) ?? 0;
|
|
32553
|
-
const centerYmm = parseDimensionToMm(circleEl.center?.y) ?? 0;
|
|
32554
|
-
const canvasCenterX = canvasXFromPcb(centerXmm);
|
|
32555
|
-
const canvasCenterY = canvasYFromPcb(centerYmm);
|
|
32556
|
-
const radiusPx = radius * traceTextureResolution;
|
|
32557
|
-
ctx.save();
|
|
32558
|
-
ctx.translate(canvasCenterX, canvasCenterY);
|
|
32559
|
-
if (hasStroke) {
|
|
32560
|
-
const outerRadiusPx = radiusPx + strokeWidth / 2 * traceTextureResolution;
|
|
32561
|
-
const innerRadiusPx = Math.max(
|
|
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;
|
|
32532
|
+
}
|
|
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();
|
|
32543
|
+
}
|
|
32544
|
+
const crossSection = CrossSection.ofPolygons([pointsVec2]);
|
|
32545
|
+
manifoldInstancesForCleanup.push(crossSection);
|
|
32546
|
+
pourOp = Manifold.extrude(
|
|
32547
|
+
crossSection,
|
|
32548
|
+
pourThickness,
|
|
32562
32549
|
0,
|
|
32563
|
-
|
|
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
|
|
32564
32565
|
);
|
|
32565
|
-
if (
|
|
32566
|
-
|
|
32567
|
-
ctx.arc(0, 0, outerRadiusPx, 0, 2 * Math.PI);
|
|
32568
|
-
ctx.arc(0, 0, innerRadiusPx, 0, 2 * Math.PI, true);
|
|
32569
|
-
ctx.fill("evenodd");
|
|
32570
|
-
} else {
|
|
32571
|
-
ctx.beginPath();
|
|
32572
|
-
ctx.arc(0, 0, outerRadiusPx, 0, 2 * Math.PI);
|
|
32573
|
-
ctx.fill();
|
|
32566
|
+
if (arePointsClockwise4(outerRingPoints)) {
|
|
32567
|
+
outerRingPoints = outerRingPoints.reverse();
|
|
32574
32568
|
}
|
|
32575
|
-
|
|
32576
|
-
|
|
32577
|
-
|
|
32578
|
-
|
|
32579
|
-
|
|
32580
|
-
|
|
32581
|
-
|
|
32582
|
-
|
|
32583
|
-
|
|
32584
|
-
|
|
32585
|
-
if (width10 <= 0 || height10 <= 0) return;
|
|
32586
|
-
const centerXmm = parseDimensionToMm(rect.center?.x) ?? 0;
|
|
32587
|
-
const centerYmm = parseDimensionToMm(rect.center?.y) ?? 0;
|
|
32588
|
-
const canvasCenterX = canvasXFromPcb(centerXmm);
|
|
32589
|
-
const canvasCenterY = canvasYFromPcb(centerYmm);
|
|
32590
|
-
const rawRadius = extractRectBorderRadius(rect);
|
|
32591
|
-
const borderRadiusInput = typeof rawRadius === "string" ? parseDimensionToMm(rawRadius) : rawRadius;
|
|
32592
|
-
const borderRadiusMm = clampRectBorderRadius(
|
|
32593
|
-
width10,
|
|
32594
|
-
height10,
|
|
32595
|
-
borderRadiusInput
|
|
32596
|
-
);
|
|
32597
|
-
ctx.save();
|
|
32598
|
-
ctx.translate(canvasCenterX, canvasCenterY);
|
|
32599
|
-
const halfWidthPx = width10 / 2 * traceTextureResolution;
|
|
32600
|
-
const halfHeightPx = height10 / 2 * traceTextureResolution;
|
|
32601
|
-
const borderRadiusPx = Math.min(
|
|
32602
|
-
borderRadiusMm * traceTextureResolution,
|
|
32603
|
-
halfWidthPx,
|
|
32604
|
-
halfHeightPx
|
|
32605
|
-
);
|
|
32606
|
-
const hasStroke = rect.has_stroke ?? false;
|
|
32607
|
-
const isFilled = rect.is_filled ?? true;
|
|
32608
|
-
const isDashed = rect.is_stroke_dashed ?? false;
|
|
32609
|
-
const strokeWidthPx = hasStroke ? coerceDimensionToMm(rect.stroke_width, 0.1) * traceTextureResolution : 0;
|
|
32610
|
-
const drawRoundedRectPath = (x, y, rectWidth, rectHeight, radius) => {
|
|
32611
|
-
ctx.beginPath();
|
|
32612
|
-
if (radius <= 0) {
|
|
32613
|
-
ctx.rect(x, y, rectWidth, rectHeight);
|
|
32614
|
-
} else {
|
|
32615
|
-
const r = radius;
|
|
32616
|
-
const right = x + rectWidth;
|
|
32617
|
-
const bottom = y + rectHeight;
|
|
32618
|
-
ctx.moveTo(x + r, y);
|
|
32619
|
-
ctx.lineTo(right - r, y);
|
|
32620
|
-
ctx.quadraticCurveTo(right, y, right, y + r);
|
|
32621
|
-
ctx.lineTo(right, bottom - r);
|
|
32622
|
-
ctx.quadraticCurveTo(right, bottom, right - r, bottom);
|
|
32623
|
-
ctx.lineTo(x + r, bottom);
|
|
32624
|
-
ctx.quadraticCurveTo(x, bottom, x, bottom - r);
|
|
32625
|
-
ctx.lineTo(x, y + r);
|
|
32626
|
-
ctx.quadraticCurveTo(x, y, x + r, y);
|
|
32627
|
-
ctx.closePath();
|
|
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);
|
|
32628
32579
|
}
|
|
32629
|
-
|
|
32630
|
-
|
|
32631
|
-
|
|
32632
|
-
|
|
32633
|
-
|
|
32634
|
-
|
|
32635
|
-
|
|
32636
|
-
|
|
32637
|
-
|
|
32638
|
-
|
|
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);
|
|
32639
32595
|
}
|
|
32640
|
-
if (
|
|
32641
|
-
|
|
32642
|
-
|
|
32643
|
-
|
|
32644
|
-
|
|
32645
|
-
}
|
|
32646
|
-
ctx.stroke();
|
|
32647
|
-
if (isDashed) {
|
|
32648
|
-
ctx.setLineDash([]);
|
|
32596
|
+
if (pourOp) {
|
|
32597
|
+
if (holeUnion) {
|
|
32598
|
+
const withHoles = pourOp.subtract(holeUnion);
|
|
32599
|
+
manifoldInstancesForCleanup.push(withHoles);
|
|
32600
|
+
pourOp = withHoles;
|
|
32649
32601
|
}
|
|
32650
|
-
|
|
32651
|
-
|
|
32652
|
-
|
|
32653
|
-
|
|
32654
|
-
const fontSize = textS.font_size || 0.25;
|
|
32655
|
-
const textStrokeWidth = Math.min(Math.max(0.01, fontSize * 0.1), fontSize * 0.05) * traceTextureResolution;
|
|
32656
|
-
ctx.lineWidth = textStrokeWidth;
|
|
32657
|
-
ctx.lineCap = "butt";
|
|
32658
|
-
ctx.lineJoin = "miter";
|
|
32659
|
-
const rawTextOutlines = (0, import_text2.vectorText)({
|
|
32660
|
-
height: fontSize * 0.45,
|
|
32661
|
-
input: textS.text
|
|
32662
|
-
});
|
|
32663
|
-
const processedTextOutlines = [];
|
|
32664
|
-
rawTextOutlines.forEach((outline) => {
|
|
32665
|
-
if (outline.length === 29) {
|
|
32666
|
-
processedTextOutlines.push(
|
|
32667
|
-
outline.slice(0, 15)
|
|
32668
|
-
);
|
|
32669
|
-
processedTextOutlines.push(
|
|
32670
|
-
outline.slice(14, 29)
|
|
32671
|
-
);
|
|
32672
|
-
} else if (outline.length === 17) {
|
|
32673
|
-
processedTextOutlines.push(
|
|
32674
|
-
outline.slice(0, 10)
|
|
32675
|
-
);
|
|
32676
|
-
processedTextOutlines.push(
|
|
32677
|
-
outline.slice(9, 17)
|
|
32678
|
-
);
|
|
32679
|
-
} else {
|
|
32680
|
-
processedTextOutlines.push(outline);
|
|
32602
|
+
if (boardClipVolume) {
|
|
32603
|
+
const clipped = Manifold.intersection([pourOp, boardClipVolume]);
|
|
32604
|
+
manifoldInstancesForCleanup.push(clipped);
|
|
32605
|
+
pourOp = clipped;
|
|
32681
32606
|
}
|
|
32682
|
-
|
|
32683
|
-
|
|
32684
|
-
|
|
32685
|
-
|
|
32686
|
-
|
|
32687
|
-
|
|
32688
|
-
|
|
32689
|
-
|
|
32690
|
-
|
|
32691
|
-
|
|
32692
|
-
|
|
32693
|
-
|
|
32694
|
-
const alignment = textS.anchor_alignment || "center";
|
|
32695
|
-
if (alignment.includes("left")) {
|
|
32696
|
-
xOff = -textBounds.minX;
|
|
32697
|
-
} else if (alignment.includes("right")) {
|
|
32698
|
-
xOff = -textBounds.maxX;
|
|
32699
|
-
}
|
|
32700
|
-
if (alignment.includes("top")) {
|
|
32701
|
-
yOff = -textBounds.maxY;
|
|
32702
|
-
} else if (alignment.includes("bottom")) {
|
|
32703
|
-
yOff = -textBounds.minY;
|
|
32704
|
-
}
|
|
32705
|
-
const transformMatrices = [];
|
|
32706
|
-
let rotationDeg = textS.ccw_rotation ?? 0;
|
|
32707
|
-
if (textS.layer === "bottom") {
|
|
32708
|
-
transformMatrices.push(
|
|
32709
|
-
translate4(textCenterX, textCenterY),
|
|
32710
|
-
{ a: -1, b: 0, c: 0, d: 1, e: 0, f: 0 },
|
|
32711
|
-
translate4(-textCenterX, -textCenterY)
|
|
32712
|
-
);
|
|
32713
|
-
rotationDeg = -rotationDeg;
|
|
32714
|
-
}
|
|
32715
|
-
if (rotationDeg) {
|
|
32716
|
-
const rad = rotationDeg * Math.PI / 180;
|
|
32717
|
-
transformMatrices.push(
|
|
32718
|
-
translate4(textCenterX, textCenterY),
|
|
32719
|
-
rotate2(rad),
|
|
32720
|
-
translate4(-textCenterX, -textCenterY)
|
|
32721
|
-
);
|
|
32722
|
-
}
|
|
32723
|
-
const finalTransformMatrix = transformMatrices.length > 0 ? compose(...transformMatrices) : void 0;
|
|
32724
|
-
processedTextOutlines.forEach((segment) => {
|
|
32725
|
-
ctx.beginPath();
|
|
32726
|
-
segment.forEach((p, index2) => {
|
|
32727
|
-
let transformedP = { x: p[0], y: p[1] };
|
|
32728
|
-
if (finalTransformMatrix) {
|
|
32729
|
-
transformedP = applyToPoint(finalTransformMatrix, transformedP);
|
|
32730
|
-
}
|
|
32731
|
-
const pcbX = transformedP.x + xOff + textS.anchor_position.x;
|
|
32732
|
-
const pcbY = transformedP.y + yOff + textS.anchor_position.y;
|
|
32733
|
-
const canvasX = canvasXFromPcb(pcbX);
|
|
32734
|
-
const canvasY = canvasYFromPcb(pcbY);
|
|
32735
|
-
if (index2 === 0) ctx.moveTo(canvasX, canvasY);
|
|
32736
|
-
else ctx.lineTo(canvasX, canvasY);
|
|
32737
|
-
});
|
|
32738
|
-
ctx.stroke();
|
|
32739
|
-
});
|
|
32740
|
-
});
|
|
32741
|
-
const texture = new THREE20.CanvasTexture(canvas);
|
|
32742
|
-
texture.generateMipmaps = true;
|
|
32743
|
-
texture.minFilter = THREE20.LinearMipmapLinearFilter;
|
|
32744
|
-
texture.magFilter = THREE20.LinearFilter;
|
|
32745
|
-
texture.anisotropy = 16;
|
|
32746
|
-
texture.needsUpdate = true;
|
|
32747
|
-
return texture;
|
|
32748
|
-
}
|
|
32749
|
-
|
|
32750
|
-
// src/utils/manifold/process-non-plated-holes.ts
|
|
32751
|
-
import { su as su9 } from "@tscircuit/circuit-json-util";
|
|
32752
|
-
|
|
32753
|
-
// src/utils/hole-geoms.ts
|
|
32754
|
-
function createCircleHoleDrill({
|
|
32755
|
-
Manifold,
|
|
32756
|
-
x,
|
|
32757
|
-
y,
|
|
32758
|
-
diameter,
|
|
32759
|
-
thickness,
|
|
32760
|
-
segments = 32
|
|
32761
|
-
}) {
|
|
32762
|
-
const drill = Manifold.cylinder(
|
|
32763
|
-
thickness * 1.2,
|
|
32764
|
-
diameter / 2,
|
|
32765
|
-
diameter / 2,
|
|
32766
|
-
segments,
|
|
32767
|
-
true
|
|
32768
|
-
);
|
|
32769
|
-
return drill.translate([x, y, 0]);
|
|
32770
|
-
}
|
|
32771
|
-
function createPlatedHoleDrill({
|
|
32772
|
-
Manifold,
|
|
32773
|
-
x,
|
|
32774
|
-
y,
|
|
32775
|
-
holeDiameter,
|
|
32776
|
-
thickness,
|
|
32777
|
-
zOffset = 1e-3,
|
|
32778
|
-
segments = 32
|
|
32779
|
-
}) {
|
|
32780
|
-
const boardHoleRadius = holeDiameter / 2 + zOffset;
|
|
32781
|
-
const drill = Manifold.cylinder(
|
|
32782
|
-
thickness * 1.2,
|
|
32783
|
-
boardHoleRadius,
|
|
32784
|
-
boardHoleRadius,
|
|
32785
|
-
segments,
|
|
32786
|
-
true
|
|
32787
|
-
);
|
|
32788
|
-
return drill.translate([x, y, 0]);
|
|
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 };
|
|
32789
32619
|
}
|
|
32790
32620
|
|
|
32621
|
+
// src/utils/manifold/process-cutouts.ts
|
|
32622
|
+
import { su as su7 } from "@tscircuit/circuit-json-util";
|
|
32623
|
+
|
|
32791
32624
|
// src/utils/pad-geoms.ts
|
|
32792
32625
|
var RECT_PAD_SEGMENTS2 = 64;
|
|
32793
32626
|
function createRoundedRectPrism({
|
|
@@ -32863,6 +32696,156 @@ function createPadManifoldOp({
|
|
|
32863
32696
|
return null;
|
|
32864
32697
|
}
|
|
32865
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
|
+
|
|
32866
32849
|
// src/utils/manifold/process-non-plated-holes.ts
|
|
32867
32850
|
function isCircleHole(hole) {
|
|
32868
32851
|
return (hole.shape === "circle" || hole.hole_shape === "circle") && typeof hole.hole_diameter === "number";
|
|
@@ -32875,7 +32858,7 @@ function isRotatedPillHole(hole) {
|
|
|
32875
32858
|
}
|
|
32876
32859
|
function processNonPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup) {
|
|
32877
32860
|
const nonPlatedHoleBoardDrills = [];
|
|
32878
|
-
const pcbHoles =
|
|
32861
|
+
const pcbHoles = su8(circuitJson).pcb_hole.list();
|
|
32879
32862
|
const createPillOp = (width10, height10, depth) => {
|
|
32880
32863
|
const pillOp = createRoundedRectPrism({
|
|
32881
32864
|
Manifold,
|
|
@@ -32924,9 +32907,9 @@ function processNonPlatedHolesForManifold(Manifold, circuitJson, pcbThickness, m
|
|
|
32924
32907
|
}
|
|
32925
32908
|
|
|
32926
32909
|
// src/utils/manifold/process-plated-holes.ts
|
|
32927
|
-
import { su as
|
|
32928
|
-
import * as
|
|
32929
|
-
var
|
|
32910
|
+
import { su as su9 } from "@tscircuit/circuit-json-util";
|
|
32911
|
+
import * as THREE20 from "three";
|
|
32912
|
+
var arePointsClockwise6 = (points) => {
|
|
32930
32913
|
let area = 0;
|
|
32931
32914
|
for (let i = 0; i < points.length; i++) {
|
|
32932
32915
|
const j = (i + 1) % points.length;
|
|
@@ -32946,11 +32929,11 @@ var createEllipsePoints = (width10, height10, segments) => {
|
|
|
32946
32929
|
}
|
|
32947
32930
|
return points;
|
|
32948
32931
|
};
|
|
32949
|
-
var COPPER_COLOR = new
|
|
32932
|
+
var COPPER_COLOR = new THREE20.Color(...colors.copper);
|
|
32950
32933
|
var PLATED_HOLE_LIP_HEIGHT = 0.05;
|
|
32951
32934
|
function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbThickness, manifoldInstancesForCleanup, boardClipVolume) {
|
|
32952
32935
|
const platedHoleBoardDrills = [];
|
|
32953
|
-
const pcbPlatedHoles =
|
|
32936
|
+
const pcbPlatedHoles = su9(circuitJson).pcb_plated_hole.list();
|
|
32954
32937
|
const platedHoleCopperGeoms = [];
|
|
32955
32938
|
const platedHoleCopperOpsForSubtract = [];
|
|
32956
32939
|
const createPillOp = (width10, height10, depth) => {
|
|
@@ -32973,7 +32956,7 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
|
|
|
32973
32956
|
point2.x,
|
|
32974
32957
|
point2.y
|
|
32975
32958
|
]);
|
|
32976
|
-
if (
|
|
32959
|
+
if (arePointsClockwise6(points)) {
|
|
32977
32960
|
points = points.reverse();
|
|
32978
32961
|
}
|
|
32979
32962
|
const crossSection = CrossSection.ofPolygons([points]);
|
|
@@ -33018,7 +33001,7 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
|
|
|
33018
33001
|
const height10 = Math.max(baseHeight + sizeDelta, M);
|
|
33019
33002
|
if (holeShape === "oval") {
|
|
33020
33003
|
let points = createEllipsePoints(width10, height10, SMOOTH_CIRCLE_SEGMENTS);
|
|
33021
|
-
if (
|
|
33004
|
+
if (arePointsClockwise6(points)) {
|
|
33022
33005
|
points = points.reverse();
|
|
33023
33006
|
}
|
|
33024
33007
|
const crossSection = CrossSection.ofPolygons([points]);
|
|
@@ -33333,7 +33316,7 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
|
|
|
33333
33316
|
drillH,
|
|
33334
33317
|
SMOOTH_CIRCLE_SEGMENTS
|
|
33335
33318
|
);
|
|
33336
|
-
if (
|
|
33319
|
+
if (arePointsClockwise6(boardDrillPoints)) {
|
|
33337
33320
|
boardDrillPoints = boardDrillPoints.reverse();
|
|
33338
33321
|
}
|
|
33339
33322
|
const boardDrillCrossSection = CrossSection.ofPolygons([boardDrillPoints]);
|
|
@@ -33361,7 +33344,7 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
|
|
|
33361
33344
|
outerH,
|
|
33362
33345
|
SMOOTH_CIRCLE_SEGMENTS
|
|
33363
33346
|
);
|
|
33364
|
-
if (
|
|
33347
|
+
if (arePointsClockwise6(outerPoints)) {
|
|
33365
33348
|
outerPoints = outerPoints.reverse();
|
|
33366
33349
|
}
|
|
33367
33350
|
const outerCrossSection = CrossSection.ofPolygons([outerPoints]);
|
|
@@ -33380,7 +33363,7 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
|
|
|
33380
33363
|
holeH,
|
|
33381
33364
|
SMOOTH_CIRCLE_SEGMENTS
|
|
33382
33365
|
);
|
|
33383
|
-
if (
|
|
33366
|
+
if (arePointsClockwise6(innerPoints)) {
|
|
33384
33367
|
innerPoints = innerPoints.reverse();
|
|
33385
33368
|
}
|
|
33386
33369
|
const innerCrossSection = CrossSection.ofPolygons([innerPoints]);
|
|
@@ -33518,6 +33501,46 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
|
|
|
33518
33501
|
return { platedHoleBoardDrills, platedHoleCopperGeoms, platedHoleSubtractOp };
|
|
33519
33502
|
}
|
|
33520
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
|
+
|
|
33521
33544
|
// src/utils/manifold/process-vias.ts
|
|
33522
33545
|
import { su as su11 } from "@tscircuit/circuit-json-util";
|
|
33523
33546
|
import * as THREE22 from "three";
|
|
@@ -33573,7 +33596,7 @@ function createViaCopper({
|
|
|
33573
33596
|
}
|
|
33574
33597
|
|
|
33575
33598
|
// src/utils/manifold/process-vias.ts
|
|
33576
|
-
var
|
|
33599
|
+
var COPPER_COLOR3 = new THREE22.Color(...colors.copper);
|
|
33577
33600
|
function processViasForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup, boardClipVolume) {
|
|
33578
33601
|
const viaBoardDrills = [];
|
|
33579
33602
|
const pcbVias = su11(circuitJson).pcb_via.list();
|
|
@@ -33613,396 +33636,566 @@ function processViasForManifold(Manifold, circuitJson, pcbThickness, manifoldIns
|
|
|
33613
33636
|
manifoldInstancesForCleanup.push(clipped);
|
|
33614
33637
|
finalCopperOp = clipped;
|
|
33615
33638
|
}
|
|
33616
|
-
const threeGeom = manifoldMeshToThreeGeometry(finalCopperOp.getMesh());
|
|
33617
|
-
viaCopperGeoms.push({
|
|
33618
|
-
key: `via-${via.pcb_via_id || index2}`,
|
|
33619
|
-
geometry: threeGeom,
|
|
33620
|
-
color:
|
|
33621
|
-
});
|
|
33639
|
+
const threeGeom = manifoldMeshToThreeGeometry(finalCopperOp.getMesh());
|
|
33640
|
+
viaCopperGeoms.push({
|
|
33641
|
+
key: `via-${via.pcb_via_id || index2}`,
|
|
33642
|
+
geometry: threeGeom,
|
|
33643
|
+
color: COPPER_COLOR3
|
|
33644
|
+
});
|
|
33645
|
+
}
|
|
33646
|
+
});
|
|
33647
|
+
return { viaBoardDrills, viaCopperGeoms };
|
|
33648
|
+
}
|
|
33649
|
+
|
|
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;
|
|
33673
|
+
}
|
|
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();
|
|
33622
33748
|
}
|
|
33749
|
+
ctx.restore();
|
|
33623
33750
|
});
|
|
33624
|
-
|
|
33625
|
-
|
|
33626
|
-
|
|
33627
|
-
|
|
33628
|
-
|
|
33629
|
-
|
|
33630
|
-
|
|
33631
|
-
|
|
33632
|
-
|
|
33633
|
-
|
|
33634
|
-
|
|
33635
|
-
|
|
33636
|
-
|
|
33637
|
-
|
|
33638
|
-
|
|
33639
|
-
|
|
33640
|
-
|
|
33641
|
-
|
|
33642
|
-
|
|
33643
|
-
|
|
33644
|
-
|
|
33645
|
-
|
|
33646
|
-
|
|
33647
|
-
|
|
33648
|
-
|
|
33649
|
-
|
|
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
|
|
33765
|
+
);
|
|
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();
|
|
33650
33797
|
}
|
|
33651
|
-
|
|
33652
|
-
|
|
33653
|
-
|
|
33654
|
-
|
|
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([]);
|
|
33655
33818
|
}
|
|
33656
|
-
const threeGeom = manifoldMeshToThreeGeometry(finalPadOp.getMesh());
|
|
33657
|
-
smtPadGeoms.push({
|
|
33658
|
-
key: `smt_pad-${pad2.layer || "top"}-${pad2.pcb_smtpad_id || index2}`,
|
|
33659
|
-
geometry: threeGeom,
|
|
33660
|
-
color: COPPER_COLOR3
|
|
33661
|
-
});
|
|
33662
33819
|
}
|
|
33820
|
+
ctx.restore();
|
|
33663
33821
|
});
|
|
33664
|
-
|
|
33665
|
-
|
|
33666
|
-
|
|
33667
|
-
|
|
33668
|
-
|
|
33669
|
-
|
|
33670
|
-
|
|
33671
|
-
|
|
33672
|
-
|
|
33673
|
-
|
|
33674
|
-
|
|
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;
|
|
33675
33868
|
}
|
|
33676
|
-
|
|
33677
|
-
|
|
33678
|
-
|
|
33679
|
-
|
|
33680
|
-
function createManifoldBoard(Manifold, CrossSection, boardData, pcbThickness, manifoldInstancesForCleanup) {
|
|
33681
|
-
let boardOp;
|
|
33682
|
-
let outlineCrossSection = null;
|
|
33683
|
-
if (boardData.outline && boardData.outline.length >= 3) {
|
|
33684
|
-
let outlineVec2 = boardData.outline.map((p) => [
|
|
33685
|
-
p.x,
|
|
33686
|
-
p.y
|
|
33687
|
-
]);
|
|
33688
|
-
if (arePointsClockwise4(outlineVec2)) {
|
|
33689
|
-
outlineVec2 = outlineVec2.reverse();
|
|
33869
|
+
if (alignment.includes("top")) {
|
|
33870
|
+
yOff = -textBounds.maxY;
|
|
33871
|
+
} else if (alignment.includes("bottom")) {
|
|
33872
|
+
yOff = -textBounds.minY;
|
|
33690
33873
|
}
|
|
33691
|
-
const
|
|
33692
|
-
|
|
33693
|
-
|
|
33694
|
-
|
|
33695
|
-
|
|
33696
|
-
|
|
33697
|
-
|
|
33698
|
-
// nDivisions
|
|
33699
|
-
void 0,
|
|
33700
|
-
// twistDegrees
|
|
33701
|
-
void 0,
|
|
33702
|
-
// scaleTop
|
|
33703
|
-
true
|
|
33704
|
-
// center (for Z-axis)
|
|
33705
|
-
);
|
|
33706
|
-
manifoldInstancesForCleanup.push(boardOp);
|
|
33707
|
-
} else {
|
|
33708
|
-
if (boardData.outline && boardData.outline.length > 0) {
|
|
33709
|
-
console.warn(
|
|
33710
|
-
"Board outline has fewer than 3 points, falling back to rectangular board."
|
|
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)
|
|
33711
33881
|
);
|
|
33882
|
+
rotationDeg = -rotationDeg;
|
|
33712
33883
|
}
|
|
33713
|
-
|
|
33714
|
-
|
|
33715
|
-
|
|
33716
|
-
|
|
33717
|
-
|
|
33718
|
-
|
|
33719
|
-
|
|
33720
|
-
|
|
33721
|
-
|
|
33722
|
-
|
|
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;
|
|
33723
33917
|
}
|
|
33724
33918
|
|
|
33725
|
-
// src/utils/
|
|
33919
|
+
// src/utils/soldermask-texture.ts
|
|
33726
33920
|
import * as THREE24 from "three";
|
|
33727
|
-
|
|
33728
|
-
|
|
33729
|
-
|
|
33730
|
-
|
|
33731
|
-
|
|
33732
|
-
|
|
33733
|
-
|
|
33734
|
-
|
|
33735
|
-
|
|
33736
|
-
const
|
|
33737
|
-
|
|
33738
|
-
|
|
33739
|
-
|
|
33740
|
-
|
|
33741
|
-
|
|
33742
|
-
|
|
33743
|
-
|
|
33744
|
-
|
|
33745
|
-
const dy = p2[1] - p1[1];
|
|
33746
|
-
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
33747
|
-
if (dist < 1e-9) return [];
|
|
33748
|
-
const radius = Math.abs(dist / (2 * Math.sin(theta / 2)));
|
|
33749
|
-
const m = Math.sqrt(Math.max(0, radius * radius - dist / 2 * (dist / 2)));
|
|
33750
|
-
const midPoint = [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2];
|
|
33751
|
-
const ux = dx / dist;
|
|
33752
|
-
const uy = dy / dist;
|
|
33753
|
-
const nx = -uy;
|
|
33754
|
-
const ny = ux;
|
|
33755
|
-
const centerX = midPoint[0] + nx * m * Math.sign(bulge);
|
|
33756
|
-
const centerY = midPoint[1] + ny * m * Math.sign(bulge);
|
|
33757
|
-
const startAngle = Math.atan2(p1[1] - centerY, p1[0] - centerX);
|
|
33758
|
-
const points = [];
|
|
33759
|
-
const numSteps = Math.max(
|
|
33760
|
-
2,
|
|
33761
|
-
Math.ceil(arcSegments * Math.abs(theta) / (Math.PI * 2) * 4)
|
|
33762
|
-
);
|
|
33763
|
-
const angleStep = theta / numSteps;
|
|
33764
|
-
for (let i = 1; i < numSteps; i++) {
|
|
33765
|
-
const angle = startAngle + angleStep * i;
|
|
33766
|
-
points.push([
|
|
33767
|
-
centerX + radius * Math.cos(angle),
|
|
33768
|
-
centerY + radius * Math.sin(angle)
|
|
33769
|
-
]);
|
|
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);
|
|
33770
33939
|
}
|
|
33771
|
-
|
|
33772
|
-
|
|
33773
|
-
|
|
33774
|
-
const
|
|
33775
|
-
|
|
33776
|
-
|
|
33777
|
-
|
|
33778
|
-
|
|
33779
|
-
|
|
33780
|
-
if (
|
|
33781
|
-
|
|
33782
|
-
|
|
33783
|
-
|
|
33784
|
-
|
|
33785
|
-
|
|
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
|
|
33786
33989
|
);
|
|
33787
|
-
|
|
33990
|
+
ctx.fill();
|
|
33788
33991
|
}
|
|
33789
|
-
}
|
|
33790
|
-
|
|
33791
|
-
|
|
33792
|
-
|
|
33793
|
-
|
|
33794
|
-
|
|
33795
|
-
(
|
|
33796
|
-
|
|
33797
|
-
|
|
33798
|
-
|
|
33799
|
-
|
|
33800
|
-
|
|
33801
|
-
|
|
33802
|
-
|
|
33803
|
-
|
|
33804
|
-
|
|
33805
|
-
|
|
33806
|
-
|
|
33807
|
-
|
|
33808
|
-
|
|
33809
|
-
|
|
33810
|
-
|
|
33811
|
-
|
|
33812
|
-
} else if (
|
|
33813
|
-
|
|
33814
|
-
|
|
33815
|
-
|
|
33816
|
-
|
|
33817
|
-
|
|
33818
|
-
|
|
33819
|
-
|
|
33820
|
-
|
|
33821
|
-
|
|
33822
|
-
|
|
33823
|
-
pourOp = Manifold.extrude(
|
|
33824
|
-
crossSection,
|
|
33825
|
-
pourThickness,
|
|
33826
|
-
0,
|
|
33827
|
-
// nDivisions
|
|
33828
|
-
0,
|
|
33829
|
-
// twistDegrees
|
|
33830
|
-
[1, 1],
|
|
33831
|
-
// scaleTop
|
|
33832
|
-
true
|
|
33833
|
-
// center extrusion
|
|
33834
|
-
).translate([0, 0, zPos]);
|
|
33835
|
-
manifoldInstancesForCleanup.push(pourOp);
|
|
33836
|
-
} else if (pour.shape === "brep") {
|
|
33837
|
-
const brepShape = pour.brep_shape;
|
|
33838
|
-
if (!brepShape || !brepShape.outer_ring) continue;
|
|
33839
|
-
let outerRingPoints = ringToPoints2(
|
|
33840
|
-
brepShape.outer_ring,
|
|
33841
|
-
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
|
|
33842
34026
|
);
|
|
33843
|
-
|
|
33844
|
-
outerRingPoints = outerRingPoints.reverse();
|
|
33845
|
-
}
|
|
33846
|
-
const polygons = [outerRingPoints];
|
|
33847
|
-
if (brepShape.inner_rings) {
|
|
33848
|
-
const innerRingsPoints = brepShape.inner_rings.map((ring2) => {
|
|
33849
|
-
let points = ringToPoints2(ring2, SMOOTH_CIRCLE_SEGMENTS);
|
|
33850
|
-
if (!arePointsClockwise5(points)) {
|
|
33851
|
-
points = points.reverse();
|
|
33852
|
-
}
|
|
33853
|
-
return points;
|
|
33854
|
-
});
|
|
33855
|
-
polygons.push(...innerRingsPoints);
|
|
33856
|
-
}
|
|
33857
|
-
const crossSection = CrossSection.ofPolygons(polygons);
|
|
33858
|
-
manifoldInstancesForCleanup.push(crossSection);
|
|
33859
|
-
pourOp = Manifold.extrude(
|
|
33860
|
-
crossSection,
|
|
33861
|
-
pourThickness,
|
|
33862
|
-
0,
|
|
33863
|
-
// nDivisions
|
|
33864
|
-
0,
|
|
33865
|
-
// twistDegrees
|
|
33866
|
-
[1, 1],
|
|
33867
|
-
// scaleTop
|
|
33868
|
-
true
|
|
33869
|
-
// center extrusion
|
|
33870
|
-
).translate([0, 0, zPos]);
|
|
33871
|
-
manifoldInstancesForCleanup.push(pourOp);
|
|
34027
|
+
ctx.fill();
|
|
33872
34028
|
}
|
|
33873
|
-
|
|
33874
|
-
|
|
33875
|
-
|
|
33876
|
-
|
|
33877
|
-
|
|
33878
|
-
|
|
33879
|
-
|
|
33880
|
-
|
|
33881
|
-
|
|
33882
|
-
|
|
34029
|
+
});
|
|
34030
|
+
const pcbHoles = su13(circuitJson).pcb_hole.list();
|
|
34031
|
+
pcbHoles.forEach((hole) => {
|
|
34032
|
+
const x = hole.x;
|
|
34033
|
+
const y = hole.y;
|
|
34034
|
+
const canvasX = canvasXFromPcb(x);
|
|
34035
|
+
const canvasY = canvasYFromPcb(y);
|
|
34036
|
+
const holeShape = hole.hole_shape || hole.shape;
|
|
34037
|
+
if (holeShape === "circle" && typeof hole.hole_diameter === "number") {
|
|
34038
|
+
const canvasRadius = hole.hole_diameter / 2 * traceTextureResolution;
|
|
34039
|
+
ctx.beginPath();
|
|
34040
|
+
ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI);
|
|
34041
|
+
ctx.fill();
|
|
34042
|
+
} else if (holeShape === "pill" && typeof hole.hole_width === "number" && typeof hole.hole_height === "number") {
|
|
34043
|
+
const width10 = hole.hole_width * traceTextureResolution;
|
|
34044
|
+
const height10 = hole.hole_height * traceTextureResolution;
|
|
34045
|
+
const radius = Math.min(width10, height10) / 2;
|
|
34046
|
+
ctx.beginPath();
|
|
34047
|
+
ctx.roundRect(
|
|
34048
|
+
canvasX - width10 / 2,
|
|
34049
|
+
canvasY - height10 / 2,
|
|
34050
|
+
width10,
|
|
34051
|
+
height10,
|
|
34052
|
+
radius
|
|
34053
|
+
);
|
|
34054
|
+
ctx.fill();
|
|
34055
|
+
} else if (holeShape === "rotated_pill" && typeof hole.hole_width === "number" && typeof hole.hole_height === "number") {
|
|
34056
|
+
const width10 = hole.hole_width * traceTextureResolution;
|
|
34057
|
+
const height10 = hole.hole_height * traceTextureResolution;
|
|
34058
|
+
const radius = Math.min(width10, height10) / 2;
|
|
34059
|
+
const rotation2 = (hole.ccw_rotation || 0) * (Math.PI / 180);
|
|
34060
|
+
ctx.save();
|
|
34061
|
+
ctx.translate(canvasX, canvasY);
|
|
34062
|
+
if (layer === "bottom") {
|
|
34063
|
+
ctx.rotate(-rotation2);
|
|
34064
|
+
} else {
|
|
34065
|
+
ctx.rotate(rotation2);
|
|
33883
34066
|
}
|
|
33884
|
-
|
|
33885
|
-
|
|
33886
|
-
|
|
33887
|
-
|
|
33888
|
-
|
|
33889
|
-
|
|
33890
|
-
|
|
33891
|
-
|
|
34067
|
+
ctx.beginPath();
|
|
34068
|
+
ctx.roundRect(-width10 / 2, -height10 / 2, width10, height10, radius);
|
|
34069
|
+
ctx.fill();
|
|
34070
|
+
ctx.restore();
|
|
34071
|
+
}
|
|
34072
|
+
});
|
|
34073
|
+
const pcbCopperPours = su13(circuitJson).pcb_copper_pour.list();
|
|
34074
|
+
pcbCopperPours.forEach((pour) => {
|
|
34075
|
+
if (pour.layer !== layer) return;
|
|
34076
|
+
if (pour.covered_with_solder_mask !== false) return;
|
|
34077
|
+
if (pour.shape === "rect") {
|
|
34078
|
+
const centerX = canvasXFromPcb(pour.center.x);
|
|
34079
|
+
const centerY = canvasYFromPcb(pour.center.y);
|
|
34080
|
+
const width10 = pour.width * traceTextureResolution;
|
|
34081
|
+
const height10 = pour.height * traceTextureResolution;
|
|
34082
|
+
ctx.fillRect(centerX - width10 / 2, centerY - height10 / 2, width10, height10);
|
|
34083
|
+
} else if (pour.shape === "polygon" && pour.points) {
|
|
34084
|
+
ctx.beginPath();
|
|
34085
|
+
pour.points.forEach((point2, index2) => {
|
|
34086
|
+
const px = canvasXFromPcb(point2.x);
|
|
34087
|
+
const py = canvasYFromPcb(point2.y);
|
|
34088
|
+
if (index2 === 0) {
|
|
34089
|
+
ctx.moveTo(px, py);
|
|
34090
|
+
} else {
|
|
34091
|
+
ctx.lineTo(px, py);
|
|
34092
|
+
}
|
|
33892
34093
|
});
|
|
34094
|
+
ctx.closePath();
|
|
34095
|
+
ctx.fill();
|
|
33893
34096
|
}
|
|
33894
|
-
}
|
|
33895
|
-
|
|
34097
|
+
});
|
|
34098
|
+
ctx.globalCompositeOperation = "source-over";
|
|
34099
|
+
const texture = new THREE24.CanvasTexture(canvas);
|
|
34100
|
+
texture.generateMipmaps = true;
|
|
34101
|
+
texture.minFilter = THREE24.LinearMipmapLinearFilter;
|
|
34102
|
+
texture.magFilter = THREE24.LinearFilter;
|
|
34103
|
+
texture.anisotropy = 16;
|
|
34104
|
+
texture.needsUpdate = true;
|
|
34105
|
+
return texture;
|
|
33896
34106
|
}
|
|
33897
34107
|
|
|
33898
|
-
// src/utils/
|
|
33899
|
-
import
|
|
33900
|
-
|
|
33901
|
-
|
|
33902
|
-
|
|
33903
|
-
|
|
33904
|
-
|
|
33905
|
-
|
|
33906
|
-
|
|
33907
|
-
|
|
34108
|
+
// src/utils/trace-texture.ts
|
|
34109
|
+
import * as THREE25 from "three";
|
|
34110
|
+
import { su as su14 } from "@tscircuit/circuit-json-util";
|
|
34111
|
+
function isWireRoutePoint(point2) {
|
|
34112
|
+
return point2 && point2.route_type === "wire" && typeof point2.layer === "string" && typeof point2.width === "number";
|
|
34113
|
+
}
|
|
34114
|
+
function createTraceTextureForLayer({
|
|
34115
|
+
layer,
|
|
34116
|
+
circuitJson,
|
|
34117
|
+
boardData,
|
|
34118
|
+
traceColor,
|
|
34119
|
+
traceTextureResolution
|
|
34120
|
+
}) {
|
|
34121
|
+
const pcbTraces = su14(circuitJson).pcb_trace.list();
|
|
34122
|
+
const allPcbVias = su14(circuitJson).pcb_via.list();
|
|
34123
|
+
const allPcbPlatedHoles = su14(
|
|
34124
|
+
circuitJson
|
|
34125
|
+
).pcb_plated_hole.list();
|
|
34126
|
+
const tracesOnLayer = pcbTraces.filter(
|
|
34127
|
+
(t) => t.route.some((p) => isWireRoutePoint(p) && p.layer === layer)
|
|
34128
|
+
);
|
|
34129
|
+
if (tracesOnLayer.length === 0) return null;
|
|
34130
|
+
const canvas = document.createElement("canvas");
|
|
34131
|
+
const canvasWidth = Math.floor(boardData.width * traceTextureResolution);
|
|
34132
|
+
const canvasHeight = Math.floor(boardData.height * traceTextureResolution);
|
|
34133
|
+
canvas.width = canvasWidth;
|
|
34134
|
+
canvas.height = canvasHeight;
|
|
34135
|
+
const ctx = canvas.getContext("2d");
|
|
34136
|
+
if (!ctx) return null;
|
|
34137
|
+
if (layer === "bottom") {
|
|
34138
|
+
ctx.translate(0, canvasHeight);
|
|
34139
|
+
ctx.scale(1, -1);
|
|
33908
34140
|
}
|
|
33909
|
-
|
|
33910
|
-
|
|
33911
|
-
|
|
33912
|
-
|
|
33913
|
-
|
|
33914
|
-
|
|
33915
|
-
|
|
33916
|
-
|
|
33917
|
-
|
|
33918
|
-
|
|
33919
|
-
|
|
33920
|
-
const rectCornerRadius = extractRectBorderRadius(cutout);
|
|
33921
|
-
if (typeof rectCornerRadius === "number" && rectCornerRadius > 0) {
|
|
33922
|
-
cutoutOp = createRoundedRectPrism({
|
|
33923
|
-
Manifold,
|
|
33924
|
-
width: cutout.width,
|
|
33925
|
-
height: cutout.height,
|
|
33926
|
-
thickness: cutoutHeight,
|
|
33927
|
-
borderRadius: rectCornerRadius
|
|
33928
|
-
});
|
|
33929
|
-
} else {
|
|
33930
|
-
cutoutOp = Manifold.cube(
|
|
33931
|
-
[cutout.width, cutout.height, cutoutHeight],
|
|
33932
|
-
true
|
|
33933
|
-
// centered
|
|
33934
|
-
);
|
|
33935
|
-
}
|
|
33936
|
-
manifoldInstancesForCleanup.push(cutoutOp);
|
|
33937
|
-
if (cutout.rotation) {
|
|
33938
|
-
const rotatedOp = cutoutOp.rotate([0, 0, cutout.rotation]);
|
|
33939
|
-
manifoldInstancesForCleanup.push(rotatedOp);
|
|
33940
|
-
cutoutOp = rotatedOp;
|
|
33941
|
-
}
|
|
33942
|
-
cutoutOp = cutoutOp.translate([
|
|
33943
|
-
cutout.center.x,
|
|
33944
|
-
cutout.center.y,
|
|
33945
|
-
0
|
|
33946
|
-
// Centered vertically by Manifold.cube, so Z is 0 for board plane
|
|
33947
|
-
]);
|
|
33948
|
-
manifoldInstancesForCleanup.push(cutoutOp);
|
|
33949
|
-
break;
|
|
33950
|
-
}
|
|
33951
|
-
case "circle":
|
|
33952
|
-
cutoutOp = Manifold.cylinder(
|
|
33953
|
-
cutoutHeight,
|
|
33954
|
-
cutout.radius,
|
|
33955
|
-
-1,
|
|
33956
|
-
// default for radiusHigh
|
|
33957
|
-
SMOOTH_CIRCLE_SEGMENTS,
|
|
33958
|
-
true
|
|
33959
|
-
// centered
|
|
33960
|
-
);
|
|
33961
|
-
manifoldInstancesForCleanup.push(cutoutOp);
|
|
33962
|
-
cutoutOp = cutoutOp.translate([cutout.center.x, cutout.center.y, 0]);
|
|
33963
|
-
manifoldInstancesForCleanup.push(cutoutOp);
|
|
33964
|
-
break;
|
|
33965
|
-
case "polygon":
|
|
33966
|
-
if (cutout.points.length < 3) {
|
|
33967
|
-
console.warn(
|
|
33968
|
-
`PCB Cutout [${cutout.pcb_cutout_id}] polygon has fewer than 3 points, skipping.`
|
|
33969
|
-
);
|
|
33970
|
-
continue;
|
|
33971
|
-
}
|
|
33972
|
-
let pointsVec2 = cutout.points.map((p) => [
|
|
33973
|
-
p.x,
|
|
33974
|
-
p.y
|
|
33975
|
-
]);
|
|
33976
|
-
if (arePointsClockwise6(pointsVec2)) {
|
|
33977
|
-
pointsVec2 = pointsVec2.reverse();
|
|
33978
|
-
}
|
|
33979
|
-
const crossSection = CrossSection.ofPolygons([pointsVec2]);
|
|
33980
|
-
manifoldInstancesForCleanup.push(crossSection);
|
|
33981
|
-
cutoutOp = Manifold.extrude(
|
|
33982
|
-
crossSection,
|
|
33983
|
-
cutoutHeight,
|
|
33984
|
-
0,
|
|
33985
|
-
// nDivisions
|
|
33986
|
-
0,
|
|
33987
|
-
// twistDegrees
|
|
33988
|
-
[1, 1],
|
|
33989
|
-
// scaleTop
|
|
33990
|
-
true
|
|
33991
|
-
// center extrusion
|
|
33992
|
-
);
|
|
33993
|
-
manifoldInstancesForCleanup.push(cutoutOp);
|
|
33994
|
-
break;
|
|
33995
|
-
default:
|
|
33996
|
-
console.warn(
|
|
33997
|
-
`Unsupported cutout shape: ${cutout.shape} for cutout ${cutout.pcb_cutout_id}`
|
|
33998
|
-
);
|
|
34141
|
+
tracesOnLayer.forEach((trace) => {
|
|
34142
|
+
let firstPoint = true;
|
|
34143
|
+
ctx.beginPath();
|
|
34144
|
+
ctx.strokeStyle = traceColor;
|
|
34145
|
+
ctx.lineCap = "round";
|
|
34146
|
+
ctx.lineJoin = "round";
|
|
34147
|
+
let currentLineWidth = 0;
|
|
34148
|
+
for (const point2 of trace.route) {
|
|
34149
|
+
if (!isWireRoutePoint(point2) || point2.layer !== layer) {
|
|
34150
|
+
if (!firstPoint) ctx.stroke();
|
|
34151
|
+
firstPoint = true;
|
|
33999
34152
|
continue;
|
|
34153
|
+
}
|
|
34154
|
+
const pcbX = point2.x;
|
|
34155
|
+
const pcbY = point2.y;
|
|
34156
|
+
currentLineWidth = point2.width * traceTextureResolution;
|
|
34157
|
+
ctx.lineWidth = currentLineWidth;
|
|
34158
|
+
const canvasX = (pcbX - boardData.center.x + boardData.width / 2) * traceTextureResolution;
|
|
34159
|
+
const canvasY = (-(pcbY - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
|
|
34160
|
+
if (firstPoint) {
|
|
34161
|
+
ctx.moveTo(canvasX, canvasY);
|
|
34162
|
+
firstPoint = false;
|
|
34163
|
+
} else {
|
|
34164
|
+
ctx.lineTo(canvasX, canvasY);
|
|
34165
|
+
}
|
|
34000
34166
|
}
|
|
34001
|
-
if (
|
|
34002
|
-
|
|
34167
|
+
if (!firstPoint) {
|
|
34168
|
+
ctx.stroke();
|
|
34003
34169
|
}
|
|
34004
|
-
}
|
|
34005
|
-
|
|
34170
|
+
});
|
|
34171
|
+
ctx.globalCompositeOperation = "destination-out";
|
|
34172
|
+
ctx.fillStyle = "black";
|
|
34173
|
+
allPcbVias.forEach((via) => {
|
|
34174
|
+
const canvasX = (via.x - boardData.center.x + boardData.width / 2) * traceTextureResolution;
|
|
34175
|
+
const canvasY = (-(via.y - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
|
|
34176
|
+
const canvasRadius = via.outer_diameter / 2 * traceTextureResolution;
|
|
34177
|
+
ctx.beginPath();
|
|
34178
|
+
ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI, false);
|
|
34179
|
+
ctx.fill();
|
|
34180
|
+
});
|
|
34181
|
+
allPcbPlatedHoles.forEach((ph) => {
|
|
34182
|
+
if (ph.layers.includes(layer) && ph.shape === "circle") {
|
|
34183
|
+
const canvasX = (ph.x - boardData.center.x + boardData.width / 2) * traceTextureResolution;
|
|
34184
|
+
const canvasY = (-(ph.y - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
|
|
34185
|
+
const canvasRadius = ph.outer_diameter / 2 * traceTextureResolution;
|
|
34186
|
+
ctx.beginPath();
|
|
34187
|
+
ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI, false);
|
|
34188
|
+
ctx.fill();
|
|
34189
|
+
}
|
|
34190
|
+
});
|
|
34191
|
+
ctx.globalCompositeOperation = "source-over";
|
|
34192
|
+
const texture = new THREE25.CanvasTexture(canvas);
|
|
34193
|
+
texture.generateMipmaps = true;
|
|
34194
|
+
texture.minFilter = THREE25.LinearMipmapLinearFilter;
|
|
34195
|
+
texture.magFilter = THREE25.LinearFilter;
|
|
34196
|
+
texture.anisotropy = 16;
|
|
34197
|
+
texture.needsUpdate = true;
|
|
34198
|
+
return texture;
|
|
34006
34199
|
}
|
|
34007
34200
|
|
|
34008
34201
|
// src/hooks/useManifoldBoardBuilder.ts
|
|
@@ -34017,7 +34210,7 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
|
|
|
34017
34210
|
const panels = circuitJson.filter(
|
|
34018
34211
|
(e) => e.type === "pcb_panel"
|
|
34019
34212
|
);
|
|
34020
|
-
const boards =
|
|
34213
|
+
const boards = su15(circuitJson).pcb_board.list();
|
|
34021
34214
|
if (panels.length > 0) {
|
|
34022
34215
|
const panel = panels[0];
|
|
34023
34216
|
return {
|
|
@@ -34036,7 +34229,7 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
|
|
|
34036
34229
|
return boardsNotInPanel.length > 0 ? boardsNotInPanel[0] : null;
|
|
34037
34230
|
}, [circuitJson]);
|
|
34038
34231
|
const isFauxBoard = useMemo19(() => {
|
|
34039
|
-
const boards =
|
|
34232
|
+
const boards = su15(circuitJson).pcb_board.list();
|
|
34040
34233
|
return boards.length > 0 && boards[0].pcb_board_id === "faux-board";
|
|
34041
34234
|
}, [circuitJson]);
|
|
34042
34235
|
useEffect22(() => {
|
|
@@ -34171,7 +34364,7 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
|
|
|
34171
34364
|
{
|
|
34172
34365
|
key: "plated-holes-union",
|
|
34173
34366
|
geometry: cutPlatedGeom,
|
|
34174
|
-
color: new
|
|
34367
|
+
color: new THREE26.Color(
|
|
34175
34368
|
colors.copper[0],
|
|
34176
34369
|
colors.copper[1],
|
|
34177
34370
|
colors.copper[2]
|
|
@@ -34201,7 +34394,7 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
|
|
|
34201
34394
|
const matColorArray = boardMaterialColors[boardData.material] ?? colors.fr4Green;
|
|
34202
34395
|
currentGeoms.board = {
|
|
34203
34396
|
geometry: finalBoardGeom,
|
|
34204
|
-
color: new
|
|
34397
|
+
color: new THREE26.Color(
|
|
34205
34398
|
matColorArray[0],
|
|
34206
34399
|
matColorArray[1],
|
|
34207
34400
|
matColorArray[2]
|
|
@@ -34262,6 +34455,22 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
|
|
|
34262
34455
|
silkscreenColor,
|
|
34263
34456
|
traceTextureResolution: TRACE_TEXTURE_RESOLUTION
|
|
34264
34457
|
});
|
|
34458
|
+
const soldermaskColorArr = tracesMaterialColors[boardData.material] ?? colors.fr4GreenSolderWithMask;
|
|
34459
|
+
const soldermaskColor = `rgb(${Math.round(soldermaskColorArr[0] * 255)}, ${Math.round(soldermaskColorArr[1] * 255)}, ${Math.round(soldermaskColorArr[2] * 255)})`;
|
|
34460
|
+
currentTextures.topSoldermask = createSoldermaskTextureForLayer({
|
|
34461
|
+
layer: "top",
|
|
34462
|
+
circuitJson,
|
|
34463
|
+
boardData,
|
|
34464
|
+
soldermaskColor,
|
|
34465
|
+
traceTextureResolution: TRACE_TEXTURE_RESOLUTION
|
|
34466
|
+
});
|
|
34467
|
+
currentTextures.bottomSoldermask = createSoldermaskTextureForLayer({
|
|
34468
|
+
layer: "bottom",
|
|
34469
|
+
circuitJson,
|
|
34470
|
+
boardData,
|
|
34471
|
+
soldermaskColor,
|
|
34472
|
+
traceTextureResolution: TRACE_TEXTURE_RESOLUTION
|
|
34473
|
+
});
|
|
34265
34474
|
setTextures(currentTextures);
|
|
34266
34475
|
} catch (e) {
|
|
34267
34476
|
console.error("Error processing geometry with Manifold in hook:", e);
|
|
@@ -34290,11 +34499,11 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
|
|
|
34290
34499
|
};
|
|
34291
34500
|
|
|
34292
34501
|
// src/utils/manifold/create-three-geometry-meshes.ts
|
|
34293
|
-
import * as
|
|
34502
|
+
import * as THREE28 from "three";
|
|
34294
34503
|
|
|
34295
34504
|
// src/utils/create-board-material.ts
|
|
34296
|
-
import * as
|
|
34297
|
-
var DEFAULT_SIDE =
|
|
34505
|
+
import * as THREE27 from "three";
|
|
34506
|
+
var DEFAULT_SIDE = THREE27.DoubleSide;
|
|
34298
34507
|
var createBoardMaterial = ({
|
|
34299
34508
|
material,
|
|
34300
34509
|
color,
|
|
@@ -34302,7 +34511,7 @@ var createBoardMaterial = ({
|
|
|
34302
34511
|
isFaux = false
|
|
34303
34512
|
}) => {
|
|
34304
34513
|
if (material === "fr4") {
|
|
34305
|
-
return new
|
|
34514
|
+
return new THREE27.MeshPhysicalMaterial({
|
|
34306
34515
|
color,
|
|
34307
34516
|
side,
|
|
34308
34517
|
metalness: 0,
|
|
@@ -34316,7 +34525,7 @@ var createBoardMaterial = ({
|
|
|
34316
34525
|
flatShading: true
|
|
34317
34526
|
});
|
|
34318
34527
|
}
|
|
34319
|
-
return new
|
|
34528
|
+
return new THREE27.MeshStandardMaterial({
|
|
34320
34529
|
color,
|
|
34321
34530
|
side,
|
|
34322
34531
|
flatShading: true,
|
|
@@ -34332,12 +34541,12 @@ function createGeometryMeshes(geoms) {
|
|
|
34332
34541
|
const meshes = [];
|
|
34333
34542
|
if (!geoms) return meshes;
|
|
34334
34543
|
if (geoms.board && geoms.board.geometry) {
|
|
34335
|
-
const mesh = new
|
|
34544
|
+
const mesh = new THREE28.Mesh(
|
|
34336
34545
|
geoms.board.geometry,
|
|
34337
34546
|
createBoardMaterial({
|
|
34338
34547
|
material: geoms.board.material,
|
|
34339
34548
|
color: geoms.board.color,
|
|
34340
|
-
side:
|
|
34549
|
+
side: THREE28.DoubleSide,
|
|
34341
34550
|
isFaux: geoms.board.isFaux
|
|
34342
34551
|
})
|
|
34343
34552
|
);
|
|
@@ -34347,11 +34556,11 @@ function createGeometryMeshes(geoms) {
|
|
|
34347
34556
|
const createMeshesFromArray = (geomArray) => {
|
|
34348
34557
|
if (geomArray) {
|
|
34349
34558
|
geomArray.forEach((comp) => {
|
|
34350
|
-
const mesh = new
|
|
34559
|
+
const mesh = new THREE28.Mesh(
|
|
34351
34560
|
comp.geometry,
|
|
34352
|
-
new
|
|
34561
|
+
new THREE28.MeshStandardMaterial({
|
|
34353
34562
|
color: comp.color,
|
|
34354
|
-
side:
|
|
34563
|
+
side: THREE28.DoubleSide,
|
|
34355
34564
|
flatShading: true,
|
|
34356
34565
|
// Consistent with board
|
|
34357
34566
|
polygonOffset: true,
|
|
@@ -34372,21 +34581,24 @@ function createGeometryMeshes(geoms) {
|
|
|
34372
34581
|
}
|
|
34373
34582
|
|
|
34374
34583
|
// src/utils/manifold/create-three-texture-meshes.ts
|
|
34375
|
-
import * as
|
|
34584
|
+
import * as THREE29 from "three";
|
|
34376
34585
|
function createTextureMeshes(textures, boardData, pcbThickness) {
|
|
34377
34586
|
const meshes = [];
|
|
34378
34587
|
if (!textures || !boardData || pcbThickness === null) return meshes;
|
|
34379
|
-
const createTexturePlane = (texture, yOffset, isBottomLayer, keySuffix) => {
|
|
34588
|
+
const createTexturePlane = (texture, yOffset, isBottomLayer, keySuffix, usePolygonOffset = false) => {
|
|
34380
34589
|
if (!texture) return null;
|
|
34381
|
-
const planeGeom = new
|
|
34382
|
-
const material = new
|
|
34590
|
+
const planeGeom = new THREE29.PlaneGeometry(boardData.width, boardData.height);
|
|
34591
|
+
const material = new THREE29.MeshBasicMaterial({
|
|
34383
34592
|
map: texture,
|
|
34384
34593
|
transparent: true,
|
|
34385
|
-
side:
|
|
34386
|
-
depthWrite: false
|
|
34594
|
+
side: THREE29.DoubleSide,
|
|
34595
|
+
depthWrite: false,
|
|
34387
34596
|
// Important for layers to avoid z-fighting issues with board itself
|
|
34597
|
+
polygonOffset: usePolygonOffset,
|
|
34598
|
+
polygonOffsetFactor: usePolygonOffset ? -1 : 0,
|
|
34599
|
+
polygonOffsetUnits: usePolygonOffset ? -1 : 0
|
|
34388
34600
|
});
|
|
34389
|
-
const mesh = new
|
|
34601
|
+
const mesh = new THREE29.Mesh(planeGeom, material);
|
|
34390
34602
|
mesh.position.set(boardData.center.x, boardData.center.y, yOffset);
|
|
34391
34603
|
if (isBottomLayer) {
|
|
34392
34604
|
mesh.rotation.set(Math.PI, 0, 0);
|
|
@@ -34425,6 +34637,26 @@ function createTextureMeshes(textures, boardData, pcbThickness) {
|
|
|
34425
34637
|
"silkscreen"
|
|
34426
34638
|
);
|
|
34427
34639
|
if (bottomSilkscreenMesh) meshes.push(bottomSilkscreenMesh);
|
|
34640
|
+
const topSoldermaskMesh = createTexturePlane(
|
|
34641
|
+
textures.topSoldermask,
|
|
34642
|
+
pcbThickness / 2 + 8e-4,
|
|
34643
|
+
// Just above board surface, below traces
|
|
34644
|
+
false,
|
|
34645
|
+
"soldermask",
|
|
34646
|
+
true
|
|
34647
|
+
// Enable polygon offset
|
|
34648
|
+
);
|
|
34649
|
+
if (topSoldermaskMesh) meshes.push(topSoldermaskMesh);
|
|
34650
|
+
const bottomSoldermaskMesh = createTexturePlane(
|
|
34651
|
+
textures.bottomSoldermask,
|
|
34652
|
+
-pcbThickness / 2 - 8e-4,
|
|
34653
|
+
// Just below board surface (bottom side)
|
|
34654
|
+
true,
|
|
34655
|
+
"soldermask",
|
|
34656
|
+
true
|
|
34657
|
+
// Enable polygon offset
|
|
34658
|
+
);
|
|
34659
|
+
if (bottomSoldermaskMesh) meshes.push(bottomSoldermaskMesh);
|
|
34428
34660
|
return meshes;
|
|
34429
34661
|
}
|
|
34430
34662
|
|
|
@@ -34469,6 +34701,10 @@ var BoardMeshes = ({
|
|
|
34469
34701
|
shouldShow = visibility.topSilkscreen;
|
|
34470
34702
|
} else if (mesh.name.includes("bottom-silkscreen")) {
|
|
34471
34703
|
shouldShow = visibility.bottomSilkscreen;
|
|
34704
|
+
} else if (mesh.name.includes("top-soldermask")) {
|
|
34705
|
+
shouldShow = visibility.topMask;
|
|
34706
|
+
} else if (mesh.name.includes("bottom-soldermask")) {
|
|
34707
|
+
shouldShow = visibility.bottomMask;
|
|
34472
34708
|
}
|
|
34473
34709
|
if (shouldShow) {
|
|
34474
34710
|
rootObject.add(mesh);
|
|
@@ -34581,7 +34817,7 @@ try {
|
|
|
34581
34817
|
[textures, boardData, pcbThickness]
|
|
34582
34818
|
);
|
|
34583
34819
|
const cadComponents = useMemo20(
|
|
34584
|
-
() =>
|
|
34820
|
+
() => su16(circuitJson).cad_component.list(),
|
|
34585
34821
|
[circuitJson]
|
|
34586
34822
|
);
|
|
34587
34823
|
const boardDimensions = useMemo20(() => {
|
|
@@ -40696,6 +40932,48 @@ var AppearanceMenu = () => {
|
|
|
40696
40932
|
]
|
|
40697
40933
|
}
|
|
40698
40934
|
),
|
|
40935
|
+
/* @__PURE__ */ jsxs8(
|
|
40936
|
+
Item22,
|
|
40937
|
+
{
|
|
40938
|
+
style: {
|
|
40939
|
+
...itemStyles,
|
|
40940
|
+
backgroundColor: hoveredItem === "topMask" ? "#404040" : "transparent"
|
|
40941
|
+
},
|
|
40942
|
+
onSelect: (e) => e.preventDefault(),
|
|
40943
|
+
onPointerDown: (e) => {
|
|
40944
|
+
e.preventDefault();
|
|
40945
|
+
setLayerVisibility("topMask", !visibility.topMask);
|
|
40946
|
+
},
|
|
40947
|
+
onMouseEnter: () => setHoveredItem("topMask"),
|
|
40948
|
+
onMouseLeave: () => setHoveredItem(null),
|
|
40949
|
+
onTouchStart: () => setHoveredItem("topMask"),
|
|
40950
|
+
children: [
|
|
40951
|
+
/* @__PURE__ */ jsx34("span", { style: iconContainerStyles, children: visibility.topMask && /* @__PURE__ */ jsx34(CheckIcon, {}) }),
|
|
40952
|
+
/* @__PURE__ */ jsx34("span", { style: { display: "flex", alignItems: "center" }, children: "Top Soldermask" })
|
|
40953
|
+
]
|
|
40954
|
+
}
|
|
40955
|
+
),
|
|
40956
|
+
/* @__PURE__ */ jsxs8(
|
|
40957
|
+
Item22,
|
|
40958
|
+
{
|
|
40959
|
+
style: {
|
|
40960
|
+
...itemStyles,
|
|
40961
|
+
backgroundColor: hoveredItem === "bottomMask" ? "#404040" : "transparent"
|
|
40962
|
+
},
|
|
40963
|
+
onSelect: (e) => e.preventDefault(),
|
|
40964
|
+
onPointerDown: (e) => {
|
|
40965
|
+
e.preventDefault();
|
|
40966
|
+
setLayerVisibility("bottomMask", !visibility.bottomMask);
|
|
40967
|
+
},
|
|
40968
|
+
onMouseEnter: () => setHoveredItem("bottomMask"),
|
|
40969
|
+
onMouseLeave: () => setHoveredItem(null),
|
|
40970
|
+
onTouchStart: () => setHoveredItem("bottomMask"),
|
|
40971
|
+
children: [
|
|
40972
|
+
/* @__PURE__ */ jsx34("span", { style: iconContainerStyles, children: visibility.bottomMask && /* @__PURE__ */ jsx34(CheckIcon, {}) }),
|
|
40973
|
+
/* @__PURE__ */ jsx34("span", { style: { display: "flex", alignItems: "center" }, children: "Bottom Soldermask" })
|
|
40974
|
+
]
|
|
40975
|
+
}
|
|
40976
|
+
),
|
|
40699
40977
|
/* @__PURE__ */ jsxs8(
|
|
40700
40978
|
Item22,
|
|
40701
40979
|
{
|
|
@@ -41535,7 +41813,7 @@ var CadViewerInner = (props) => {
|
|
|
41535
41813
|
);
|
|
41536
41814
|
};
|
|
41537
41815
|
var CadViewer = (props) => {
|
|
41538
|
-
const defaultTarget = useMemo28(() => new
|
|
41816
|
+
const defaultTarget = useMemo28(() => new THREE30.Vector3(0, 0, 0), []);
|
|
41539
41817
|
const initialCameraPosition = useMemo28(
|
|
41540
41818
|
() => [5, -5, 5],
|
|
41541
41819
|
[]
|
|
@@ -41552,12 +41830,12 @@ var CadViewer = (props) => {
|
|
|
41552
41830
|
|
|
41553
41831
|
// src/convert-circuit-json-to-3d-svg.ts
|
|
41554
41832
|
var import_debug = __toESM(require_browser(), 1);
|
|
41555
|
-
import { su as
|
|
41556
|
-
import * as
|
|
41833
|
+
import { su as su17 } from "@tscircuit/circuit-json-util";
|
|
41834
|
+
import * as THREE34 from "three";
|
|
41557
41835
|
import { SVGRenderer } from "three/examples/jsm/renderers/SVGRenderer.js";
|
|
41558
41836
|
|
|
41559
41837
|
// src/utils/create-geometry-from-polygons.ts
|
|
41560
|
-
import * as
|
|
41838
|
+
import * as THREE31 from "three";
|
|
41561
41839
|
import { BufferGeometry as BufferGeometry3, Float32BufferAttribute as Float32BufferAttribute2 } from "three";
|
|
41562
41840
|
function createGeometryFromPolygons(polygons) {
|
|
41563
41841
|
const geometry = new BufferGeometry3();
|
|
@@ -41571,12 +41849,12 @@ function createGeometryFromPolygons(polygons) {
|
|
|
41571
41849
|
...polygon3.vertices[i + 1]
|
|
41572
41850
|
// Third vertex
|
|
41573
41851
|
);
|
|
41574
|
-
const v1 = new
|
|
41575
|
-
const v2 = new
|
|
41576
|
-
const v3 = new
|
|
41577
|
-
const normal = new
|
|
41578
|
-
new
|
|
41579
|
-
new
|
|
41852
|
+
const v1 = new THREE31.Vector3(...polygon3.vertices[0]);
|
|
41853
|
+
const v2 = new THREE31.Vector3(...polygon3.vertices[i]);
|
|
41854
|
+
const v3 = new THREE31.Vector3(...polygon3.vertices[i + 1]);
|
|
41855
|
+
const normal = new THREE31.Vector3().crossVectors(
|
|
41856
|
+
new THREE31.Vector3().subVectors(v2, v1),
|
|
41857
|
+
new THREE31.Vector3().subVectors(v3, v1)
|
|
41580
41858
|
).normalize();
|
|
41581
41859
|
normals.push(
|
|
41582
41860
|
normal.x,
|
|
@@ -41600,10 +41878,10 @@ function createGeometryFromPolygons(polygons) {
|
|
|
41600
41878
|
var import_modeling2 = __toESM(require_src(), 1);
|
|
41601
41879
|
var import_jscad_planner2 = __toESM(require_dist(), 1);
|
|
41602
41880
|
var jscadModeling2 = __toESM(require_src(), 1);
|
|
41603
|
-
import * as
|
|
41881
|
+
import * as THREE33 from "three";
|
|
41604
41882
|
|
|
41605
41883
|
// src/utils/load-model.ts
|
|
41606
|
-
import * as
|
|
41884
|
+
import * as THREE32 from "three";
|
|
41607
41885
|
import { GLTFLoader as GLTFLoader2 } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
41608
41886
|
import { OBJLoader as OBJLoader2 } from "three/examples/jsm/loaders/OBJLoader.js";
|
|
41609
41887
|
import { STLLoader as STLLoader2 } from "three/examples/jsm/loaders/STLLoader.js";
|
|
@@ -41611,12 +41889,12 @@ async function load3DModel(url) {
|
|
|
41611
41889
|
if (url.endsWith(".stl")) {
|
|
41612
41890
|
const loader = new STLLoader2();
|
|
41613
41891
|
const geometry = await loader.loadAsync(url);
|
|
41614
|
-
const material = new
|
|
41892
|
+
const material = new THREE32.MeshStandardMaterial({
|
|
41615
41893
|
color: 8947848,
|
|
41616
41894
|
metalness: 0.5,
|
|
41617
41895
|
roughness: 0.5
|
|
41618
41896
|
});
|
|
41619
|
-
return new
|
|
41897
|
+
return new THREE32.Mesh(geometry, material);
|
|
41620
41898
|
}
|
|
41621
41899
|
if (url.endsWith(".obj")) {
|
|
41622
41900
|
const loader = new OBJLoader2();
|
|
@@ -41649,9 +41927,9 @@ async function renderComponent(component, scene) {
|
|
|
41649
41927
|
}
|
|
41650
41928
|
if (component.rotation) {
|
|
41651
41929
|
model.rotation.set(
|
|
41652
|
-
|
|
41653
|
-
|
|
41654
|
-
|
|
41930
|
+
THREE33.MathUtils.degToRad(component.rotation.x ?? 0),
|
|
41931
|
+
THREE33.MathUtils.degToRad(component.rotation.y ?? 0),
|
|
41932
|
+
THREE33.MathUtils.degToRad(component.rotation.z ?? 0)
|
|
41655
41933
|
);
|
|
41656
41934
|
}
|
|
41657
41935
|
scene.add(model);
|
|
@@ -41665,13 +41943,13 @@ async function renderComponent(component, scene) {
|
|
|
41665
41943
|
);
|
|
41666
41944
|
if (jscadObject && (jscadObject.polygons || jscadObject.sides)) {
|
|
41667
41945
|
const threeGeom = convertCSGToThreeGeom(jscadObject);
|
|
41668
|
-
const material2 = new
|
|
41946
|
+
const material2 = new THREE33.MeshStandardMaterial({
|
|
41669
41947
|
color: 8947848,
|
|
41670
41948
|
metalness: 0.5,
|
|
41671
41949
|
roughness: 0.5,
|
|
41672
|
-
side:
|
|
41950
|
+
side: THREE33.DoubleSide
|
|
41673
41951
|
});
|
|
41674
|
-
const mesh2 = new
|
|
41952
|
+
const mesh2 = new THREE33.Mesh(threeGeom, material2);
|
|
41675
41953
|
if (component.position) {
|
|
41676
41954
|
mesh2.position.set(
|
|
41677
41955
|
component.position.x ?? 0,
|
|
@@ -41681,9 +41959,9 @@ async function renderComponent(component, scene) {
|
|
|
41681
41959
|
}
|
|
41682
41960
|
if (component.rotation) {
|
|
41683
41961
|
mesh2.rotation.set(
|
|
41684
|
-
|
|
41685
|
-
|
|
41686
|
-
|
|
41962
|
+
THREE33.MathUtils.degToRad(component.rotation.x ?? 0),
|
|
41963
|
+
THREE33.MathUtils.degToRad(component.rotation.y ?? 0),
|
|
41964
|
+
THREE33.MathUtils.degToRad(component.rotation.z ?? 0)
|
|
41687
41965
|
);
|
|
41688
41966
|
}
|
|
41689
41967
|
scene.add(mesh2);
|
|
@@ -41700,17 +41978,17 @@ async function renderComponent(component, scene) {
|
|
|
41700
41978
|
if (!geom || !geom.polygons && !geom.sides) {
|
|
41701
41979
|
continue;
|
|
41702
41980
|
}
|
|
41703
|
-
const color = new
|
|
41981
|
+
const color = new THREE33.Color(geomInfo.color);
|
|
41704
41982
|
color.convertLinearToSRGB();
|
|
41705
41983
|
const geomWithColor = { ...geom, color: [color.r, color.g, color.b] };
|
|
41706
41984
|
const threeGeom = convertCSGToThreeGeom(geomWithColor);
|
|
41707
|
-
const material2 = new
|
|
41985
|
+
const material2 = new THREE33.MeshStandardMaterial({
|
|
41708
41986
|
vertexColors: true,
|
|
41709
41987
|
metalness: 0.2,
|
|
41710
41988
|
roughness: 0.8,
|
|
41711
|
-
side:
|
|
41989
|
+
side: THREE33.DoubleSide
|
|
41712
41990
|
});
|
|
41713
|
-
const mesh2 = new
|
|
41991
|
+
const mesh2 = new THREE33.Mesh(threeGeom, material2);
|
|
41714
41992
|
if (component.position) {
|
|
41715
41993
|
mesh2.position.set(
|
|
41716
41994
|
component.position.x ?? 0,
|
|
@@ -41720,22 +41998,22 @@ async function renderComponent(component, scene) {
|
|
|
41720
41998
|
}
|
|
41721
41999
|
if (component.rotation) {
|
|
41722
42000
|
mesh2.rotation.set(
|
|
41723
|
-
|
|
41724
|
-
|
|
41725
|
-
|
|
42001
|
+
THREE33.MathUtils.degToRad(component.rotation.x ?? 0),
|
|
42002
|
+
THREE33.MathUtils.degToRad(component.rotation.y ?? 0),
|
|
42003
|
+
THREE33.MathUtils.degToRad(component.rotation.z ?? 0)
|
|
41726
42004
|
);
|
|
41727
42005
|
}
|
|
41728
42006
|
scene.add(mesh2);
|
|
41729
42007
|
}
|
|
41730
42008
|
return;
|
|
41731
42009
|
}
|
|
41732
|
-
const geometry = new
|
|
41733
|
-
const material = new
|
|
42010
|
+
const geometry = new THREE33.BoxGeometry(0.5, 0.5, 0.5);
|
|
42011
|
+
const material = new THREE33.MeshStandardMaterial({
|
|
41734
42012
|
color: 16711680,
|
|
41735
42013
|
transparent: true,
|
|
41736
42014
|
opacity: 0.25
|
|
41737
42015
|
});
|
|
41738
|
-
const mesh = new
|
|
42016
|
+
const mesh = new THREE33.Mesh(geometry, material);
|
|
41739
42017
|
if (component.position) {
|
|
41740
42018
|
mesh.position.set(
|
|
41741
42019
|
component.position.x ?? 0,
|
|
@@ -41756,11 +42034,11 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
|
|
|
41756
42034
|
padding = 20,
|
|
41757
42035
|
zoom = 1.5
|
|
41758
42036
|
} = options;
|
|
41759
|
-
const scene = new
|
|
42037
|
+
const scene = new THREE34.Scene();
|
|
41760
42038
|
const renderer = new SVGRenderer();
|
|
41761
42039
|
renderer.setSize(width10, height10);
|
|
41762
|
-
renderer.setClearColor(new
|
|
41763
|
-
const camera = new
|
|
42040
|
+
renderer.setClearColor(new THREE34.Color(backgroundColor), 1);
|
|
42041
|
+
const camera = new THREE34.OrthographicCamera();
|
|
41764
42042
|
const aspect = width10 / height10;
|
|
41765
42043
|
const frustumSize = 100;
|
|
41766
42044
|
const halfFrustumSize = frustumSize / 2 / zoom;
|
|
@@ -41774,25 +42052,25 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
|
|
|
41774
42052
|
camera.position.set(position.x, position.y, position.z);
|
|
41775
42053
|
camera.up.set(0, 1, 0);
|
|
41776
42054
|
const lookAt = options.camera?.lookAt ?? { x: 0, y: 0, z: 0 };
|
|
41777
|
-
camera.lookAt(new
|
|
42055
|
+
camera.lookAt(new THREE34.Vector3(lookAt.x, lookAt.y, lookAt.z));
|
|
41778
42056
|
camera.updateProjectionMatrix();
|
|
41779
|
-
const ambientLight = new
|
|
42057
|
+
const ambientLight = new THREE34.AmbientLight(16777215, Math.PI / 2);
|
|
41780
42058
|
scene.add(ambientLight);
|
|
41781
|
-
const pointLight = new
|
|
42059
|
+
const pointLight = new THREE34.PointLight(16777215, Math.PI / 4);
|
|
41782
42060
|
pointLight.position.set(-10, -10, 10);
|
|
41783
42061
|
scene.add(pointLight);
|
|
41784
|
-
const components =
|
|
42062
|
+
const components = su17(circuitJson).cad_component.list();
|
|
41785
42063
|
for (const component of components) {
|
|
41786
42064
|
await renderComponent(component, scene);
|
|
41787
42065
|
}
|
|
41788
|
-
const boardData =
|
|
42066
|
+
const boardData = su17(circuitJson).pcb_board.list()[0];
|
|
41789
42067
|
const boardGeom = createBoardGeomFromCircuitJson(circuitJson);
|
|
41790
42068
|
if (boardGeom) {
|
|
41791
42069
|
for (const geom of boardGeom) {
|
|
41792
42070
|
const g = geom;
|
|
41793
42071
|
if (!g.polygons || g.polygons.length === 0) continue;
|
|
41794
42072
|
const geometry = createGeometryFromPolygons(g.polygons);
|
|
41795
|
-
const baseColor = new
|
|
42073
|
+
const baseColor = new THREE34.Color(
|
|
41796
42074
|
g.color?.[0] ?? 0,
|
|
41797
42075
|
g.color?.[1] ?? 0,
|
|
41798
42076
|
g.color?.[2] ?? 0
|
|
@@ -41800,18 +42078,18 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
|
|
|
41800
42078
|
const material = createBoardMaterial({
|
|
41801
42079
|
material: boardData?.material,
|
|
41802
42080
|
color: baseColor,
|
|
41803
|
-
side:
|
|
42081
|
+
side: THREE34.DoubleSide
|
|
41804
42082
|
});
|
|
41805
|
-
const mesh = new
|
|
42083
|
+
const mesh = new THREE34.Mesh(geometry, material);
|
|
41806
42084
|
scene.add(mesh);
|
|
41807
42085
|
}
|
|
41808
42086
|
}
|
|
41809
|
-
const gridHelper = new
|
|
42087
|
+
const gridHelper = new THREE34.GridHelper(100, 100);
|
|
41810
42088
|
gridHelper.rotation.x = Math.PI / 2;
|
|
41811
42089
|
scene.add(gridHelper);
|
|
41812
|
-
const box = new
|
|
41813
|
-
const center = box.getCenter(new
|
|
41814
|
-
const size5 = box.getSize(new
|
|
42090
|
+
const box = new THREE34.Box3().setFromObject(scene);
|
|
42091
|
+
const center = box.getCenter(new THREE34.Vector3());
|
|
42092
|
+
const size5 = box.getSize(new THREE34.Vector3());
|
|
41815
42093
|
scene.position.sub(center);
|
|
41816
42094
|
const maxDim = Math.max(size5.x, size5.y, size5.z);
|
|
41817
42095
|
if (maxDim > 0) {
|