@tscircuit/pcb-viewer 1.11.268 → 1.11.270

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
@@ -6278,7 +6278,8 @@ var STORAGE_KEYS = {
6278
6278
  IS_SHOWING_PCB_GROUPS: "pcb_viewer_is_showing_pcb_groups",
6279
6279
  PCB_GROUP_VIEW_MODE: "pcb_viewer_group_view_mode",
6280
6280
  IS_SHOWING_COPPER_POURS: "pcb_viewer_is_showing_copper_pours",
6281
- IS_SHOWING_GROUP_ANCHOR_OFFSETS: "pcb_viewer_is_showing_group_anchor_offsets"
6281
+ IS_SHOWING_GROUP_ANCHOR_OFFSETS: "pcb_viewer_is_showing_group_anchor_offsets",
6282
+ IS_SHOWING_SOLDER_MASK: "pcb_viewer_is_showing_solder_mask"
6282
6283
  };
6283
6284
  var getStoredBoolean = (key, defaultValue) => {
6284
6285
  if (typeof window === "undefined") return defaultValue;
@@ -6338,6 +6339,10 @@ var createStore = (initialState = {}, disablePcbGroups = false) => createZustand
6338
6339
  STORAGE_KEYS.IS_SHOWING_GROUP_ANCHOR_OFFSETS,
6339
6340
  true
6340
6341
  ),
6342
+ is_showing_solder_mask: getStoredBoolean(
6343
+ STORAGE_KEYS.IS_SHOWING_SOLDER_MASK,
6344
+ false
6345
+ ),
6341
6346
  pcb_group_view_mode: disablePcbGroups ? "all" : getStoredString(
6342
6347
  STORAGE_KEYS.PCB_GROUP_VIEW_MODE,
6343
6348
  DEFAULT_PCB_GROUP_VIEW_MODE
@@ -6375,6 +6380,10 @@ var createStore = (initialState = {}, disablePcbGroups = false) => createZustand
6375
6380
  );
6376
6381
  set({ is_showing_group_anchor_offsets: is_showing });
6377
6382
  },
