@tscircuit/pcb-viewer 1.11.259 → 1.11.261

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
@@ -6421,7 +6421,7 @@ var ToastContainer = () => {
6421
6421
  };
6422
6422
 
6423
6423
  // src/PCBViewer.tsx
6424
- import { useEffect as useEffect16, useMemo as useMemo7, useRef as useRef11, useState as useState11 } from "react";
6424
+ import { useEffect as useEffect16, useMemo as useMemo8, useRef as useRef11, useState as useState11 } from "react";
6425
6425
 
6426
6426
  // node_modules/react-use/esm/misc/util.js
6427
6427
  var noop = function() {
@@ -6972,7 +6972,7 @@ function addInteractionMetadataToPrimitives({
6972
6972
  }
6973
6973
 
6974
6974
  // src/components/CanvasElementsRenderer.tsx
6975
- import { useCallback as useCallback5, useMemo as useMemo6, useState as useState10 } from "react";
6975
+ import { useCallback as useCallback5, useMemo as useMemo7, useState as useState10 } from "react";
6976
6976
 
6977
6977
  // src/lib/util/expand-stroke.ts
6978
6978
  function getExpandedStroke(strokeInput, defaultWidth) {
@@ -9902,7 +9902,7 @@ var WarningGraphicsOverlay = ({
9902
9902
  };
9903
9903
 
9904
9904
  // src/components/DimensionOverlay.tsx
9905
- import { useCallback as useCallback3, useEffect as useEffect7, useMemo as useMemo3, useRef as useRef5, useState as useState3 } from "react";
9905
+ import { useCallback as useCallback3, useEffect as useEffect7, useMemo as useMemo4, useRef as useRef5, useState as useState3 } from "react";
9906
9906
  import { applyToPoint as applyToPoint8, identity as identity4, inverse as inverse2 } from "transformation-matrix";
9907
9907
 
9908
9908
  // src/lib/util/get-primitive-bounding-box.ts
@@ -10055,6 +10055,228 @@ var getPrimitiveBoundingBox = (primitive) => {
10055
10055
  }
10056
10056
  };
10057
10057
 
10058
+ // src/hooks/useDiagonalLabel.ts
10059
+ import { useMemo as useMemo3 } from "react";
10060
+
10061
+ // src/lib/util/calculate-diagonal-label.ts
10062
+ function calculateDiagonalLabel(params) {
10063
+ const {
10064
+ dimensionStart,
10065
+ dimensionEnd,
10066
+ screenDimensionStart,
10067
+ screenDimensionEnd,
10068
+ flipX,
10069
+ flipY
10070
+ } = params;
10071
+ const deltaX = dimensionEnd.x - dimensionStart.x;
10072
+ const deltaY = dimensionEnd.y - dimensionStart.y;
10073
+ const distance5 = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
10074
+ const screenDeltaX = screenDimensionEnd.x - screenDimensionStart.x;
10075
+ const screenDeltaY = screenDimensionEnd.y - screenDimensionStart.y;
10076
+ const screenDistance = Math.sqrt(
10077
+ screenDeltaX * screenDeltaX + screenDeltaY * screenDeltaY
10078
+ );
10079
+ const angle = Math.atan2(screenDeltaY, screenDeltaX) * (180 / Math.PI);
10080
+ const normalizedAngle = Math.abs(angle) % 90;
10081
+ const angleFromAxis = Math.min(normalizedAngle, 90 - normalizedAngle);
10082
+ const isDiagonal = angleFromAxis > 15;
10083
+ const midX = (screenDimensionStart.x + screenDimensionEnd.x) / 2;
10084
+ const midY = (screenDimensionStart.y + screenDimensionEnd.y) / 2;
10085
+ const offsetDistance = 15;
10086
+ const perpendicularAngle = angle + 90;
10087
+ let offsetX = Math.cos(perpendicularAngle * Math.PI / 180) * offsetDistance;
10088
+ let offsetY = Math.sin(perpendicularAngle * Math.PI / 180) * offsetDistance;
10089
+ const isNE = screenDeltaX > 0 && screenDeltaY < 0;
10090
+ const isNW = screenDeltaX < 0 && screenDeltaY < 0;
10091
+ const isSE = screenDeltaX > 0 && screenDeltaY > 0;
10092
+ const isSW = screenDeltaX < 0 && screenDeltaY > 0;
10093
+ if (flipX !== flipY && !isNE) {
10094
+ offsetX = -offsetX;
10095
+ offsetY = -offsetY;
10096
+ }
10097
+ if (isNE) {
10098
+ const lessOffset = -45;
10099
+ offsetX += Math.cos(perpendicularAngle * Math.PI / 180) * lessOffset;
10100
+ offsetY += Math.sin(perpendicularAngle * Math.PI / 180) * lessOffset;
10101
+ }
10102
+ if (isSE) {
10103
+ const seAdjust = -10;
10104
+ offsetX += Math.cos(perpendicularAngle * Math.PI / 180) + seAdjust * 2;
10105
+ offsetY += Math.sin(perpendicularAngle * Math.PI / 180) + seAdjust;
10106
+ }
10107
+ if (isSW) {
10108
+ const reduceOffset = 10;
10109
+ offsetX += Math.cos(perpendicularAngle * Math.PI / 180) * reduceOffset;
10110
+ offsetY += Math.sin(perpendicularAngle * Math.PI / 180) * reduceOffset;
10111
+ }
10112
+ const x = midX + offsetX;
10113
+ const y = midY + offsetY;
10114
+ return {
10115
+ distance: distance5,
10116
+ screenDistance,
10117
+ x,
10118
+ y,
10119
+ show: distance5 > 0.01 && screenDistance > 30 && isDiagonal
10120
+ };
10121
+ }
10122
+
10123
+ // src/hooks/useDiagonalLabel.ts
10124
+ function useDiagonalLabel(params) {
10125
+ const {
10126
+ dimensionStart,
10127
+ dimensionEnd,
10128
+ screenDimensionStart,
10129
+ screenDimensionEnd,
10130
+ flipX,
10131
+ flipY
10132
+ } = params;
10133
+ return useMemo3(
10134
+ () => calculateDiagonalLabel({
10135
+ dimensionStart,
10136
+ dimensionEnd,
10137
+ screenDimensionStart,
10138
+ screenDimensionEnd,
10139
+ flipX,
10140
+ flipY
10141
+ }),
10142
+ [
10143
+ dimensionStart,
10144
+ dimensionEnd,
10145
+ screenDimensionStart,
10146
+ screenDimensionEnd,
10147
+ flipX,
10148
+ flipY
10149
+ ]
10150
+ );
10151
+ }
10152
+
10153
+ // src/lib/util/get-primitive-snap-points.ts
10154
+ var rotatePoint2 = (point, center, rotationDeg) => {
10155
+ const radians = rotationDeg * Math.PI / 180;
10156
+ const cos = Math.cos(radians);
10157
+ const sin = Math.sin(radians);
10158
+ const translatedX = point.x - center.x;
10159
+ const translatedY = point.y - center.y;
10160
+ const rotatedX = translatedX * cos - translatedY * sin;
10161
+ const rotatedY = translatedX * sin + translatedY * cos;
10162
+ return {
10163
+ x: rotatedX + center.x,
10164
+ y: rotatedY + center.y
10165
+ };
10166
+ };
10167
+ var getNinePointAnchors = (center, halfWidth, halfHeight, rotationDeg) => {
10168
+ const basePoints = {
10169
+ top_left: { x: center.x - halfWidth, y: center.y - halfHeight },
10170
+ top_center: { x: center.x, y: center.y - halfHeight },
10171
+ top_right: { x: center.x + halfWidth, y: center.y - halfHeight },
10172
+ center_left: { x: center.x - halfWidth, y: center.y },
10173
+ center: { x: center.x, y: center.y },
10174
+ center_right: { x: center.x + halfWidth, y: center.y },
10175
+ bottom_left: { x: center.x - halfWidth, y: center.y + halfHeight },
10176
+ bottom_center: { x: center.x, y: center.y + halfHeight },
10177
+ bottom_right: { x: center.x + halfWidth, y: center.y + halfHeight }
10178
+ };
10179
+ if (rotationDeg === 0) {
10180
+ return Object.entries(basePoints).map(([anchor, point]) => ({
10181
+ anchor,
10182
+ point
10183
+ }));
10184
+ }
10185
+ return Object.entries(basePoints).map(([anchor, point]) => ({
10186
+ anchor,
10187
+ point: rotatePoint2(point, center, rotationDeg)
10188
+ }));
10189
+ };
10190
+ var getPrimitiveSnapPoints = (primitive) => {
10191
+ switch (primitive.pcb_drawing_type) {
10192
+ case "rect": {
10193
+ const rotation = primitive.ccw_rotation ?? 0;
10194
+ return getNinePointAnchors(
10195
+ { x: primitive.x, y: primitive.y },
10196
+ primitive.w / 2,
10197
+ primitive.h / 2,
10198
+ rotation
10199
+ );
10200
+ }
10201
+ case "pill": {
10202
+ const rotation = primitive.ccw_rotation ?? 0;
10203
+ return getNinePointAnchors(
10204
+ { x: primitive.x, y: primitive.y },
10205
+ primitive.w / 2,
10206
+ primitive.h / 2,
10207
+ rotation
10208
+ );
10209
+ }
10210
+ case "circle": {
10211
+ return [
10212
+ { anchor: "circle_center", point: { x: primitive.x, y: primitive.y } },
10213
+ {
10214
+ anchor: "circle_right",
10215
+ point: { x: primitive.x + primitive.r, y: primitive.y }
10216
+ },
10217
+ {
10218
+ anchor: "circle_left",
10219
+ point: { x: primitive.x - primitive.r, y: primitive.y }
10220
+ },
10221
+ {
10222
+ anchor: "circle_top",
10223
+ point: { x: primitive.x, y: primitive.y - primitive.r }
10224
+ },
10225
+ {
10226
+ anchor: "circle_bottom",
10227
+ point: { x: primitive.x, y: primitive.y + primitive.r }
10228
+ }
10229
+ ];
10230
+ }
10231
+ case "oval": {
10232
+ return [
10233
+ { anchor: "oval_center", point: { x: primitive.x, y: primitive.y } },
10234
+ {
10235
+ anchor: "oval_right",
10236
+ point: { x: primitive.x + primitive.rX, y: primitive.y }
10237
+ },
10238
+ {
10239
+ anchor: "oval_left",
10240
+ point: { x: primitive.x - primitive.rX, y: primitive.y }
10241
+ },
10242
+ {
10243
+ anchor: "oval_top",
10244
+ point: { x: primitive.x, y: primitive.y - primitive.rY }
10245
+ },
10246
+ {
10247
+ anchor: "oval_bottom",
10248
+ point: { x: primitive.x, y: primitive.y + primitive.rY }
10249
+ }
10250
+ ];
10251
+ }
10252
+ case "line": {
10253
+ const midPoint = {
10254
+ x: (primitive.x1 + primitive.x2) / 2,
10255
+ y: (primitive.y1 + primitive.y2) / 2
10256
+ };
10257
+ return [
10258
+ { anchor: "line_start", point: { x: primitive.x1, y: primitive.y1 } },
10259
+ { anchor: "line_mid", point: midPoint },
10260
+ { anchor: "line_end", point: { x: primitive.x2, y: primitive.y2 } }
10261
+ ];
10262
+ }
10263
+ case "polygon": {
10264
+ return primitive.points.map((point, index) => ({
10265
+ anchor: `polygon_vertex_${index}`,
10266
+ point
10267
+ }));
10268
+ }
10269
+ case "polygon_with_arcs": {
10270
+ return primitive.brep_shape.outer_ring.vertices.map((vertex, index) => ({
10271
+ anchor: `polygon_with_arcs_vertex_${index}`,
10272
+ point: { x: vertex.x, y: vertex.y }
10273
+ }));
10274
+ }
10275
+ default:
10276
+ return [];
10277
+ }
10278
+ };
10279
+
10058
10280
  // src/components/DimensionOverlay.tsx
10059
10281
  import { Fragment, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
10060
10282
  var SNAP_THRESHOLD_PX = 16;
@@ -10101,11 +10323,14 @@ var DimensionOverlay = ({
10101
10323
  const containerRef = useRef5(null);
10102
10324
  const container = containerRef.current;
10103
10325
  const containerBounds = container?.getBoundingClientRect();
10104
- const elementBoundingBoxes = useMemo3(() => {
10326
+ const elementBoundingBoxes = useMemo4(() => {
10105
10327
  const boundingBoxes = /* @__PURE__ */ new Map();
10106
10328
  for (const primitive of primitives) {
10107
10329
  if (!primitive._element) continue;
10108
10330
  if (shouldExcludePrimitiveFromSnapping(primitive)) continue;
10331
+ if (primitive.pcb_drawing_type === "pill") continue;
10332
+ if (primitive.pcb_drawing_type === "rect" && primitive.ccw_rotation && primitive.ccw_rotation !== 0)
10333
+ continue;
10109
10334
  const bbox = getPrimitiveBoundingBox(primitive);
10110
10335
  if (!bbox) continue;
10111
10336
  const existing = boundingBoxes.get(primitive._element);
@@ -10116,7 +10341,24 @@ var DimensionOverlay = ({
10116
10341
  }
10117
10342
  return boundingBoxes;
10118
10343
  }, [primitives]);
10119
- const snappingPoints = useMemo3(() => {
10344
+ const primitiveSnappingPoints = useMemo4(() => {
10345
+ const snapPoints = [];
10346
+ for (const primitive of primitives) {
10347
+ if (!primitive._element) continue;
10348
+ if (shouldExcludePrimitiveFromSnapping(primitive)) continue;
10349
+ const primitivePoints = getPrimitiveSnapPoints(primitive);
10350
+ if (primitivePoints.length === 0) continue;
10351
+ for (const snap of primitivePoints) {
10352
+ snapPoints.push({
10353
+ anchor: snap.anchor,
10354
+ point: snap.point,
10355
+ element: primitive._element
10356
+ });
10357
+ }
10358
+ }
10359
+ return snapPoints;
10360
+ }, [primitives]);
10361
+ const snappingPoints = useMemo4(() => {
10120
10362
  const points = [];
10121
10363
  elementBoundingBoxes.forEach((bounds, element) => {
10122
10364
  if (!bounds) return;
@@ -10141,14 +10383,17 @@ var DimensionOverlay = ({
10141
10383
  });
10142
10384
  }
10143
10385
  });
10386
+ for (const snap of primitiveSnappingPoints) {
10387
+ points.push(snap);
10388
+ }
10144
10389
  points.push({
10145
10390
  anchor: "origin",
10146
10391
  point: { x: 0, y: 0 },
10147
10392
  element: null
10148
10393
  });
10149
10394
  return points;
10150
- }, [elementBoundingBoxes]);
10151
- const snappingPointsWithScreen = useMemo3(() => {
10395
+ }, [elementBoundingBoxes, primitiveSnappingPoints]);
10396
+ const snappingPointsWithScreen = useMemo4(() => {
10152
10397
  return snappingPoints.map((snap, index) => ({
10153
10398
  ...snap,
10154
10399
  id: `${index}-${snap.anchor}`,
@@ -10255,6 +10500,14 @@ var DimensionOverlay = ({
10255
10500
  };
10256
10501
  arrowScreenBounds.width = arrowScreenBounds.right - arrowScreenBounds.left;
10257
10502
  arrowScreenBounds.height = arrowScreenBounds.bottom - arrowScreenBounds.top;
10503
+ const diagonalLabel = useDiagonalLabel({
10504
+ dimensionStart: dStart,
10505
+ dimensionEnd: dEnd,
10506
+ screenDimensionStart: screenDStart,
10507
+ screenDimensionEnd: screenDEnd,
10508
+ flipX: arrowScreenBounds.flipX,
10509
+ flipY: arrowScreenBounds.flipY
10510
+ });
10258
10511
  return /* @__PURE__ */ jsxs4(
10259
10512
  "div",
10260
10513
  {
@@ -10308,6 +10561,24 @@ var DimensionOverlay = ({
10308
10561
  children: [
10309
10562
  children,
10310
10563
  dimensionToolVisible && /* @__PURE__ */ jsxs4(Fragment, { children: [
10564
+ diagonalLabel.show && /* @__PURE__ */ jsx6(
10565
+ "div",
10566
+ {
10567
+ style: {
10568
+ position: "absolute",
10569
+ left: diagonalLabel.x,
10570
+ top: diagonalLabel.y,
10571
+ color: "red",
10572
+ mixBlendMode: "difference",
10573
+ pointerEvents: "none",
10574
+ fontSize: 12,
10575
+ fontFamily: "sans-serif",
10576
+ whiteSpace: "nowrap",
10577
+ zIndex: zIndexMap.dimensionOverlay
10578
+ },
10579
+ children: diagonalLabel.distance.toFixed(2)
10580
+ }
10581
+ ),
10311
10582
  /* @__PURE__ */ jsx6(
10312
10583
  "div",
10313
10584
  {
@@ -11572,7 +11843,7 @@ var ErrorOverlay = ({
11572
11843
  };
11573
11844
 
11574
11845
  // src/components/MouseElementTracker.tsx
11575
- import { useState as useState7, useMemo as useMemo4 } from "react";
11846
+ import { useState as useState7, useMemo as useMemo5 } from "react";
11576
11847
  import { applyToPoint as applyToPoint12, inverse as inverse5 } from "transformation-matrix";
11577
11848
 
11578
11849
  // src/components/ElementOverlayBox.tsx
@@ -11989,7 +12260,7 @@ var MouseElementTracker = ({
11989
12260
  }) => {
11990
12261
  const [mousedPrimitives, setMousedPrimitives] = useState7([]);
11991
12262
  const [mousePos, setMousePos] = useState7({ x: 0, y: 0 });
11992
- const highlightedPrimitives = useMemo4(() => {
12263
+ const highlightedPrimitives = useMemo5(() => {
11993
12264
  const highlightedPrimitives2 = [];
11994
12265
  for (const primitive of mousedPrimitives) {
11995
12266
  if (primitive._element?.type === "pcb_via") continue;
@@ -12353,15 +12624,15 @@ var PcbGroupOverlay = ({
12353
12624
 
12354
12625
  // src/components/RatsNestOverlay.tsx
12355
12626
  import { applyToPoint as applyToPoint14, identity as identity9 } from "transformation-matrix";
12356
- import { useMemo as useMemo5 } from "react";
12627
+ import { useMemo as useMemo6 } from "react";
12357
12628
  import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
12358
12629
  var RatsNestOverlay = ({ transform, soup, children }) => {
12359
12630
  const isShowingRatsNest = useGlobalStore((s) => s.is_showing_rats_nest);
12360
- const { netMap, idToNetMap } = useMemo5(
12631
+ const { netMap, idToNetMap } = useMemo6(
12361
12632
  () => getFullConnectivityMapFromCircuitJson(soup || []),
12362
12633
  [soup]
12363
12634
  );
12364
- const ratsNestLines = useMemo5(() => {
12635
+ const ratsNestLines = useMemo6(() => {
12365
12636
  if (!soup || !isShowingRatsNest) return [];
12366
12637
  const getElementPosition = (id) => {
12367
12638
  const element = su(soup)[id.replace(/_\d+$/, "")].get(id);
@@ -12466,7 +12737,7 @@ import { css as css3 } from "@emotion/css";
12466
12737
  // package.json
12467
12738
  var package_default = {
12468
12739
  name: "@tscircuit/pcb-viewer",
12469
- version: "1.11.258",
12740
+ version: "1.11.260",
12470
12741
  main: "dist/index.js",
12471
12742
  type: "module",
12472
12743
  repository: "tscircuit/pcb-viewer",
@@ -13300,11 +13571,11 @@ var CanvasElementsRenderer = (props) => {
13300
13571
  const isShowingCopperPours = useGlobalStore(
13301
13572
  (state) => state.is_showing_copper_pours
13302
13573
  );
13303
- const elementsToRender = useMemo6(
13574
+ const elementsToRender = useMemo7(
13304
13575
  () => isShowingCopperPours ? elements : elements.filter((elm) => elm.type !== "pcb_copper_pour"),
13305
13576
  [elements, isShowingCopperPours]
13306
13577
  );
13307
- const [primitivesWithoutInteractionMetadata, connectivityMap] = useMemo6(() => {
13578
+ const [primitivesWithoutInteractionMetadata, connectivityMap] = useMemo7(() => {
13308
13579
  const primitivesWithoutInteractionMetadata2 = elementsToRender.flatMap(
13309
13580
  (elm) => convertElementToPrimitives(elm, props.elements)
13310
13581
  );
@@ -13317,7 +13588,7 @@ var CanvasElementsRenderer = (props) => {
13317
13588
  drawingObjectIdsWithMouseOver: /* @__PURE__ */ new Set(),
13318
13589
  primitiveIdsInMousedOverNet: []
13319
13590
  });
13320
- const errorRelatedIds = useMemo6(() => {
13591
+ const errorRelatedIds = useMemo7(() => {
13321
13592
  if (!hoveredErrorId) return [];
13322
13593
  const errorElements = elements.filter(
13323
13594
  (el) => el.type.includes("error")
@@ -13336,7 +13607,7 @@ var CanvasElementsRenderer = (props) => {
13336
13607
  }
13337
13608
  return relatedIds;
13338
13609
  }, [hoveredErrorId, elements]);
13339
- const primitives = useMemo6(() => {
13610
+ const primitives = useMemo7(() => {
13340
13611
  const combinedPrimitiveIds = [
13341
13612
  ...hoverState.primitiveIdsInMousedOverNet,
13342
13613
  ...errorRelatedIds
@@ -13510,7 +13781,7 @@ var PCBViewer = ({
13510
13781
  editEvents = editEventsProp ?? editEvents;
13511
13782
  const initialRenderCompleted = useRef11(false);
13512
13783
  const touchStartRef = useRef11(null);
13513
- const circuitJsonKey = useMemo7(
13784
+ const circuitJsonKey = useMemo8(
13514
13785
  () => calculateCircuitJsonKey(circuitJson),
13515
13786
  [circuitJson]
13516
13787
  );
@@ -13543,12 +13814,12 @@ var PCBViewer = ({
13543
13814
  initialRenderCompleted.current = true;
13544
13815
  }
13545
13816
  }, [circuitJson, refDimensions]);
13546
- const pcbElmsPreEdit = useMemo7(() => {
13817
+ const pcbElmsPreEdit = useMemo8(() => {
13547
13818
  return circuitJson?.filter(
13548
13819
  (e) => e.type.startsWith("pcb_") || e.type.startsWith("source_")
13549
13820
  ) ?? [];
13550
13821
  }, [circuitJsonKey]);
13551
- const elements = useMemo7(() => {
13822
+ const elements = useMemo8(() => {
13552
13823
  return applyEditEvents({
13553
13824
  circuitJson: pcbElmsPreEdit,
13554
13825
  editEvents
@@ -13565,7 +13836,7 @@ var PCBViewer = ({
13565
13836
  setEditEvents(newEditEvents);
13566
13837
  onEditEventsChanged?.(newEditEvents);
13567
13838
  };
13568
- const mergedInitialState = useMemo7(
13839
+ const mergedInitialState = useMemo8(
13569
13840
  () => ({
13570
13841
  ...initialState,
13571
13842
  ...disablePcbGroups && { is_showing_pcb_groups: false }