@tscircuit/pcb-viewer 1.11.279 → 1.11.280

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
@@ -7074,7 +7074,7 @@ function getExpandedStroke(strokeInput, defaultWidth) {
7074
7074
  }
7075
7075
 
7076
7076
  // src/lib/convert-element-to-primitive.ts
7077
- import { distance } from "circuit-json";
7077
+ import { distance as distance2 } from "circuit-json";
7078
7078
 
7079
7079
  // src/lib/element-to-primitive-converters/convert-smtpad-rect.ts
7080
7080
  var convertSmtpadRect = (element, metadata) => {
@@ -7271,12 +7271,49 @@ var convertSmtpadRotatedPill = (element, metadata) => {
7271
7271
  return primitives;
7272
7272
  };
7273
7273
 
7274
+ // src/lib/element-to-primitive/convert-pcb-copper-text-to-primitive.ts
7275
+ import { distance } from "circuit-json";
7276
+ var convertPcbCopperTextToPrimitive = (element, metadata) => {
7277
+ const { _parent_pcb_component, _parent_source_component } = metadata;
7278
+ const copperText = element;
7279
+ const fontSize = typeof copperText.font_size === "string" ? distance.parse(copperText.font_size) : copperText.font_size ?? 0.2;
7280
+ let knockoutPadding;
7281
+ if (copperText.knockout_padding) {
7282
+ knockoutPadding = {
7283
+ left: typeof copperText.knockout_padding.left === "string" ? distance.parse(copperText.knockout_padding.left) : copperText.knockout_padding.left,
7284
+ top: typeof copperText.knockout_padding.top === "string" ? distance.parse(copperText.knockout_padding.top) : copperText.knockout_padding.top,
7285
+ bottom: typeof copperText.knockout_padding.bottom === "string" ? distance.parse(copperText.knockout_padding.bottom) : copperText.knockout_padding.bottom,
7286
+ right: typeof copperText.knockout_padding.right === "string" ? distance.parse(copperText.knockout_padding.right) : copperText.knockout_padding.right
7287
+ };
7288
+ }
7289
+ return [
7290
+ {
7291
+ _pcb_drawing_object_id: getNewPcbDrawingObjectId("text"),
7292
+ pcb_drawing_type: "text",
7293
+ x: copperText.anchor_position.x,
7294
+ y: copperText.anchor_position.y,
7295
+ layer: copperText.layer,
7296
+ // "top", "bottom", or inner layers
7297
+ align: copperText.anchor_alignment ?? "center",
7298
+ text: copperText.text,
7299
+ size: fontSize,
7300
+ ccw_rotation: copperText.ccw_rotation,
7301
+ is_mirrored: copperText.is_mirrored,
7302
+ is_knockout: copperText.is_knockout,
7303
+ knockout_padding: knockoutPadding,
7304
+ _element: element,
7305
+ _parent_pcb_component,
7306
+ _parent_source_component
7307
+ }
7308
+ ];
7309
+ };
7310
+
7274
7311
  // src/lib/convert-element-to-primitive.ts
7275
7312
  var globalPcbDrawingObjectCount = 0;
7276
7313
  var getNewPcbDrawingObjectId = (prefix) => `${prefix}_${globalPcbDrawingObjectCount++}`;
7277
7314
  var normalizePolygonPoints = (points) => (points ?? []).map((point) => ({
7278
- x: distance.parse(point.x),
7279
- y: distance.parse(point.y)
7315
+ x: distance2.parse(point.x),
7316
+ y: distance2.parse(point.y)
7280
7317
  }));
7281
7318
  var convertElementToPrimitives = (element, allElements) => {
7282
7319
  const _parent_pcb_component = "pcb_component_id" in element ? allElements.find(
@@ -7803,10 +7840,10 @@ var convertElementToPrimitives = (element, allElements) => {
7803
7840
  hole_height,
7804
7841
  layers
7805
7842
  } = element;
7806
- const hole_offset_x = distance.parse(
7843
+ const hole_offset_x = distance2.parse(
7807
7844
  element.hole_offset_x ?? 0
7808
7845
  );
7809
- const hole_offset_y = distance.parse(
7846
+ const hole_offset_y = distance2.parse(
7810
7847
  element.hole_offset_y ?? 0
7811
7848
  );
7812
7849
  const pcb_outline = pad_outline;
@@ -8189,6 +8226,12 @@ var convertElementToPrimitives = (element, allElements) => {
8189
8226
  }
8190
8227
  ];
8191
8228
  }
8229
+ case "pcb_copper_text": {
8230
+ return convertPcbCopperTextToPrimitive(element, {
8231
+ _parent_pcb_component,
8232
+ _parent_source_component
8233
+ });
8234
+ }
8192
8235
  case "pcb_copper_pour": {
8193
8236
  const pour = element;
8194
8237
  switch (pour.shape) {
@@ -9480,6 +9523,7 @@ var getTextGeometry = (text) => {
9480
9523
  return {
9481
9524
  text_lines,
9482
9525
  line_count,
9526
+ line_widths,
9483
9527
  target_height,
9484
9528
  target_width_char,
9485
9529
  space_between_chars,
@@ -9502,17 +9546,21 @@ var convertTextToLines = (text) => {
9502
9546
  const {
9503
9547
  text_lines,
9504
9548
  line_count,
9549
+ line_widths,
9505
9550
  target_height,
9506
9551
  target_width_char,
9507
9552
  space_between_chars,
9508
- space_between_lines
9553
+ space_between_lines,
9554
+ width: maxWidth
9509
9555
  } = getTextGeometry(text);
9510
9556
  if (target_height <= 0 || line_count === 0) return [];
9511
9557
  const strokeWidth = target_height / 12;
9512
9558
  const lines = [];
9513
9559
  for (let lineIndex = 0; lineIndex < line_count; lineIndex++) {
9514
- const lineYOffset = lineIndex * (target_height + space_between_lines);
9515
- let current_x_origin_for_char_box = text.x;
9560
+ const lineYOffset = -lineIndex * (target_height + space_between_lines);
9561
+ const lineWidth = line_widths[lineIndex];
9562
+ const lineXOffset = (maxWidth - lineWidth) / 2;
9563
+ let current_x_origin_for_char_box = text.x + lineXOffset;
9516
9564
  for (let letterIndex = 0; letterIndex < text_lines[lineIndex].length; letterIndex++) {
9517
9565
  const letter = text_lines[lineIndex][letterIndex];
9518
9566
  const letterLines = lineAlphabet[letter] ?? lineAlphabet[letter.toUpperCase()];
@@ -9572,56 +9620,44 @@ var drawLine = (drawer, line) => {
9572
9620
  drawer.moveTo(line.x1, line.y1);
9573
9621
  drawer.lineTo(line.x2, line.y2);
9574
9622
  };
9575
- var drawText = (drawer, text) => {
9576
- drawer.equip({
9577
- fontSize: text.size,
9578
- color: getColor(text),
9579
- layer: text.layer
9580
- });
9581
- let alignOffset = { x: 0, y: 0 };
9582
- const { width: textWidth, height: textHeight } = getTextMetrics(text);
9583
- switch (text.align) {
9623
+ var getAlignOffset = (textWidth, textHeight, align) => {
9624
+ switch (align) {
9584
9625
  case "top_left":
9585
- alignOffset.x = 0;
9586
- alignOffset.y = -textHeight;
9587
- break;
9626
+ return { x: 0, y: -textHeight };
9588
9627
  case "top_center":
9589
- alignOffset.x = -textWidth / 2;
9590
- alignOffset.y = -textHeight;
9591
- break;
9628
+ return { x: -textWidth / 2, y: -textHeight };
9592
9629
  case "top_right":
9593
- alignOffset.x = -textWidth;
9594
- alignOffset.y = -textHeight;
9595
- break;
9630
+ return { x: -textWidth, y: -textHeight };
9596
9631
  case "center_left":
9597
- alignOffset.x = 0;
9598
- alignOffset.y = -textHeight / 2;
9599
- break;
9632
+ return { x: 0, y: -textHeight / 2 };
9600
9633
  case "center":
9601
- alignOffset.x = -textWidth / 2;
9602
- alignOffset.y = -textHeight / 2;
9603
- break;
9634
+ return { x: -textWidth / 2, y: -textHeight / 2 };
9604
9635
  case "center_right":
9605
- alignOffset.x = -textWidth;
9606
- alignOffset.y = -textHeight / 2;
9607
- break;
9636
+ return { x: -textWidth, y: -textHeight / 2 };
9608
9637
  case "bottom_left":
9609
- alignOffset.x = 0;
9610
- alignOffset.y = 0;
9611
- break;
9638
+ return { x: 0, y: 0 };
9612
9639
  case "bottom_center":
9613
- alignOffset.x = -textWidth / 2;
9614
- alignOffset.y = 0;
9615
- break;
9640
+ return { x: -textWidth / 2, y: 0 };
9616
9641
  case "bottom_right":
9617
- alignOffset.x = -textWidth;
9618
- alignOffset.y = 0;
9619
- break;
9642
+ return { x: -textWidth, y: 0 };
9620
9643
  default:
9621
- alignOffset.x = 0;
9622
- alignOffset.y = 0;
9623
- break;
9644
+ return { x: 0, y: 0 };
9645
+ }
9646
+ };
9647
+ var shouldMirrorText = (text) => {
9648
+ if (text.layer === "bottom_silkscreen" || text.layer === "bottom") {
9649
+ return text.is_mirrored !== false;
9624
9650
  }
9651
+ return text.is_mirrored === true;
9652
+ };
9653
+ var drawText = (drawer, text) => {
9654
+ drawer.equip({
9655
+ fontSize: text.size,
9656
+ color: getColor(text),
9657
+ layer: text.layer
9658
+ });
9659
+ const { width: textWidth, height: textHeight } = getTextMetrics(text);
9660
+ const alignOffset = getAlignOffset(textWidth, textHeight, text.align);
9625
9661
  text.x ??= 0;
9626
9662
  text.y ??= 0;
9627
9663
  const rotationAnchor = {
@@ -9633,7 +9669,7 @@ var drawText = (drawer, text) => {
9633
9669
  x: text.x + alignOffset.x,
9634
9670
  y: text.y + alignOffset.y
9635
9671
  });
9636
- if (text.layer === "bottom_silkscreen") {
9672
+ if (shouldMirrorText(text)) {
9637
9673
  text_lines = text_lines.map((line) => ({
9638
9674
  ...line,
9639
9675
  x1: 2 * text.x - line.x1,
@@ -9652,6 +9688,80 @@ var drawText = (drawer, text) => {
9652
9688
  drawLine(drawer, line);
9653
9689
  }
9654
9690
  };
9691
+ var drawKnockoutText = (drawer, text) => {
9692
+ const {
9693
+ width: textWidth,
9694
+ height: textHeight,
9695
+ lineHeight
9696
+ } = getTextMetrics(text);
9697
+ text.x ??= 0;
9698
+ text.y ??= 0;
9699
+ const padding = text.knockout_padding ?? {
9700
+ left: text.size * 0.5,
9701
+ right: text.size * 0.5,
9702
+ top: text.size * 0.3,
9703
+ bottom: text.size * 0.3
9704
+ };
9705
+ const rectWidth = textWidth + padding.left + padding.right;
9706
+ const rectHeight = textHeight + padding.top + padding.bottom;
9707
+ const textCenterXAtOrigin = textWidth / 2;
9708
+ const textCenterYAtOrigin = lineHeight - textHeight / 2;
9709
+ const textDrawX = text.x - textCenterXAtOrigin;
9710
+ const textDrawY = text.y - textCenterYAtOrigin;
9711
+ const rotationAnchor = {
9712
+ x: text.x,
9713
+ y: text.y
9714
+ };
9715
+ const rect = {
9716
+ _pcb_drawing_object_id: `knockout_rect_${text._pcb_drawing_object_id}`,
9717
+ pcb_drawing_type: "rect",
9718
+ x: text.x,
9719
+ y: text.y,
9720
+ w: rectWidth,
9721
+ h: rectHeight,
9722
+ layer: text.layer,
9723
+ ccw_rotation: text.ccw_rotation
9724
+ };
9725
+ if (text.ccw_rotation) {
9726
+ drawRotatedRect(drawer, rect);
9727
+ } else {
9728
+ drawRect(drawer, rect);
9729
+ }
9730
+ let text_lines = convertTextToLines({
9731
+ ...text,
9732
+ x: textDrawX,
9733
+ y: textDrawY
9734
+ });
9735
+ if (shouldMirrorText(text)) {
9736
+ text_lines = text_lines.map((line) => ({
9737
+ ...line,
9738
+ x1: 2 * text.x - line.x1,
9739
+ x2: 2 * text.x - line.x2
9740
+ }));
9741
+ }
9742
+ if (text.ccw_rotation) {
9743
+ const rotateTextParams = {
9744
+ lines: text_lines,
9745
+ anchorPoint: rotationAnchor,
9746
+ ccwRotation: text.ccw_rotation
9747
+ };
9748
+ text_lines = rotateText(rotateTextParams);
9749
+ }
9750
+ for (const line of text_lines) {
9751
+ drawer.equip({
9752
+ size: line.width,
9753
+ shape: "circle",
9754
+ color: "black",
9755
+ layer: text.layer,
9756
+ mode: "subtract"
9757
+ });
9758
+ drawer.moveTo(line.x1, line.y1);
9759
+ drawer.lineTo(line.x2, line.y2);
9760
+ }
9761
+ drawer.equip({
9762
+ mode: "add"
9763
+ });
9764
+ };
9655
9765
  var drawRect = (drawer, rect) => {
9656
9766
  drawer.equip({
9657
9767
  color: getColor(rect),
@@ -9733,6 +9843,9 @@ var drawPrimitive = (drawer, primitive) => {
9733
9843
  case "line":
9734
9844
  return drawLine(drawer, primitive);
9735
9845
  case "text":
9846
+ if (primitive.is_knockout) {
9847
+ return drawKnockoutText(drawer, primitive);
9848
+ }
9736
9849
  return drawText(drawer, primitive);
9737
9850
  case "rect": {
9738
9851
  if (primitive.ccw_rotation) {
@@ -10492,7 +10605,7 @@ function calculateDiagonalLabel(params) {
10492
10605
  } = params;
10493
10606
  const deltaX = dimensionEnd.x - dimensionStart.x;
10494
10607
  const deltaY = dimensionEnd.y - dimensionStart.y;
10495
- const distance3 = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
10608
+ const distance4 = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
10496
10609
  const screenDeltaX = screenDimensionEnd.x - screenDimensionStart.x;
10497
10610
  const screenDeltaY = screenDimensionEnd.y - screenDimensionStart.y;
10498
10611
  const screenDistance = Math.sqrt(
@@ -10534,11 +10647,11 @@ function calculateDiagonalLabel(params) {
10534
10647
  const x = midX + offsetX;
10535
10648
  const y = midY + offsetY;
10536
10649
  return {
10537
- distance: distance3,
10650
+ distance: distance4,
10538
10651
  screenDistance,
10539
10652
  x,
10540
10653
  y,
10541
- show: distance3 > 0.01 && screenDistance > 30 && isDiagonal
10654
+ show: distance4 > 0.01 && screenDistance > 30 && isDiagonal
10542
10655
  };
10543
10656
  }
10544
10657
 
@@ -10832,11 +10945,11 @@ var DimensionOverlay = ({
10832
10945
  for (const snap of snappingPointsWithScreen) {
10833
10946
  const dx = snap.screenPoint.x - screenPoint.x;
10834
10947
  const dy = snap.screenPoint.y - screenPoint.y;
10835
- const distance3 = Math.hypot(dx, dy);
10836
- if (distance3 > SNAP_THRESHOLD_PX) continue;
10837
- if (!bestMatch || distance3 < bestMatch.distance) {
10948
+ const distance4 = Math.hypot(dx, dy);
10949
+ if (distance4 > SNAP_THRESHOLD_PX) continue;
10950
+ if (!bestMatch || distance4 < bestMatch.distance) {
10838
10951
  bestMatch = {
10839
- distance: distance3,
10952
+ distance: distance4,
10840
10953
  id: snap.id,
10841
10954
  point: snap.point
10842
10955
  };
@@ -11435,10 +11548,10 @@ var isInsideOfSmtpad = (elm, point, padding = 0) => {
11435
11548
  };
11436
11549
  var isInsideOfPlatedHole = (hole, point, padding = 0) => {
11437
11550
  if (hole.shape === "circle") {
11438
- const distance3 = Math.sqrt(
11551
+ const distance4 = Math.sqrt(
11439
11552
  (point.x - hole.x) ** 2 + (point.y - hole.y) ** 2
11440
11553
  );
11441
- return distance3 <= hole.outer_diameter / 2 + padding;
11554
+ return distance4 <= hole.outer_diameter / 2 + padding;
11442
11555
  } else if (hole.shape === "circular_hole_with_rect_pad") {
11443
11556
  const dx = Math.abs(point.x - hole.x);
11444
11557
  const dy = Math.abs(point.y - hole.y);
@@ -12266,7 +12379,7 @@ var ErrorOverlay = ({
12266
12379
 
12267
12380
  // src/components/MouseElementTracker.tsx
12268
12381
  import { pointToSegmentDistance } from "@tscircuit/math-utils";
12269
- import { distance as distance2 } from "circuit-json";
12382
+ import { distance as distance3 } from "circuit-json";
12270
12383
 
12271
12384
  // src/lib/util/if-sets-match-exactly.ts
12272
12385
  function ifSetsMatchExactly(set1, set2) {
@@ -12816,22 +12929,22 @@ var getPrimitivesUnderPoint = (primitives, rwPoint, transform) => {
12816
12929
  for (const primitive of primitives) {
12817
12930
  if (!primitive._element) continue;
12818
12931
  if ("x1" in primitive && primitive._element?.type === "pcb_trace") {
12819
- const distance3 = pointToSegmentDistance(
12932
+ const distance4 = pointToSegmentDistance(
12820
12933
  { x: rwPoint.x, y: rwPoint.y },
12821
12934
  { x: primitive.x1, y: primitive.y1 },
12822
12935
  { x: primitive.x2, y: primitive.y2 }
12823
12936
  );
12824
12937
  const lineWidth = primitive.width || 0.5;
12825
12938
  const detectionThreshold = Math.max(lineWidth * 25, 2) / transform.a;
12826
- if (distance3 < detectionThreshold) {
12939
+ if (distance4 < detectionThreshold) {
12827
12940
  newMousedPrimitives.push(primitive);
12828
12941
  }
12829
12942
  continue;
12830
12943
  }
12831
12944
  if (primitive.pcb_drawing_type === "polygon") {
12832
12945
  const points = primitive.points.map((point) => ({
12833
- x: distance2.parse(point.x),
12834
- y: distance2.parse(point.y)
12946
+ x: distance3.parse(point.x),
12947
+ y: distance3.parse(point.y)
12835
12948
  }));
12836
12949
  const boundingBox = getPolygonBoundingBox(points);
12837
12950
  if (!boundingBox) continue;
@@ -12845,8 +12958,8 @@ var getPrimitivesUnderPoint = (primitives, rwPoint, transform) => {
12845
12958
  }
12846
12959
  if (primitive.pcb_drawing_type === "polygon_with_arcs") {
12847
12960
  const points = primitive.brep_shape.outer_ring.vertices.map((v) => ({
12848
- x: distance2.parse(v.x),
12849
- y: distance2.parse(v.y)
12961
+ x: distance3.parse(v.x),
12962
+ y: distance3.parse(v.y)
12850
12963
  }));
12851
12964
  const boundingBox = getPolygonBoundingBox(points);
12852
12965
  if (!boundingBox) continue;
@@ -13288,11 +13401,11 @@ var RatsNestOverlay = ({ transform, soup, children }) => {
13288
13401
  connectedIds.forEach((id) => {
13289
13402
  const pos = getElementPosition(id);
13290
13403
  if (pos) {
13291
- const distance3 = Math.sqrt(
13404
+ const distance4 = Math.sqrt(
13292
13405
  (sourcePoint.x - pos.x) ** 2 + (sourcePoint.y - pos.y) ** 2
13293
13406
  );
13294
- if (distance3 < minDistance && distance3 > 0) {
13295
- minDistance = distance3;
13407
+ if (distance4 < minDistance && distance4 > 0) {
13408
+ minDistance = distance4;
13296
13409
  nearestPoint = pos;
13297
13410
  }
13298
13411
  }
@@ -13372,7 +13485,7 @@ import { css as css3 } from "@emotion/css";
13372
13485
  // package.json
13373
13486
  var package_default = {
13374
13487
  name: "@tscircuit/pcb-viewer",
13375
- version: "1.11.278",
13488
+ version: "1.11.279",
13376
13489
  main: "dist/index.js",
13377
13490
  type: "module",
13378
13491
  repository: "tscircuit/pcb-viewer",