@tscircuit/core 0.0.2025 → 0.0.2027

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)],
@@ -19561,7 +19561,9 @@ function getAutoroutingPhasePcbTracePaths({
19561
19561
  let exported = false;
19562
19562
  for (const routeIndex of remainingRoutes) {
19563
19563
  for (const reverse of [false, true]) {
19564
- const candidates = endpoints[routeIndex][reverse ? 1 : 0];
19564
+ const candidates = endpoints[routeIndex][reverse ? 1 : 0].filter(
19565
+ (terminal2) => terminal2.pcb_port_id
19566
+ );
19565
19567
  if (candidates.length !== 1) continue;
19566
19568
  const terminal = candidates[0];
19567
19569
  if (!terminal.pcb_port_id || degreeByTerminal.get(terminal) !== 1)
@@ -19629,7 +19631,7 @@ import {
19629
19631
  groupProps
19630
19632
  } from "@tscircuit/props";
19631
19633
  import {
19632
- distance as distance16
19634
+ distance as distance18
19633
19635
  } from "circuit-json";
19634
19636
  import Debug15 from "debug";
19635
19637
 
@@ -38873,9 +38875,9 @@ function getFreeBoundaryTracks(params) {
38873
38875
  for (const via of vias) {
38874
38876
  if (!via.spanLayers.includes(targetLayer2)) continue;
38875
38877
  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);
38878
+ const distance25 = Math.abs(via.center[across] - edge);
38879
+ if (distance25 >= radius) continue;
38880
+ const extent = Math.sqrt(radius * radius - distance25 * distance25);
38879
38881
  blocked.push([via.center[along] - extent, via.center[along] + extent]);
38880
38882
  }
38881
38883
  const gaps = [];
@@ -50566,6 +50568,8 @@ function getPresetAutoroutingConfig(autorouterConfig, platformConfig) {
50566
50568
  };
50567
50569
  case "bus-lanes":
50568
50570
  return { local: true, groupMode: "subcircuit", preset: "bus_lanes" };
50571
+ case "dogbone":
50572
+ return { local: true, groupMode: "subcircuit", preset: "dogbone" };
50569
50573
  case "fanout":
50570
50574
  return {
50571
50575
  local: true,
@@ -50628,11 +50632,1027 @@ function getPresetAutoroutingConfig(autorouterConfig, platformConfig) {
50628
50632
  }
50629
50633
  }
50630
50634
 
50631
- // node_modules/@tscircuit/bus-lanes-solver/lib/bus-lanes-solver.ts
50635
+ // node_modules/@tscircuit/dogbone-solver/lib/dogbone-fanout-solver.ts
50636
+ import { BaseSolver as BaseSolver7 } from "@tscircuit/solver-utils";
50637
+
50638
+ // node_modules/@tscircuit/dogbone-solver/node_modules/@tscircuit/fanout-solver/lib/geometry.ts
50639
+ var EPSILON24 = 1e-9;
50640
+ function obstacleIsCircular2(obstacle) {
50641
+ return obstacle.shape === "circle";
50642
+ }
50643
+ function toObstacleLocalPoint2(point6, obstacle) {
50644
+ const rotationRadians = -(obstacle.ccwRotationDegrees ?? 0) * Math.PI / 180;
50645
+ const dx = point6.x - obstacle.center.x;
50646
+ const dy = point6.y - obstacle.center.y;
50647
+ return {
50648
+ x: dx * Math.cos(rotationRadians) - dy * Math.sin(rotationRadians),
50649
+ y: dx * Math.sin(rotationRadians) + dy * Math.cos(rotationRadians)
50650
+ };
50651
+ }
50652
+ function distance12(a, b) {
50653
+ return Math.hypot(a.x - b.x, a.y - b.y);
50654
+ }
50655
+ function distancePointToSegment2(point6, start, end) {
50656
+ const dx = end.x - start.x;
50657
+ const dy = end.y - start.y;
50658
+ const lengthSquared = dx * dx + dy * dy;
50659
+ const rawT = lengthSquared < EPSILON24 ? 0 : ((point6.x - start.x) * dx + (point6.y - start.y) * dy) / lengthSquared;
50660
+ const t = Math.max(0, Math.min(1, rawT));
50661
+ return Math.hypot(point6.x - (start.x + t * dx), point6.y - (start.y + t * dy));
50662
+ }
50663
+ function cross2(origin, a, b) {
50664
+ return (a.x - origin.x) * (b.y - origin.y) - (a.y - origin.y) * (b.x - origin.x);
50665
+ }
50666
+ function segmentsProperlyCross2(a, b, c, d) {
50667
+ const d1 = cross2(c, d, a);
50668
+ const d2 = cross2(c, d, b);
50669
+ const d3 = cross2(a, b, c);
50670
+ const d4 = cross2(a, b, d);
50671
+ return (d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0);
50672
+ }
50673
+ function distanceSegmentToSegment2(firstStart, firstEnd, secondStart, secondEnd) {
50674
+ if (segmentsProperlyCross2(firstStart, firstEnd, secondStart, secondEnd)) {
50675
+ return 0;
50676
+ }
50677
+ return Math.min(
50678
+ distancePointToSegment2(firstStart, secondStart, secondEnd),
50679
+ distancePointToSegment2(firstEnd, secondStart, secondEnd),
50680
+ distancePointToSegment2(secondStart, firstStart, firstEnd),
50681
+ distancePointToSegment2(secondEnd, firstStart, firstEnd)
50682
+ );
50683
+ }
50684
+ function distancePointToObstacle2(point6, obstacle) {
50685
+ if (obstacleIsCircular2(obstacle)) {
50686
+ return Math.max(0, distance12(point6, obstacle.center) - obstacle.width / 2);
50687
+ }
50688
+ const localPoint = toObstacleLocalPoint2(point6, obstacle);
50689
+ const dx = Math.max(Math.abs(localPoint.x) - obstacle.width / 2, 0);
50690
+ const dy = Math.max(Math.abs(localPoint.y) - obstacle.height / 2, 0);
50691
+ return Math.hypot(dx, dy);
50692
+ }
50693
+ function distanceSegmentToObstacle2(segment2, obstacle) {
50694
+ if (obstacleIsCircular2(obstacle)) {
50695
+ return Math.max(
50696
+ 0,
50697
+ distancePointToSegment2(obstacle.center, segment2.start, segment2.end) - obstacle.width / 2
50698
+ );
50699
+ }
50700
+ const localStart = toObstacleLocalPoint2(segment2.start, obstacle);
50701
+ const localEnd = toObstacleLocalPoint2(segment2.end, obstacle);
50702
+ if (Math.abs(localStart.x) <= obstacle.width / 2 + EPSILON24 && Math.abs(localStart.y) <= obstacle.height / 2 + EPSILON24) {
50703
+ return 0;
50704
+ }
50705
+ if (Math.abs(localEnd.x) <= obstacle.width / 2 + EPSILON24 && Math.abs(localEnd.y) <= obstacle.height / 2 + EPSILON24) {
50706
+ return 0;
50707
+ }
50708
+ const minX = -obstacle.width / 2;
50709
+ const maxX = obstacle.width / 2;
50710
+ const minY = -obstacle.height / 2;
50711
+ const maxY = obstacle.height / 2;
50712
+ const corners = [
50713
+ { x: minX, y: minY },
50714
+ { x: maxX, y: minY },
50715
+ { x: maxX, y: maxY },
50716
+ { x: minX, y: maxY }
50717
+ ];
50718
+ let minimumDistance = Number.POSITIVE_INFINITY;
50719
+ for (let index = 0; index < corners.length; index++) {
50720
+ minimumDistance = Math.min(
50721
+ minimumDistance,
50722
+ distanceSegmentToSegment2(
50723
+ localStart,
50724
+ localEnd,
50725
+ corners[index],
50726
+ corners[(index + 1) % corners.length]
50727
+ )
50728
+ );
50729
+ }
50730
+ return minimumDistance;
50731
+ }
50732
+ function segmentsAreClear2(first, second, clearance) {
50733
+ if (first.layer !== second.layer) return true;
50734
+ const requiredDistance = (first.width + second.width) / 2 + clearance;
50735
+ const broadPhaseDistance = requiredDistance + EPSILON24;
50736
+ 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) {
50737
+ return true;
50738
+ }
50739
+ return distanceSegmentToSegment2(
50740
+ first.start,
50741
+ first.end,
50742
+ second.start,
50743
+ second.end
50744
+ ) >= requiredDistance - EPSILON24;
50745
+ }
50746
+
50747
+ // node_modules/@tscircuit/dogbone-solver/node_modules/@tscircuit/fanout-solver/lib/match-component-dogbone-via-sites.ts
50748
+ var EPSILON25 = 1e-9;
50749
+ var DEFAULT_MAXIMUM_SEARCH_STATES2 = 1e5;
50750
+ function assertGeometryRules2(rules) {
50751
+ for (const [name, value] of [
50752
+ ["viaDiameter", rules.viaDiameter],
50753
+ ["traceWidth", rules.traceWidth]
50754
+ ]) {
50755
+ if (!Number.isFinite(value) || value <= 0) {
50756
+ throw new Error(
50757
+ `FanoutSolver: dogbone ${name} must be a positive finite number, received ${value}`
50758
+ );
50759
+ }
50760
+ }
50761
+ if (!Number.isFinite(rules.clearance) || rules.clearance < 0) {
50762
+ throw new Error(
50763
+ `FanoutSolver: dogbone clearance must be a non-negative finite number, received ${rules.clearance}`
50764
+ );
50765
+ }
50766
+ if (rules.viaHoleDiameter !== void 0 && (!Number.isFinite(rules.viaHoleDiameter) || rules.viaHoleDiameter <= 0)) {
50767
+ throw new Error(
50768
+ `FanoutSolver: dogbone viaHoleDiameter must be a positive finite number, received ${rules.viaHoleDiameter}`
50769
+ );
50770
+ }
50771
+ if (rules.holeToHoleClearance !== void 0 && (!Number.isFinite(rules.holeToHoleClearance) || rules.holeToHoleClearance < 0)) {
50772
+ throw new Error(
50773
+ `FanoutSolver: dogbone holeToHoleClearance must be a non-negative finite number, received ${rules.holeToHoleClearance}`
50774
+ );
50775
+ }
50776
+ if (rules.holeToHoleClearance !== void 0 && rules.viaHoleDiameter === void 0) {
50777
+ throw new Error(
50778
+ "FanoutSolver: dogbone holeToHoleClearance requires viaHoleDiameter"
50779
+ );
50780
+ }
50781
+ const maximumSearchStates = rules.maximumSearchStates ?? DEFAULT_MAXIMUM_SEARCH_STATES2;
50782
+ if (!Number.isInteger(maximumSearchStates) || maximumSearchStates < 1) {
50783
+ throw new Error(
50784
+ `FanoutSolver: dogbone maximumSearchStates must be a positive integer, received ${maximumSearchStates}`
50785
+ );
50786
+ }
50787
+ return maximumSearchStates;
50788
+ }
50789
+ function uniqueSortedCoordinates2(values) {
50790
+ const result = [];
50791
+ for (const value of values.toSorted((first, second) => first - second)) {
50792
+ if (!Number.isFinite(value)) continue;
50793
+ if (result.length === 0 || Math.abs(result.at(-1) - value) > EPSILON25) {
50794
+ result.push(value);
50795
+ }
50796
+ }
50797
+ return result;
50798
+ }
50799
+ function getComponentMatchingInputs2(preparedBuses) {
50800
+ const byComponent = /* @__PURE__ */ new Map();
50801
+ const componentByConnectionIndex = /* @__PURE__ */ new Map();
50802
+ const obstacleSetByComponentId = /* @__PURE__ */ new Map();
50803
+ for (const bus of preparedBuses) {
50804
+ let component = byComponent.get(bus.componentId);
50805
+ if (!component) {
50806
+ component = {
50807
+ componentId: bus.componentId,
50808
+ connections: [],
50809
+ obstacles: [],
50810
+ xCoordinates: [],
50811
+ yCoordinates: [],
50812
+ pitchX: Number.POSITIVE_INFINITY,
50813
+ pitchY: Number.POSITIVE_INFINITY
50814
+ };
50815
+ byComponent.set(bus.componentId, component);
50816
+ obstacleSetByComponentId.set(bus.componentId, /* @__PURE__ */ new Set());
50817
+ }
50818
+ component.xCoordinates.push(...bus.xCoordinates);
50819
+ component.yCoordinates.push(...bus.yCoordinates);
50820
+ if (Number.isFinite(bus.pitchX) && bus.pitchX > EPSILON25) {
50821
+ component.pitchX = Math.min(component.pitchX, bus.pitchX);
50822
+ }
50823
+ if (Number.isFinite(bus.pitchY) && bus.pitchY > EPSILON25) {
50824
+ component.pitchY = Math.min(component.pitchY, bus.pitchY);
50825
+ }
50826
+ const obstacleSet = obstacleSetByComponentId.get(bus.componentId);
50827
+ for (const obstacle of bus.componentObstacles) {
50828
+ if (!obstacleSet.has(obstacle)) {
50829
+ obstacleSet.add(obstacle);
50830
+ component.obstacles.push(obstacle);
50831
+ }
50832
+ }
50833
+ for (const preparedConnection of bus.connections) {
50834
+ const existingComponent = componentByConnectionIndex.get(
50835
+ preparedConnection.connectionIndex
50836
+ );
50837
+ if (existingComponent !== void 0) {
50838
+ if (existingComponent !== bus.componentId) {
50839
+ throw new Error(
50840
+ `FanoutSolver: connection index ${preparedConnection.connectionIndex} belongs to multiple components`
50841
+ );
50842
+ }
50843
+ continue;
50844
+ }
50845
+ componentByConnectionIndex.set(
50846
+ preparedConnection.connectionIndex,
50847
+ bus.componentId
50848
+ );
50849
+ component.connections.push({
50850
+ preparedConnection,
50851
+ busId: bus.busId,
50852
+ direction: bus.direction,
50853
+ terminationType: bus.termination.type
50854
+ });
50855
+ }
50856
+ }
50857
+ return [...byComponent.values()].map((component) => ({
50858
+ ...component,
50859
+ connections: component.connections.toSorted(
50860
+ (first, second) => first.preparedConnection.connectionIndex - second.preparedConnection.connectionIndex
50861
+ ),
50862
+ xCoordinates: uniqueSortedCoordinates2(component.xCoordinates),
50863
+ yCoordinates: uniqueSortedCoordinates2(component.yCoordinates)
50864
+ })).toSorted(
50865
+ (first, second) => first.componentId.localeCompare(second.componentId)
50866
+ );
50867
+ }
50868
+ function getInterstitialCoordinates2(params) {
50869
+ const { coordinates, pitch } = params;
50870
+ if (coordinates.length === 0 || !Number.isFinite(pitch) || pitch <= EPSILON25) {
50871
+ return [];
50872
+ }
50873
+ const interstitialCoordinates = [coordinates[0] - pitch / 2];
50874
+ for (let index = 1; index < coordinates.length; index++) {
50875
+ interstitialCoordinates.push(
50876
+ (coordinates[index - 1] + coordinates[index]) / 2
50877
+ );
50878
+ }
50879
+ interstitialCoordinates.push(coordinates.at(-1) + pitch / 2);
50880
+ return uniqueSortedCoordinates2(interstitialCoordinates);
50881
+ }
50882
+ function getAdjacentInterstitialCoordinates2(params) {
50883
+ const { sourceCoordinate, coordinates, pitch } = params;
50884
+ const interstitialCoordinates = getInterstitialCoordinates2({
50885
+ coordinates,
50886
+ pitch
50887
+ });
50888
+ const before = interstitialCoordinates.filter((coordinate) => coordinate < sourceCoordinate - EPSILON25).at(-1);
50889
+ const after = interstitialCoordinates.find(
50890
+ (coordinate) => coordinate > sourceCoordinate + EPSILON25
50891
+ );
50892
+ return [before, after].filter(
50893
+ (coordinate) => coordinate !== void 0
50894
+ );
50895
+ }
50896
+ function directSegmentIsStraightOr452(start, end) {
50897
+ const absoluteX = Math.abs(end.x - start.x);
50898
+ const absoluteY = Math.abs(end.y - start.y);
50899
+ return absoluteX <= EPSILON25 || absoluteY <= EPSILON25 || Math.abs(absoluteX - absoluteY) <= EPSILON25;
50900
+ }
50901
+ function getOutwardRank2(params) {
50902
+ const { source, site, direction } = params;
50903
+ const outwardDisplacement = direction === "right" ? site.x - source.x : direction === "left" ? source.x - site.x : direction === "up" ? site.y - source.y : source.y - site.y;
50904
+ return outwardDisplacement > EPSILON25 ? 0 : Math.abs(outwardDisplacement) <= EPSILON25 ? 1 : 2;
50905
+ }
50906
+ function viaSiteClearsObstacles2(params) {
50907
+ const { point: point6, obstacles, viaDiameter, clearance } = params;
50908
+ const requiredClearance = viaDiameter / 2 + clearance;
50909
+ return obstacles.every(
50910
+ (obstacle) => distancePointToObstacle2(point6, obstacle) >= requiredClearance - EPSILON25
50911
+ );
50912
+ }
50913
+ function sourceSegmentClearsOtherObstacles2(params) {
50914
+ const { segment: segment2, sourceObstacle, obstacles, clearance } = params;
50915
+ const requiredClearance = segment2.width / 2 + clearance;
50916
+ return obstacles.every(
50917
+ (obstacle) => obstacle === sourceObstacle || distanceSegmentToObstacle2(segment2, obstacle) >= requiredClearance - EPSILON25
50918
+ );
50919
+ }
50920
+ function getConnectionCandidates2(params) {
50921
+ const { connection, component, rules } = params;
50922
+ const { preparedConnection, direction } = connection;
50923
+ const obstacles = rules.additionalObstacles ? [.../* @__PURE__ */ new Set([...component.obstacles, ...rules.additionalObstacles])] : component.obstacles;
50924
+ const source = {
50925
+ x: preparedConnection.sourcePoint.x,
50926
+ y: preparedConnection.sourcePoint.y
50927
+ };
50928
+ const adjacentX = getAdjacentInterstitialCoordinates2({
50929
+ sourceCoordinate: source.x,
50930
+ coordinates: component.xCoordinates,
50931
+ pitch: component.pitchX
50932
+ });
50933
+ const adjacentY = getAdjacentInterstitialCoordinates2({
50934
+ sourceCoordinate: source.y,
50935
+ coordinates: component.yCoordinates,
50936
+ pitch: component.pitchY
50937
+ });
50938
+ const rawPoints = [
50939
+ ...adjacentX.map((x) => ({ x, y: source.y })),
50940
+ ...adjacentY.map((y) => ({ x: source.x, y })),
50941
+ ...adjacentX.flatMap((x) => adjacentY.map((y) => ({ x, y })))
50942
+ ];
50943
+ const uniquePoints2 = [];
50944
+ for (const point6 of rawPoints) {
50945
+ if (!uniquePoints2.some((candidate) => distance12(candidate, point6) <= EPSILON25)) {
50946
+ uniquePoints2.push(point6);
50947
+ }
50948
+ }
50949
+ const fixedViaPoint = rules.fixedViaPointsByConnectionIndex?.get(
50950
+ preparedConnection.connectionIndex
50951
+ );
50952
+ const candidatePoints = fixedViaPoint ? [fixedViaPoint] : uniquePoints2;
50953
+ const candidates = [];
50954
+ for (const point6 of candidatePoints) {
50955
+ if (connection.terminationType === "plane" && !directSegmentIsStraightOr452(source, point6)) {
50956
+ continue;
50957
+ }
50958
+ if (!viaSiteClearsObstacles2({
50959
+ point: point6,
50960
+ obstacles,
50961
+ viaDiameter: rules.viaDiameter,
50962
+ clearance: rules.clearance
50963
+ })) {
50964
+ continue;
50965
+ }
50966
+ const sourceSegment = {
50967
+ start: source,
50968
+ end: point6,
50969
+ width: rules.traceWidth,
50970
+ layer: preparedConnection.sourceLayer
50971
+ };
50972
+ if (!sourceSegmentClearsOtherObstacles2({
50973
+ segment: sourceSegment,
50974
+ sourceObstacle: preparedConnection.sourceObstacle,
50975
+ obstacles,
50976
+ clearance: rules.clearance
50977
+ })) {
50978
+ continue;
50979
+ }
50980
+ const candidateClearsRoutedCopper = (rules.blockingSegments ?? []).every(
50981
+ (blocker) => {
50982
+ if (blocker.connectionIndex === preparedConnection.connectionIndex) {
50983
+ return true;
50984
+ }
50985
+ if (rules.canShareCopper?.(
50986
+ preparedConnection.connectionIndex,
50987
+ blocker.connectionIndex
50988
+ )) {
50989
+ return true;
50990
+ }
50991
+ const viaToTraceClearance = rules.viaDiameter / 2 + blocker.segment.width / 2 + rules.clearance;
50992
+ if (distancePointToSegment2(
50993
+ point6,
50994
+ blocker.segment.start,
50995
+ blocker.segment.end
50996
+ ) < viaToTraceClearance - EPSILON25) {
50997
+ return false;
50998
+ }
50999
+ return blocker.segment.layer !== sourceSegment.layer || segmentsAreClear2(sourceSegment, blocker.segment, rules.clearance);
51000
+ }
51001
+ );
51002
+ if (!candidateClearsRoutedCopper) continue;
51003
+ const candidateClearsRoutedVias = (rules.blockingVias ?? []).every(
51004
+ (blocker) => {
51005
+ if (blocker.connectionIndex === preparedConnection.connectionIndex) {
51006
+ return true;
51007
+ }
51008
+ if (distance12(point6, blocker.center) < (rules.viaDiameter + blocker.diameter) / 2 + rules.clearance - EPSILON25) {
51009
+ return false;
51010
+ }
51011
+ if (blocker.spanLayers.includes(sourceSegment.layer) && distancePointToSegment2(
51012
+ blocker.center,
51013
+ sourceSegment.start,
51014
+ sourceSegment.end
51015
+ ) < blocker.diameter / 2 + sourceSegment.width / 2 + rules.clearance - EPSILON25) {
51016
+ return false;
51017
+ }
51018
+ return true;
51019
+ }
51020
+ );
51021
+ if (!candidateClearsRoutedVias) continue;
51022
+ candidates.push({
51023
+ connectionIndex: preparedConnection.connectionIndex,
51024
+ point: point6,
51025
+ sourceSegment,
51026
+ outwardRank: getOutwardRank2({ source, site: point6, direction })
51027
+ });
51028
+ }
51029
+ const perpendicularCoordinates = direction === "left" || direction === "right" ? component.yCoordinates : component.xCoordinates;
51030
+ const sourcePerpendicularCoordinate = direction === "left" || direction === "right" ? source.y : source.x;
51031
+ const perpendicularGridIndex = perpendicularCoordinates.findIndex(
51032
+ (coordinate) => Math.abs(coordinate - sourcePerpendicularCoordinate) <= EPSILON25
51033
+ );
51034
+ const planeCheckerboardSide = perpendicularGridIndex >= 0 ? perpendicularGridIndex % 2 === 0 ? 1 : -1 : void 0;
51035
+ const preferredPerpendicularSide = connection.terminationType === "boundary" ? rules.preferredBoundaryPerpendicularSideByBusId?.get(connection.busId) : rules.preferPlaneCheckerboardSites && connection.terminationType === "plane" && (direction === "left" || direction === "right") ? planeCheckerboardSide : void 0;
51036
+ const preferOutward = connection.terminationType === "boundary" ? rules.preferBoundaryOutwardByBusId?.get(connection.busId) ?? true : true;
51037
+ const preferredViaPoint = rules.preferredViaPointsByConnectionIndex?.get(
51038
+ preparedConnection.connectionIndex
51039
+ );
51040
+ const getPerpendicularPreferenceRank = (candidate) => {
51041
+ if (preferredPerpendicularSide === void 0) return 0;
51042
+ const displacement = direction === "left" || direction === "right" ? candidate.point.y - source.y : candidate.point.x - source.x;
51043
+ return displacement * preferredPerpendicularSide > EPSILON25 ? 0 : Math.abs(displacement) <= EPSILON25 ? 1 : 2;
51044
+ };
51045
+ return candidates.toSorted(
51046
+ (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
51047
+ );
51048
+ }
51049
+ function candidatesAreMutuallyClear2(params) {
51050
+ const { first, second, rules } = params;
51051
+ const canShareCopper = rules.canShareCopper?.(first.connectionIndex, second.connectionIndex) ?? false;
51052
+ const requiredHoleSeparation = rules.viaHoleDiameter ? rules.viaHoleDiameter + (rules.holeToHoleClearance ?? rules.clearance) : 0;
51053
+ const requiredViaSeparation = canShareCopper ? requiredHoleSeparation : Math.max(rules.viaDiameter + rules.clearance, requiredHoleSeparation);
51054
+ if (distance12(first.point, second.point) < requiredViaSeparation - EPSILON25) {
51055
+ return false;
51056
+ }
51057
+ const requiredViaToTraceClearance = rules.viaDiameter / 2 + rules.traceWidth / 2 + rules.clearance;
51058
+ if (!canShareCopper) {
51059
+ if (distancePointToSegment2(
51060
+ first.point,
51061
+ second.sourceSegment.start,
51062
+ second.sourceSegment.end
51063
+ ) < requiredViaToTraceClearance - EPSILON25 || distancePointToSegment2(
51064
+ second.point,
51065
+ first.sourceSegment.start,
51066
+ first.sourceSegment.end
51067
+ ) < requiredViaToTraceClearance - EPSILON25) {
51068
+ return false;
51069
+ }
51070
+ }
51071
+ if (canShareCopper) return true;
51072
+ return segmentsAreClear2(
51073
+ first.sourceSegment,
51074
+ second.sourceSegment,
51075
+ rules.clearance
51076
+ );
51077
+ }
51078
+ function matchComponent2(params) {
51079
+ const { component, rules, consumeSearchState } = params;
51080
+ const entries = component.connections.map(
51081
+ (connection) => ({
51082
+ connection,
51083
+ candidates: getConnectionCandidates2({ connection, component, rules })
51084
+ })
51085
+ );
51086
+ if (entries.some((entry) => entry.candidates.length === 0)) return null;
51087
+ const compatibilityCache = /* @__PURE__ */ new Map();
51088
+ const candidatesAreCompatible = (first, second) => {
51089
+ const cached = compatibilityCache.get(first)?.get(second);
51090
+ if (cached !== void 0) return cached;
51091
+ const compatible = candidatesAreMutuallyClear2({ first, second, rules });
51092
+ const firstCache = compatibilityCache.get(first) ?? /* @__PURE__ */ new Map();
51093
+ firstCache.set(second, compatible);
51094
+ compatibilityCache.set(first, firstCache);
51095
+ const secondCache = compatibilityCache.get(second) ?? /* @__PURE__ */ new Map();
51096
+ secondCache.set(first, compatible);
51097
+ compatibilityCache.set(second, secondCache);
51098
+ return compatible;
51099
+ };
51100
+ const forcedCandidates = entries.flatMap(
51101
+ (entry) => entry.candidates.length === 1 ? [entry.candidates[0]] : []
51102
+ );
51103
+ for (let candidateIndex = 0; candidateIndex < forcedCandidates.length; candidateIndex++) {
51104
+ const candidate = forcedCandidates[candidateIndex];
51105
+ for (let previousIndex = 0; previousIndex < candidateIndex; previousIndex++) {
51106
+ if (!candidatesAreCompatible(candidate, forcedCandidates[previousIndex])) {
51107
+ return null;
51108
+ }
51109
+ }
51110
+ }
51111
+ const assignedCandidates = new Map(
51112
+ forcedCandidates.map((candidate) => [candidate.connectionIndex, candidate])
51113
+ );
51114
+ const remaining = new Set(
51115
+ entries.flatMap(
51116
+ (entry) => entry.candidates.length > 1 ? [entry.connection.preparedConnection.connectionIndex] : []
51117
+ )
51118
+ );
51119
+ const entryByConnectionIndex = new Map(
51120
+ entries.map((entry) => [
51121
+ entry.connection.preparedConnection.connectionIndex,
51122
+ entry
51123
+ ])
51124
+ );
51125
+ if (remaining.size === 0) {
51126
+ return new Map(
51127
+ [...assignedCandidates.entries()].map(([connectionIndex, candidate]) => [
51128
+ connectionIndex,
51129
+ { ...candidate.point }
51130
+ ])
51131
+ );
51132
+ }
51133
+ const getViableCandidates = (entry) => entry.candidates.filter(
51134
+ (candidate) => [...assignedCandidates.values()].every(
51135
+ (assignedCandidate) => candidatesAreCompatible(candidate, assignedCandidate)
51136
+ )
51137
+ );
51138
+ const augmentMatching = () => {
51139
+ if (!consumeSearchState()) return false;
51140
+ if (remaining.size === 0) return true;
51141
+ let selectedEntry;
51142
+ let selectedCandidates = [];
51143
+ for (const connectionIndex2 of [...remaining].toSorted(
51144
+ (first, second) => first - second
51145
+ )) {
51146
+ const entry = entryByConnectionIndex.get(connectionIndex2);
51147
+ const viableCandidates = getViableCandidates(entry);
51148
+ if (viableCandidates.length === 0) return false;
51149
+ if (!selectedEntry || viableCandidates.length < selectedCandidates.length || viableCandidates.length === selectedCandidates.length && connectionIndex2 < selectedEntry.connection.preparedConnection.connectionIndex) {
51150
+ selectedEntry = entry;
51151
+ selectedCandidates = viableCandidates;
51152
+ }
51153
+ }
51154
+ const connectionIndex = selectedEntry.connection.preparedConnection.connectionIndex;
51155
+ remaining.delete(connectionIndex);
51156
+ for (const candidate of selectedCandidates) {
51157
+ assignedCandidates.set(connectionIndex, candidate);
51158
+ if (augmentMatching()) return true;
51159
+ assignedCandidates.delete(connectionIndex);
51160
+ }
51161
+ remaining.add(connectionIndex);
51162
+ return false;
51163
+ };
51164
+ if (!augmentMatching()) return null;
51165
+ return new Map(
51166
+ [...assignedCandidates.entries()].toSorted(([first], [second]) => first - second).map(([connectionIndex, candidate]) => [
51167
+ connectionIndex,
51168
+ { ...candidate.point }
51169
+ ])
51170
+ );
51171
+ }
51172
+ function matchComponentDogboneViaSites2(preparedBuses, rules) {
51173
+ const maximumSearchStates = assertGeometryRules2(rules);
51174
+ if (preparedBuses.length === 0) return /* @__PURE__ */ new Map();
51175
+ let consumedSearchStates = 0;
51176
+ const consumeSearchState = () => {
51177
+ consumedSearchStates++;
51178
+ return consumedSearchStates <= maximumSearchStates;
51179
+ };
51180
+ const result = /* @__PURE__ */ new Map();
51181
+ for (const component of getComponentMatchingInputs2(preparedBuses)) {
51182
+ const componentResult = matchComponent2({
51183
+ component,
51184
+ rules,
51185
+ consumeSearchState
51186
+ });
51187
+ if (!componentResult) return null;
51188
+ for (const [connectionIndex, point6] of componentResult) {
51189
+ result.set(connectionIndex, point6);
51190
+ }
51191
+ }
51192
+ return result;
51193
+ }
51194
+
51195
+ // node_modules/@tscircuit/dogbone-solver/node_modules/@tscircuit/fanout-solver/lib/route-reserved-via-buses.ts
51196
+ import {
51197
+ PortfolioSingleIntraNodeSolver as PortfolioSingleIntraNodeSolver2
51198
+ } from "@tscircuit/capacity-autorouter";
50632
51199
  import { BaseSolver as BaseSolver4 } from "@tscircuit/solver-utils";
50633
51200
 
51201
+ // node_modules/@tscircuit/dogbone-solver/node_modules/@tscircuit/fanout-solver/lib/subsolver-request.ts
51202
+ import { BaseSolver as BaseSolver5 } from "@tscircuit/solver-utils";
51203
+
51204
+ // node_modules/@tscircuit/dogbone-solver/node_modules/@tscircuit/fanout-solver/lib/fanout-solver.ts
51205
+ import { BaseSolver as BaseSolver6 } from "@tscircuit/solver-utils";
51206
+ import { mergeGraphics as mergeGraphics2 } from "graphics-debug";
51207
+
51208
+ // node_modules/@tscircuit/dogbone-solver/node_modules/@tscircuit/fanout-solver/lib/match-angularly-ordered-local-vias.ts
51209
+ var TAU2 = Math.PI * 2;
51210
+
51211
+ // node_modules/@tscircuit/dogbone-solver/node_modules/@tscircuit/fanout-solver/lib/route-single-layer-adaptive-exits.ts
51212
+ var FANOUT_FLOW_DEBUG_ENABLED2 = globalThis.process?.env?.FANOUT_FLOW_DEBUG === "1";
51213
+
51214
+ // node_modules/@tscircuit/dogbone-solver/node_modules/@tscircuit/fanout-solver/lib/runtime-process.ts
51215
+ var getRuntimeProcess2 = (runtime) => runtime.process ?? { env: {} };
51216
+
51217
+ // node_modules/@tscircuit/dogbone-solver/node_modules/@tscircuit/fanout-solver/lib/fanout-solver.ts
51218
+ var process2 = getRuntimeProcess2(globalThis);
51219
+
51220
+ // node_modules/@tscircuit/dogbone-solver/lib/dogbone-fanout-solver.ts
51221
+ import { layer_ref as layer_ref3 } from "circuit-json";
51222
+
51223
+ // node_modules/@tscircuit/dogbone-solver/lib/get-via-board-layers.ts
51224
+ function getViaBoardLayers2(layerCount) {
51225
+ if (layerCount <= 1) return ["top"];
51226
+ return [
51227
+ "top",
51228
+ ...Array.from(
51229
+ { length: layerCount - 2 },
51230
+ (_, index) => `inner${index + 1}`
51231
+ ),
51232
+ "bottom"
51233
+ ];
51234
+ }
51235
+
51236
+ // node_modules/@tscircuit/dogbone-solver/lib/dogbone-fanout-solver.ts
51237
+ var DogboneFanoutSolver = class extends BaseSolver7 {
51238
+ constructor(params) {
51239
+ super();
51240
+ this.params = params;
51241
+ this.MAX_ITERATIONS = params.input.connections.length * 2 + 10;
51242
+ this.steps = this.solveSteps();
51243
+ }
51244
+ params;
51245
+ phase = "preparing";
51246
+ sources = [];
51247
+ sites = /* @__PURE__ */ new Map();
51248
+ traces = [];
51249
+ steps;
51250
+ getConstructorParams() {
51251
+ return [this.params];
51252
+ }
51253
+ _step() {
51254
+ try {
51255
+ if (this.steps.next().done) {
51256
+ this.phase = "complete";
51257
+ this.progress = 1;
51258
+ this.solved = true;
51259
+ }
51260
+ } catch (error) {
51261
+ this.error = error instanceof Error ? error.message : String(error);
51262
+ this.failed = true;
51263
+ }
51264
+ }
51265
+ getOutput() {
51266
+ if (!this.solved)
51267
+ throw new Error(this.error ?? "Dogbone fanout is not solved");
51268
+ return this.traces;
51269
+ }
51270
+ visualize() {
51271
+ return {
51272
+ rects: this.params.input.obstacles.filter((pad) => pad.shape !== "circle").map((pad) => ({
51273
+ center: pad.center,
51274
+ width: pad.width,
51275
+ height: pad.height,
51276
+ fill: "rgba(200,40,40,0.3)"
51277
+ })),
51278
+ circles: [
51279
+ ...this.params.input.obstacles.filter((pad) => pad.shape === "circle").map((pad) => ({
51280
+ center: pad.center,
51281
+ radius: pad.width / 2,
51282
+ fill: "rgba(200,40,40,0.3)"
51283
+ })),
51284
+ ...[...this.sites.values()].map((center) => ({
51285
+ center,
51286
+ radius: (this.params.input.minViaPadDiameter ?? this.params.input.minViaDiameter ?? 0.6) / 2,
51287
+ fill: "orange"
51288
+ }))
51289
+ ],
51290
+ lines: [...this.sites].map(([index, via]) => ({
51291
+ points: [this.sources[index].port, via],
51292
+ strokeColor: "orange",
51293
+ strokeWidth: this.params.input.minTraceWidth
51294
+ }))
51295
+ };
51296
+ }
51297
+ *solveSteps() {
51298
+ const { input, fanoutRoutingLayers } = this.params;
51299
+ const traceWidth = input.minTraceWidth;
51300
+ const clearance = input.minTraceToPadEdgeClearance ?? 0.1;
51301
+ const viaDiameter = input.minViaPadDiameter ?? input.minViaDiameter ?? 0.6;
51302
+ const holeDiameter = input.minViaHoleDiameter ?? 0.3;
51303
+ const viaClearance = input.minViaEdgeToPadEdgeClearance ?? clearance;
51304
+ const boardEdgeClearance = input.minBoardEdgeClearance ?? clearance;
51305
+ const holeClearance = input.minViaHoleEdgeToViaHoleEdgeClearance ?? clearance;
51306
+ this.sources = input.connections.map((connection) => {
51307
+ const point6 = connection.pointsToConnect.find(
51308
+ (point7) => point7.pcb_port_id && input.obstacles.some(
51309
+ (pad2) => pad2.componentId && Math.hypot(pad2.center.x - point7.x, pad2.center.y - point7.y) < 1e-6
51310
+ )
51311
+ );
51312
+ if (!point6?.pcb_port_id)
51313
+ throw new Error(
51314
+ `Dogbone connection ${connection.name} has no source pad`
51315
+ );
51316
+ const pad = input.obstacles.find(
51317
+ (pad2) => pad2.componentId && Math.hypot(pad2.center.x - point6.x, pad2.center.y - point6.y) < 1e-6
51318
+ );
51319
+ return {
51320
+ port: {
51321
+ pcb_port_id: point6.pcb_port_id,
51322
+ pcb_component_id: pad.componentId,
51323
+ x: point6.x,
51324
+ y: point6.y,
51325
+ layers: [layer_ref3.parse(point6.layer)]
51326
+ },
51327
+ sourceTraceId: connection.source_trace_id
51328
+ };
51329
+ });
51330
+ const sources = this.sources;
51331
+ const layers = getViaBoardLayers2(input.layerCount);
51332
+ const allowedLayers = fanoutRoutingLayers ?? layers;
51333
+ if (allowedLayers.some(
51334
+ (layer) => !layers.some((boardLayer) => boardLayer === layer)
51335
+ ))
51336
+ throw new Error("Dogbone fanout targets an unavailable board layer");
51337
+ const buses = [];
51338
+ const targets = sources.map(({ port, sourceTraceId }, connectionIndex) => {
51339
+ const sourceObstacle = input.obstacles.find(
51340
+ (obstacle) => obstacle.componentId === port.pcb_component_id && Math.hypot(obstacle.center.x - port.x, obstacle.center.y - port.y) < 1e-6
51341
+ );
51342
+ if (!sourceObstacle)
51343
+ throw new Error(
51344
+ "Dogbone fanout requires a component pad at each source port"
51345
+ );
51346
+ if (port.layers.length !== 1 || !layers.includes(port.layers[0])) {
51347
+ throw new Error("Dogbone fanout requires a single-layer SMT source pad");
51348
+ }
51349
+ const sourceLayer = port.layers[0];
51350
+ const targetLayer2 = allowedLayers.find((layer) => layer !== sourceLayer);
51351
+ if (!targetLayer2)
51352
+ throw new Error(
51353
+ "Dogbone fanout requires a legal layer distinct from the source pad"
51354
+ );
51355
+ let bus = buses.find(
51356
+ (bus2) => bus2.componentId === sourceObstacle.componentId
51357
+ );
51358
+ if (!bus) {
51359
+ const pads = input.obstacles.filter(
51360
+ (obstacle) => obstacle.componentId === sourceObstacle.componentId
51361
+ );
51362
+ const xs = [
51363
+ ...new Set(pads.map((pad) => Math.round(pad.center.x * 1e6) / 1e6))
51364
+ ].sort((a, b) => a - b);
51365
+ const ys = [
51366
+ ...new Set(pads.map((pad) => Math.round(pad.center.y * 1e6) / 1e6))
51367
+ ].sort((a, b) => a - b);
51368
+ if (xs.length < 2 || ys.length < 2)
51369
+ throw new Error("Dogbone fanout requires a two-dimensional pad grid");
51370
+ const pitch = (coordinates) => Math.min(...coordinates.slice(1).map((v, i) => v - coordinates[i]));
51371
+ bus = {
51372
+ busId: sourceObstacle.componentId,
51373
+ componentId: sourceObstacle.componentId,
51374
+ componentObstacles: pads,
51375
+ connections: [],
51376
+ direction: "down",
51377
+ termination: { type: "boundary" },
51378
+ componentBounds: {
51379
+ minX: xs[0],
51380
+ maxX: xs.at(-1),
51381
+ minY: ys[0],
51382
+ maxY: ys.at(-1)
51383
+ },
51384
+ sharedBoundary: input.bounds,
51385
+ xCoordinates: xs,
51386
+ yCoordinates: ys,
51387
+ pitchX: pitch(xs),
51388
+ pitchY: pitch(ys)
51389
+ };
51390
+ buses.push(bus);
51391
+ }
51392
+ const sourcePoint = {
51393
+ x: port.x,
51394
+ y: port.y,
51395
+ layer: sourceLayer,
51396
+ pcb_port_id: port.pcb_port_id
51397
+ };
51398
+ bus.connections.push({
51399
+ connectionIndex,
51400
+ connection: { name: port.pcb_port_id, pointsToConnect: [sourcePoint] },
51401
+ sourcePoint,
51402
+ sourcePointIndex: 0,
51403
+ sourceLayer,
51404
+ sourceObstacle,
51405
+ targetPoint: sourcePoint
51406
+ });
51407
+ return { sourceTraceId, port, sourceLayer, targetLayer: targetLayer2 };
51408
+ });
51409
+ const sites = this.sites;
51410
+ const blockingSegments = [];
51411
+ const blockingVias = [];
51412
+ for (const bus of buses) {
51413
+ this.phase = "assigning_sites";
51414
+ yield;
51415
+ const assignment = matchComponentDogboneViaSites2([bus], {
51416
+ traceWidth,
51417
+ clearance: Math.max(clearance, viaClearance),
51418
+ viaDiameter,
51419
+ viaHoleDiameter: holeDiameter,
51420
+ holeToHoleClearance: holeClearance,
51421
+ additionalObstacles: input.obstacles,
51422
+ blockingSegments,
51423
+ blockingVias
51424
+ });
51425
+ if (!assignment || assignment.size !== bus.connections.length)
51426
+ throw new Error(
51427
+ "No complete local dogbone assignment within clearance/search limits"
51428
+ );
51429
+ for (const connection of bus.connections) {
51430
+ const connectionIndex = connection.connectionIndex;
51431
+ const via = assignment.get(connectionIndex);
51432
+ sites.set(connectionIndex, via);
51433
+ blockingSegments.push({
51434
+ connectionIndex,
51435
+ segment: {
51436
+ start: connection.sourcePoint,
51437
+ end: via,
51438
+ width: traceWidth,
51439
+ layer: connection.sourceLayer
51440
+ }
51441
+ });
51442
+ blockingVias.push({
51443
+ connectionIndex,
51444
+ center: via,
51445
+ diameter: viaDiameter,
51446
+ spanLayers: layers
51447
+ });
51448
+ }
51449
+ }
51450
+ this.phase = "creating_traces";
51451
+ for (const [
51452
+ index,
51453
+ { sourceTraceId, port, sourceLayer, targetLayer: targetLayer2 }
51454
+ ] of targets.entries()) {
51455
+ const via = sites.get(index);
51456
+ const dx = Math.abs(via.x - port.x), dy = Math.abs(via.y - port.y);
51457
+ if (dx > 1e-6 && dy > 1e-6 && Math.abs(dx - dy) > 1e-6)
51458
+ throw new Error(
51459
+ "Dogbone fanout requires straight or 45-degree local escapes"
51460
+ );
51461
+ const margin = viaDiameter / 2 + boardEdgeClearance;
51462
+ 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)
51463
+ throw new Error("Dogbone via violates board-edge clearance");
51464
+ this.traces.push({
51465
+ type: "pcb_trace",
51466
+ pcb_trace_id: `dogbone_${port.pcb_port_id}`,
51467
+ source_trace_id: sourceTraceId,
51468
+ connectsTo: [port.pcb_port_id],
51469
+ route: [
51470
+ {
51471
+ route_type: "wire",
51472
+ x: port.x,
51473
+ y: port.y,
51474
+ layer: sourceLayer,
51475
+ width: traceWidth
51476
+ },
51477
+ { route_type: "wire", ...via, layer: sourceLayer, width: traceWidth },
51478
+ {
51479
+ route_type: "via",
51480
+ ...via,
51481
+ from_layer: sourceLayer,
51482
+ to_layer: targetLayer2,
51483
+ via_diameter: viaDiameter,
51484
+ via_hole_diameter: holeDiameter,
51485
+ layers
51486
+ },
51487
+ { route_type: "wire", ...via, layer: targetLayer2, width: traceWidth },
51488
+ { route_type: "wire", ...via, layer: targetLayer2, width: traceWidth }
51489
+ ]
51490
+ });
51491
+ this.progress = (index + 1) / targets.length;
51492
+ yield;
51493
+ }
51494
+ }
51495
+ };
51496
+
51497
+ // node_modules/@tscircuit/dogbone-solver/lib/dogbone-autorouter.ts
51498
+ var DogboneAutorouter = class {
51499
+ constructor(params, callbacks = {}) {
51500
+ this.params = params;
51501
+ this.callbacks = callbacks;
51502
+ this.solver = new DogboneFanoutSolver(params);
51503
+ }
51504
+ params;
51505
+ callbacks;
51506
+ get input() {
51507
+ return this.params.input;
51508
+ }
51509
+ isRouting = false;
51510
+ solver;
51511
+ timer;
51512
+ listeners = [];
51513
+ finishSolver() {
51514
+ this.callbacks.onSolverEnded?.({
51515
+ solverName: "DogboneFanoutSolver",
51516
+ solved: this.solver.solved,
51517
+ failed: this.solver.failed,
51518
+ iterations: this.solver.iterations,
51519
+ error: this.solver.error
51520
+ });
51521
+ }
51522
+ getTraces() {
51523
+ return this.solver.getOutput().map((trace) => {
51524
+ const connection = this.params.input.connections.find(
51525
+ (connection2) => connection2.pointsToConnect.some(
51526
+ (point6) => point6.pcb_port_id && trace.connectsTo?.includes(point6.pcb_port_id)
51527
+ )
51528
+ );
51529
+ if (!connection)
51530
+ throw new Error("Dogbone fanout lost its source connection");
51531
+ return {
51532
+ ...trace,
51533
+ connection_name: connection.name,
51534
+ source_trace_id: connection.source_trace_id,
51535
+ connectsTo: [
51536
+ .../* @__PURE__ */ new Set([
51537
+ ...trace.connectsTo ?? [],
51538
+ ...connection.routingPcbGroupId ? connection.pointsToConnect.flatMap(
51539
+ (point6) => !point6.pcb_port_id && point6.pointId ? [point6.pointId] : []
51540
+ ) : [`${trace.pcb_trace_id}_exit`]
51541
+ ])
51542
+ ]
51543
+ };
51544
+ });
51545
+ }
51546
+ getOutputSimpleRouteJson() {
51547
+ const traces = this.getTraces();
51548
+ return {
51549
+ ...this.params.input,
51550
+ traces,
51551
+ connections: this.params.input.connections.map((connection) => {
51552
+ const trace = traces.find(
51553
+ (trace2) => trace2.connection_name === connection.name
51554
+ );
51555
+ if (!trace) return connection;
51556
+ const exit = trace.route.at(-1);
51557
+ if (exit.route_type !== "wire")
51558
+ throw new Error("Dogbone fanout lost its exit");
51559
+ return {
51560
+ ...connection,
51561
+ pointsToConnect: connection.pointsToConnect.map((point6) => {
51562
+ const isHandoff = !point6.pcb_port_id;
51563
+ const relocate = connection.routingPcbGroupId ? isHandoff : Boolean(
51564
+ point6.pcb_port_id && trace.connectsTo?.includes(point6.pcb_port_id)
51565
+ );
51566
+ if (!relocate) return point6;
51567
+ if (connection.routingPcbGroupId)
51568
+ return { ...point6, x: exit.x, y: exit.y, layer: exit.layer };
51569
+ const { pcb_port_id: _port, ...handoff } = point6;
51570
+ return {
51571
+ ...handoff,
51572
+ pointId: `${trace.pcb_trace_id}_exit`,
51573
+ x: exit.x,
51574
+ y: exit.y,
51575
+ layer: exit.layer
51576
+ };
51577
+ })
51578
+ };
51579
+ })
51580
+ };
51581
+ }
51582
+ on(event, callback) {
51583
+ this.listeners.push({
51584
+ event,
51585
+ callback
51586
+ });
51587
+ }
51588
+ emit(event) {
51589
+ for (const listener of this.listeners)
51590
+ if (listener.event === event.type) listener.callback(event);
51591
+ }
51592
+ start() {
51593
+ if (this.isRouting) return;
51594
+ this.isRouting = true;
51595
+ this.callbacks.onSolverStarted?.({
51596
+ solverName: "DogboneFanoutSolver",
51597
+ solverParams: this.params,
51598
+ solverConstructorArgs: this.solver.getConstructorParams()
51599
+ });
51600
+ const tick = () => {
51601
+ if (!this.isRouting) return;
51602
+ try {
51603
+ for (let i = 0; i < 16 && !this.solver.solved && !this.solver.failed; i++)
51604
+ this.solver.step();
51605
+ this.emit({
51606
+ type: "progress",
51607
+ steps: this.solver.iterations,
51608
+ progress: this.solver.progress,
51609
+ phase: this.solver.phase,
51610
+ debugGraphics: this.solver.visualize()
51611
+ });
51612
+ if (!this.isRouting) return;
51613
+ if (this.solver.failed) {
51614
+ this.finishSolver();
51615
+ this.isRouting = false;
51616
+ this.emit({
51617
+ type: "error",
51618
+ error: new Error(this.solver.error ?? "Dogbone routing failed")
51619
+ });
51620
+ return;
51621
+ }
51622
+ if (this.solver.solved) {
51623
+ this.finishSolver();
51624
+ this.isRouting = false;
51625
+ this.emit({ type: "complete", traces: this.getTraces() });
51626
+ return;
51627
+ }
51628
+ this.timer = setTimeout(tick, 0);
51629
+ } catch (error) {
51630
+ this.isRouting = false;
51631
+ this.emit({
51632
+ type: "error",
51633
+ error: error instanceof Error ? error : new Error(String(error))
51634
+ });
51635
+ }
51636
+ };
51637
+ this.timer = setTimeout(tick, 0);
51638
+ }
51639
+ stop() {
51640
+ this.isRouting = false;
51641
+ if (this.timer) clearTimeout(this.timer);
51642
+ }
51643
+ solveSync() {
51644
+ this.solver.solve();
51645
+ if (this.solver.failed)
51646
+ throw new Error(this.solver.error ?? "Dogbone routing failed");
51647
+ return this.getTraces();
51648
+ }
51649
+ };
51650
+
51651
+ // node_modules/@tscircuit/bus-lanes-solver/lib/bus-lanes-solver.ts
51652
+ import { BaseSolver as BaseSolver8 } from "@tscircuit/solver-utils";
51653
+
50634
51654
  // node_modules/@tscircuit/bus-lanes-solver/lib/geometry.ts
50635
- var distance12 = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
51655
+ var distance14 = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
50636
51656
  function pointSegmentDistance(p, edge) {
50637
51657
  const [a, b] = edge, dx = b.x - a.x, dy = b.y - a.y, t = Math.max(
50638
51658
  0,
@@ -50643,10 +51663,10 @@ function pointSegmentDistance(p, edge) {
50643
51663
  );
50644
51664
  return Math.hypot(p.x - a.x - t * dx, p.y - a.y - t * dy);
50645
51665
  }
50646
- var cross2 = (a, b, c) => (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
51666
+ var cross3 = (a, b, c) => (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
50647
51667
  function segmentDistance(ab, cd) {
50648
51668
  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)
51669
+ 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
51670
  return 0;
50651
51671
  return Math.min(
50652
51672
  pointSegmentDistance(a, cd),
@@ -50655,10 +51675,10 @@ function segmentDistance(ab, cd) {
50655
51675
  pointSegmentDistance(d, ab)
50656
51676
  );
50657
51677
  }
50658
- var length = (path) => path.slice(1).reduce((sum, p, i) => sum + distance12(path[i], p), 0);
51678
+ var length = (path) => path.slice(1).reduce((sum, p, i) => sum + distance14(path[i], p), 0);
50659
51679
  function simplify(path) {
50660
51680
  return path.filter(
50661
- (p, i) => !i || i === path.length - 1 || Math.abs(cross2(path[i - 1], p, path[i + 1])) > 1e-9
51681
+ (p, i) => !i || i === path.length - 1 || Math.abs(cross3(path[i - 1], p, path[i + 1])) > 1e-9
50662
51682
  );
50663
51683
  }
50664
51684
 
@@ -50702,7 +51722,7 @@ function fixedCopper(input) {
50702
51722
  throw Error("Fixed trace changes layer without a via");
50703
51723
  result.push({ a: p, b: q, radius: p.width / 2, layer: p.layer, owners });
50704
51724
  }
50705
- if (q && p.route_type !== q.route_type && distance12(p, q) > 1e-10) {
51725
+ if (q && p.route_type !== q.route_type && distance14(p, q) > 1e-10) {
50706
51726
  const wire = p.route_type === "wire" ? p : q;
50707
51727
  result.push({
50708
51728
  a: p,
@@ -50822,7 +51842,7 @@ var VectorScene = class {
50822
51842
  );
50823
51843
  } else {
50824
51844
  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])
51845
+ for (const p of distance14(c.a, c.b) < 1e-9 ? [c.a] : [c.a, c.b])
50826
51846
  for (const [x, y] of [
50827
51847
  [1, k],
50828
51848
  [k, 1],
@@ -50910,7 +51930,7 @@ var VectorVisibilitySearch = class {
50910
51930
  this.start = start;
50911
51931
  this.end = end;
50912
51932
  this.vertices = [start, end];
50913
- this.open.push({ id: 0, g: 0, f: distance12(start, end) });
51933
+ this.open.push({ id: 0, g: 0, f: distance14(start, end) });
50914
51934
  this.best.set(0, 0);
50915
51935
  }
50916
51936
  scene;
@@ -50970,7 +51990,7 @@ var VectorVisibilitySearch = class {
50970
51990
  const from = this.vertices[cur.id];
50971
51991
  for (let id = 1; id < this.vertices.length; id++) {
50972
51992
  if (id === cur.id) continue;
50973
- const to = this.vertices[id], lower = cur.g + distance12(from, to);
51993
+ const to = this.vertices[id], lower = cur.g + distance14(from, to);
50974
51994
  if (lower >= (this.best.get(id) ?? Infinity) - 1e-9) continue;
50975
51995
  const candidates = connectors(from, to).sort(
50976
51996
  (a, b) => length(a) - length(b)
@@ -50983,7 +52003,7 @@ var VectorVisibilitySearch = class {
50983
52003
  this.open.push({
50984
52004
  id,
50985
52005
  g,
50986
- f: g + distance12(to, this.end),
52006
+ f: g + distance14(to, this.end),
50987
52007
  parent: cur,
50988
52008
  edge: path
50989
52009
  });
@@ -51225,9 +52245,9 @@ function tuneLengths(input, traces, targets) {
51225
52245
  const clearance = input.minTraceToPadEdgeClearance ?? input.defaultObstacleMargin ?? 0.075;
51226
52246
  const returnSpacing = Math.max(width + clearance, 3 * width);
51227
52247
  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);
52248
+ const segments = t.route.slice(1).map((p, i) => ({ i, span: distance14(t.route[i], p) })).sort((a, b) => b.span - a.span);
51229
52249
  for (const { i } of segments) {
51230
- const a = t.route[i], b = t.route[i + 1], span = distance12(a, b);
52250
+ const a = t.route[i], b = t.route[i + 1], span = distance14(a, b);
51231
52251
  if (span < 0.01) continue;
51232
52252
  const ux = (b.x - a.x) / span, uy = (b.y - a.y) / span;
51233
52253
  const maximumTeeth = Math.floor(span * 0.9 / pitch);
@@ -51327,7 +52347,7 @@ function tuneLengths(input, traces, targets) {
51327
52347
  function tuningPathIsSelfClear(path, required) {
51328
52348
  const cumulative = [0];
51329
52349
  for (let i = 1; i < path.length; i++)
51330
- cumulative.push(cumulative[i - 1] + distance12(path[i - 1], path[i]));
52350
+ cumulative.push(cumulative[i - 1] + distance14(path[i - 1], path[i]));
51331
52351
  for (let i = 0; i < path.length - 1; i++)
51332
52352
  for (let j = i + 2; j < path.length - 1; j++) {
51333
52353
  if (cumulative[j] - cumulative[i + 1] <= required + 1e-8) continue;
@@ -51365,7 +52385,7 @@ var layerColors = {
51365
52385
  var layerColor = (layer) => layerColors[layer] ?? "#64748b";
51366
52386
 
51367
52387
  // node_modules/@tscircuit/bus-lanes-solver/lib/bus-lanes-solver.ts
51368
- var BusLanesSolver = class extends BaseSolver4 {
52388
+ var BusLanesSolver = class extends BaseSolver8 {
51369
52389
  input;
51370
52390
  options;
51371
52391
  phase = "validate";
@@ -51575,7 +52595,7 @@ var BusLanesSolver = class extends BaseSolver4 {
51575
52595
  const t = this.traces.find((t2) => t2.connection_name === c.name);
51576
52596
  if (!t) throw Error("Missing lane");
51577
52597
  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)
52598
+ if (distance14(t.route[0], c.pointsToConnect[0]) > 1e-8 || distance14(t.route.at(-1), c.pointsToConnect[1]) > 1e-8)
51579
52599
  throw Error("Broken lane endpoints");
51580
52600
  for (let i = 1; i < t.route.length; i++) {
51581
52601
  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 +52660,7 @@ var BusLanesSolver = class extends BaseSolver4 {
51640
52660
  });
51641
52661
  continue;
51642
52662
  }
51643
- if (distance12(c.a, c.b) < 1e-9)
52663
+ if (distance14(c.a, c.b) < 1e-9)
51644
52664
  circles.push({
51645
52665
  center: c.a,
51646
52666
  radius: c.radius,
@@ -51801,7 +52821,7 @@ import { CreateFdmEnclosureSolver } from "@tscircuit/create-fdm-enclosure";
51801
52821
  // node_modules/@tscircuit/implicit-copper-pour-solver/lib/implicit-copper-pour-pipeline-solver.ts
51802
52822
  import {
51803
52823
  BasePipelineSolver,
51804
- BaseSolver as BaseSolver5,
52824
+ BaseSolver as BaseSolver9,
51805
52825
  definePipelineStep
51806
52826
  } from "@tscircuit/solver-utils";
51807
52827
 
@@ -51819,7 +52839,7 @@ import { applyToPoint as applyToPoint30, compose as compose11, scale as scale2,
51819
52839
  // node_modules/@tscircuit/implicit-copper-pour-solver/lib/geometry.ts
51820
52840
  import {
51821
52841
  clamp,
51822
- distance as distance13,
52842
+ distance as distance15,
51823
52843
  doBoundsOverlap as doBoundsOverlap2,
51824
52844
  getBoundsFromPoints as getBoundsFromPoints3,
51825
52845
  getUnitVectorFromPointAToB as getUnitVectorFromPointAToB2,
@@ -51868,7 +52888,7 @@ var distanceToPolygon = (point6, polygon) => {
51868
52888
  var distanceToPrimitive = (primitive, px, py) => {
51869
52889
  const point6 = { x: px, y: py };
51870
52890
  if (primitive.kind === "circle") {
51871
- return Math.max(distance13(point6, primitive) - primitive.radius, 0);
52891
+ return Math.max(distance15(point6, primitive) - primitive.radius, 0);
51872
52892
  }
51873
52893
  if (primitive.kind === "segment") {
51874
52894
  return Math.max(
@@ -51919,7 +52939,7 @@ var getClosestPointOnPrimitive = (primitive, point6) => {
51919
52939
  const start = primitive.points[index];
51920
52940
  const end = primitive.points[(index + 1) % primitive.points.length];
51921
52941
  const candidate = pointToSegmentClosestPoint(point6, start, end);
51922
- const candidateDistance = distance13(point6, candidate);
52942
+ const candidateDistance = distance15(point6, candidate);
51923
52943
  if (candidateDistance < bestDistance) {
51924
52944
  bestDistance = candidateDistance;
51925
52945
  closestPoint = candidate;
@@ -52975,7 +53995,7 @@ var prepareCircuitJson = (input) => {
52975
53995
 
52976
53996
  // node_modules/@tscircuit/implicit-copper-pour-solver/lib/simplify-polygon-edges.ts
52977
53997
  import {
52978
- distance as distance14,
53998
+ distance as distance16,
52979
53999
  doSegmentsIntersect,
52980
54000
  onSegment,
52981
54001
  pointToSegmentDistance as pointToSegmentDistance2
@@ -53038,8 +54058,8 @@ var simplifyOpenPath = (points, tolerance) => {
53038
54058
  const point6 = points[index];
53039
54059
  const next = points[index + 1];
53040
54060
  const shortestLeg = Math.min(
53041
- distance14(point6, previous),
53042
- distance14(next, point6)
54061
+ distance16(point6, previous),
54062
+ distance16(next, point6)
53043
54063
  );
53044
54064
  if (repeatsTurnDirection || shortestLeg > tolerance) {
53045
54065
  anchorIndices.push(index);
@@ -53088,8 +54108,8 @@ var normalizePolygon = (polygon) => {
53088
54108
  };
53089
54109
  var isPointStrictlyInsideSegment = (point6, start, end) => {
53090
54110
  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;
54111
+ const cross5 = crossProduct(start, end, point6);
54112
+ if (Math.abs(cross5) > 1e-9) return false;
53093
54113
  return onSegment(start, point6, end);
53094
54114
  };
53095
54115
  var splitEdgesAtVertices = (polygon, allVertices) => {
@@ -53441,7 +54461,7 @@ var mergeSolverGraphics = (source, overlay) => {
53441
54461
  };
53442
54462
 
53443
54463
  // node_modules/@tscircuit/implicit-copper-pour-solver/lib/implicit-copper-pour-pipeline-solver.ts
53444
- var PrepareCircuitJsonSolver = class extends BaseSolver5 {
54464
+ var PrepareCircuitJsonSolver = class extends BaseSolver9 {
53445
54465
  constructor(input) {
53446
54466
  super();
53447
54467
  this.input = input;
@@ -53457,7 +54477,7 @@ var PrepareCircuitJsonSolver = class extends BaseSolver5 {
53457
54477
  return this.output;
53458
54478
  }
53459
54479
  };
53460
- var AssignGridCellsSolver = class extends BaseSolver5 {
54480
+ var AssignGridCellsSolver = class extends BaseSolver9 {
53461
54481
  constructor(input) {
53462
54482
  super();
53463
54483
  this.input = input;
@@ -53480,7 +54500,7 @@ var AssignGridCellsSolver = class extends BaseSolver5 {
53480
54500
  return this.output;
53481
54501
  }
53482
54502
  };
53483
- var TracePowerPolygonsSolver = class extends BaseSolver5 {
54503
+ var TracePowerPolygonsSolver = class extends BaseSolver9 {
53484
54504
  constructor(input) {
53485
54505
  super();
53486
54506
  this.input = input;
@@ -53516,7 +54536,7 @@ var TracePowerPolygonsSolver = class extends BaseSolver5 {
53516
54536
  };
53517
54537
  }
53518
54538
  };
53519
- var SimplifyPolygonEdgesSolver = class extends BaseSolver5 {
54539
+ var SimplifyPolygonEdgesSolver = class extends BaseSolver9 {
53520
54540
  constructor(input) {
53521
54541
  super();
53522
54542
  this.input = input;
@@ -53654,13 +54674,13 @@ import { LayoutPipelineSolver } from "@tscircuit/matchpack";
53654
54674
  import { SchematicTracePipelineSolver } from "@tscircuit/schematic-trace-solver";
53655
54675
 
53656
54676
  // node_modules/@tscircuit/via-stitch-solver/lib/solvers/ViaStitchSolver.ts
53657
- import { BaseSolver as BaseSolver6 } from "@tscircuit/solver-utils";
54677
+ import { BaseSolver as BaseSolver10 } from "@tscircuit/solver-utils";
53658
54678
 
53659
54679
  // node_modules/@tscircuit/via-stitch-solver/lib/geometry/brep-point-containment.ts
53660
54680
  var POINT_EPSILON = 1e-8;
53661
54681
  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;
54682
+ const cross5 = (point6.y - start.y) * (end.x - start.x) - (point6.x - start.x) * (end.y - start.y);
54683
+ if (Math.abs(cross5) > POINT_EPSILON) return false;
53664
54684
  const dot = (point6.x - start.x) * (end.x - start.x) + (point6.y - start.y) * (end.y - start.y);
53665
54685
  if (dot < -POINT_EPSILON) return false;
53666
54686
  const squaredLength = (end.x - start.x) ** 2 + (end.y - start.y) ** 2;
@@ -53981,7 +55001,7 @@ var getGridCoordinates = ({
53981
55001
  }
53982
55002
  return coordinates;
53983
55003
  };
53984
- var ViaStitchSolver = class extends BaseSolver6 {
55004
+ var ViaStitchSolver = class extends BaseSolver10 {
53985
55005
  constructor(input) {
53986
55006
  super();
53987
55007
  this.input = input;
@@ -54128,6 +55148,7 @@ var ViaStitchSolver = class extends BaseSolver6 {
54128
55148
  // lib/solvers.ts
54129
55149
  import { PackSolver2 } from "calculate-packing";
54130
55150
  var SOLVERS = {
55151
+ DogboneFanoutSolver,
54131
55152
  BusLanesSolver,
54132
55153
  PackSolver2,
54133
55154
  LayoutPipelineSolver,
@@ -54591,6 +55612,27 @@ var localAutorouterStrategies = /* @__PURE__ */ new Map([
54591
55612
  createFanoutAutorouterStrategy("single_layer_fanout")
54592
55613
  ],
54593
55614
  ["fanout", createFanoutAutorouterStrategy("fanout")],
55615
+ [
55616
+ "dogbone",
55617
+ {
55618
+ name: "dogbone",
55619
+ preserveOutputTraces: true,
55620
+ followUpAutorouter: "default",
55621
+ cacheable: false,
55622
+ getSolverName: () => "DogboneFanoutSolver",
55623
+ create: ({
55624
+ simpleRouteJson,
55625
+ fanoutRoutingLayers,
55626
+ onSolverStarted,
55627
+ onSolverEnded
55628
+ }) => {
55629
+ return new DogboneAutorouter(
55630
+ { input: simpleRouteJson, fanoutRoutingLayers },
55631
+ { onSolverStarted, onSolverEnded }
55632
+ );
55633
+ }
55634
+ }
55635
+ ],
54594
55636
  [
54595
55637
  "bus_lanes",
54596
55638
  {
@@ -61067,19 +62109,19 @@ import Debug12 from "debug";
61067
62109
  import { isValidElement as isValidElement2 } from "react";
61068
62110
 
61069
62111
  // lib/components/primitive-components/Group/Group_doInitialSchematicTraceRender/segment-intersection.ts
61070
- var cross3 = (ax, ay, bx, by) => {
62112
+ var cross4 = (ax, ay, bx, by) => {
61071
62113
  return ax * by - ay * bx;
61072
62114
  };
61073
62115
  function segmentIntersection(p1, p2, q1, q2, tol) {
61074
62116
  const r = { x: p2.x - p1.x, y: p2.y - p1.y };
61075
62117
  const s = { x: q2.x - q1.x, y: q2.y - q1.y };
61076
- const rxs = cross3(r.x, r.y, s.x, s.y);
62118
+ const rxs = cross4(r.x, r.y, s.x, s.y);
61077
62119
  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);
62120
+ const q_pxr = cross4(q_p.x, q_p.y, r.x, r.y);
61079
62121
  if (Math.abs(rxs) < tol && Math.abs(q_pxr) < tol) return null;
61080
62122
  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;
62123
+ const t = cross4(q_p.x, q_p.y, s.x, s.y) / rxs;
62124
+ const u = cross4(q_p.x, q_p.y, r.x, r.y) / rxs;
61083
62125
  if (t < -tol || t > 1 + tol || u < -tol || u > 1 + tol) return null;
61084
62126
  return { x: p1.x + t * r.x, y: p1.y + t * r.y };
61085
62127
  }
@@ -61272,8 +62314,8 @@ function isParallel(e1, e2, tol = TOL2) {
61272
62314
  const L1 = Math.hypot(v1.x, v1.y);
61273
62315
  const L2 = Math.hypot(v2.x, v2.y);
61274
62316
  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;
62317
+ const cross5 = v1.x * v2.y - v1.y * v2.x;
62318
+ return Math.abs(cross5) <= tol * L1 * L2;
61277
62319
  }
61278
62320
  function incidentEdgesAtPoint(trace, p, tol = TOL2) {
61279
62321
  return trace.edges.filter(
@@ -64540,6 +65582,16 @@ function Group_getRoutingPhasePlans(group) {
64540
65582
  nets: nets.filter((net) => breakoutByNet.get(net) === breakout),
64541
65583
  traces: breakoutTraces
64542
65584
  };
65585
+ if (breakout.isDogboneFanout && breakoutProps2.pcbTracePaths === void 0) {
65586
+ breakoutPlans.push({
65587
+ ...breakoutPlan,
65588
+ // Keep the local fanout outline while allowing dogbone sites outside
65589
+ // the pad bounding box to use the full board routing bounds.
65590
+ routingBounds: void 0,
65591
+ fanoutBounds: routingBounds
65592
+ });
65593
+ continue;
65594
+ }
64543
65595
  if (breakout._parsedProps.pcbTracePaths !== void 0) {
64544
65596
  breakoutPlans.push({
64545
65597
  ...breakoutPlan,
@@ -64612,7 +65664,7 @@ function Group_getRoutingPhasePlans(group) {
64612
65664
  var package_default = {
64613
65665
  name: "@tscircuit/core",
64614
65666
  type: "module",
64615
- version: "0.0.2024",
65667
+ version: "0.0.2026",
64616
65668
  types: "dist/index.d.ts",
64617
65669
  main: "dist/index.js",
64618
65670
  module: "dist/index.js",
@@ -64653,6 +65705,7 @@ var package_default = {
64653
65705
  "@tscircuit/common": "^0.0.20",
64654
65706
  "@tscircuit/copper-pour-solver": "^0.0.62",
64655
65707
  "@tscircuit/create-fdm-enclosure": "0.0.4",
65708
+ "@tscircuit/dogbone-solver": "https://jscdn.tscircuit.com/@tscircuit/dogbone-solver/0.0.1.tgz",
64656
65709
  "@tscircuit/fanout-solver": "0.0.78",
64657
65710
  "@tscircuit/flex-utils": "https://jscdn.tscircuit.com/@tscircuit/flex-utils/0.0.5.tgz",
64658
65711
  "@tscircuit/footprinter": "^0.0.426",
@@ -65080,7 +66133,6 @@ function Group_syncFanoutExitsWithGlobalConnections({
65080
66133
  ) ? [{ globalConnection, pointIndex, previousPoint }] : []
65081
66134
  )
65082
66135
  );
65083
- if (globalPointMatches.length === 0) continue;
65084
66136
  const changedPointIndex = outputConnection.pointsToConnect.findIndex(
65085
66137
  (outputPoint, pointIndex) => {
65086
66138
  const inputPoint = inputConnection.pointsToConnect[pointIndex];
@@ -65089,6 +66141,14 @@ function Group_syncFanoutExitsWithGlobalConnections({
65089
66141
  );
65090
66142
  if (changedPointIndex < 0) continue;
65091
66143
  const fanoutExitPoint = outputConnection.pointsToConnect[changedPointIndex];
66144
+ if (globalPointMatches.length === 0) {
66145
+ synchronizedBreakoutPoints.push({
66146
+ sourceTraceId: inputConnection.source_trace_id,
66147
+ routingPcbGroupId,
66148
+ previousPoint: inputConnection.pointsToConnect[changedPointIndex],
66149
+ fanoutExitPoint
66150
+ });
66151
+ }
65092
66152
  for (const {
65093
66153
  globalConnection,
65094
66154
  pointIndex,
@@ -65349,7 +66409,7 @@ function getAccumulatedPcbTracesWithStageOutputReplacements({
65349
66409
  }
65350
66410
 
65351
66411
  // 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";
66412
+ import { distance as distance17, pointToSegmentDistance as pointToSegmentDistance3 } from "@tscircuit/math-utils";
65353
66413
  import { ConnectivityMap as ConnectivityMap5 } from "circuit-json-to-connectivity-map";
65354
66414
  var POINT_EPSILON2 = 1e-6;
65355
66415
  function isPointOnWireSegment({
@@ -65371,7 +66431,7 @@ function isPointInSmtPad({
65371
66431
  return Math.abs(point6.x - pad.x) <= pad.width / 2 + traceWidth / 2 && Math.abs(point6.y - pad.y) <= pad.height / 2 + traceWidth / 2;
65372
66432
  }
65373
66433
  if (pad.shape === "circle") {
65374
- return distance15(point6, pad) <= pad.radius + traceWidth / 2;
66434
+ return distance17(point6, pad) <= pad.radius + traceWidth / 2;
65375
66435
  }
65376
66436
  return false;
65377
66437
  }
@@ -65380,7 +66440,7 @@ function isPointInPlatedHole({
65380
66440
  hole,
65381
66441
  traceWidth = 0
65382
66442
  }) {
65383
- return distance15(point6, hole) <= (hole.outer_diameter ?? hole.hole_diameter ?? 0) / 2 + traceWidth / 2;
66443
+ return distance17(point6, hole) <= (hole.outer_diameter ?? hole.hole_diameter ?? 0) / 2 + traceWidth / 2;
65384
66444
  }
65385
66445
  function getEndpointSourcePortIdsFromGeometry(db, trace) {
65386
66446
  const sourcePortIds = /* @__PURE__ */ new Set();
@@ -65393,7 +66453,7 @@ function getEndpointSourcePortIdsFromGeometry(db, trace) {
65393
66453
  if (!pcbPort.source_port_id) continue;
65394
66454
  if (endpoint.layer && pcbPort.layers && !pcbPort.layers.includes(endpoint.layer))
65395
66455
  continue;
65396
- if (distance15(endpoint, pcbPort) <= 0.01) {
66456
+ if (distance17(endpoint, pcbPort) <= 0.01) {
65397
66457
  sourcePortIds.add(pcbPort.source_port_id);
65398
66458
  }
65399
66459
  }
@@ -66077,8 +67137,8 @@ var Group5 = class extends NormalComponent {
66077
67137
  const groupProps2 = props;
66078
67138
  const hasOutline = groupProps2.outline && groupProps2.outline.length > 0;
66079
67139
  const numericOutline = hasOutline ? groupProps2.outline.map((point6) => ({
66080
- x: distance16.parse(point6.x),
66081
- y: distance16.parse(point6.y)
67140
+ x: distance18.parse(point6.x),
67141
+ y: distance18.parse(point6.y)
66082
67142
  })) : void 0;
66083
67143
  const ctx = this.props;
66084
67144
  const anchorPosition = this._getGlobalPcbPositionBeforeLayout();
@@ -66116,8 +67176,8 @@ var Group5 = class extends NormalComponent {
66116
67176
  const hasExplicitPositioning = this._parsedProps.pcbX !== void 0 || this._parsedProps.pcbY !== void 0;
66117
67177
  if (hasOutline) {
66118
67178
  const numericOutline = props.outline.map((point6) => ({
66119
- x: distance16.parse(point6.x),
66120
- y: distance16.parse(point6.y)
67179
+ x: distance18.parse(point6.x),
67180
+ y: distance18.parse(point6.y)
66121
67181
  }));
66122
67182
  const outlineBounds = getBoundsFromPoints9(numericOutline);
66123
67183
  if (!outlineBounds) return;
@@ -66518,7 +67578,9 @@ var Group5 = class extends NormalComponent {
66518
67578
  routingPhaseDebugLabels.set(plan, `${label} ${ordinal}`);
66519
67579
  }
66520
67580
  const hasFanoutStage = routingStages.some(
66521
- ({ autorouterConfig: autorouterConfig2 }) => ["fanout", "single_layer_fanout"].includes(autorouterConfig2.preset ?? "")
67581
+ ({ autorouterConfig: autorouterConfig2 }) => ["fanout", "single_layer_fanout", "dogbone"].includes(
67582
+ autorouterConfig2.preset ?? ""
67583
+ )
66522
67584
  );
66523
67585
  const fanoutPourNetMap = hasFanoutStage ? Group_getFanoutPourNetMap(this, routingPhasePlans) : void 0;
66524
67586
  let { simpleRouteJson: baseSimpleRouteJson } = getSimpleRouteJsonFromCircuitJson({
@@ -66941,6 +68003,11 @@ var Group5 = class extends NormalComponent {
66941
68003
  } else {
66942
68004
  autorouter = localAutorouterStrategy.create({
66943
68005
  simpleRouteJson,
68006
+ onSolverEnded: (event) => this.root?.emit("solver:ended", {
68007
+ ...event,
68008
+ type: "solver:ended",
68009
+ componentName: this.getString()
68010
+ }),
66944
68011
  commonAutorouterOptions,
66945
68012
  busFanoutDirections: routingPhasePlan.busFanoutDirections,
66946
68013
  fanoutBounds: routingPhasePlan.fanoutBounds,
@@ -66996,7 +68063,7 @@ var Group5 = class extends NormalComponent {
66996
68063
  traces = await routingPromise;
66997
68064
  }
66998
68065
  let transformedSimpleRouteJson = precomputedOutputSimpleRouteJson ?? autorouter?.getOutputSimpleRouteJson?.();
66999
- if (transformedSimpleRouteJson && !usesPreviousStageOutput && ["fanout", "single_layer_fanout"].includes(
68066
+ if (transformedSimpleRouteJson && !usesPreviousStageOutput && ["fanout", "single_layer_fanout", "dogbone"].includes(
67000
68067
  phaseAutorouterConfig.preset ?? ""
67001
68068
  ) && routingPhasePlan.routingPcbGroupId) {
67002
68069
  const synchronizedFanout = Group_syncFanoutExitsWithGlobalConnections(
@@ -67016,10 +68083,14 @@ var Group5 = class extends NormalComponent {
67016
68083
  if (!breakoutPoint) continue;
67017
68084
  db.pcb_breakout_point.update(breakoutPoint.pcb_breakout_point_id, {
67018
68085
  x: synchronizedPoint.fanoutExitPoint.x,
67019
- y: synchronizedPoint.fanoutExitPoint.y
68086
+ y: synchronizedPoint.fanoutExitPoint.y,
68087
+ layer: synchronizedPoint.fanoutExitPoint.layer
67020
68088
  });
67021
68089
  }
67022
68090
  }
68091
+ if (localAutorouterStrategy.preserveOutputTraces) {
68092
+ for (const trace of traces) fixedTraceIds.add(trace.pcb_trace_id);
68093
+ }
67023
68094
  let stageOutputTraces = traces;
67024
68095
  if (transformedSimpleRouteJson?.traces) {
67025
68096
  stageOutputTraces = transformedSimpleRouteJson.traces;
@@ -67052,11 +68123,13 @@ var Group5 = class extends NormalComponent {
67052
68123
  });
67053
68124
  }
67054
68125
  const savedPhasePaths = getAutoroutingPhasePcbTracePaths({
67055
- group: routingPhasePlan.autoroutingPhase?.getGroup() ?? this,
68126
+ group: this.selectAll("group").find(
68127
+ (group) => group.pcb_group_id === routingPhasePlan.fanoutRegionPcbGroupId
68128
+ ) ?? routingPhasePlan.autoroutingPhase?.getGroup() ?? this,
67056
68129
  subcircuit: this,
67057
68130
  input: simpleRouteJson,
67058
68131
  traces,
67059
- isFanout: ["fanout", "single_layer_fanout"].includes(
68132
+ isFanout: ["fanout", "single_layer_fanout", "dogbone"].includes(
67060
68133
  phaseAutorouterConfig.preset ?? ""
67061
68134
  )
67062
68135
  });
@@ -67989,8 +69062,8 @@ var Led_addPolarityFabricationSymbol = (led) => {
67989
69062
  const a = db.pcb_port.getWhere({ source_port_id: anode.source_port_id });
67990
69063
  const k = db.pcb_port.getWhere({ source_port_id: cathode.source_port_id });
67991
69064
  if (!a || !k) return;
67992
- const distance23 = Math.hypot(k.x - a.x, k.y - a.y);
67993
- if (distance23 < 1e-6) return;
69065
+ const distance25 = Math.hypot(k.x - a.x, k.y - a.y);
69066
+ if (distance25 < 1e-6) return;
67994
69067
  const angle = Math.atan2(k.y - a.y, k.x - a.x);
67995
69068
  const frame = compose15(
67996
69069
  translate15((a.x + k.x) / 2, (a.y + k.y) / 2),
@@ -68004,10 +69077,10 @@ var Led_addPolarityFabricationSymbol = (led) => {
68004
69077
  };
68005
69078
  const notes = [];
68006
69079
  generatedNotes.set(led, notes);
68007
- const halfLength = Math.min(distance23 * 0.2, 0.4);
69080
+ const halfLength = Math.min(distance25 * 0.2, 0.4);
68008
69081
  const halfHeight = halfLength * 0.75;
68009
69082
  const routes = [
68010
- [point6(-distance23 / 2, 0), point6(-halfLength, 0)],
69083
+ [point6(-distance25 / 2, 0), point6(-halfLength, 0)],
68011
69084
  [
68012
69085
  point6(-halfLength, -halfHeight),
68013
69086
  point6(halfLength, 0),
@@ -68015,7 +69088,7 @@ var Led_addPolarityFabricationSymbol = (led) => {
68015
69088
  point6(-halfLength, -halfHeight)
68016
69089
  ],
68017
69090
  [point6(halfLength, -halfHeight), point6(halfLength, halfHeight)],
68018
- [point6(halfLength, 0), point6(distance23 / 2, 0)]
69091
+ [point6(halfLength, 0), point6(distance25 / 2, 0)]
68019
69092
  ];
68020
69093
  for (const route of routes) {
68021
69094
  notes.push(
@@ -69949,7 +71022,7 @@ var Board_doInitialPcbPlacementDesignRuleChecks = (board) => {
69949
71022
 
69950
71023
  // lib/components/normal-components/Board/board-castellated-hole.ts
69951
71024
  import {
69952
- distance as distance17
71025
+ distance as distance19
69953
71026
  } from "circuit-json";
69954
71027
  var CASTELLATED_HOLE_ENDPOINT_TOLERANCE_MM = 1e-6;
69955
71028
  var getConnectionTargets2 = (connectsTo) => {
@@ -69970,8 +71043,8 @@ var BoardCastellatedHole = class _BoardCastellatedHole extends PlatedHole {
69970
71043
  constructor(outlinePoint, outlinePointIndex) {
69971
71044
  const name = `castellated_hole_${outlinePointIndex + 1}`;
69972
71045
  const connectionTargets = getConnectionTargets2(outlinePoint.connectsTo);
69973
- const holeDiameter = distance17.parse(outlinePoint.holeDiameter);
69974
- const padDiameter = distance17.parse(outlinePoint.padDiameter);
71046
+ const holeDiameter = distance19.parse(outlinePoint.holeDiameter);
71047
+ const padDiameter = distance19.parse(outlinePoint.padDiameter);
69975
71048
  super({
69976
71049
  shape: "circle",
69977
71050
  holeDiameter,
@@ -70721,11 +71794,11 @@ var MountedBoard = class extends Subcircuit {
70721
71794
 
70722
71795
  // lib/components/normal-components/Panel.ts
70723
71796
  import { panelProps } from "@tscircuit/props";
70724
- import { distance as distance21 } from "circuit-json";
71797
+ import { distance as distance23 } from "circuit-json";
70725
71798
 
70726
71799
  // lib/components/normal-components/Subpanel.ts
70727
71800
  import { subpanelProps } from "@tscircuit/props";
70728
- import { distance as distance20 } from "circuit-json";
71801
+ import { distance as distance22 } from "circuit-json";
70729
71802
  import { compose as compose17, identity as identity9, translate as translate17 } from "transformation-matrix";
70730
71803
 
70731
71804
  // lib/utils/panels/generate-panel-tabs-and-mouse-bites.ts
@@ -70989,15 +72062,15 @@ function generatePanelOutlineRoutingCutouts(boards, options) {
70989
72062
  }
70990
72063
 
70991
72064
  // lib/utils/panels/pack-into-grid.ts
70992
- import { distance as distance19 } from "circuit-json";
72065
+ import { distance as distance21 } from "circuit-json";
70993
72066
 
70994
72067
  // lib/utils/panels/get-board-dimensions-from-props.ts
70995
72068
  import { getBoundsFromPoints as getBoundsFromPoints11 } from "@tscircuit/math-utils";
70996
- import { distance as distance18 } from "circuit-json";
72069
+ import { distance as distance20 } from "circuit-json";
70997
72070
  var getBoardDimensionsFromProps = (board) => {
70998
72071
  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;
72072
+ let width = props.width != null ? distance20.parse(props.width) : void 0;
72073
+ let height = props.height != null ? distance20.parse(props.height) : void 0;
71001
72074
  if ((width === void 0 || height === void 0) && props.outline?.length) {
71002
72075
  const outlineBounds = getBoundsFromPoints11(props.outline);
71003
72076
  if (outlineBounds) {
@@ -71041,8 +72114,8 @@ function packIntoGrid({
71041
72114
  }
71042
72115
  let cols;
71043
72116
  let rows;
71044
- const minCellWidth = cellWidth ? distance19.parse(cellWidth) : 0;
71045
- const minCellHeight = cellHeight ? distance19.parse(cellHeight) : 0;
72117
+ const minCellWidth = cellWidth ? distance21.parse(cellWidth) : 0;
72118
+ const minCellHeight = cellHeight ? distance21.parse(cellHeight) : 0;
71046
72119
  if (col !== void 0) {
71047
72120
  cols = col;
71048
72121
  rows = row ?? Math.ceil(itemsWithDims.length / cols);
@@ -71141,8 +72214,8 @@ function getItemDimensions(item, db) {
71141
72214
  const props = subpanel._parsedProps;
71142
72215
  if (props.width !== void 0 && props.height !== void 0) {
71143
72216
  return {
71144
- width: distance19.parse(props.width),
71145
- height: distance19.parse(props.height)
72217
+ width: distance21.parse(props.width),
72218
+ height: distance21.parse(props.height)
71146
72219
  };
71147
72220
  }
71148
72221
  const directBoards = subpanel._getDirectBoardChildren();
@@ -71169,7 +72242,7 @@ function getItemDimensions(item, db) {
71169
72242
  return getBoardDimensionsFromProps(directBoards[0]);
71170
72243
  }
71171
72244
  if (subpanel._cachedGridWidth > 0 && subpanel._cachedGridHeight > 0) {
71172
- const edgePadding = distance19.parse(props.edgePadding ?? 5);
72245
+ const edgePadding = distance21.parse(props.edgePadding ?? 5);
71173
72246
  return {
71174
72247
  width: subpanel._cachedGridWidth + edgePadding * 2,
71175
72248
  height: subpanel._cachedGridHeight + edgePadding * 2
@@ -71327,17 +72400,17 @@ var Subpanel = class _Subpanel extends Group5 {
71327
72400
  edgePaddingTop: edgePaddingTopProp,
71328
72401
  edgePaddingBottom: edgePaddingBottomProp
71329
72402
  } = this._parsedProps;
71330
- const edgePadding = distance20.parse(edgePaddingProp ?? 5);
71331
- const edgePaddingLeft = distance20.parse(edgePaddingLeftProp ?? edgePadding);
71332
- const edgePaddingRight = distance20.parse(
72403
+ const edgePadding = distance22.parse(edgePaddingProp ?? 5);
72404
+ const edgePaddingLeft = distance22.parse(edgePaddingLeftProp ?? edgePadding);
72405
+ const edgePaddingRight = distance22.parse(
71333
72406
  edgePaddingRightProp ?? edgePadding
71334
72407
  );
71335
- const edgePaddingTop = distance20.parse(edgePaddingTopProp ?? edgePadding);
71336
- const edgePaddingBottom = distance20.parse(
72408
+ const edgePaddingTop = distance22.parse(edgePaddingTopProp ?? edgePadding);
72409
+ const edgePaddingBottom = distance22.parse(
71337
72410
  edgePaddingBottomProp ?? edgePadding
71338
72411
  );
71339
- const panelWidth = distance20.parse(this._parsedProps.width);
71340
- const panelHeight = distance20.parse(this._parsedProps.height);
72412
+ const panelWidth = distance22.parse(this._parsedProps.width);
72413
+ const panelHeight = distance22.parse(this._parsedProps.height);
71341
72414
  availablePanelWidth = panelWidth - edgePaddingLeft - edgePaddingRight;
71342
72415
  availablePanelHeight = panelHeight - edgePaddingTop - edgePaddingBottom;
71343
72416
  }
@@ -71412,8 +72485,8 @@ var Subpanel = class _Subpanel extends Group5 {
71412
72485
  if (!this.pcb_group_id) return;
71413
72486
  if (hasExplicitWidth && hasExplicitHeight) {
71414
72487
  db.pcb_group.update(this.pcb_group_id, {
71415
- width: distance20.parse(this._parsedProps.width),
71416
- height: distance20.parse(this._parsedProps.height)
72488
+ width: distance22.parse(this._parsedProps.width),
72489
+ height: distance22.parse(this._parsedProps.height)
71417
72490
  });
71418
72491
  } else if (gridWidth > 0 || gridHeight > 0) {
71419
72492
  const {
@@ -71423,18 +72496,18 @@ var Subpanel = class _Subpanel extends Group5 {
71423
72496
  edgePaddingTop: edgePaddingTopProp,
71424
72497
  edgePaddingBottom: edgePaddingBottomProp
71425
72498
  } = this._parsedProps;
71426
- const edgePadding = distance20.parse(edgePaddingProp ?? 5);
71427
- const edgePaddingLeft = distance20.parse(edgePaddingLeftProp ?? edgePadding);
71428
- const edgePaddingRight = distance20.parse(
72499
+ const edgePadding = distance22.parse(edgePaddingProp ?? 5);
72500
+ const edgePaddingLeft = distance22.parse(edgePaddingLeftProp ?? edgePadding);
72501
+ const edgePaddingRight = distance22.parse(
71429
72502
  edgePaddingRightProp ?? edgePadding
71430
72503
  );
71431
- const edgePaddingTop = distance20.parse(edgePaddingTopProp ?? edgePadding);
71432
- const edgePaddingBottom = distance20.parse(
72504
+ const edgePaddingTop = distance22.parse(edgePaddingTopProp ?? edgePadding);
72505
+ const edgePaddingBottom = distance22.parse(
71433
72506
  edgePaddingBottomProp ?? edgePadding
71434
72507
  );
71435
72508
  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
72509
+ width: hasExplicitWidth ? distance22.parse(this._parsedProps.width) : gridWidth + edgePaddingLeft + edgePaddingRight,
72510
+ height: hasExplicitHeight ? distance22.parse(this._parsedProps.height) : gridHeight + edgePaddingTop + edgePaddingBottom
71438
72511
  });
71439
72512
  }
71440
72513
  }
@@ -71522,8 +72595,8 @@ var Panel = class extends Subpanel {
71522
72595
  const { db } = this.root;
71523
72596
  const props = this._parsedProps;
71524
72597
  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,
72598
+ width: props.width !== void 0 ? distance23.parse(props.width) : 0,
72599
+ height: props.height !== void 0 ? distance23.parse(props.height) : 0,
71527
72600
  thickness: 1.6,
71528
72601
  center: this._getGlobalPcbPositionBeforeLayout(),
71529
72602
  covered_with_solder_mask: !(props.noSolderMask ?? false)
@@ -71542,8 +72615,8 @@ var Panel = class extends Subpanel {
71542
72615
  if (!this.pcb_panel_id) return;
71543
72616
  if (hasExplicitWidth && hasExplicitHeight) {
71544
72617
  db.pcb_panel.update(this.pcb_panel_id, {
71545
- width: distance21.parse(this._parsedProps.width),
71546
- height: distance21.parse(this._parsedProps.height)
72618
+ width: distance23.parse(this._parsedProps.width),
72619
+ height: distance23.parse(this._parsedProps.height)
71547
72620
  });
71548
72621
  } else if (gridWidth > 0 || gridHeight > 0) {
71549
72622
  const {
@@ -71553,18 +72626,18 @@ var Panel = class extends Subpanel {
71553
72626
  edgePaddingTop: edgePaddingTopProp,
71554
72627
  edgePaddingBottom: edgePaddingBottomProp
71555
72628
  } = this._parsedProps;
71556
- const edgePadding = distance21.parse(edgePaddingProp ?? 5);
71557
- const edgePaddingLeft = distance21.parse(edgePaddingLeftProp ?? edgePadding);
71558
- const edgePaddingRight = distance21.parse(
72629
+ const edgePadding = distance23.parse(edgePaddingProp ?? 5);
72630
+ const edgePaddingLeft = distance23.parse(edgePaddingLeftProp ?? edgePadding);
72631
+ const edgePaddingRight = distance23.parse(
71559
72632
  edgePaddingRightProp ?? edgePadding
71560
72633
  );
71561
- const edgePaddingTop = distance21.parse(edgePaddingTopProp ?? edgePadding);
71562
- const edgePaddingBottom = distance21.parse(
72634
+ const edgePaddingTop = distance23.parse(edgePaddingTopProp ?? edgePadding);
72635
+ const edgePaddingBottom = distance23.parse(
71563
72636
  edgePaddingBottomProp ?? edgePadding
71564
72637
  );
71565
72638
  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
72639
+ width: hasExplicitWidth ? distance23.parse(this._parsedProps.width) : gridWidth + edgePaddingLeft + edgePaddingRight,
72640
+ height: hasExplicitHeight ? distance23.parse(this._parsedProps.height) : gridHeight + edgePaddingTop + edgePaddingBottom
71568
72641
  });
71569
72642
  }
71570
72643
  }
@@ -71575,8 +72648,8 @@ var Panel = class extends Subpanel {
71575
72648
  const props = this._parsedProps;
71576
72649
  const currentPanel = db.pcb_panel.get(this.pcb_panel_id);
71577
72650
  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,
72651
+ width: props.width !== void 0 ? distance23.parse(props.width) : currentPanel?.width,
72652
+ height: props.height !== void 0 ? distance23.parse(props.height) : currentPanel?.height,
71580
72653
  center: this._getGlobalPcbPositionBeforeLayout(),
71581
72654
  covered_with_solder_mask: !(props.noSolderMask ?? false)
71582
72655
  });
@@ -73398,7 +74471,7 @@ var PcbNoteDimension = class extends PrimitiveComponent2 {
73398
74471
  import { breakoutProps } from "@tscircuit/props";
73399
74472
 
73400
74473
  // lib/components/primitive-components/AutoplacedBreakoutPoint.ts
73401
- import { layer_ref as layer_ref3 } from "circuit-json";
74474
+ import { layer_ref as layer_ref4 } from "circuit-json";
73402
74475
  var AutoplacedBreakoutPoint = class extends BaseBreakoutPoint {
73403
74476
  get config() {
73404
74477
  return {
@@ -73412,7 +74485,7 @@ var AutoplacedBreakoutPoint = class extends BaseBreakoutPoint {
73412
74485
  position
73413
74486
  }) {
73414
74487
  this.matchedSourceTraceId = sourceTraceId;
73415
- this._renderPcbBreakoutPoint({ layer: layer_ref3.parse(layer) });
74488
+ this._renderPcbBreakoutPoint({ layer: layer_ref4.parse(layer) });
73416
74489
  this._setPositionFromLayout(position);
73417
74490
  }
73418
74491
  };
@@ -74124,7 +75197,7 @@ var validateWindingBreakoutInput = (input) => {
74124
75197
  };
74125
75198
 
74126
75199
  // node_modules/@tscircuit/winding-breakout-point-solver/lib/solvers/GatePlacementSolver.ts
74127
- import { BaseSolver as BaseSolver7 } from "@tscircuit/solver-utils";
75200
+ import { BaseSolver as BaseSolver11 } from "@tscircuit/solver-utils";
74128
75201
 
74129
75202
  // node_modules/@tscircuit/winding-breakout-point-solver/lib/gate-placement/place-breakout-gates.ts
74130
75203
  var GEOMETRY_EPSILON = 1e-9;
@@ -74654,7 +75727,7 @@ var getSharedGateSlotCount = (points) => new Set(
74654
75727
  (point6) => `${point6.regionId}:${point6.x.toFixed(9)}:${point6.y.toFixed(9)}`
74655
75728
  )
74656
75729
  ).size;
74657
- var GatePlacementSolver = class extends BaseSolver7 {
75730
+ var GatePlacementSolver = class extends BaseSolver11 {
74658
75731
  constructor(params) {
74659
75732
  super();
74660
75733
  this.params = params;
@@ -74793,7 +75866,7 @@ var GatePlacementSolver = class extends BaseSolver7 {
74793
75866
  };
74794
75867
 
74795
75868
  // node_modules/@tscircuit/winding-breakout-point-solver/lib/solvers/ReferenceOrderingSolver.ts
74796
- import { BaseSolver as BaseSolver8 } from "@tscircuit/solver-utils";
75869
+ import { BaseSolver as BaseSolver12 } from "@tscircuit/solver-utils";
74797
75870
 
74798
75871
  // node_modules/@tscircuit/winding-breakout-point-solver/lib/ordering/get-natural-winding-order.ts
74799
75872
  var getNaturalWindingOrder = (region, connections) => connections.map((connection) => {
@@ -74860,7 +75933,7 @@ var getReferenceWindingOrder = (naturalOrder, atomicGroups, buses) => {
74860
75933
  };
74861
75934
 
74862
75935
  // node_modules/@tscircuit/winding-breakout-point-solver/lib/solvers/ReferenceOrderingSolver.ts
74863
- var ReferenceOrderingSolver = class extends BaseSolver8 {
75936
+ var ReferenceOrderingSolver = class extends BaseSolver12 {
74864
75937
  constructor(params) {
74865
75938
  super();
74866
75939
  this.params = params;
@@ -75912,6 +76985,10 @@ var solveImplicitBreakoutPoints = (breakout, solverFn) => {
75912
76985
 
75913
76986
  // lib/components/primitive-components/Breakout/Breakout.ts
75914
76987
  var Breakout = class extends Group5 {
76988
+ get isDogboneFanout() {
76989
+ const autorouter = this._parsedProps.autorouter;
76990
+ return (typeof autorouter === "string" ? autorouter : autorouter?.preset) === "dogbone";
76991
+ }
75915
76992
  get isRoutingDirective() {
75916
76993
  return true;
75917
76994
  }
@@ -75970,7 +77047,7 @@ var Breakout = class extends Group5 {
75970
77047
  for (const port of result.ports) {
75971
77048
  const isInside = breakoutPortSet.has(port);
75972
77049
  const hasOutsidePort = result.ports.some((p) => !breakoutPortSet.has(p));
75973
- if (!isInside || !hasOutsidePort) continue;
77050
+ if (!isInside || !hasOutsidePort && !this.isDogboneFanout) continue;
75974
77051
  if (manuallyMappedPorts.has(port)) continue;
75975
77052
  if (autoPlacedPorts.has(port)) continue;
75976
77053
  autoPlacedPorts.add(port);
@@ -75983,6 +77060,20 @@ var Breakout = class extends Group5 {
75983
77060
  }
75984
77061
  doInitialPcbAutoplaceBreakoutPoints() {
75985
77062
  if (this.root?.pcbDisabled) return;
77063
+ if (this.isDogboneFanout && this._parsedProps.pcbTracePaths === void 0) {
77064
+ for (const point6 of this.children) {
77065
+ if (!(point6 instanceof AutoplacedBreakoutPoint)) continue;
77066
+ const port = this.root.db.pcb_port.get(
77067
+ point6.matchedPort.pcb_port_id
77068
+ );
77069
+ point6._applySolvedBreakoutPoint({
77070
+ sourceTraceId: point6.matchedSourceTraceId,
77071
+ layer: port.layers[0],
77072
+ position: port
77073
+ });
77074
+ }
77075
+ return;
77076
+ }
75986
77077
  const ownAutorouter = this.props.autorouter;
75987
77078
  const inheritedAutorouter = ownAutorouter === void 0 ? this.parent?.getInheritedProperty("autorouter") : void 0;
75988
77079
  const implicitBreakoutPointSolverFn = typeof ownAutorouter === "object" ? ownAutorouter.implicitBreakoutPointSolverFn : typeof inheritedAutorouter === "object" ? inheritedAutorouter.implicitBreakoutPointSolverFn : void 0;
@@ -76205,7 +77296,7 @@ var SilkscreenGraphic = class extends PrimitiveComponent2 {
76205
77296
 
76206
77297
  // lib/components/primitive-components/Fiducial.ts
76207
77298
  import "zod";
76208
- import { distance as distance22 } from "circuit-json";
77299
+ import { distance as distance24 } from "circuit-json";
76209
77300
  import { fiducialProps as fiducialProps2 } from "@tscircuit/props";
76210
77301
  var Fiducial = class extends PrimitiveComponent2 {
76211
77302
  pcb_smtpad_id = null;
@@ -76230,15 +77321,15 @@ var Fiducial = class extends PrimitiveComponent2 {
76230
77321
  shape: "circle",
76231
77322
  x: position.x,
76232
77323
  y: position.y,
76233
- radius: distance22.parse(props.padDiameter) / 2,
76234
- soldermask_margin: props.soldermaskPullback ? distance22.parse(props.soldermaskPullback) : distance22.parse(props.padDiameter) / 2,
77324
+ radius: distance24.parse(props.padDiameter) / 2,
77325
+ soldermask_margin: props.soldermaskPullback ? distance24.parse(props.soldermaskPullback) : distance24.parse(props.padDiameter) / 2,
76235
77326
  is_covered_with_solder_mask: true
76236
77327
  });
76237
77328
  this.pcb_smtpad_id = pcb_smtpad.pcb_smtpad_id;
76238
77329
  }
76239
77330
  getPcbSize() {
76240
77331
  const { _parsedProps: props } = this;
76241
- const d = distance22.parse(props.padDiameter);
77332
+ const d = distance24.parse(props.padDiameter);
76242
77333
  return { width: d, height: d };
76243
77334
  }
76244
77335
  _setPositionFromLayout(newCenter) {
@@ -76282,15 +77373,15 @@ import {
76282
77373
  } from "@tscircuit/copper-pour-solver";
76283
77374
 
76284
77375
  // lib/components/primitive-components/CopperPour/utils/mark-trace-segments-inside-copper-pour.ts
76285
- var EPSILON24 = 1e-9;
77376
+ var EPSILON26 = 1e-9;
76286
77377
  var isWireRoutePoint = (routePoint) => routePoint.route_type === "wire";
76287
77378
  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;
77379
+ const cross5 = (p.y - a.y) * (b.x - a.x) - (p.x - a.x) * (b.y - a.y);
77380
+ if (Math.abs(cross5) > EPSILON26) return false;
76290
77381
  const dot = (p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y);
76291
- if (dot < -EPSILON24) return false;
77382
+ if (dot < -EPSILON26) return false;
76292
77383
  const squaredLength = (b.x - a.x) ** 2 + (b.y - a.y) ** 2;
76293
- if (dot - squaredLength > EPSILON24) return false;
77384
+ if (dot - squaredLength > EPSILON26) return false;
76294
77385
  return true;
76295
77386
  };
76296
77387
  var isPointInRing2 = (point6, ring) => {
@@ -76314,7 +77405,7 @@ var isPointInRectPour = (p, pour) => {
76314
77405
  const dy = p.y - center.y;
76315
77406
  const localX = dx * cosR - dy * sinR;
76316
77407
  const localY = dx * sinR + dy * cosR;
76317
- return Math.abs(localX) <= width / 2 + EPSILON24 && Math.abs(localY) <= height / 2 + EPSILON24;
77408
+ return Math.abs(localX) <= width / 2 + EPSILON26 && Math.abs(localY) <= height / 2 + EPSILON26;
76318
77409
  };
76319
77410
  var isPointInBrepPour = (p, pour) => {
76320
77411
  const outerRing = pour.brep_shape.outer_ring.vertices.map((v) => ({
@@ -76348,7 +77439,7 @@ var isSegmentFullyInsideCopperPour = (start, end, pour) => {
76348
77439
  const dx = end.x - start.x;
76349
77440
  const dy = end.y - start.y;
76350
77441
  const length8 = Math.hypot(dx, dy);
76351
- if (length8 <= EPSILON24) return false;
77442
+ if (length8 <= EPSILON26) return false;
76352
77443
  const samples = [0, 0.25, 0.5, 0.75, 1];
76353
77444
  return samples.every(
76354
77445
  (t) => isPointInCopperPour(