@tscircuit/checks 0.0.125 → 0.0.127

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
@@ -243,7 +243,7 @@ import {
243
243
  segmentToBoundsMinDistance,
244
244
  segmentToCircleMinDistance
245
245
  } from "@tscircuit/math-utils";
246
- import { segmentToSegmentMinDistance } from "@tscircuit/math-utils";
246
+ import { segmentToSegmentMinDistance as segmentToSegmentMinDistance2 } from "@tscircuit/math-utils";
247
247
  import {
248
248
  getFullConnectivityMapFromCircuitJson as getFullConnectivityMapFromCircuitJson2
249
249
  } from "circuit-json-to-connectivity-map";
@@ -572,7 +572,7 @@ var getClosestPointBetweenSegments = (segmentA, segmentB) => {
572
572
  };
573
573
  const dx = closestOnA.x - closestOnB.x;
574
574
  const dy = closestOnA.y - closestOnB.y;
575
- const distance4 = Math.sqrt(dx * dx + dy * dy);
575
+ const distance3 = Math.sqrt(dx * dx + dy * dy);
576
576
  const averagePoint = {
577
577
  x: (closestOnA.x + closestOnB.x) / 2,
578
578
  y: (closestOnA.y + closestOnB.y) / 2
@@ -636,6 +636,156 @@ var clamp = (value, min, max) => {
636
636
 
637
637
  // lib/check-each-pcb-trace-non-overlapping/getCollidableBounds.ts
638
638
  import { getBoundsOfPcbElements as getBoundsOfPcbElements2 } from "@tscircuit/circuit-json-util";
639
+
640
+ // lib/check-each-pcb-trace-non-overlapping/segment-to-polygon-clearance.ts
641
+ import {
642
+ distSq,
643
+ getSegmentIntersection,
644
+ isPointInsidePolygon,
645
+ pointToSegmentClosestPoint,
646
+ segmentToSegmentMinDistance
647
+ } from "@tscircuit/math-utils";
648
+ var rotatePoint = (point, angleDegrees) => {
649
+ const angle = angleDegrees * Math.PI / 180;
650
+ return {
651
+ x: point.x * Math.cos(angle) - point.y * Math.sin(angle),
652
+ y: point.x * Math.sin(angle) + point.y * Math.cos(angle)
653
+ };
654
+ };
655
+ var getRotatedRectPoints = ({
656
+ x,
657
+ y,
658
+ width,
659
+ height,
660
+ ccwRotation
661
+ }) => {
662
+ const halfWidth = width / 2;
663
+ const halfHeight = height / 2;
664
+ return [
665
+ { x: -halfWidth, y: -halfHeight },
666
+ { x: halfWidth, y: -halfHeight },
667
+ { x: halfWidth, y: halfHeight },
668
+ { x: -halfWidth, y: halfHeight }
669
+ ].map((point) => {
670
+ const rotated = rotatePoint(point, ccwRotation);
671
+ return { x: x + rotated.x, y: y + rotated.y };
672
+ });
673
+ };
674
+ var getPolygonPointsForPad = (pad) => {
675
+ if (pad.type === "pcb_smtpad") {
676
+ if (pad.shape === "polygon") return pad.points;
677
+ if (pad.shape === "rotated_rect") {
678
+ return getRotatedRectPoints({
679
+ x: pad.x,
680
+ y: pad.y,
681
+ width: pad.width,
682
+ height: pad.height,
683
+ ccwRotation: pad.ccw_rotation
684
+ });
685
+ }
686
+ }
687
+ if (pad.type === "pcb_plated_hole" && "rect_pad_width" in pad && "rect_pad_height" in pad) {
688
+ return getRotatedRectPoints({
689
+ x: pad.x,
690
+ y: pad.y,
691
+ width: pad.rect_pad_width,
692
+ height: pad.rect_pad_height,
693
+ ccwRotation: "rect_ccw_rotation" in pad && typeof pad.rect_ccw_rotation === "number" ? pad.rect_ccw_rotation : 0
694
+ });
695
+ }
696
+ throw new Error(
697
+ `Expected polygonal pad geometry, got ${pad.type} with shape "${pad.shape}"`
698
+ );
699
+ };
700
+ var getPolygonEdges = (points) => points.map(
701
+ (point, index) => [point, points[(index + 1) % points.length]]
702
+ );
703
+ var getClosestPointsBetweenSegments = (a1, a2, b1, b2) => {
704
+ const intersection = getSegmentIntersection(a1, a2, b1, b2);
705
+ if (intersection) {
706
+ return {
707
+ distance: 0,
708
+ pointOnA: intersection,
709
+ pointOnB: intersection,
710
+ center: intersection
711
+ };
712
+ }
713
+ const candidates = [
714
+ { pointOnA: a1, pointOnB: pointToSegmentClosestPoint(a1, b1, b2) },
715
+ { pointOnA: a2, pointOnB: pointToSegmentClosestPoint(a2, b1, b2) },
716
+ { pointOnA: pointToSegmentClosestPoint(b1, a1, a2), pointOnB: b1 },
717
+ { pointOnA: pointToSegmentClosestPoint(b2, a1, a2), pointOnB: b2 }
718
+ ];
719
+ let best = candidates[0];
720
+ let bestDistanceSquared = distSq(best.pointOnA, best.pointOnB);
721
+ for (const candidate of candidates.slice(1)) {
722
+ const candidateDistanceSquared = distSq(
723
+ candidate.pointOnA,
724
+ candidate.pointOnB
725
+ );
726
+ if (candidateDistanceSquared < bestDistanceSquared) {
727
+ best = candidate;
728
+ bestDistanceSquared = candidateDistanceSquared;
729
+ }
730
+ }
731
+ return {
732
+ distance: segmentToSegmentMinDistance(a1, a2, b1, b2),
733
+ pointOnA: best.pointOnA,
734
+ pointOnB: best.pointOnB,
735
+ center: {
736
+ x: (best.pointOnA.x + best.pointOnB.x) / 2,
737
+ y: (best.pointOnA.y + best.pointOnB.y) / 2
738
+ }
739
+ };
740
+ };
741
+ var getSegmentToPolygonClearance = (segment, polygon) => {
742
+ const start = { x: segment.x1, y: segment.y1 };
743
+ const end = { x: segment.x2, y: segment.y2 };
744
+ if (polygon.length < 3) {
745
+ return { distance: Number.POSITIVE_INFINITY, center: start };
746
+ }
747
+ const intersections = getPolygonEdges(polygon).map(
748
+ ([edgeStart, edgeEnd]) => getSegmentIntersection(start, end, edgeStart, edgeEnd)
749
+ ).filter((point) => point !== null);
750
+ if (intersections.length > 0) {
751
+ const dx = end.x - start.x;
752
+ const dy = end.y - start.y;
753
+ const lengthSquared = dx * dx + dy * dy;
754
+ intersections.sort((a, b) => {
755
+ const ta = ((a.x - start.x) * dx + (a.y - start.y) * dy) / lengthSquared;
756
+ const tb = ((b.x - start.x) * dx + (b.y - start.y) * dy) / lengthSquared;
757
+ return ta - tb;
758
+ });
759
+ return { distance: 0, center: intersections[0] };
760
+ }
761
+ if (isPointInsidePolygon(start, polygon) || isPointInsidePolygon(end, polygon)) {
762
+ return {
763
+ distance: 0,
764
+ center: {
765
+ x: (start.x + end.x) / 2,
766
+ y: (start.y + end.y) / 2
767
+ }
768
+ };
769
+ }
770
+ let best = getClosestPointsBetweenSegments(
771
+ start,
772
+ end,
773
+ polygon[0],
774
+ polygon[1]
775
+ );
776
+ for (const [edgeStart, edgeEnd] of getPolygonEdges(polygon).slice(1)) {
777
+ const candidate = getClosestPointsBetweenSegments(
778
+ start,
779
+ end,
780
+ edgeStart,
781
+ edgeEnd
782
+ );
783
+ if (candidate.distance < best.distance) best = candidate;
784
+ }
785
+ return { distance: best.distance, center: best.center };
786
+ };
787
+
788
+ // lib/check-each-pcb-trace-non-overlapping/getCollidableBounds.ts
639
789
  var getCollidableBounds = (collidable) => {
640
790
  if (collidable.type === "pcb_trace_segment") {
641
791
  return {
@@ -645,6 +795,18 @@ var getCollidableBounds = (collidable) => {
645
795
  maxY: Math.max(collidable.y1, collidable.y2)
646
796
  };
647
797
  }
798
+ if (collidable.type === "pcb_smtpad" || collidable.type === "pcb_plated_hole") {
799
+ const isPolygon = collidable.type === "pcb_smtpad" && (collidable.shape === "rotated_rect" || collidable.shape === "polygon") || collidable.type === "pcb_plated_hole" && "rect_pad_width" in collidable && "rect_pad_height" in collidable;
800
+ if (isPolygon) {
801
+ const polygonPoints = getPolygonPointsForPad(collidable);
802
+ return {
803
+ minX: Math.min(...polygonPoints.map((point) => point.x)),
804
+ minY: Math.min(...polygonPoints.map((point) => point.y)),
805
+ maxX: Math.max(...polygonPoints.map((point) => point.x)),
806
+ maxY: Math.max(...polygonPoints.map((point) => point.y))
807
+ };
808
+ }
809
+ }
648
810
  return getBoundsOfPcbElements2([collidable]);
649
811
  };
650
812
 
@@ -763,7 +925,7 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
763
925
  if (segmentA.layer !== segmentB.layer) continue;
764
926
  if (connMap.areIdsConnected(segmentA.pcb_trace_id, segmentB.pcb_trace_id))
765
927
  continue;
766
- const gap2 = segmentToSegmentMinDistance(
928
+ const gap2 = segmentToSegmentMinDistance2(
767
929
  { x: segmentA.x1, y: segmentA.y1 },
768
930
  { x: segmentA.x2, y: segmentA.y2 },
769
931
  { x: segmentB.x1, y: segmentB.y1 },
@@ -804,12 +966,12 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
804
966
  const isCircular = obj.type === "pcb_via" || obj.type === "pcb_plated_hole" && obj.shape === "circle" || obj.type === "pcb_hole" || obj.type === "pcb_smtpad" && obj.shape === "circle";
805
967
  if (isCircular) {
806
968
  const radius = getRadiusOfCircuitJsonElement(obj);
807
- const distance4 = segmentToCircleMinDistance(
969
+ const distance3 = segmentToCircleMinDistance(
808
970
  { x: segmentA.x1, y: segmentA.y1 },
809
971
  { x: segmentA.x2, y: segmentA.y2 },
810
972
  { x: obj.x, y: obj.y, radius }
811
973
  );
812
- const gap2 = distance4 - segmentA.thickness / 2;
974
+ const gap2 = distance3 - segmentA.thickness / 2;
813
975
  if (gap2 > minClearance - EPSILON) continue;
814
976
  const pcb_trace_error_id = `overlap_${segmentA.pcb_trace_id}_${primaryObjId}`;
815
977
  if (errorIds.has(pcb_trace_error_id)) continue;
@@ -838,6 +1000,40 @@ function checkEachPcbTraceNonOverlapping(circuitJson, {
838
1000
  ].filter(Boolean)
839
1001
  });
840
1002
  }
1003
+ const isPolygon = obj.type === "pcb_smtpad" && (obj.shape === "rotated_rect" || obj.shape === "polygon") || obj.type === "pcb_plated_hole" && "rect_pad_width" in obj && "rect_pad_height" in obj;
1004
+ if (isPolygon) {
1005
+ const padOutline = getPolygonPointsForPad(obj);
1006
+ const { distance: distance3, center } = getSegmentToPolygonClearance(
1007
+ segmentA,
1008
+ padOutline
1009
+ );
1010
+ const gap2 = distance3 - segmentA.thickness / 2;
1011
+ if (gap2 > minClearance - EPSILON) continue;
1012
+ const pcb_trace_error_id = `overlap_${segmentA.pcb_trace_id}_${primaryObjId}`;
1013
+ if (errorIds.has(pcb_trace_error_id)) continue;
1014
+ errorIds.add(pcb_trace_error_id);
1015
+ errors.push({
1016
+ type: "pcb_trace_error",
1017
+ error_type: "pcb_trace_error",
1018
+ message: constructErrorMessage(
1019
+ getReadableName(segmentA.pcb_trace_id),
1020
+ `${obj.type} "${getReadableName(getPrimaryId(obj))}"`,
1021
+ gap2
1022
+ ),
1023
+ pcb_trace_id: segmentA.pcb_trace_id,
1024
+ center,
1025
+ source_trace_id: "",
1026
+ pcb_trace_error_id,
1027
+ pcb_component_ids: [
1028
+ "pcb_component_id" in obj ? obj.pcb_component_id : void 0
1029
+ ].filter(Boolean),
1030
+ pcb_port_ids: [
1031
+ ...getPcbPortIdsConnectedToTraces([segmentA._pcbTrace]),
1032
+ "pcb_port_id" in obj ? obj.pcb_port_id : void 0
1033
+ ].filter(Boolean)
1034
+ });
1035
+ continue;
1036
+ }
841
1037
  const gap = segmentToBoundsMinDistance(
842
1038
  { x: segmentA.x1, y: segmentA.y1 },
843
1039
  { x: segmentA.x2, y: segmentA.y2 },
@@ -1041,11 +1237,11 @@ function computeOverlapDistance(compPoly, boardPoly, componentCenter, componentW
1041
1237
  });
1042
1238
  }
1043
1239
  const matrix = rotateDEG(rotationDeg, componentCenter.x, componentCenter.y);
1044
- const rotatePoint = (pt) => {
1240
+ const rotatePoint2 = (pt) => {
1045
1241
  const p = applyToPoint(matrix, pt);
1046
1242
  return new Flatten.Point(p.x, p.y);
1047
1243
  };
1048
- const rotatedPoints = corners.concat(midpoints).map(rotatePoint);
1244
+ const rotatedPoints = corners.concat(midpoints).map(rotatePoint2);
1049
1245
  let maxDistance = 0;
1050
1246
  for (const pt of rotatedPoints) {
1051
1247
  if (!boardPoly.contains(pt)) {
@@ -1420,7 +1616,7 @@ function checkSourceTracesHavePcbTraces(circuitJson) {
1420
1616
 
1421
1617
  // lib/check-trace-out-of-board/checkTraceOutOfBoard.ts
1422
1618
  import { cju as cju3 } from "@tscircuit/circuit-json-util";
1423
- import { segmentToSegmentMinDistance as segmentToSegmentMinDistance2 } from "@tscircuit/math-utils";
1619
+ import { segmentToSegmentMinDistance as segmentToSegmentMinDistance3 } from "@tscircuit/math-utils";
1424
1620
  function getBoardPolygonPoints(board) {
1425
1621
  if (board.outline && board.outline.length > 0) {
1426
1622
  return board.outline.map((p) => ({ x: p.x, y: p.y }));
@@ -1464,14 +1660,14 @@ function checkPcbTracesOutOfBoard(circuitJson, config = {}) {
1464
1660
  for (let j = 0; j < boardPoints.length; j++) {
1465
1661
  const edgeStart = boardPoints[j];
1466
1662
  const edgeEnd = boardPoints[(j + 1) % boardPoints.length];
1467
- const distance4 = segmentToSegmentMinDistance2(
1663
+ const distance3 = segmentToSegmentMinDistance3(
1468
1664
  segmentStart,
1469
1665
  segmentEnd,
1470
1666
  edgeStart,
1471
1667
  edgeEnd
1472
1668
  );
1473
- if (distance4 < minDistance) {
1474
- minDistance = distance4;
1669
+ if (distance3 < minDistance) {
1670
+ minDistance = distance3;
1475
1671
  }
1476
1672
  }
1477
1673
  const minimumDistance = traceWidth / 2 + margin;
@@ -2245,7 +2441,7 @@ function checkConnectorAccessibleOrientation(circuitJson) {
2245
2441
  // lib/check-courtyard-overlap/checkCourtyardOverlap.ts
2246
2442
  import {
2247
2443
  doSegmentsIntersect,
2248
- isPointInsidePolygon
2444
+ isPointInsidePolygon as isPointInsidePolygon2
2249
2445
  } from "@tscircuit/math-utils";
2250
2446
  function getCourtyardPolygon(el) {
2251
2447
  if (el.type === "pcb_courtyard_rect") {
@@ -2291,8 +2487,8 @@ function getComponentName2(circuitJson, pcbComponentId) {
2291
2487
  return pcbComponentId;
2292
2488
  }
2293
2489
  function polygonsOverlap(polyA, polyB) {
2294
- if (polyA.some((p) => isPointInsidePolygon(p, polyB))) return true;
2295
- if (polyB.some((p) => isPointInsidePolygon(p, polyA))) return true;
2490
+ if (polyA.some((p) => isPointInsidePolygon2(p, polyB))) return true;
2491
+ if (polyB.some((p) => isPointInsidePolygon2(p, polyA))) return true;
2296
2492
  for (let i = 0; i < polyA.length; i++) {
2297
2493
  const a1 = polyA[i];
2298
2494
  const a2 = polyA[(i + 1) % polyA.length];
@@ -2349,13 +2545,22 @@ function checkCourtyardOverlap(circuitJson) {
2349
2545
  }
2350
2546
 
2351
2547
  // lib/check-traces-are-contiguous/is-point-in-pad.ts
2352
- function distance3(x1, y1, x2, y2) {
2353
- return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
2548
+ function getDistanceBetweenPoints(pointA, pointB) {
2549
+ return Math.sqrt((pointB.x - pointA.x) ** 2 + (pointB.y - pointA.y) ** 2);
2550
+ }
2551
+ var POINT_ON_SEGMENT_TOLERANCE_MM = 1e-9;
2552
+ function isPointOnSegment(point, segment) {
2553
+ const crossProduct = (point.y - segment.start.y) * (segment.end.x - segment.start.x) - (point.x - segment.start.x) * (segment.end.y - segment.start.y);
2554
+ if (Math.abs(crossProduct) > POINT_ON_SEGMENT_TOLERANCE_MM) return false;
2555
+ const dotProduct = (point.x - segment.start.x) * (segment.end.x - segment.start.x) + (point.y - segment.start.y) * (segment.end.y - segment.start.y);
2556
+ if (dotProduct < -POINT_ON_SEGMENT_TOLERANCE_MM) return false;
2557
+ const squaredLength = (segment.end.x - segment.start.x) ** 2 + (segment.end.y - segment.start.y) ** 2;
2558
+ return dotProduct <= squaredLength + POINT_ON_SEGMENT_TOLERANCE_MM;
2354
2559
  }
2355
2560
  function isPointInPad(point, pad) {
2356
2561
  if (pad.type === "pcb_smtpad") {
2357
2562
  if (pad.shape === "circle") {
2358
- return distance3(point.x, point.y, pad.x, pad.y) <= pad.radius;
2563
+ return getDistanceBetweenPoints(point, pad) <= pad.radius;
2359
2564
  }
2360
2565
  if (pad.shape === "rect") {
2361
2566
  const halfWidth = pad.width / 2;
@@ -2387,10 +2592,21 @@ function isPointInPad(point, pad) {
2387
2592
  );
2388
2593
  return cornerX * cornerX + cornerY * cornerY <= radius * radius;
2389
2594
  }
2595
+ if (pad.shape === "polygon") {
2596
+ let inside = false;
2597
+ for (let i = 0, j = pad.points.length - 1; i < pad.points.length; j = i++) {
2598
+ const pi = pad.points[i];
2599
+ const pj = pad.points[j];
2600
+ if (isPointOnSegment(point, { start: pi, end: pj })) return true;
2601
+ const intersects = pi.y > point.y !== pj.y > point.y && point.x < (pj.x - pi.x) * (point.y - pi.y) / (pj.y - pi.y) + pi.x;
2602
+ if (intersects) inside = !inside;
2603
+ }
2604
+ return inside;
2605
+ }
2390
2606
  }
2391
2607
  if (pad.type === "pcb_plated_hole") {
2392
2608
  if (pad.shape === "circle") {
2393
- return distance3(point.x, point.y, pad.x, pad.y) <= pad.outer_diameter / 2;
2609
+ return getDistanceBetweenPoints(point, pad) <= pad.outer_diameter / 2;
2394
2610
  }
2395
2611
  if (pad.shape === "oval" || pad.shape === "pill") {
2396
2612
  return Math.abs(point.x - pad.x) <= pad.outer_width / 2 && Math.abs(point.y - pad.y) <= pad.outer_height / 2;
@@ -2430,12 +2646,15 @@ function checkTracesAreContiguous(circuitJson) {
2430
2646
  const padMap = /* @__PURE__ */ new Map();
2431
2647
  for (const pad of pcbSmtPads) {
2432
2648
  if (pad.pcb_port_id) {
2433
- padMap.set(pad.pcb_port_id, pad);
2649
+ padMap.set(pad.pcb_port_id, [...padMap.get(pad.pcb_port_id) ?? [], pad]);
2434
2650
  }
2435
2651
  }
2436
2652
  for (const hole of pcbPlatedHoles) {
2437
2653
  if (hole.pcb_port_id) {
2438
- padMap.set(hole.pcb_port_id, hole);
2654
+ padMap.set(hole.pcb_port_id, [
2655
+ ...padMap.get(hole.pcb_port_id) ?? [],
2656
+ hole
2657
+ ]);
2439
2658
  }
2440
2659
  }
2441
2660
  for (const trace of pcbTraces) {
@@ -2483,16 +2702,20 @@ function checkTracesAreContiguous(circuitJson) {
2483
2702
  );
2484
2703
  for (const port of expectedPorts) {
2485
2704
  if (!port.pcb_port_id) continue;
2486
- const pad = padMap.get(port.pcb_port_id);
2487
- if (!pad) continue;
2488
- const isFirstPointConnected = firstPoint.route_type === "wire" && isPointInPad({ x: firstPoint.x, y: firstPoint.y }, pad);
2489
- const isLastPointConnected = lastPoint.route_type === "wire" && isPointInPad({ x: lastPoint.x, y: lastPoint.y }, pad);
2705
+ const pads = padMap.get(port.pcb_port_id);
2706
+ if (!pads?.length) continue;
2707
+ const isFirstPointConnected = firstPoint.route_type === "wire" && pads.some(
2708
+ (pad) => isPointInPad({ x: firstPoint.x, y: firstPoint.y }, pad)
2709
+ );
2710
+ const isLastPointConnected = lastPoint.route_type === "wire" && pads.some(
2711
+ (pad) => isPointInPad({ x: lastPoint.x, y: lastPoint.y }, pad)
2712
+ );
2490
2713
  if (!isFirstPointConnected && !isLastPointConnected) {
2491
2714
  const portName = getReadableNameForPcbPort2(
2492
2715
  circuitJson,
2493
2716
  port.pcb_port_id
2494
2717
  ).replace("pcb_port", "");
2495
- const padType = pad.type.replace(/pcb_/, "");
2718
+ const padType = pads[0].type.replace(/pcb_/, "");
2496
2719
  const errorCenter = {
2497
2720
  x: (firstPoint.x + lastPoint.x) / 2,
2498
2721
  y: (firstPoint.y + lastPoint.y) / 2
@@ -2514,11 +2737,15 @@ function checkTracesAreContiguous(circuitJson) {
2514
2737
  let firstConnectsToAnyPad = false;
2515
2738
  let lastConnectsToAnyPad = false;
2516
2739
  const endpointsAreSame = firstPoint.route_type === "wire" && lastPoint.route_type === "wire" && Math.abs(firstPoint.x - lastPoint.x) < 0.01 && Math.abs(firstPoint.y - lastPoint.y) < 0.01;
2517
- for (const [portId, pad] of padMap) {
2518
- if (firstPoint.route_type === "wire" && isPointInPad({ x: firstPoint.x, y: firstPoint.y }, pad)) {
2740
+ for (const pads of padMap.values()) {
2741
+ if (firstPoint.route_type === "wire" && pads.some(
2742
+ (pad) => isPointInPad({ x: firstPoint.x, y: firstPoint.y }, pad)
2743
+ )) {
2519
2744
  firstConnectsToAnyPad = true;
2520
2745
  }
2521
- if (lastPoint.route_type === "wire" && isPointInPad({ x: lastPoint.x, y: lastPoint.y }, pad)) {
2746
+ if (lastPoint.route_type === "wire" && pads.some(
2747
+ (pad) => isPointInPad({ x: lastPoint.x, y: lastPoint.y }, pad)
2748
+ )) {
2522
2749
  lastConnectsToAnyPad = true;
2523
2750
  }
2524
2751
  }