@tscircuit/3d-viewer 0.0.451 → 0.0.452
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1076 -841
- 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.451",
|
|
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
|
-
ctx.beginPath();
|
|
32536
|
-
ctx.lineWidth = coerceDimensionToMm(path.stroke_width, 0.1) * traceTextureResolution;
|
|
32537
|
-
ctx.lineCap = "round";
|
|
32538
|
-
ctx.lineJoin = "round";
|
|
32539
|
-
path.route.forEach((point2, index2) => {
|
|
32540
|
-
const canvasX = canvasXFromPcb(parseDimensionToMm(point2.x) ?? 0);
|
|
32541
|
-
const canvasY = canvasYFromPcb(parseDimensionToMm(point2.y) ?? 0);
|
|
32542
|
-
if (index2 === 0) ctx.moveTo(canvasX, canvasY);
|
|
32543
|
-
else ctx.lineTo(canvasX, canvasY);
|
|
32544
|
-
});
|
|
32545
|
-
ctx.stroke();
|
|
32546
|
-
});
|
|
32547
|
-
circlesOnLayer.forEach((circleEl) => {
|
|
32548
|
-
const radius = coerceDimensionToMm(circleEl.radius, 0);
|
|
32549
|
-
if (radius <= 0) return;
|
|
32550
|
-
const strokeWidth = coerceDimensionToMm(circleEl.stroke_width, 0.12);
|
|
32551
|
-
const hasStroke = strokeWidth > 0;
|
|
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(
|
|
32562
|
-
0,
|
|
32563
|
-
radiusPx - strokeWidth / 2 * traceTextureResolution
|
|
32564
|
-
);
|
|
32565
|
-
if (innerRadiusPx > 0) {
|
|
32566
|
-
ctx.beginPath();
|
|
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();
|
|
32574
|
-
}
|
|
32575
|
-
} else {
|
|
32576
|
-
ctx.beginPath();
|
|
32577
|
-
ctx.arc(0, 0, radiusPx, 0, 2 * Math.PI);
|
|
32578
|
-
ctx.fill();
|
|
32579
|
-
}
|
|
32580
|
-
ctx.restore();
|
|
32581
|
-
});
|
|
32582
|
-
rectsOnLayer.forEach((rect) => {
|
|
32583
|
-
const width10 = coerceDimensionToMm(rect.width, 0);
|
|
32584
|
-
const height10 = coerceDimensionToMm(rect.height, 0);
|
|
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();
|
|
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;
|
|
32628
32532
|
}
|
|
32629
|
-
|
|
32630
|
-
|
|
32631
|
-
|
|
32632
|
-
|
|
32633
|
-
|
|
32634
|
-
|
|
32635
|
-
|
|
32636
|
-
|
|
32637
|
-
|
|
32638
|
-
|
|
32639
|
-
}
|
|
32640
|
-
if (hasStroke && strokeWidthPx > 0) {
|
|
32641
|
-
ctx.lineWidth = strokeWidthPx;
|
|
32642
|
-
if (isDashed) {
|
|
32643
|
-
const dashLength = Math.max(strokeWidthPx * 2, 1);
|
|
32644
|
-
ctx.setLineDash([dashLength, dashLength]);
|
|
32533
|
+
pourOp = pourOp.translate([pour.center.x, pour.center.y, zPos]);
|
|
32534
|
+
manifoldInstancesForCleanup.push(pourOp);
|
|
32535
|
+
} else if (pour.shape === "polygon") {
|
|
32536
|
+
if (pour.points.length < 3) continue;
|
|
32537
|
+
let pointsVec2 = pour.points.map((p) => [
|
|
32538
|
+
p.x,
|
|
32539
|
+
p.y
|
|
32540
|
+
]);
|
|
32541
|
+
if (arePointsClockwise4(pointsVec2)) {
|
|
32542
|
+
pointsVec2 = pointsVec2.reverse();
|
|
32645
32543
|
}
|
|
32646
|
-
|
|
32647
|
-
|
|
32648
|
-
|
|
32544
|
+
const crossSection = CrossSection.ofPolygons([pointsVec2]);
|
|
32545
|
+
manifoldInstancesForCleanup.push(crossSection);
|
|
32546
|
+
pourOp = Manifold.extrude(
|
|
32547
|
+
crossSection,
|
|
32548
|
+
pourThickness,
|
|
32549
|
+
0,
|
|
32550
|
+
// nDivisions
|
|
32551
|
+
0,
|
|
32552
|
+
// twistDegrees
|
|
32553
|
+
[1, 1],
|
|
32554
|
+
// scaleTop
|
|
32555
|
+
true
|
|
32556
|
+
// center extrusion
|
|
32557
|
+
).translate([0, 0, zPos]);
|
|
32558
|
+
manifoldInstancesForCleanup.push(pourOp);
|
|
32559
|
+
} else if (pour.shape === "brep") {
|
|
32560
|
+
const brepShape = pour.brep_shape;
|
|
32561
|
+
if (!brepShape || !brepShape.outer_ring) continue;
|
|
32562
|
+
let outerRingPoints = ringToPoints2(
|
|
32563
|
+
brepShape.outer_ring,
|
|
32564
|
+
SMOOTH_CIRCLE_SEGMENTS
|
|
32565
|
+
);
|
|
32566
|
+
if (arePointsClockwise4(outerRingPoints)) {
|
|
32567
|
+
outerRingPoints = outerRingPoints.reverse();
|
|
32649
32568
|
}
|
|
32650
|
-
|
|
32651
|
-
|
|
32652
|
-
|
|
32653
|
-
|
|
32654
|
-
|
|
32655
|
-
|
|
32656
|
-
|
|
32657
|
-
|
|
32658
|
-
|
|
32659
|
-
|
|
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);
|
|
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);
|
|
32681
32579
|
}
|
|
32682
|
-
|
|
32683
|
-
|
|
32684
|
-
|
|
32685
|
-
|
|
32686
|
-
|
|
32687
|
-
|
|
32688
|
-
|
|
32689
|
-
|
|
32690
|
-
|
|
32691
|
-
|
|
32692
|
-
|
|
32693
|
-
|
|
32694
|
-
|
|
32695
|
-
|
|
32696
|
-
|
|
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
|
-
);
|
|
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);
|
|
32722
32595
|
}
|
|
32723
|
-
|
|
32724
|
-
|
|
32725
|
-
|
|
32726
|
-
|
|
32727
|
-
|
|
32728
|
-
|
|
32729
|
-
|
|
32730
|
-
|
|
32731
|
-
|
|
32732
|
-
|
|
32733
|
-
|
|
32734
|
-
|
|
32735
|
-
|
|
32736
|
-
|
|
32737
|
-
|
|
32738
|
-
|
|
32739
|
-
|
|
32740
|
-
|
|
32741
|
-
|
|
32742
|
-
|
|
32743
|
-
|
|
32744
|
-
|
|
32745
|
-
|
|
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]);
|
|
32596
|
+
if (pourOp) {
|
|
32597
|
+
if (holeUnion) {
|
|
32598
|
+
const withHoles = pourOp.subtract(holeUnion);
|
|
32599
|
+
manifoldInstancesForCleanup.push(withHoles);
|
|
32600
|
+
pourOp = withHoles;
|
|
32601
|
+
}
|
|
32602
|
+
if (boardClipVolume) {
|
|
32603
|
+
const clipped = Manifold.intersection([pourOp, boardClipVolume]);
|
|
32604
|
+
manifoldInstancesForCleanup.push(clipped);
|
|
32605
|
+
pourOp = clipped;
|
|
32606
|
+
}
|
|
32607
|
+
const covered = pour.covered_with_solder_mask !== false;
|
|
32608
|
+
const pourColorArr = covered ? tracesMaterialColors[boardMaterial] ?? colors.fr4GreenSolderWithMask : colors.copper;
|
|
32609
|
+
const pourColor = new THREE19.Color(...pourColorArr);
|
|
32610
|
+
const threeGeom = manifoldMeshToThreeGeometry(pourOp.getMesh());
|
|
32611
|
+
copperPourGeoms.push({
|
|
32612
|
+
key: `coppour-${pour.pcb_copper_pour_id}`,
|
|
32613
|
+
geometry: threeGeom,
|
|
32614
|
+
color: pourColor
|
|
32615
|
+
});
|
|
32616
|
+
}
|
|
32617
|
+
}
|
|
32618
|
+
return { copperPourGeoms };
|
|
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,523 @@ 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: COPPER_COLOR2
|
|
33621
|
-
});
|
|
33622
|
-
}
|
|
33623
|
-
});
|
|
33624
|
-
return { viaBoardDrills, viaCopperGeoms };
|
|
33625
|
-
}
|
|
33626
|
-
|
|
33627
|
-
// src/utils/manifold/process-smt-pads.ts
|
|
33628
|
-
import { su as su12 } from "@tscircuit/circuit-json-util";
|
|
33629
|
-
import * as THREE23 from "three";
|
|
33630
|
-
var COPPER_COLOR3 = new THREE23.Color(...colors.copper);
|
|
33631
|
-
function processSmtPadsForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup, holeUnion, boardClipVolume) {
|
|
33632
|
-
const smtPadGeoms = [];
|
|
33633
|
-
const smtPads = su12(circuitJson).pcb_smtpad.list();
|
|
33634
|
-
smtPads.forEach((pad2, index2) => {
|
|
33635
|
-
const padBaseThickness = DEFAULT_SMT_PAD_THICKNESS;
|
|
33636
|
-
const zPos = pad2.layer === "bottom" ? -pcbThickness / 2 - BOARD_SURFACE_OFFSET.copper : pcbThickness / 2 + BOARD_SURFACE_OFFSET.copper;
|
|
33637
|
-
let padManifoldOp = createPadManifoldOp({
|
|
33638
|
-
Manifold,
|
|
33639
|
-
pad: pad2,
|
|
33640
|
-
padBaseThickness
|
|
33641
|
-
});
|
|
33642
|
-
if (padManifoldOp) {
|
|
33643
|
-
manifoldInstancesForCleanup.push(padManifoldOp);
|
|
33644
|
-
const translatedPad = padManifoldOp.translate([pad2.x, pad2.y, zPos]);
|
|
33645
|
-
manifoldInstancesForCleanup.push(translatedPad);
|
|
33646
|
-
let finalPadOp = translatedPad;
|
|
33647
|
-
if (holeUnion) {
|
|
33648
|
-
finalPadOp = translatedPad.subtract(holeUnion);
|
|
33649
|
-
manifoldInstancesForCleanup.push(finalPadOp);
|
|
33650
|
-
}
|
|
33651
|
-
if (boardClipVolume) {
|
|
33652
|
-
const clipped = Manifold.intersection([finalPadOp, boardClipVolume]);
|
|
33653
|
-
manifoldInstancesForCleanup.push(clipped);
|
|
33654
|
-
finalPadOp = clipped;
|
|
33655
|
-
}
|
|
33656
|
-
const threeGeom = manifoldMeshToThreeGeometry(finalPadOp.getMesh());
|
|
33657
|
-
smtPadGeoms.push({
|
|
33658
|
-
key: `smt_pad-${pad2.layer || "top"}-${pad2.pcb_smtpad_id || index2}`,
|
|
33639
|
+
const threeGeom = manifoldMeshToThreeGeometry(finalCopperOp.getMesh());
|
|
33640
|
+
viaCopperGeoms.push({
|
|
33641
|
+
key: `via-${via.pcb_via_id || index2}`,
|
|
33659
33642
|
geometry: threeGeom,
|
|
33660
33643
|
color: COPPER_COLOR3
|
|
33661
33644
|
});
|
|
33662
33645
|
}
|
|
33663
33646
|
});
|
|
33664
|
-
return {
|
|
33647
|
+
return { viaBoardDrills, viaCopperGeoms };
|
|
33665
33648
|
}
|
|
33666
33649
|
|
|
33667
|
-
// src/utils/
|
|
33668
|
-
var
|
|
33669
|
-
|
|
33670
|
-
|
|
33671
|
-
|
|
33672
|
-
|
|
33673
|
-
|
|
33674
|
-
|
|
33675
|
-
|
|
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;
|
|
33676
33673
|
}
|
|
33677
|
-
const
|
|
33678
|
-
|
|
33679
|
-
|
|
33680
|
-
|
|
33681
|
-
|
|
33682
|
-
|
|
33683
|
-
if (
|
|
33684
|
-
|
|
33685
|
-
|
|
33686
|
-
|
|
33687
|
-
|
|
33688
|
-
|
|
33689
|
-
|
|
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();
|
|
33690
33748
|
}
|
|
33691
|
-
|
|
33692
|
-
|
|
33693
|
-
|
|
33694
|
-
|
|
33695
|
-
|
|
33696
|
-
|
|
33697
|
-
|
|
33698
|
-
|
|
33699
|
-
|
|
33700
|
-
|
|
33701
|
-
|
|
33702
|
-
|
|
33703
|
-
|
|
33704
|
-
|
|
33749
|
+
ctx.restore();
|
|
33750
|
+
});
|
|
33751
|
+
rectsOnLayer.forEach((rect) => {
|
|
33752
|
+
const width10 = coerceDimensionToMm(rect.width, 0);
|
|
33753
|
+
const height10 = coerceDimensionToMm(rect.height, 0);
|
|
33754
|
+
if (width10 <= 0 || height10 <= 0) return;
|
|
33755
|
+
const centerXmm = parseDimensionToMm(rect.center?.x) ?? 0;
|
|
33756
|
+
const centerYmm = parseDimensionToMm(rect.center?.y) ?? 0;
|
|
33757
|
+
const canvasCenterX = canvasXFromPcb(centerXmm);
|
|
33758
|
+
const canvasCenterY = canvasYFromPcb(centerYmm);
|
|
33759
|
+
const rawRadius = extractRectBorderRadius(rect);
|
|
33760
|
+
const borderRadiusInput = typeof rawRadius === "string" ? parseDimensionToMm(rawRadius) : rawRadius;
|
|
33761
|
+
const borderRadiusMm = clampRectBorderRadius(
|
|
33762
|
+
width10,
|
|
33763
|
+
height10,
|
|
33764
|
+
borderRadiusInput
|
|
33705
33765
|
);
|
|
33706
|
-
|
|
33707
|
-
|
|
33708
|
-
|
|
33709
|
-
|
|
33710
|
-
|
|
33766
|
+
ctx.save();
|
|
33767
|
+
ctx.translate(canvasCenterX, canvasCenterY);
|
|
33768
|
+
const halfWidthPx = width10 / 2 * traceTextureResolution;
|
|
33769
|
+
const halfHeightPx = height10 / 2 * traceTextureResolution;
|
|
33770
|
+
const borderRadiusPx = Math.min(
|
|
33771
|
+
borderRadiusMm * traceTextureResolution,
|
|
33772
|
+
halfWidthPx,
|
|
33773
|
+
halfHeightPx
|
|
33774
|
+
);
|
|
33775
|
+
const hasStroke = rect.has_stroke ?? false;
|
|
33776
|
+
const isFilled = rect.is_filled ?? true;
|
|
33777
|
+
const isDashed = rect.is_stroke_dashed ?? false;
|
|
33778
|
+
const strokeWidthPx = hasStroke ? coerceDimensionToMm(rect.stroke_width, 0.1) * traceTextureResolution : 0;
|
|
33779
|
+
const drawRoundedRectPath = (x, y, rectWidth, rectHeight, radius) => {
|
|
33780
|
+
ctx.beginPath();
|
|
33781
|
+
if (radius <= 0) {
|
|
33782
|
+
ctx.rect(x, y, rectWidth, rectHeight);
|
|
33783
|
+
} else {
|
|
33784
|
+
const r = radius;
|
|
33785
|
+
const right = x + rectWidth;
|
|
33786
|
+
const bottom = y + rectHeight;
|
|
33787
|
+
ctx.moveTo(x + r, y);
|
|
33788
|
+
ctx.lineTo(right - r, y);
|
|
33789
|
+
ctx.quadraticCurveTo(right, y, right, y + r);
|
|
33790
|
+
ctx.lineTo(right, bottom - r);
|
|
33791
|
+
ctx.quadraticCurveTo(right, bottom, right - r, bottom);
|
|
33792
|
+
ctx.lineTo(x + r, bottom);
|
|
33793
|
+
ctx.quadraticCurveTo(x, bottom, x, bottom - r);
|
|
33794
|
+
ctx.lineTo(x, y + r);
|
|
33795
|
+
ctx.quadraticCurveTo(x, y, x + r, y);
|
|
33796
|
+
ctx.closePath();
|
|
33797
|
+
}
|
|
33798
|
+
};
|
|
33799
|
+
drawRoundedRectPath(
|
|
33800
|
+
-halfWidthPx,
|
|
33801
|
+
-halfHeightPx,
|
|
33802
|
+
halfWidthPx * 2,
|
|
33803
|
+
halfHeightPx * 2,
|
|
33804
|
+
borderRadiusPx
|
|
33805
|
+
);
|
|
33806
|
+
if (isFilled) {
|
|
33807
|
+
ctx.fill();
|
|
33808
|
+
}
|
|
33809
|
+
if (hasStroke && strokeWidthPx > 0) {
|
|
33810
|
+
ctx.lineWidth = strokeWidthPx;
|
|
33811
|
+
if (isDashed) {
|
|
33812
|
+
const dashLength = Math.max(strokeWidthPx * 2, 1);
|
|
33813
|
+
ctx.setLineDash([dashLength, dashLength]);
|
|
33814
|
+
}
|
|
33815
|
+
ctx.stroke();
|
|
33816
|
+
if (isDashed) {
|
|
33817
|
+
ctx.setLineDash([]);
|
|
33818
|
+
}
|
|
33819
|
+
}
|
|
33820
|
+
ctx.restore();
|
|
33821
|
+
});
|
|
33822
|
+
textsOnLayer.forEach((textS) => {
|
|
33823
|
+
const fontSize = textS.font_size || 0.25;
|
|
33824
|
+
const textStrokeWidth = Math.min(Math.max(0.01, fontSize * 0.1), fontSize * 0.05) * traceTextureResolution;
|
|
33825
|
+
ctx.lineWidth = textStrokeWidth;
|
|
33826
|
+
ctx.lineCap = "butt";
|
|
33827
|
+
ctx.lineJoin = "miter";
|
|
33828
|
+
const rawTextOutlines = (0, import_text2.vectorText)({
|
|
33829
|
+
height: fontSize * 0.45,
|
|
33830
|
+
input: textS.text
|
|
33831
|
+
});
|
|
33832
|
+
const processedTextOutlines = [];
|
|
33833
|
+
rawTextOutlines.forEach((outline) => {
|
|
33834
|
+
if (outline.length === 29) {
|
|
33835
|
+
processedTextOutlines.push(
|
|
33836
|
+
outline.slice(0, 15)
|
|
33837
|
+
);
|
|
33838
|
+
processedTextOutlines.push(
|
|
33839
|
+
outline.slice(14, 29)
|
|
33840
|
+
);
|
|
33841
|
+
} else if (outline.length === 17) {
|
|
33842
|
+
processedTextOutlines.push(
|
|
33843
|
+
outline.slice(0, 10)
|
|
33844
|
+
);
|
|
33845
|
+
processedTextOutlines.push(
|
|
33846
|
+
outline.slice(9, 17)
|
|
33847
|
+
);
|
|
33848
|
+
} else {
|
|
33849
|
+
processedTextOutlines.push(outline);
|
|
33850
|
+
}
|
|
33851
|
+
});
|
|
33852
|
+
const points = processedTextOutlines.flat();
|
|
33853
|
+
const textBounds = {
|
|
33854
|
+
minX: points.length > 0 ? Math.min(...points.map((p) => p[0])) : 0,
|
|
33855
|
+
maxX: points.length > 0 ? Math.max(...points.map((p) => p[0])) : 0,
|
|
33856
|
+
minY: points.length > 0 ? Math.min(...points.map((p) => p[1])) : 0,
|
|
33857
|
+
maxY: points.length > 0 ? Math.max(...points.map((p) => p[1])) : 0
|
|
33858
|
+
};
|
|
33859
|
+
const textCenterX = (textBounds.minX + textBounds.maxX) / 2;
|
|
33860
|
+
const textCenterY = (textBounds.minY + textBounds.maxY) / 2;
|
|
33861
|
+
let xOff = -textCenterX;
|
|
33862
|
+
let yOff = -textCenterY;
|
|
33863
|
+
const alignment = textS.anchor_alignment || "center";
|
|
33864
|
+
if (alignment.includes("left")) {
|
|
33865
|
+
xOff = -textBounds.minX;
|
|
33866
|
+
} else if (alignment.includes("right")) {
|
|
33867
|
+
xOff = -textBounds.maxX;
|
|
33868
|
+
}
|
|
33869
|
+
if (alignment.includes("top")) {
|
|
33870
|
+
yOff = -textBounds.maxY;
|
|
33871
|
+
} else if (alignment.includes("bottom")) {
|
|
33872
|
+
yOff = -textBounds.minY;
|
|
33873
|
+
}
|
|
33874
|
+
const transformMatrices = [];
|
|
33875
|
+
let rotationDeg = textS.ccw_rotation ?? 0;
|
|
33876
|
+
if (textS.layer === "bottom") {
|
|
33877
|
+
transformMatrices.push(
|
|
33878
|
+
translate4(textCenterX, textCenterY),
|
|
33879
|
+
{ a: -1, b: 0, c: 0, d: 1, e: 0, f: 0 },
|
|
33880
|
+
translate4(-textCenterX, -textCenterY)
|
|
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
|
-
|
|
33883
|
-
|
|
33884
|
-
|
|
33885
|
-
|
|
33886
|
-
|
|
33887
|
-
|
|
33888
|
-
|
|
33889
|
-
|
|
33890
|
-
|
|
33891
|
-
|
|
34029
|
+
});
|
|
34030
|
+
const pcbCopperPours = su13(circuitJson).pcb_copper_pour.list();
|
|
34031
|
+
pcbCopperPours.forEach((pour) => {
|
|
34032
|
+
if (pour.layer !== layer) return;
|
|
34033
|
+
if (pour.covered_with_solder_mask !== false) return;
|
|
34034
|
+
if (pour.shape === "rect") {
|
|
34035
|
+
const centerX = canvasXFromPcb(pour.center.x);
|
|
34036
|
+
const centerY = canvasYFromPcb(pour.center.y);
|
|
34037
|
+
const width10 = pour.width * traceTextureResolution;
|
|
34038
|
+
const height10 = pour.height * traceTextureResolution;
|
|
34039
|
+
ctx.fillRect(centerX - width10 / 2, centerY - height10 / 2, width10, height10);
|
|
34040
|
+
} else if (pour.shape === "polygon" && pour.points) {
|
|
34041
|
+
ctx.beginPath();
|
|
34042
|
+
pour.points.forEach((point2, index2) => {
|
|
34043
|
+
const px = canvasXFromPcb(point2.x);
|
|
34044
|
+
const py = canvasYFromPcb(point2.y);
|
|
34045
|
+
if (index2 === 0) {
|
|
34046
|
+
ctx.moveTo(px, py);
|
|
34047
|
+
} else {
|
|
34048
|
+
ctx.lineTo(px, py);
|
|
34049
|
+
}
|
|
33892
34050
|
});
|
|
34051
|
+
ctx.closePath();
|
|
34052
|
+
ctx.fill();
|
|
33893
34053
|
}
|
|
33894
|
-
}
|
|
33895
|
-
|
|
34054
|
+
});
|
|
34055
|
+
ctx.globalCompositeOperation = "source-over";
|
|
34056
|
+
const texture = new THREE24.CanvasTexture(canvas);
|
|
34057
|
+
texture.generateMipmaps = true;
|
|
34058
|
+
texture.minFilter = THREE24.LinearMipmapLinearFilter;
|
|
34059
|
+
texture.magFilter = THREE24.LinearFilter;
|
|
34060
|
+
texture.anisotropy = 16;
|
|
34061
|
+
texture.needsUpdate = true;
|
|
34062
|
+
return texture;
|
|
33896
34063
|
}
|
|
33897
34064
|
|
|
33898
|
-
// src/utils/
|
|
33899
|
-
import
|
|
33900
|
-
|
|
33901
|
-
|
|
33902
|
-
|
|
33903
|
-
|
|
33904
|
-
|
|
33905
|
-
|
|
33906
|
-
|
|
33907
|
-
|
|
34065
|
+
// src/utils/trace-texture.ts
|
|
34066
|
+
import * as THREE25 from "three";
|
|
34067
|
+
import { su as su14 } from "@tscircuit/circuit-json-util";
|
|
34068
|
+
function isWireRoutePoint(point2) {
|
|
34069
|
+
return point2 && point2.route_type === "wire" && typeof point2.layer === "string" && typeof point2.width === "number";
|
|
34070
|
+
}
|
|
34071
|
+
function createTraceTextureForLayer({
|
|
34072
|
+
layer,
|
|
34073
|
+
circuitJson,
|
|
34074
|
+
boardData,
|
|
34075
|
+
traceColor,
|
|
34076
|
+
traceTextureResolution
|
|
34077
|
+
}) {
|
|
34078
|
+
const pcbTraces = su14(circuitJson).pcb_trace.list();
|
|
34079
|
+
const allPcbVias = su14(circuitJson).pcb_via.list();
|
|
34080
|
+
const allPcbPlatedHoles = su14(
|
|
34081
|
+
circuitJson
|
|
34082
|
+
).pcb_plated_hole.list();
|
|
34083
|
+
const tracesOnLayer = pcbTraces.filter(
|
|
34084
|
+
(t) => t.route.some((p) => isWireRoutePoint(p) && p.layer === layer)
|
|
34085
|
+
);
|
|
34086
|
+
if (tracesOnLayer.length === 0) return null;
|
|
34087
|
+
const canvas = document.createElement("canvas");
|
|
34088
|
+
const canvasWidth = Math.floor(boardData.width * traceTextureResolution);
|
|
34089
|
+
const canvasHeight = Math.floor(boardData.height * traceTextureResolution);
|
|
34090
|
+
canvas.width = canvasWidth;
|
|
34091
|
+
canvas.height = canvasHeight;
|
|
34092
|
+
const ctx = canvas.getContext("2d");
|
|
34093
|
+
if (!ctx) return null;
|
|
34094
|
+
if (layer === "bottom") {
|
|
34095
|
+
ctx.translate(0, canvasHeight);
|
|
34096
|
+
ctx.scale(1, -1);
|
|
33908
34097
|
}
|
|
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
|
-
);
|
|
34098
|
+
tracesOnLayer.forEach((trace) => {
|
|
34099
|
+
let firstPoint = true;
|
|
34100
|
+
ctx.beginPath();
|
|
34101
|
+
ctx.strokeStyle = traceColor;
|
|
34102
|
+
ctx.lineCap = "round";
|
|
34103
|
+
ctx.lineJoin = "round";
|
|
34104
|
+
let currentLineWidth = 0;
|
|
34105
|
+
for (const point2 of trace.route) {
|
|
34106
|
+
if (!isWireRoutePoint(point2) || point2.layer !== layer) {
|
|
34107
|
+
if (!firstPoint) ctx.stroke();
|
|
34108
|
+
firstPoint = true;
|
|
33999
34109
|
continue;
|
|
34110
|
+
}
|
|
34111
|
+
const pcbX = point2.x;
|
|
34112
|
+
const pcbY = point2.y;
|
|
34113
|
+
currentLineWidth = point2.width * traceTextureResolution;
|
|
34114
|
+
ctx.lineWidth = currentLineWidth;
|
|
34115
|
+
const canvasX = (pcbX - boardData.center.x + boardData.width / 2) * traceTextureResolution;
|
|
34116
|
+
const canvasY = (-(pcbY - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
|
|
34117
|
+
if (firstPoint) {
|
|
34118
|
+
ctx.moveTo(canvasX, canvasY);
|
|
34119
|
+
firstPoint = false;
|
|
34120
|
+
} else {
|
|
34121
|
+
ctx.lineTo(canvasX, canvasY);
|
|
34122
|
+
}
|
|
34000
34123
|
}
|
|
34001
|
-
if (
|
|
34002
|
-
|
|
34124
|
+
if (!firstPoint) {
|
|
34125
|
+
ctx.stroke();
|
|
34003
34126
|
}
|
|
34004
|
-
}
|
|
34005
|
-
|
|
34127
|
+
});
|
|
34128
|
+
ctx.globalCompositeOperation = "destination-out";
|
|
34129
|
+
ctx.fillStyle = "black";
|
|
34130
|
+
allPcbVias.forEach((via) => {
|
|
34131
|
+
const canvasX = (via.x - boardData.center.x + boardData.width / 2) * traceTextureResolution;
|
|
34132
|
+
const canvasY = (-(via.y - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
|
|
34133
|
+
const canvasRadius = via.outer_diameter / 2 * traceTextureResolution;
|
|
34134
|
+
ctx.beginPath();
|
|
34135
|
+
ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI, false);
|
|
34136
|
+
ctx.fill();
|
|
34137
|
+
});
|
|
34138
|
+
allPcbPlatedHoles.forEach((ph) => {
|
|
34139
|
+
if (ph.layers.includes(layer) && ph.shape === "circle") {
|
|
34140
|
+
const canvasX = (ph.x - boardData.center.x + boardData.width / 2) * traceTextureResolution;
|
|
34141
|
+
const canvasY = (-(ph.y - boardData.center.y) + boardData.height / 2) * traceTextureResolution;
|
|
34142
|
+
const canvasRadius = ph.outer_diameter / 2 * traceTextureResolution;
|
|
34143
|
+
ctx.beginPath();
|
|
34144
|
+
ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI, false);
|
|
34145
|
+
ctx.fill();
|
|
34146
|
+
}
|
|
34147
|
+
});
|
|
34148
|
+
ctx.globalCompositeOperation = "source-over";
|
|
34149
|
+
const texture = new THREE25.CanvasTexture(canvas);
|
|
34150
|
+
texture.generateMipmaps = true;
|
|
34151
|
+
texture.minFilter = THREE25.LinearMipmapLinearFilter;
|
|
34152
|
+
texture.magFilter = THREE25.LinearFilter;
|
|
34153
|
+
texture.anisotropy = 16;
|
|
34154
|
+
texture.needsUpdate = true;
|
|
34155
|
+
return texture;
|
|
34006
34156
|
}
|
|
34007
34157
|
|
|
34008
34158
|
// src/hooks/useManifoldBoardBuilder.ts
|
|
@@ -34017,7 +34167,7 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
|
|
|
34017
34167
|
const panels = circuitJson.filter(
|
|
34018
34168
|
(e) => e.type === "pcb_panel"
|
|
34019
34169
|
);
|
|
34020
|
-
const boards =
|
|
34170
|
+
const boards = su15(circuitJson).pcb_board.list();
|
|
34021
34171
|
if (panels.length > 0) {
|
|
34022
34172
|
const panel = panels[0];
|
|
34023
34173
|
return {
|
|
@@ -34036,7 +34186,7 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
|
|
|
34036
34186
|
return boardsNotInPanel.length > 0 ? boardsNotInPanel[0] : null;
|
|
34037
34187
|
}, [circuitJson]);
|
|
34038
34188
|
const isFauxBoard = useMemo19(() => {
|
|
34039
|
-
const boards =
|
|
34189
|
+
const boards = su15(circuitJson).pcb_board.list();
|
|
34040
34190
|
return boards.length > 0 && boards[0].pcb_board_id === "faux-board";
|
|
34041
34191
|
}, [circuitJson]);
|
|
34042
34192
|
useEffect22(() => {
|
|
@@ -34171,7 +34321,7 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
|
|
|
34171
34321
|
{
|
|
34172
34322
|
key: "plated-holes-union",
|
|
34173
34323
|
geometry: cutPlatedGeom,
|
|
34174
|
-
color: new
|
|
34324
|
+
color: new THREE26.Color(
|
|
34175
34325
|
colors.copper[0],
|
|
34176
34326
|
colors.copper[1],
|
|
34177
34327
|
colors.copper[2]
|
|
@@ -34201,7 +34351,7 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
|
|
|
34201
34351
|
const matColorArray = boardMaterialColors[boardData.material] ?? colors.fr4Green;
|
|
34202
34352
|
currentGeoms.board = {
|
|
34203
34353
|
geometry: finalBoardGeom,
|
|
34204
|
-
color: new
|
|
34354
|
+
color: new THREE26.Color(
|
|
34205
34355
|
matColorArray[0],
|
|
34206
34356
|
matColorArray[1],
|
|
34207
34357
|
matColorArray[2]
|
|
@@ -34262,6 +34412,22 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
|
|
|
34262
34412
|
silkscreenColor,
|
|
34263
34413
|
traceTextureResolution: TRACE_TEXTURE_RESOLUTION
|
|
34264
34414
|
});
|
|
34415
|
+
const soldermaskColorArr = tracesMaterialColors[boardData.material] ?? colors.fr4GreenSolderWithMask;
|
|
34416
|
+
const soldermaskColor = `rgb(${Math.round(soldermaskColorArr[0] * 255)}, ${Math.round(soldermaskColorArr[1] * 255)}, ${Math.round(soldermaskColorArr[2] * 255)})`;
|
|
34417
|
+
currentTextures.topSoldermask = createSoldermaskTextureForLayer({
|
|
34418
|
+
layer: "top",
|
|
34419
|
+
circuitJson,
|
|
34420
|
+
boardData,
|
|
34421
|
+
soldermaskColor,
|
|
34422
|
+
traceTextureResolution: TRACE_TEXTURE_RESOLUTION
|
|
34423
|
+
});
|
|
34424
|
+
currentTextures.bottomSoldermask = createSoldermaskTextureForLayer({
|
|
34425
|
+
layer: "bottom",
|
|
34426
|
+
circuitJson,
|
|
34427
|
+
boardData,
|
|
34428
|
+
soldermaskColor,
|
|
34429
|
+
traceTextureResolution: TRACE_TEXTURE_RESOLUTION
|
|
34430
|
+
});
|
|
34265
34431
|
setTextures(currentTextures);
|
|
34266
34432
|
} catch (e) {
|
|
34267
34433
|
console.error("Error processing geometry with Manifold in hook:", e);
|
|
@@ -34290,11 +34456,11 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson) => {
|
|
|
34290
34456
|
};
|
|
34291
34457
|
|
|
34292
34458
|
// src/utils/manifold/create-three-geometry-meshes.ts
|
|
34293
|
-
import * as
|
|
34459
|
+
import * as THREE28 from "three";
|
|
34294
34460
|
|
|
34295
34461
|
// src/utils/create-board-material.ts
|
|
34296
|
-
import * as
|
|
34297
|
-
var DEFAULT_SIDE =
|
|
34462
|
+
import * as THREE27 from "three";
|
|
34463
|
+
var DEFAULT_SIDE = THREE27.DoubleSide;
|
|
34298
34464
|
var createBoardMaterial = ({
|
|
34299
34465
|
material,
|
|
34300
34466
|
color,
|
|
@@ -34302,7 +34468,7 @@ var createBoardMaterial = ({
|
|
|
34302
34468
|
isFaux = false
|
|
34303
34469
|
}) => {
|
|
34304
34470
|
if (material === "fr4") {
|
|
34305
|
-
return new
|
|
34471
|
+
return new THREE27.MeshPhysicalMaterial({
|
|
34306
34472
|
color,
|
|
34307
34473
|
side,
|
|
34308
34474
|
metalness: 0,
|
|
@@ -34316,7 +34482,7 @@ var createBoardMaterial = ({
|
|
|
34316
34482
|
flatShading: true
|
|
34317
34483
|
});
|
|
34318
34484
|
}
|
|
34319
|
-
return new
|
|
34485
|
+
return new THREE27.MeshStandardMaterial({
|
|
34320
34486
|
color,
|
|
34321
34487
|
side,
|
|
34322
34488
|
flatShading: true,
|
|
@@ -34332,12 +34498,12 @@ function createGeometryMeshes(geoms) {
|
|
|
34332
34498
|
const meshes = [];
|
|
34333
34499
|
if (!geoms) return meshes;
|
|
34334
34500
|
if (geoms.board && geoms.board.geometry) {
|
|
34335
|
-
const mesh = new
|
|
34501
|
+
const mesh = new THREE28.Mesh(
|
|
34336
34502
|
geoms.board.geometry,
|
|
34337
34503
|
createBoardMaterial({
|
|
34338
34504
|
material: geoms.board.material,
|
|
34339
34505
|
color: geoms.board.color,
|
|
34340
|
-
side:
|
|
34506
|
+
side: THREE28.DoubleSide,
|
|
34341
34507
|
isFaux: geoms.board.isFaux
|
|
34342
34508
|
})
|
|
34343
34509
|
);
|
|
@@ -34347,11 +34513,11 @@ function createGeometryMeshes(geoms) {
|
|
|
34347
34513
|
const createMeshesFromArray = (geomArray) => {
|
|
34348
34514
|
if (geomArray) {
|
|
34349
34515
|
geomArray.forEach((comp) => {
|
|
34350
|
-
const mesh = new
|
|
34516
|
+
const mesh = new THREE28.Mesh(
|
|
34351
34517
|
comp.geometry,
|
|
34352
|
-
new
|
|
34518
|
+
new THREE28.MeshStandardMaterial({
|
|
34353
34519
|
color: comp.color,
|
|
34354
|
-
side:
|
|
34520
|
+
side: THREE28.DoubleSide,
|
|
34355
34521
|
flatShading: true,
|
|
34356
34522
|
// Consistent with board
|
|
34357
34523
|
polygonOffset: true,
|
|
@@ -34372,21 +34538,24 @@ function createGeometryMeshes(geoms) {
|
|
|
34372
34538
|
}
|
|
34373
34539
|
|
|
34374
34540
|
// src/utils/manifold/create-three-texture-meshes.ts
|
|
34375
|
-
import * as
|
|
34541
|
+
import * as THREE29 from "three";
|
|
34376
34542
|
function createTextureMeshes(textures, boardData, pcbThickness) {
|
|
34377
34543
|
const meshes = [];
|
|
34378
34544
|
if (!textures || !boardData || pcbThickness === null) return meshes;
|
|
34379
|
-
const createTexturePlane = (texture, yOffset, isBottomLayer, keySuffix) => {
|
|
34545
|
+
const createTexturePlane = (texture, yOffset, isBottomLayer, keySuffix, usePolygonOffset = false) => {
|
|
34380
34546
|
if (!texture) return null;
|
|
34381
|
-
const planeGeom = new
|
|
34382
|
-
const material = new
|
|
34547
|
+
const planeGeom = new THREE29.PlaneGeometry(boardData.width, boardData.height);
|
|
34548
|
+
const material = new THREE29.MeshBasicMaterial({
|
|
34383
34549
|
map: texture,
|
|
34384
34550
|
transparent: true,
|
|
34385
|
-
side:
|
|
34386
|
-
depthWrite: false
|
|
34551
|
+
side: THREE29.DoubleSide,
|
|
34552
|
+
depthWrite: false,
|
|
34387
34553
|
// Important for layers to avoid z-fighting issues with board itself
|
|
34554
|
+
polygonOffset: usePolygonOffset,
|
|
34555
|
+
polygonOffsetFactor: usePolygonOffset ? -1 : 0,
|
|
34556
|
+
polygonOffsetUnits: usePolygonOffset ? -1 : 0
|
|
34388
34557
|
});
|
|
34389
|
-
const mesh = new
|
|
34558
|
+
const mesh = new THREE29.Mesh(planeGeom, material);
|
|
34390
34559
|
mesh.position.set(boardData.center.x, boardData.center.y, yOffset);
|
|
34391
34560
|
if (isBottomLayer) {
|
|
34392
34561
|
mesh.rotation.set(Math.PI, 0, 0);
|
|
@@ -34425,6 +34594,26 @@ function createTextureMeshes(textures, boardData, pcbThickness) {
|
|
|
34425
34594
|
"silkscreen"
|
|
34426
34595
|
);
|
|
34427
34596
|
if (bottomSilkscreenMesh) meshes.push(bottomSilkscreenMesh);
|
|
34597
|
+
const topSoldermaskMesh = createTexturePlane(
|
|
34598
|
+
textures.topSoldermask,
|
|
34599
|
+
pcbThickness / 2 + 8e-4,
|
|
34600
|
+
// Just above board surface, below traces
|
|
34601
|
+
false,
|
|
34602
|
+
"soldermask",
|
|
34603
|
+
true
|
|
34604
|
+
// Enable polygon offset
|
|
34605
|
+
);
|
|
34606
|
+
if (topSoldermaskMesh) meshes.push(topSoldermaskMesh);
|
|
34607
|
+
const bottomSoldermaskMesh = createTexturePlane(
|
|
34608
|
+
textures.bottomSoldermask,
|
|
34609
|
+
-pcbThickness / 2 - 8e-4,
|
|
34610
|
+
// Just below board surface (bottom side)
|
|
34611
|
+
true,
|
|
34612
|
+
"soldermask",
|
|
34613
|
+
true
|
|
34614
|
+
// Enable polygon offset
|
|
34615
|
+
);
|
|
34616
|
+
if (bottomSoldermaskMesh) meshes.push(bottomSoldermaskMesh);
|
|
34428
34617
|
return meshes;
|
|
34429
34618
|
}
|
|
34430
34619
|
|
|
@@ -34469,6 +34658,10 @@ var BoardMeshes = ({
|
|
|
34469
34658
|
shouldShow = visibility.topSilkscreen;
|
|
34470
34659
|
} else if (mesh.name.includes("bottom-silkscreen")) {
|
|
34471
34660
|
shouldShow = visibility.bottomSilkscreen;
|
|
34661
|
+
} else if (mesh.name.includes("top-soldermask")) {
|
|
34662
|
+
shouldShow = visibility.topMask;
|
|
34663
|
+
} else if (mesh.name.includes("bottom-soldermask")) {
|
|
34664
|
+
shouldShow = visibility.bottomMask;
|
|
34472
34665
|
}
|
|
34473
34666
|
if (shouldShow) {
|
|
34474
34667
|
rootObject.add(mesh);
|
|
@@ -34581,7 +34774,7 @@ try {
|
|
|
34581
34774
|
[textures, boardData, pcbThickness]
|
|
34582
34775
|
);
|
|
34583
34776
|
const cadComponents = useMemo20(
|
|
34584
|
-
() =>
|
|
34777
|
+
() => su16(circuitJson).cad_component.list(),
|
|
34585
34778
|
[circuitJson]
|
|
34586
34779
|
);
|
|
34587
34780
|
const boardDimensions = useMemo20(() => {
|
|
@@ -40696,6 +40889,48 @@ var AppearanceMenu = () => {
|
|
|
40696
40889
|
]
|
|
40697
40890
|
}
|
|
40698
40891
|
),
|
|
40892
|
+
/* @__PURE__ */ jsxs8(
|
|
40893
|
+
Item22,
|
|
40894
|
+
{
|
|
40895
|
+
style: {
|
|
40896
|
+
...itemStyles,
|
|
40897
|
+
backgroundColor: hoveredItem === "topMask" ? "#404040" : "transparent"
|
|
40898
|
+
},
|
|
40899
|
+
onSelect: (e) => e.preventDefault(),
|
|
40900
|
+
onPointerDown: (e) => {
|
|
40901
|
+
e.preventDefault();
|
|
40902
|
+
setLayerVisibility("topMask", !visibility.topMask);
|
|
40903
|
+
},
|
|
40904
|
+
onMouseEnter: () => setHoveredItem("topMask"),
|
|
40905
|
+
onMouseLeave: () => setHoveredItem(null),
|
|
40906
|
+
onTouchStart: () => setHoveredItem("topMask"),
|
|
40907
|
+
children: [
|
|
40908
|
+
/* @__PURE__ */ jsx34("span", { style: iconContainerStyles, children: visibility.topMask && /* @__PURE__ */ jsx34(CheckIcon, {}) }),
|
|
40909
|
+
/* @__PURE__ */ jsx34("span", { style: { display: "flex", alignItems: "center" }, children: "Top Soldermask" })
|
|
40910
|
+
]
|
|
40911
|
+
}
|
|
40912
|
+
),
|
|
40913
|
+
/* @__PURE__ */ jsxs8(
|
|
40914
|
+
Item22,
|
|
40915
|
+
{
|
|
40916
|
+
style: {
|
|
40917
|
+
...itemStyles,
|
|
40918
|
+
backgroundColor: hoveredItem === "bottomMask" ? "#404040" : "transparent"
|
|
40919
|
+
},
|
|
40920
|
+
onSelect: (e) => e.preventDefault(),
|
|
40921
|
+
onPointerDown: (e) => {
|
|
40922
|
+
e.preventDefault();
|
|
40923
|
+
setLayerVisibility("bottomMask", !visibility.bottomMask);
|
|
40924
|
+
},
|
|
40925
|
+
onMouseEnter: () => setHoveredItem("bottomMask"),
|
|
40926
|
+
onMouseLeave: () => setHoveredItem(null),
|
|
40927
|
+
onTouchStart: () => setHoveredItem("bottomMask"),
|
|
40928
|
+
children: [
|
|
40929
|
+
/* @__PURE__ */ jsx34("span", { style: iconContainerStyles, children: visibility.bottomMask && /* @__PURE__ */ jsx34(CheckIcon, {}) }),
|
|
40930
|
+
/* @__PURE__ */ jsx34("span", { style: { display: "flex", alignItems: "center" }, children: "Bottom Soldermask" })
|
|
40931
|
+
]
|
|
40932
|
+
}
|
|
40933
|
+
),
|
|
40699
40934
|
/* @__PURE__ */ jsxs8(
|
|
40700
40935
|
Item22,
|
|
40701
40936
|
{
|
|
@@ -41535,7 +41770,7 @@ var CadViewerInner = (props) => {
|
|
|
41535
41770
|
);
|
|
41536
41771
|
};
|
|
41537
41772
|
var CadViewer = (props) => {
|
|
41538
|
-
const defaultTarget = useMemo28(() => new
|
|
41773
|
+
const defaultTarget = useMemo28(() => new THREE30.Vector3(0, 0, 0), []);
|
|
41539
41774
|
const initialCameraPosition = useMemo28(
|
|
41540
41775
|
() => [5, -5, 5],
|
|
41541
41776
|
[]
|
|
@@ -41552,12 +41787,12 @@ var CadViewer = (props) => {
|
|
|
41552
41787
|
|
|
41553
41788
|
// src/convert-circuit-json-to-3d-svg.ts
|
|
41554
41789
|
var import_debug = __toESM(require_browser(), 1);
|
|
41555
|
-
import { su as
|
|
41556
|
-
import * as
|
|
41790
|
+
import { su as su17 } from "@tscircuit/circuit-json-util";
|
|
41791
|
+
import * as THREE34 from "three";
|
|
41557
41792
|
import { SVGRenderer } from "three/examples/jsm/renderers/SVGRenderer.js";
|
|
41558
41793
|
|
|
41559
41794
|
// src/utils/create-geometry-from-polygons.ts
|
|
41560
|
-
import * as
|
|
41795
|
+
import * as THREE31 from "three";
|
|
41561
41796
|
import { BufferGeometry as BufferGeometry3, Float32BufferAttribute as Float32BufferAttribute2 } from "three";
|
|
41562
41797
|
function createGeometryFromPolygons(polygons) {
|
|
41563
41798
|
const geometry = new BufferGeometry3();
|
|
@@ -41571,12 +41806,12 @@ function createGeometryFromPolygons(polygons) {
|
|
|
41571
41806
|
...polygon3.vertices[i + 1]
|
|
41572
41807
|
// Third vertex
|
|
41573
41808
|
);
|
|
41574
|
-
const v1 = new
|
|
41575
|
-
const v2 = new
|
|
41576
|
-
const v3 = new
|
|
41577
|
-
const normal = new
|
|
41578
|
-
new
|
|
41579
|
-
new
|
|
41809
|
+
const v1 = new THREE31.Vector3(...polygon3.vertices[0]);
|
|
41810
|
+
const v2 = new THREE31.Vector3(...polygon3.vertices[i]);
|
|
41811
|
+
const v3 = new THREE31.Vector3(...polygon3.vertices[i + 1]);
|
|
41812
|
+
const normal = new THREE31.Vector3().crossVectors(
|
|
41813
|
+
new THREE31.Vector3().subVectors(v2, v1),
|
|
41814
|
+
new THREE31.Vector3().subVectors(v3, v1)
|
|
41580
41815
|
).normalize();
|
|
41581
41816
|
normals.push(
|
|
41582
41817
|
normal.x,
|
|
@@ -41600,10 +41835,10 @@ function createGeometryFromPolygons(polygons) {
|
|
|
41600
41835
|
var import_modeling2 = __toESM(require_src(), 1);
|
|
41601
41836
|
var import_jscad_planner2 = __toESM(require_dist(), 1);
|
|
41602
41837
|
var jscadModeling2 = __toESM(require_src(), 1);
|
|
41603
|
-
import * as
|
|
41838
|
+
import * as THREE33 from "three";
|
|
41604
41839
|
|
|
41605
41840
|
// src/utils/load-model.ts
|
|
41606
|
-
import * as
|
|
41841
|
+
import * as THREE32 from "three";
|
|
41607
41842
|
import { GLTFLoader as GLTFLoader2 } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
41608
41843
|
import { OBJLoader as OBJLoader2 } from "three/examples/jsm/loaders/OBJLoader.js";
|
|
41609
41844
|
import { STLLoader as STLLoader2 } from "three/examples/jsm/loaders/STLLoader.js";
|
|
@@ -41611,12 +41846,12 @@ async function load3DModel(url) {
|
|
|
41611
41846
|
if (url.endsWith(".stl")) {
|
|
41612
41847
|
const loader = new STLLoader2();
|
|
41613
41848
|
const geometry = await loader.loadAsync(url);
|
|
41614
|
-
const material = new
|
|
41849
|
+
const material = new THREE32.MeshStandardMaterial({
|
|
41615
41850
|
color: 8947848,
|
|
41616
41851
|
metalness: 0.5,
|
|
41617
41852
|
roughness: 0.5
|
|
41618
41853
|
});
|
|
41619
|
-
return new
|
|
41854
|
+
return new THREE32.Mesh(geometry, material);
|
|
41620
41855
|
}
|
|
41621
41856
|
if (url.endsWith(".obj")) {
|
|
41622
41857
|
const loader = new OBJLoader2();
|
|
@@ -41649,9 +41884,9 @@ async function renderComponent(component, scene) {
|
|
|
41649
41884
|
}
|
|
41650
41885
|
if (component.rotation) {
|
|
41651
41886
|
model.rotation.set(
|
|
41652
|
-
|
|
41653
|
-
|
|
41654
|
-
|
|
41887
|
+
THREE33.MathUtils.degToRad(component.rotation.x ?? 0),
|
|
41888
|
+
THREE33.MathUtils.degToRad(component.rotation.y ?? 0),
|
|
41889
|
+
THREE33.MathUtils.degToRad(component.rotation.z ?? 0)
|
|
41655
41890
|
);
|
|
41656
41891
|
}
|
|
41657
41892
|
scene.add(model);
|
|
@@ -41665,13 +41900,13 @@ async function renderComponent(component, scene) {
|
|
|
41665
41900
|
);
|
|
41666
41901
|
if (jscadObject && (jscadObject.polygons || jscadObject.sides)) {
|
|
41667
41902
|
const threeGeom = convertCSGToThreeGeom(jscadObject);
|
|
41668
|
-
const material2 = new
|
|
41903
|
+
const material2 = new THREE33.MeshStandardMaterial({
|
|
41669
41904
|
color: 8947848,
|
|
41670
41905
|
metalness: 0.5,
|
|
41671
41906
|
roughness: 0.5,
|
|
41672
|
-
side:
|
|
41907
|
+
side: THREE33.DoubleSide
|
|
41673
41908
|
});
|
|
41674
|
-
const mesh2 = new
|
|
41909
|
+
const mesh2 = new THREE33.Mesh(threeGeom, material2);
|
|
41675
41910
|
if (component.position) {
|
|
41676
41911
|
mesh2.position.set(
|
|
41677
41912
|
component.position.x ?? 0,
|
|
@@ -41681,9 +41916,9 @@ async function renderComponent(component, scene) {
|
|
|
41681
41916
|
}
|
|
41682
41917
|
if (component.rotation) {
|
|
41683
41918
|
mesh2.rotation.set(
|
|
41684
|
-
|
|
41685
|
-
|
|
41686
|
-
|
|
41919
|
+
THREE33.MathUtils.degToRad(component.rotation.x ?? 0),
|
|
41920
|
+
THREE33.MathUtils.degToRad(component.rotation.y ?? 0),
|
|
41921
|
+
THREE33.MathUtils.degToRad(component.rotation.z ?? 0)
|
|
41687
41922
|
);
|
|
41688
41923
|
}
|
|
41689
41924
|
scene.add(mesh2);
|
|
@@ -41700,17 +41935,17 @@ async function renderComponent(component, scene) {
|
|
|
41700
41935
|
if (!geom || !geom.polygons && !geom.sides) {
|
|
41701
41936
|
continue;
|
|
41702
41937
|
}
|
|
41703
|
-
const color = new
|
|
41938
|
+
const color = new THREE33.Color(geomInfo.color);
|
|
41704
41939
|
color.convertLinearToSRGB();
|
|
41705
41940
|
const geomWithColor = { ...geom, color: [color.r, color.g, color.b] };
|
|
41706
41941
|
const threeGeom = convertCSGToThreeGeom(geomWithColor);
|
|
41707
|
-
const material2 = new
|
|
41942
|
+
const material2 = new THREE33.MeshStandardMaterial({
|
|
41708
41943
|
vertexColors: true,
|
|
41709
41944
|
metalness: 0.2,
|
|
41710
41945
|
roughness: 0.8,
|
|
41711
|
-
side:
|
|
41946
|
+
side: THREE33.DoubleSide
|
|
41712
41947
|
});
|
|
41713
|
-
const mesh2 = new
|
|
41948
|
+
const mesh2 = new THREE33.Mesh(threeGeom, material2);
|
|
41714
41949
|
if (component.position) {
|
|
41715
41950
|
mesh2.position.set(
|
|
41716
41951
|
component.position.x ?? 0,
|
|
@@ -41720,22 +41955,22 @@ async function renderComponent(component, scene) {
|
|
|
41720
41955
|
}
|
|
41721
41956
|
if (component.rotation) {
|
|
41722
41957
|
mesh2.rotation.set(
|
|
41723
|
-
|
|
41724
|
-
|
|
41725
|
-
|
|
41958
|
+
THREE33.MathUtils.degToRad(component.rotation.x ?? 0),
|
|
41959
|
+
THREE33.MathUtils.degToRad(component.rotation.y ?? 0),
|
|
41960
|
+
THREE33.MathUtils.degToRad(component.rotation.z ?? 0)
|
|
41726
41961
|
);
|
|
41727
41962
|
}
|
|
41728
41963
|
scene.add(mesh2);
|
|
41729
41964
|
}
|
|
41730
41965
|
return;
|
|
41731
41966
|
}
|
|
41732
|
-
const geometry = new
|
|
41733
|
-
const material = new
|
|
41967
|
+
const geometry = new THREE33.BoxGeometry(0.5, 0.5, 0.5);
|
|
41968
|
+
const material = new THREE33.MeshStandardMaterial({
|
|
41734
41969
|
color: 16711680,
|
|
41735
41970
|
transparent: true,
|
|
41736
41971
|
opacity: 0.25
|
|
41737
41972
|
});
|
|
41738
|
-
const mesh = new
|
|
41973
|
+
const mesh = new THREE33.Mesh(geometry, material);
|
|
41739
41974
|
if (component.position) {
|
|
41740
41975
|
mesh.position.set(
|
|
41741
41976
|
component.position.x ?? 0,
|
|
@@ -41756,11 +41991,11 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
|
|
|
41756
41991
|
padding = 20,
|
|
41757
41992
|
zoom = 1.5
|
|
41758
41993
|
} = options;
|
|
41759
|
-
const scene = new
|
|
41994
|
+
const scene = new THREE34.Scene();
|
|
41760
41995
|
const renderer = new SVGRenderer();
|
|
41761
41996
|
renderer.setSize(width10, height10);
|
|
41762
|
-
renderer.setClearColor(new
|
|
41763
|
-
const camera = new
|
|
41997
|
+
renderer.setClearColor(new THREE34.Color(backgroundColor), 1);
|
|
41998
|
+
const camera = new THREE34.OrthographicCamera();
|
|
41764
41999
|
const aspect = width10 / height10;
|
|
41765
42000
|
const frustumSize = 100;
|
|
41766
42001
|
const halfFrustumSize = frustumSize / 2 / zoom;
|
|
@@ -41774,25 +42009,25 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
|
|
|
41774
42009
|
camera.position.set(position.x, position.y, position.z);
|
|
41775
42010
|
camera.up.set(0, 1, 0);
|
|
41776
42011
|
const lookAt = options.camera?.lookAt ?? { x: 0, y: 0, z: 0 };
|
|
41777
|
-
camera.lookAt(new
|
|
42012
|
+
camera.lookAt(new THREE34.Vector3(lookAt.x, lookAt.y, lookAt.z));
|
|
41778
42013
|
camera.updateProjectionMatrix();
|
|
41779
|
-
const ambientLight = new
|
|
42014
|
+
const ambientLight = new THREE34.AmbientLight(16777215, Math.PI / 2);
|
|
41780
42015
|
scene.add(ambientLight);
|
|
41781
|
-
const pointLight = new
|
|
42016
|
+
const pointLight = new THREE34.PointLight(16777215, Math.PI / 4);
|
|
41782
42017
|
pointLight.position.set(-10, -10, 10);
|
|
41783
42018
|
scene.add(pointLight);
|
|
41784
|
-
const components =
|
|
42019
|
+
const components = su17(circuitJson).cad_component.list();
|
|
41785
42020
|
for (const component of components) {
|
|
41786
42021
|
await renderComponent(component, scene);
|
|
41787
42022
|
}
|
|
41788
|
-
const boardData =
|
|
42023
|
+
const boardData = su17(circuitJson).pcb_board.list()[0];
|
|
41789
42024
|
const boardGeom = createBoardGeomFromCircuitJson(circuitJson);
|
|
41790
42025
|
if (boardGeom) {
|
|
41791
42026
|
for (const geom of boardGeom) {
|
|
41792
42027
|
const g = geom;
|
|
41793
42028
|
if (!g.polygons || g.polygons.length === 0) continue;
|
|
41794
42029
|
const geometry = createGeometryFromPolygons(g.polygons);
|
|
41795
|
-
const baseColor = new
|
|
42030
|
+
const baseColor = new THREE34.Color(
|
|
41796
42031
|
g.color?.[0] ?? 0,
|
|
41797
42032
|
g.color?.[1] ?? 0,
|
|
41798
42033
|
g.color?.[2] ?? 0
|
|
@@ -41800,18 +42035,18 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
|
|
|
41800
42035
|
const material = createBoardMaterial({
|
|
41801
42036
|
material: boardData?.material,
|
|
41802
42037
|
color: baseColor,
|
|
41803
|
-
side:
|
|
42038
|
+
side: THREE34.DoubleSide
|
|
41804
42039
|
});
|
|
41805
|
-
const mesh = new
|
|
42040
|
+
const mesh = new THREE34.Mesh(geometry, material);
|
|
41806
42041
|
scene.add(mesh);
|
|
41807
42042
|
}
|
|
41808
42043
|
}
|
|
41809
|
-
const gridHelper = new
|
|
42044
|
+
const gridHelper = new THREE34.GridHelper(100, 100);
|
|
41810
42045
|
gridHelper.rotation.x = Math.PI / 2;
|
|
41811
42046
|
scene.add(gridHelper);
|
|
41812
|
-
const box = new
|
|
41813
|
-
const center = box.getCenter(new
|
|
41814
|
-
const size5 = box.getSize(new
|
|
42047
|
+
const box = new THREE34.Box3().setFromObject(scene);
|
|
42048
|
+
const center = box.getCenter(new THREE34.Vector3());
|
|
42049
|
+
const size5 = box.getSize(new THREE34.Vector3());
|
|
41815
42050
|
scene.position.sub(center);
|
|
41816
42051
|
const maxDim = Math.max(size5.x, size5.y, size5.z);
|
|
41817
42052
|
if (maxDim > 0) {
|