6383
+ setIsShowingSolderMask: (is_showing) => {
6384
+ setStoredBoolean(STORAGE_KEYS.IS_SHOWING_SOLDER_MASK, is_showing);
6385
+ set({ is_showing_solder_mask: is_showing });
6386
+ },
6378
6387
  setPcbGroupViewMode: (mode) => {
6379
6388
  if (disablePcbGroups) return;
6380
6389
  setStoredString(STORAGE_KEYS.PCB_GROUP_VIEW_MODE, mode);
@@ -7139,76 +7148,123 @@ var convertElementToPrimitives = (element, allElements) => {
7139
7148
  }
7140
7149
  case "pcb_board": {
7141
7150
  const { width, height, center, outline } = element;
7151
+ const primitives = [];
7152
+ const hasSolderMask = allElements.some(
7153
+ (elm) => elm.type === "pcb_smtpad" && elm.is_covered_with_solder_mask === true
7154
+ );
7155
+ if (hasSolderMask) {
7156
+ if (outline && outline.length > 2) {
7157
+ primitives.push({
7158
+ _pcb_drawing_object_id: `polygon_${globalPcbDrawingObjectCount++}`,
7159
+ pcb_drawing_type: "polygon",
7160
+ points: normalizePolygonPoints(outline),
7161
+ layer: "soldermask_top",
7162
+ _element: element
7163
+ });
7164
+ primitives.push({
7165
+ _pcb_drawing_object_id: `polygon_${globalPcbDrawingObjectCount++}`,
7166
+ pcb_drawing_type: "polygon",
7167
+ points: normalizePolygonPoints(outline),
7168
+ layer: "soldermask_bottom",
7169
+ _element: element
7170
+ });
7171
+ } else if (width && height) {
7172
+ primitives.push({
7173
+ _pcb_drawing_object_id: `rect_${globalPcbDrawingObjectCount++}`,
7174
+ pcb_drawing_type: "rect",
7175
+ x: center.x,
7176
+ y: center.y,
7177
+ w: width,
7178
+ h: height,
7179
+ layer: "soldermask_top",
7180
+ _element: element
7181
+ });
7182
+ primitives.push({
7183
+ _pcb_drawing_object_id: `rect_${globalPcbDrawingObjectCount++}`,
7184
+ pcb_drawing_type: "rect",
7185
+ x: center.x,
7186
+ y: center.y,
7187
+ w: width,
7188
+ h: height,
7189
+ layer: "soldermask_bottom",
7190
+ _element: element
7191
+ });
7192
+ }
7193
+ }
7142
7194
  if (outline && outline.length > 2) {
7143
- return outline.map((point, index, array) => ({
7144
- _pcb_drawing_object_id: `line_${globalPcbDrawingObjectCount++}`,
7145
- pcb_drawing_type: "line",
7146
- x1: point.x,
7147
- y1: point.y,
7148
- x2: index === array.length - 1 ? array[0].x : array[index + 1].x,
7149
- y2: index === array.length - 1 ? array[0].y : array[index + 1].y,
7150
- width: 1,
7151
- zoomIndependent: true,
7152
- layer: "board",
7153
- _element: element
7154
- }));
7195
+ primitives.push(
7196
+ ...outline.map((point, index, array) => ({
7197
+ _pcb_drawing_object_id: `line_${globalPcbDrawingObjectCount++}`,
7198
+ pcb_drawing_type: "line",
7199
+ x1: point.x,
7200
+ y1: point.y,
7201
+ x2: index === array.length - 1 ? array[0].x : array[index + 1].x,
7202
+ y2: index === array.length - 1 ? array[0].y : array[index + 1].y,
7203
+ width: 1,
7204
+ zoomIndependent: true,
7205
+ layer: "board",
7206
+ _element: element
7207
+ }))
7208
+ );
7209
+ } else {
7210
+ primitives.push(
7211
+ {
7212
+ _pcb_drawing_object_id: `line_${globalPcbDrawingObjectCount++}`,
7213
+ pcb_drawing_type: "line",
7214
+ x1: center.x - width / 2,
7215
+ y1: center.y - height / 2,
7216
+ x2: center.x + width / 2,
7217
+ y2: center.y - height / 2,
7218
+ width: 1,
7219
+ zoomIndependent: true,
7220
+ layer: "board",
7221
+ _element: element
7222
+ },
7223
+ {
7224
+ _pcb_drawing_object_id: `line_${globalPcbDrawingObjectCount++}`,
7225
+ pcb_drawing_type: "line",
7226
+ x1: center.x - width / 2,
7227
+ y1: center.y + height / 2,
7228
+ x2: center.x + width / 2,
7229
+ y2: center.y + height / 2,
7230
+ width: 1,
7231
+ zoomIndependent: true,
7232
+ layer: "board",
7233
+ _element: element
7234
+ },
7235
+ {
7236
+ _pcb_drawing_object_id: `line_${globalPcbDrawingObjectCount++}`,
7237
+ pcb_drawing_type: "line",
7238
+ x1: center.x - width / 2,
7239
+ y1: center.y - height / 2,
7240
+ x2: center.x - width / 2,
7241
+ y2: center.y + height / 2,
7242
+ width: 1,
7243
+ zoomIndependent: true,
7244
+ layer: "board",
7245
+ _element: element
7246
+ },
7247
+ {
7248
+ _pcb_drawing_object_id: `line_${globalPcbDrawingObjectCount++}`,
7249
+ pcb_drawing_type: "line",
7250
+ x1: center.x + width / 2,
7251
+ y1: center.y - height / 2,
7252
+ x2: center.x + width / 2,
7253
+ y2: center.y + height / 2,
7254
+ width: 1,
7255
+ zoomIndependent: true,
7256
+ layer: "board",
7257
+ _element: element
7258
+ }
7259
+ );
7155
7260
  }
7156
- return [
7157
- {
7158
- _pcb_drawing_object_id: `line_${globalPcbDrawingObjectCount++}`,
7159
- pcb_drawing_type: "line",
7160
- x1: center.x - width / 2,
7161
- y1: center.y - height / 2,
7162
- x2: center.x + width / 2,
7163
- y2: center.y - height / 2,
7164
- width: 1,
7165
- zoomIndependent: true,
7166
- layer: "board",
7167
- _element: element
7168
- },
7169
- {
7170
- _pcb_drawing_object_id: `line_${globalPcbDrawingObjectCount++}`,
7171
- pcb_drawing_type: "line",
7172
- x1: center.x - width / 2,
7173
- y1: center.y + height / 2,
7174
- x2: center.x + width / 2,
7175
- y2: center.y + height / 2,
7176
- width: 1,
7177
- zoomIndependent: true,
7178
- layer: "board",
7179
- _element: element
7180
- },
7181
- {
7182
- _pcb_drawing_object_id: `line_${globalPcbDrawingObjectCount++}`,
7183
- pcb_drawing_type: "line",
7184
- x1: center.x - width / 2,
7185
- y1: center.y - height / 2,
7186
- x2: center.x - width / 2,
7187
- y2: center.y + height / 2,
7188
- width: 1,
7189
- zoomIndependent: true,
7190
- layer: "board",
7191
- _element: element
7192
- },
7193
- {
7194
- _pcb_drawing_object_id: `line_${globalPcbDrawingObjectCount++}`,
7195
- pcb_drawing_type: "line",
7196
- x1: center.x + width / 2,
7197
- y1: center.y - height / 2,
7198
- x2: center.x + width / 2,
7199
- y2: center.y + height / 2,
7200
- width: 1,
7201
- zoomIndependent: true,
7202
- layer: "board",
7203
- _element: element
7204
- }
7205
- ];
7261
+ return primitives;
7206
7262
  }
7207
7263
  case "pcb_smtpad": {
7208
7264
  if (element.shape === "rect" || element.shape === "rotated_rect") {
7209
- const { shape, x, y, width, height, layer, rect_border_radius } = element;
7265
+ const { x, y, width, height, layer, rect_border_radius } = element;
7210
7266
  const corner_radius = element.corner_radius ?? rect_border_radius ?? 0;
7211
- return [
7267
+ const primitives = [
7212
7268
  {
7213
7269
  _pcb_drawing_object_id: `rect_${globalPcbDrawingObjectCount++}`,
7214
7270
  pcb_drawing_type: "rect",
@@ -7225,9 +7281,152 @@ var convertElementToPrimitives = (element, allElements) => {
7225
7281
  roundness: corner_radius
7226
7282
  }
7227
7283
  ];
7284
+ if (element.is_covered_with_solder_mask) {
7285
+ const rawMargin = element.soldermask_margin;
7286
+ const maskLayer = layer === "bottom" ? "soldermask_with_copper_bottom" : "soldermask_with_copper_top";
7287
+ if (rawMargin === void 0 || rawMargin === null) {
7288
+ const soldermask_margin2 = 0;
7289
+ const openingWidth = Math.max(0.01, width + 2 * soldermask_margin2);
7290
+ const openingHeight = Math.max(0.01, height + 2 * soldermask_margin2);
7291
+ const openingPrimitive = {
7292
+ _pcb_drawing_object_id: `rect_${globalPcbDrawingObjectCount++}`,
7293
+ pcb_drawing_type: "rect",
7294
+ x,
7295
+ y,
7296
+ w: openingWidth,
7297
+ h: openingHeight,
7298
+ layer: maskLayer,
7299
+ _element: element,
7300
+ _parent_pcb_component,
7301
+ _parent_source_component,
7302
+ _source_port,
7303
+ ccw_rotation: element.ccw_rotation,
7304
+ roundness: corner_radius
7305
+ };
7306
+ if (element.solder_mask_color) {
7307
+ openingPrimitive.color = element.solder_mask_color;
7308
+ }
7309
+ primitives.push(openingPrimitive);
7310
+ const maskCoverageLayer = layer === "bottom" ? "soldermask_bottom" : "soldermask_top";
7311
+ const fullMaskCoverage = {
7312
+ _pcb_drawing_object_id: `rect_${globalPcbDrawingObjectCount++}`,
7313
+ pcb_drawing_type: "rect",
7314
+ x,
7315
+ y,
7316
+ w: width,
7317
+ h: height,
7318
+ layer: maskCoverageLayer,
7319
+ _element: element,
7320
+ _parent_pcb_component,
7321
+ _parent_source_component,
7322
+ _source_port,
7323
+ ccw_rotation: element.ccw_rotation,
7324
+ roundness: corner_radius
7325
+ };
7326
+ primitives.push(fullMaskCoverage);
7327
+ const cutoutOpening = {
7328
+ _pcb_drawing_object_id: `rect_${globalPcbDrawingObjectCount++}`,
7329
+ pcb_drawing_type: "rect",
7330
+ x,
7331
+ y,
7332
+ w: openingWidth,
7333
+ h: openingHeight,
7334
+ layer: maskCoverageLayer,
7335
+ _element: element,
7336
+ _parent_pcb_component,
7337
+ _parent_source_component,
7338
+ _source_port,
7339
+ ccw_rotation: element.ccw_rotation,
7340
+ roundness: corner_radius,
7341
+ composite_mode: "destination-out"
7342
+ };
7343
+ primitives.push(cutoutOpening);
7344
+ return primitives;
7345
+ }
7346
+ const soldermask_margin = rawMargin;
7347
+ if (soldermask_margin === 0) {
7348
+ return primitives;
7349
+ }
7350
+ if (soldermask_margin > 0) {
7351
+ const marginRing = {
7352
+ _pcb_drawing_object_id: `rect_${globalPcbDrawingObjectCount++}`,
7353
+ pcb_drawing_type: "rect",
7354
+ x,
7355
+ y,
7356
+ w: width + 2 * soldermask_margin,
7357
+ h: height + 2 * soldermask_margin,
7358
+ layer: maskLayer,
7359
+ _element: element,
7360
+ _parent_pcb_component,
7361
+ _parent_source_component,
7362
+ _source_port,
7363
+ ccw_rotation: element.ccw_rotation,
7364
+ roundness: corner_radius,
7365
+ color: "rgb(201, 162, 110)"
7366
+ };
7367
+ primitives.push(marginRing);
7368
+ const cutout = {
7369
+ _pcb_drawing_object_id: `rect_${globalPcbDrawingObjectCount++}`,
7370
+ pcb_drawing_type: "rect",
7371
+ x,
7372
+ y,
7373
+ w: width,
7374
+ h: height,
7375
+ layer: maskLayer,
7376
+ _element: element,
7377
+ _parent_pcb_component,
7378
+ _parent_source_component,
7379
+ _source_port,
7380
+ ccw_rotation: element.ccw_rotation,
7381
+ roundness: corner_radius,
7382
+ composite_mode: "destination-out"
7383
+ };
7384
+ primitives.push(cutout);
7385
+ } else {
7386
+ const openingWidth = Math.max(0.01, width + 2 * soldermask_margin);
7387
+ const openingHeight = Math.max(0.01, height + 2 * soldermask_margin);
7388
+ const innerRingBase = {
7389
+ _pcb_drawing_object_id: `rect_${globalPcbDrawingObjectCount++}`,
7390
+ pcb_drawing_type: "rect",
7391
+ x,
7392
+ y,
7393
+ w: width,
7394
+ h: height,
7395
+ layer: maskLayer,
7396
+ _element: element,
7397
+ _parent_pcb_component,
7398
+ _parent_source_component,
7399
+ _source_port,
7400
+ ccw_rotation: element.ccw_rotation,
7401
+ roundness: corner_radius
7402
+ };
7403
+ if (element.solder_mask_color) {
7404
+ innerRingBase.color = element.solder_mask_color;
7405
+ }
7406
+ primitives.push(innerRingBase);
7407
+ const innerCutout = {
7408
+ _pcb_drawing_object_id: `rect_${globalPcbDrawingObjectCount++}`,
7409
+ pcb_drawing_type: "rect",
7410
+ x,
7411
+ y,
7412
+ w: openingWidth,
7413
+ h: openingHeight,
7414
+ layer: maskLayer,
7415
+ _element: element,
7416
+ _parent_pcb_component,
7417
+ _parent_source_component,
7418
+ _source_port,
7419
+ ccw_rotation: element.ccw_rotation,
7420
+ roundness: corner_radius,
7421
+ composite_mode: "destination-out"
7422
+ };
7423
+ primitives.push(innerCutout);
7424
+ }
7425
+ }
7426
+ return primitives;
7228
7427
  } else if (element.shape === "circle") {
7229
7428
  const { x, y, radius, layer } = element;
7230
- return [
7429
+ const primitives = [
7231
7430
  {
7232
7431
  _pcb_drawing_object_id: `circle_${globalPcbDrawingObjectCount++}`,
7233
7432
  pcb_drawing_type: "circle",
@@ -7241,9 +7440,129 @@ var convertElementToPrimitives = (element, allElements) => {
7241
7440
  _source_port
7242
7441
  }
7243
7442
  ];
7443
+ if (element.is_covered_with_solder_mask) {
7444
+ const rawMargin = element.soldermask_margin;
7445
+ const maskLayer = layer === "bottom" ? "soldermask_with_copper_bottom" : "soldermask_with_copper_top";
7446
+ if (rawMargin === void 0 || rawMargin === null) {
7447
+ const soldermask_margin2 = 0;
7448
+ const openingRadius = Math.max(0.01, radius + soldermask_margin2);
7449
+ const openingPrimitive = {
7450
+ _pcb_drawing_object_id: `circle_${globalPcbDrawingObjectCount++}`,
7451
+ pcb_drawing_type: "circle",
7452
+ x,
7453
+ y,
7454
+ r: openingRadius,
7455
+ layer: maskLayer,
7456
+ _element: element,
7457
+ _parent_pcb_component,
7458
+ _parent_source_component,
7459
+ _source_port
7460
+ };
7461
+ if (element.solder_mask_color) {
7462
+ openingPrimitive.color = element.solder_mask_color;
7463
+ }
7464
+ primitives.push(openingPrimitive);
7465
+ const maskCoverageLayer = layer === "bottom" ? "soldermask_bottom" : "soldermask_top";
7466
+ const fullMaskCoverage = {
7467
+ _pcb_drawing_object_id: `circle_${globalPcbDrawingObjectCount++}`,
7468
+ pcb_drawing_type: "circle",
7469
+ x,
7470
+ y,
7471
+ r: radius,
7472
+ layer: maskCoverageLayer,
7473
+ _element: element,
7474
+ _parent_pcb_component,
7475
+ _parent_source_component,
7476
+ _source_port
7477
+ };
7478
+ primitives.push(fullMaskCoverage);
7479
+ const cutoutOpening = {
7480
+ _pcb_drawing_object_id: `circle_${globalPcbDrawingObjectCount++}`,
7481
+ pcb_drawing_type: "circle",
7482
+ x,
7483
+ y,
7484
+ r: openingRadius,
7485
+ layer: maskCoverageLayer,
7486
+ _element: element,
7487
+ _parent_pcb_component,
7488
+ _parent_source_component,
7489
+ _source_port,
7490
+ composite_mode: "destination-out"
7491
+ };
7492
+ primitives.push(cutoutOpening);
7493
+ return primitives;
7494
+ }
7495
+ const soldermask_margin = rawMargin;
7496
+ if (soldermask_margin === 0) {
7497
+ return primitives;
7498
+ }
7499
+ if (soldermask_margin > 0) {
7500
+ const marginRing = {
7501
+ _pcb_drawing_object_id: `circle_${globalPcbDrawingObjectCount++}`,
7502
+ pcb_drawing_type: "circle",
7503
+ x,
7504
+ y,
7505
+ r: radius + soldermask_margin,
7506
+ layer: maskLayer,
7507
+ _element: element,
7508
+ _parent_pcb_component,
7509
+ _parent_source_component,
7510
+ _source_port,
7511
+ color: "rgb(201, 162, 110)"
7512
+ };
7513
+ primitives.push(marginRing);
7514
+ const cutout = {
7515
+ _pcb_drawing_object_id: `circle_${globalPcbDrawingObjectCount++}`,
7516
+ pcb_drawing_type: "circle",
7517
+ x,
7518
+ y,
7519
+ r: radius,
7520
+ layer: maskLayer,
7521
+ _element: element,
7522
+ _parent_pcb_component,
7523
+ _parent_source_component,
7524
+ _source_port,
7525
+ composite_mode: "destination-out"
7526
+ };
7527
+ primitives.push(cutout);
7528
+ } else {
7529
+ const openingRadius = Math.max(0.01, radius + soldermask_margin);
7530
+ const innerRingBase = {
7531
+ _pcb_drawing_object_id: `circle_${globalPcbDrawingObjectCount++}`,
7532
+ pcb_drawing_type: "circle",
7533
+ x,
7534
+ y,
7535
+ r: radius,
7536
+ layer: maskLayer,
7537
+ _element: element,
7538
+ _parent_pcb_component,
7539
+ _parent_source_component,
7540
+ _source_port
7541
+ };
7542
+ if (element.solder_mask_color) {
7543
+ innerRingBase.color = element.solder_mask_color;
7544
+ }
7545
+ primitives.push(innerRingBase);
7546
+ const innerCutout = {
7547
+ _pcb_drawing_object_id: `circle_${globalPcbDrawingObjectCount++}`,
7548
+ pcb_drawing_type: "circle",
7549
+ x,
7550
+ y,
7551
+ r: openingRadius,
7552
+ layer: maskLayer,
7553
+ _element: element,
7554
+ _parent_pcb_component,
7555
+ _parent_source_component,
7556
+ _source_port,
7557
+ composite_mode: "destination-out"
7558
+ };
7559
+ primitives.push(innerCutout);
7560
+ }
7561
+ }
7562
+ return primitives;
7244
7563
  } else if (element.shape === "polygon") {
7245
7564
  const { layer, points } = element;
7246
- return [
7565
+ const primitives = [
7247
7566
  {
7248
7567
  _pcb_drawing_object_id: `polygon_${globalPcbDrawingObjectCount++}`,
7249
7568
  pcb_drawing_type: "polygon",
@@ -7255,9 +7574,27 @@ var convertElementToPrimitives = (element, allElements) => {
7255
7574
  _source_port
7256
7575
  }
7257
7576
  ];
7577
+ if (element.is_covered_with_solder_mask) {
7578
+ const maskLayer = layer === "bottom" ? "soldermask_with_copper_bottom" : "soldermask_with_copper_top";
7579
+ const maskPrimitive = {
7580
+ _pcb_drawing_object_id: `polygon_${globalPcbDrawingObjectCount++}`,
7581
+ pcb_drawing_type: "polygon",
7582
+ points: normalizePolygonPoints(points),
7583
+ layer: maskLayer,
7584
+ _element: element,
7585
+ _parent_pcb_component,
7586
+ _parent_source_component,
7587
+ _source_port
7588
+ };
7589
+ if (element.solder_mask_color) {
7590
+ maskPrimitive.color = element.solder_mask_color;
7591
+ }
7592
+ primitives.push(maskPrimitive);
7593
+ }
7594
+ return primitives;
7258
7595
  } else if (element.shape === "pill" || element.shape === "rotated_pill") {
7259
7596
  const { x, y, width, height, layer } = element;
7260
- return [
7597
+ const primitives = [
7261
7598
  {
7262
7599
  _pcb_drawing_object_id: `pill_${globalPcbDrawingObjectCount++}`,
7263
7600
  pcb_drawing_type: "pill",
@@ -7273,6 +7610,142 @@ var convertElementToPrimitives = (element, allElements) => {
7273
7610
  ccw_rotation: element.ccw_rotation
7274
7611
  }
7275
7612
  ];
7613
+ if (element.is_covered_with_solder_mask) {
7614
+ const rawMargin = element.soldermask_margin;
7615
+ const maskLayer = layer === "bottom" ? "soldermask_with_copper_bottom" : "soldermask_with_copper_top";
7616
+ if (rawMargin === void 0 || rawMargin === null) {
7617
+ const soldermask_margin2 = 0;
7618
+ const openingWidth = Math.max(0.01, width + 2 * soldermask_margin2);
7619
+ const openingHeight = Math.max(0.01, height + 2 * soldermask_margin2);
7620
+ const openingPrimitive = {
7621
+ _pcb_drawing_object_id: `pill_${globalPcbDrawingObjectCount++}`,
7622
+ pcb_drawing_type: "pill",
7623
+ x,
7624
+ y,
7625
+ w: openingWidth,
7626
+ h: openingHeight,
7627
+ layer: maskLayer,
7628
+ _element: element,
7629
+ _parent_pcb_component,
7630
+ _parent_source_component,
7631
+ _source_port,
7632
+ ccw_rotation: element.ccw_rotation
7633
+ };
7634
+ if (element.solder_mask_color) {
7635
+ openingPrimitive.color = element.solder_mask_color;
7636
+ }
7637
+ primitives.push(openingPrimitive);
7638
+ const maskCoverageLayer = layer === "bottom" ? "soldermask_bottom" : "soldermask_top";
7639
+ const fullMaskCoverage = {
7640
+ _pcb_drawing_object_id: `pill_${globalPcbDrawingObjectCount++}`,
7641
+ pcb_drawing_type: "pill",
7642
+ x,
7643
+ y,
7644
+ w: width,
7645
+ h: height,
7646
+ layer: maskCoverageLayer,
7647
+ _element: element,
7648
+ _parent_pcb_component,
7649
+ _parent_source_component,
7650
+ _source_port,
7651
+ ccw_rotation: element.ccw_rotation
7652
+ };
7653
+ primitives.push(fullMaskCoverage);
7654
+ const cutoutOpening = {
7655
+ _pcb_drawing_object_id: `pill_${globalPcbDrawingObjectCount++}`,
7656
+ pcb_drawing_type: "pill",
7657
+ x,
7658
+ y,
7659
+ w: openingWidth,
7660
+ h: openingHeight,
7661
+ layer: maskCoverageLayer,
7662
+ _element: element,
7663
+ _parent_pcb_component,
7664
+ _parent_source_component,
7665
+ _source_port,
7666
+ ccw_rotation: element.ccw_rotation,
7667
+ composite_mode: "destination-out"
7668
+ };
7669
+ primitives.push(cutoutOpening);
7670
+ return primitives;
7671
+ }
7672
+ const soldermask_margin = rawMargin;
7673
+ if (soldermask_margin === 0) {
7674
+ return primitives;
7675
+ }
7676
+ if (soldermask_margin > 0) {
7677
+ const marginRing = {
7678
+ _pcb_drawing_object_id: `pill_${globalPcbDrawingObjectCount++}`,
7679
+ pcb_drawing_type: "pill",
7680
+ x,
7681
+ y,
7682
+ w: width + 2 * soldermask_margin,
7683
+ h: height + 2 * soldermask_margin,
7684
+ layer: maskLayer,
7685
+ _element: element,
7686
+ _parent_pcb_component,
7687
+ _parent_source_component,
7688
+ _source_port,
7689
+ ccw_rotation: element.ccw_rotation,
7690
+ color: "rgb(201, 162, 110)"
7691
+ };
7692
+ primitives.push(marginRing);
7693
+ const cutout = {
7694
+ _pcb_drawing_object_id: `pill_${globalPcbDrawingObjectCount++}`,
7695
+ pcb_drawing_type: "pill",
7696
+ x,
7697
+ y,
7698
+ w: width,
7699
+ h: height,
7700
+ layer: maskLayer,
7701
+ _element: element,
7702
+ _parent_pcb_component,
7703
+ _parent_source_component,
7704
+ _source_port,
7705
+ ccw_rotation: element.ccw_rotation,
7706
+ composite_mode: "destination-out"
7707
+ };
7708
+ primitives.push(cutout);
7709
+ } else {
7710
+ const openingWidth = Math.max(0.01, width + 2 * soldermask_margin);
7711
+ const openingHeight = Math.max(0.01, height + 2 * soldermask_margin);
7712
+ const innerRingBase = {
7713
+ _pcb_drawing_object_id: `pill_${globalPcbDrawingObjectCount++}`,
7714
+ pcb_drawing_type: "pill",
7715
+ x,
7716
+ y,
7717
+ w: width,
7718
+ h: height,
7719
+ layer: maskLayer,
7720
+ _element: element,
7721
+ _parent_pcb_component,
7722
+ _parent_source_component,
7723
+ _source_port,
7724
+ ccw_rotation: element.ccw_rotation
7725
+ };
7726
+ if (element.solder_mask_color) {
7727
+ innerRingBase.color = element.solder_mask_color;
7728
+ }
7729
+ primitives.push(innerRingBase);
7730
+ const innerCutout = {
7731
+ _pcb_drawing_object_id: `pill_${globalPcbDrawingObjectCount++}`,
7732
+ pcb_drawing_type: "pill",
7733
+ x,
7734
+ y,
7735
+ w: openingWidth,
7736
+ h: openingHeight,
7737
+ layer: maskLayer,
7738
+ _element: element,
7739
+ _parent_pcb_component,
7740
+ _parent_source_component,
7741
+ _source_port,
7742
+ ccw_rotation: element.ccw_rotation,
7743
+ composite_mode: "destination-out"
7744
+ };
7745
+ primitives.push(innerCutout);
7746
+ }
7747
+ }
7748
+ return primitives;
7276
7749
  }
7277
7750
  return [];
7278
7751
  }
@@ -8543,6 +9016,14 @@ var colors_default = {
8543
9016
  plated_hole: "rgb(26, 196, 210)",
8544
9017
  ratsnest: "rgba(245, 255, 213, 0.702)",
8545
9018
  select_overlay: "rgb(4, 255, 67)",
9019
+ soldermaskWithCopper: {
9020
+ top: "rgb(18, 82, 50)",
9021
+ bottom: "rgb(77, 127, 196)"
9022
+ },
9023
+ soldermask: {
9024
+ top: "rgb(12, 55, 33)",
9025
+ bottom: "rgb(12, 55, 33)"
9026
+ },
8546
9027
  through_via: "rgb(236, 236, 236)",
8547
9028
  user_1: "rgb(194, 194, 194)",
8548
9029
  user_2: "rgb(89, 148, 220)",
@@ -8739,6 +9220,10 @@ var LAYER_NAME_TO_COLOR = {
8739
9220
  bottom_silkscreen: colors_default.board.b_silks,
8740
9221
  top_fabrication: colors_default.board.f_fab,
8741
9222
  bottom_fabrication: colors_default.board.b_fab,
9223
+ soldermask_top: colors_default.board.soldermask.top,
9224
+ soldermask_bottom: colors_default.board.soldermask.bottom,
9225
+ soldermask_with_copper_top: colors_default.board.soldermaskWithCopper.top,
9226
+ soldermask_with_copper_bottom: colors_default.board.soldermaskWithCopper.bottom,
8742
9227
  notes: colors_default.board.user_2,
8743
9228
  ...colors_default.board
8744
9229
  };
@@ -8750,9 +9235,13 @@ var DEFAULT_DRAW_ORDER = [
8750
9235
  "inner2",
8751
9236
  "inner1",
8752
9237
  "bottom",
9238
+ "soldermask_bottom",
8753
9239
  "bottom_silkscreen",
8754
9240
  "top",
9241
+ "soldermask_top",
8755
9242
  "top_silkscreen",
9243
+ "soldermask_with_copper_bottom",
9244
+ "soldermask_with_copper_top",
8756
9245
  "board"
8757
9246
  ];
8758
9247
  var Drawer = class {
@@ -8763,6 +9252,7 @@ var Drawer = class {
8763
9252
  transform;
8764
9253
  foregroundLayer = "top";
8765
9254
  lastPoint;
9255
+ _tempCompositeMode;
8766
9256
  constructor(canvasLayerMap) {
8767
9257
  this.canvasLayerMap = canvasLayerMap;
8768
9258
  this.ctxLayerMap = Object.fromEntries(
@@ -9044,23 +9534,27 @@ var Drawer = class {
9044
9534
  */
9045
9535
  orderAndFadeLayers() {
9046
9536
  const { canvasLayerMap, foregroundLayer } = this;
9537
+ const associatedSilkscreen = foregroundLayer === "top" ? "top_silkscreen" : foregroundLayer === "bottom" ? "bottom_silkscreen" : void 0;
9538
+ const maskWithCopperLayerForForeground = foregroundLayer === "top" ? "soldermask_with_copper_top" : foregroundLayer === "bottom" ? "soldermask_with_copper_bottom" : void 0;
9047
9539
  const opaqueLayers = /* @__PURE__ */ new Set([
9048
9540
  foregroundLayer,
9049
9541
  "drill",
9050
9542
  "other",
9051
9543
  "board",
9052
- foregroundLayer === "top" ? "top_silkscreen" : foregroundLayer === "bottom" ? "bottom_silkscreen" : ""
9544
+ ...associatedSilkscreen ? [associatedSilkscreen] : [],
9545
+ ...maskWithCopperLayerForForeground ? [maskWithCopperLayerForForeground] : []
9053
9546
  ]);
9054
- const associatedSilkscreen = foregroundLayer === "top" ? "top_silkscreen" : foregroundLayer === "bottom" ? "bottom_silkscreen" : void 0;
9055
9547
  const layersToShiftToTop = [
9056
9548
  foregroundLayer,
9057
- ...associatedSilkscreen ? [associatedSilkscreen] : []
9549
+ ...associatedSilkscreen ? [associatedSilkscreen] : [],
9550
+ ...maskWithCopperLayerForForeground ? [maskWithCopperLayerForForeground] : []
9058
9551
  ];
9059
9552
  const order = [
9060
9553
  ...DEFAULT_DRAW_ORDER.filter(
9061
9554
  (l) => !layersToShiftToTop.includes(l)
9062
9555
  ),
9063
9556
  foregroundLayer,
9557
+ ...maskWithCopperLayerForForeground ? [maskWithCopperLayerForForeground] : [],
9064
9558
  "drill",
9065
9559
  ...associatedSilkscreen ? [associatedSilkscreen] : []
9066
9560
  ];
@@ -9099,6 +9593,9 @@ var Drawer = class {
9099
9593
  ctx.fillStyle = "rgba(0,0,0,1)";
9100
9594
  ctx.strokeStyle = "rgba(0,0,0,1)";
9101
9595
  }
9596
+ if (this._tempCompositeMode) {
9597
+ ctx.globalCompositeOperation = this._tempCompositeMode;
9598
+ }
9102
9599
  ctx.font = `${scaleOnly(inverse(transform), fontSize)}px sans-serif`;
9103
9600
  }
9104
9601
  moveTo(x, y) {
@@ -9388,6 +9885,9 @@ var drawRect = (drawer, rect) => {
9388
9885
  layer: rect.layer,
9389
9886
  size: rect.stroke_width
9390
9887
  });
9888
+ if (rect.composite_mode) {
9889
+ drawer._tempCompositeMode = rect.composite_mode;
9890
+ }
9391
9891
  drawer.rect({
9392
9892
  x: rect.x,
9393
9893
  y: rect.y,
@@ -9400,12 +9900,18 @@ var drawRect = (drawer, rect) => {
9400
9900
  stroke_width: rect.stroke_width,
9401
9901
  roundness: rect.roundness
9402
9902
  });
9903
+ if (rect.composite_mode) {
9904
+ drawer._tempCompositeMode = void 0;
9905
+ }
9403
9906
  };
9404
9907
  var drawRotatedRect = (drawer, rect) => {
9405
9908
  drawer.equip({
9406
9909
  color: getColor(rect),
9407
9910
  layer: rect.layer
9408
9911
  });
9912
+ if (rect.composite_mode) {
9913
+ drawer._tempCompositeMode = rect.composite_mode;
9914
+ }
9409
9915
  drawer.rotatedRect(
9410
9916
  rect.x,
9411
9917
  rect.y,
@@ -9415,20 +9921,35 @@ var drawRotatedRect = (drawer, rect) => {
9415
9921
  rect.roundness,
9416
9922
  rect.mesh_fill
9417
9923
  );
9924
+ if (rect.composite_mode) {
9925
+ drawer._tempCompositeMode = void 0;
9926
+ }
9418
9927
  };
9419
9928
  var drawRotatedPill = (drawer, pill) => {
9420
9929
  drawer.equip({
9421
9930
  color: getColor(pill),
9422
9931
  layer: pill.layer
9423
9932
  });
9933
+ if (pill.composite_mode) {
9934
+ drawer._tempCompositeMode = pill.composite_mode;
9935
+ }
9424
9936
  drawer.rotatedPill(pill.x, pill.y, pill.w, pill.h, pill.ccw_rotation);
9937
+ if (pill.composite_mode) {
9938
+ drawer._tempCompositeMode = void 0;
9939
+ }
9425
9940
  };
9426
9941
  var drawCircle = (drawer, circle) => {
9427
9942
  drawer.equip({
9428
9943
  color: getColor(circle),
9429
9944
  layer: circle.layer
9430
9945
  });
9946
+ if (circle.composite_mode) {
9947
+ drawer._tempCompositeMode = circle.composite_mode;
9948
+ }
9431
9949
  drawer.circle(circle.x, circle.y, circle.r, circle.mesh_fill);
9950
+ if (circle.composite_mode) {
9951
+ drawer._tempCompositeMode = void 0;
9952
+ }
9432
9953
  };
9433
9954
  var drawOval = (drawer, oval) => {
9434
9955
  drawer.equip({
@@ -9442,7 +9963,13 @@ var drawPill = (drawer, pill) => {
9442
9963
  color: getColor(pill),
9443
9964
  layer: pill.layer
9444
9965
  });
9966
+ if (pill.composite_mode) {
9967
+ drawer._tempCompositeMode = pill.composite_mode;
9968
+ }
9445
9969
  drawer.pill(pill.x, pill.y, pill.w, pill.h);
9970
+ if (pill.composite_mode) {
9971
+ drawer._tempCompositeMode = void 0;
9972
+ }
9446
9973
  };
9447
9974
  var drawPolygon = (drawer, polygon) => {
9448
9975
  drawer.equip({
@@ -9497,7 +10024,11 @@ var orderedLayers = [
9497
10024
  "board",
9498
10025
  "bottom_silkscreen",
9499
10026
  "bottom",
10027
+ "soldermask_bottom",
9500
10028
  "top",
10029
+ "soldermask_top",
10030
+ "soldermask_with_copper_bottom",
10031
+ "soldermask_with_copper_top",
9501
10032
  "top_silkscreen",
9502
10033
  "inner1",
9503
10034
  "inner2",
@@ -9518,16 +10049,28 @@ var CanvasPrimitiveRenderer = ({
9518
10049
  }) => {
9519
10050
  const canvasRefs = useRef2({});
9520
10051
  const selectedLayer = useGlobalStore((s) => s.selected_layer);
10052
+ const isShowingSolderMask = useGlobalStore((s) => s.is_showing_solder_mask);
9521
10053
  useEffect4(() => {
9522
10054
  if (!canvasRefs.current) return;
9523
10055
  if (Object.keys(canvasRefs.current).length === 0) return;
9524
- const drawer = new Drawer(canvasRefs.current);
10056
+ const filteredCanvasRefs = Object.fromEntries(
10057
+ Object.entries(canvasRefs.current).filter(([layer, canvas]) => {
10058
+ if (!canvas) return false;
10059
+ if (!isShowingSolderMask && layer.includes("soldermask")) {
10060
+ return false;
10061
+ }
10062
+ return true;
10063
+ })
10064
+ );
10065
+ if (Object.keys(filteredCanvasRefs).length === 0) return;
10066
+ const drawer = new Drawer(filteredCanvasRefs);
9525
10067
  if (transform) drawer.transform = transform;
9526
10068
  drawer.clear();
9527
10069
  drawer.foregroundLayer = selectedLayer;
9528
- drawPrimitives(drawer, primitives);
10070
+ const filteredPrimitives = isShowingSolderMask ? primitives : primitives.filter((p) => !p.layer?.includes("soldermask"));
10071
+ drawPrimitives(drawer, filteredPrimitives);
9529
10072
  drawer.orderAndFadeLayers();
9530
- }, [primitives, transform, selectedLayer]);
10073
+ }, [primitives, transform, selectedLayer, isShowingSolderMask]);
9531
10074
  return /* @__PURE__ */ jsxs(
9532
10075
  "div",
9533
10076
  {
@@ -9551,13 +10094,22 @@ var CanvasPrimitiveRenderer = ({
9551
10094
  stringifyCoord: (x, y, z) => `${toMMSI(x, z)}, ${toMMSI(y, z)}`
9552
10095
  }
9553
10096
  ),
9554
- orderedLayers.map((l) => l.replace(/-/g, "")).map((layer, i) => /* @__PURE__ */ jsx3(
10097
+ orderedLayers.filter((layer) => {
10098
+ if (!isShowingSolderMask) {
10099
+ return !layer.includes("soldermask");
10100
+ }
10101
+ return true;
10102
+ }).map((l) => l.replace(/-/g, "")).map((layer, i) => /* @__PURE__ */ jsx3(
9555
10103
  "canvas",
9556
10104
  {
9557
10105
  className: `pcb-layer-${layer}`,
9558
10106
  ref: (el) => {
9559
10107
  canvasRefs.current ??= {};
9560
- canvasRefs.current[layer] = el;
10108
+ if (el) {
10109
+ canvasRefs.current[layer] = el;
10110
+ } else {
10111
+ delete canvasRefs.current[layer];
10112
+ }
9561
10113
  },
9562
10114
  style: {
9563
10115
  position: "absolute",
@@ -13077,7 +13629,7 @@ import { css as css3 } from "@emotion/css";
13077
13629
  // package.json
13078
13630
  var package_default = {
13079
13631
  name: "@tscircuit/pcb-viewer",
13080
- version: "1.11.266",
13632
+ version: "1.11.269",
13081
13633
  main: "dist/index.js",
13082
13634
  type: "module",
13083
13635
  repository: "tscircuit/pcb-viewer",
@@ -13130,7 +13682,7 @@ var package_default = {
13130
13682
  "@tscircuit/alphabet": "^0.0.3",
13131
13683
  "@tscircuit/math-utils": "^0.0.29",
13132
13684
  "@vitejs/plugin-react": "^5.0.2",
13133
- "circuit-json": "^0.0.321",
13685
+ "circuit-json": "^0.0.325",
13134
13686
  "circuit-to-svg": "^0.0.271",
13135
13687
  color: "^4.2.3",
13136
13688
  "react-supergrid": "^1.0.10",
@@ -13345,6 +13897,7 @@ var ToolbarOverlay = ({ children, elements }) => {
13345
13897
  setIsShowingCopperPours,
13346
13898
  setIsShowingPcbGroups,
13347
13899
  setIsShowingGroupAnchorOffsets,
13900
+ setIsShowingSolderMask,
13348
13901
  setPcbGroupViewMode,
13349
13902
  setHoveredErrorId
13350
13903
  } = useGlobalStore((s) => ({
@@ -13364,6 +13917,7 @@ var ToolbarOverlay = ({ children, elements }) => {
13364
13917
  is_showing_copper_pours: s.is_showing_copper_pours,
13365
13918
  is_showing_pcb_groups: s.is_showing_pcb_groups,
13366
13919
  is_showing_group_anchor_offsets: s.is_showing_group_anchor_offsets,
13920
+ is_showing_solder_mask: s.is_showing_solder_mask,
13367
13921
  pcb_group_view_mode: s.pcb_group_view_mode
13368
13922
  },
13369
13923
  setEditMode: s.setEditMode,
@@ -13374,6 +13928,7 @@ var ToolbarOverlay = ({ children, elements }) => {
13374
13928
  setIsShowingCopperPours: s.setIsShowingCopperPours,
13375
13929
  setIsShowingPcbGroups: s.setIsShowingPcbGroups,
13376
13930
  setIsShowingGroupAnchorOffsets: s.setIsShowingGroupAnchorOffsets,
13931
+ setIsShowingSolderMask: s.setIsShowingSolderMask,
13377
13932
  setPcbGroupViewMode: s.setPcbGroupViewMode,
13378
13933
  setHoveredErrorId: s.setHoveredErrorId
13379
13934
  }));
@@ -13863,6 +14418,16 @@ var ToolbarOverlay = ({ children, elements }) => {
13863
14418
  }
13864
14419
  }
13865
14420
  ),
14421
+ /* @__PURE__ */ jsx16(
14422
+ CheckboxMenuItem,
14423
+ {
14424
+ label: "Show Solder Mask",
14425
+ checked: viewSettings.is_showing_solder_mask,
14426
+ onClick: () => {
14427
+ setIsShowingSolderMask(!viewSettings.is_showing_solder_mask);
14428
+ }
14429
+ }
14430
+ ),
13866
14431
  /* @__PURE__ */ jsx16(
13867
14432
  CheckboxMenuItem,
13868
14433
  {