@tscircuit/3d-viewer 0.0.450 → 0.0.452

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