@tscircuit/core 0.0.2025 → 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
@@ -6277,10 +6277,10 @@ function addTraceTeardrops(route, contacts) {
6277
6277
  result.push(start);
6278
6278
  continue;
6279
6279
  }
6280
- const atDistance = (distance23) => ({
6280
+ const atDistance = (distance25) => ({
6281
6281
  route_type: "wire",
6282
- x: start.x + (end.x - start.x) * distance23 / length8,
6283
- 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,
6284
6284
  width: start.width,
6285
6285
  layer: start.layer
6286
6286
  });
@@ -6803,11 +6803,11 @@ function Trace_doInitialPcbTraceRender(trace) {
6803
6803
  const closestNetBPort = getClosest(netAPort, netBPorts);
6804
6804
  const netAPortPosition = netAPort._getGlobalPcbPositionBeforeLayout();
6805
6805
  const netBPortPosition = closestNetBPort._getGlobalPcbPositionBeforeLayout();
6806
- const distance23 = Math.sqrt(
6806
+ const distance25 = Math.sqrt(
6807
6807
  (netAPortPosition.x - netBPortPosition.x) ** 2 + (netAPortPosition.y - netBPortPosition.y) ** 2
6808
6808
  );
6809
- if (distance23 < closestDistance) {
6810
- closestDistance = distance23;
6809
+ if (distance25 < closestDistance) {
6810
+ closestDistance = distance25;
6811
6811
  closestPortPair = [netAPort, closestNetBPort];
6812
6812
  }
6813
6813
  }
@@ -13018,7 +13018,7 @@ var createComponentsFromCircuitJson = ({
13018
13018
  const schematicSymbolId = schematicComponent?.schematic_symbol_id;
13019
13019
  const parentSymbol = typeof schematicSymbolId === "string" ? schematicSymbolsByImportedId.get(schematicSymbolId) : void 0;
13020
13020
  if (parentSymbol && schematicComponent?.is_box_with_pins === true && elm.center && elm.side_of_component) {
13021
- const distance23 = elm.distance_from_component_edge;
13021
+ const distance25 = elm.distance_from_component_edge;
13022
13022
  const facingDirection = getFacingDirectionFromSide(
13023
13023
  elm.side_of_component
13024
13024
  );
@@ -13026,8 +13026,8 @@ var createComponentsFromCircuitJson = ({
13026
13026
  const directionVector = getUnitVectorFromDirection2(facingDirection);
13027
13027
  const stemDirection = elm.facing_direction === facingDirection ? 1 : -1;
13028
13028
  const portCenter = {
13029
- x: elm.center.x + directionVector.x * distance23 * stemDirection,
13030
- 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
13031
13031
  };
13032
13032
  const sourcePort = sourcePortsByImportedId.get(elm.source_port_id);
13033
13033
  const aliases = Array.from(
@@ -17796,15 +17796,15 @@ var Capacitor_addPolarityFabricationSymbols = (capacitor) => {
17796
17796
  source_port_id: negatives[0].source_port_id
17797
17797
  });
17798
17798
  if (!positive || !negative) return;
17799
- const distance23 = Math.hypot(negative.x - positive.x, negative.y - positive.y);
17800
- if (distance23 < 1e-6) return;
17799
+ const distance25 = Math.hypot(negative.x - positive.x, negative.y - positive.y);
17800
+ if (distance25 < 1e-6) return;
17801
17801
  const frame = compose7(
17802
17802
  translate5((positive.x + negative.x) / 2, (positive.y + negative.y) / 2),
17803
17803
  rotate4(Math.atan2(negative.y - positive.y, negative.x - positive.x))
17804
17804
  );
17805
17805
  const point6 = (x, y) => applyToPoint26(frame, { x, y });
17806
- const offset = distance23 / 4;
17807
- const halfSize = Math.min(distance23 * 0.12, 0.3);
17806
+ const offset = distance25 / 4;
17807
+ const halfSize = Math.min(distance25 * 0.12, 0.3);
17808
17808
  const routes = [
17809
17809
  [point6(-offset - halfSize, 0), point6(-offset + halfSize, 0)],
17810
17810
  [point6(-offset, -halfSize), point6(-offset, halfSize)],
@@ -19629,7 +19629,7 @@ import {
19629
19629
  groupProps
19630
19630
  } from "@tscircuit/props";
19631
19631
  import {
19632
- distance as distance16
19632
+ distance as distance18
19633
19633
  } from "circuit-json";
19634
19634
  import Debug15 from "debug";
19635
19635
 
@@ -38873,9 +38873,9 @@ function getFreeBoundaryTracks(params) {
38873
38873
  for (const via of vias) {
38874
38874
  if (!via.spanLayers.includes(targetLayer2)) continue;
38875
38875
  const radius = via.diameter / 2 + traceWidth / 2 + clearance;
38876
- const distance23 = Math.abs(via.center[across] - edge);
38877
- if (distance23 >= radius) continue;
38878
- 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);
38879
38879
  blocked.push([via.center[along] - extent, via.center[along] + extent]);
38880
38880
  }
38881
38881
  const gaps = [];
@@ -50566,6 +50566,8 @@ function getPresetAutoroutingConfig(autorouterConfig, platformConfig) {
50566
50566
  };
50567
50567
  case "bus-lanes":
50568
50568
  return { local: true, groupMode: "subcircuit", preset: "bus_lanes" };
50569
+ case "dogbone":
50570
+ return { local: true, groupMode: "subcircuit", preset: "dogbone" };
50569
50571
  case "fanout":
50570
50572
  return {
50571
50573
  local: true,
@@ -50628,11 +50630,1027 @@ function getPresetAutoroutingConfig(autorouterConfig, platformConfig) {
50628
50630
  }
50629
50631
  }
50630
50632
 
50631
- // 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";
50632
51197
  import { BaseSolver as BaseSolver4 } from "@tscircuit/solver-utils";
50633
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
+
50634
51652
  // node_modules/@tscircuit/bus-lanes-solver/lib/geometry.ts
50635
- 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);
50636
51654
  function pointSegmentDistance(p, edge) {
50637
51655
  const [a, b] = edge, dx = b.x - a.x, dy = b.y - a.y, t = Math.max(
50638
51656
  0,
@@ -50643,10 +51661,10 @@ function pointSegmentDistance(p, edge) {
50643
51661
  );
50644
51662
  return Math.hypot(p.x - a.x - t * dx, p.y - a.y - t * dy);
50645
51663
  }
50646
- 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);
50647
51665
  function segmentDistance(ab, cd) {
50648
51666
  const [a, b] = ab, [c, d] = cd;
50649
- 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)
50650
51668
  return 0;
50651
51669
  return Math.min(
50652
51670
  pointSegmentDistance(a, cd),
@@ -50655,10 +51673,10 @@ function segmentDistance(ab, cd) {
50655
51673
  pointSegmentDistance(d, ab)
50656
51674
  );
50657
51675
  }
50658
- 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);
50659
51677
  function simplify(path) {
50660
51678
  return path.filter(
50661
- (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
50662
51680
  );
50663
51681
  }
50664
51682
 
@@ -50702,7 +51720,7 @@ function fixedCopper(input) {
50702
51720
  throw Error("Fixed trace changes layer without a via");
50703
51721
  result.push({ a: p, b: q, radius: p.width / 2, layer: p.layer, owners });
50704
51722
  }
50705
- 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) {
50706
51724
  const wire = p.route_type === "wire" ? p : q;
50707
51725
  result.push({
50708
51726
  a: p,
@@ -50822,7 +51840,7 @@ var VectorScene = class {
50822
51840
  );
50823
51841
  } else {
50824
51842
  const r = c.radius + this.margin + epsilon;
50825
- 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])
50826
51844
  for (const [x, y] of [
50827
51845
  [1, k],
50828
51846
  [k, 1],
@@ -50910,7 +51928,7 @@ var VectorVisibilitySearch = class {
50910
51928
  this.start = start;
50911
51929
  this.end = end;
50912
51930
  this.vertices = [start, end];
50913
- this.open.push({ id: 0, g: 0, f: distance12(start, end) });
51931
+ this.open.push({ id: 0, g: 0, f: distance14(start, end) });
50914
51932
  this.best.set(0, 0);
50915
51933
  }
50916
51934
  scene;
@@ -50970,7 +51988,7 @@ var VectorVisibilitySearch = class {
50970
51988
  const from = this.vertices[cur.id];
50971
51989
  for (let id = 1; id < this.vertices.length; id++) {
50972
51990
  if (id === cur.id) continue;
50973
- const to = this.vertices[id], lower = cur.g + distance12(from, to);
51991
+ const to = this.vertices[id], lower = cur.g + distance14(from, to);
50974
51992
  if (lower >= (this.best.get(id) ?? Infinity) - 1e-9) continue;
50975
51993
  const candidates = connectors(from, to).sort(
50976
51994
  (a, b) => length(a) - length(b)
@@ -50983,7 +52001,7 @@ var VectorVisibilitySearch = class {
50983
52001
  this.open.push({
50984
52002
  id,
50985
52003
  g,
50986
- f: g + distance12(to, this.end),
52004
+ f: g + distance14(to, this.end),
50987
52005
  parent: cur,
50988
52006
  edge: path
50989
52007
  });
@@ -51225,9 +52243,9 @@ function tuneLengths(input, traces, targets) {
51225
52243
  const clearance = input.minTraceToPadEdgeClearance ?? input.defaultObstacleMargin ?? 0.075;
51226
52244
  const returnSpacing = Math.max(width + clearance, 3 * width);
51227
52245
  const pitch = 2 * (returnSpacing + 2 * width);
51228
- 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);
51229
52247
  for (const { i } of segments) {
51230
- 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);
51231
52249
  if (span < 0.01) continue;
51232
52250
  const ux = (b.x - a.x) / span, uy = (b.y - a.y) / span;
51233
52251
  const maximumTeeth = Math.floor(span * 0.9 / pitch);
@@ -51327,7 +52345,7 @@ function tuneLengths(input, traces, targets) {
51327
52345
  function tuningPathIsSelfClear(path, required) {
51328
52346
  const cumulative = [0];
51329
52347
  for (let i = 1; i < path.length; i++)
51330
- cumulative.push(cumulative[i - 1] + distance12(path[i - 1], path[i]));
52348
+ cumulative.push(cumulative[i - 1] + distance14(path[i - 1], path[i]));
51331
52349
  for (let i = 0; i < path.length - 1; i++)
51332
52350
  for (let j = i + 2; j < path.length - 1; j++) {
51333
52351
  if (cumulative[j] - cumulative[i + 1] <= required + 1e-8) continue;
@@ -51365,7 +52383,7 @@ var layerColors = {
51365
52383
  var layerColor = (layer) => layerColors[layer] ?? "#64748b";
51366
52384
 
51367
52385
  // node_modules/@tscircuit/bus-lanes-solver/lib/bus-lanes-solver.ts
51368
- var BusLanesSolver = class extends BaseSolver4 {
52386
+ var BusLanesSolver = class extends BaseSolver8 {
51369
52387
  input;
51370
52388
  options;
51371
52389
  phase = "validate";
@@ -51575,7 +52593,7 @@ var BusLanesSolver = class extends BaseSolver4 {
51575
52593
  const t = this.traces.find((t2) => t2.connection_name === c.name);
51576
52594
  if (!t) throw Error("Missing lane");
51577
52595
  const scene = this.scene(c);
51578
- 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)
51579
52597
  throw Error("Broken lane endpoints");
51580
52598
  for (let i = 1; i < t.route.length; i++) {
51581
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);
@@ -51640,7 +52658,7 @@ var BusLanesSolver = class extends BaseSolver4 {
51640
52658
  });
51641
52659
  continue;
51642
52660
  }
51643
- if (distance12(c.a, c.b) < 1e-9)
52661
+ if (distance14(c.a, c.b) < 1e-9)
51644
52662
  circles.push({
51645
52663
  center: c.a,
51646
52664
  radius: c.radius,
@@ -51801,7 +52819,7 @@ import { CreateFdmEnclosureSolver } from "@tscircuit/create-fdm-enclosure";
51801
52819
  // node_modules/@tscircuit/implicit-copper-pour-solver/lib/implicit-copper-pour-pipeline-solver.ts
51802
52820
  import {
51803
52821
  BasePipelineSolver,
51804
- BaseSolver as BaseSolver5,
52822
+ BaseSolver as BaseSolver9,
51805
52823
  definePipelineStep
51806
52824
  } from "@tscircuit/solver-utils";
51807
52825
 
@@ -51819,7 +52837,7 @@ import { applyToPoint as applyToPoint30, compose as compose11, scale as scale2,
51819
52837
  // node_modules/@tscircuit/implicit-copper-pour-solver/lib/geometry.ts
51820
52838
  import {
51821
52839
  clamp,
51822
- distance as distance13,
52840
+ distance as distance15,
51823
52841
  doBoundsOverlap as doBoundsOverlap2,
51824
52842
  getBoundsFromPoints as getBoundsFromPoints3,
51825
52843
  getUnitVectorFromPointAToB as getUnitVectorFromPointAToB2,
@@ -51868,7 +52886,7 @@ var distanceToPolygon = (point6, polygon) => {
51868
52886
  var distanceToPrimitive = (primitive, px, py) => {
51869
52887
  const point6 = { x: px, y: py };
51870
52888
  if (primitive.kind === "circle") {
51871
- return Math.max(distance13(point6, primitive) - primitive.radius, 0);
52889
+ return Math.max(distance15(point6, primitive) - primitive.radius, 0);
51872
52890
  }
51873
52891
  if (primitive.kind === "segment") {
51874
52892
  return Math.max(
@@ -51919,7 +52937,7 @@ var getClosestPointOnPrimitive = (primitive, point6) => {
51919
52937
  const start = primitive.points[index];
51920
52938
  const end = primitive.points[(index + 1) % primitive.points.length];
51921
52939
  const candidate = pointToSegmentClosestPoint(point6, start, end);
51922
- const candidateDistance = distance13(point6, candidate);
52940
+ const candidateDistance = distance15(point6, candidate);
51923
52941
  if (candidateDistance < bestDistance) {
51924
52942
  bestDistance = candidateDistance;
51925
52943
  closestPoint = candidate;
@@ -52975,7 +53993,7 @@ var prepareCircuitJson = (input) => {
52975
53993
 
52976
53994
  // node_modules/@tscircuit/implicit-copper-pour-solver/lib/simplify-polygon-edges.ts
52977
53995
  import {
52978
- distance as distance14,
53996
+ distance as distance16,
52979
53997
  doSegmentsIntersect,
52980
53998
  onSegment,
52981
53999
  pointToSegmentDistance as pointToSegmentDistance2
@@ -53038,8 +54056,8 @@ var simplifyOpenPath = (points, tolerance) => {
53038
54056
  const point6 = points[index];
53039
54057
  const next = points[index + 1];
53040
54058
  const shortestLeg = Math.min(
53041
- distance14(point6, previous),
53042
- distance14(next, point6)
54059
+ distance16(point6, previous),
54060
+ distance16(next, point6)
53043
54061
  );
53044
54062
  if (repeatsTurnDirection || shortestLeg > tolerance) {
53045
54063
  anchorIndices.push(index);
@@ -53088,8 +54106,8 @@ var normalizePolygon = (polygon) => {
53088
54106
  };
53089
54107
  var isPointStrictlyInsideSegment = (point6, start, end) => {
53090
54108
  if (pointsEqual(point6, start) || pointsEqual(point6, end)) return false;
53091
- const cross4 = crossProduct(start, end, point6);
53092
- if (Math.abs(cross4) > 1e-9) return false;
54109
+ const cross5 = crossProduct(start, end, point6);
54110
+ if (Math.abs(cross5) > 1e-9) return false;
53093
54111
  return onSegment(start, point6, end);
53094
54112
  };
53095
54113
  var splitEdgesAtVertices = (polygon, allVertices) => {
@@ -53441,7 +54459,7 @@ var mergeSolverGraphics = (source, overlay) => {
53441
54459
  };
53442
54460
 
53443
54461
  // node_modules/@tscircuit/implicit-copper-pour-solver/lib/implicit-copper-pour-pipeline-solver.ts
53444
- var PrepareCircuitJsonSolver = class extends BaseSolver5 {
54462
+ var PrepareCircuitJsonSolver = class extends BaseSolver9 {
53445
54463
  constructor(input) {
53446
54464
  super();
53447
54465
  this.input = input;
@@ -53457,7 +54475,7 @@ var PrepareCircuitJsonSolver = class extends BaseSolver5 {
53457
54475
  return this.output;
53458
54476
  }
53459
54477
  };
53460
- var AssignGridCellsSolver = class extends BaseSolver5 {
54478
+ var AssignGridCellsSolver = class extends BaseSolver9 {
53461
54479
  constructor(input) {
53462
54480
  super();
53463
54481
  this.input = input;
@@ -53480,7 +54498,7 @@ var AssignGridCellsSolver = class extends BaseSolver5 {
53480
54498
  return this.output;
53481
54499
  }
53482
54500
  };
53483
- var TracePowerPolygonsSolver = class extends BaseSolver5 {
54501
+ var TracePowerPolygonsSolver = class extends BaseSolver9 {
53484
54502
  constructor(input) {
53485
54503
  super();
53486
54504
  this.input = input;
@@ -53516,7 +54534,7 @@ var TracePowerPolygonsSolver = class extends BaseSolver5 {
53516
54534
  };
53517
54535
  }
53518
54536
  };
53519
- var SimplifyPolygonEdgesSolver = class extends BaseSolver5 {
54537
+ var SimplifyPolygonEdgesSolver = class extends BaseSolver9 {
53520
54538
  constructor(input) {
53521
54539
  super();
53522
54540
  this.input = input;
@@ -53654,13 +54672,13 @@ import { LayoutPipelineSolver } from "@tscircuit/matchpack";
53654
54672
  import { SchematicTracePipelineSolver } from "@tscircuit/schematic-trace-solver";
53655
54673
 
53656
54674
  // node_modules/@tscircuit/via-stitch-solver/lib/solvers/ViaStitchSolver.ts
53657
- import { BaseSolver as BaseSolver6 } from "@tscircuit/solver-utils";
54675
+ import { BaseSolver as BaseSolver10 } from "@tscircuit/solver-utils";
53658
54676
 
53659
54677
  // node_modules/@tscircuit/via-stitch-solver/lib/geometry/brep-point-containment.ts
53660
54678
  var POINT_EPSILON = 1e-8;
53661
54679
  var isPointOnSegment = (point6, start, end) => {
53662
- const cross4 = (point6.y - start.y) * (end.x - start.x) - (point6.x - start.x) * (end.y - start.y);
53663
- 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;
53664
54682
  const dot = (point6.x - start.x) * (end.x - start.x) + (point6.y - start.y) * (end.y - start.y);
53665
54683
  if (dot < -POINT_EPSILON) return false;
53666
54684
  const squaredLength = (end.x - start.x) ** 2 + (end.y - start.y) ** 2;
@@ -53981,7 +54999,7 @@ var getGridCoordinates = ({
53981
54999
  }
53982
55000
  return coordinates;
53983
55001
  };
53984
- var ViaStitchSolver = class extends BaseSolver6 {
55002
+ var ViaStitchSolver = class extends BaseSolver10 {
53985
55003
  constructor(input) {
53986
55004
  super();
53987
55005
  this.input = input;
@@ -54128,6 +55146,7 @@ var ViaStitchSolver = class extends BaseSolver6 {
54128
55146
  // lib/solvers.ts
54129
55147
  import { PackSolver2 } from "calculate-packing";
54130
55148
  var SOLVERS = {
55149
+ DogboneFanoutSolver,
54131
55150
  BusLanesSolver,
54132
55151
  PackSolver2,
54133
55152
  LayoutPipelineSolver,
@@ -54591,6 +55610,27 @@ var localAutorouterStrategies = /* @__PURE__ */ new Map([
54591
55610
  createFanoutAutorouterStrategy("single_layer_fanout")
54592
55611
  ],
54593
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
+ ],
54594
55634
  [
54595
55635
  "bus_lanes",
54596
55636
  {
@@ -61067,19 +62107,19 @@ import Debug12 from "debug";
61067
62107
  import { isValidElement as isValidElement2 } from "react";
61068
62108
 
61069
62109
  // lib/components/primitive-components/Group/Group_doInitialSchematicTraceRender/segment-intersection.ts
61070
- var cross3 = (ax, ay, bx, by) => {
62110
+ var cross4 = (ax, ay, bx, by) => {
61071
62111
  return ax * by - ay * bx;
61072
62112
  };
61073
62113
  function segmentIntersection(p1, p2, q1, q2, tol) {
61074
62114
  const r = { x: p2.x - p1.x, y: p2.y - p1.y };
61075
62115
  const s = { x: q2.x - q1.x, y: q2.y - q1.y };
61076
- const rxs = cross3(r.x, r.y, s.x, s.y);
62116
+ const rxs = cross4(r.x, r.y, s.x, s.y);
61077
62117
  const q_p = { x: q1.x - p1.x, y: q1.y - p1.y };
61078
- 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);
61079
62119
  if (Math.abs(rxs) < tol && Math.abs(q_pxr) < tol) return null;
61080
62120
  if (Math.abs(rxs) < tol && Math.abs(q_pxr) >= tol) return null;
61081
- const t = cross3(q_p.x, q_p.y, s.x, s.y) / rxs;
61082
- 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;
61083
62123
  if (t < -tol || t > 1 + tol || u < -tol || u > 1 + tol) return null;
61084
62124
  return { x: p1.x + t * r.x, y: p1.y + t * r.y };
61085
62125
  }
@@ -61272,8 +62312,8 @@ function isParallel(e1, e2, tol = TOL2) {
61272
62312
  const L1 = Math.hypot(v1.x, v1.y);
61273
62313
  const L2 = Math.hypot(v2.x, v2.y);
61274
62314
  if (L1 < tol || L2 < tol) return true;
61275
- const cross4 = v1.x * v2.y - v1.y * v2.x;
61276
- 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;
61277
62317
  }
61278
62318
  function incidentEdgesAtPoint(trace, p, tol = TOL2) {
61279
62319
  return trace.edges.filter(
@@ -64540,6 +65580,16 @@ function Group_getRoutingPhasePlans(group) {
64540
65580
  nets: nets.filter((net) => breakoutByNet.get(net) === breakout),
64541
65581
  traces: breakoutTraces
64542
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
+ }
64543
65593
  if (breakout._parsedProps.pcbTracePaths !== void 0) {
64544
65594
  breakoutPlans.push({
64545
65595
  ...breakoutPlan,
@@ -64612,7 +65662,7 @@ function Group_getRoutingPhasePlans(group) {
64612
65662
  var package_default = {
64613
65663
  name: "@tscircuit/core",
64614
65664
  type: "module",
64615
- version: "0.0.2024",
65665
+ version: "0.0.2025",
64616
65666
  types: "dist/index.d.ts",
64617
65667
  main: "dist/index.js",
64618
65668
  module: "dist/index.js",
@@ -64653,6 +65703,7 @@ var package_default = {
64653
65703
  "@tscircuit/common": "^0.0.20",
64654
65704
  "@tscircuit/copper-pour-solver": "^0.0.62",
64655
65705
  "@tscircuit/create-fdm-enclosure": "0.0.4",
65706
+ "@tscircuit/dogbone-solver": "https://jscdn.tscircuit.com/@tscircuit/dogbone-solver/0.0.1.tgz",
64656
65707
  "@tscircuit/fanout-solver": "0.0.78",
64657
65708
  "@tscircuit/flex-utils": "https://jscdn.tscircuit.com/@tscircuit/flex-utils/0.0.5.tgz",
64658
65709
  "@tscircuit/footprinter": "^0.0.426",
@@ -65080,7 +66131,6 @@ function Group_syncFanoutExitsWithGlobalConnections({
65080
66131
  ) ? [{ globalConnection, pointIndex, previousPoint }] : []
65081
66132
  )
65082
66133
  );
65083
- if (globalPointMatches.length === 0) continue;
65084
66134
  const changedPointIndex = outputConnection.pointsToConnect.findIndex(
65085
66135
  (outputPoint, pointIndex) => {
65086
66136
  const inputPoint = inputConnection.pointsToConnect[pointIndex];
@@ -65089,6 +66139,14 @@ function Group_syncFanoutExitsWithGlobalConnections({
65089
66139
  );
65090
66140
  if (changedPointIndex < 0) continue;
65091
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
+ }
65092
66150
  for (const {
65093
66151
  globalConnection,
65094
66152
  pointIndex,
@@ -65349,7 +66407,7 @@ function getAccumulatedPcbTracesWithStageOutputReplacements({
65349
66407
  }
65350
66408
 
65351
66409
  // lib/components/primitive-components/Group/get-source-trace-id-for-routed-trace.ts
65352
- import { distance as distance15, pointToSegmentDistance as pointToSegmentDistance3 } from "@tscircuit/math-utils";
66410
+ import { distance as distance17, pointToSegmentDistance as pointToSegmentDistance3 } from "@tscircuit/math-utils";
65353
66411
  import { ConnectivityMap as ConnectivityMap5 } from "circuit-json-to-connectivity-map";
65354
66412
  var POINT_EPSILON2 = 1e-6;
65355
66413
  function isPointOnWireSegment({
@@ -65371,7 +66429,7 @@ function isPointInSmtPad({
65371
66429
  return Math.abs(point6.x - pad.x) <= pad.width / 2 + traceWidth / 2 && Math.abs(point6.y - pad.y) <= pad.height / 2 + traceWidth / 2;
65372
66430
  }
65373
66431
  if (pad.shape === "circle") {
65374
- return distance15(point6, pad) <= pad.radius + traceWidth / 2;
66432
+ return distance17(point6, pad) <= pad.radius + traceWidth / 2;
65375
66433
  }
65376
66434
  return false;
65377
66435
  }
@@ -65380,7 +66438,7 @@ function isPointInPlatedHole({
65380
66438
  hole,
65381
66439
  traceWidth = 0
65382
66440
  }) {
65383
- 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;
65384
66442
  }
65385
66443
  function getEndpointSourcePortIdsFromGeometry(db, trace) {
65386
66444
  const sourcePortIds = /* @__PURE__ */ new Set();
@@ -65393,7 +66451,7 @@ function getEndpointSourcePortIdsFromGeometry(db, trace) {
65393
66451
  if (!pcbPort.source_port_id) continue;
65394
66452
  if (endpoint.layer && pcbPort.layers && !pcbPort.layers.includes(endpoint.layer))
65395
66453
  continue;
65396
- if (distance15(endpoint, pcbPort) <= 0.01) {
66454
+ if (distance17(endpoint, pcbPort) <= 0.01) {
65397
66455
  sourcePortIds.add(pcbPort.source_port_id);
65398
66456
  }
65399
66457
  }
@@ -66077,8 +67135,8 @@ var Group5 = class extends NormalComponent {
66077
67135
  const groupProps2 = props;
66078
67136
  const hasOutline = groupProps2.outline && groupProps2.outline.length > 0;
66079
67137
  const numericOutline = hasOutline ? groupProps2.outline.map((point6) => ({
66080
- x: distance16.parse(point6.x),
66081
- y: distance16.parse(point6.y)
67138
+ x: distance18.parse(point6.x),
67139
+ y: distance18.parse(point6.y)
66082
67140
  })) : void 0;
66083
67141
  const ctx = this.props;
66084
67142
  const anchorPosition = this._getGlobalPcbPositionBeforeLayout();
@@ -66116,8 +67174,8 @@ var Group5 = class extends NormalComponent {
66116
67174
  const hasExplicitPositioning = this._parsedProps.pcbX !== void 0 || this._parsedProps.pcbY !== void 0;
66117
67175
  if (hasOutline) {
66118
67176
  const numericOutline = props.outline.map((point6) => ({
66119
- x: distance16.parse(point6.x),
66120
- y: distance16.parse(point6.y)
67177
+ x: distance18.parse(point6.x),
67178
+ y: distance18.parse(point6.y)
66121
67179
  }));
66122
67180
  const outlineBounds = getBoundsFromPoints9(numericOutline);
66123
67181
  if (!outlineBounds) return;
@@ -66518,7 +67576,9 @@ var Group5 = class extends NormalComponent {
66518
67576
  routingPhaseDebugLabels.set(plan, `${label} ${ordinal}`);
66519
67577
  }
66520
67578
  const hasFanoutStage = routingStages.some(
66521
- ({ autorouterConfig: autorouterConfig2 }) => ["fanout", "single_layer_fanout"].includes(autorouterConfig2.preset ?? "")
67579
+ ({ autorouterConfig: autorouterConfig2 }) => ["fanout", "single_layer_fanout", "dogbone"].includes(
67580
+ autorouterConfig2.preset ?? ""
67581
+ )
66522
67582
  );
66523
67583
  const fanoutPourNetMap = hasFanoutStage ? Group_getFanoutPourNetMap(this, routingPhasePlans) : void 0;
66524
67584
  let { simpleRouteJson: baseSimpleRouteJson } = getSimpleRouteJsonFromCircuitJson({
@@ -66941,6 +68001,11 @@ var Group5 = class extends NormalComponent {
66941
68001
  } else {
66942
68002
  autorouter = localAutorouterStrategy.create({
66943
68003
  simpleRouteJson,
68004
+ onSolverEnded: (event) => this.root?.emit("solver:ended", {
68005
+ ...event,
68006
+ type: "solver:ended",
68007
+ componentName: this.getString()
68008
+ }),
66944
68009
  commonAutorouterOptions,
66945
68010
  busFanoutDirections: routingPhasePlan.busFanoutDirections,
66946
68011
  fanoutBounds: routingPhasePlan.fanoutBounds,
@@ -66996,7 +68061,7 @@ var Group5 = class extends NormalComponent {
66996
68061
  traces = await routingPromise;
66997
68062
  }
66998
68063
  let transformedSimpleRouteJson = precomputedOutputSimpleRouteJson ?? autorouter?.getOutputSimpleRouteJson?.();
66999
- if (transformedSimpleRouteJson && !usesPreviousStageOutput && ["fanout", "single_layer_fanout"].includes(
68064
+ if (transformedSimpleRouteJson && !usesPreviousStageOutput && ["fanout", "single_layer_fanout", "dogbone"].includes(
67000
68065
  phaseAutorouterConfig.preset ?? ""
67001
68066
  ) && routingPhasePlan.routingPcbGroupId) {
67002
68067
  const synchronizedFanout = Group_syncFanoutExitsWithGlobalConnections(
@@ -67016,10 +68081,14 @@ var Group5 = class extends NormalComponent {
67016
68081
  if (!breakoutPoint) continue;
67017
68082
  db.pcb_breakout_point.update(breakoutPoint.pcb_breakout_point_id, {
67018
68083
  x: synchronizedPoint.fanoutExitPoint.x,
67019
- y: synchronizedPoint.fanoutExitPoint.y
68084
+ y: synchronizedPoint.fanoutExitPoint.y,
68085
+ layer: synchronizedPoint.fanoutExitPoint.layer
67020
68086
  });
67021
68087
  }
67022
68088
  }
68089
+ if (localAutorouterStrategy.preserveOutputTraces) {
68090
+ for (const trace of traces) fixedTraceIds.add(trace.pcb_trace_id);
68091
+ }
67023
68092
  let stageOutputTraces = traces;
67024
68093
  if (transformedSimpleRouteJson?.traces) {
67025
68094
  stageOutputTraces = transformedSimpleRouteJson.traces;
@@ -67056,7 +68125,7 @@ var Group5 = class extends NormalComponent {
67056
68125
  subcircuit: this,
67057
68126
  input: simpleRouteJson,
67058
68127
  traces,
67059
- isFanout: ["fanout", "single_layer_fanout"].includes(
68128
+ isFanout: ["fanout", "single_layer_fanout", "dogbone"].includes(
67060
68129
  phaseAutorouterConfig.preset ?? ""
67061
68130
  )
67062
68131
  });
@@ -67989,8 +69058,8 @@ var Led_addPolarityFabricationSymbol = (led) => {
67989
69058
  const a = db.pcb_port.getWhere({ source_port_id: anode.source_port_id });
67990
69059
  const k = db.pcb_port.getWhere({ source_port_id: cathode.source_port_id });
67991
69060
  if (!a || !k) return;
67992
- const distance23 = Math.hypot(k.x - a.x, k.y - a.y);
67993
- if (distance23 < 1e-6) return;
69061
+ const distance25 = Math.hypot(k.x - a.x, k.y - a.y);
69062
+ if (distance25 < 1e-6) return;
67994
69063
  const angle = Math.atan2(k.y - a.y, k.x - a.x);
67995
69064
  const frame = compose15(
67996
69065
  translate15((a.x + k.x) / 2, (a.y + k.y) / 2),
@@ -68004,10 +69073,10 @@ var Led_addPolarityFabricationSymbol = (led) => {
68004
69073
  };
68005
69074
  const notes = [];
68006
69075
  generatedNotes.set(led, notes);
68007
- const halfLength = Math.min(distance23 * 0.2, 0.4);
69076
+ const halfLength = Math.min(distance25 * 0.2, 0.4);
68008
69077
  const halfHeight = halfLength * 0.75;
68009
69078
  const routes = [
68010
- [point6(-distance23 / 2, 0), point6(-halfLength, 0)],
69079
+ [point6(-distance25 / 2, 0), point6(-halfLength, 0)],
68011
69080
  [
68012
69081
  point6(-halfLength, -halfHeight),
68013
69082
  point6(halfLength, 0),
@@ -68015,7 +69084,7 @@ var Led_addPolarityFabricationSymbol = (led) => {
68015
69084
  point6(-halfLength, -halfHeight)
68016
69085
  ],
68017
69086
  [point6(halfLength, -halfHeight), point6(halfLength, halfHeight)],
68018
- [point6(halfLength, 0), point6(distance23 / 2, 0)]
69087
+ [point6(halfLength, 0), point6(distance25 / 2, 0)]
68019
69088
  ];
68020
69089
  for (const route of routes) {
68021
69090
  notes.push(
@@ -69949,7 +71018,7 @@ var Board_doInitialPcbPlacementDesignRuleChecks = (board) => {
69949
71018
 
69950
71019
  // lib/components/normal-components/Board/board-castellated-hole.ts
69951
71020
  import {
69952
- distance as distance17
71021
+ distance as distance19
69953
71022
  } from "circuit-json";
69954
71023
  var CASTELLATED_HOLE_ENDPOINT_TOLERANCE_MM = 1e-6;
69955
71024
  var getConnectionTargets2 = (connectsTo) => {
@@ -69970,8 +71039,8 @@ var BoardCastellatedHole = class _BoardCastellatedHole extends PlatedHole {
69970
71039
  constructor(outlinePoint, outlinePointIndex) {
69971
71040
  const name = `castellated_hole_${outlinePointIndex + 1}`;
69972
71041
  const connectionTargets = getConnectionTargets2(outlinePoint.connectsTo);
69973
- const holeDiameter = distance17.parse(outlinePoint.holeDiameter);
69974
- const padDiameter = distance17.parse(outlinePoint.padDiameter);
71042
+ const holeDiameter = distance19.parse(outlinePoint.holeDiameter);
71043
+ const padDiameter = distance19.parse(outlinePoint.padDiameter);
69975
71044
  super({
69976
71045
  shape: "circle",
69977
71046
  holeDiameter,
@@ -70721,11 +71790,11 @@ var MountedBoard = class extends Subcircuit {
70721
71790
 
70722
71791
  // lib/components/normal-components/Panel.ts
70723
71792
  import { panelProps } from "@tscircuit/props";
70724
- import { distance as distance21 } from "circuit-json";
71793
+ import { distance as distance23 } from "circuit-json";
70725
71794
 
70726
71795
  // lib/components/normal-components/Subpanel.ts
70727
71796
  import { subpanelProps } from "@tscircuit/props";
70728
- import { distance as distance20 } from "circuit-json";
71797
+ import { distance as distance22 } from "circuit-json";
70729
71798
  import { compose as compose17, identity as identity9, translate as translate17 } from "transformation-matrix";
70730
71799
 
70731
71800
  // lib/utils/panels/generate-panel-tabs-and-mouse-bites.ts
@@ -70989,15 +72058,15 @@ function generatePanelOutlineRoutingCutouts(boards, options) {
70989
72058
  }
70990
72059
 
70991
72060
  // lib/utils/panels/pack-into-grid.ts
70992
- import { distance as distance19 } from "circuit-json";
72061
+ import { distance as distance21 } from "circuit-json";
70993
72062
 
70994
72063
  // lib/utils/panels/get-board-dimensions-from-props.ts
70995
72064
  import { getBoundsFromPoints as getBoundsFromPoints11 } from "@tscircuit/math-utils";
70996
- import { distance as distance18 } from "circuit-json";
72065
+ import { distance as distance20 } from "circuit-json";
70997
72066
  var getBoardDimensionsFromProps = (board) => {
70998
72067
  const props = board._parsedProps;
70999
- let width = props.width != null ? distance18.parse(props.width) : void 0;
71000
- 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;
71001
72070
  if ((width === void 0 || height === void 0) && props.outline?.length) {
71002
72071
  const outlineBounds = getBoundsFromPoints11(props.outline);
71003
72072
  if (outlineBounds) {
@@ -71041,8 +72110,8 @@ function packIntoGrid({
71041
72110
  }
71042
72111
  let cols;
71043
72112
  let rows;
71044
- const minCellWidth = cellWidth ? distance19.parse(cellWidth) : 0;
71045
- const minCellHeight = cellHeight ? distance19.parse(cellHeight) : 0;
72113
+ const minCellWidth = cellWidth ? distance21.parse(cellWidth) : 0;
72114
+ const minCellHeight = cellHeight ? distance21.parse(cellHeight) : 0;
71046
72115
  if (col !== void 0) {
71047
72116
  cols = col;
71048
72117
  rows = row ?? Math.ceil(itemsWithDims.length / cols);
@@ -71141,8 +72210,8 @@ function getItemDimensions(item, db) {
71141
72210
  const props = subpanel._parsedProps;
71142
72211
  if (props.width !== void 0 && props.height !== void 0) {
71143
72212
  return {
71144
- width: distance19.parse(props.width),
71145
- height: distance19.parse(props.height)
72213
+ width: distance21.parse(props.width),
72214
+ height: distance21.parse(props.height)
71146
72215
  };
71147
72216
  }
71148
72217
  const directBoards = subpanel._getDirectBoardChildren();
@@ -71169,7 +72238,7 @@ function getItemDimensions(item, db) {
71169
72238
  return getBoardDimensionsFromProps(directBoards[0]);
71170
72239
  }
71171
72240
  if (subpanel._cachedGridWidth > 0 && subpanel._cachedGridHeight > 0) {
71172
- const edgePadding = distance19.parse(props.edgePadding ?? 5);
72241
+ const edgePadding = distance21.parse(props.edgePadding ?? 5);
71173
72242
  return {
71174
72243
  width: subpanel._cachedGridWidth + edgePadding * 2,
71175
72244
  height: subpanel._cachedGridHeight + edgePadding * 2
@@ -71327,17 +72396,17 @@ var Subpanel = class _Subpanel extends Group5 {
71327
72396
  edgePaddingTop: edgePaddingTopProp,
71328
72397
  edgePaddingBottom: edgePaddingBottomProp
71329
72398
  } = this._parsedProps;
71330
- const edgePadding = distance20.parse(edgePaddingProp ?? 5);
71331
- const edgePaddingLeft = distance20.parse(edgePaddingLeftProp ?? edgePadding);
71332
- const edgePaddingRight = distance20.parse(
72399
+ const edgePadding = distance22.parse(edgePaddingProp ?? 5);
72400
+ const edgePaddingLeft = distance22.parse(edgePaddingLeftProp ?? edgePadding);
72401
+ const edgePaddingRight = distance22.parse(
71333
72402
  edgePaddingRightProp ?? edgePadding
71334
72403
  );
71335
- const edgePaddingTop = distance20.parse(edgePaddingTopProp ?? edgePadding);
71336
- const edgePaddingBottom = distance20.parse(
72404
+ const edgePaddingTop = distance22.parse(edgePaddingTopProp ?? edgePadding);
72405
+ const edgePaddingBottom = distance22.parse(
71337
72406
  edgePaddingBottomProp ?? edgePadding
71338
72407
  );
71339
- const panelWidth = distance20.parse(this._parsedProps.width);
71340
- const panelHeight = distance20.parse(this._parsedProps.height);
72408
+ const panelWidth = distance22.parse(this._parsedProps.width);
72409
+ const panelHeight = distance22.parse(this._parsedProps.height);
71341
72410
  availablePanelWidth = panelWidth - edgePaddingLeft - edgePaddingRight;
71342
72411
  availablePanelHeight = panelHeight - edgePaddingTop - edgePaddingBottom;
71343
72412
  }
@@ -71412,8 +72481,8 @@ var Subpanel = class _Subpanel extends Group5 {
71412
72481
  if (!this.pcb_group_id) return;
71413
72482
  if (hasExplicitWidth && hasExplicitHeight) {
71414
72483
  db.pcb_group.update(this.pcb_group_id, {
71415
- width: distance20.parse(this._parsedProps.width),
71416
- height: distance20.parse(this._parsedProps.height)
72484
+ width: distance22.parse(this._parsedProps.width),
72485
+ height: distance22.parse(this._parsedProps.height)
71417
72486
  });
71418
72487
  } else if (gridWidth > 0 || gridHeight > 0) {
71419
72488
  const {
@@ -71423,18 +72492,18 @@ var Subpanel = class _Subpanel extends Group5 {
71423
72492
  edgePaddingTop: edgePaddingTopProp,
71424
72493
  edgePaddingBottom: edgePaddingBottomProp
71425
72494
  } = this._parsedProps;
71426
- const edgePadding = distance20.parse(edgePaddingProp ?? 5);
71427
- const edgePaddingLeft = distance20.parse(edgePaddingLeftProp ?? edgePadding);
71428
- const edgePaddingRight = distance20.parse(
72495
+ const edgePadding = distance22.parse(edgePaddingProp ?? 5);
72496
+ const edgePaddingLeft = distance22.parse(edgePaddingLeftProp ?? edgePadding);
72497
+ const edgePaddingRight = distance22.parse(
71429
72498
  edgePaddingRightProp ?? edgePadding
71430
72499
  );
71431
- const edgePaddingTop = distance20.parse(edgePaddingTopProp ?? edgePadding);
71432
- const edgePaddingBottom = distance20.parse(
72500
+ const edgePaddingTop = distance22.parse(edgePaddingTopProp ?? edgePadding);
72501
+ const edgePaddingBottom = distance22.parse(
71433
72502
  edgePaddingBottomProp ?? edgePadding
71434
72503
  );
71435
72504
  db.pcb_group.update(this.pcb_group_id, {
71436
- width: hasExplicitWidth ? distance20.parse(this._parsedProps.width) : gridWidth + edgePaddingLeft + edgePaddingRight,
71437
- 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
71438
72507
  });
71439
72508
  }
71440
72509
  }
@@ -71522,8 +72591,8 @@ var Panel = class extends Subpanel {
71522
72591
  const { db } = this.root;
71523
72592
  const props = this._parsedProps;
71524
72593
  const inserted = db.pcb_panel.insert({
71525
- width: props.width !== void 0 ? distance21.parse(props.width) : 0,
71526
- 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,
71527
72596
  thickness: 1.6,
71528
72597
  center: this._getGlobalPcbPositionBeforeLayout(),
71529
72598
  covered_with_solder_mask: !(props.noSolderMask ?? false)
@@ -71542,8 +72611,8 @@ var Panel = class extends Subpanel {
71542
72611
  if (!this.pcb_panel_id) return;
71543
72612
  if (hasExplicitWidth && hasExplicitHeight) {
71544
72613
  db.pcb_panel.update(this.pcb_panel_id, {
71545
- width: distance21.parse(this._parsedProps.width),
71546
- height: distance21.parse(this._parsedProps.height)
72614
+ width: distance23.parse(this._parsedProps.width),
72615
+ height: distance23.parse(this._parsedProps.height)
71547
72616
  });
71548
72617
  } else if (gridWidth > 0 || gridHeight > 0) {
71549
72618
  const {
@@ -71553,18 +72622,18 @@ var Panel = class extends Subpanel {
71553
72622
  edgePaddingTop: edgePaddingTopProp,
71554
72623
  edgePaddingBottom: edgePaddingBottomProp
71555
72624
  } = this._parsedProps;
71556
- const edgePadding = distance21.parse(edgePaddingProp ?? 5);
71557
- const edgePaddingLeft = distance21.parse(edgePaddingLeftProp ?? edgePadding);
71558
- const edgePaddingRight = distance21.parse(
72625
+ const edgePadding = distance23.parse(edgePaddingProp ?? 5);
72626
+ const edgePaddingLeft = distance23.parse(edgePaddingLeftProp ?? edgePadding);
72627
+ const edgePaddingRight = distance23.parse(
71559
72628
  edgePaddingRightProp ?? edgePadding
71560
72629
  );
71561
- const edgePaddingTop = distance21.parse(edgePaddingTopProp ?? edgePadding);
71562
- const edgePaddingBottom = distance21.parse(
72630
+ const edgePaddingTop = distance23.parse(edgePaddingTopProp ?? edgePadding);
72631
+ const edgePaddingBottom = distance23.parse(
71563
72632
  edgePaddingBottomProp ?? edgePadding
71564
72633
  );
71565
72634
  db.pcb_panel.update(this.pcb_panel_id, {
71566
- width: hasExplicitWidth ? distance21.parse(this._parsedProps.width) : gridWidth + edgePaddingLeft + edgePaddingRight,
71567
- 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
71568
72637
  });
71569
72638
  }
71570
72639
  }
@@ -71575,8 +72644,8 @@ var Panel = class extends Subpanel {
71575
72644
  const props = this._parsedProps;
71576
72645
  const currentPanel = db.pcb_panel.get(this.pcb_panel_id);
71577
72646
  db.pcb_panel.update(this.pcb_panel_id, {
71578
- width: props.width !== void 0 ? distance21.parse(props.width) : currentPanel?.width,
71579
- 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,
71580
72649
  center: this._getGlobalPcbPositionBeforeLayout(),
71581
72650
  covered_with_solder_mask: !(props.noSolderMask ?? false)
71582
72651
  });
@@ -73398,7 +74467,7 @@ var PcbNoteDimension = class extends PrimitiveComponent2 {
73398
74467
  import { breakoutProps } from "@tscircuit/props";
73399
74468
 
73400
74469
  // lib/components/primitive-components/AutoplacedBreakoutPoint.ts
73401
- import { layer_ref as layer_ref3 } from "circuit-json";
74470
+ import { layer_ref as layer_ref4 } from "circuit-json";
73402
74471
  var AutoplacedBreakoutPoint = class extends BaseBreakoutPoint {
73403
74472
  get config() {
73404
74473
  return {
@@ -73412,7 +74481,7 @@ var AutoplacedBreakoutPoint = class extends BaseBreakoutPoint {
73412
74481
  position
73413
74482
  }) {
73414
74483
  this.matchedSourceTraceId = sourceTraceId;
73415
- this._renderPcbBreakoutPoint({ layer: layer_ref3.parse(layer) });
74484
+ this._renderPcbBreakoutPoint({ layer: layer_ref4.parse(layer) });
73416
74485
  this._setPositionFromLayout(position);
73417
74486
  }
73418
74487
  };
@@ -74124,7 +75193,7 @@ var validateWindingBreakoutInput = (input) => {
74124
75193
  };
74125
75194
 
74126
75195
  // node_modules/@tscircuit/winding-breakout-point-solver/lib/solvers/GatePlacementSolver.ts
74127
- import { BaseSolver as BaseSolver7 } from "@tscircuit/solver-utils";
75196
+ import { BaseSolver as BaseSolver11 } from "@tscircuit/solver-utils";
74128
75197
 
74129
75198
  // node_modules/@tscircuit/winding-breakout-point-solver/lib/gate-placement/place-breakout-gates.ts
74130
75199
  var GEOMETRY_EPSILON = 1e-9;
@@ -74654,7 +75723,7 @@ var getSharedGateSlotCount = (points) => new Set(
74654
75723
  (point6) => `${point6.regionId}:${point6.x.toFixed(9)}:${point6.y.toFixed(9)}`
74655
75724
  )
74656
75725
  ).size;
74657
- var GatePlacementSolver = class extends BaseSolver7 {
75726
+ var GatePlacementSolver = class extends BaseSolver11 {
74658
75727
  constructor(params) {
74659
75728
  super();
74660
75729
  this.params = params;
@@ -74793,7 +75862,7 @@ var GatePlacementSolver = class extends BaseSolver7 {
74793
75862
  };
74794
75863
 
74795
75864
  // node_modules/@tscircuit/winding-breakout-point-solver/lib/solvers/ReferenceOrderingSolver.ts
74796
- import { BaseSolver as BaseSolver8 } from "@tscircuit/solver-utils";
75865
+ import { BaseSolver as BaseSolver12 } from "@tscircuit/solver-utils";
74797
75866
 
74798
75867
  // node_modules/@tscircuit/winding-breakout-point-solver/lib/ordering/get-natural-winding-order.ts
74799
75868
  var getNaturalWindingOrder = (region, connections) => connections.map((connection) => {
@@ -74860,7 +75929,7 @@ var getReferenceWindingOrder = (naturalOrder, atomicGroups, buses) => {
74860
75929
  };
74861
75930
 
74862
75931
  // node_modules/@tscircuit/winding-breakout-point-solver/lib/solvers/ReferenceOrderingSolver.ts
74863
- var ReferenceOrderingSolver = class extends BaseSolver8 {
75932
+ var ReferenceOrderingSolver = class extends BaseSolver12 {
74864
75933
  constructor(params) {
74865
75934
  super();
74866
75935
  this.params = params;
@@ -75912,6 +76981,10 @@ var solveImplicitBreakoutPoints = (breakout, solverFn) => {
75912
76981
 
75913
76982
  // lib/components/primitive-components/Breakout/Breakout.ts
75914
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
+ }
75915
76988
  get isRoutingDirective() {
75916
76989
  return true;
75917
76990
  }
@@ -75970,7 +77043,7 @@ var Breakout = class extends Group5 {
75970
77043
  for (const port of result.ports) {
75971
77044
  const isInside = breakoutPortSet.has(port);
75972
77045
  const hasOutsidePort = result.ports.some((p) => !breakoutPortSet.has(p));
75973
- if (!isInside || !hasOutsidePort) continue;
77046
+ if (!isInside || !hasOutsidePort && !this.isDogboneFanout) continue;
75974
77047
  if (manuallyMappedPorts.has(port)) continue;
75975
77048
  if (autoPlacedPorts.has(port)) continue;
75976
77049
  autoPlacedPorts.add(port);
@@ -75983,6 +77056,20 @@ var Breakout = class extends Group5 {
75983
77056
  }
75984
77057
  doInitialPcbAutoplaceBreakoutPoints() {
75985
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
+ }
75986
77073
  const ownAutorouter = this.props.autorouter;
75987
77074
  const inheritedAutorouter = ownAutorouter === void 0 ? this.parent?.getInheritedProperty("autorouter") : void 0;
75988
77075
  const implicitBreakoutPointSolverFn = typeof ownAutorouter === "object" ? ownAutorouter.implicitBreakoutPointSolverFn : typeof inheritedAutorouter === "object" ? inheritedAutorouter.implicitBreakoutPointSolverFn : void 0;
@@ -76205,7 +77292,7 @@ var SilkscreenGraphic = class extends PrimitiveComponent2 {
76205
77292
 
76206
77293
  // lib/components/primitive-components/Fiducial.ts
76207
77294
  import "zod";
76208
- import { distance as distance22 } from "circuit-json";
77295
+ import { distance as distance24 } from "circuit-json";
76209
77296
  import { fiducialProps as fiducialProps2 } from "@tscircuit/props";
76210
77297
  var Fiducial = class extends PrimitiveComponent2 {
76211
77298
  pcb_smtpad_id = null;
@@ -76230,15 +77317,15 @@ var Fiducial = class extends PrimitiveComponent2 {
76230
77317
  shape: "circle",
76231
77318
  x: position.x,
76232
77319
  y: position.y,
76233
- radius: distance22.parse(props.padDiameter) / 2,
76234
- 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,
76235
77322
  is_covered_with_solder_mask: true
76236
77323
  });
76237
77324
  this.pcb_smtpad_id = pcb_smtpad.pcb_smtpad_id;
76238
77325
  }
76239
77326
  getPcbSize() {
76240
77327
  const { _parsedProps: props } = this;
76241
- const d = distance22.parse(props.padDiameter);
77328
+ const d = distance24.parse(props.padDiameter);
76242
77329
  return { width: d, height: d };
76243
77330
  }
76244
77331
  _setPositionFromLayout(newCenter) {
@@ -76282,15 +77369,15 @@ import {
76282
77369
  } from "@tscircuit/copper-pour-solver";
76283
77370
 
76284
77371
  // lib/components/primitive-components/CopperPour/utils/mark-trace-segments-inside-copper-pour.ts
76285
- var EPSILON24 = 1e-9;
77372
+ var EPSILON26 = 1e-9;
76286
77373
  var isWireRoutePoint = (routePoint) => routePoint.route_type === "wire";
76287
77374
  var isPointOnSegment2 = (p, a, b) => {
76288
- const cross4 = (p.y - a.y) * (b.x - a.x) - (p.x - a.x) * (b.y - a.y);
76289
- 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;
76290
77377
  const dot = (p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y);
76291
- if (dot < -EPSILON24) return false;
77378
+ if (dot < -EPSILON26) return false;
76292
77379
  const squaredLength = (b.x - a.x) ** 2 + (b.y - a.y) ** 2;
76293
- if (dot - squaredLength > EPSILON24) return false;
77380
+ if (dot - squaredLength > EPSILON26) return false;
76294
77381
  return true;
76295
77382
  };
76296
77383
  var isPointInRing2 = (point6, ring) => {
@@ -76314,7 +77401,7 @@ var isPointInRectPour = (p, pour) => {
76314
77401
  const dy = p.y - center.y;
76315
77402
  const localX = dx * cosR - dy * sinR;
76316
77403
  const localY = dx * sinR + dy * cosR;
76317
- 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;
76318
77405
  };
76319
77406
  var isPointInBrepPour = (p, pour) => {
76320
77407
  const outerRing = pour.brep_shape.outer_ring.vertices.map((v) => ({
@@ -76348,7 +77435,7 @@ var isSegmentFullyInsideCopperPour = (start, end, pour) => {
76348
77435
  const dx = end.x - start.x;
76349
77436
  const dy = end.y - start.y;
76350
77437
  const length8 = Math.hypot(dx, dy);
76351
- if (length8 <= EPSILON24) return false;
77438
+ if (length8 <= EPSILON26) return false;
76352
77439
  const samples = [0, 0.25, 0.5, 0.75, 1];
76353
77440
  return samples.every(
76354
77441
  (t) => isPointInCopperPour(