@tscircuit/core 0.0.2024 → 0.0.2026

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
@@ -3280,10 +3280,70 @@ var getAxisAlignedRectFromPolygon = (points) => {
3280
3280
  };
3281
3281
  };
3282
3282
 
3283
+ // lib/utils/obstacles/getCircumscribedRectFromCircularPcbKeepoutOutline.ts
3284
+ var MINIMUM_CIRCULAR_OUTLINE_POINT_COUNT = 12;
3285
+ var MINIMUM_CIRCULAR_OUTLINE_ASPECT_RATIO = 0.99;
3286
+ var MAXIMUM_RADIUS_VARIATION_RATIO = 1e-3;
3287
+ var getCircumscribedRectFromCircularPcbKeepoutOutline = (keepout) => {
3288
+ const { outline, stroke_width } = keepout;
3289
+ const firstPoint = outline[0];
3290
+ const lastPoint = outline.at(-1);
3291
+ if (!firstPoint || !lastPoint) return null;
3292
+ if (firstPoint.x !== lastPoint.x || firstPoint.y !== lastPoint.y) return null;
3293
+ const centerlinePoints = outline.slice(0, -1);
3294
+ if (centerlinePoints.length < MINIMUM_CIRCULAR_OUTLINE_POINT_COUNT)
3295
+ return null;
3296
+ const minX = Math.min(...centerlinePoints.map(({ x }) => x));
3297
+ const maxX = Math.max(...centerlinePoints.map(({ x }) => x));
3298
+ const minY = Math.min(...centerlinePoints.map(({ y }) => y));
3299
+ const maxY = Math.max(...centerlinePoints.map(({ y }) => y));
3300
+ const centerlineWidth = maxX - minX;
3301
+ const centerlineHeight = maxY - minY;
3302
+ const centerlineDiameter = Math.max(centerlineWidth, centerlineHeight);
3303
+ if (!Number.isFinite(centerlineDiameter) || centerlineDiameter <= 0 || Math.min(centerlineWidth, centerlineHeight) / centerlineDiameter < MINIMUM_CIRCULAR_OUTLINE_ASPECT_RATIO) {
3304
+ return null;
3305
+ }
3306
+ const center = { x: (minX + maxX) / 2, y: (minY + maxY) / 2 };
3307
+ const radii = centerlinePoints.map(
3308
+ ({ x, y }) => Math.hypot(x - center.x, y - center.y)
3309
+ );
3310
+ if (Math.max(...radii) - Math.min(...radii) > centerlineDiameter * MAXIMUM_RADIUS_VARIATION_RATIO) {
3311
+ return null;
3312
+ }
3313
+ const angles = centerlinePoints.map(
3314
+ ({ x, y }) => Math.atan2(y - center.y, x - center.x)
3315
+ );
3316
+ const angularSteps = angles.map((angle, angleIndex) => {
3317
+ const nextAngle = angles[(angleIndex + 1) % angles.length];
3318
+ return Math.atan2(Math.sin(nextAngle - angle), Math.cos(nextAngle - angle));
3319
+ });
3320
+ const traversalDirection = Math.sign(angularSteps[0]);
3321
+ if (traversalDirection === 0 || angularSteps.some(
3322
+ (angularStep) => Math.sign(angularStep) !== traversalDirection || Math.abs(angularStep) > 4 * Math.PI / centerlinePoints.length
3323
+ )) {
3324
+ return null;
3325
+ }
3326
+ const outerDiameter = centerlineDiameter + stroke_width;
3327
+ return { center, width: outerDiameter, height: outerDiameter };
3328
+ };
3329
+
3283
3330
  // lib/utils/obstacles/getObstaclesFromPcbKeepoutOutline.ts
