@tscircuit/3d-viewer 0.0.516 → 0.0.518

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.
Files changed (2) hide show
  1. package/dist/index.js +476 -815
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -14248,7 +14248,7 @@ import { useState as useState36, useCallback as useCallback21, useRef as useRef2
14248
14248
  import * as THREE36 from "three";
14249
14249
 
14250
14250
  // src/CadViewerJscad.tsx
14251
- import { su as su13 } from "@tscircuit/circuit-json-util";
14251
+ import { su as su12 } from "@tscircuit/circuit-json-util";
14252
14252
  import { forwardRef as forwardRef3, useMemo as useMemo20 } from "react";
14253
14253
 
14254
14254
  // src/AnyCadComponent.tsx
@@ -28483,7 +28483,7 @@ import * as THREE16 from "three";
28483
28483
  // package.json
28484
28484
  var package_default = {
28485
28485
  name: "@tscircuit/3d-viewer",
28486
- version: "0.0.515",
28486
+ version: "0.0.517",
28487
28487
  main: "./dist/index.js",
28488
28488
  module: "./dist/index.js",
28489
28489
  type: "module",
@@ -28513,7 +28513,7 @@ var package_default = {
28513
28513
  "@jscad/regl-renderer": "^2.6.12",
28514
28514
  "@jscad/stl-serializer": "^2.1.20",
28515
28515
  "circuit-json": "^0.0.372",
28516
- "circuit-to-canvas": "^0.0.49",
28516
+ "circuit-to-canvas": "^0.0.80",
28517
28517
  "react-hot-toast": "^2.6.0",
28518
28518
  three: "^0.165.0",
28519
28519
  "three-stdlib": "^2.36.0",
@@ -29687,10 +29687,6 @@ import { su as su3 } from "@tscircuit/circuit-json-util";
29687
29687
 
29688
29688
  // src/geoms/constants.ts
29689
29689
  var M = 0.01;
29690
- var BOARD_SURFACE_OFFSET = {
29691
- traces: 1e-3,
29692
- copper: 2e-3
29693
- };
29694
29690
  var colors = {
29695
29691
  copper: [0.9, 0.6, 0.2],
29696
29692
  fr4Tan: [0.6, 0.43, 0.28],
@@ -29703,7 +29699,6 @@ var colors = {
29703
29699
  };
29704
29700
  var MANIFOLD_Z_OFFSET = 1e-3;
29705
29701
  var SMOOTH_CIRCLE_SEGMENTS = 32;
29706
- var DEFAULT_SMT_PAD_THICKNESS = 0.035;
29707
29702
  var TRACE_TEXTURE_RESOLUTION = 150;
29708
29703
  var FAUX_BOARD_OPACITY = 0.6;
29709
29704
  var boardMaterialColors = {
@@ -29932,7 +29927,7 @@ function extractRectBorderRadius(source) {
29932
29927
  }
29933
29928
 
29934
29929
  // src/geoms/plated-hole.ts
29935
- var platedHoleLipHeight = 0.02;
29930
+ var PLATED_HOLE_DRILL_OVERREACH = 0.05;
29936
29931
  var RECT_PAD_SEGMENTS = 64;
29937
29932
  var maybeClip = (geom, clipGeom) => clipGeom ? (0, import_booleans2.intersect)(clipGeom, geom) : geom;
29938
29933
  var createRectPadGeom = ({
@@ -29958,13 +29953,21 @@ var createRectPadGeom = ({
29958
29953
  var platedHole = (plated_hole, ctx, options = {}) => {
29959
29954
  const { clipGeom } = options;
29960
29955
  if (!plated_hole.shape) plated_hole.shape = "circle";
29961
- const throughDrillHeight = ctx.pcbThickness + 2 * platedHoleLipHeight + 4 * M;
29962
- const topSurfaceZ = ctx.pcbThickness / 2 + BOARD_SURFACE_OFFSET.copper;
29963
- const bottomSurfaceZ = -ctx.pcbThickness / 2 - BOARD_SURFACE_OFFSET.copper;
29964
- const copperSpan = topSurfaceZ - bottomSurfaceZ;
29956
+ const padThickness = Math.max(ctx.pcbThickness * 2e-3, M / 5);
29957
+ const surfaceClearance = Math.max(ctx.pcbThickness * 3e-4, M / 20);
29958
+ const fillClearance = Math.max(ctx.pcbThickness * 25e-4, M / 4);
29959
+ const throughDrillHeight = ctx.pcbThickness + 2 * PLATED_HOLE_DRILL_OVERREACH + 4 * M;
29960
+ const topSurfaceZ = ctx.pcbThickness / 2 + surfaceClearance + padThickness / 2;
29961
+ const bottomSurfaceZ = -ctx.pcbThickness / 2 - surfaceClearance - padThickness / 2;
29962
+ const copperSurfaceSpan = ctx.pcbThickness + 2 * surfaceClearance;
29963
+ const copperFillHeight = Math.max(
29964
+ ctx.pcbThickness - 2 * (padThickness + surfaceClearance + fillClearance),
29965
+ M
29966
+ );
29967
+ const barrelHeight = Math.max(copperFillHeight, M);
29965
29968
  if (plated_hole.shape === "circle") {
29966
29969
  const outerDiameter = plated_hole.outer_diameter ?? Math.max(plated_hole.hole_diameter, 0);
29967
- const copperHeight = copperSpan + 0.01;
29970
+ const copperHeight = copperSurfaceSpan;
29968
29971
  const copperBody = (0, import_primitives4.cylinder)({
29969
29972
  center: [plated_hole.x, plated_hole.y, 0],
29970
29973
  radius: outerDiameter / 2,
@@ -29987,7 +29990,7 @@ var platedHole = (plated_hole, ctx, options = {}) => {
29987
29990
  const circle = (0, import_primitives4.cylinder)({
29988
29991
  center: [0, 0, 0],
29989
29992
  radius: 1,
29990
- height: copperSpan + 0.01,
29993
+ height: copperSurfaceSpan,
29991
29994
  segments: 64
29992
29995
  // High segment count for smooth ellipse
29993
29996
  });
@@ -30023,8 +30026,7 @@ var platedHole = (plated_hole, ctx, options = {}) => {
30023
30026
  createRectPadGeom({
30024
30027
  width: padWidth,
30025
30028
  height: padHeight,
30026
- thickness: platedHoleLipHeight,
30027
- // Slightly thicker to ensure connection
30029
+ thickness: padThickness,
30028
30030
  center: [plated_hole.x, plated_hole.y, topSurfaceZ],
30029
30031
  borderRadius: rectBorderRadius
30030
30032
  }),
@@ -30032,25 +30034,20 @@ var platedHole = (plated_hole, ctx, options = {}) => {
30032
30034
  createRectPadGeom({
30033
30035
  width: padWidth,
30034
30036
  height: padHeight,
30035
- thickness: platedHoleLipHeight,
30036
- // Slightly thicker to ensure connection
30037
+ thickness: padThickness,
30037
30038
  center: [plated_hole.x, plated_hole.y, bottomSurfaceZ],
30038
30039
  borderRadius: rectBorderRadius
30039
30040
  }),
30040
30041
  // Main copper fill between pads with rounded corners
30041
30042
  (() => {
30042
- const height10 = Math.max(copperSpan - platedHoleLipHeight * 2, M);
30043
- const topPadBottom = topSurfaceZ;
30044
- const bottomPadTop = bottomSurfaceZ;
30045
- const centerZ = (topPadBottom + bottomPadTop) / 2;
30046
30043
  const rect2d = (0, import_primitives4.roundedRectangle)({
30047
30044
  size: [padWidth, padHeight],
30048
30045
  roundRadius: rectBorderRadius || 0,
30049
30046
  segments: RECT_PAD_SEGMENTS
30050
30047
  });
30051
- const extruded = (0, import_extrusions3.extrudeLinear)({ height: height10 }, rect2d);
30048
+ const extruded = (0, import_extrusions3.extrudeLinear)({ height: copperFillHeight }, rect2d);
30052
30049
  return (0, import_transforms4.translate)(
30053
- [plated_hole.x, plated_hole.y, centerZ - height10 / 2],
30050
+ [plated_hole.x, plated_hole.y, -copperFillHeight / 2],
30054
30051
  extruded
30055
30052
  );
30056
30053
  })(),
@@ -30062,7 +30059,7 @@ var platedHole = (plated_hole, ctx, options = {}) => {
30062
30059
  0
30063
30060
  ],
30064
30061
  radius: plated_hole.hole_diameter / 2,
30065
- height: copperSpan
30062
+ height: barrelHeight
30066
30063
  })
30067
30064
  ),
30068
30065
  clipGeom
@@ -30076,27 +30073,14 @@ var platedHole = (plated_hole, ctx, options = {}) => {
30076
30073
  radius: Math.max(plated_hole.hole_diameter / 2 - M, 0.01),
30077
30074
  height: throughDrillHeight
30078
30075
  });
30079
- const barrel = (0, import_primitives4.cylinder)({
30080
- center: [
30081
- plated_hole.x + (holeOffsetX || 0),
30082
- plated_hole.y + (holeOffsetY || 0),
30083
- 0
30084
- ],
30085
- radius: plated_hole.hole_diameter / 2,
30086
- height: copperSpan
30087
- });
30088
- let finalCopper = (0, import_booleans2.union)(
30089
- (0, import_booleans2.subtract)(copperSolid, barrel),
30090
- // Subtract the barrel from the main shape
30091
- barrel
30092
- // Add the barrel back to ensure proper connection
30093
- );
30076
+ const copperWithDrill = (0, import_booleans2.subtract)(copperSolid, drill);
30094
30077
  if (options.clipGeom) {
30095
- finalCopper = (0, import_booleans2.subtract)(finalCopper, drill);
30096
- finalCopper = (0, import_booleans2.intersect)(finalCopper, options.clipGeom);
30097
- return (0, import_colors2.colorize)(colors.copper, finalCopper);
30078
+ return (0, import_colors2.colorize)(
30079
+ colors.copper,
30080
+ (0, import_booleans2.intersect)(copperWithDrill, options.clipGeom)
30081
+ );
30098
30082
  }
30099
- return (0, import_colors2.colorize)(colors.copper, (0, import_booleans2.subtract)(finalCopper, drill));
30083
+ return (0, import_colors2.colorize)(colors.copper, copperWithDrill);
30100
30084
  }
30101
30085
  if (plated_hole.shape === "pill") {
30102
30086
  const rotationRadians = (plated_hole.ccw_rotation || 0) * Math.PI / 180;
@@ -30115,7 +30099,7 @@ var platedHole = (plated_hole, ctx, options = {}) => {
30115
30099
  const outerRadius = outerPillHeight / 2;
30116
30100
  const rectLength = Math.abs(holeWidth - holeHeight);
30117
30101
  const outerRectLength = Math.abs(outerPillWidth - outerPillHeight);
30118
- const copperHeight = copperSpan + 0.01;
30102
+ const copperHeight = copperSurfaceSpan;
30119
30103
  const createPillSection = (width10, height10, thickness) => {
30120
30104
  const radius = height10 / 2;
30121
30105
  const length51 = Math.abs(width10 - height10);
@@ -30189,11 +30173,11 @@ var platedHole = (plated_hole, ctx, options = {}) => {
30189
30173
  size: shouldRotate ? [
30190
30174
  holeHeight + 2 * barrelMargin,
30191
30175
  rectLength + 2 * barrelMargin,
30192
- copperSpan
30176
+ barrelHeight
30193
30177
  ] : [
30194
30178
  rectLength + 2 * barrelMargin,
30195
30179
  holeHeight + 2 * barrelMargin,
30196
- copperSpan
30180
+ barrelHeight
30197
30181
  ]
30198
30182
  }),
30199
30183
  (0, import_primitives4.cylinder)({
@@ -30207,7 +30191,7 @@ var platedHole = (plated_hole, ctx, options = {}) => {
30207
30191
  0
30208
30192
  ],
30209
30193
  radius: holeRadius + barrelMargin,
30210
- height: copperSpan
30194
+ height: barrelHeight
30211
30195
  }),
30212
30196
  (0, import_primitives4.cylinder)({
30213
30197
  center: shouldRotate ? [
@@ -30220,13 +30204,13 @@ var platedHole = (plated_hole, ctx, options = {}) => {
30220
30204
  0
30221
30205
  ],
30222
30206
  radius: holeRadius + barrelMargin,
30223
- height: copperSpan
30207
+ height: barrelHeight
30224
30208
  })
30225
30209
  );
30226
30210
  const holeCut = (0, import_booleans2.union)(
30227
30211
  (0, import_primitives4.cuboid)({
30228
30212
  center: [plated_hole.x + holeOffsetX, plated_hole.y + holeOffsetY, 0],
30229
- size: shouldRotate ? [holeHeight, rectLength, throughDrillHeight * 1.1] : [rectLength, holeHeight, throughDrillHeight * 1.1]
30213
+ size: shouldRotate ? [holeHeight, rectLength, throughDrillHeight] : [rectLength, holeHeight, throughDrillHeight]
30230
30214
  }),
30231
30215
  (0, import_primitives4.cylinder)({
30232
30216
  center: shouldRotate ? [
@@ -30239,7 +30223,7 @@ var platedHole = (plated_hole, ctx, options = {}) => {
30239
30223
  0
30240
30224
  ],
30241
30225
  radius: holeRadius,
30242
- height: throughDrillHeight * 1.1
30226
+ height: throughDrillHeight
30243
30227
  }),
30244
30228
  (0, import_primitives4.cylinder)({
30245
30229
  center: shouldRotate ? [
@@ -30252,36 +30236,32 @@ var platedHole = (plated_hole, ctx, options = {}) => {
30252
30236
  0
30253
30237
  ],
30254
30238
  radius: holeRadius,
30255
- height: throughDrillHeight * 1.1
30239
+ height: throughDrillHeight
30256
30240
  })
30257
30241
  );
30258
30242
  const copperTopPad = createRectPadGeom({
30259
30243
  width: padWidth,
30260
30244
  height: padHeight,
30261
- thickness: platedHoleLipHeight,
30245
+ thickness: padThickness,
30262
30246
  center: [plated_hole.x, plated_hole.y, topSurfaceZ],
30263
30247
  borderRadius: rectBorderRadius
30264
30248
  });
30265
30249
  const copperBottomPad = createRectPadGeom({
30266
30250
  width: padWidth,
30267
30251
  height: padHeight,
30268
- thickness: platedHoleLipHeight,
30252
+ thickness: padThickness,
30269
30253
  center: [plated_hole.x, plated_hole.y, bottomSurfaceZ],
30270
30254
  borderRadius: rectBorderRadius
30271
30255
  });
30272
30256
  const copperFill = (() => {
30273
- const height10 = Math.max(copperSpan - platedHoleLipHeight * 2, M);
30274
- const topPadBottom = topSurfaceZ;
30275
- const bottomPadTop = bottomSurfaceZ;
30276
- const centerZ = (topPadBottom + bottomPadTop) / 2;
30277
30257
  const rect2d = (0, import_primitives4.roundedRectangle)({
30278
30258
  size: [padWidth, padHeight],
30279
30259
  roundRadius: rectBorderRadius || 0,
30280
30260
  segments: RECT_PAD_SEGMENTS
30281
30261
  });
30282
- const extruded = (0, import_extrusions3.extrudeLinear)({ height: height10 }, rect2d);
30262
+ const extruded = (0, import_extrusions3.extrudeLinear)({ height: copperFillHeight }, rect2d);
30283
30263
  return (0, import_transforms4.translate)(
30284
- [plated_hole.x, plated_hole.y, centerZ - height10 / 2],
30264
+ [plated_hole.x, plated_hole.y, -copperFillHeight / 2],
30285
30265
  extruded
30286
30266
  );
30287
30267
  })();
@@ -30291,7 +30271,7 @@ var platedHole = (plated_hole, ctx, options = {}) => {
30291
30271
  const barrelHoleCut = (0, import_booleans2.union)(
30292
30272
  (0, import_primitives4.cuboid)({
30293
30273
  center: [plated_hole.x + holeOffsetX, plated_hole.y + holeOffsetY, 0],
30294
- size: shouldRotate ? [holeHeight - 2 * M, rectLength - 2 * M, throughDrillHeight * 1.1] : [rectLength - 2 * M, holeHeight - 2 * M, throughDrillHeight * 1.1]
30274
+ size: shouldRotate ? [holeHeight - 2 * M, rectLength - 2 * M, throughDrillHeight] : [rectLength - 2 * M, holeHeight - 2 * M, throughDrillHeight]
30295
30275
  }),
30296
30276
  (0, import_primitives4.cylinder)({
30297
30277
  center: shouldRotate ? [
@@ -30304,7 +30284,7 @@ var platedHole = (plated_hole, ctx, options = {}) => {
30304
30284
  0
30305
30285
  ],
30306
30286
  radius: holeRadius - M,
30307
- height: throughDrillHeight * 1.1
30287
+ height: throughDrillHeight
30308
30288
  }),
30309
30289
  (0, import_primitives4.cylinder)({
30310
30290
  center: shouldRotate ? [
@@ -30317,7 +30297,7 @@ var platedHole = (plated_hole, ctx, options = {}) => {
30317
30297
  0
30318
30298
  ],
30319
30299
  radius: holeRadius - M,
30320
- height: throughDrillHeight * 1.1
30300
+ height: throughDrillHeight
30321
30301
  })
30322
30302
  );
30323
30303
  const barrelWithHole = (0, import_booleans2.subtract)(barrel, barrelHoleCut);
@@ -30369,22 +30349,22 @@ var platedHole = (plated_hole, ctx, options = {}) => {
30369
30349
  size: isHorizontal ? [
30370
30350
  rectLength + 2 * barrelMargin,
30371
30351
  shortDim + 2 * barrelMargin,
30372
- copperSpan
30352
+ barrelHeight
30373
30353
  ] : [
30374
30354
  shortDim + 2 * barrelMargin,
30375
30355
  rectLength + 2 * barrelMargin,
30376
- copperSpan
30356
+ barrelHeight
30377
30357
  ]
30378
30358
  }),
30379
30359
  (0, import_primitives4.cylinder)({
30380
30360
  center: isHorizontal ? [-rectLength / 2, 0, 0] : [0, -rectLength / 2, 0],
30381
30361
  radius: holeRadius + barrelMargin,
30382
- height: copperSpan
30362
+ height: barrelHeight
30383
30363
  }),
30384
30364
  (0, import_primitives4.cylinder)({
30385
30365
  center: isHorizontal ? [rectLength / 2, 0, 0] : [0, rectLength / 2, 0],
30386
30366
  radius: holeRadius + barrelMargin,
30387
- height: copperSpan
30367
+ height: barrelHeight
30388
30368
  })
30389
30369
  )
30390
30370
  );
@@ -30392,17 +30372,17 @@ var platedHole = (plated_hole, ctx, options = {}) => {
30392
30372
  (0, import_booleans2.union)(
30393
30373
  (0, import_primitives4.cuboid)({
30394
30374
  center: [0, 0, 0],
30395
- size: isHorizontal ? [rectLength, shortDim, throughDrillHeight * 1.1] : [shortDim, rectLength, throughDrillHeight * 1.1]
30375
+ size: isHorizontal ? [rectLength, shortDim, throughDrillHeight] : [shortDim, rectLength, throughDrillHeight]
30396
30376
  }),
30397
30377
  (0, import_primitives4.cylinder)({
30398
30378
  center: isHorizontal ? [-rectLength / 2, 0, 0] : [0, -rectLength / 2, 0],
30399
30379
  radius: holeRadius,
30400
- height: throughDrillHeight * 1.1
30380
+ height: throughDrillHeight
30401
30381
  }),
30402
30382
  (0, import_primitives4.cylinder)({
30403
30383
  center: isHorizontal ? [rectLength / 2, 0, 0] : [0, rectLength / 2, 0],
30404
30384
  radius: holeRadius,
30405
- height: throughDrillHeight * 1.1
30385
+ height: throughDrillHeight
30406
30386
  })
30407
30387
  )
30408
30388
  );
@@ -30410,7 +30390,7 @@ var platedHole = (plated_hole, ctx, options = {}) => {
30410
30390
  createRectPadGeom({
30411
30391
  width: padWidth,
30412
30392
  height: padHeight,
30413
- thickness: platedHoleLipHeight,
30393
+ thickness: padThickness,
30414
30394
  center: [plated_hole.x, plated_hole.y, topSurfaceZ],
30415
30395
  borderRadius: rectBorderRadius
30416
30396
  })
@@ -30419,25 +30399,21 @@ var platedHole = (plated_hole, ctx, options = {}) => {
30419
30399
  createRectPadGeom({
30420
30400
  width: padWidth,
30421
30401
  height: padHeight,
30422
- thickness: platedHoleLipHeight,
30402
+ thickness: padThickness,
30423
30403
  center: [plated_hole.x, plated_hole.y, bottomSurfaceZ],
30424
30404
  borderRadius: rectBorderRadius
30425
30405
  })
30426
30406
  );
30427
30407
  const copperFill = rotateRectPad(
30428
30408
  (() => {
30429
- const height10 = Math.max(copperSpan - platedHoleLipHeight * 2, M);
30430
- const topPadBottom = topSurfaceZ;
30431
- const bottomPadTop = bottomSurfaceZ;
30432
- const centerZ = (topPadBottom + bottomPadTop) / 2;
30433
30409
  const rect2d = (0, import_primitives4.roundedRectangle)({
30434
30410
  size: [padWidth, padHeight],
30435
30411
  roundRadius: rectBorderRadius || 0,
30436
30412
  segments: RECT_PAD_SEGMENTS
30437
30413
  });
30438
- const extruded = (0, import_extrusions3.extrudeLinear)({ height: height10 }, rect2d);
30414
+ const extruded = (0, import_extrusions3.extrudeLinear)({ height: copperFillHeight }, rect2d);
30439
30415
  return (0, import_transforms4.translate)(
30440
- [plated_hole.x, plated_hole.y, centerZ - height10 / 2],
30416
+ [plated_hole.x, plated_hole.y, -copperFillHeight / 2],
30441
30417
  extruded
30442
30418
  );
30443
30419
  })()
@@ -30449,17 +30425,17 @@ var platedHole = (plated_hole, ctx, options = {}) => {
30449
30425
  (0, import_booleans2.union)(
30450
30426
  (0, import_primitives4.cuboid)({
30451
30427
  center: [0, 0, 0],
30452
- size: isHorizontal ? [rectLength - 2 * M, shortDim - 2 * M, throughDrillHeight * 1.1] : [shortDim - 2 * M, rectLength - 2 * M, throughDrillHeight * 1.1]
30428
+ size: isHorizontal ? [rectLength - 2 * M, shortDim - 2 * M, throughDrillHeight] : [shortDim - 2 * M, rectLength - 2 * M, throughDrillHeight]
30453
30429
  }),
30454
30430
  (0, import_primitives4.cylinder)({
30455
30431
  center: isHorizontal ? [-rectLength / 2, 0, 0] : [0, -rectLength / 2, 0],
30456
30432
  radius: holeRadius - M,
30457
- height: throughDrillHeight * 1.1
30433
+ height: throughDrillHeight
30458
30434
  }),
30459
30435
  (0, import_primitives4.cylinder)({
30460
30436
  center: isHorizontal ? [rectLength / 2, 0, 0] : [0, rectLength / 2, 0],
30461
30437
  radius: holeRadius - M,
30462
- height: throughDrillHeight * 1.1
30438
+ height: throughDrillHeight
30463
30439
  })
30464
30440
  )
30465
30441
  );
@@ -30481,7 +30457,7 @@ var platedHole = (plated_hole, ctx, options = {}) => {
30481
30457
  point.y
30482
30458
  ]);
30483
30459
  const polygon2d = (0, import_primitives4.polygon)({ points: polygonPoints });
30484
- const centerZ = (topSurfaceZ + bottomSurfaceZ) / 2;
30460
+ const centerZ = 0;
30485
30461
  const createPolygonPad = (thickness, zCenter) => {
30486
30462
  const safeThickness = Math.max(thickness, M);
30487
30463
  const extruded = (0, import_extrusions3.extrudeLinear)({ height: safeThickness }, polygon2d);
@@ -30490,14 +30466,11 @@ var platedHole = (plated_hole, ctx, options = {}) => {
30490
30466
  extruded
30491
30467
  );
30492
30468
  };
30493
- const mainFill = createPolygonPad(
30494
- Math.max(copperSpan - platedHoleLipHeight * 2, M),
30495
- centerZ
30496
- );
30497
- const topPad = createPolygonPad(platedHoleLipHeight, topSurfaceZ);
30498
- const bottomPad = createPolygonPad(platedHoleLipHeight, bottomSurfaceZ);
30469
+ const mainFill = createPolygonPad(copperFillHeight, centerZ);
30470
+ const topPad = createPolygonPad(padThickness, topSurfaceZ);
30471
+ const bottomPad = createPolygonPad(padThickness, bottomSurfaceZ);
30499
30472
  const copperSolid = maybeClip((0, import_booleans2.union)(mainFill, topPad, bottomPad), clipGeom);
30500
- const barrel = createHoleWithPolygonPadHoleGeom(plated_hole, copperSpan);
30473
+ const barrel = createHoleWithPolygonPadHoleGeom(plated_hole, barrelHeight);
30501
30474
  if (!barrel) return (0, import_colors2.colorize)(colors.copper, copperSolid);
30502
30475
  const drill = createHoleWithPolygonPadHoleGeom(plated_hole, throughDrillHeight, {
30503
30476
  sizeDelta: -2 * M
@@ -31367,7 +31340,8 @@ function STLModel({
31367
31340
  stlData,
31368
31341
  mtlUrl,
31369
31342
  color,
31370
- opacity = 1
31343
+ opacity = 1,
31344
+ layerType
31371
31345
  }) {
31372
31346
  const { rootObject } = useThree();
31373
31347
  const [geom, setGeom] = useState14(null);
@@ -31391,13 +31365,19 @@ function STLModel({
31391
31365
  }, [stlUrl, stlData]);
31392
31366
  const mesh = useMemo18(() => {
31393
31367
  if (!geom) return null;
31368
+ const isBoardLayer = layerType === "board";
31394
31369
  const material = new THREE18.MeshStandardMaterial({
31395
31370
  color: Array.isArray(color) ? new THREE18.Color(color[0], color[1], color[2]) : color,
31396
31371
  transparent: opacity !== 1,
31397
- opacity
31372
+ opacity,
31373
+ polygonOffset: isBoardLayer,
31374
+ polygonOffsetFactor: isBoardLayer ? 6 : 0,
31375
+ polygonOffsetUnits: isBoardLayer ? 6 : 0
31398
31376
  });
31399
- return new THREE18.Mesh(geom, material);
31400
- }, [geom, color, opacity]);
31377
+ const createdMesh = new THREE18.Mesh(geom, material);
31378
+ createdMesh.renderOrder = isBoardLayer ? -1 : 1;
31379
+ return createdMesh;
31380
+ }, [geom, color, opacity, layerType]);
31401
31381
  useEffect22(() => {
31402
31382
  if (!rootObject || !mesh) return;
31403
31383
  rootObject.add(mesh);
@@ -31438,7 +31418,15 @@ function VisibleSTLModel({
31438
31418
  if (!shouldShow) {
31439
31419
  return null;
31440
31420
  }
31441
- return /* @__PURE__ */ jsx16(STLModel, { stlData, color, opacity });
31421
+ return /* @__PURE__ */ jsx16(
31422
+ STLModel,
31423
+ {
31424
+ stlData,
31425
+ color,
31426
+ opacity,
31427
+ layerType
31428
+ }
31429
+ );
31442
31430
  }
31443
31431
 
31444
31432
  // src/three-components/ThreeErrorBoundary.tsx
@@ -31460,7 +31448,7 @@ var ThreeErrorBoundary = class extends React11.Component {
31460
31448
  };
31461
31449
 
31462
31450
  // src/three-components/JscadBoardTextures.tsx
31463
- import { su as su10 } from "@tscircuit/circuit-json-util";
31451
+ import { su as su9 } from "@tscircuit/circuit-json-util";
31464
31452
  import { useEffect as useEffect23, useMemo as useMemo19 } from "react";
31465
31453
 
31466
31454
  // src/textures/create-combined-board-textures.ts
@@ -32058,6 +32046,31 @@ function createPadTextureForLayer({
32058
32046
  ctx.beginPath();
32059
32047
  ctx.arc(canvasX, canvasY, radius, 0, 2 * Math.PI);
32060
32048
  ctx.fill();
32049
+ } else if (pad2.shape === "pill" || pad2.shape === "rotated_pill") {
32050
+ const width10 = pad2.width * traceTextureResolution;
32051
+ const height10 = pad2.height * traceTextureResolution;
32052
+ const borderRadius = Math.min(width10, height10) / 2;
32053
+ const ccwRotation = pad2.ccw_rotation || 0;
32054
+ const rotation = -ccwRotation * (Math.PI / 180);
32055
+ if (rotation) {
32056
+ ctx.save();
32057
+ ctx.translate(canvasX, canvasY);
32058
+ ctx.rotate(rotation);
32059
+ ctx.beginPath();
32060
+ ctx.roundRect(-width10 / 2, -height10 / 2, width10, height10, borderRadius);
32061
+ ctx.fill();
32062
+ ctx.restore();
32063
+ } else {
32064
+ ctx.beginPath();
32065
+ ctx.roundRect(
32066
+ canvasX - width10 / 2,
32067
+ canvasY - height10 / 2,
32068
+ width10,
32069
+ height10,
32070
+ borderRadius
32071
+ );
32072
+ ctx.fill();
32073
+ }
32061
32074
  } else if (pad2.shape === "rotated_rect") {
32062
32075
  const width10 = pad2.width * traceTextureResolution;
32063
32076
  const height10 = pad2.height * traceTextureResolution;
@@ -32611,16 +32624,28 @@ function createSilkscreenTextureForLayer({
32611
32624
  return texture;
32612
32625
  }
32613
32626
 
32614
- // src/utils/soldermask-texture.ts
32615
- import { su as su8 } from "@tscircuit/circuit-json-util";
32627
+ // src/utils/trace-texture.ts
32616
32628
  import * as THREE23 from "three";
32617
- function createSoldermaskTextureForLayer({
32629
+ import { su as su8 } from "@tscircuit/circuit-json-util";
32630
+ function isWireRoutePoint(point) {
32631
+ return point && point.route_type === "wire" && typeof point.layer === "string" && typeof point.width === "number";
32632
+ }
32633
+ function createTraceTextureForLayer({
32618
32634
  layer,
32619
32635
  circuitJson,
32620
32636
  boardData,
32621
- soldermaskColor,
32637
+ traceColor,
32622
32638
  traceTextureResolution
32623
32639
  }) {
32640
+ const pcbTraces = su8(circuitJson).pcb_trace.list();
32641
+ const allPcbVias = su8(circuitJson).pcb_via.list();
32642
+ const allPcbPlatedHoles = su8(
32643
+ circuitJson
32644
+ ).pcb_plated_hole.list();
32645
+ const tracesOnLayer = pcbTraces.filter(
32646
+ (t) => t.route.some((p) => isWireRoutePoint(p) && p.layer === layer)
32647
+ );
32648
+ if (tracesOnLayer.length === 0) return null;
32624
32649
  const boardOutlineBounds = calculateOutlineBounds(boardData);
32625
32650
  const canvas = document.createElement("canvas");
32626
32651
  const canvasWidth = Math.floor(
@@ -32637,546 +32662,81 @@ function createSoldermaskTextureForLayer({
32637
32662
  ctx.translate(0, canvasHeight);
32638
32663
  ctx.scale(1, -1);
32639
32664
  }
32640
- const canvasXFromPcb = (pcbX) => (pcbX - boardOutlineBounds.minX) * traceTextureResolution;
32641
- const canvasYFromPcb = (pcbY) => (boardOutlineBounds.maxY - pcbY) * traceTextureResolution;
32642
- ctx.fillStyle = soldermaskColor;
32643
- if (boardData.outline && boardData.outline.length >= 3) {
32665
+ tracesOnLayer.forEach((trace) => {
32666
+ let firstPoint = true;
32644
32667
  ctx.beginPath();
32645
- const firstPoint = boardData.outline[0];
32646
- ctx.moveTo(canvasXFromPcb(firstPoint.x), canvasYFromPcb(firstPoint.y));
32647
- for (let i = 1; i < boardData.outline.length; i++) {
32648
- const point = boardData.outline[i];
32649
- ctx.lineTo(canvasXFromPcb(point.x), canvasYFromPcb(point.y));
32650
- }
32651
- ctx.closePath();
32652
- ctx.fill();
32653
- } else {
32654
- ctx.fillRect(0, 0, canvasWidth, canvasHeight);
32655
- }
32656
- ctx.globalCompositeOperation = "destination-out";
32657
- ctx.fillStyle = "black";
32658
- const pcbSmtPads = su8(circuitJson).pcb_smtpad.list();
32659
- const smtPadsOnLayer = pcbSmtPads.filter((pad2) => pad2.layer === layer);
32660
- smtPadsOnLayer.forEach((pad2) => {
32661
- if (pad2.shape === "polygon" && pad2.points) {
32662
- ctx.beginPath();
32663
- pad2.points.forEach((point, index2) => {
32664
- const px = canvasXFromPcb(point.x);
32665
- const py = canvasYFromPcb(point.y);
32666
- if (index2 === 0) {
32667
- ctx.moveTo(px, py);
32668
- } else {
32669
- ctx.lineTo(px, py);
32670
- }
32671
- });
32672
- ctx.closePath();
32673
- ctx.fill();
32674
- return;
32675
- }
32676
- if (pad2.x === void 0 || pad2.y === void 0) return;
32677
- if (Number.isNaN(pad2.x) || Number.isNaN(pad2.y)) {
32678
- console.warn(
32679
- `[soldermask-texture] Skipping pad ${pad2.pcb_smtpad_id} with NaN coordinates`
32680
- );
32681
- return;
32682
- }
32683
- const x = pad2.x;
32684
- const y = pad2.y;
32685
- const canvasX = canvasXFromPcb(x);
32686
- const canvasY = canvasYFromPcb(y);
32687
- if (pad2.shape === "rect") {
32688
- const width10 = pad2.width * traceTextureResolution;
32689
- const height10 = pad2.height * traceTextureResolution;
32690
- const rawRadius = extractRectBorderRadius(pad2);
32691
- const borderRadius = clampRectBorderRadius(
32692
- pad2.width,
32693
- pad2.height,
32694
- rawRadius
32695
- ) * traceTextureResolution;
32696
- if (borderRadius > 0) {
32697
- ctx.beginPath();
32698
- ctx.roundRect(
32699
- canvasX - width10 / 2,
32700
- canvasY - height10 / 2,
32701
- width10,
32702
- height10,
32703
- borderRadius
32704
- );
32705
- ctx.fill();
32668
+ ctx.strokeStyle = traceColor;
32669
+ ctx.lineCap = "round";
32670
+ ctx.lineJoin = "round";
32671
+ let currentLineWidth = 0;
32672
+ for (const point of trace.route) {
32673
+ if (!isWireRoutePoint(point) || point.layer !== layer) {
32674
+ if (!firstPoint) ctx.stroke();
32675
+ firstPoint = true;
32676
+ continue;
32677
+ }
32678
+ const pcbX = point.x;
32679
+ const pcbY = point.y;
32680
+ currentLineWidth = point.width * traceTextureResolution;
32681
+ ctx.lineWidth = currentLineWidth;
32682
+ const canvasX = (pcbX - boardOutlineBounds.minX) * traceTextureResolution;
32683
+ const canvasY = (boardOutlineBounds.maxY - pcbY) * traceTextureResolution;
32684
+ if (firstPoint) {
32685
+ ctx.moveTo(canvasX, canvasY);
32686
+ firstPoint = false;
32706
32687
  } else {
32707
- ctx.fillRect(canvasX - width10 / 2, canvasY - height10 / 2, width10, height10);
32688
+ ctx.lineTo(canvasX, canvasY);
32708
32689
  }
32709
- } else if (pad2.shape === "circle") {
32710
- const radius = (pad2.radius ?? pad2.width / 2) * traceTextureResolution;
32711
- ctx.beginPath();
32712
- ctx.arc(canvasX, canvasY, radius, 0, 2 * Math.PI);
32713
- ctx.fill();
32714
- } else if (pad2.shape === "pill") {
32715
- const width10 = pad2.width * traceTextureResolution;
32716
- const height10 = pad2.height * traceTextureResolution;
32717
- const rawRadius = extractRectBorderRadius(pad2);
32718
- const borderRadius = clampRectBorderRadius(
32719
- pad2.width,
32720
- pad2.height,
32721
- rawRadius
32722
- ) * traceTextureResolution;
32723
- ctx.beginPath();
32724
- ctx.roundRect(
32725
- canvasX - width10 / 2,
32726
- canvasY - height10 / 2,
32727
- width10,
32728
- height10,
32729
- borderRadius
32730
- );
32731
- ctx.fill();
32732
- } else if (pad2.shape === "rotated_rect") {
32733
- const width10 = pad2.width * traceTextureResolution;
32734
- const height10 = pad2.height * traceTextureResolution;
32735
- const rawRadius = extractRectBorderRadius(pad2);
32736
- const borderRadius = clampRectBorderRadius(
32737
- pad2.width,
32738
- pad2.height,
32739
- rawRadius
32740
- ) * traceTextureResolution;
32741
- const ccwRotation = pad2.ccw_rotation || 0;
32742
- const rotation = -ccwRotation * (Math.PI / 180);
32743
- ctx.save();
32744
- ctx.translate(canvasX, canvasY);
32745
- ctx.rotate(rotation);
32746
- ctx.beginPath();
32747
- ctx.roundRect(-width10 / 2, -height10 / 2, width10, height10, borderRadius);
32748
- ctx.fill();
32749
- ctx.restore();
32690
+ }
32691
+ if (!firstPoint) {
32692
+ ctx.stroke();
32750
32693
  }
32751
32694
  });
32752
- const pcbVias = su8(circuitJson).pcb_via.list();
32753
- pcbVias.forEach((via) => {
32754
- const canvasX = canvasXFromPcb(via.x);
32755
- const canvasY = canvasYFromPcb(via.y);
32695
+ ctx.globalCompositeOperation = "destination-out";
32696
+ ctx.fillStyle = "black";
32697
+ allPcbVias.forEach((via) => {
32698
+ const canvasX = (via.x - boardOutlineBounds.minX) * traceTextureResolution;
32699
+ const canvasY = (boardOutlineBounds.maxY - via.y) * traceTextureResolution;
32756
32700
  const canvasRadius = via.outer_diameter / 2 * traceTextureResolution;
32757
32701
  ctx.beginPath();
32758
- ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI);
32702
+ ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI, false);
32759
32703
  ctx.fill();
32760
32704
  });
32761
- const pcbPlatedHoles = su8(circuitJson).pcb_plated_hole.list();
32762
- pcbPlatedHoles.forEach((hole) => {
32763
- if (!hole.layers?.includes(layer)) return;
32764
- const x = hole.x;
32765
- const y = hole.y;
32766
- const canvasX = canvasXFromPcb(x);
32767
- const canvasY = canvasYFromPcb(y);
32768
- if (hole.shape === "circle") {
32769
- const outerDiameter = hole.outer_diameter;
32770
- const canvasRadius = outerDiameter / 2 * traceTextureResolution;
32705
+ allPcbPlatedHoles.forEach((ph) => {
32706
+ if (ph.layers.includes(layer) && ph.shape === "circle") {
32707
+ const canvasX = (ph.x - boardOutlineBounds.minX) * traceTextureResolution;
32708
+ const canvasY = (boardOutlineBounds.maxY - ph.y) * traceTextureResolution;
32709
+ const canvasRadius = ph.outer_diameter / 2 * traceTextureResolution;
32771
32710
  ctx.beginPath();
32772
- ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI);
32711
+ ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI, false);
32773
32712
  ctx.fill();
32774
- } else if (hole.shape === "pill") {
32775
- const width10 = (hole.outer_width ?? hole.outer_diameter ?? hole.hole_width) * traceTextureResolution;
32776
- const height10 = (hole.outer_height ?? hole.outer_diameter ?? hole.hole_height) * traceTextureResolution;
32777
- const radius = Math.min(width10, height10) / 2;
32778
- const ccwRotationDeg = hole.ccw_rotation || 0;
32779
- const rotation = -ccwRotationDeg;
32780
- if (rotation) {
32781
- ctx.save();
32782
- ctx.translate(canvasX, canvasY);
32783
- ctx.rotate(rotation * Math.PI / 180);
32784
- ctx.beginPath();
32785
- ctx.roundRect(-width10 / 2, -height10 / 2, width10, height10, radius);
32786
- ctx.fill();
32787
- ctx.restore();
32788
- } else {
32789
- ctx.beginPath();
32790
- ctx.roundRect(
32791
- canvasX - width10 / 2,
32792
- canvasY - height10 / 2,
32793
- width10,
32794
- height10,
32795
- radius
32796
- );
32797
- ctx.fill();
32798
- }
32799
- } else if (hole.shape === "oval") {
32800
- const width10 = (hole.outer_width ?? hole.outer_diameter ?? hole.hole_width) * traceTextureResolution;
32801
- const height10 = (hole.outer_height ?? hole.outer_diameter ?? hole.hole_height) * traceTextureResolution;
32802
- const radiusX = width10 / 2;
32803
- const radiusY = height10 / 2;
32804
- const ccwRotationDeg = hole.ccw_rotation || 0;
32805
- const rotation = -ccwRotationDeg;
32806
- if (rotation) {
32713
+ } else if (ph.layers.includes(layer) && ph.shape === "rotated_pill_hole_with_rect_pad") {
32714
+ const canvasX = (ph.x - boardOutlineBounds.minX) * traceTextureResolution;
32715
+ const canvasY = (boardOutlineBounds.maxY - ph.y) * traceTextureResolution;
32716
+ const padWidth = (ph.rect_pad_width ?? ph.hole_width ?? 0) * traceTextureResolution;
32717
+ const padHeight = (ph.rect_pad_height ?? ph.hole_height ?? 0) * traceTextureResolution;
32718
+ const rectCcwRotationDeg = ph.rect_ccw_rotation || 0;
32719
+ const rectRotation = -rectCcwRotationDeg;
32720
+ if (rectRotation) {
32807
32721
  ctx.save();
32808
32722
  ctx.translate(canvasX, canvasY);
32809
- ctx.rotate(rotation * Math.PI / 180);
32723
+ ctx.rotate(rectRotation * Math.PI / 180);
32810
32724
  ctx.beginPath();
32811
- ctx.ellipse(0, 0, radiusX, radiusY, 0, 0, 2 * Math.PI);
32725
+ ctx.rect(-padWidth / 2, -padHeight / 2, padWidth, padHeight);
32812
32726
  ctx.fill();
32813
32727
  ctx.restore();
32814
32728
  } else {
32815
32729
  ctx.beginPath();
32816
- ctx.ellipse(canvasX, canvasY, radiusX, radiusY, 0, 0, 2 * Math.PI);
32817
- ctx.fill();
32818
- }
32819
- } else if (hole.shape === "hole_with_polygon_pad") {
32820
- const holeShape = hole.hole_shape || "circle";
32821
- const holeOffsetX = hole.hole_offset_x || 0;
32822
- const holeOffsetY = hole.hole_offset_y || 0;
32823
- const adjustedCanvasX = canvasXFromPcb(hole.x + holeOffsetX);
32824
- const adjustedCanvasY = canvasYFromPcb(hole.y + holeOffsetY);
32825
- if (holeShape === "pill" || holeShape === "rotated_pill") {
32826
- const width10 = (hole.outer_width ?? hole.outer_diameter ?? hole.hole_width) * traceTextureResolution;
32827
- const height10 = (hole.outer_height ?? hole.outer_diameter ?? hole.hole_height) * traceTextureResolution;
32828
- const radius = Math.min(width10, height10) / 2;
32829
- const ccwRotationDeg = hole.ccw_rotation || 0;
32830
- const rotation = -ccwRotationDeg;
32831
- if (rotation) {
32832
- ctx.save();
32833
- ctx.translate(adjustedCanvasX, adjustedCanvasY);
32834
- ctx.rotate(rotation * Math.PI / 180);
32835
- ctx.beginPath();
32836
- ctx.roundRect(-width10 / 2, -height10 / 2, width10, height10, radius);
32837
- ctx.fill();
32838
- ctx.restore();
32839
- } else {
32840
- ctx.beginPath();
32841
- ctx.roundRect(
32842
- adjustedCanvasX - width10 / 2,
32843
- adjustedCanvasY - height10 / 2,
32844
- width10,
32845
- height10,
32846
- radius
32847
- );
32848
- ctx.fill();
32849
- }
32850
- } else if (holeShape === "oval") {
32851
- const width10 = (hole.outer_width ?? hole.outer_diameter ?? hole.hole_width) * traceTextureResolution;
32852
- const height10 = (hole.outer_height ?? hole.outer_diameter ?? hole.hole_height) * traceTextureResolution;
32853
- const radiusX = width10 / 2;
32854
- const radiusY = height10 / 2;
32855
- const ccwRotationDeg = hole.ccw_rotation || 0;
32856
- const rotation = -ccwRotationDeg;
32857
- if (rotation) {
32858
- ctx.save();
32859
- ctx.translate(adjustedCanvasX, adjustedCanvasY);
32860
- ctx.rotate(rotation * Math.PI / 180);
32861
- ctx.beginPath();
32862
- ctx.ellipse(0, 0, radiusX, radiusY, 0, 0, 2 * Math.PI);
32863
- ctx.fill();
32864
- ctx.restore();
32865
- } else {
32866
- ctx.beginPath();
32867
- ctx.ellipse(
32868
- adjustedCanvasX,
32869
- adjustedCanvasY,
32870
- radiusX,
32871
- radiusY,
32872
- 0,
32873
- 0,
32874
- 2 * Math.PI
32875
- );
32876
- ctx.fill();
32877
- }
32878
- } else if (holeShape === "circle") {
32879
- const outerDiameter = (hole.outer_diameter ?? hole.hole_diameter ?? 0) * traceTextureResolution;
32880
- const canvasRadius = outerDiameter / 2;
32881
- ctx.beginPath();
32882
- ctx.arc(adjustedCanvasX, adjustedCanvasY, canvasRadius, 0, 2 * Math.PI);
32883
- ctx.fill();
32884
- }
32885
- if (hole.pad_outline && hole.pad_outline.length >= 3) {
32886
- ctx.beginPath();
32887
- hole.pad_outline.forEach(
32888
- (point, index2) => {
32889
- const px = canvasXFromPcb(hole.x + point.x);
32890
- const py = canvasYFromPcb(hole.y + point.y);
32891
- if (index2 === 0) {
32892
- ctx.moveTo(px, py);
32893
- } else {
32894
- ctx.lineTo(px, py);
32895
- }
32896
- }
32897
- );
32898
- ctx.closePath();
32899
- ctx.fill();
32900
- }
32901
- } else if (hole.shape === "circular_hole_with_rect_pad") {
32902
- const padWidth = (hole.rect_pad_width ?? hole.hole_diameter ?? 0) * traceTextureResolution;
32903
- const padHeight = (hole.rect_pad_height ?? hole.hole_diameter ?? 0) * traceTextureResolution;
32904
- const rawRadius = extractRectBorderRadius(hole);
32905
- const borderRadius = clampRectBorderRadius(
32906
- hole.rect_pad_width ?? hole.hole_diameter ?? 0,
32907
- hole.rect_pad_height ?? hole.hole_diameter ?? 0,
32908
- rawRadius
32909
- ) * traceTextureResolution;
32910
- if (borderRadius > 0) {
32911
- ctx.beginPath();
32912
- ctx.roundRect(
32913
- canvasX - padWidth / 2,
32914
- canvasY - padHeight / 2,
32915
- padWidth,
32916
- padHeight,
32917
- borderRadius
32918
- );
32919
- ctx.fill();
32920
- } else {
32921
- ctx.fillRect(
32730
+ ctx.rect(
32922
32731
  canvasX - padWidth / 2,
32923
32732
  canvasY - padHeight / 2,
32924
32733
  padWidth,
32925
32734
  padHeight
32926
32735
  );
32927
- }
32928
- } else if (hole.shape === "pill_hole_with_rect_pad") {
32929
- const padWidth = (hole.rect_pad_width ?? hole.hole_width ?? 0) * traceTextureResolution;
32930
- const padHeight = (hole.rect_pad_height ?? hole.hole_height ?? 0) * traceTextureResolution;
32931
- const rawRadius = extractRectBorderRadius(hole);
32932
- const borderRadius = clampRectBorderRadius(
32933
- hole.rect_pad_width ?? hole.hole_width ?? 0,
32934
- hole.rect_pad_height ?? hole.hole_height ?? 0,
32935
- rawRadius
32936
- ) * traceTextureResolution;
32937
- const ccwRotationDeg = hole.ccw_rotation || 0;
32938
- const rotation = -ccwRotationDeg;
32939
- if (rotation) {
32940
- ctx.save();
32941
- ctx.translate(canvasX, canvasY);
32942
- ctx.rotate(rotation * Math.PI / 180);
32943
- ctx.beginPath();
32944
- ctx.roundRect(
32945
- -padWidth / 2,
32946
- -padHeight / 2,
32947
- padWidth,
32948
- padHeight,
32949
- borderRadius
32950
- );
32951
- ctx.fill();
32952
- ctx.restore();
32953
- } else {
32954
- ctx.beginPath();
32955
- ctx.roundRect(
32956
- canvasX - padWidth / 2,
32957
- canvasY - padHeight / 2,
32958
- padWidth,
32959
- padHeight,
32960
- borderRadius
32961
- );
32962
- ctx.fill();
32963
- }
32964
- } else if (hole.shape === "rotated_pill_hole_with_rect_pad") {
32965
- const padWidth = (hole.rect_pad_width ?? hole.hole_width ?? 0) * traceTextureResolution;
32966
- const padHeight = (hole.rect_pad_height ?? hole.hole_height ?? 0) * traceTextureResolution;
32967
- const rawRadius = extractRectBorderRadius(hole);
32968
- const borderRadius = clampRectBorderRadius(
32969
- hole.rect_pad_width ?? hole.hole_width ?? 0,
32970
- hole.rect_pad_height ?? hole.hole_height ?? 0,
32971
- rawRadius
32972
- ) * traceTextureResolution;
32973
- const rectCcwRotationDeg = hole.rect_ccw_rotation || 0;
32974
- const rectRotation = -rectCcwRotationDeg;
32975
- if (rectRotation) {
32976
- ctx.save();
32977
- ctx.translate(canvasX, canvasY);
32978
- ctx.rotate(rectRotation * Math.PI / 180);
32979
- ctx.beginPath();
32980
- ctx.roundRect(
32981
- -padWidth / 2,
32982
- -padHeight / 2,
32983
- padWidth,
32984
- padHeight,
32985
- borderRadius
32986
- );
32987
- ctx.fill();
32988
- ctx.restore();
32989
- } else {
32990
- ctx.beginPath();
32991
- ctx.roundRect(
32992
- canvasX - padWidth / 2,
32993
- canvasY - padHeight / 2,
32994
- padWidth,
32995
- padHeight,
32996
- borderRadius
32997
- );
32998
- ctx.fill();
32736
+ ctx.fill();
32999
32737
  }
33000
32738
  }
33001
32739
  });
33002
- const pcbHoles = su8(circuitJson).pcb_hole.list();
33003
- pcbHoles.forEach((hole) => {
33004
- const x = hole.x;
33005
- const y = hole.y;
33006
- const canvasX = canvasXFromPcb(x);
33007
- const canvasY = canvasYFromPcb(y);
33008
- const holeShape = hole.hole_shape;
33009
- if (holeShape === "circle" && typeof hole.hole_diameter === "number") {
33010
- const canvasRadius = hole.hole_diameter / 2 * traceTextureResolution;
33011
- ctx.beginPath();
33012
- ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI);
33013
- ctx.fill();
33014
- } else if (holeShape === "pill" && typeof hole.hole_width === "number" && typeof hole.hole_height === "number") {
33015
- const width10 = hole.hole_width * traceTextureResolution;
33016
- const height10 = hole.hole_height * traceTextureResolution;
33017
- const radius = Math.min(width10, height10) / 2;
33018
- ctx.beginPath();
33019
- ctx.roundRect(
33020
- canvasX - width10 / 2,
33021
- canvasY - height10 / 2,
33022
- width10,
33023
- height10,
33024
- radius
33025
- );
33026
- ctx.fill();
33027
- } else if (holeShape === "rotated_pill" && typeof hole.hole_width === "number" && typeof hole.hole_height === "number") {
33028
- const width10 = hole.hole_width * traceTextureResolution;
33029
- const height10 = hole.hole_height * traceTextureResolution;
33030
- const radius = Math.min(width10, height10) / 2;
33031
- const ccwRotationDeg = hole.ccw_rotation || 0;
33032
- const rotation = -ccwRotationDeg * (Math.PI / 180);
33033
- if (rotation) {
33034
- ctx.save();
33035
- ctx.translate(canvasX, canvasY);
33036
- ctx.rotate(rotation);
33037
- ctx.beginPath();
33038
- ctx.roundRect(-width10 / 2, -height10 / 2, width10, height10, radius);
33039
- ctx.fill();
33040
- ctx.restore();
33041
- } else {
33042
- ctx.beginPath();
33043
- ctx.roundRect(
33044
- canvasX - width10 / 2,
33045
- canvasY - height10 / 2,
33046
- width10,
33047
- height10,
33048
- radius
33049
- );
33050
- ctx.fill();
33051
- }
33052
- } else if (holeShape === "oval" && typeof hole.hole_width === "number" && typeof hole.hole_height === "number") {
33053
- const width10 = hole.hole_width * traceTextureResolution;
33054
- const height10 = hole.hole_height * traceTextureResolution;
33055
- const radiusX = width10 / 2;
33056
- const radiusY = height10 / 2;
33057
- ctx.beginPath();
33058
- ctx.ellipse(canvasX, canvasY, radiusX, radiusY, 0, 0, 2 * Math.PI);
33059
- ctx.fill();
33060
- }
33061
- });
33062
- const pcbCopperPours = su8(circuitJson).pcb_copper_pour.list();
33063
- pcbCopperPours.forEach((pour) => {
33064
- if (pour.layer !== layer) return;
33065
- if (pour.covered_with_solder_mask !== false) return;
33066
- if (pour.shape === "rect") {
33067
- const centerX = canvasXFromPcb(pour.center.x);
33068
- const centerY = canvasYFromPcb(pour.center.y);
33069
- const width10 = pour.width * traceTextureResolution;
33070
- const height10 = pour.height * traceTextureResolution;
33071
- ctx.fillRect(centerX - width10 / 2, centerY - height10 / 2, width10, height10);
33072
- } else if (pour.shape === "polygon" && pour.points) {
33073
- ctx.beginPath();
33074
- pour.points.forEach((point, index2) => {
33075
- const px = canvasXFromPcb(point.x);
33076
- const py = canvasYFromPcb(point.y);
33077
- if (index2 === 0) {
33078
- ctx.moveTo(px, py);
33079
- } else {
33080
- ctx.lineTo(px, py);
33081
- }
33082
- });
33083
- ctx.closePath();
33084
- ctx.fill();
33085
- }
33086
- });
33087
- const pcbCutouts = su8(circuitJson).pcb_cutout.list();
33088
- pcbCutouts.forEach((cutout) => {
33089
- switch (cutout.shape) {
33090
- case "rect": {
33091
- const canvasX = canvasXFromPcb(cutout.center.x);
33092
- const canvasY = canvasYFromPcb(cutout.center.y);
33093
- const width10 = cutout.width * traceTextureResolution;
33094
- const height10 = cutout.height * traceTextureResolution;
33095
- const rectCornerRadius = extractRectBorderRadius(cutout);
33096
- const borderRadius = clampRectBorderRadius(
33097
- cutout.width,
33098
- cutout.height,
33099
- rectCornerRadius
33100
- );
33101
- if (cutout.rotation) {
33102
- ctx.save();
33103
- ctx.translate(canvasX, canvasY);
33104
- const rotation = -cutout.rotation * (Math.PI / 180);
33105
- ctx.rotate(rotation);
33106
- if (borderRadius > 0) {
33107
- ctx.beginPath();
33108
- ctx.roundRect(
33109
- -width10 / 2,
33110
- -height10 / 2,
33111
- width10,
33112
- height10,
33113
- borderRadius * traceTextureResolution
33114
- );
33115
- ctx.fill();
33116
- } else {
33117
- ctx.fillRect(-width10 / 2, -height10 / 2, width10, height10);
33118
- }
33119
- ctx.restore();
33120
- } else {
33121
- if (borderRadius > 0) {
33122
- ctx.beginPath();
33123
- ctx.roundRect(
33124
- canvasX - width10 / 2,
33125
- canvasY - height10 / 2,
33126
- width10,
33127
- height10,
33128
- borderRadius * traceTextureResolution
33129
- );
33130
- ctx.fill();
33131
- } else {
33132
- ctx.fillRect(
33133
- canvasX - width10 / 2,
33134
- canvasY - height10 / 2,
33135
- width10,
33136
- height10
33137
- );
33138
- }
33139
- }
33140
- break;
33141
- }
33142
- case "circle": {
33143
- const canvasX = canvasXFromPcb(cutout.center.x);
33144
- const canvasY = canvasYFromPcb(cutout.center.y);
33145
- const canvasRadius = cutout.radius * traceTextureResolution;
33146
- ctx.beginPath();
33147
- ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI);
33148
- ctx.fill();
33149
- break;
33150
- }
33151
- case "polygon": {
33152
- if (!cutout.points || cutout.points.length < 3) {
33153
- console.warn(
33154
- `PCB Cutout [${cutout.pcb_cutout_id}] polygon has fewer than 3 points, skipping in soldermask texture.`
33155
- );
33156
- break;
33157
- }
33158
- ctx.beginPath();
33159
- cutout.points.forEach(
33160
- (point, index2) => {
33161
- const px = canvasXFromPcb(point.x);
33162
- const py = canvasYFromPcb(point.y);
33163
- if (index2 === 0) {
33164
- ctx.moveTo(px, py);
33165
- } else {
33166
- ctx.lineTo(px, py);
33167
- }
33168
- }
33169
- );
33170
- ctx.closePath();
33171
- ctx.fill();
33172
- break;
33173
- }
33174
- default:
33175
- console.warn(
33176
- `Unsupported cutout shape: ${cutout.shape} for cutout ${cutout.pcb_cutout_id} in soldermask texture.`
33177
- );
33178
- }
33179
- });
33180
32740
  ctx.globalCompositeOperation = "source-over";
33181
32741
  const texture = new THREE23.CanvasTexture(canvas);
33182
32742
  texture.generateMipmaps = true;
@@ -33187,131 +32747,8 @@ function createSoldermaskTextureForLayer({
33187
32747
  return texture;
33188
32748
  }
33189
32749
 
33190
- // src/utils/trace-texture.ts
33191
- import * as THREE24 from "three";
33192
- import { su as su9 } from "@tscircuit/circuit-json-util";
33193
- function isWireRoutePoint(point) {
33194
- return point && point.route_type === "wire" && typeof point.layer === "string" && typeof point.width === "number";
33195
- }
33196
- function createTraceTextureForLayer({
33197
- layer,
33198
- circuitJson,
33199
- boardData,
33200
- traceColor,
33201
- traceTextureResolution
33202
- }) {
33203
- const pcbTraces = su9(circuitJson).pcb_trace.list();
33204
- const allPcbVias = su9(circuitJson).pcb_via.list();
33205
- const allPcbPlatedHoles = su9(
33206
- circuitJson
33207
- ).pcb_plated_hole.list();
33208
- const tracesOnLayer = pcbTraces.filter(
33209
- (t) => t.route.some((p) => isWireRoutePoint(p) && p.layer === layer)
33210
- );
33211
- if (tracesOnLayer.length === 0) return null;
33212
- const boardOutlineBounds = calculateOutlineBounds(boardData);
33213
- const canvas = document.createElement("canvas");
33214
- const canvasWidth = Math.floor(
33215
- boardOutlineBounds.width * traceTextureResolution
33216
- );
33217
- const canvasHeight = Math.floor(
33218
- boardOutlineBounds.height * traceTextureResolution
33219
- );
33220
- canvas.width = canvasWidth;
33221
- canvas.height = canvasHeight;
33222
- const ctx = canvas.getContext("2d");
33223
- if (!ctx) return null;
33224
- if (layer === "bottom") {
33225
- ctx.translate(0, canvasHeight);
33226
- ctx.scale(1, -1);
33227
- }
33228
- tracesOnLayer.forEach((trace) => {
33229
- let firstPoint = true;
33230
- ctx.beginPath();
33231
- ctx.strokeStyle = traceColor;
33232
- ctx.lineCap = "round";
33233
- ctx.lineJoin = "round";
33234
- let currentLineWidth = 0;
33235
- for (const point of trace.route) {
33236
- if (!isWireRoutePoint(point) || point.layer !== layer) {
33237
- if (!firstPoint) ctx.stroke();
33238
- firstPoint = true;
33239
- continue;
33240
- }
33241
- const pcbX = point.x;
33242
- const pcbY = point.y;
33243
- currentLineWidth = point.width * traceTextureResolution;
33244
- ctx.lineWidth = currentLineWidth;
33245
- const canvasX = (pcbX - boardOutlineBounds.minX) * traceTextureResolution;
33246
- const canvasY = (boardOutlineBounds.maxY - pcbY) * traceTextureResolution;
33247
- if (firstPoint) {
33248
- ctx.moveTo(canvasX, canvasY);
33249
- firstPoint = false;
33250
- } else {
33251
- ctx.lineTo(canvasX, canvasY);
33252
- }
33253
- }
33254
- if (!firstPoint) {
33255
- ctx.stroke();
33256
- }
33257
- });
33258
- ctx.globalCompositeOperation = "destination-out";
33259
- ctx.fillStyle = "black";
33260
- allPcbVias.forEach((via) => {
33261
- const canvasX = (via.x - boardOutlineBounds.minX) * traceTextureResolution;
33262
- const canvasY = (boardOutlineBounds.maxY - via.y) * traceTextureResolution;
33263
- const canvasRadius = via.outer_diameter / 2 * traceTextureResolution;
33264
- ctx.beginPath();
33265
- ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI, false);
33266
- ctx.fill();
33267
- });
33268
- allPcbPlatedHoles.forEach((ph) => {
33269
- if (ph.layers.includes(layer) && ph.shape === "circle") {
33270
- const canvasX = (ph.x - boardOutlineBounds.minX) * traceTextureResolution;
33271
- const canvasY = (boardOutlineBounds.maxY - ph.y) * traceTextureResolution;
33272
- const canvasRadius = ph.outer_diameter / 2 * traceTextureResolution;
33273
- ctx.beginPath();
33274
- ctx.arc(canvasX, canvasY, canvasRadius, 0, 2 * Math.PI, false);
33275
- ctx.fill();
33276
- } else if (ph.layers.includes(layer) && ph.shape === "rotated_pill_hole_with_rect_pad") {
33277
- const canvasX = (ph.x - boardOutlineBounds.minX) * traceTextureResolution;
33278
- const canvasY = (boardOutlineBounds.maxY - ph.y) * traceTextureResolution;
33279
- const padWidth = (ph.rect_pad_width ?? ph.hole_width ?? 0) * traceTextureResolution;
33280
- const padHeight = (ph.rect_pad_height ?? ph.hole_height ?? 0) * traceTextureResolution;
33281
- const rectCcwRotationDeg = ph.rect_ccw_rotation || 0;
33282
- const rectRotation = -rectCcwRotationDeg;
33283
- if (rectRotation) {
33284
- ctx.save();
33285
- ctx.translate(canvasX, canvasY);
33286
- ctx.rotate(rectRotation * Math.PI / 180);
33287
- ctx.beginPath();
33288
- ctx.rect(-padWidth / 2, -padHeight / 2, padWidth, padHeight);
33289
- ctx.fill();
33290
- ctx.restore();
33291
- } else {
33292
- ctx.beginPath();
33293
- ctx.rect(
33294
- canvasX - padWidth / 2,
33295
- canvasY - padHeight / 2,
33296
- padWidth,
33297
- padHeight
33298
- );
33299
- ctx.fill();
33300
- }
33301
- }
33302
- });
33303
- ctx.globalCompositeOperation = "source-over";
33304
- const texture = new THREE24.CanvasTexture(canvas);
33305
- texture.generateMipmaps = true;
33306
- texture.minFilter = THREE24.LinearMipmapLinearFilter;
33307
- texture.magFilter = THREE24.LinearFilter;
33308
- texture.anisotropy = 16;
33309
- texture.needsUpdate = true;
33310
- return texture;
33311
- }
33312
-
33313
32750
  // src/textures/create-copper-pour-texture-for-layer.ts
33314
- import * as THREE25 from "three";
32751
+ import * as THREE24 from "three";
33315
32752
  import { CircuitToCanvasDrawer } from "circuit-to-canvas";
33316
32753
 
33317
32754
  // src/geoms/brep-converter.ts
@@ -33517,6 +32954,190 @@ function createCopperPourTextureForLayer({
33517
32954
  ctx.fillStyle = copperColor;
33518
32955
  drawBrepShape({ ctx, pour, canvasXFromPcb, canvasYFromPcb });
33519
32956
  }
32957
+ const texture = new THREE24.CanvasTexture(canvas);
32958
+ texture.generateMipmaps = true;
32959
+ texture.minFilter = THREE24.LinearMipmapLinearFilter;
32960
+ texture.magFilter = THREE24.LinearFilter;
32961
+ texture.anisotropy = 16;
32962
+ texture.needsUpdate = true;
32963
+ return texture;
32964
+ }
32965
+
32966
+ // src/textures/create-soldermask-texture-for-layer.ts
32967
+ import * as THREE25 from "three";
32968
+
32969
+ // src/textures/soldermask/soldermask-drawing.ts
32970
+ import { CircuitToCanvasDrawer as CircuitToCanvasDrawer2 } from "circuit-to-canvas";
32971
+ var toRgb = (colorArr) => {
32972
+ const [r = 0, g = 0, b = 0] = colorArr;
32973
+ return `rgb(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(
32974
+ b * 255
32975
+ )})`;
32976
+ };
32977
+ var getSoldermaskPalette = (material) => {
32978
+ const soldermask = toRgb(
32979
+ soldermaskColors[material] ?? colors.fr4SolderMaskGreen
32980
+ );
32981
+ const soldermaskOverCopper = material === "fr1" ? toRgb(colors.fr1TracesWithMaskCopper) : toRgb(colors.fr4TracesWithMaskGreen);
32982
+ return {
32983
+ soldermask,
32984
+ soldermaskOverCopper,
32985
+ copper: toRgb(colors.copper),
32986
+ transparent: "rgba(0,0,0,0)"
32987
+ };
32988
+ };
32989
+ var setDrawerBounds = (drawer, bounds) => {
32990
+ drawer.setCameraBounds({
32991
+ minX: bounds.minX,
32992
+ maxX: bounds.maxX,
32993
+ minY: bounds.minY,
32994
+ maxY: bounds.maxY
32995
+ });
32996
+ };
32997
+ var drawSoldermaskLayer = ({
32998
+ ctx,
32999
+ layer,
33000
+ bounds,
33001
+ elements,
33002
+ boardMaterial
33003
+ }) => {
33004
+ const palette = getSoldermaskPalette(boardMaterial);
33005
+ const copperRenderLayer = layer === "top" ? "top_copper" : "bottom_copper";
33006
+ const drawer = new CircuitToCanvasDrawer2(ctx);
33007
+ drawer.configure({
33008
+ colorOverrides: {
33009
+ copper: {
33010
+ top: palette.transparent,
33011
+ bottom: palette.transparent,
33012
+ inner1: palette.transparent,
33013
+ inner2: palette.transparent,
33014
+ inner3: palette.transparent,
33015
+ inner4: palette.transparent,
33016
+ inner5: palette.transparent,
33017
+ inner6: palette.transparent
33018
+ },
33019
+ drill: palette.transparent,
33020
+ boardOutline: palette.transparent,
33021
+ substrate: palette.transparent,
33022
+ keepout: palette.transparent,
33023
+ fabricationNote: palette.transparent,
33024
+ silkscreen: { top: palette.transparent, bottom: palette.transparent },
33025
+ courtyard: { top: palette.transparent, bottom: palette.transparent },
33026
+ soldermask: { top: palette.soldermask, bottom: palette.soldermask },
33027
+ soldermaskWithCopperUnderneath: {
33028
+ top: palette.soldermaskOverCopper,
33029
+ bottom: palette.soldermaskOverCopper
33030
+ },
33031
+ soldermaskOverCopper: {
33032
+ top: palette.soldermaskOverCopper,
33033
+ bottom: palette.soldermaskOverCopper
33034
+ }
33035
+ }
33036
+ });
33037
+ setDrawerBounds(drawer, bounds);
33038
+ drawer.drawElements(elements, {
33039
+ layers: [copperRenderLayer],
33040
+ drawSoldermask: true,
33041
+ drawSoldermaskTop: layer === "top",
33042
+ drawSoldermaskBottom: layer === "bottom"
33043
+ });
33044
+ const uncoveredPours = elements.filter(
33045
+ (e) => e.type === "pcb_copper_pour" && e.layer === layer && e.covered_with_solder_mask === false
33046
+ );
33047
+ if (uncoveredPours.length > 0) {
33048
+ ctx.save();
33049
+ ctx.globalCompositeOperation = "destination-out";
33050
+ const cutoutDrawer = new CircuitToCanvasDrawer2(ctx);
33051
+ cutoutDrawer.configure({
33052
+ colorOverrides: {
33053
+ copper: {
33054
+ top: palette.copper,
33055
+ bottom: palette.copper,
33056
+ inner1: palette.copper,
33057
+ inner2: palette.copper,
33058
+ inner3: palette.copper,
33059
+ inner4: palette.copper,
33060
+ inner5: palette.copper,
33061
+ inner6: palette.copper
33062
+ }
33063
+ }
33064
+ });
33065
+ setDrawerBounds(cutoutDrawer, bounds);
33066
+ cutoutDrawer.drawElements(uncoveredPours, { layers: [copperRenderLayer] });
33067
+ ctx.restore();
33068
+ }
33069
+ };
33070
+
33071
+ // src/textures/soldermask/soldermask-bounds.ts
33072
+ var boundsFromPanel = (panel) => ({
33073
+ minX: panel.center.x - panel.width / 2,
33074
+ maxX: panel.center.x + panel.width / 2,
33075
+ minY: panel.center.y - panel.height / 2,
33076
+ maxY: panel.center.y + panel.height / 2,
33077
+ width: panel.width,
33078
+ height: panel.height,
33079
+ centerX: panel.center.x,
33080
+ centerY: panel.center.y
33081
+ });
33082
+ var mergeBounds = (a, b) => {
33083
+ const minX = Math.min(a.minX, b.minX);
33084
+ const maxX = Math.max(a.maxX, b.maxX);
33085
+ const minY = Math.min(a.minY, b.minY);
33086
+ const maxY = Math.max(a.maxY, b.maxY);
33087
+ return {
33088
+ minX,
33089
+ maxX,
33090
+ minY,
33091
+ maxY,
33092
+ width: maxX - minX,
33093
+ height: maxY - minY,
33094
+ centerX: (minX + maxX) / 2,
33095
+ centerY: (minY + maxY) / 2
33096
+ };
33097
+ };
33098
+ var getSoldermaskRenderBounds = (circuitJson, boardData) => {
33099
+ const panels = circuitJson.filter(
33100
+ (e) => e.type === "pcb_panel"
33101
+ );
33102
+ const boards = circuitJson.filter(
33103
+ (e) => e.type === "pcb_board"
33104
+ );
33105
+ const activePanel = panels.find((panel) => panel.pcb_panel_id === boardData.pcb_board_id) ?? panels[0];
33106
+ if (activePanel && activePanel.width > 0 && activePanel.height > 0) {
33107
+ return boundsFromPanel(activePanel);
33108
+ }
33109
+ const boardsForBounds = boards.length > 1 ? boards : [boardData];
33110
+ return boardsForBounds.map((board) => calculateOutlineBounds(board)).reduce((acc, bounds) => mergeBounds(acc, bounds));
33111
+ };
33112
+
33113
+ // src/textures/create-soldermask-texture-for-layer.ts
33114
+ function createSoldermaskTextureForLayer({
33115
+ layer,
33116
+ circuitJson,
33117
+ boardData,
33118
+ traceTextureResolution = TRACE_TEXTURE_RESOLUTION
33119
+ }) {
33120
+ const bounds = getSoldermaskRenderBounds(circuitJson, boardData);
33121
+ const canvasWidth = Math.floor(bounds.width * traceTextureResolution);
33122
+ const canvasHeight = Math.floor(bounds.height * traceTextureResolution);
33123
+ if (canvasWidth <= 0 || canvasHeight <= 0) return null;
33124
+ const canvas = document.createElement("canvas");
33125
+ canvas.width = canvasWidth;
33126
+ canvas.height = canvasHeight;
33127
+ const ctx = canvas.getContext("2d");
33128
+ if (!ctx) return null;
33129
+ if (layer === "bottom") {
33130
+ ctx.translate(0, canvasHeight);
33131
+ ctx.scale(1, -1);
33132
+ }
33133
+ const elements = circuitJson.some((e) => e.type === "pcb_board") ? circuitJson : [boardData, ...circuitJson];
33134
+ drawSoldermaskLayer({
33135
+ ctx,
33136
+ layer,
33137
+ bounds,
33138
+ elements,
33139
+ boardMaterial: boardData.material
33140
+ });
33520
33141
  const texture = new THREE25.CanvasTexture(canvas);
33521
33142
  texture.generateMipmaps = true;
33522
33143
  texture.minFilter = THREE25.LinearMipmapLinearFilter;
@@ -33527,7 +33148,7 @@ function createCopperPourTextureForLayer({
33527
33148
  }
33528
33149
 
33529
33150
  // src/textures/create-combined-board-textures.ts
33530
- var toRgb = (colorArr) => {
33151
+ var toRgb2 = (colorArr) => {
33531
33152
  const [r = 0, g = 0, b = 0] = colorArr;
33532
33153
  return `rgb(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(
33533
33154
  b * 255
@@ -33550,7 +33171,7 @@ var createCombinedTexture = ({
33550
33171
  if (canvasWidth <= 0 || canvasHeight <= 0) return null;
33551
33172
  const canvas = document.createElement("canvas");
33552
33173
  canvas.width = canvasWidth;
33553
- canvas.height = canvasHeight;
33174
+ canvas.height = canvasHeight + 1;
33554
33175
  const ctx = canvas.getContext("2d");
33555
33176
  if (!ctx) return null;
33556
33177
  textures.forEach((texture) => {
@@ -33559,9 +33180,10 @@ var createCombinedTexture = ({
33559
33180
  ctx.drawImage(image, 0, 0, canvasWidth, canvasHeight);
33560
33181
  });
33561
33182
  const combinedTexture = new THREE26.CanvasTexture(canvas);
33562
- combinedTexture.generateMipmaps = true;
33563
- combinedTexture.minFilter = THREE26.LinearMipmapLinearFilter;
33183
+ combinedTexture.generateMipmaps = false;
33184
+ combinedTexture.minFilter = THREE26.LinearFilter;
33564
33185
  combinedTexture.magFilter = THREE26.LinearFilter;
33186
+ combinedTexture.premultiplyAlpha = true;
33565
33187
  combinedTexture.anisotropy = 16;
33566
33188
  combinedTexture.needsUpdate = true;
33567
33189
  return combinedTexture;
@@ -33572,13 +33194,10 @@ function createCombinedBoardTextures({
33572
33194
  traceTextureResolution,
33573
33195
  visibility
33574
33196
  }) {
33575
- const soldermaskColor = toRgb(
33576
- soldermaskColors[boardData.material] ?? colors.fr4SolderMaskGreen
33577
- );
33578
- const traceColorWithMask = toRgb(colors.fr4TracesWithMaskGreen);
33579
- const traceColorWithoutMask = toRgb(colors.fr4TracesWithoutMaskTan);
33197
+ const traceColorWithMask = toRgb2(colors.fr4TracesWithMaskGreen);
33198
+ const traceColorWithoutMask = toRgb2(colors.fr4TracesWithoutMaskTan);
33580
33199
  const silkscreenColor = "rgb(255,255,255)";
33581
- const copperColor = toRgb(colors.copper);
33200
+ const copperColor = toRgb2(colors.copper);
33582
33201
  const showBoardBody = visibility?.boardBody ?? true;
33583
33202
  const buildForLayer = (layer) => {
33584
33203
  const showMask = (layer === "top" ? visibility?.topMask : visibility?.bottomMask) ?? true;
@@ -33588,7 +33207,6 @@ function createCombinedBoardTextures({
33588
33207
  layer,
33589
33208
  circuitJson,
33590
33209
  boardData,
33591
- soldermaskColor,
33592
33210
  traceTextureResolution
33593
33211
  }) : null;
33594
33212
  const traceTexture = showCopper ? createTraceTextureForLayer({
@@ -33633,11 +33251,11 @@ function createCombinedBoardTextures({
33633
33251
  }) : null;
33634
33252
  return createCombinedTexture({
33635
33253
  textures: [
33636
- soldermaskTexture,
33637
33254
  copperPourTexture,
33638
33255
  traceTexture,
33639
33256
  copperTextTexture,
33640
33257
  padTexture,
33258
+ soldermaskTexture,
33641
33259
  silkscreenTexture,
33642
33260
  panelOutlineTexture
33643
33261
  ],
@@ -33672,8 +33290,9 @@ function createTexturePlane(config, boardData) {
33672
33290
  const material = new THREE27.MeshBasicMaterial({
33673
33291
  map: texture,
33674
33292
  transparent: true,
33293
+ alphaTest: 0.08,
33675
33294
  side: THREE27.DoubleSide,
33676
- depthWrite: false,
33295
+ depthWrite: true,
33677
33296
  polygonOffset: usePolygonOffset,
33678
33297
  polygonOffsetFactor: usePolygonOffset ? -4 : 0,
33679
33298
  // Increased for better z-fighting prevention
@@ -33696,10 +33315,11 @@ function createTexturePlane(config, boardData) {
33696
33315
  function createTextureMeshes(textures, boardData, pcbThickness, isFaux = false) {
33697
33316
  const meshes = [];
33698
33317
  if (!textures || !boardData || pcbThickness === null) return meshes;
33318
+ const SURFACE_OFFSET = 5e-3;
33699
33319
  const topBoardMesh = createTexturePlane(
33700
33320
  {
33701
33321
  texture: textures.topBoard,
33702
- yOffset: pcbThickness / 2 + 1e-3,
33322
+ yOffset: pcbThickness / 2 + SURFACE_OFFSET,
33703
33323
  isBottomLayer: false,
33704
33324
  usePolygonOffset: true,
33705
33325
  renderOrder: 1,
@@ -33711,7 +33331,7 @@ function createTextureMeshes(textures, boardData, pcbThickness, isFaux = false)
33711
33331
  const bottomBoardMesh = createTexturePlane(
33712
33332
  {
33713
33333
  texture: textures.bottomBoard,
33714
- yOffset: -pcbThickness / 2 - 1e-3,
33334
+ yOffset: -pcbThickness / 2 - SURFACE_OFFSET,
33715
33335
  isBottomLayer: true,
33716
33336
  usePolygonOffset: true,
33717
33337
  renderOrder: 1,
@@ -33765,7 +33385,7 @@ function JscadBoardTextures({
33765
33385
  const panels = circuitJson.filter(
33766
33386
  (e) => e.type === "pcb_panel"
33767
33387
  );
33768
- const boards = su10(circuitJson).pcb_board.list();
33388
+ const boards = su9(circuitJson).pcb_board.list();
33769
33389
  if (panels.length > 0) {
33770
33390
  const panel = panels[0];
33771
33391
  const firstBoardInPanel = boards.find(
@@ -33827,7 +33447,7 @@ function JscadBoardTextures({
33827
33447
  }
33828
33448
  material.dispose();
33829
33449
  };
33830
- const createTexturePlane2 = (texture, zOffset, isBottomLayer, name, usePolygonOffset = false, depthWrite = false, renderOrder = 1) => {
33450
+ const createTexturePlane2 = (texture, zOffset, isBottomLayer, name, usePolygonOffset = false, depthWrite = true, renderOrder = 1) => {
33831
33451
  if (!texture) return null;
33832
33452
  const boardOutlineBounds = calculateOutlineBounds(boardData);
33833
33453
  const planeGeom = new THREE28.PlaneGeometry(
@@ -33837,9 +33457,11 @@ function JscadBoardTextures({
33837
33457
  const material = new THREE28.MeshBasicMaterial({
33838
33458
  map: texture,
33839
33459
  transparent: true,
33460
+ alphaTest: 0.08,
33840
33461
  side: THREE28.DoubleSide,
33841
33462
  depthWrite,
33842
33463
  polygonOffset: usePolygonOffset,
33464
+ polygonOffsetFactor: usePolygonOffset ? -4 : 0,
33843
33465
  polygonOffsetUnits: usePolygonOffset ? -4 : 0,
33844
33466
  opacity: isFaux ? FAUX_BOARD_OPACITY : 1
33845
33467
  });
@@ -33857,7 +33479,7 @@ function JscadBoardTextures({
33857
33479
  mesh.frustumCulled = false;
33858
33480
  return mesh;
33859
33481
  };
33860
- const SURFACE_OFFSET = 1e-3;
33482
+ const SURFACE_OFFSET = 5e-3;
33861
33483
  const topBoardMesh = createTexturePlane2(
33862
33484
  textures.topBoard,
33863
33485
  pcbThickness / 2 + SURFACE_OFFSET,
@@ -33900,16 +33522,16 @@ function JscadBoardTextures({
33900
33522
  }
33901
33523
 
33902
33524
  // src/utils/preprocess-circuit-json.ts
33903
- import { su as su12 } from "@tscircuit/circuit-json-util";
33525
+ import { su as su11 } from "@tscircuit/circuit-json-util";
33904
33526
 
33905
33527
  // src/utils/create-faux-board.ts
33906
- import { su as su11, getBoundsOfPcbElements } from "@tscircuit/circuit-json-util";
33528
+ import { su as su10, getBoundsOfPcbElements } from "@tscircuit/circuit-json-util";
33907
33529
  function createFauxBoard(circuitJson) {
33908
- const cadComponents = su11(circuitJson).cad_component.list();
33909
- const pads = su11(circuitJson).pcb_smtpad.list();
33910
- const holes = su11(circuitJson).pcb_hole.list();
33911
- const platedHoles = su11(circuitJson).pcb_plated_hole.list();
33912
- const vias = su11(circuitJson).pcb_via.list();
33530
+ const cadComponents = su10(circuitJson).cad_component.list();
33531
+ const pads = su10(circuitJson).pcb_smtpad.list();
33532
+ const holes = su10(circuitJson).pcb_hole.list();
33533
+ const platedHoles = su10(circuitJson).pcb_plated_hole.list();
33534
+ const vias = su10(circuitJson).pcb_via.list();
33913
33535
  if (cadComponents.length === 0 && pads.length === 0 && holes.length === 0 && platedHoles.length === 0 && vias.length === 0) {
33914
33536
  return null;
33915
33537
  }
@@ -33958,7 +33580,7 @@ function createFauxBoard(circuitJson) {
33958
33580
 
33959
33581
  // src/utils/preprocess-circuit-json.ts
33960
33582
  function addFauxBoardIfNeeded(circuitJson) {
33961
- const boards = su12(circuitJson).pcb_board.list();
33583
+ const boards = su11(circuitJson).pcb_board.list();
33962
33584
  if (boards.length > 0) {
33963
33585
  return circuitJson;
33964
33586
  }
@@ -34009,7 +33631,7 @@ var CadViewerJscad = forwardRef3(
34009
33631
  const initialCameraPosition = useMemo20(() => {
34010
33632
  if (!internalCircuitJson) return [5, -5, 5];
34011
33633
  try {
34012
- const board = su13(internalCircuitJson).pcb_board.list()[0];
33634
+ const board = su12(internalCircuitJson).pcb_board.list()[0];
34013
33635
  if (!board) return [5, -5, 5];
34014
33636
  const { width: width10, height: height10 } = board;
34015
33637
  if (!width10 && !height10) {
@@ -34035,7 +33657,7 @@ var CadViewerJscad = forwardRef3(
34035
33657
  const isFauxBoard = useMemo20(() => {
34036
33658
  if (!internalCircuitJson) return false;
34037
33659
  try {
34038
- const board = su13(internalCircuitJson).pcb_board.list()[0];
33660
+ const board = su12(internalCircuitJson).pcb_board.list()[0];
34039
33661
  return !!board && board.pcb_board_id === "faux-board";
34040
33662
  } catch (e) {
34041
33663
  return false;
@@ -34044,7 +33666,7 @@ var CadViewerJscad = forwardRef3(
34044
33666
  const boardDimensions = useMemo20(() => {
34045
33667
  if (!internalCircuitJson) return void 0;
34046
33668
  try {
34047
- const board = su13(internalCircuitJson).pcb_board.list()[0];
33669
+ const board = su12(internalCircuitJson).pcb_board.list()[0];
34048
33670
  if (!board) return void 0;
34049
33671
  return { width: board.width ?? 0, height: board.height ?? 0 };
34050
33672
  } catch (e) {
@@ -34055,7 +33677,7 @@ var CadViewerJscad = forwardRef3(
34055
33677
  const boardCenter = useMemo20(() => {
34056
33678
  if (!internalCircuitJson) return void 0;
34057
33679
  try {
34058
- const board = su13(internalCircuitJson).pcb_board.list()[0];
33680
+ const board = su12(internalCircuitJson).pcb_board.list()[0];
34059
33681
  if (!board || !board.center) return void 0;
34060
33682
  return { x: board.center.x, y: board.center.y };
34061
33683
  } catch (e) {
@@ -34065,7 +33687,7 @@ var CadViewerJscad = forwardRef3(
34065
33687
  }, [internalCircuitJson]);
34066
33688
  const pcbThickness = usePcbThickness(internalCircuitJson);
34067
33689
  const { stls: boardStls, loading } = useStlsFromGeom(boardGeom);
34068
- const cad_components = su13(internalCircuitJson).cad_component.list();
33690
+ const cad_components = su12(internalCircuitJson).cad_component.list();
34069
33691
  return /* @__PURE__ */ jsxs5(
34070
33692
  CadViewerContainer,
34071
33693
  {
@@ -34119,12 +33741,12 @@ var CadViewerJscad = forwardRef3(
34119
33741
  );
34120
33742
 
34121
33743
  // src/CadViewerManifold.tsx
34122
- import { su as su19 } from "@tscircuit/circuit-json-util";
33744
+ import { su as su18 } from "@tscircuit/circuit-json-util";
34123
33745
  import { useEffect as useEffect25, useMemo as useMemo22, useState as useState16 } from "react";
34124
33746
  import * as THREE35 from "three";
34125
33747
 
34126
33748
  // src/hooks/useManifoldBoardBuilder.ts
34127
- import { su as su18 } from "@tscircuit/circuit-json-util";
33749
+ import { su as su17 } from "@tscircuit/circuit-json-util";
34128
33750
  import { useEffect as useEffect24, useMemo as useMemo21, useRef as useRef9, useState as useState15 } from "react";
34129
33751
  import * as THREE32 from "three";
34130
33752
 
@@ -34187,7 +33809,7 @@ function createManifoldBoard(Manifold, CrossSection, boardData, pcbThickness, ma
34187
33809
  }
34188
33810
 
34189
33811
  // src/utils/manifold/process-cutouts.ts
34190
- import { su as su14 } from "@tscircuit/circuit-json-util";
33812
+ import { su as su13 } from "@tscircuit/circuit-json-util";
34191
33813
 
34192
33814
  // src/utils/pad-geoms.ts
34193
33815
  var RECT_PAD_SEGMENTS2 = 64;
@@ -34246,7 +33868,7 @@ var arePointsClockwise3 = (points) => {
34246
33868
  };
34247
33869
  function processCutoutsForManifold(Manifold, CrossSection, circuitJson, pcbThickness, manifoldInstancesForCleanup) {
34248
33870
  const cutoutOps = [];
34249
- const pcbCutouts = su14(circuitJson).pcb_cutout.list();
33871
+ const pcbCutouts = su13(circuitJson).pcb_cutout.list();
34250
33872
  for (const cutout of pcbCutouts) {
34251
33873
  let cutoutOp;
34252
33874
  const cutoutHeight = pcbThickness * 1.5;
@@ -34341,7 +33963,7 @@ function processCutoutsForManifold(Manifold, CrossSection, circuitJson, pcbThick
34341
33963
  }
34342
33964
 
34343
33965
  // src/utils/manifold/process-non-plated-holes.ts
34344
- import { su as su15 } from "@tscircuit/circuit-json-util";
33966
+ import { su as su14 } from "@tscircuit/circuit-json-util";
34345
33967
 
34346
33968
  // src/utils/hole-geoms.ts
34347
33969
  function createCircleHoleDrill({
@@ -34384,7 +34006,7 @@ function createPlatedHoleDrill({
34384
34006
  // src/utils/manifold/process-non-plated-holes.ts
34385
34007
  function processNonPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbThickness, manifoldInstancesForCleanup) {
34386
34008
  const nonPlatedHoleBoardDrills = [];
34387
- const pcbHoles = su15(circuitJson).pcb_hole.list();
34009
+ const pcbHoles = su14(circuitJson).pcb_hole.list();
34388
34010
  const createPillOp = (width10, height10, depth) => {
34389
34011
  const pillOp = createRoundedRectPrism({
34390
34012
  Manifold,
@@ -34467,7 +34089,7 @@ function processNonPlatedHolesForManifold(Manifold, CrossSection, circuitJson, p
34467
34089
  }
34468
34090
 
34469
34091
  // src/utils/manifold/process-plated-holes.ts
34470
- import { su as su16 } from "@tscircuit/circuit-json-util";
34092
+ import { su as su15 } from "@tscircuit/circuit-json-util";
34471
34093
  import * as THREE30 from "three";
34472
34094
 
34473
34095
  // src/utils/manifold-mesh-to-three-geometry.ts
@@ -34514,9 +34136,12 @@ var createEllipsePoints = (width10, height10, segments) => {
34514
34136
  };
34515
34137
  var COPPER_COLOR = new THREE30.Color(...colors.copper);
34516
34138
  var PLATED_HOLE_LIP_HEIGHT = 0.05;
34139
+ var PLATED_HOLE_PAD_THICKNESS = 3e-3;
34140
+ var PLATED_HOLE_SURFACE_CLEARANCE = 5e-4;
34141
+ var PLATED_HOLE_FILL_CLEARANCE = 4e-3;
34517
34142
  function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbThickness, manifoldInstancesForCleanup, boardClipVolume) {
34518
34143
  const platedHoleBoardDrills = [];
34519
- const pcbPlatedHoles = su16(circuitJson).pcb_plated_hole.list();
34144
+ const pcbPlatedHoles = su15(circuitJson).pcb_plated_hole.list();
34520
34145
  const platedHoleCopperGeoms = [];
34521
34146
  const platedHoleCopperOpsForSubtract = [];
34522
34147
  const createPillOp = (width10, height10, depth) => {
@@ -34733,7 +34358,7 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
34733
34358
  const padWidth = ph.rect_pad_width;
34734
34359
  const padHeight = ph.rect_pad_height;
34735
34360
  const rectBorderRadius = extractRectBorderRadius(ph);
34736
- const padThickness = DEFAULT_SMT_PAD_THICKNESS;
34361
+ const padThickness = PLATED_HOLE_PAD_THICKNESS;
34737
34362
  const drillW = holeW + 2 * MANIFOLD_Z_OFFSET;
34738
34363
  const drillH = holeH + 2 * MANIFOLD_Z_OFFSET;
34739
34364
  const drillDepth = pcbThickness * 1.2;
@@ -34753,8 +34378,11 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
34753
34378
  Manifold,
34754
34379
  width: padWidth,
34755
34380
  height: padHeight,
34756
- thickness: pcbThickness - 2 * padThickness - 2 * BOARD_SURFACE_OFFSET.copper + 0.1,
34757
- // Fill between pads
34381
+ thickness: Math.max(
34382
+ pcbThickness - 2 * (padThickness + PLATED_HOLE_SURFACE_CLEARANCE + PLATED_HOLE_FILL_CLEARANCE),
34383
+ M
34384
+ ),
34385
+ // Fill between pads (recessed from board surfaces)
34758
34386
  borderRadius: rectBorderRadius
34759
34387
  });
34760
34388
  manifoldInstancesForCleanup.push(mainFill);
@@ -34764,20 +34392,28 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
34764
34392
  height: padHeight,
34765
34393
  thickness: padThickness,
34766
34394
  borderRadius: rectBorderRadius
34767
- }).translate([0, 0, pcbThickness / 2 / 2 + BOARD_SURFACE_OFFSET.copper]);
34395
+ }).translate([
34396
+ 0,
34397
+ 0,
34398
+ pcbThickness / 2 + PLATED_HOLE_SURFACE_CLEARANCE + padThickness / 2
34399
+ ]);
34768
34400
  const bottomPad = createRoundedRectPrism({
34769
34401
  Manifold,
34770
34402
  width: padWidth,
34771
34403
  height: padHeight,
34772
34404
  thickness: padThickness,
34773
34405
  borderRadius: rectBorderRadius
34774
- }).translate([0, 0, -pcbThickness / 2 / 2 - BOARD_SURFACE_OFFSET.copper]);
34406
+ }).translate([
34407
+ 0,
34408
+ 0,
34409
+ -pcbThickness / 2 - PLATED_HOLE_SURFACE_CLEARANCE - padThickness / 2
34410
+ ]);
34775
34411
  manifoldInstancesForCleanup.push(topPad, bottomPad);
34776
34412
  const barrelPill = createPillOp(
34777
34413
  holeW,
34778
34414
  holeH,
34779
- pcbThickness * 1.02
34780
- // Slightly taller than board
34415
+ pcbThickness * 0.8
34416
+ // Slightly shorter than board
34781
34417
  ).translate([holeOffsetX, holeOffsetY, 0]);
34782
34418
  manifoldInstancesForCleanup.push(barrelPill);
34783
34419
  const copperUnion = Manifold.union([
@@ -34825,7 +34461,7 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
34825
34461
  const padWidth = ph.rect_pad_width;
34826
34462
  const padHeight = ph.rect_pad_height;
34827
34463
  const rectBorderRadius = extractRectBorderRadius(ph);
34828
- const padThickness = DEFAULT_SMT_PAD_THICKNESS;
34464
+ const padThickness = PLATED_HOLE_PAD_THICKNESS;
34829
34465
  const drillW = holeW + 2 * MANIFOLD_Z_OFFSET;
34830
34466
  const drillH = holeH + 2 * MANIFOLD_Z_OFFSET;
34831
34467
  const drillDepth = pcbThickness * 1.2;
@@ -34852,8 +34488,11 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
34852
34488
  Manifold,
34853
34489
  width: padWidth,
34854
34490
  height: padHeight,
34855
- thickness: pcbThickness - 2 * padThickness - 2 * BOARD_SURFACE_OFFSET.copper + 0.1,
34856
- // Fill between pads
34491
+ thickness: Math.max(
34492
+ pcbThickness - 2 * (padThickness + PLATED_HOLE_SURFACE_CLEARANCE + PLATED_HOLE_FILL_CLEARANCE),
34493
+ M
34494
+ ),
34495
+ // Fill between pads (recessed from board surfaces)
34857
34496
  borderRadius: rectBorderRadius
34858
34497
  });
34859
34498
  manifoldInstancesForCleanup.push(mainFill);
@@ -34868,7 +34507,11 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
34868
34507
  height: padHeight,
34869
34508
  thickness: padThickness,
34870
34509
  borderRadius: rectBorderRadius
34871
- }).translate([0, 0, pcbThickness / 2 / 2 + BOARD_SURFACE_OFFSET.copper]);
34510
+ }).translate([
34511
+ 0,
34512
+ 0,
34513
+ pcbThickness / 2 + PLATED_HOLE_SURFACE_CLEARANCE + padThickness / 2
34514
+ ]);
34872
34515
  if (ph.rect_ccw_rotation) {
34873
34516
  const rotatedTopPad = topPad.rotate([0, 0, ph.rect_ccw_rotation]);
34874
34517
  manifoldInstancesForCleanup.push(rotatedTopPad);
@@ -34880,7 +34523,11 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
34880
34523
  height: padHeight,
34881
34524
  thickness: padThickness,
34882
34525
  borderRadius: rectBorderRadius
34883
- }).translate([0, 0, -pcbThickness / 2 / 2 - BOARD_SURFACE_OFFSET.copper]);
34526
+ }).translate([
34527
+ 0,
34528
+ 0,
34529
+ -pcbThickness / 2 - PLATED_HOLE_SURFACE_CLEARANCE - padThickness / 2
34530
+ ]);
34884
34531
  if (ph.rect_ccw_rotation) {
34885
34532
  const rotatedBottomPad = bottomPad.rotate([0, 0, ph.rect_ccw_rotation]);
34886
34533
  manifoldInstancesForCleanup.push(rotatedBottomPad);
@@ -34890,7 +34537,7 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
34890
34537
  let barrelPill = createPillOp(
34891
34538
  holeW,
34892
34539
  holeH,
34893
- pcbThickness * 1.02
34540
+ pcbThickness * 0.8
34894
34541
  // Slightly taller than board
34895
34542
  ).translate([holeOffsetX, holeOffsetY, 0]);
34896
34543
  if (ph.hole_ccw_rotation) {
@@ -34952,9 +34599,9 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
34952
34599
  const translatedBoardHole = boardHoleOp.translate([ph.x, ph.y, 0]);
34953
34600
  manifoldInstancesForCleanup.push(translatedBoardHole);
34954
34601
  platedHoleBoardDrills.push(translatedBoardHole);
34955
- const padThickness = DEFAULT_SMT_PAD_THICKNESS;
34602
+ const padThickness = PLATED_HOLE_PAD_THICKNESS;
34956
34603
  const fillThickness = Math.max(
34957
- pcbThickness - 2 * padThickness - 2 * BOARD_SURFACE_OFFSET.copper + 0.1,
34604
+ pcbThickness - 2 * (padThickness + PLATED_HOLE_SURFACE_CLEARANCE + PLATED_HOLE_FILL_CLEARANCE),
34958
34605
  M
34959
34606
  );
34960
34607
  const mainFill = createPolygonPadOp({
@@ -34970,12 +34617,12 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
34970
34617
  const topTranslated = topPad.translate([
34971
34618
  0,
34972
34619
  0,
34973
- pcbThickness / 2 / 2 + BOARD_SURFACE_OFFSET.copper
34620
+ pcbThickness / 2 + PLATED_HOLE_SURFACE_CLEARANCE + padThickness / 2
34974
34621
  ]);
34975
34622
  const bottomTranslated = bottomPad.translate([
34976
34623
  0,
34977
34624
  0,
34978
- -pcbThickness / 2 / 2 - BOARD_SURFACE_OFFSET.copper
34625
+ -pcbThickness / 2 - PLATED_HOLE_SURFACE_CLEARANCE - padThickness / 2
34979
34626
  ]);
34980
34627
  manifoldInstancesForCleanup.push(topTranslated, bottomTranslated);
34981
34628
  const barrelOp = createHoleOpForPolygonPad({
@@ -34985,7 +34632,7 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
34985
34632
  if (!barrelOp) return;
34986
34633
  const holeCutOp = createHoleOpForPolygonPad({
34987
34634
  ph,
34988
- depth: pcbThickness * 1.2,
34635
+ depth: pcbThickness * 0.8,
34989
34636
  sizeDelta: -2 * M
34990
34637
  }) || barrelOp;
34991
34638
  const copperUnion = Manifold.union([
@@ -35135,14 +34782,17 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
35135
34782
  const padWidth = ph.rect_pad_width ?? ph.hole_diameter;
35136
34783
  const padHeight = ph.rect_pad_height ?? ph.hole_diameter;
35137
34784
  const rectBorderRadius = extractRectBorderRadius(ph);
35138
- const padThickness = DEFAULT_SMT_PAD_THICKNESS;
34785
+ const padThickness = PLATED_HOLE_PAD_THICKNESS;
35139
34786
  const holeRadius = ph.hole_diameter / 2;
35140
34787
  const mainFill = createRoundedRectPrism({
35141
34788
  Manifold,
35142
34789
  width: padWidth,
35143
34790
  height: padHeight,
35144
- thickness: pcbThickness - 2 * padThickness - 2 * BOARD_SURFACE_OFFSET.copper + 0.1,
35145
- // Fill between pads
34791
+ thickness: Math.max(
34792
+ pcbThickness - 2 * (padThickness + PLATED_HOLE_SURFACE_CLEARANCE + PLATED_HOLE_FILL_CLEARANCE),
34793
+ M
34794
+ ),
34795
+ // Fill between pads (recessed from board surfaces)
35146
34796
  borderRadius: rectBorderRadius
35147
34797
  });
35148
34798
  manifoldInstancesForCleanup.push(mainFill);
@@ -35152,17 +34802,25 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
35152
34802
  height: padHeight,
35153
34803
  thickness: padThickness,
35154
34804
  borderRadius: rectBorderRadius
35155
- }).translate([0, 0, pcbThickness / 2 / 2 + BOARD_SURFACE_OFFSET.copper]);
34805
+ }).translate([
34806
+ 0,
34807
+ 0,
34808
+ pcbThickness / 2 + PLATED_HOLE_SURFACE_CLEARANCE + padThickness / 2
34809
+ ]);
35156
34810
  const bottomPad = createRoundedRectPrism({
35157
34811
  Manifold,
35158
34812
  width: padWidth,
35159
34813
  height: padHeight,
35160
34814
  thickness: padThickness,
35161
34815
  borderRadius: rectBorderRadius
35162
- }).translate([0, 0, -pcbThickness / 2 / 2 - BOARD_SURFACE_OFFSET.copper]);
34816
+ }).translate([
34817
+ 0,
34818
+ 0,
34819
+ -pcbThickness / 2 - PLATED_HOLE_SURFACE_CLEARANCE - padThickness / 2
34820
+ ]);
35163
34821
  manifoldInstancesForCleanup.push(topPad, bottomPad);
35164
34822
  const barrelCylinder = Manifold.cylinder(
35165
- pcbThickness * 1.02,
34823
+ pcbThickness * 0.8,
35166
34824
  // Slightly taller than board
35167
34825
  holeRadius,
35168
34826
  holeRadius,
@@ -35217,7 +34875,7 @@ function processPlatedHolesForManifold(Manifold, CrossSection, circuitJson, pcbT
35217
34875
  }
35218
34876
 
35219
34877
  // src/utils/manifold/process-vias.ts
35220
- import { su as su17 } from "@tscircuit/circuit-json-util";
34878
+ import { su as su16 } from "@tscircuit/circuit-json-util";
35221
34879
  import * as THREE31 from "three";
35222
34880
 
35223
34881
  // src/utils/via-geoms.ts
@@ -35274,7 +34932,7 @@ function createViaCopper2({
35274
34932
  var COPPER_COLOR2 = new THREE31.Color(...colors.copper);
35275
34933
  function processViasForManifold(Manifold, circuitJson, pcbThickness, manifoldInstancesForCleanup, boardClipVolume) {
35276
34934
  const viaBoardDrills = [];
35277
- const pcbVias = su17(circuitJson).pcb_via.list();
34935
+ const pcbVias = su16(circuitJson).pcb_via.list();
35278
34936
  const viaCopperGeoms = [];
35279
34937
  pcbVias.forEach((via, index2) => {
35280
34938
  if (typeof via.hole_diameter === "number") {
@@ -35333,7 +34991,7 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson, visibility) => {
35333
34991
  const panels = circuitJson.filter(
35334
34992
  (e) => e.type === "pcb_panel"
35335
34993
  );
35336
- const boards = su18(circuitJson).pcb_board.list();
34994
+ const boards = su17(circuitJson).pcb_board.list();
35337
34995
  if (panels.length > 0) {
35338
34996
  const panel = panels[0];
35339
34997
  const firstBoardInPanel = boards.find(
@@ -35354,7 +35012,7 @@ var useManifoldBoardBuilder = (manifoldJSModule, circuitJson, visibility) => {
35354
35012
  return boardsNotInPanel.length > 0 ? boardsNotInPanel[0] : null;
35355
35013
  }, [circuitJson]);
35356
35014
  const isFauxBoard = useMemo21(() => {
35357
- const boards = su18(circuitJson).pcb_board.list();
35015
+ const boards = su17(circuitJson).pcb_board.list();
35358
35016
  return boards.length > 0 && boards[0].pcb_board_id === "faux-board";
35359
35017
  }, [circuitJson]);
35360
35018
  const traceTextureResolution = useMemo21(() => {
@@ -35589,7 +35247,10 @@ var createBoardMaterial = ({
35589
35247
  clearcoat: 0,
35590
35248
  transparent: isFaux,
35591
35249
  opacity: isFaux ? FAUX_BOARD_OPACITY : 1,
35592
- flatShading: true
35250
+ flatShading: true,
35251
+ polygonOffset: true,
35252
+ polygonOffsetFactor: 1,
35253
+ polygonOffsetUnits: 1
35593
35254
  });
35594
35255
  }
35595
35256
  return new THREE33.MeshStandardMaterial({
@@ -35599,7 +35260,10 @@ var createBoardMaterial = ({
35599
35260
  metalness: 0.1,
35600
35261
  roughness: 0.8,
35601
35262
  transparent: true,
35602
- opacity: isFaux ? FAUX_BOARD_OPACITY : 0.9
35263
+ opacity: isFaux ? FAUX_BOARD_OPACITY : 0.9,
35264
+ polygonOffset: true,
35265
+ polygonOffsetFactor: 1,
35266
+ polygonOffsetUnits: 1
35603
35267
  });
35604
35268
  };
35605
35269
 
@@ -35628,11 +35292,8 @@ function createGeometryMeshes(geoms) {
35628
35292
  new THREE34.MeshStandardMaterial({
35629
35293
  color: comp.color,
35630
35294
  side: THREE34.DoubleSide,
35631
- flatShading: true,
35295
+ flatShading: true
35632
35296
  // Consistent with board
35633
- polygonOffset: true,
35634
- polygonOffsetFactor: -1,
35635
- polygonOffsetUnits: -1
35636
35297
  })
35637
35298
  );
35638
35299
  mesh.name = comp.key;
@@ -35809,7 +35470,7 @@ try {
35809
35470
  [textures, boardData, pcbThickness, isFauxBoard]
35810
35471
  );
35811
35472
  const cadComponents = useMemo22(
35812
- () => su19(circuitJson).cad_component.list(),
35473
+ () => su18(circuitJson).cad_component.list(),
35813
35474
  [circuitJson]
35814
35475
  );
35815
35476
  const boardDimensions = useMemo22(() => {
@@ -42823,7 +42484,7 @@ var CadViewer = (props) => {
42823
42484
 
42824
42485
  // src/convert-circuit-json-to-3d-svg.ts
42825
42486
  var import_debug = __toESM(require_browser(), 1);
42826
- import { su as su20 } from "@tscircuit/circuit-json-util";
42487
+ import { su as su19 } from "@tscircuit/circuit-json-util";
42827
42488
  import * as THREE40 from "three";
42828
42489
  import { SVGRenderer } from "three/examples/jsm/renderers/SVGRenderer.js";
42829
42490
 
@@ -43052,11 +42713,11 @@ async function convertCircuitJsonTo3dSvg(circuitJson, options = {}) {
43052
42713
  const pointLight = new THREE40.PointLight(16777215, Math.PI / 4);
43053
42714
  pointLight.position.set(-10, -10, 10);
43054
42715
  scene.add(pointLight);
43055
- const components = su20(circuitJson).cad_component.list();
42716
+ const components = su19(circuitJson).cad_component.list();
43056
42717
  for (const component of components) {
43057
42718
  await renderComponent(component, scene);
43058
42719
  }
43059
- const boardData = su20(circuitJson).pcb_board.list()[0];
42720
+ const boardData = su19(circuitJson).pcb_board.list()[0];
43060
42721
  const boardGeom = createBoardGeomFromCircuitJson(circuitJson);
43061
42722
  if (boardGeom) {
43062
42723
  const solderMaskColor = colors.fr4SolderMaskGreen;