3284
3331
  var getObstaclesFromPcbKeepoutOutline = (keepout, componentId) => {
3285
3332
  const { outline, stroke_width } = keepout;
3286
3333
  if (!Number.isFinite(stroke_width) || stroke_width <= 0) return [];
3334
+ const circularKeepoutRect = getCircumscribedRectFromCircularPcbKeepoutOutline(keepout);
3335
+ if (circularKeepoutRect) {
3336
+ return [
3337
+ {
3338
+ obstacleId: keepout.pcb_keepout_id,
3339
+ componentId,
3340
+ type: "rect",
3341
+ layers: keepout.layers,
3342
+ ...circularKeepoutRect,
3343
+ connectedTo: []
3344
+ }
3345
+ ];
3346
+ }
3287
3347
  const obstacles = [];
3288
3348
  for (let segmentIndex = 0; segmentIndex < outline.length - 1; segmentIndex++) {
3289
3349
  const start = outline[segmentIndex];
@@ -3548,8 +3608,7 @@ var getObstaclesFromCircuitJson = (circuitJson, connMap) => {
3548
3608
  if (element.shape === "circle") {
3549
3609
  obstacles.push({
3550
3610
  componentId: pcbComponentId,
3551
- // @ts-ignore
3552
- type: "oval",
3611
+ type: "rect",
3553
3612
  layers: element.layers,
3554
3613
  center: {
3555
3614
  x: element.center.x,
@@ -6218,10 +6277,10 @@ function addTraceTeardrops(route, contacts) {
6218
6277
  result.push(start);
6219
6278
  continue;
6220
6279
  }
6221
- const atDistance = (distance23) => ({
6280
+ const atDistance = (distance25) => ({
6222
6281
  route_type: "wire",
6223
- x: start.x + (end.x - start.x) * distance23 / length8,
6224
- y: start.y + (end.y - start.y) * distance23 / length8,
6282
+ x: start.x + (end.x - start.x) * distance25 / length8,
6283
+ y: start.y + (end.y - start.y) * distance25 / length8,
6225
6284
  width: start.width,
6226
6285
  layer: start.layer
6227
6286
  });
@@ -6744,11 +6803,11 @@ function Trace_doInitialPcbTraceRender(trace) {
6744
6803
  const closestNetBPort = getClosest(netAPort, netBPorts);
6745
6804
  const netAPortPosition = netAPort._getGlobalPcbPositionBeforeLayout();
6746
6805
  const netBPortPosition = closestNetBPort._getGlobalPcbPositionBeforeLayout();
6747
- const distance23 = Math.sqrt(
6806
+ const distance25 = Math.sqrt(
6748
6807
  (netAPortPosition.x - netBPortPosition.x) ** 2 + (netAPortPosition.y - netBPortPosition.y) ** 2
6749
6808
  );
6750
- if (distance23 < closestDistance) {
6751
- closestDistance = distance23;
6809
+ if (distance25 < closestDistance) {
6810
+ closestDistance = distance25;
6752
6811
  closestPortPair = [netAPort, closestNetBPort];
6753
6812
  }
6754
6813
  }
@@ -12959,7 +13018,7 @@ var createComponentsFromCircuitJson = ({
12959
13018
  const schematicSymbolId = schematicComponent?.schematic_symbol_id;
12960
13019
  const parentSymbol = typeof schematicSymbolId === "string" ? schematicSymbolsByImportedId.get(schematicSymbolId) : void 0;
12961
13020
  if (parentSymbol && schematicComponent?.is_box_with_pins === true && elm.center && elm.side_of_component) {
12962
- const distance23 = elm.distance_from_component_edge;
13021
+ const distance25 = elm.distance_from_component_edge;
12963
13022
  const facingDirection = getFacingDirectionFromSide(
12964
13023
  elm.side_of_component
12965
13024
  );
@@ -12967,8 +13026,8 @@ var createComponentsFromCircuitJson = ({
12967
13026
  const directionVector = getUnitVectorFromDirection2(facingDirection);
12968
13027
  const stemDirection = elm.facing_direction === facingDirection ? 1 : -1;
12969
13028
  const portCenter = {
12970
- x: elm.center.x + directionVector.x * distance23 * stemDirection,
12971
- y: elm.center.y + directionVector.y * distance23 * stemDirection
13029
+ x: elm.center.x + directionVector.x * distance25 * stemDirection,
13030
+ y: elm.center.y + directionVector.y * distance25 * stemDirection
12972
13031
  };
12973
13032
  const sourcePort = sourcePortsByImportedId.get(elm.source_port_id);
12974
13033
  const aliases = Array.from(
@@ -17737,15 +17796,15 @@ var Capacitor_addPolarityFabricationSymbols = (capacitor) => {
17737
17796
  source_port_id: negatives[0].source_port_id
17738
17797
  });
17739
17798
  if (!positive || !negative) return;
17740
- const distance23 = Math.hypot(negative.x - positive.x, negative.y - positive.y);
17741
- if (distance23 < 1e-6) return;
17799
+ const distance25 = Math.hypot(negative.x - positive.x, negative.y - positive.y);
17800
+ if (distance25 < 1e-6) return;
17742
17801
  const frame = compose7(
17743
17802
  translate5((positive.x + negative.x) / 2, (positive.y + negative.y) / 2),
17744
17803
  rotate4(Math.atan2(negative.y - positive.y, negative.x - positive.x))
17745
17804
  );
17746
17805
  const point6 = (x, y) => applyToPoint26(frame, { x, y });
17747
- const offset = distance23 / 4;
17748
- const halfSize = Math.min(distance23 * 0.12, 0.3);
17806
+ const offset = distance25 / 4;
17807
+ const halfSize = Math.min(distance25 * 0.12, 0.3);
17749
17808
  const routes = [
17750
17809
  [point6(-offset - halfSize, 0), point6(-offset + halfSize, 0)],
17751
17810
  [point6(-offset, -halfSize), point6(-offset, halfSize)],
@@ -19570,7 +19629,7 @@ import {
19570
19629
  groupProps
19571
19630
  } from "@tscircuit/props";
19572
19631
  import {
19573
- distance as distance16
19632
+ distance as distance18
19574
19633
  } from "circuit-json";
19575
19634
  import Debug15 from "debug";
19576
19635
 
@@ -38814,9 +38873,9 @@ function getFreeBoundaryTracks(params) {
38814
38873
  for (const via of vias) {
38815
38874
  if (!via.spanLayers.includes(targetLayer2)) continue;
38816
38875
  const radius = via.diameter / 2 + traceWidth / 2 + clearance;
38817
- const distance23 = Math.abs(via.center[across] - edge);
38818
- if (distance23 >= radius) continue;
38819
- const extent = Math.sqrt(radius * radius - distance23 * distance23);
38876
+ const distance25 = Math.abs(via.center[across] - edge);
38877
+ if (distance25 >= radius) continue;
38878
+ const extent = Math.sqrt(radius * radius - distance25 * distance25);
38820
38879
  blocked.push([via.center[along] - extent, via.center[along] + extent]);
38821
38880
  }
38822
38881
  const gaps = [];
@@ -50507,6 +50566,8 @@ function getPresetAutoroutingConfig(autorouterConfig, platformConfig) {
50507
50566
  };
50508
50567
  case "bus-lanes":
50509
50568
  return { local: true, groupMode: "subcircuit", preset: "bus_lanes" };
50569
+ case "dogbone":
50570
+ return { local: true, groupMode: "subcircuit", preset: "dogbone" };
50510
50571
  case "fanout":
50511
50572
  return {
50512
50573
  local: true,
@@ -50569,11 +50630,1027 @@ function getPresetAutoroutingConfig(autorouterConfig, platformConfig) {
50569
50630
  }
50570
50631
  }
50571
50632
 
50572
- // node_modules/@tscircuit/bus-lanes-solver/lib/bus-lanes-solver.ts
50633
+ // node_modules/@tscircuit/dogbone-solver/lib/dogbone-fanout-solver.ts
50634
+ import { BaseSolver as BaseSolver7 } from "@tscircuit/solver-utils";
50635
+
50636
+ // node_modules/@tscircuit/dogbone-solver/node_modules/@tscircuit/fanout-solver/lib/geometry.ts
50637
+ var EPSILON24 = 1e-9;
50638
+ function obstacleIsCircular2(obstacle) {
50639
+ return obstacle.shape === "circle";
50640
+ }
50641
+ function toObstacleLocalPoint2(point6, obstacle) {
50642
+ const rotationRadians = -(obstacle.ccwRotationDegrees ?? 0) * Math.PI / 180;
50643
+ const dx = point6.x - obstacle.center.x;
50644
+ const dy = point6.y - obstacle.center.y;
50645
+ return {
50646
+ x: dx * Math.cos(rotationRadians) - dy * Math.sin(rotationRadians),
50647
+ y: dx * Math.sin(rotationRadians) + dy * Math.cos(rotationRadians)
50648
+ };
50649
+ }
50650
+ function distance12(a, b) {
50651
+ return Math.hypot(a.x - b.x, a.y - b.y);
50652
+ }
50653
+ function distancePointToSegment2(point6, start, end) {
50654
+ const dx = end.x - start.x;
50655
+ const dy = end.y - start.y;
50656
+ const lengthSquared = dx * dx + dy * dy;
50657
+ const rawT = lengthSquared < EPSILON24 ? 0 : ((point6.x - start.x) * dx + (point6.y - start.y) * dy) / lengthSquared;
50658
+ const t = Math.max(0, Math.min(1, rawT));
50659
+ return Math.hypot(point6.x - (start.x + t * dx), point6.y - (start.y + t * dy));
50660
+ }
50661
+ function cross2(origin, a, b) {
50662
+ return (a.x - origin.x) * (b.y - origin.y) - (a.y - origin.y) * (b.x - origin.x);
50663
+ }
50664
+ function segmentsProperlyCross2(a, b, c, d) {
50665
+ const d1 = cross2(c, d, a);
50666
+ const d2 = cross2(c, d, b);
50667
+ const d3 = cross2(a, b, c);
50668
+ const d4 = cross2(a, b, d);
50669
+ return (d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0);
50670
+ }
50671
+ function distanceSegmentToSegment2(firstStart, firstEnd, secondStart, secondEnd) {
50672
+ if (segmentsProperlyCross2(firstStart, firstEnd, secondStart, secondEnd)) {
50673
+ return 0;
50674
+ }
50675
+ return Math.min(
50676
+ distancePointToSegment2(firstStart, secondStart, secondEnd),
50677
+ distancePointToSegment2(firstEnd, secondStart, secondEnd),
50678
+ distancePointToSegment2(secondStart, firstStart, firstEnd),
50679
+ distancePointToSegment2(secondEnd, firstStart, firstEnd)
50680
+ );
50681
+ }
50682
+ function distancePointToObstacle2(point6, obstacle) {
50683
+ if (obstacleIsCircular2(obstacle)) {
50684
+ return Math.max(0, distance12(point6, obstacle.center) - obstacle.width / 2);
50685
+ }
50686
+ const localPoint = toObstacleLocalPoint2(point6, obstacle);
50687
+ const dx = Math.max(Math.abs(localPoint.x) - obstacle.width / 2, 0);
50688
+ const dy = Math.max(Math.abs(localPoint.y) - obstacle.height / 2, 0);
50689
+ return Math.hypot(dx, dy);
50690
+ }
50691
+ function distanceSegmentToObstacle2(segment2, obstacle) {
50692
+ if (obstacleIsCircular2(obstacle)) {
50693
+ return Math.max(
50694
+ 0,
50695
+ distancePointToSegment2(obstacle.center, segment2.start, segment2.end) - obstacle.width / 2
50696
+ );
50697
+ }
50698
+ const localStart = toObstacleLocalPoint2(segment2.start, obstacle);
50699
+ const localEnd = toObstacleLocalPoint2(segment2.end, obstacle);
50700
+ if (Math.abs(localStart.x) <= obstacle.width / 2 + EPSILON24 && Math.abs(localStart.y) <= obstacle.height / 2 + EPSILON24) {
50701
+ return 0;
50702
+ }
50703
+ if (Math.abs(localEnd.x) <= obstacle.width / 2 + EPSILON24 && Math.abs(localEnd.y) <= obstacle.height / 2 + EPSILON24) {
50704
+ return 0;
50705
+ }
50706
+ const minX = -obstacle.width / 2;
50707
+ const maxX = obstacle.width / 2;
50708
+ const minY = -obstacle.height / 2;
50709
+ const maxY = obstacle.height / 2;
50710
+ const corners = [
50711
+ { x: minX, y: minY },
50712
+ { x: maxX, y: minY },
50713
+ { x: maxX, y: maxY },
50714
+ { x: minX, y: maxY }
50715
+ ];
50716
+ let minimumDistance = Number.POSITIVE_INFINITY;
50717
+ for (let index = 0; index < corners.length; index++) {
50718
+ minimumDistance = Math.min(
50719
+ minimumDistance,
50720
+ distanceSegmentToSegment2(
50721
+ localStart,
50722
+ localEnd,
50723
+ corners[index],
50724
+ corners[(index + 1) % corners.length]
50725
+ )
50726
+ );
50727
+ }
50728
+ return minimumDistance;
50729
+ }
50730
+ function segmentsAreClear2(first, second, clearance) {
50731
+ if (first.layer !== second.layer) return true;
50732
+ const requiredDistance = (first.width + second.width) / 2 + clearance;
50733
+ const broadPhaseDistance = requiredDistance + EPSILON24;
50734
+ if (Math.min(first.start.x, first.end.x) - Math.max(second.start.x, second.end.x) > broadPhaseDistance || Math.min(second.start.x, second.end.x) - Math.max(first.start.x, first.end.x) > broadPhaseDistance || Math.min(first.start.y, first.end.y) - Math.max(second.start.y, second.end.y) > broadPhaseDistance || Math.min(second.start.y, second.end.y) - Math.max(first.start.y, first.end.y) > broadPhaseDistance) {
50735
+ return true;
50736
+ }
50737
+ return distanceSegmentToSegment2(
50738
+ first.start,
50739
+ first.end,
50740
+ second.start,
50741
+ second.end
50742
+ ) >= requiredDistance - EPSILON24;
50743
+ }
50744
+
50745
+ // node_modules/@tscircuit/dogbone-solver/node_modules/@tscircuit/fanout-solver/lib/match-component-dogbone-via-sites.ts
50746
+ var EPSILON25 = 1e-9;
50747
+ var DEFAULT_MAXIMUM_SEARCH_STATES2 = 1e5;
50748
+ function assertGeometryRules2(rules) {
50749
+ for (const [name, value] of [
50750
+ ["viaDiameter", rules.viaDiameter],
50751
+ ["traceWidth", rules.traceWidth]
50752
+ ]) {
50753
+ if (!Number.isFinite(value) || value <= 0) {
50754
+ throw new Error(
50755
+ `FanoutSolver: dogbone ${name} must be a positive finite number, received ${value}`
50756
+ );
50757
+ }
50758
+ }
50759
+ if (!Number.isFinite(rules.clearance) || rules.clearance < 0) {
50760
+ throw new Error(
50761
+ `FanoutSolver: dogbone clearance must be a non-negative finite number, received ${rules.clearance}`
50762
+ );
50763
+ }
50764
+ if (rules.viaHoleDiameter !== void 0 && (!Number.isFinite(rules.viaHoleDiameter) || rules.viaHoleDiameter <= 0)) {
50765
+ throw new Error(
50766
+ `FanoutSolver: dogbone viaHoleDiameter must be a positive finite number, received ${rules.viaHoleDiameter}`
50767
+ );
50768
+ }
50769
+ if (rules.holeToHoleClearance !== void 0 && (!Number.isFinite(rules.holeToHoleClearance) || rules.holeToHoleClearance < 0)) {
50770
+ throw new Error(
50771
+ `FanoutSolver: dogbone holeToHoleClearance must be a non-negative finite number, received ${rules.holeToHoleClearance}`
50772
+ );
50773
+ }
50774
+ if (rules.holeToHoleClearance !== void 0 && rules.viaHoleDiameter === void 0) {
50775
+ throw new Error(
50776
+ "FanoutSolver: dogbone holeToHoleClearance requires viaHoleDiameter"
50777
+ );
50778
+ }
50779
+ const maximumSearchStates = rules.maximumSearchStates ?? DEFAULT_MAXIMUM_SEARCH_STATES2;
50780
+ if (!Number.isInteger(maximumSearchStates) || maximumSearchStates < 1) {
50781
+ throw new Error(
50782
+ `FanoutSolver: dogbone maximumSearchStates must be a positive integer, received ${maximumSearchStates}`
50783
+ );
50784
+ }
50785
+ return maximumSearchStates;
50786
+ }
50787
+ function uniqueSortedCoordinates2(values) {
50788
+ const result = [];
50789
+ for (const value of values.toSorted((first, second) => first - second)) {
50790
+ if (!Number.isFinite(value)) continue;
50791
+ if (result.length === 0 || Math.abs(result.at(-1) - value) > EPSILON25) {
50792
+ result.push(value);
50793
+ }
50794
+ }
50795
+ return result;
50796
+ }
50797
+ function getComponentMatchingInputs2(preparedBuses) {
50798
+ const byComponent = /* @__PURE__ */ new Map();
50799
+ const componentByConnectionIndex = /* @__PURE__ */ new Map();
50800
+ const obstacleSetByComponentId = /* @__PURE__ */ new Map();
50801
+ for (const bus of preparedBuses) {
50802
+ let component = byComponent.get(bus.componentId);
50803
+ if (!component) {
50804
+ component = {
50805
+ componentId: bus.componentId,
50806
+ connections: [],
50807
+ obstacles: [],
50808
+ xCoordinates: [],
50809
+ yCoordinates: [],
50810
+ pitchX: Number.POSITIVE_INFINITY,
50811
+ pitchY: Number.POSITIVE_INFINITY
50812
+ };
50813
+ byComponent.set(bus.componentId, component);
50814
+ obstacleSetByComponentId.set(bus.componentId, /* @__PURE__ */ new Set());
50815
+ }
50816
+ component.xCoordinates.push(...bus.xCoordinates);
50817
+ component.yCoordinates.push(...bus.yCoordinates);
50818
+ if (Number.isFinite(bus.pitchX) && bus.pitchX > EPSILON25) {
50819
+ component.pitchX = Math.min(component.pitchX, bus.pitchX);
50820
+ }
50821
+ if (Number.isFinite(bus.pitchY) && bus.pitchY > EPSILON25) {
50822
+ component.pitchY = Math.min(component.pitchY, bus.pitchY);
50823
+ }
50824
+ const obstacleSet = obstacleSetByComponentId.get(bus.componentId);
50825
+ for (const obstacle of bus.componentObstacles) {
50826
+ if (!obstacleSet.has(obstacle)) {
50827
+ obstacleSet.add(obstacle);
50828
+ component.obstacles.push(obstacle);
50829
+ }
50830
+ }
50831
+ for (const preparedConnection of bus.connections) {
50832
+ const existingComponent = componentByConnectionIndex.get(
50833
+ preparedConnection.connectionIndex
50834
+ );
50835
+ if (existingComponent !== void 0) {
50836
+ if (existingComponent !== bus.componentId) {
50837
+ throw new Error(
50838
+ `FanoutSolver: connection index ${preparedConnection.connectionIndex} belongs to multiple components`
50839
+ );
50840
+ }
50841
+ continue;
50842
+ }
50843
+ componentByConnectionIndex.set(
50844
+ preparedConnection.connectionIndex,
50845
+ bus.componentId
50846
+ );
50847
+ component.connections.push({
50848
+ preparedConnection,
50849
+ busId: bus.busId,
50850
+ direction: bus.direction,
50851
+ terminationType: bus.termination.type
50852
+ });
50853
+ }
50854
+ }
50855
+ return [...byComponent.values()].map((component) => ({
50856
+ ...component,
50857
+ connections: component.connections.toSorted(
50858
+ (first, second) => first.preparedConnection.connectionIndex - second.preparedConnection.connectionIndex
50859
+ ),
50860
+ xCoordinates: uniqueSortedCoordinates2(component.xCoordinates),
50861
+ yCoordinates: uniqueSortedCoordinates2(component.yCoordinates)
50862
+ })).toSorted(
50863
+ (first, second) => first.componentId.localeCompare(second.componentId)
50864
+ );
50865
+ }
50866
+ function getInterstitialCoordinates2(params) {
50867
+ const { coordinates, pitch } = params;
50868
+ if (coordinates.length === 0 || !Number.isFinite(pitch) || pitch <= EPSILON25) {
50869
+ return [];
50870
+ }
50871
+ const interstitialCoordinates = [coordinates[0] - pitch / 2];
50872
+ for (let index = 1; index < coordinates.length; index++) {
50873
+ interstitialCoordinates.push(
50874
+ (coordinates[index - 1] + coordinates[index]) / 2
50875
+ );
50876
+ }
50877
+ interstitialCoordinates.push(coordinates.at(-1) + pitch / 2);
50878
+ return uniqueSortedCoordinates2(interstitialCoordinates);
50879
+ }
50880
+ function getAdjacentInterstitialCoordinates2(params) {
50881
+ const { sourceCoordinate, coordinates, pitch } = params;
50882
+ const interstitialCoordinates = getInterstitialCoordinates2({
50883
+ coordinates,
50884
+ pitch
50885
+ });
50886
+ const before = interstitialCoordinates.filter((coordinate) => coordinate < sourceCoordinate - EPSILON25).at(-1);
50887
+ const after = interstitialCoordinates.find(
50888
+ (coordinate) => coordinate > sourceCoordinate + EPSILON25
50889
+ );
50890
+ return [before, after].filter(
50891
+ (coordinate) => coordinate !== void 0
50892
+ );
50893
+ }
50894
+ function directSegmentIsStraightOr452(start, end) {
50895
+ const absoluteX = Math.abs(end.x - start.x);
50896
+ const absoluteY = Math.abs(end.y - start.y);
50897
+ return absoluteX <= EPSILON25 || absoluteY <= EPSILON25 || Math.abs(absoluteX - absoluteY) <= EPSILON25;
50898
+ }
50899
+ function getOutwardRank2(params) {
50900
+ const { source, site, direction } = params;
50901
+ const outwardDisplacement = direction === "right" ? site.x - source.x : direction === "left" ? source.x - site.x : direction === "up" ? site.y - source.y : source.y - site.y;
50902
+ return outwardDisplacement > EPSILON25 ? 0 : Math.abs(outwardDisplacement) <= EPSILON25 ? 1 : 2;
50903
+ }
50904
+ function viaSiteClearsObstacles2(params) {
50905
+ const { point: point6, obstacles, viaDiameter, clearance } = params;
50906
+ const requiredClearance = viaDiameter / 2 + clearance;
50907
+ return obstacles.every(
50908
+ (obstacle) => distancePointToObstacle2(point6, obstacle) >= requiredClearance - EPSILON25
50909
+ );
50910
+ }
50911
+ function sourceSegmentClearsOtherObstacles2(params) {
50912
+ const { segment: segment2, sourceObstacle, obstacles, clearance } = params;
50913
+ const requiredClearance = segment2.width / 2 + clearance;
50914
+ return obstacles.every(
50915
+ (obstacle) => obstacle === sourceObstacle || distanceSegmentToObstacle2(segment2, obstacle) >= requiredClearance - EPSILON25
50916
+ );
50917
+ }
50918
+ function getConnectionCandidates2(params) {
50919
+ const { connection, component, rules } = params;
50920
+ const { preparedConnection, direction } = connection;
50921
+ const obstacles = rules.additionalObstacles ? [.../* @__PURE__ */ new Set([...component.obstacles, ...rules.additionalObstacles])] : component.obstacles;
50922
+ const source = {
50923
+ x: preparedConnection.sourcePoint.x,
50924
+ y: preparedConnection.sourcePoint.y
50925
+ };
50926
+ const adjacentX = getAdjacentInterstitialCoordinates2({
50927
+ sourceCoordinate: source.x,
50928
+ coordinates: component.xCoordinates,
50929
+ pitch: component.pitchX
50930
+ });
50931
+ const adjacentY = getAdjacentInterstitialCoordinates2({
50932
+ sourceCoordinate: source.y,
50933
+ coordinates: component.yCoordinates,
50934
+ pitch: component.pitchY
50935
+ });
50936
+ const rawPoints = [
50937
+ ...adjacentX.map((x) => ({ x, y: source.y })),
50938
+ ...adjacentY.map((y) => ({ x: source.x, y })),
50939
+ ...adjacentX.flatMap((x) => adjacentY.map((y) => ({ x, y })))
50940
+ ];
50941
+ const uniquePoints2 = [];
50942
+ for (const point6 of rawPoints) {
50943
+ if (!uniquePoints2.some((candidate) => distance12(candidate, point6) <= EPSILON25)) {
50944
+ uniquePoints2.push(point6);
50945
+ }
50946
+ }
50947
+ const fixedViaPoint = rules.fixedViaPointsByConnectionIndex?.get(
50948
+ preparedConnection.connectionIndex
50949
+ );
50950
+ const candidatePoints = fixedViaPoint ? [fixedViaPoint] : uniquePoints2;
50951
+ const candidates = [];
50952
+ for (const point6 of candidatePoints) {
50953
+ if (connection.terminationType === "plane" && !directSegmentIsStraightOr452(source, point6)) {
50954
+ continue;
50955
+ }
50956
+ if (!viaSiteClearsObstacles2({
50957
+ point: point6,
50958
+ obstacles,
50959
+ viaDiameter: rules.viaDiameter,
50960
+ clearance: rules.clearance
50961
+ })) {
50962
+ continue;
50963
+ }
50964
+ const sourceSegment = {
50965
+ start: source,
50966
+ end: point6,
50967
+ width: rules.traceWidth,
50968
+ layer: preparedConnection.sourceLayer
50969
+ };
50970
+ if (!sourceSegmentClearsOtherObstacles2({
50971
+ segment: sourceSegment,
50972
+ sourceObstacle: preparedConnection.sourceObstacle,
50973
+ obstacles,
50974
+ clearance: rules.clearance
50975
+ })) {
50976
+ continue;
50977
+ }
50978
+ const candidateClearsRoutedCopper = (rules.blockingSegments ?? []).every(
50979
+ (blocker) => {
50980
+ if (blocker.connectionIndex === preparedConnection.connectionIndex) {
50981
+ return true;
50982
+ }
50983
+ if (rules.canShareCopper?.(
50984
+ preparedConnection.connectionIndex,
50985
+ blocker.connectionIndex
50986
+ )) {
50987
+ return true;
50988
+ }
50989
+ const viaToTraceClearance = rules.viaDiameter / 2 + blocker.segment.width / 2 + rules.clearance;
50990
+ if (distancePointToSegment2(
50991
+ point6,
50992
+ blocker.segment.start,
50993
+ blocker.segment.end
50994
+ ) < viaToTraceClearance - EPSILON25) {
50995
+ return false;
50996
+ }
50997
+ return blocker.segment.layer !== sourceSegment.layer || segmentsAreClear2(sourceSegment, blocker.segment, rules.clearance);
50998
+ }
50999
+ );
51000
+ if (!candidateClearsRoutedCopper) continue;
51001
+ const candidateClearsRoutedVias = (rules.blockingVias ?? []).every(
51002
+ (blocker) => {
51003
+ if (blocker.connectionIndex === preparedConnection.connectionIndex) {
51004
+ return true;
51005
+ }
51006
+ if (distance12(point6, blocker.center) < (rules.viaDiameter + blocker.diameter) / 2 + rules.clearance - EPSILON25) {
51007
+ return false;
51008
+ }
51009
+ if (blocker.spanLayers.includes(sourceSegment.layer) && distancePointToSegment2(
51010
+ blocker.center,
51011
+ sourceSegment.start,
51012
+ sourceSegment.end
51013
+ ) < blocker.diameter / 2 + sourceSegment.width / 2 + rules.clearance - EPSILON25) {
51014
+ return false;
51015
+ }
51016
+ return true;
51017
+ }
51018
+ );
51019
+ if (!candidateClearsRoutedVias) continue;
51020
+ candidates.push({
51021
+ connectionIndex: preparedConnection.connectionIndex,
51022
+ point: point6,
51023
+ sourceSegment,
51024
+ outwardRank: getOutwardRank2({ source, site: point6, direction })
51025
+ });
51026
+ }
51027
+ const perpendicularCoordinates = direction === "left" || direction === "right" ? component.yCoordinates : component.xCoordinates;
51028
+ const sourcePerpendicularCoordinate = direction === "left" || direction === "right" ? source.y : source.x;
51029
+ const perpendicularGridIndex = perpendicularCoordinates.findIndex(
51030
+ (coordinate) => Math.abs(coordinate - sourcePerpendicularCoordinate) <= EPSILON25
51031
+ );
51032
+ const planeCheckerboardSide = perpendicularGridIndex >= 0 ? perpendicularGridIndex % 2 === 0 ? 1 : -1 : void 0;
51033
+ const preferredPerpendicularSide = connection.terminationType === "boundary" ? rules.preferredBoundaryPerpendicularSideByBusId?.get(connection.busId) : rules.preferPlaneCheckerboardSites && connection.terminationType === "plane" && (direction === "left" || direction === "right") ? planeCheckerboardSide : void 0;
51034
+ const preferOutward = connection.terminationType === "boundary" ? rules.preferBoundaryOutwardByBusId?.get(connection.busId) ?? true : true;
51035
+ const preferredViaPoint = rules.preferredViaPointsByConnectionIndex?.get(
51036
+ preparedConnection.connectionIndex
51037
+ );
51038
+ const getPerpendicularPreferenceRank = (candidate) => {
51039
+ if (preferredPerpendicularSide === void 0) return 0;
51040
+ const displacement = direction === "left" || direction === "right" ? candidate.point.y - source.y : candidate.point.x - source.x;
51041
+ return displacement * preferredPerpendicularSide > EPSILON25 ? 0 : Math.abs(displacement) <= EPSILON25 ? 1 : 2;
51042
+ };
51043
+ return candidates.toSorted(
51044
+ (first, second) => (preferredViaPoint ? Number(distance12(first.point, preferredViaPoint) > EPSILON25) - Number(distance12(second.point, preferredViaPoint) > EPSILON25) : 0) || (preferOutward ? first.outwardRank - second.outwardRank : second.outwardRank - first.outwardRank) || getPerpendicularPreferenceRank(first) - getPerpendicularPreferenceRank(second) || distance12(source, first.point) - distance12(source, second.point) || first.point.x - second.point.x || first.point.y - second.point.y
51045
+ );
51046
+ }
51047
+ function candidatesAreMutuallyClear2(params) {
51048
+ const { first, second, rules } = params;
51049
+ const canShareCopper = rules.canShareCopper?.(first.connectionIndex, second.connectionIndex) ?? false;
51050
+ const requiredHoleSeparation = rules.viaHoleDiameter ? rules.viaHoleDiameter + (rules.holeToHoleClearance ?? rules.clearance) : 0;
51051
+ const requiredViaSeparation = canShareCopper ? requiredHoleSeparation : Math.max(rules.viaDiameter + rules.clearance, requiredHoleSeparation);
51052
+ if (distance12(first.point, second.point) < requiredViaSeparation - EPSILON25) {
51053
+ return false;
51054
+ }
51055
+ const requiredViaToTraceClearance = rules.viaDiameter / 2 + rules.traceWidth / 2 + rules.clearance;
51056
+ if (!canShareCopper) {
51057
+ if (distancePointToSegment2(
51058
+ first.point,
51059
+ second.sourceSegment.start,
51060
+ second.sourceSegment.end
51061
+ ) < requiredViaToTraceClearance - EPSILON25 || distancePointToSegment2(
51062
+ second.point,
51063
+ first.sourceSegment.start,
51064
+ first.sourceSegment.end
51065
+ ) < requiredViaToTraceClearance - EPSILON25) {
51066
+ return false;
51067
+ }
51068
+ }
51069
+ if (canShareCopper) return true;
51070
+ return segmentsAreClear2(
51071
+ first.sourceSegment,
51072
+ second.sourceSegment,
51073
+ rules.clearance
51074
+ );
51075
+ }
51076
+ function matchComponent2(params) {
51077
+ const { component, rules, consumeSearchState } = params;
51078
+ const entries = component.connections.map(
51079
+ (connection) => ({
51080
+ connection,
51081
+ candidates: getConnectionCandidates2({ connection, component, rules })
51082
+ })
51083
+ );
51084
+ if (entries.some((entry) => entry.candidates.length === 0)) return null;
51085
+ const compatibilityCache = /* @__PURE__ */ new Map();
51086
+ const candidatesAreCompatible = (first, second) => {
51087
+ const cached = compatibilityCache.get(first)?.get(second);
51088
+ if (cached !== void 0) return cached;
51089
+ const compatible = candidatesAreMutuallyClear2({ first, second, rules });
51090
+ const firstCache = compatibilityCache.get(first) ?? /* @__PURE__ */ new Map();
51091
+ firstCache.set(second, compatible);
51092
+ compatibilityCache.set(first, firstCache);
51093
+ const secondCache = compatibilityCache.get(second) ?? /* @__PURE__ */ new Map();
51094
+ secondCache.set(first, compatible);
51095
+ compatibilityCache.set(second, secondCache);
51096
+ return compatible;
51097
+ };
51098
+ const forcedCandidates = entries.flatMap(
51099
+ (entry) => entry.candidates.length === 1 ? [entry.candidates[0]] : []
51100
+ );
51101
+ for (let candidateIndex = 0; candidateIndex < forcedCandidates.length; candidateIndex++) {
51102
+ const candidate = forcedCandidates[candidateIndex];
51103
+ for (let previousIndex = 0; previousIndex < candidateIndex; previousIndex++) {
51104
+ if (!candidatesAreCompatible(candidate, forcedCandidates[previousIndex])) {
51105
+ return null;
51106
+ }
51107
+ }
51108
+ }
51109
+ const assignedCandidates = new Map(
51110
+ forcedCandidates.map((candidate) => [candidate.connectionIndex, candidate])
51111
+ );
51112
+ const remaining = new Set(
51113
+ entries.flatMap(
51114
+ (entry) => entry.candidates.length > 1 ? [entry.connection.preparedConnection.connectionIndex] : []
51115
+ )
51116
+ );
51117
+ const entryByConnectionIndex = new Map(
51118
+ entries.map((entry) => [
51119
+ entry.connection.preparedConnection.connectionIndex,
51120
+ entry
51121
+ ])
51122
+ );
51123
+ if (remaining.size === 0) {
51124
+ return new Map(
51125
+ [...assignedCandidates.entries()].map(([connectionIndex, candidate]) => [
51126
+ connectionIndex,
51127
+ { ...candidate.point }
51128
+ ])
51129
+ );
51130
+ }
51131
+ const getViableCandidates = (entry) => entry.candidates.filter(
51132
+ (candidate) => [...assignedCandidates.values()].every(
51133
+ (assignedCandidate) => candidatesAreCompatible(candidate, assignedCandidate)
51134
+ )
51135
+ );
51136
+ const augmentMatching = () => {
51137
+ if (!consumeSearchState()) return false;
51138
+ if (remaining.size === 0) return true;
51139
+ let selectedEntry;
51140
+ let selectedCandidates = [];
51141
+ for (const connectionIndex2 of [...remaining].toSorted(
51142
+ (first, second) => first - second
51143
+ )) {
51144
+ const entry = entryByConnectionIndex.get(connectionIndex2);
51145
+ const viableCandidates = getViableCandidates(entry);
51146
+ if (viableCandidates.length === 0) return false;
51147
+ if (!selectedEntry || viableCandidates.length < selectedCandidates.length || viableCandidates.length === selectedCandidates.length && connectionIndex2 < selectedEntry.connection.preparedConnection.connectionIndex) {
51148
+ selectedEntry = entry;
51149
+ selectedCandidates = viableCandidates;
51150
+ }
51151
+ }
51152
+ const connectionIndex = selectedEntry.connection.preparedConnection.connectionIndex;
51153
+ remaining.delete(connectionIndex);
51154
+ for (const candidate of selectedCandidates) {
51155
+ assignedCandidates.set(connectionIndex, candidate);
51156
+ if (augmentMatching()) return true;
51157
+ assignedCandidates.delete(connectionIndex);
51158
+ }
51159
+ remaining.add(connectionIndex);
51160
+ return false;
51161
+ };
51162
+ if (!augmentMatching()) return null;
51163
+ return new Map(
51164
+ [...assignedCandidates.entries()].toSorted(([first], [second]) => first - second).map(([connectionIndex, candidate]) => [
51165
+ connectionIndex,
51166
+ { ...candidate.point }
51167
+ ])
51168
+ );
51169
+ }
51170
+ function matchComponentDogboneViaSites2(preparedBuses, rules) {
51171
+ const maximumSearchStates = assertGeometryRules2(rules);
51172
+ if (preparedBuses.length === 0) return /* @__PURE__ */ new Map();
51173
+ let consumedSearchStates = 0;
51174
+ const consumeSearchState = () => {
51175
+ consumedSearchStates++;
51176
+ return consumedSearchStates <= maximumSearchStates;
51177
+ };
51178
+ const result = /* @__PURE__ */ new Map();
51179
+ for (const component of getComponentMatchingInputs2(preparedBuses)) {
51180
+ const componentResult = matchComponent2({
51181
+ component,
51182
+ rules,
51183
+ consumeSearchState
51184
+ });
51185
+ if (!componentResult) return null;
51186
+ for (const [connectionIndex, point6] of componentResult) {
51187
+ result.set(connectionIndex, point6);
51188
+ }
51189
+ }
51190
+ return result;
51191
+ }
51192
+
51193
+ // node_modules/@tscircuit/dogbone-solver/node_modules/@tscircuit/fanout-solver/lib/route-reserved-via-buses.ts
51194
+ import {
51195
+ PortfolioSingleIntraNodeSolver as PortfolioSingleIntraNodeSolver2
51196
+ } from "@tscircuit/capacity-autorouter";
50573
51197
  import { BaseSolver as BaseSolver4 } from "@tscircuit/solver-utils";
50574
51198
 
51199
+ // node_modules/@tscircuit/dogbone-solver/node_modules/@tscircuit/fanout-solver/lib/subsolver-request.ts
51200
+ import { BaseSolver as BaseSolver5 } from "@tscircuit/solver-utils";
51201
+
51202
+ // node_modules/@tscircuit/dogbone-solver/node_modules/@tscircuit/fanout-solver/lib/fanout-solver.ts
51203
+ import { BaseSolver as BaseSolver6 } from "@tscircuit/solver-utils";
51204
+ import { mergeGraphics as mergeGraphics2 } from "graphics-debug";
51205
+
51206
+ // node_modules/@tscircuit/dogbone-solver/node_modules/@tscircuit/fanout-solver/lib/match-angularly-ordered-local-vias.ts
51207
+ var TAU2 = Math.PI * 2;
51208
+
51209
+ // node_modules/@tscircuit/dogbone-solver/node_modules/@tscircuit/fanout-solver/lib/route-single-layer-adaptive-exits.ts
51210
+ var FANOUT_FLOW_DEBUG_ENABLED2 = globalThis.process?.env?.FANOUT_FLOW_DEBUG === "1";
51211
+
51212
+ // node_modules/@tscircuit/dogbone-solver/node_modules/@tscircuit/fanout-solver/lib/runtime-process.ts
51213
+ var getRuntimeProcess2 = (runtime) => runtime.process ?? { env: {} };
51214
+
51215
+ // node_modules/@tscircuit/dogbone-solver/node_modules/@tscircuit/fanout-solver/lib/fanout-solver.ts
51216
+ var process2 = getRuntimeProcess2(globalThis);
51217
+
51218
+ // node_modules/@tscircuit/dogbone-solver/lib/dogbone-fanout-solver.ts
51219
+ import { layer_ref as layer_ref3 } from "circuit-json";
51220
+
51221
+ // node_modules/@tscircuit/dogbone-solver/lib/get-via-board-layers.ts
51222
+ function getViaBoardLayers2(layerCount) {
51223
+ if (layerCount <= 1) return ["top"];
51224
+ return [
51225
+ "top",
51226
+ ...Array.from(
51227
+ { length: layerCount - 2 },
51228
+ (_, index) => `inner${index + 1}`
51229
+ ),
51230
+ "bottom"
51231
+ ];
51232
+ }
51233
+
51234
+ // node_modules/@tscircuit/dogbone-solver/lib/dogbone-fanout-solver.ts
51235
+ var DogboneFanoutSolver = class extends BaseSolver7 {
51236
+ constructor(params) {
51237
+ super();
51238
+ this.params = params;
51239
+ this.MAX_ITERATIONS = params.input.connections.length * 2 + 10;
51240
+ this.steps = this.solveSteps();
51241
+ }
51242
+ params;
51243
+ phase = "preparing";
51244
+ sources = [];
51245
+ sites = /* @__PURE__ */ new Map();
51246
+ traces = [];
51247
+ steps;
51248
+ getConstructorParams() {
51249
+ return [this.params];
51250
+ }
51251
+ _step() {
51252
+ try {
51253
+ if (this.steps.next().done) {
51254
+ this.phase = "complete";
51255
+ this.progress = 1;
51256
+ this.solved = true;
51257
+ }
51258
+ } catch (error) {
51259
+ this.error = error instanceof Error ? error.message : String(error);
51260
+ this.failed = true;
51261
+ }
51262
+ }
51263
+ getOutput() {
51264
+ if (!this.solved)
51265
+ throw new Error(this.error ?? "Dogbone fanout is not solved");
51266
+ return this.traces;
51267
+ }
51268
+ visualize() {
51269
+ return {
51270
+ rects: this.params.input.obstacles.filter((pad) => pad.shape !== "circle").map((pad) => ({
51271
+ center: pad.center,
51272
+ width: pad.width,
51273
+ height: pad.height,
51274
+ fill: "rgba(200,40,40,0.3)"
51275
+ })),
51276
+ circles: [
51277
+ ...this.params.input.obstacles.filter((pad) => pad.shape === "circle").map((pad) => ({
51278
+ center: pad.center,
51279
+ radius: pad.width / 2,
51280
+ fill: "rgba(200,40,40,0.3)"
51281
+ })),
51282
+ ...[...this.sites.values()].map((center) => ({
51283
+ center,
51284
+ radius: (this.params.input.minViaPadDiameter ?? this.params.input.minViaDiameter ?? 0.6) / 2,
51285
+ fill: "orange"
51286
+ }))
51287
+ ],
51288
+ lines: [...this.sites].map(([index, via]) => ({
51289
+ points: [this.sources[index].port, via],
51290
+ strokeColor: "orange",
51291
+ strokeWidth: this.params.input.minTraceWidth
51292
+ }))
51293
+ };
51294
+ }
51295
+ *solveSteps() {
51296
+ const { input, fanoutRoutingLayers } = this.params;
51297
+ const traceWidth = input.minTraceWidth;
51298
+ const clearance = input.minTraceToPadEdgeClearance ?? 0.1;
51299
+ const viaDiameter = input.minViaPadDiameter ?? input.minViaDiameter ?? 0.6;
51300
+ const holeDiameter = input.minViaHoleDiameter ?? 0.3;
51301
+ const viaClearance = input.minViaEdgeToPadEdgeClearance ?? clearance;
51302
+ const boardEdgeClearance = input.minBoardEdgeClearance ?? clearance;
51303
+ const holeClearance = input.minViaHoleEdgeToViaHoleEdgeClearance ?? clearance;
51304
+ this.sources = input.connections.map((connection) => {
51305
+ const point6 = connection.pointsToConnect.find(
51306
+ (point7) => point7.pcb_port_id && input.obstacles.some(
51307
+ (pad2) => pad2.componentId && Math.hypot(pad2.center.x - point7.x, pad2.center.y - point7.y) < 1e-6
51308
+ )
51309
+ );
51310
+ if (!point6?.pcb_port_id)
51311
+ throw new Error(
51312
+ `Dogbone connection ${connection.name} has no source pad`
51313
+ );
51314
+ const pad = input.obstacles.find(
51315
+ (pad2) => pad2.componentId && Math.hypot(pad2.center.x - point6.x, pad2.center.y - point6.y) < 1e-6
51316
+ );
51317
+ return {
51318
+ port: {
51319
+ pcb_port_id: point6.pcb_port_id,
51320
+ pcb_component_id: pad.componentId,
51321
+ x: point6.x,
51322
+ y: point6.y,
51323
+ layers: [layer_ref3.parse(point6.layer)]
51324
+ },
51325
+ sourceTraceId: connection.source_trace_id
51326
+ };
51327
+ });
51328
+ const sources = this.sources;
51329
+ const layers = getViaBoardLayers2(input.layerCount);
51330
+ const allowedLayers = fanoutRoutingLayers ?? layers;
51331
+ if (allowedLayers.some(
51332
+ (layer) => !layers.some((boardLayer) => boardLayer === layer)
51333
+ ))
51334
+ throw new Error("Dogbone fanout targets an unavailable board layer");
51335
+ const buses = [];
51336
+ const targets = sources.map(({ port, sourceTraceId }, connectionIndex) => {
51337
+ const sourceObstacle = input.obstacles.find(
51338
+ (obstacle) => obstacle.componentId === port.pcb_component_id && Math.hypot(obstacle.center.x - port.x, obstacle.center.y - port.y) < 1e-6
51339
+ );
51340
+ if (!sourceObstacle)
51341
+ throw new Error(
51342
+ "Dogbone fanout requires a component pad at each source port"
51343
+ );
51344
+ if (port.layers.length !== 1 || !layers.includes(port.layers[0])) {
51345
+ throw new Error("Dogbone fanout requires a single-layer SMT source pad");
51346
+ }
51347
+ const sourceLayer = port.layers[0];
51348
+ const targetLayer2 = allowedLayers.find((layer) => layer !== sourceLayer);
51349
+ if (!targetLayer2)
51350
+ throw new Error(
51351
+ "Dogbone fanout requires a legal layer distinct from the source pad"
51352
+ );
51353
+ let bus = buses.find(
51354
+ (bus2) => bus2.componentId === sourceObstacle.componentId
51355
+ );
51356
+ if (!bus) {
51357
+ const pads = input.obstacles.filter(
51358
+ (obstacle) => obstacle.componentId === sourceObstacle.componentId
51359
+ );
51360
+ const xs = [
51361
+ ...new Set(pads.map((pad) => Math.round(pad.center.x * 1e6) / 1e6))
51362
+ ].sort((a, b) => a - b);
51363
+ const ys = [
51364
+ ...new Set(pads.map((pad) => Math.round(pad.center.y * 1e6) / 1e6))
51365
+ ].sort((a, b) => a - b);
51366
+ if (xs.length < 2 || ys.length < 2)
51367
+ throw new Error("Dogbone fanout requires a two-dimensional pad grid");
51368
+ const pitch = (coordinates) => Math.min(...coordinates.slice(1).map((v, i) => v - coordinates[i]));
51369
+ bus = {
51370
+ busId: sourceObstacle.componentId,
51371
+ componentId: sourceObstacle.componentId,
51372
+ componentObstacles: pads,
51373
+ connections: [],
51374
+ direction: "down",
51375
+ termination: { type: "boundary" },
51376
+ componentBounds: {
51377
+ minX: xs[0],
51378
+ maxX: xs.at(-1),
51379
+ minY: ys[0],
51380
+ maxY: ys.at(-1)
51381
+ },
51382
+ sharedBoundary: input.bounds,
51383
+ xCoordinates: xs,
51384
+ yCoordinates: ys,
51385
+ pitchX: pitch(xs),
51386
+ pitchY: pitch(ys)
51387
+ };
51388
+ buses.push(bus);
51389
+ }
51390
+ const sourcePoint = {
51391
+ x: port.x,
51392
+ y: port.y,
51393
+ layer: sourceLayer,
51394
+ pcb_port_id: port.pcb_port_id
51395
+ };
51396
+ bus.connections.push({
51397
+ connectionIndex,
51398
+ connection: { name: port.pcb_port_id, pointsToConnect: [sourcePoint] },
51399
+ sourcePoint,
51400
+ sourcePointIndex: 0,
51401
+ sourceLayer,
51402
+ sourceObstacle,
51403
+ targetPoint: sourcePoint
51404
+ });
51405
+ return { sourceTraceId, port, sourceLayer, targetLayer: targetLayer2 };
51406
+ });
51407
+ const sites = this.sites;
51408
+ const blockingSegments = [];
51409
+ const blockingVias = [];
51410
+ for (const bus of buses) {
51411
+ this.phase = "assigning_sites";
51412
+ yield;
51413
+ const assignment = matchComponentDogboneViaSites2([bus], {
51414
+ traceWidth,
51415
+ clearance: Math.max(clearance, viaClearance),
51416
+ viaDiameter,
51417
+ viaHoleDiameter: holeDiameter,
51418
+ holeToHoleClearance: holeClearance,
51419
+ additionalObstacles: input.obstacles,
51420
+ blockingSegments,
51421
+ blockingVias
51422
+ });
51423
+ if (!assignment || assignment.size !== bus.connections.length)
51424
+ throw new Error(
51425
+ "No complete local dogbone assignment within clearance/search limits"
51426
+ );
51427
+ for (const connection of bus.connections) {
51428
+ const connectionIndex = connection.connectionIndex;
51429
+ const via = assignment.get(connectionIndex);
51430
+ sites.set(connectionIndex, via);
51431
+ blockingSegments.push({
51432
+ connectionIndex,
51433
+ segment: {
51434
+ start: connection.sourcePoint,
51435
+ end: via,
51436
+ width: traceWidth,
51437
+ layer: connection.sourceLayer
51438
+ }
51439
+ });
51440
+ blockingVias.push({
51441
+ connectionIndex,
51442
+ center: via,
51443
+ diameter: viaDiameter,
51444
+ spanLayers: layers
51445
+ });
51446
+ }
51447
+ }
51448
+ this.phase = "creating_traces";
51449
+ for (const [
51450
+ index,
51451
+ { sourceTraceId, port, sourceLayer, targetLayer: targetLayer2 }
51452
+ ] of targets.entries()) {
51453
+ const via = sites.get(index);
51454
+ const dx = Math.abs(via.x - port.x), dy = Math.abs(via.y - port.y);
51455
+ if (dx > 1e-6 && dy > 1e-6 && Math.abs(dx - dy) > 1e-6)
51456
+ throw new Error(
51457
+ "Dogbone fanout requires straight or 45-degree local escapes"
51458
+ );
51459
+ const margin = viaDiameter / 2 + boardEdgeClearance;
51460
+ if (via.x < input.bounds.minX + margin || via.x > input.bounds.maxX - margin || via.y < input.bounds.minY + margin || via.y > input.bounds.maxY - margin)
51461
+ throw new Error("Dogbone via violates board-edge clearance");
51462
+ this.traces.push({
51463
+ type: "pcb_trace",
51464
+ pcb_trace_id: `dogbone_${port.pcb_port_id}`,
51465
+ source_trace_id: sourceTraceId,
51466
+ connectsTo: [port.pcb_port_id],
51467
+ route: [
51468
+ {
51469
+ route_type: "wire",
51470
+ x: port.x,
51471
+ y: port.y,
51472
+ layer: sourceLayer,
51473
+ width: traceWidth
51474
+ },
51475
+ { route_type: "wire", ...via, layer: sourceLayer, width: traceWidth },
51476
+ {
51477
+ route_type: "via",
51478
+ ...via,
51479
+ from_layer: sourceLayer,
51480
+ to_layer: targetLayer2,
51481
+ via_diameter: viaDiameter,
51482
+ via_hole_diameter: holeDiameter,
51483
+ layers
51484
+ },
51485
+ { route_type: "wire", ...via, layer: targetLayer2, width: traceWidth },
51486
+ { route_type: "wire", ...via, layer: targetLayer2, width: traceWidth }
51487
+ ]
51488
+ });
51489
+ this.progress = (index + 1) / targets.length;
51490
+ yield;
51491
+ }
51492
+ }
51493
+ };
51494
+
51495
+ // node_modules/@tscircuit/dogbone-solver/lib/dogbone-autorouter.ts
51496
+ var DogboneAutorouter = class {
51497
+ constructor(params, callbacks = {}) {
51498
+ this.params = params;
51499
+ this.callbacks = callbacks;
51500
+ this.solver = new DogboneFanoutSolver(params);
51501
+ }
51502
+ params;
51503
+ callbacks;
51504
+ get input() {
51505
+ return this.params.input;
51506
+ }
51507
+ isRouting = false;
51508
+ solver;
51509
+ timer;
51510
+ listeners = [];
51511
+ finishSolver() {
51512
+ this.callbacks.onSolverEnded?.({
51513
+ solverName: "DogboneFanoutSolver",
51514
+ solved: this.solver.solved,
51515
+ failed: this.solver.failed,
51516
+ iterations: this.solver.iterations,
51517
+ error: this.solver.error
51518
+ });
51519
+ }
51520
+ getTraces() {
51521
+ return this.solver.getOutput().map((trace) => {
51522
+ const connection = this.params.input.connections.find(
51523
+ (connection2) => connection2.pointsToConnect.some(
51524
+ (point6) => point6.pcb_port_id && trace.connectsTo?.includes(point6.pcb_port_id)
51525
+ )
51526
+ );
51527
+ if (!connection)
51528
+ throw new Error("Dogbone fanout lost its source connection");
51529
+ return {
51530
+ ...trace,
51531
+ connection_name: connection.name,
51532
+ source_trace_id: connection.source_trace_id,
51533
+ connectsTo: [
51534
+ .../* @__PURE__ */ new Set([
51535
+ ...trace.connectsTo ?? [],
51536
+ ...connection.routingPcbGroupId ? connection.pointsToConnect.flatMap(
51537
+ (point6) => !point6.pcb_port_id && point6.pointId ? [point6.pointId] : []
51538
+ ) : [`${trace.pcb_trace_id}_exit`]
51539
+ ])
51540
+ ]
51541
+ };
51542
+ });
51543
+ }
51544
+ getOutputSimpleRouteJson() {
51545
+ const traces = this.getTraces();
51546
+ return {
51547
+ ...this.params.input,
51548
+ traces,
51549
+ connections: this.params.input.connections.map((connection) => {
51550
+ const trace = traces.find(
51551
+ (trace2) => trace2.connection_name === connection.name
51552
+ );
51553
+ if (!trace) return connection;
51554
+ const exit = trace.route.at(-1);
51555
+ if (exit.route_type !== "wire")
51556
+ throw new Error("Dogbone fanout lost its exit");
51557
+ return {
51558
+ ...connection,
51559
+ pointsToConnect: connection.pointsToConnect.map((point6) => {
51560
+ const isHandoff = !point6.pcb_port_id;
51561
+ const relocate = connection.routingPcbGroupId ? isHandoff : Boolean(
51562
+ point6.pcb_port_id && trace.connectsTo?.includes(point6.pcb_port_id)
51563
+ );
51564
+ if (!relocate) return point6;
51565
+ if (connection.routingPcbGroupId)
51566
+ return { ...point6, x: exit.x, y: exit.y, layer: exit.layer };
51567
+ const { pcb_port_id: _port, ...handoff } = point6;
51568
+ return {
51569
+ ...handoff,
51570
+ pointId: `${trace.pcb_trace_id}_exit`,
51571
+ x: exit.x,
51572
+ y: exit.y,
51573
+ layer: exit.layer
51574
+ };
51575
+ })
51576
+ };
51577
+ })
51578
+ };
51579
+ }
51580
+ on(event, callback) {
51581
+ this.listeners.push({
51582
+ event,
51583
+ callback
51584
+ });
51585
+ }
51586
+ emit(event) {
51587
+ for (const listener of this.listeners)
51588
+ if (listener.event === event.type) listener.callback(event);
51589
+ }
51590
+ start() {
51591
+ if (this.isRouting) return;
51592
+ this.isRouting = true;
51593
+ this.callbacks.onSolverStarted?.({
51594
+ solverName: "DogboneFanoutSolver",
51595
+ solverParams: this.params,
51596
+ solverConstructorArgs: this.solver.getConstructorParams()
51597
+ });
51598
+ const tick = () => {
51599
+ if (!this.isRouting) return;
51600
+ try {
51601
+ for (let i = 0; i < 16 && !this.solver.solved && !this.solver.failed; i++)
51602
+ this.solver.step();
51603
+ this.emit({
51604
+ type: "progress",
51605
+ steps: this.solver.iterations,
51606
+ progress: this.solver.progress,
51607
+ phase: this.solver.phase,
51608
+ debugGraphics: this.solver.visualize()
51609
+ });
51610
+ if (!this.isRouting) return;
51611
+ if (this.solver.failed) {
51612
+ this.finishSolver();
51613
+ this.isRouting = false;
51614
+ this.emit({
51615
+ type: "error",
51616
+ error: new Error(this.solver.error ?? "Dogbone routing failed")
51617
+ });
51618
+ return;
51619
+ }
51620
+ if (this.solver.solved) {
51621
+ this.finishSolver();
51622
+ this.isRouting = false;
51623
+ this.emit({ type: "complete", traces: this.getTraces() });
51624
+ return;
51625
+ }
51626
+ this.timer = setTimeout(tick, 0);
51627
+ } catch (error) {
51628
+ this.isRouting = false;
51629
+ this.emit({
51630
+ type: "error",
51631
+ error: error instanceof Error ? error : new Error(String(error))
51632
+ });
51633
+ }
51634
+ };
51635
+ this.timer = setTimeout(tick, 0);
51636
+ }
51637
+ stop() {
51638
+ this.isRouting = false;
51639
+ if (this.timer) clearTimeout(this.timer);
51640
+ }
51641
+ solveSync() {
51642
+ this.solver.solve();
51643
+ if (this.solver.failed)
51644
+ throw new Error(this.solver.error ?? "Dogbone routing failed");
51645
+ return this.getTraces();
51646
+ }
51647
+ };
51648
+
51649
+ // node_modules/@tscircuit/bus-lanes-solver/lib/bus-lanes-solver.ts
51650
+ import { BaseSolver as BaseSolver8 } from "@tscircuit/solver-utils";
51651
+
50575
51652
  // node_modules/@tscircuit/bus-lanes-solver/lib/geometry.ts
50576
- var distance12 = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
51653
+ var distance14 = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
50577
51654
  function pointSegmentDistance(p, edge) {
50578
51655
  const [a, b] = edge, dx = b.x - a.x, dy = b.y - a.y, t = Math.max(
50579
51656
  0,
@@ -50584,10 +51661,10 @@ function pointSegmentDistance(p, edge) {
50584
51661
  );
50585
51662
  return Math.hypot(p.x - a.x - t * dx, p.y - a.y - t * dy);
50586
51663
  }
50587
- var cross2 = (a, b, c) => (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
51664
+ var cross3 = (a, b, c) => (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
50588
51665
  function segmentDistance(ab, cd) {
50589
51666
  const [a, b] = ab, [c, d] = cd;
50590
- if (Math.max(a.x, b.x) >= Math.min(c.x, d.x) && Math.max(c.x, d.x) >= Math.min(a.x, b.x) && Math.max(a.y, b.y) >= Math.min(c.y, d.y) && Math.max(c.y, d.y) >= Math.min(a.y, b.y) && cross2(a, b, c) * cross2(a, b, d) <= 0 && cross2(c, d, a) * cross2(c, d, b) <= 0)
51667
+ if (Math.max(a.x, b.x) >= Math.min(c.x, d.x) && Math.max(c.x, d.x) >= Math.min(a.x, b.x) && Math.max(a.y, b.y) >= Math.min(c.y, d.y) && Math.max(c.y, d.y) >= Math.min(a.y, b.y) && cross3(a, b, c) * cross3(a, b, d) <= 0 && cross3(c, d, a) * cross3(c, d, b) <= 0)
50591
51668
  return 0;
50592
51669
  return Math.min(
50593
51670
  pointSegmentDistance(a, cd),
@@ -50596,10 +51673,10 @@ function segmentDistance(ab, cd) {
50596
51673
  pointSegmentDistance(d, ab)
50597
51674
  );
50598
51675
  }
50599
- var length = (path) => path.slice(1).reduce((sum, p, i) => sum + distance12(path[i], p), 0);
51676
+ var length = (path) => path.slice(1).reduce((sum, p, i) => sum + distance14(path[i], p), 0);
50600
51677
  function simplify(path) {
50601
51678
  return path.filter(
50602
- (p, i) => !i || i === path.length - 1 || Math.abs(cross2(path[i - 1], p, path[i + 1])) > 1e-9
51679
+ (p, i) => !i || i === path.length - 1 || Math.abs(cross3(path[i - 1], p, path[i + 1])) > 1e-9
50603
51680
  );
50604
51681
  }
50605
51682
 
@@ -50643,7 +51720,7 @@ function fixedCopper(input) {
50643
51720
  throw Error("Fixed trace changes layer without a via");
50644
51721
  result.push({ a: p, b: q, radius: p.width / 2, layer: p.layer, owners });
50645
51722
  }
50646
- if (q && p.route_type !== q.route_type && distance12(p, q) > 1e-10) {
51723
+ if (q && p.route_type !== q.route_type && distance14(p, q) > 1e-10) {
50647
51724
  const wire = p.route_type === "wire" ? p : q;
50648
51725
  result.push({
50649
51726
  a: p,
@@ -50763,7 +51840,7 @@ var VectorScene = class {
50763
51840
  );
50764
51841
  } else {
50765
51842
  const r = c.radius + this.margin + epsilon;
50766
- for (const p of distance12(c.a, c.b) < 1e-9 ? [c.a] : [c.a, c.b])
51843
+ for (const p of distance14(c.a, c.b) < 1e-9 ? [c.a] : [c.a, c.b])
50767
51844
  for (const [x, y] of [
50768
51845
  [1, k],
50769
51846
  [k, 1],
@@ -50851,7 +51928,7 @@ var VectorVisibilitySearch = class {
50851
51928
  this.start = start;
50852
51929
  this.end = end;
50853
51930
  this.vertices = [start, end];
50854
- this.open.push({ id: 0, g: 0, f: distance12(start, end) });
51931
+ this.open.push({ id: 0, g: 0, f: distance14(start, end) });
50855
51932
  this.best.set(0, 0);
50856
51933
  }
50857
51934
  scene;
@@ -50911,7 +51988,7 @@ var VectorVisibilitySearch = class {
50911
51988
  const from = this.vertices[cur.id];
50912
51989
  for (let id = 1; id < this.vertices.length; id++) {
50913
51990
  if (id === cur.id) continue;
50914
- const to = this.vertices[id], lower = cur.g + distance12(from, to);
51991
+ const to = this.vertices[id], lower = cur.g + distance14(from, to);
50915
51992
  if (lower >= (this.best.get(id) ?? Infinity) - 1e-9) continue;
50916
51993
  const candidates = connectors(from, to).sort(
50917
51994
  (a, b) => length(a) - length(b)
@@ -50924,7 +52001,7 @@ var VectorVisibilitySearch = class {
50924
52001
  this.open.push({
50925
52002
  id,
50926
52003
  g,
50927
- f: g + distance12(to, this.end),
52004
+ f: g + distance14(to, this.end),
50928
52005
  parent: cur,
50929
52006
  edge: path
50930
52007
  });
@@ -51166,9 +52243,9 @@ function tuneLengths(input, traces, targets) {
51166
52243
  const clearance = input.minTraceToPadEdgeClearance ?? input.defaultObstacleMargin ?? 0.075;
51167
52244
  const returnSpacing = Math.max(width + clearance, 3 * width);
51168
52245
  const pitch = 2 * (returnSpacing + 2 * width);
51169
- const segments = t.route.slice(1).map((p, i) => ({ i, span: distance12(t.route[i], p) })).sort((a, b) => b.span - a.span);
52246
+ const segments = t.route.slice(1).map((p, i) => ({ i, span: distance14(t.route[i], p) })).sort((a, b) => b.span - a.span);
51170
52247
  for (const { i } of segments) {
51171
- const a = t.route[i], b = t.route[i + 1], span = distance12(a, b);
52248
+ const a = t.route[i], b = t.route[i + 1], span = distance14(a, b);
51172
52249
  if (span < 0.01) continue;
51173
52250
  const ux = (b.x - a.x) / span, uy = (b.y - a.y) / span;
51174
52251
  const maximumTeeth = Math.floor(span * 0.9 / pitch);
@@ -51268,7 +52345,7 @@ function tuneLengths(input, traces, targets) {
51268
52345
  function tuningPathIsSelfClear(path, required) {
51269
52346
  const cumulative = [0];
51270
52347
  for (let i = 1; i < path.length; i++)
51271
- cumulative.push(cumulative[i - 1] + distance12(path[i - 1], path[i]));
52348
+ cumulative.push(cumulative[i - 1] + distance14(path[i - 1], path[i]));
51272
52349
  for (let i = 0; i < path.length - 1; i++)
51273
52350
  for (let j = i + 2; j < path.length - 1; j++) {
51274
52351
  if (cumulative[j] - cumulative[i + 1] <= required + 1e-8) continue;
@@ -51306,7 +52383,7 @@ var layerColors = {
51306
52383
  var layerColor = (layer) => layerColors[layer] ?? "#64748b";
51307
52384
 
51308
52385
  // node_modules/@tscircuit/bus-lanes-solver/lib/bus-lanes-solver.ts
51309
- var BusLanesSolver = class extends BaseSolver4 {
52386
+ var BusLanesSolver = class extends BaseSolver8 {
51310
52387
  input;
51311
52388
  options;
51312
52389
  phase = "validate";
@@ -51516,7 +52593,7 @@ var BusLanesSolver = class extends BaseSolver4 {
51516
52593
  const t = this.traces.find((t2) => t2.connection_name === c.name);
51517
52594
  if (!t) throw Error("Missing lane");
51518
52595
  const scene = this.scene(c);
51519
- if (distance12(t.route[0], c.pointsToConnect[0]) > 1e-8 || distance12(t.route.at(-1), c.pointsToConnect[1]) > 1e-8)
52596
+ if (distance14(t.route[0], c.pointsToConnect[0]) > 1e-8 || distance14(t.route.at(-1), c.pointsToConnect[1]) > 1e-8)
51520
52597
  throw Error("Broken lane endpoints");
51521
52598
  for (let i = 1; i < t.route.length; i++) {
51522
52599
  const a = t.route[i - 1], b = t.route[i], dx = Math.abs(a.x - b.x), dy = Math.abs(a.y - b.y);
@@ -51581,7 +52658,7 @@ var BusLanesSolver = class extends BaseSolver4 {
51581
52658
  });
51582
52659
  continue;
51583
52660
  }
51584
- if (distance12(c.a, c.b) < 1e-9)
52661
+ if (distance14(c.a, c.b) < 1e-9)
51585
52662
  circles.push({
51586
52663
  center: c.a,
51587
52664
  radius: c.radius,
@@ -51742,7 +52819,7 @@ import { CreateFdmEnclosureSolver } from "@tscircuit/create-fdm-enclosure";
51742
52819
  // node_modules/@tscircuit/implicit-copper-pour-solver/lib/implicit-copper-pour-pipeline-solver.ts
51743
52820
  import {
51744
52821
  BasePipelineSolver,
51745
- BaseSolver as BaseSolver5,
52822
+ BaseSolver as BaseSolver9,
51746
52823
  definePipelineStep
51747
52824
  } from "@tscircuit/solver-utils";
51748
52825
 
@@ -51760,7 +52837,7 @@ import { applyToPoint as applyToPoint30, compose as compose11, scale as scale2,
51760
52837
  // node_modules/@tscircuit/implicit-copper-pour-solver/lib/geometry.ts
51761
52838
  import {
51762
52839
  clamp,
51763
- distance as distance13,
52840
+ distance as distance15,
51764
52841
  doBoundsOverlap as doBoundsOverlap2,
51765
52842
  getBoundsFromPoints as getBoundsFromPoints3,
51766
52843
  getUnitVectorFromPointAToB as getUnitVectorFromPointAToB2,
@@ -51809,7 +52886,7 @@ var distanceToPolygon = (point6, polygon) => {
51809
52886
  var distanceToPrimitive = (primitive, px, py) => {
51810
52887
  const point6 = { x: px, y: py };
51811
52888
  if (primitive.kind === "circle") {
51812
- return Math.max(distance13(point6, primitive) - primitive.radius, 0);
52889
+ return Math.max(distance15(point6, primitive) - primitive.radius, 0);
51813
52890
  }
51814
52891
  if (primitive.kind === "segment") {
51815
52892
  return Math.max(
@@ -51860,7 +52937,7 @@ var getClosestPointOnPrimitive = (primitive, point6) => {
51860
52937
  const start = primitive.points[index];
51861
52938
  const end = primitive.points[(index + 1) % primitive.points.length];
51862
52939
  const candidate = pointToSegmentClosestPoint(point6, start, end);
51863
- const candidateDistance = distance13(point6, candidate);
52940
+ const candidateDistance = distance15(point6, candidate);
51864
52941
  if (candidateDistance < bestDistance) {
51865
52942
  bestDistance = candidateDistance;
51866
52943
  closestPoint = candidate;
@@ -52916,7 +53993,7 @@ var prepareCircuitJson = (input) => {
52916
53993
 
52917
53994
  // node_modules/@tscircuit/implicit-copper-pour-solver/lib/simplify-polygon-edges.ts
52918
53995
  import {
52919
- distance as distance14,
53996
+ distance as distance16,
52920
53997
  doSegmentsIntersect,
52921
53998
  onSegment,
52922
53999
  pointToSegmentDistance as pointToSegmentDistance2
@@ -52979,8 +54056,8 @@ var simplifyOpenPath = (points, tolerance) => {
52979
54056
  const point6 = points[index];
52980
54057
  const next = points[index + 1];
52981
54058
  const shortestLeg = Math.min(
52982
- distance14(point6, previous),
52983
- distance14(next, point6)
54059
+ distance16(point6, previous),
54060
+ distance16(next, point6)
52984
54061
  );
52985
54062
  if (repeatsTurnDirection || shortestLeg > tolerance) {
52986
54063
  anchorIndices.push(index);
@@ -53029,8 +54106,8 @@ var normalizePolygon = (polygon) => {
53029
54106
  };
53030
54107
  var isPointStrictlyInsideSegment = (point6, start, end) => {
53031
54108
  if (pointsEqual(point6, start) || pointsEqual(point6, end)) return false;
53032
- const cross4 = crossProduct(start, end, point6);
53033
- if (Math.abs(cross4) > 1e-9) return false;
54109
+ const cross5 = crossProduct(start, end, point6);
54110
+ if (Math.abs(cross5) > 1e-9) return false;
53034
54111
  return onSegment(start, point6, end);
53035
54112
  };
53036
54113
  var splitEdgesAtVertices = (polygon, allVertices) => {
@@ -53382,7 +54459,7 @@ var mergeSolverGraphics = (source, overlay) => {
53382
54459
  };
53383
54460
 
53384
54461
  // node_modules/@tscircuit/implicit-copper-pour-solver/lib/implicit-copper-pour-pipeline-solver.ts
53385
- var PrepareCircuitJsonSolver = class extends BaseSolver5 {
54462
+ var PrepareCircuitJsonSolver = class extends BaseSolver9 {
53386
54463
  constructor(input) {
53387
54464
  super();
53388
54465
  this.input = input;
@@ -53398,7 +54475,7 @@ var PrepareCircuitJsonSolver = class extends BaseSolver5 {
53398
54475
  return this.output;
53399
54476
  }
53400
54477
  };
53401
- var AssignGridCellsSolver = class extends BaseSolver5 {
54478
+ var AssignGridCellsSolver = class extends BaseSolver9 {
53402
54479
  constructor(input) {
53403
54480
  super();
53404
54481
  this.input = input;
@@ -53421,7 +54498,7 @@ var AssignGridCellsSolver = class extends BaseSolver5 {
53421
54498
  return this.output;
53422
54499
  }
53423
54500
  };
53424
- var TracePowerPolygonsSolver = class extends BaseSolver5 {
54501
+ var TracePowerPolygonsSolver = class extends BaseSolver9 {
53425
54502
  constructor(input) {
53426
54503
  super();
53427
54504
  this.input = input;
@@ -53457,7 +54534,7 @@ var TracePowerPolygonsSolver = class extends BaseSolver5 {
53457
54534
  };
53458
54535
  }
53459
54536
  };
53460
- var SimplifyPolygonEdgesSolver = class extends BaseSolver5 {
54537
+ var SimplifyPolygonEdgesSolver = class extends BaseSolver9 {
53461
54538
  constructor(input) {
53462
54539
  super();
53463
54540
  this.input = input;
@@ -53595,13 +54672,13 @@ import { LayoutPipelineSolver } from "@tscircuit/matchpack";
53595
54672
  import { SchematicTracePipelineSolver } from "@tscircuit/schematic-trace-solver";
53596
54673
 
53597
54674
  // node_modules/@tscircuit/via-stitch-solver/lib/solvers/ViaStitchSolver.ts
53598
- import { BaseSolver as BaseSolver6 } from "@tscircuit/solver-utils";
54675
+ import { BaseSolver as BaseSolver10 } from "@tscircuit/solver-utils";
53599
54676
 
53600
54677
  // node_modules/@tscircuit/via-stitch-solver/lib/geometry/brep-point-containment.ts
53601
54678
  var POINT_EPSILON = 1e-8;
53602
54679
  var isPointOnSegment = (point6, start, end) => {
53603
- const cross4 = (point6.y - start.y) * (end.x - start.x) - (point6.x - start.x) * (end.y - start.y);
53604
- if (Math.abs(cross4) > POINT_EPSILON) return false;
54680
+ const cross5 = (point6.y - start.y) * (end.x - start.x) - (point6.x - start.x) * (end.y - start.y);
54681
+ if (Math.abs(cross5) > POINT_EPSILON) return false;
53605
54682
  const dot = (point6.x - start.x) * (end.x - start.x) + (point6.y - start.y) * (end.y - start.y);
53606
54683
  if (dot < -POINT_EPSILON) return false;
53607
54684
  const squaredLength = (end.x - start.x) ** 2 + (end.y - start.y) ** 2;
@@ -53922,7 +54999,7 @@ var getGridCoordinates = ({
53922
54999
  }
53923
55000
  return coordinates;
53924
55001
  };
53925
- var ViaStitchSolver = class extends BaseSolver6 {
55002
+ var ViaStitchSolver = class extends BaseSolver10 {
53926
55003
  constructor(input) {
53927
55004
  super();
53928
55005
  this.input = input;
@@ -54069,6 +55146,7 @@ var ViaStitchSolver = class extends BaseSolver6 {
54069
55146
  // lib/solvers.ts
54070
55147
  import { PackSolver2 } from "calculate-packing";
54071
55148
  var SOLVERS = {
55149
+ DogboneFanoutSolver,
54072
55150
  BusLanesSolver,
54073
55151
  PackSolver2,
54074
55152
  LayoutPipelineSolver,
@@ -54532,6 +55610,27 @@ var localAutorouterStrategies = /* @__PURE__ */ new Map([
54532
55610
  createFanoutAutorouterStrategy("single_layer_fanout")
54533
55611
  ],
54534
55612
  ["fanout", createFanoutAutorouterStrategy("fanout")],
55613
+ [
55614
+ "dogbone",
55615
+ {
55616
+ name: "dogbone",
55617
+ preserveOutputTraces: true,
55618
+ followUpAutorouter: "default",
55619
+ cacheable: false,
55620
+ getSolverName: () => "DogboneFanoutSolver",
55621
+ create: ({
55622
+ simpleRouteJson,
55623
+ fanoutRoutingLayers,
55624
+ onSolverStarted,
55625
+ onSolverEnded
55626
+ }) => {
55627
+ return new DogboneAutorouter(
55628
+ { input: simpleRouteJson, fanoutRoutingLayers },
55629
+ { onSolverStarted, onSolverEnded }
55630
+ );
55631
+ }
55632
+ }
55633
+ ],
54535
55634
  [
54536
55635
  "bus_lanes",
54537
55636
  {
@@ -61008,19 +62107,19 @@ import Debug12 from "debug";
61008
62107
  import { isValidElement as isValidElement2 } from "react";
61009
62108
 
61010
62109
  // lib/components/primitive-components/Group/Group_doInitialSchematicTraceRender/segment-intersection.ts
61011
- var cross3 = (ax, ay, bx, by) => {
62110
+ var cross4 = (ax, ay, bx, by) => {
61012
62111
  return ax * by - ay * bx;
61013
62112
  };
61014
62113
  function segmentIntersection(p1, p2, q1, q2, tol) {
61015
62114
  const r = { x: p2.x - p1.x, y: p2.y - p1.y };
61016
62115
  const s = { x: q2.x - q1.x, y: q2.y - q1.y };
61017
- const rxs = cross3(r.x, r.y, s.x, s.y);
62116
+ const rxs = cross4(r.x, r.y, s.x, s.y);
61018
62117
  const q_p = { x: q1.x - p1.x, y: q1.y - p1.y };
61019
- const q_pxr = cross3(q_p.x, q_p.y, r.x, r.y);
62118
+ const q_pxr = cross4(q_p.x, q_p.y, r.x, r.y);
61020
62119
  if (Math.abs(rxs) < tol && Math.abs(q_pxr) < tol) return null;
61021
62120
  if (Math.abs(rxs) < tol && Math.abs(q_pxr) >= tol) return null;
61022
- const t = cross3(q_p.x, q_p.y, s.x, s.y) / rxs;
61023
- const u = cross3(q_p.x, q_p.y, r.x, r.y) / rxs;
62121
+ const t = cross4(q_p.x, q_p.y, s.x, s.y) / rxs;
62122
+ const u = cross4(q_p.x, q_p.y, r.x, r.y) / rxs;
61024
62123
  if (t < -tol || t > 1 + tol || u < -tol || u > 1 + tol) return null;
61025
62124
  return { x: p1.x + t * r.x, y: p1.y + t * r.y };
61026
62125
  }
@@ -61213,8 +62312,8 @@ function isParallel(e1, e2, tol = TOL2) {
61213
62312
  const L1 = Math.hypot(v1.x, v1.y);
61214
62313
  const L2 = Math.hypot(v2.x, v2.y);
61215
62314
  if (L1 < tol || L2 < tol) return true;
61216
- const cross4 = v1.x * v2.y - v1.y * v2.x;
61217
- return Math.abs(cross4) <= tol * L1 * L2;
62315
+ const cross5 = v1.x * v2.y - v1.y * v2.x;
62316
+ return Math.abs(cross5) <= tol * L1 * L2;
61218
62317
  }
61219
62318
  function incidentEdgesAtPoint(trace, p, tol = TOL2) {
61220
62319
  return trace.edges.filter(
@@ -64481,6 +65580,16 @@ function Group_getRoutingPhasePlans(group) {
64481
65580
  nets: nets.filter((net) => breakoutByNet.get(net) === breakout),
64482
65581
  traces: breakoutTraces
64483
65582
  };
65583
+ if (breakout.isDogboneFanout && breakoutProps2.pcbTracePaths === void 0) {
65584
+ breakoutPlans.push({
65585
+ ...breakoutPlan,
65586
+ // Keep the local fanout outline while allowing dogbone sites outside
65587
+ // the pad bounding box to use the full board routing bounds.
65588
+ routingBounds: void 0,
65589
+ fanoutBounds: routingBounds
65590
+ });
65591
+ continue;
65592
+ }
64484
65593
  if (breakout._parsedProps.pcbTracePaths !== void 0) {
64485
65594
  breakoutPlans.push({
64486
65595
  ...breakoutPlan,
@@ -64553,7 +65662,7 @@ function Group_getRoutingPhasePlans(group) {
64553
65662
  var package_default = {
64554
65663
  name: "@tscircuit/core",
64555
65664
  type: "module",
64556
- version: "0.0.2023",
65665
+ version: "0.0.2025",
64557
65666
  types: "dist/index.d.ts",
64558
65667
  main: "dist/index.js",
64559
65668
  module: "dist/index.js",
@@ -64594,6 +65703,7 @@ var package_default = {
64594
65703
  "@tscircuit/common": "^0.0.20",
64595
65704
  "@tscircuit/copper-pour-solver": "^0.0.62",
64596
65705
  "@tscircuit/create-fdm-enclosure": "0.0.4",
65706
+ "@tscircuit/dogbone-solver": "https://jscdn.tscircuit.com/@tscircuit/dogbone-solver/0.0.1.tgz",
64597
65707
  "@tscircuit/fanout-solver": "0.0.78",
64598
65708
  "@tscircuit/flex-utils": "https://jscdn.tscircuit.com/@tscircuit/flex-utils/0.0.5.tgz",
64599
65709
  "@tscircuit/footprinter": "^0.0.426",
@@ -65021,7 +66131,6 @@ function Group_syncFanoutExitsWithGlobalConnections({
65021
66131
  ) ? [{ globalConnection, pointIndex, previousPoint }] : []
65022
66132
  )
65023
66133
  );
65024
- if (globalPointMatches.length === 0) continue;
65025
66134
  const changedPointIndex = outputConnection.pointsToConnect.findIndex(
65026
66135
  (outputPoint, pointIndex) => {
65027
66136
  const inputPoint = inputConnection.pointsToConnect[pointIndex];
@@ -65030,6 +66139,14 @@ function Group_syncFanoutExitsWithGlobalConnections({
65030
66139
  );
65031
66140
  if (changedPointIndex < 0) continue;
65032
66141
  const fanoutExitPoint = outputConnection.pointsToConnect[changedPointIndex];
66142
+ if (globalPointMatches.length === 0) {
66143
+ synchronizedBreakoutPoints.push({
66144
+ sourceTraceId: inputConnection.source_trace_id,
66145
+ routingPcbGroupId,
66146
+ previousPoint: inputConnection.pointsToConnect[changedPointIndex],
66147
+ fanoutExitPoint
66148
+ });
66149
+ }
65033
66150
  for (const {
65034
66151
  globalConnection,
65035
66152
  pointIndex,
@@ -65290,7 +66407,7 @@ function getAccumulatedPcbTracesWithStageOutputReplacements({
65290
66407
  }
65291
66408
 
65292
66409
  // lib/components/primitive-components/Group/get-source-trace-id-for-routed-trace.ts
65293
- import { distance as distance15, pointToSegmentDistance as pointToSegmentDistance3 } from "@tscircuit/math-utils";
66410
+ import { distance as distance17, pointToSegmentDistance as pointToSegmentDistance3 } from "@tscircuit/math-utils";
65294
66411
  import { ConnectivityMap as ConnectivityMap5 } from "circuit-json-to-connectivity-map";
65295
66412
  var POINT_EPSILON2 = 1e-6;
65296
66413
  function isPointOnWireSegment({
@@ -65312,7 +66429,7 @@ function isPointInSmtPad({
65312
66429
  return Math.abs(point6.x - pad.x) <= pad.width / 2 + traceWidth / 2 && Math.abs(point6.y - pad.y) <= pad.height / 2 + traceWidth / 2;
65313
66430
  }
65314
66431
  if (pad.shape === "circle") {
65315
- return distance15(point6, pad) <= pad.radius + traceWidth / 2;
66432
+ return distance17(point6, pad) <= pad.radius + traceWidth / 2;
65316
66433
  }
65317
66434
  return false;
65318
66435
  }
@@ -65321,7 +66438,7 @@ function isPointInPlatedHole({
65321
66438
  hole,
65322
66439
  traceWidth = 0
65323
66440
  }) {
65324
- return distance15(point6, hole) <= (hole.outer_diameter ?? hole.hole_diameter ?? 0) / 2 + traceWidth / 2;
66441
+ return distance17(point6, hole) <= (hole.outer_diameter ?? hole.hole_diameter ?? 0) / 2 + traceWidth / 2;
65325
66442
  }
65326
66443
  function getEndpointSourcePortIdsFromGeometry(db, trace) {
65327
66444
  const sourcePortIds = /* @__PURE__ */ new Set();
@@ -65334,7 +66451,7 @@ function getEndpointSourcePortIdsFromGeometry(db, trace) {
65334
66451
  if (!pcbPort.source_port_id) continue;
65335
66452
  if (endpoint.layer && pcbPort.layers && !pcbPort.layers.includes(endpoint.layer))
65336
66453
  continue;
65337
- if (distance15(endpoint, pcbPort) <= 0.01) {
66454
+ if (distance17(endpoint, pcbPort) <= 0.01) {
65338
66455
  sourcePortIds.add(pcbPort.source_port_id);
65339
66456
  }
65340
66457
  }
@@ -66018,8 +67135,8 @@ var Group5 = class extends NormalComponent {
66018
67135
  const groupProps2 = props;
66019
67136
  const hasOutline = groupProps2.outline && groupProps2.outline.length > 0;
66020
67137
  const numericOutline = hasOutline ? groupProps2.outline.map((point6) => ({
66021
- x: distance16.parse(point6.x),
66022
- y: distance16.parse(point6.y)
67138
+ x: distance18.parse(point6.x),
67139
+ y: distance18.parse(point6.y)
66023
67140
  })) : void 0;
66024
67141
  const ctx = this.props;
66025
67142
  const anchorPosition = this._getGlobalPcbPositionBeforeLayout();
@@ -66057,8 +67174,8 @@ var Group5 = class extends NormalComponent {
66057
67174
  const hasExplicitPositioning = this._parsedProps.pcbX !== void 0 || this._parsedProps.pcbY !== void 0;
66058
67175
  if (hasOutline) {
66059
67176
  const numericOutline = props.outline.map((point6) => ({
66060
- x: distance16.parse(point6.x),
66061
- y: distance16.parse(point6.y)
67177
+ x: distance18.parse(point6.x),
67178
+ y: distance18.parse(point6.y)
66062
67179
  }));
66063
67180
  const outlineBounds = getBoundsFromPoints9(numericOutline);
66064
67181
  if (!outlineBounds) return;
@@ -66459,7 +67576,9 @@ var Group5 = class extends NormalComponent {
66459
67576
  routingPhaseDebugLabels.set(plan, `${label} ${ordinal}`);
66460
67577
  }
66461
67578
  const hasFanoutStage = routingStages.some(
66462
- ({ autorouterConfig: autorouterConfig2 }) => ["fanout", "single_layer_fanout"].includes(autorouterConfig2.preset ?? "")
67579
+ ({ autorouterConfig: autorouterConfig2 }) => ["fanout", "single_layer_fanout", "dogbone"].includes(
67580
+ autorouterConfig2.preset ?? ""
67581
+ )
66463
67582
  );
66464
67583
  const fanoutPourNetMap = hasFanoutStage ? Group_getFanoutPourNetMap(this, routingPhasePlans) : void 0;
66465
67584
  let { simpleRouteJson: baseSimpleRouteJson } = getSimpleRouteJsonFromCircuitJson({
@@ -66882,6 +68001,11 @@ var Group5 = class extends NormalComponent {
66882
68001
  } else {
66883
68002
  autorouter = localAutorouterStrategy.create({
66884
68003
  simpleRouteJson,
68004
+ onSolverEnded: (event) => this.root?.emit("solver:ended", {
68005
+ ...event,
68006
+ type: "solver:ended",
68007
+ componentName: this.getString()
68008
+ }),
66885
68009
  commonAutorouterOptions,
66886
68010
  busFanoutDirections: routingPhasePlan.busFanoutDirections,
66887
68011
  fanoutBounds: routingPhasePlan.fanoutBounds,
@@ -66937,7 +68061,7 @@ var Group5 = class extends NormalComponent {
66937
68061
  traces = await routingPromise;
66938
68062
  }
66939
68063
  let transformedSimpleRouteJson = precomputedOutputSimpleRouteJson ?? autorouter?.getOutputSimpleRouteJson?.();
66940
- if (transformedSimpleRouteJson && !usesPreviousStageOutput && ["fanout", "single_layer_fanout"].includes(
68064
+ if (transformedSimpleRouteJson && !usesPreviousStageOutput && ["fanout", "single_layer_fanout", "dogbone"].includes(
66941
68065
  phaseAutorouterConfig.preset ?? ""
66942
68066
  ) && routingPhasePlan.routingPcbGroupId) {
66943
68067
  const synchronizedFanout = Group_syncFanoutExitsWithGlobalConnections(
@@ -66957,10 +68081,14 @@ var Group5 = class extends NormalComponent {
66957
68081
  if (!breakoutPoint) continue;
66958
68082
  db.pcb_breakout_point.update(breakoutPoint.pcb_breakout_point_id, {
66959
68083
  x: synchronizedPoint.fanoutExitPoint.x,
66960
- y: synchronizedPoint.fanoutExitPoint.y
68084
+ y: synchronizedPoint.fanoutExitPoint.y,
68085
+ layer: synchronizedPoint.fanoutExitPoint.layer
66961
68086
  });
66962
68087
  }
66963
68088
  }
68089
+ if (localAutorouterStrategy.preserveOutputTraces) {
68090
+ for (const trace of traces) fixedTraceIds.add(trace.pcb_trace_id);
68091
+ }
66964
68092
  let stageOutputTraces = traces;
66965
68093
  if (transformedSimpleRouteJson?.traces) {
66966
68094
  stageOutputTraces = transformedSimpleRouteJson.traces;
@@ -66997,7 +68125,7 @@ var Group5 = class extends NormalComponent {
66997
68125
  subcircuit: this,
66998
68126
  input: simpleRouteJson,
66999
68127
  traces,
67000
- isFanout: ["fanout", "single_layer_fanout"].includes(
68128
+ isFanout: ["fanout", "single_layer_fanout", "dogbone"].includes(
67001
68129
  phaseAutorouterConfig.preset ?? ""
67002
68130
  )
67003
68131
  });
@@ -67930,8 +69058,8 @@ var Led_addPolarityFabricationSymbol = (led) => {
67930
69058
  const a = db.pcb_port.getWhere({ source_port_id: anode.source_port_id });
67931
69059
  const k = db.pcb_port.getWhere({ source_port_id: cathode.source_port_id });
67932
69060
  if (!a || !k) return;
67933
- const distance23 = Math.hypot(k.x - a.x, k.y - a.y);
67934
- if (distance23 < 1e-6) return;
69061
+ const distance25 = Math.hypot(k.x - a.x, k.y - a.y);
69062
+ if (distance25 < 1e-6) return;
67935
69063
  const angle = Math.atan2(k.y - a.y, k.x - a.x);
67936
69064
  const frame = compose15(
67937
69065
  translate15((a.x + k.x) / 2, (a.y + k.y) / 2),
@@ -67945,10 +69073,10 @@ var Led_addPolarityFabricationSymbol = (led) => {
67945
69073
  };
67946
69074
  const notes = [];
67947
69075
  generatedNotes.set(led, notes);
67948
- const halfLength = Math.min(distance23 * 0.2, 0.4);
69076
+ const halfLength = Math.min(distance25 * 0.2, 0.4);
67949
69077
  const halfHeight = halfLength * 0.75;
67950
69078
  const routes = [
67951
- [point6(-distance23 / 2, 0), point6(-halfLength, 0)],
69079
+ [point6(-distance25 / 2, 0), point6(-halfLength, 0)],
67952
69080
  [
67953
69081
  point6(-halfLength, -halfHeight),
67954
69082
  point6(halfLength, 0),
@@ -67956,7 +69084,7 @@ var Led_addPolarityFabricationSymbol = (led) => {
67956
69084
  point6(-halfLength, -halfHeight)
67957
69085
  ],
67958
69086
  [point6(halfLength, -halfHeight), point6(halfLength, halfHeight)],
67959
- [point6(halfLength, 0), point6(distance23 / 2, 0)]
69087
+ [point6(halfLength, 0), point6(distance25 / 2, 0)]
67960
69088
  ];
67961
69089
  for (const route of routes) {
67962
69090
  notes.push(
@@ -69890,7 +71018,7 @@ var Board_doInitialPcbPlacementDesignRuleChecks = (board) => {
69890
71018
 
69891
71019
  // lib/components/normal-components/Board/board-castellated-hole.ts
69892
71020
  import {
69893
- distance as distance17
71021
+ distance as distance19
69894
71022
  } from "circuit-json";
69895
71023
  var CASTELLATED_HOLE_ENDPOINT_TOLERANCE_MM = 1e-6;
69896
71024
  var getConnectionTargets2 = (connectsTo) => {
@@ -69911,8 +71039,8 @@ var BoardCastellatedHole = class _BoardCastellatedHole extends PlatedHole {
69911
71039
  constructor(outlinePoint, outlinePointIndex) {
69912
71040
  const name = `castellated_hole_${outlinePointIndex + 1}`;
69913
71041
  const connectionTargets = getConnectionTargets2(outlinePoint.connectsTo);
69914
- const holeDiameter = distance17.parse(outlinePoint.holeDiameter);
69915
- const padDiameter = distance17.parse(outlinePoint.padDiameter);
71042
+ const holeDiameter = distance19.parse(outlinePoint.holeDiameter);
71043
+ const padDiameter = distance19.parse(outlinePoint.padDiameter);
69916
71044
  super({
69917
71045
  shape: "circle",
69918
71046
  holeDiameter,
@@ -70662,11 +71790,11 @@ var MountedBoard = class extends Subcircuit {
70662
71790
 
70663
71791
  // lib/components/normal-components/Panel.ts
70664
71792
  import { panelProps } from "@tscircuit/props";
70665
- import { distance as distance21 } from "circuit-json";
71793
+ import { distance as distance23 } from "circuit-json";
70666
71794
 
70667
71795
  // lib/components/normal-components/Subpanel.ts
70668
71796
  import { subpanelProps } from "@tscircuit/props";
70669
- import { distance as distance20 } from "circuit-json";
71797
+ import { distance as distance22 } from "circuit-json";
70670
71798
  import { compose as compose17, identity as identity9, translate as translate17 } from "transformation-matrix";
70671
71799
 
70672
71800
  // lib/utils/panels/generate-panel-tabs-and-mouse-bites.ts
@@ -70930,15 +72058,15 @@ function generatePanelOutlineRoutingCutouts(boards, options) {
70930
72058
  }
70931
72059
 
70932
72060
  // lib/utils/panels/pack-into-grid.ts
70933
- import { distance as distance19 } from "circuit-json";
72061
+ import { distance as distance21 } from "circuit-json";
70934
72062
 
70935
72063
  // lib/utils/panels/get-board-dimensions-from-props.ts
70936
72064
  import { getBoundsFromPoints as getBoundsFromPoints11 } from "@tscircuit/math-utils";
70937
- import { distance as distance18 } from "circuit-json";
72065
+ import { distance as distance20 } from "circuit-json";
70938
72066
  var getBoardDimensionsFromProps = (board) => {
70939
72067
  const props = board._parsedProps;
70940
- let width = props.width != null ? distance18.parse(props.width) : void 0;
70941
- let height = props.height != null ? distance18.parse(props.height) : void 0;
72068
+ let width = props.width != null ? distance20.parse(props.width) : void 0;
72069
+ let height = props.height != null ? distance20.parse(props.height) : void 0;
70942
72070
  if ((width === void 0 || height === void 0) && props.outline?.length) {
70943
72071
  const outlineBounds = getBoundsFromPoints11(props.outline);
70944
72072
  if (outlineBounds) {
@@ -70982,8 +72110,8 @@ function packIntoGrid({
70982
72110
  }
70983
72111
  let cols;
70984
72112
  let rows;
70985
- const minCellWidth = cellWidth ? distance19.parse(cellWidth) : 0;
70986
- const minCellHeight = cellHeight ? distance19.parse(cellHeight) : 0;
72113
+ const minCellWidth = cellWidth ? distance21.parse(cellWidth) : 0;
72114
+ const minCellHeight = cellHeight ? distance21.parse(cellHeight) : 0;
70987
72115
  if (col !== void 0) {
70988
72116
  cols = col;
70989
72117
  rows = row ?? Math.ceil(itemsWithDims.length / cols);
@@ -71082,8 +72210,8 @@ function getItemDimensions(item, db) {
71082
72210
  const props = subpanel._parsedProps;
71083
72211
  if (props.width !== void 0 && props.height !== void 0) {
71084
72212
  return {
71085
- width: distance19.parse(props.width),
71086
- height: distance19.parse(props.height)
72213
+ width: distance21.parse(props.width),
72214
+ height: distance21.parse(props.height)
71087
72215
  };
71088
72216
  }
71089
72217
  const directBoards = subpanel._getDirectBoardChildren();
@@ -71110,7 +72238,7 @@ function getItemDimensions(item, db) {
71110
72238
  return getBoardDimensionsFromProps(directBoards[0]);
71111
72239
  }
71112
72240
  if (subpanel._cachedGridWidth > 0 && subpanel._cachedGridHeight > 0) {
71113
- const edgePadding = distance19.parse(props.edgePadding ?? 5);
72241
+ const edgePadding = distance21.parse(props.edgePadding ?? 5);
71114
72242
  return {
71115
72243
  width: subpanel._cachedGridWidth + edgePadding * 2,
71116
72244
  height: subpanel._cachedGridHeight + edgePadding * 2
@@ -71268,17 +72396,17 @@ var Subpanel = class _Subpanel extends Group5 {
71268
72396
  edgePaddingTop: edgePaddingTopProp,
71269
72397
  edgePaddingBottom: edgePaddingBottomProp
71270
72398
  } = this._parsedProps;
71271
- const edgePadding = distance20.parse(edgePaddingProp ?? 5);
71272
- const edgePaddingLeft = distance20.parse(edgePaddingLeftProp ?? edgePadding);
71273
- const edgePaddingRight = distance20.parse(
72399
+ const edgePadding = distance22.parse(edgePaddingProp ?? 5);
72400
+ const edgePaddingLeft = distance22.parse(edgePaddingLeftProp ?? edgePadding);
72401
+ const edgePaddingRight = distance22.parse(
71274
72402
  edgePaddingRightProp ?? edgePadding
71275
72403
  );
71276
- const edgePaddingTop = distance20.parse(edgePaddingTopProp ?? edgePadding);
71277
- const edgePaddingBottom = distance20.parse(
72404
+ const edgePaddingTop = distance22.parse(edgePaddingTopProp ?? edgePadding);
72405
+ const edgePaddingBottom = distance22.parse(
71278
72406
  edgePaddingBottomProp ?? edgePadding
71279
72407
  );
71280
- const panelWidth = distance20.parse(this._parsedProps.width);
71281
- const panelHeight = distance20.parse(this._parsedProps.height);
72408
+ const panelWidth = distance22.parse(this._parsedProps.width);
72409
+ const panelHeight = distance22.parse(this._parsedProps.height);
71282
72410
  availablePanelWidth = panelWidth - edgePaddingLeft - edgePaddingRight;
71283
72411
  availablePanelHeight = panelHeight - edgePaddingTop - edgePaddingBottom;
71284
72412
  }
@@ -71353,8 +72481,8 @@ var Subpanel = class _Subpanel extends Group5 {
71353
72481
  if (!this.pcb_group_id) return;
71354
72482
  if (hasExplicitWidth && hasExplicitHeight) {
71355
72483
  db.pcb_group.update(this.pcb_group_id, {
71356
- width: distance20.parse(this._parsedProps.width),
71357
- height: distance20.parse(this._parsedProps.height)
72484
+ width: distance22.parse(this._parsedProps.width),
72485
+ height: distance22.parse(this._parsedProps.height)
71358
72486
  });
71359
72487
  } else if (gridWidth > 0 || gridHeight > 0) {
71360
72488
  const {
@@ -71364,18 +72492,18 @@ var Subpanel = class _Subpanel extends Group5 {
71364
72492
  edgePaddingTop: edgePaddingTopProp,
71365
72493
  edgePaddingBottom: edgePaddingBottomProp
71366
72494
  } = this._parsedProps;
71367
- const edgePadding = distance20.parse(edgePaddingProp ?? 5);
71368
- const edgePaddingLeft = distance20.parse(edgePaddingLeftProp ?? edgePadding);
71369
- const edgePaddingRight = distance20.parse(
72495
+ const edgePadding = distance22.parse(edgePaddingProp ?? 5);
72496
+ const edgePaddingLeft = distance22.parse(edgePaddingLeftProp ?? edgePadding);
72497
+ const edgePaddingRight = distance22.parse(
71370
72498
  edgePaddingRightProp ?? edgePadding
71371
72499
  );
71372
- const edgePaddingTop = distance20.parse(edgePaddingTopProp ?? edgePadding);
71373
- const edgePaddingBottom = distance20.parse(
72500
+ const edgePaddingTop = distance22.parse(edgePaddingTopProp ?? edgePadding);
72501
+ const edgePaddingBottom = distance22.parse(
71374
72502
  edgePaddingBottomProp ?? edgePadding
71375
72503
  );
71376
72504
  db.pcb_group.update(this.pcb_group_id, {
71377
- width: hasExplicitWidth ? distance20.parse(this._parsedProps.width) : gridWidth + edgePaddingLeft + edgePaddingRight,
71378
- height: hasExplicitHeight ? distance20.parse(this._parsedProps.height) : gridHeight + edgePaddingTop + edgePaddingBottom
72505
+ width: hasExplicitWidth ? distance22.parse(this._parsedProps.width) : gridWidth + edgePaddingLeft + edgePaddingRight,
72506
+ height: hasExplicitHeight ? distance22.parse(this._parsedProps.height) : gridHeight + edgePaddingTop + edgePaddingBottom
71379
72507
  });
71380
72508
  }
71381
72509
  }
@@ -71463,8 +72591,8 @@ var Panel = class extends Subpanel {
71463
72591
  const { db } = this.root;
71464
72592
  const props = this._parsedProps;
71465
72593
  const inserted = db.pcb_panel.insert({
71466
- width: props.width !== void 0 ? distance21.parse(props.width) : 0,
71467
- height: props.height !== void 0 ? distance21.parse(props.height) : 0,
72594
+ width: props.width !== void 0 ? distance23.parse(props.width) : 0,
72595
+ height: props.height !== void 0 ? distance23.parse(props.height) : 0,
71468
72596
  thickness: 1.6,
71469
72597
  center: this._getGlobalPcbPositionBeforeLayout(),
71470
72598
  covered_with_solder_mask: !(props.noSolderMask ?? false)
@@ -71483,8 +72611,8 @@ var Panel = class extends Subpanel {
71483
72611
  if (!this.pcb_panel_id) return;
71484
72612
  if (hasExplicitWidth && hasExplicitHeight) {
71485
72613
  db.pcb_panel.update(this.pcb_panel_id, {
71486
- width: distance21.parse(this._parsedProps.width),
71487
- height: distance21.parse(this._parsedProps.height)
72614
+ width: distance23.parse(this._parsedProps.width),
72615
+ height: distance23.parse(this._parsedProps.height)
71488
72616
  });
71489
72617
  } else if (gridWidth > 0 || gridHeight > 0) {
71490
72618
  const {
@@ -71494,18 +72622,18 @@ var Panel = class extends Subpanel {
71494
72622
  edgePaddingTop: edgePaddingTopProp,
71495
72623
  edgePaddingBottom: edgePaddingBottomProp
71496
72624
  } = this._parsedProps;
71497
- const edgePadding = distance21.parse(edgePaddingProp ?? 5);
71498
- const edgePaddingLeft = distance21.parse(edgePaddingLeftProp ?? edgePadding);
71499
- const edgePaddingRight = distance21.parse(
72625
+ const edgePadding = distance23.parse(edgePaddingProp ?? 5);
72626
+ const edgePaddingLeft = distance23.parse(edgePaddingLeftProp ?? edgePadding);
72627
+ const edgePaddingRight = distance23.parse(
71500
72628
  edgePaddingRightProp ?? edgePadding
71501
72629
  );
71502
- const edgePaddingTop = distance21.parse(edgePaddingTopProp ?? edgePadding);
71503
- const edgePaddingBottom = distance21.parse(
72630
+ const edgePaddingTop = distance23.parse(edgePaddingTopProp ?? edgePadding);
72631
+ const edgePaddingBottom = distance23.parse(
71504
72632
  edgePaddingBottomProp ?? edgePadding
71505
72633
  );
71506
72634
  db.pcb_panel.update(this.pcb_panel_id, {
71507
- width: hasExplicitWidth ? distance21.parse(this._parsedProps.width) : gridWidth + edgePaddingLeft + edgePaddingRight,
71508
- height: hasExplicitHeight ? distance21.parse(this._parsedProps.height) : gridHeight + edgePaddingTop + edgePaddingBottom
72635
+ width: hasExplicitWidth ? distance23.parse(this._parsedProps.width) : gridWidth + edgePaddingLeft + edgePaddingRight,
72636
+ height: hasExplicitHeight ? distance23.parse(this._parsedProps.height) : gridHeight + edgePaddingTop + edgePaddingBottom
71509
72637
  });
71510
72638
  }
71511
72639
  }
@@ -71516,8 +72644,8 @@ var Panel = class extends Subpanel {
71516
72644
  const props = this._parsedProps;
71517
72645
  const currentPanel = db.pcb_panel.get(this.pcb_panel_id);
71518
72646
  db.pcb_panel.update(this.pcb_panel_id, {
71519
- width: props.width !== void 0 ? distance21.parse(props.width) : currentPanel?.width,
71520
- height: props.height !== void 0 ? distance21.parse(props.height) : currentPanel?.height,
72647
+ width: props.width !== void 0 ? distance23.parse(props.width) : currentPanel?.width,
72648
+ height: props.height !== void 0 ? distance23.parse(props.height) : currentPanel?.height,
71521
72649
  center: this._getGlobalPcbPositionBeforeLayout(),
71522
72650
  covered_with_solder_mask: !(props.noSolderMask ?? false)
71523
72651
  });
@@ -73339,7 +74467,7 @@ var PcbNoteDimension = class extends PrimitiveComponent2 {
73339
74467
  import { breakoutProps } from "@tscircuit/props";
73340
74468
 
73341
74469
  // lib/components/primitive-components/AutoplacedBreakoutPoint.ts
73342
- import { layer_ref as layer_ref3 } from "circuit-json";
74470
+ import { layer_ref as layer_ref4 } from "circuit-json";
73343
74471
  var AutoplacedBreakoutPoint = class extends BaseBreakoutPoint {
73344
74472
  get config() {
73345
74473
  return {
@@ -73353,7 +74481,7 @@ var AutoplacedBreakoutPoint = class extends BaseBreakoutPoint {
73353
74481
  position
73354
74482
  }) {
73355
74483
  this.matchedSourceTraceId = sourceTraceId;
73356
- this._renderPcbBreakoutPoint({ layer: layer_ref3.parse(layer) });
74484
+ this._renderPcbBreakoutPoint({ layer: layer_ref4.parse(layer) });
73357
74485
  this._setPositionFromLayout(position);
73358
74486
  }
73359
74487
  };
@@ -74065,7 +75193,7 @@ var validateWindingBreakoutInput = (input) => {
74065
75193
  };
74066
75194
 
74067
75195
  // node_modules/@tscircuit/winding-breakout-point-solver/lib/solvers/GatePlacementSolver.ts
74068
- import { BaseSolver as BaseSolver7 } from "@tscircuit/solver-utils";
75196
+ import { BaseSolver as BaseSolver11 } from "@tscircuit/solver-utils";
74069
75197
 
74070
75198
  // node_modules/@tscircuit/winding-breakout-point-solver/lib/gate-placement/place-breakout-gates.ts
74071
75199
  var GEOMETRY_EPSILON = 1e-9;
@@ -74595,7 +75723,7 @@ var getSharedGateSlotCount = (points) => new Set(
74595
75723
  (point6) => `${point6.regionId}:${point6.x.toFixed(9)}:${point6.y.toFixed(9)}`
74596
75724
  )
74597
75725
  ).size;
74598
- var GatePlacementSolver = class extends BaseSolver7 {
75726
+ var GatePlacementSolver = class extends BaseSolver11 {
74599
75727
  constructor(params) {
74600
75728
  super();
74601
75729
  this.params = params;
@@ -74734,7 +75862,7 @@ var GatePlacementSolver = class extends BaseSolver7 {
74734
75862
  };
74735
75863
 
74736
75864
  // node_modules/@tscircuit/winding-breakout-point-solver/lib/solvers/ReferenceOrderingSolver.ts
74737
- import { BaseSolver as BaseSolver8 } from "@tscircuit/solver-utils";
75865
+ import { BaseSolver as BaseSolver12 } from "@tscircuit/solver-utils";
74738
75866
 
74739
75867
  // node_modules/@tscircuit/winding-breakout-point-solver/lib/ordering/get-natural-winding-order.ts
74740
75868
  var getNaturalWindingOrder = (region, connections) => connections.map((connection) => {
@@ -74801,7 +75929,7 @@ var getReferenceWindingOrder = (naturalOrder, atomicGroups, buses) => {
74801
75929
  };
74802
75930
 
74803
75931
  // node_modules/@tscircuit/winding-breakout-point-solver/lib/solvers/ReferenceOrderingSolver.ts
74804
- var ReferenceOrderingSolver = class extends BaseSolver8 {
75932
+ var ReferenceOrderingSolver = class extends BaseSolver12 {
74805
75933
  constructor(params) {
74806
75934
  super();
74807
75935
  this.params = params;
@@ -75853,6 +76981,10 @@ var solveImplicitBreakoutPoints = (breakout, solverFn) => {
75853
76981
 
75854
76982
  // lib/components/primitive-components/Breakout/Breakout.ts
75855
76983
  var Breakout = class extends Group5 {
76984
+ get isDogboneFanout() {
76985
+ const autorouter = this._parsedProps.autorouter;
76986
+ return (typeof autorouter === "string" ? autorouter : autorouter?.preset) === "dogbone";
76987
+ }
75856
76988
  get isRoutingDirective() {
75857
76989
  return true;
75858
76990
  }
@@ -75911,7 +77043,7 @@ var Breakout = class extends Group5 {
75911
77043
  for (const port of result.ports) {
75912
77044
  const isInside = breakoutPortSet.has(port);
75913
77045
  const hasOutsidePort = result.ports.some((p) => !breakoutPortSet.has(p));
75914
- if (!isInside || !hasOutsidePort) continue;
77046
+ if (!isInside || !hasOutsidePort && !this.isDogboneFanout) continue;
75915
77047
  if (manuallyMappedPorts.has(port)) continue;
75916
77048
  if (autoPlacedPorts.has(port)) continue;
75917
77049
  autoPlacedPorts.add(port);
@@ -75924,6 +77056,20 @@ var Breakout = class extends Group5 {
75924
77056
  }
75925
77057
  doInitialPcbAutoplaceBreakoutPoints() {
75926
77058
  if (this.root?.pcbDisabled) return;
77059
+ if (this.isDogboneFanout && this._parsedProps.pcbTracePaths === void 0) {
77060
+ for (const point6 of this.children) {
77061
+ if (!(point6 instanceof AutoplacedBreakoutPoint)) continue;
77062
+ const port = this.root.db.pcb_port.get(
77063
+ point6.matchedPort.pcb_port_id
77064
+ );
77065
+ point6._applySolvedBreakoutPoint({
77066
+ sourceTraceId: point6.matchedSourceTraceId,
77067
+ layer: port.layers[0],
77068
+ position: port
77069
+ });
77070
+ }
77071
+ return;
77072
+ }
75927
77073
  const ownAutorouter = this.props.autorouter;
75928
77074
  const inheritedAutorouter = ownAutorouter === void 0 ? this.parent?.getInheritedProperty("autorouter") : void 0;
75929
77075
  const implicitBreakoutPointSolverFn = typeof ownAutorouter === "object" ? ownAutorouter.implicitBreakoutPointSolverFn : typeof inheritedAutorouter === "object" ? inheritedAutorouter.implicitBreakoutPointSolverFn : void 0;
@@ -76146,7 +77292,7 @@ var SilkscreenGraphic = class extends PrimitiveComponent2 {
76146
77292
 
76147
77293
  // lib/components/primitive-components/Fiducial.ts
76148
77294
  import "zod";
76149
- import { distance as distance22 } from "circuit-json";
77295
+ import { distance as distance24 } from "circuit-json";
76150
77296
  import { fiducialProps as fiducialProps2 } from "@tscircuit/props";
76151
77297
  var Fiducial = class extends PrimitiveComponent2 {
76152
77298
  pcb_smtpad_id = null;
@@ -76171,15 +77317,15 @@ var Fiducial = class extends PrimitiveComponent2 {
76171
77317
  shape: "circle",
76172
77318
  x: position.x,
76173
77319
  y: position.y,
76174
- radius: distance22.parse(props.padDiameter) / 2,
76175
- soldermask_margin: props.soldermaskPullback ? distance22.parse(props.soldermaskPullback) : distance22.parse(props.padDiameter) / 2,
77320
+ radius: distance24.parse(props.padDiameter) / 2,
77321
+ soldermask_margin: props.soldermaskPullback ? distance24.parse(props.soldermaskPullback) : distance24.parse(props.padDiameter) / 2,
76176
77322
  is_covered_with_solder_mask: true
76177
77323
  });
76178
77324
  this.pcb_smtpad_id = pcb_smtpad.pcb_smtpad_id;
76179
77325
  }
76180
77326
  getPcbSize() {
76181
77327
  const { _parsedProps: props } = this;
76182
- const d = distance22.parse(props.padDiameter);
77328
+ const d = distance24.parse(props.padDiameter);
76183
77329
  return { width: d, height: d };
76184
77330
  }
76185
77331
  _setPositionFromLayout(newCenter) {
@@ -76223,15 +77369,15 @@ import {
76223
77369
  } from "@tscircuit/copper-pour-solver";
76224
77370
 
76225
77371
  // lib/components/primitive-components/CopperPour/utils/mark-trace-segments-inside-copper-pour.ts
76226
- var EPSILON24 = 1e-9;
77372
+ var EPSILON26 = 1e-9;
76227
77373
  var isWireRoutePoint = (routePoint) => routePoint.route_type === "wire";
76228
77374
  var isPointOnSegment2 = (p, a, b) => {
76229
- const cross4 = (p.y - a.y) * (b.x - a.x) - (p.x - a.x) * (b.y - a.y);
76230
- if (Math.abs(cross4) > EPSILON24) return false;
77375
+ const cross5 = (p.y - a.y) * (b.x - a.x) - (p.x - a.x) * (b.y - a.y);
77376
+ if (Math.abs(cross5) > EPSILON26) return false;
76231
77377
  const dot = (p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y);
76232
- if (dot < -EPSILON24) return false;
77378
+ if (dot < -EPSILON26) return false;
76233
77379
  const squaredLength = (b.x - a.x) ** 2 + (b.y - a.y) ** 2;
76234
- if (dot - squaredLength > EPSILON24) return false;
77380
+ if (dot - squaredLength > EPSILON26) return false;
76235
77381
  return true;
76236
77382
  };
76237
77383
  var isPointInRing2 = (point6, ring) => {
@@ -76255,7 +77401,7 @@ var isPointInRectPour = (p, pour) => {
76255
77401
  const dy = p.y - center.y;
76256
77402
  const localX = dx * cosR - dy * sinR;
76257
77403
  const localY = dx * sinR + dy * cosR;
76258
- return Math.abs(localX) <= width / 2 + EPSILON24 && Math.abs(localY) <= height / 2 + EPSILON24;
77404
+ return Math.abs(localX) <= width / 2 + EPSILON26 && Math.abs(localY) <= height / 2 + EPSILON26;
76259
77405
  };
76260
77406
  var isPointInBrepPour = (p, pour) => {
76261
77407
  const outerRing = pour.brep_shape.outer_ring.vertices.map((v) => ({
@@ -76289,7 +77435,7 @@ var isSegmentFullyInsideCopperPour = (start, end, pour) => {
76289
77435
  const dx = end.x - start.x;
76290
77436
  const dy = end.y - start.y;
76291
77437
  const length8 = Math.hypot(dx, dy);
76292
- if (length8 <= EPSILON24) return false;
77438
+ if (length8 <= EPSILON26) return false;
76293
77439
  const samples = [0, 0.25, 0.5, 0.75, 1];
76294
77440
  return samples.every(
76295
77441
  (t) => isPointInCopperPour(