@tscircuit/core 0.0.1833 → 0.0.1834

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.
Files changed (2) hide show
  1. package/dist/index.js +994 -248
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -19989,6 +19989,312 @@ function getFanoutExitPositionConfig(exitPosition) {
19989
19989
 
19990
19990
  // node_modules/@tscircuit/fanout-solver/lib/fanout-solver.ts
19991
19991
  import { BaseSolver } from "@tscircuit/solver-utils";
19992
+
19993
+ // node_modules/@tscircuit/fanout-solver/lib/select-compatible-candidates.ts
19994
+ function selectCompatibleCandidates(params) {
19995
+ const candidates = params.candidateSets.flat();
19996
+ const count = candidates.length;
19997
+ const compatibility = new Uint8Array(count * count);
19998
+ const emptyDomainIndices = /* @__PURE__ */ new Set();
19999
+ const revisionParent = /* @__PURE__ */ new Map();
20000
+ let nextIndex = 0;
20001
+ const initialDomains = params.candidateSets.map(
20002
+ (set) => set.map(() => nextIndex++)
20003
+ );
20004
+ let searchStates = 0;
20005
+ const compatible = (first, second) => {
20006
+ const index = Math.min(first, second) * count + Math.max(first, second);
20007
+ const cached = compatibility[index];
20008
+ if (cached) return cached === 1;
20009
+ const result2 = params.areCompatible(candidates[first], candidates[second]);
20010
+ compatibility[index] = result2 ? 1 : 2;
20011
+ return result2;
20012
+ };
20013
+ const neighbors = initialDomains.map(() => []);
20014
+ for (let first = 0; first < initialDomains.length; first++) {
20015
+ for (let second = first + 1; second < initialDomains.length; second++) {
20016
+ if (initialDomains[first].some(
20017
+ (a) => initialDomains[second].some((b) => !compatible(a, b))
20018
+ )) {
20019
+ neighbors[first].push(second);
20020
+ neighbors[second].push(first);
20021
+ }
20022
+ }
20023
+ }
20024
+ const propagate = (domains, indices, changed) => {
20025
+ const queue = [];
20026
+ let allDomainsRemainNonempty = true;
20027
+ for (const first of indices) {
20028
+ if (domains[first].length === 0) {
20029
+ emptyDomainIndices.add(first);
20030
+ allDomainsRemainNonempty = false;
20031
+ continue;
20032
+ }
20033
+ for (const second of neighbors[first]) {
20034
+ if (indices.includes(second) && (changed === void 0 || second === changed))
20035
+ queue.push([first, second]);
20036
+ }
20037
+ }
20038
+ for (let cursor = 0; cursor < queue.length; cursor++) {
20039
+ const [first, second] = queue[cursor];
20040
+ if (domains[first].length === 0 || domains[second].length === 0)
20041
+ continue;
20042
+ const retained = domains[first].filter(
20043
+ (candidate) => domains[second].some((other) => compatible(candidate, other))
20044
+ );
20045
+ if (retained.length === domains[first].length) continue;
20046
+ revisionParent.set(first, second);
20047
+ domains[first] = retained;
20048
+ if (retained.length === 0) {
20049
+ emptyDomainIndices.add(first);
20050
+ allDomainsRemainNonempty = false;
20051
+ continue;
20052
+ }
20053
+ for (const neighbor of neighbors[first]) {
20054
+ if (neighbor !== second && indices.includes(neighbor))
20055
+ queue.push([neighbor, first]);
20056
+ }
20057
+ }
20058
+ return allDomainsRemainNonempty;
20059
+ };
20060
+ const allIndices = initialDomains.map((_, index) => index);
20061
+ const getConflictDomainIndices = (seeds) => {
20062
+ const pending = [...seeds];
20063
+ const seen = new Set(pending);
20064
+ for (let cursor = 0; cursor < pending.length; cursor++) {
20065
+ const parent = revisionParent.get(pending[cursor]);
20066
+ if (parent !== void 0 && !seen.has(parent)) {
20067
+ seen.add(parent);
20068
+ pending.push(parent);
20069
+ }
20070
+ }
20071
+ return [...seen];
20072
+ };
20073
+ const rootDomains = initialDomains.map((domain) => [...domain]);
20074
+ if (!propagate(rootDomains, allIndices)) {
20075
+ return {
20076
+ selection: null,
20077
+ searchStates,
20078
+ emptyDomainIndices: [...emptyDomainIndices],
20079
+ conflictDomainIndices: getConflictDomainIndices([...emptyDomainIndices])
20080
+ };
20081
+ }
20082
+ const findLocallyCompatibleSelection = () => {
20083
+ const restartCount = 12;
20084
+ const stepsPerRestart = Math.max(
20085
+ 1,
20086
+ Math.min(5e3, Math.floor(params.maximumSearchStates / restartCount))
20087
+ );
20088
+ for (let restart = 0; restart < restartCount; restart++) {
20089
+ const selection = rootDomains.map(
20090
+ (domain, index) => domain[(restart * 17 + index * 7) % domain.length]
20091
+ );
20092
+ for (let step = 0; step < stepsPerRestart; step++) {
20093
+ const conflictCounts = selection.map(() => 0);
20094
+ for (let first = 0; first < selection.length; first++) {
20095
+ for (const second of neighbors[first]) {
20096
+ if (second <= first || compatible(selection[first], selection[second]))
20097
+ continue;
20098
+ conflictCounts[first]++;
20099
+ conflictCounts[second]++;
20100
+ }
20101
+ }
20102
+ const maximumConflictCount = Math.max(...conflictCounts);
20103
+ if (maximumConflictCount === 0) return selection;
20104
+ const conflicted = conflictCounts.map((conflicts, index) => ({ conflicts, index })).filter(({ conflicts }) => conflicts === maximumConflictCount);
20105
+ const selected = conflicted[(step + restart) % conflicted.length].index;
20106
+ const scored = rootDomains[selected].map((candidate, index) => ({
20107
+ candidate,
20108
+ index,
20109
+ conflicts: neighbors[selected].filter(
20110
+ (neighbor) => !compatible(candidate, selection[neighbor])
20111
+ ).length
20112
+ })).toSorted(
20113
+ (first, second) => first.conflicts - second.conflicts || (first.index - step - restart) % rootDomains[selected].length - (second.index - step - restart) % rootDomains[selected].length
20114
+ );
20115
+ selection[selected] = scored[0].candidate;
20116
+ }
20117
+ }
20118
+ return null;
20119
+ };
20120
+ const locallyCompatibleSelection = findLocallyCompatibleSelection();
20121
+ if (locallyCompatibleSelection) {
20122
+ return {
20123
+ selection: locallyCompatibleSelection.map((index) => candidates[index]),
20124
+ searchStates,
20125
+ emptyDomainIndices: [],
20126
+ conflictDomainIndices: []
20127
+ };
20128
+ }
20129
+ const search = (domains, indices, changed) => {
20130
+ if (!propagate(domains, indices, changed)) return null;
20131
+ const pending = new Set(
20132
+ indices.filter((index) => domains[index].length > 1)
20133
+ );
20134
+ const components = [];
20135
+ while (pending.size > 0) {
20136
+ const component = [pending.values().next().value];
20137
+ pending.delete(component[0]);
20138
+ for (let cursor = 0; cursor < component.length; cursor++) {
20139
+ for (const neighbor of neighbors[component[cursor]]) {
20140
+ if (pending.delete(neighbor)) component.push(neighbor);
20141
+ }
20142
+ }
20143
+ components.push(component);
20144
+ }
20145
+ if (components.length > 1) {
20146
+ let combined = domains;
20147
+ for (const component of components) {
20148
+ const solved = search(combined.slice(), component);
20149
+ if (!solved) return null;
20150
+ combined = solved;
20151
+ }
20152
+ return combined;
20153
+ }
20154
+ let selected = -1;
20155
+ for (const index of indices) {
20156
+ if (domains[index].length > 1 && (selected < 0 || domains[index].length < domains[selected].length))
20157
+ selected = index;
20158
+ }
20159
+ if (selected < 0) return domains;
20160
+ const orderedCandidates = domains[selected].map((candidate) => {
20161
+ const supportCounts = neighbors[selected].filter(
20162
+ (neighbor) => indices.includes(neighbor) && domains[neighbor].length > 1
20163
+ ).map(
20164
+ (neighbor) => domains[neighbor].filter((other) => compatible(candidate, other)).length
20165
+ );
20166
+ return {
20167
+ candidate,
20168
+ minimumSupport: Math.min(...supportCounts),
20169
+ totalSupport: supportCounts.reduce((sum, count2) => sum + count2, 0)
20170
+ };
20171
+ }).toSorted(
20172
+ (first, second) => second.minimumSupport - first.minimumSupport || second.totalSupport - first.totalSupport
20173
+ );
20174
+ for (const { candidate } of orderedCandidates) {
20175
+ if (searchStates >= params.maximumSearchStates) return null;
20176
+ searchStates++;
20177
+ const nextDomains = domains.slice();
20178
+ nextDomains[selected] = [candidate];
20179
+ const result2 = search(nextDomains, indices, selected);
20180
+ if (result2) return result2;
20181
+ }
20182
+ return null;
20183
+ };
20184
+ const result = search(rootDomains, allIndices);
20185
+ const diagnosticIndices = emptyDomainIndices.size > 0 || result ? [...emptyDomainIndices] : [
20186
+ allIndices.toSorted(
20187
+ (first, second) => neighbors[second].length - neighbors[first].length || rootDomains[first].length - rootDomains[second].length
20188
+ )[0]
20189
+ ];
20190
+ return {
20191
+ selection: result?.map((domain) => candidates[domain[0]]) ?? null,
20192
+ searchStates,
20193
+ emptyDomainIndices: diagnosticIndices,
20194
+ conflictDomainIndices: result ? [] : getConflictDomainIndices(diagnosticIndices)
20195
+ };
20196
+ }
20197
+
20198
+ // node_modules/@tscircuit/fanout-solver/lib/refine-adaptive-plane-reservations.ts
20199
+ var EPSILON5 = 1e-9;
20200
+ function refineAdaptivePlaneReservationCore(params) {
20201
+ const core = params.candidateBusIds.filter(
20202
+ (busId) => !params.activeBusIds.has(busId)
20203
+ );
20204
+ if (core.length < 2) return core;
20205
+ const terminalIndex = core.length - 1;
20206
+ const terminal = params.planeBuses.find(
20207
+ (bus) => bus.busId === core[terminalIndex]
20208
+ );
20209
+ const source = terminal?.connections[0]?.sourcePoint;
20210
+ if (!terminal || !source || terminal.connections.length !== 1 || terminal.termination.type !== "plane")
20211
+ return core;
20212
+ const terminalLayer = terminal.termination.layer;
20213
+ const perpendicularAxis = terminal.direction === "up" || terminal.direction === "down" ? "x" : "y";
20214
+ const parallelAxis = perpendicularAxis === "x" ? "y" : "x";
20215
+ const componentCoordinates = perpendicularAxis === "x" ? terminal.xCoordinates : terminal.yCoordinates;
20216
+ const pitch = perpendicularAxis === "x" ? terminal.pitchX : terminal.pitchY;
20217
+ if (componentCoordinates.length === 0 || !Number.isFinite(pitch) || pitch <= EPSILON5) {
20218
+ return core;
20219
+ }
20220
+ const componentCenter = (Math.min(...componentCoordinates) + Math.max(...componentCoordinates)) / 2;
20221
+ const inwardSign = Math.sign(componentCenter - source[perpendicularAxis]);
20222
+ if (inwardSign === 0) return core;
20223
+ const coreIds = new Set(core);
20224
+ const replacement = params.planeBuses.filter((bus) => {
20225
+ const candidateSource = bus.connections[0]?.sourcePoint;
20226
+ if (bus === terminal || bus.componentId !== terminal.componentId || bus.termination.type !== "plane" || bus.termination.layer !== terminalLayer || bus.direction !== terminal.direction || bus.connections.length !== 1 || !candidateSource || params.activeBusIds.has(bus.busId) || coreIds.has(bus.busId) || Math.abs(candidateSource[parallelAxis] - source[parallelAxis]) > EPSILON5) {
20227
+ return false;
20228
+ }
20229
+ const inwardDistance = (candidateSource[perpendicularAxis] - source[perpendicularAxis]) * inwardSign;
20230
+ return inwardDistance > EPSILON5 && inwardDistance <= pitch + EPSILON5;
20231
+ }).toSorted((first, second) => {
20232
+ const firstSource = first.connections[0].sourcePoint;
20233
+ const secondSource = second.connections[0].sourcePoint;
20234
+ return Math.abs(firstSource[perpendicularAxis] - source[perpendicularAxis]) - Math.abs(
20235
+ secondSource[perpendicularAxis] - source[perpendicularAxis]
20236
+ ) || first.connections[0].connectionIndex - second.connections[0].connectionIndex;
20237
+ })[0];
20238
+ if (!replacement) return core;
20239
+ return core.map(
20240
+ (busId, index) => index === terminalIndex ? replacement.busId : busId
20241
+ );
20242
+ }
20243
+
20244
+ // node_modules/@tscircuit/fanout-solver/lib/should-use-adaptive-dense-plane-routing.ts
20245
+ function getSourceFieldFacingEdge(buses) {
20246
+ const firstBus = buses[0];
20247
+ if (!firstBus || buses.some((bus) => bus.componentId !== firstBus.componentId))
20248
+ return void 0;
20249
+ const sourcePoints = buses.flatMap(
20250
+ (bus) => bus.connections.map((connection) => connection.sourcePoint)
20251
+ );
20252
+ if (sourcePoints.length === 0) return void 0;
20253
+ const center = sourcePoints.reduce(
20254
+ (sum, point6) => ({ x: sum.x + point6.x, y: sum.y + point6.y }),
20255
+ { x: 0, y: 0 }
20256
+ );
20257
+ center.x /= sourcePoints.length;
20258
+ center.y /= sourcePoints.length;
20259
+ const { minX, maxX, minY, maxY } = firstBus.componentBounds;
20260
+ const edgesByDistance = [
20261
+ { edge: "left", distance: Math.abs(center.x - minX) },
20262
+ { edge: "right", distance: Math.abs(maxX - center.x) },
20263
+ { edge: "bottom", distance: Math.abs(center.y - minY) },
20264
+ { edge: "top", distance: Math.abs(maxY - center.y) }
20265
+ ].toSorted((first, second) => first.distance - second.distance);
20266
+ if (Math.abs(edgesByDistance[0].distance - edgesByDistance[1].distance) <= 1e-9)
20267
+ return void 0;
20268
+ return edgesByDistance[0].edge;
20269
+ }
20270
+ function shouldUseAdaptiveDensePlaneRouting(buses, allowBlindAndBuriedVias) {
20271
+ if (allowBlindAndBuriedVias) return false;
20272
+ const boundaryBuses = buses.filter(
20273
+ (bus) => bus.termination.type === "boundary"
20274
+ );
20275
+ const singletonBoundaryBuses = boundaryBuses.filter(
20276
+ (bus) => bus.connections.length === 1
20277
+ );
20278
+ const pairBoundaryBuses = boundaryBuses.filter(
20279
+ (bus) => bus.connections.length === 2
20280
+ );
20281
+ const wideBoundaryBuses = boundaryBuses.filter(
20282
+ (bus) => bus.connections.length >= 8
20283
+ );
20284
+ const planeCount = buses.filter(
20285
+ (bus) => bus.termination.type === "plane" && bus.connections.length === 1
20286
+ ).length;
20287
+ const sharedExitEdge = wideBoundaryBuses[0]?.exitEdge;
20288
+ if (boundaryBuses.length !== 9 || singletonBoundaryBuses.length !== 3 || pairBoundaryBuses.length !== 3 || wideBoundaryBuses.length !== 3 || planeCount < 64 || sharedExitEdge === void 0 || wideBoundaryBuses.some((bus) => bus.exitEdge !== sharedExitEdge))
20289
+ return false;
20290
+ const sourceFacingEdge = getSourceFieldFacingEdge(wideBoundaryBuses);
20291
+ if (sourceFacingEdge === void 0) return false;
20292
+ const sourceFacesHorizontalEdge = sourceFacingEdge === "left" || sourceFacingEdge === "right";
20293
+ const exitUsesHorizontalEdge = sharedExitEdge === "left" || sharedExitEdge === "right";
20294
+ return sourceFacesHorizontalEdge !== exitUsesHorizontalEdge;
20295
+ }
20296
+
20297
+ // node_modules/@tscircuit/fanout-solver/lib/fanout-solver.ts
19992
20298
  import { mergeGraphics } from "graphics-debug";
19993
20299
 
19994
20300
  // node_modules/@tscircuit/fanout-solver/lib/add-via-layer-metadata.ts
@@ -20259,8 +20565,25 @@ function getAllRoutedTraceCopper(srj, allowBlindAndBuriedVias = true) {
20259
20565
  );
20260
20566
  }
20261
20567
 
20568
+ // node_modules/@tscircuit/fanout-solver/lib/get-dogbone-side-variants.ts
20569
+ function getDogboneSideVariants(connections, direction) {
20570
+ const axis = direction === "up" || direction === "down" ? "y" : "x";
20571
+ const variants = connections.slice(0, 32).map(({ connectionIndex }) => [connectionIndex]);
20572
+ for (let first = 0; first < connections.length; first++) {
20573
+ for (let second = first + 1; second < connections.length; second++) {
20574
+ if (variants.length >= 32) return variants;
20575
+ const a = connections[first];
20576
+ const b = connections[second];
20577
+ if (Math.abs(a.sourcePoint[axis] - b.sourcePoint[axis]) <= 1e-9) {
20578
+ variants.push([a.connectionIndex, b.connectionIndex]);
20579
+ }
20580
+ }
20581
+ }
20582
+ return variants;
20583
+ }
20584
+
20262
20585
  // node_modules/@tscircuit/fanout-solver/lib/match-component-dogbone-via-sites.ts
20263
- var EPSILON5 = 1e-9;
20586
+ var EPSILON6 = 1e-9;
20264
20587
  var DEFAULT_MAXIMUM_SEARCH_STATES = 1e5;
20265
20588
  function assertGeometryRules(rules) {
20266
20589
  for (const [name, value] of [
@@ -20305,7 +20628,7 @@ function uniqueSortedCoordinates(values) {
20305
20628
  const result = [];
20306
20629
  for (const value of values.toSorted((first, second) => first - second)) {
20307
20630
  if (!Number.isFinite(value)) continue;
20308
- if (result.length === 0 || Math.abs(result.at(-1) - value) > EPSILON5) {
20631
+ if (result.length === 0 || Math.abs(result.at(-1) - value) > EPSILON6) {
20309
20632
  result.push(value);
20310
20633
  }
20311
20634
  }
@@ -20332,10 +20655,10 @@ function getComponentMatchingInputs(preparedBuses) {
20332
20655
  }
20333
20656
  component.xCoordinates.push(...bus.xCoordinates);
20334
20657
  component.yCoordinates.push(...bus.yCoordinates);
20335
- if (Number.isFinite(bus.pitchX) && bus.pitchX > EPSILON5) {
20658
+ if (Number.isFinite(bus.pitchX) && bus.pitchX > EPSILON6) {
20336
20659
  component.pitchX = Math.min(component.pitchX, bus.pitchX);
20337
20660
  }
20338
- if (Number.isFinite(bus.pitchY) && bus.pitchY > EPSILON5) {
20661
+ if (Number.isFinite(bus.pitchY) && bus.pitchY > EPSILON6) {
20339
20662
  component.pitchY = Math.min(component.pitchY, bus.pitchY);
20340
20663
  }
20341
20664
  const obstacleSet = obstacleSetByComponentId.get(bus.componentId);
@@ -20382,7 +20705,7 @@ function getComponentMatchingInputs(preparedBuses) {
20382
20705
  }
20383
20706
  function getInterstitialCoordinates(params) {
20384
20707
  const { coordinates, pitch } = params;
20385
- if (coordinates.length === 0 || !Number.isFinite(pitch) || pitch <= EPSILON5) {
20708
+ if (coordinates.length === 0 || !Number.isFinite(pitch) || pitch <= EPSILON6) {
20386
20709
  return [];
20387
20710
  }
20388
20711
  const interstitialCoordinates = [coordinates[0] - pitch / 2];
@@ -20400,9 +20723,9 @@ function getAdjacentInterstitialCoordinates(params) {
20400
20723
  coordinates,
20401
20724
  pitch
20402
20725
  });
20403
- const before = interstitialCoordinates.filter((coordinate) => coordinate < sourceCoordinate - EPSILON5).at(-1);
20726
+ const before = interstitialCoordinates.filter((coordinate) => coordinate < sourceCoordinate - EPSILON6).at(-1);
20404
20727
  const after = interstitialCoordinates.find(
20405
- (coordinate) => coordinate > sourceCoordinate + EPSILON5
20728
+ (coordinate) => coordinate > sourceCoordinate + EPSILON6
20406
20729
  );
20407
20730
  return [before, after].filter(
20408
20731
  (coordinate) => coordinate !== void 0
@@ -20411,25 +20734,25 @@ function getAdjacentInterstitialCoordinates(params) {
20411
20734
  function directSegmentIsStraightOr45(start, end) {
20412
20735
  const absoluteX = Math.abs(end.x - start.x);
20413
20736
  const absoluteY = Math.abs(end.y - start.y);
20414
- return absoluteX <= EPSILON5 || absoluteY <= EPSILON5 || Math.abs(absoluteX - absoluteY) <= EPSILON5;
20737
+ return absoluteX <= EPSILON6 || absoluteY <= EPSILON6 || Math.abs(absoluteX - absoluteY) <= EPSILON6;
20415
20738
  }
20416
20739
  function getOutwardRank(params) {
20417
20740
  const { source, site, direction } = params;
20418
20741
  const outwardDisplacement = direction === "right" ? site.x - source.x : direction === "left" ? source.x - site.x : direction === "up" ? site.y - source.y : source.y - site.y;
20419
- return outwardDisplacement > EPSILON5 ? 0 : Math.abs(outwardDisplacement) <= EPSILON5 ? 1 : 2;
20742
+ return outwardDisplacement > EPSILON6 ? 0 : Math.abs(outwardDisplacement) <= EPSILON6 ? 1 : 2;
20420
20743
  }
20421
20744
  function viaSiteClearsObstacles(params) {
20422
20745
  const { point: point6, obstacles, viaDiameter, clearance } = params;
20423
20746
  const requiredClearance = viaDiameter / 2 + clearance;
20424
20747
  return obstacles.every(
20425
- (obstacle) => distancePointToObstacle(point6, obstacle) >= requiredClearance - EPSILON5
20748
+ (obstacle) => distancePointToObstacle(point6, obstacle) >= requiredClearance - EPSILON6
20426
20749
  );
20427
20750
  }
20428
20751
  function sourceSegmentClearsOtherObstacles(params) {
20429
20752
  const { segment: segment2, sourceObstacle, obstacles, clearance } = params;
20430
20753
  const requiredClearance = segment2.width / 2 + clearance;
20431
20754
  return obstacles.every(
20432
- (obstacle) => obstacle === sourceObstacle || distanceSegmentToObstacle(segment2, obstacle) >= requiredClearance - EPSILON5
20755
+ (obstacle) => obstacle === sourceObstacle || distanceSegmentToObstacle(segment2, obstacle) >= requiredClearance - EPSILON6
20433
20756
  );
20434
20757
  }
20435
20758
  function getConnectionCandidates(params) {
@@ -20457,7 +20780,7 @@ function getConnectionCandidates(params) {
20457
20780
  ];
20458
20781
  const uniquePoints2 = [];
20459
20782
  for (const point6 of rawPoints) {
20460
- if (!uniquePoints2.some((candidate) => distance9(candidate, point6) <= EPSILON5)) {
20783
+ if (!uniquePoints2.some((candidate) => distance9(candidate, point6) <= EPSILON6)) {
20461
20784
  uniquePoints2.push(point6);
20462
20785
  }
20463
20786
  }
@@ -20508,7 +20831,7 @@ function getConnectionCandidates(params) {
20508
20831
  point6,
20509
20832
  blocker.segment.start,
20510
20833
  blocker.segment.end
20511
- ) < viaToTraceClearance - EPSILON5) {
20834
+ ) < viaToTraceClearance - EPSILON6) {
20512
20835
  return false;
20513
20836
  }
20514
20837
  return blocker.segment.layer !== sourceSegment.layer || segmentsAreClear(sourceSegment, blocker.segment, rules.clearance);
@@ -20520,14 +20843,14 @@ function getConnectionCandidates(params) {
20520
20843
  if (blocker.connectionIndex === preparedConnection.connectionIndex) {
20521
20844
  return true;
20522
20845
  }
20523
- if (distance9(point6, blocker.center) < (rules.viaDiameter + blocker.diameter) / 2 + rules.clearance - EPSILON5) {
20846
+ if (distance9(point6, blocker.center) < (rules.viaDiameter + blocker.diameter) / 2 + rules.clearance - EPSILON6) {
20524
20847
  return false;
20525
20848
  }
20526
20849
  if (blocker.spanLayers.includes(sourceSegment.layer) && distancePointToSegment(
20527
20850
  blocker.center,
20528
20851
  sourceSegment.start,
20529
20852
  sourceSegment.end
20530
- ) < blocker.diameter / 2 + sourceSegment.width / 2 + rules.clearance - EPSILON5) {
20853
+ ) < blocker.diameter / 2 + sourceSegment.width / 2 + rules.clearance - EPSILON6) {
20531
20854
  return false;
20532
20855
  }
20533
20856
  return true;
@@ -20544,7 +20867,7 @@ function getConnectionCandidates(params) {
20544
20867
  const perpendicularCoordinates = direction === "left" || direction === "right" ? component.yCoordinates : component.xCoordinates;
20545
20868
  const sourcePerpendicularCoordinate = direction === "left" || direction === "right" ? source.y : source.x;
20546
20869
  const perpendicularGridIndex = perpendicularCoordinates.findIndex(
20547
- (coordinate) => Math.abs(coordinate - sourcePerpendicularCoordinate) <= EPSILON5
20870
+ (coordinate) => Math.abs(coordinate - sourcePerpendicularCoordinate) <= EPSILON6
20548
20871
  );
20549
20872
  const planeCheckerboardSide = perpendicularGridIndex >= 0 ? perpendicularGridIndex % 2 === 0 ? 1 : -1 : void 0;
20550
20873
  const preferredPerpendicularSide = connection.terminationType === "boundary" ? rules.preferredBoundaryPerpendicularSideByBusId?.get(connection.busId) : rules.preferPlaneCheckerboardSites && connection.terminationType === "plane" && (direction === "left" || direction === "right") ? planeCheckerboardSide : void 0;
@@ -20555,10 +20878,10 @@ function getConnectionCandidates(params) {
20555
20878
  const getPerpendicularPreferenceRank = (candidate) => {
20556
20879
  if (preferredPerpendicularSide === void 0) return 0;
20557
20880
  const displacement = direction === "left" || direction === "right" ? candidate.point.y - source.y : candidate.point.x - source.x;
20558
- return displacement * preferredPerpendicularSide > EPSILON5 ? 0 : Math.abs(displacement) <= EPSILON5 ? 1 : 2;
20881
+ return displacement * preferredPerpendicularSide > EPSILON6 ? 0 : Math.abs(displacement) <= EPSILON6 ? 1 : 2;
20559
20882
  };
20560
20883
  return candidates.toSorted(
20561
- (first, second) => (preferredViaPoint ? Number(distance9(first.point, preferredViaPoint) > EPSILON5) - Number(distance9(second.point, preferredViaPoint) > EPSILON5) : 0) || (preferOutward ? first.outwardRank - second.outwardRank : second.outwardRank - first.outwardRank) || getPerpendicularPreferenceRank(first) - getPerpendicularPreferenceRank(second) || distance9(source, first.point) - distance9(source, second.point) || first.point.x - second.point.x || first.point.y - second.point.y
20884
+ (first, second) => (preferredViaPoint ? Number(distance9(first.point, preferredViaPoint) > EPSILON6) - Number(distance9(second.point, preferredViaPoint) > EPSILON6) : 0) || (preferOutward ? first.outwardRank - second.outwardRank : second.outwardRank - first.outwardRank) || getPerpendicularPreferenceRank(first) - getPerpendicularPreferenceRank(second) || distance9(source, first.point) - distance9(source, second.point) || first.point.x - second.point.x || first.point.y - second.point.y
20562
20885
  );
20563
20886
  }
20564
20887
  function candidatesAreMutuallyClear(params) {
@@ -20566,7 +20889,7 @@ function candidatesAreMutuallyClear(params) {
20566
20889
  const canShareCopper = rules.canShareCopper?.(first.connectionIndex, second.connectionIndex) ?? false;
20567
20890
  const requiredHoleSeparation = rules.viaHoleDiameter ? rules.viaHoleDiameter + (rules.holeToHoleClearance ?? rules.clearance) : 0;
20568
20891
  const requiredViaSeparation = canShareCopper ? requiredHoleSeparation : Math.max(rules.viaDiameter + rules.clearance, requiredHoleSeparation);
20569
- if (distance9(first.point, second.point) < requiredViaSeparation - EPSILON5) {
20892
+ if (distance9(first.point, second.point) < requiredViaSeparation - EPSILON6) {
20570
20893
  return false;
20571
20894
  }
20572
20895
  const requiredViaToTraceClearance = rules.viaDiameter / 2 + rules.traceWidth / 2 + rules.clearance;
@@ -20575,11 +20898,11 @@ function candidatesAreMutuallyClear(params) {
20575
20898
  first.point,
20576
20899
  second.sourceSegment.start,
20577
20900
  second.sourceSegment.end
20578
- ) < requiredViaToTraceClearance - EPSILON5 || distancePointToSegment(
20901
+ ) < requiredViaToTraceClearance - EPSILON6 || distancePointToSegment(
20579
20902
  second.point,
20580
20903
  first.sourceSegment.start,
20581
20904
  first.sourceSegment.end
20582
- ) < requiredViaToTraceClearance - EPSILON5) {
20905
+ ) < requiredViaToTraceClearance - EPSILON6) {
20583
20906
  return false;
20584
20907
  }
20585
20908
  }
@@ -20721,7 +21044,7 @@ function getComponentDogboneViaSiteCandidates(preparedBuses, rules) {
20721
21044
  }
20722
21045
 
20723
21046
  // node_modules/@tscircuit/fanout-solver/lib/route-via-minimal-winding.ts
20724
- var EPSILON6 = 1e-7;
21047
+ var EPSILON7 = 1e-7;
20725
21048
  var MAX_GRID_NODE_COUNT = 12e4;
20726
21049
  var MAX_EXPANDED_STATE_COUNT = 24e4;
20727
21050
  var EXPANDED_STATES_PER_STEP = 5e3;
@@ -20877,7 +21200,7 @@ function getConnectorVariants(start, end) {
20877
21200
  const deltaY = end.y - start.y;
20878
21201
  const absoluteX = Math.abs(deltaX);
20879
21202
  const absoluteY = Math.abs(deltaY);
20880
- if (absoluteX < EPSILON6 || absoluteY < EPSILON6 || Math.abs(absoluteX - absoluteY) < EPSILON6) {
21203
+ if (absoluteX < EPSILON7 || absoluteY < EPSILON7 || Math.abs(absoluteX - absoluteY) < EPSILON7) {
20881
21204
  return [[start, end]];
20882
21205
  }
20883
21206
  if (absoluteX > absoluteY) {
@@ -20912,18 +21235,18 @@ function compressPath(points) {
20912
21235
  function getSegments(points, width, layer) {
20913
21236
  return points.slice(1).flatMap((point6, index) => {
20914
21237
  const start = points[index];
20915
- return distance9(start, point6) < EPSILON6 ? [] : [{ start, end: point6, width, layer }];
21238
+ return distance9(start, point6) < EPSILON7 ? [] : [{ start, end: point6, width, layer }];
20916
21239
  });
20917
21240
  }
20918
21241
  function segmentIsStraightOr45Degrees(segment2) {
20919
21242
  const deltaX = Math.abs(segment2.end.x - segment2.start.x);
20920
21243
  const deltaY = Math.abs(segment2.end.y - segment2.start.y);
20921
- return deltaX < EPSILON6 || deltaY < EPSILON6 || Math.abs(deltaX - deltaY) < EPSILON6;
21244
+ return deltaX < EPSILON7 || deltaY < EPSILON7 || Math.abs(deltaX - deltaY) < EPSILON7;
20922
21245
  }
20923
21246
  function pathHasNoProperSelfCrossing(segments) {
20924
21247
  for (let firstIndex = 0; firstIndex < segments.length; firstIndex++) {
20925
21248
  for (let secondIndex = firstIndex + 2; secondIndex < segments.length; secondIndex++) {
20926
- if (firstIndex === 0 && secondIndex === segments.length - 1 && distance9(segments[firstIndex].start, segments[secondIndex].end) < EPSILON6) {
21249
+ if (firstIndex === 0 && secondIndex === segments.length - 1 && distance9(segments[firstIndex].start, segments[secondIndex].end) < EPSILON7) {
20927
21250
  continue;
20928
21251
  }
20929
21252
  if (distanceSegmentToSegment(
@@ -20931,7 +21254,7 @@ function pathHasNoProperSelfCrossing(segments) {
20931
21254
  segments[firstIndex].end,
20932
21255
  segments[secondIndex].start,
20933
21256
  segments[secondIndex].end
20934
- ) < EPSILON6) {
21257
+ ) < EPSILON7) {
20935
21258
  return false;
20936
21259
  }
20937
21260
  }
@@ -21007,7 +21330,7 @@ function buildPlan(params) {
21007
21330
  width: traceWidth,
21008
21331
  layer: connection.sourceLayer
21009
21332
  };
21010
- const hasSourceDogbone = distance9(sourcePoint, terminal.viaPoint) > EPSILON6;
21333
+ const hasSourceDogbone = distance9(sourcePoint, terminal.viaPoint) > EPSILON7;
21011
21334
  const changesLayer = connection.sourceLayer !== targetLayer;
21012
21335
  const targetSegments = getSegments(targetLayerPoints, traceWidth, targetLayer);
21013
21336
  const spanLayers = getViaSpanLayers2({
@@ -21131,7 +21454,8 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21131
21454
  preferTargetDirectedLaneBias = false,
21132
21455
  allowSourceLayerRouting = false,
21133
21456
  adaptiveRouteOrder = false,
21134
- alignGridToPads = false
21457
+ alignGridToPads = false,
21458
+ includeReverseTargetRotation = false
21135
21459
  } = params;
21136
21460
  if (maximumRouteOrderAttempts !== void 0 && (!Number.isInteger(maximumRouteOrderAttempts) || maximumRouteOrderAttempts < 1)) {
21137
21461
  throw new Error(
@@ -21192,6 +21516,18 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21192
21516
  const margin = (segment2.width + traceWidth) / 2 + clearance;
21193
21517
  return !(Math.max(segment2.start.x, segment2.end.x) < minX - margin || Math.min(segment2.start.x, segment2.end.x) > maxX + margin || Math.max(segment2.start.y, segment2.end.y) < minY - margin || Math.min(segment2.start.y, segment2.end.y) > maxY + margin);
21194
21518
  });
21519
+ if (allowSourceLayerRouting) {
21520
+ blockingSegments.push(
21521
+ ...reservedVias.flatMap(
21522
+ (reserved) => reserved.sourceEscapeSegment?.layer === targetLayer ? [
21523
+ {
21524
+ connectionName: reserved.connectionName,
21525
+ segment: reserved.sourceEscapeSegment
21526
+ }
21527
+ ] : []
21528
+ )
21529
+ );
21530
+ }
21195
21531
  const blockingVias = blockingCopper.vias.filter(({ via }) => {
21196
21532
  if (!via.spanLayers.includes(targetLayer)) return false;
21197
21533
  const margin = via.diameter / 2 + traceWidth / 2 + clearance;
@@ -21248,7 +21584,7 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21248
21584
  if (obstacle.connectedTo.includes(connectionName) || allowSameNetMerges && obstacleSharesElectricalNet(srj, obstacle, connectionName)) {
21249
21585
  continue;
21250
21586
  }
21251
- if (distanceSegmentToObstacle(segment2, obstacle) < requiredObstacleClearance - EPSILON6) {
21587
+ if (distanceSegmentToObstacle(segment2, obstacle) < requiredObstacleClearance - EPSILON7) {
21252
21588
  return false;
21253
21589
  }
21254
21590
  }
@@ -21259,7 +21595,7 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21259
21595
  segment2.end,
21260
21596
  blocker.segment.start,
21261
21597
  blocker.segment.end
21262
- ) < (segment2.width + blocker.segment.width) / 2 + clearance - EPSILON6) {
21598
+ ) < (segment2.width + blocker.segment.width) / 2 + clearance - EPSILON7) {
21263
21599
  return false;
21264
21600
  }
21265
21601
  }
@@ -21273,7 +21609,7 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21273
21609
  segment2.end,
21274
21610
  blocker.segment.start,
21275
21611
  blocker.segment.end
21276
- ) < (segment2.width + blocker.segment.width) / 2 + clearance - EPSILON6) {
21612
+ ) < (segment2.width + blocker.segment.width) / 2 + clearance - EPSILON7) {
21277
21613
  return false;
21278
21614
  }
21279
21615
  }
@@ -21293,7 +21629,7 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21293
21629
  if (blocker.via.center.x < segmentMinX - requiredDistance || blocker.via.center.x > segmentMaxX + requiredDistance || blocker.via.center.y < segmentMinY - requiredDistance || blocker.via.center.y > segmentMaxY + requiredDistance) {
21294
21630
  continue;
21295
21631
  }
21296
- if (distancePointToSegment(blocker.via.center, segment2.start, segment2.end) < requiredDistance - EPSILON6) {
21632
+ if (distancePointToSegment(blocker.via.center, segment2.start, segment2.end) < requiredDistance - EPSILON7) {
21297
21633
  return false;
21298
21634
  }
21299
21635
  }
@@ -21491,10 +21827,10 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21491
21827
  let expandedBatchPoints = [];
21492
21828
  while (heap.size > 0 && expandedStateCount < MAX_EXPANDED_STATE_COUNT) {
21493
21829
  const current = heap.pop();
21494
- if (current.score >= bestGoalCost - EPSILON6) break;
21830
+ if (current.score >= bestGoalCost - EPSILON7) break;
21495
21831
  const state = current.node * 9 + current.direction;
21496
21832
  const currentDistance = distances[state];
21497
- if (current.score > currentDistance + heuristic(nodes[current.node].point) + EPSILON6)
21833
+ if (current.score > currentDistance + heuristic(nodes[current.node].point) + EPSILON7)
21498
21834
  continue;
21499
21835
  expandedStateCount++;
21500
21836
  expandedStatesSinceYield++;
@@ -21522,7 +21858,7 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21522
21858
  for (const startConnector of startConnectors) {
21523
21859
  for (const endConnector of endConnectors) {
21524
21860
  const candidateCost = currentDistance - shortestStartLength + startConnector.length + endConnector.length;
21525
- if (candidateCost >= bestGoalCost - EPSILON6) continue;
21861
+ if (candidateCost >= bestGoalCost - EPSILON7) continue;
21526
21862
  const points = compressPath([
21527
21863
  ...startConnector.points,
21528
21864
  ...gridPoints.slice(1),
@@ -21568,7 +21904,7 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21568
21904
  const lanePenalty = laneBias === 0 ? 0 : laneBias > 0 ? Math.max(0, targetTrack - nextTrack) * 0.2 : Math.max(0, nextTrack - targetTrack) * 0.2;
21569
21905
  const nextDistance = currentDistance + (deltaColumn !== 0 && deltaRow !== 0 ? gridStep * Math.SQRT2 : gridStep) + (addsTurn ? gridStep * 0.2 : 0) + lanePenalty;
21570
21906
  const nextState = nextNode * 9 + directionIndex;
21571
- if (nextDistance >= distances[nextState] - EPSILON6) continue;
21907
+ if (nextDistance >= distances[nextState] - EPSILON7) continue;
21572
21908
  const edgeIndex = current.node * 8 + directionIndex;
21573
21909
  if (edgeClearance[edgeIndex] === 0) {
21574
21910
  const clear = segmentIsClear({
@@ -21631,9 +21967,9 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21631
21967
  );
21632
21968
  const meanViaTrack = viaTracks.reduce((sum, track) => sum + track, 0) / viaTracks.length;
21633
21969
  const meanTargetTrack = targetTracks.reduce((sum, track) => sum + track, 0) / targetTracks.length;
21634
- const viasAreBeforeTargets = Math.max(...viaTracks) < Math.min(...targetTracks) - EPSILON6;
21635
- const viasAreAfterTargets = Math.min(...viaTracks) > Math.max(...targetTracks) + EPSILON6;
21636
- const laneBiases = preferTargetDirectedLaneBias ? viasAreBeforeTargets ? [0, 1, -1] : viasAreAfterTargets ? [0, -1, 1] : bus.direction === boundaryDirection && meanTargetTrack > meanViaTrack + EPSILON6 ? [1, 0, -1] : bus.direction === boundaryDirection && meanTargetTrack < meanViaTrack - EPSILON6 ? [-1, 0, 1] : [0, 1, -1] : viasAreBeforeTargets ? [1, 0, -1] : viasAreAfterTargets ? [-1, 0, 1] : [0, 1, -1];
21970
+ const viasAreBeforeTargets = Math.max(...viaTracks) < Math.min(...targetTracks) - EPSILON7;
21971
+ const viasAreAfterTargets = Math.min(...viaTracks) > Math.max(...targetTracks) + EPSILON7;
21972
+ const laneBiases = preferTargetDirectedLaneBias ? viasAreBeforeTargets ? [0, 1, -1] : viasAreAfterTargets ? [0, -1, 1] : bus.direction === boundaryDirection && meanTargetTrack > meanViaTrack + EPSILON7 ? [1, 0, -1] : bus.direction === boundaryDirection && meanTargetTrack < meanViaTrack - EPSILON7 ? [-1, 0, 1] : [0, 1, -1] : viasAreBeforeTargets ? [1, 0, -1] : viasAreAfterTargets ? [-1, 0, 1] : [0, 1, -1];
21637
21973
  const initialRouteOrderFactories = [];
21638
21974
  if (alignGridToPads && preferTargetDirectedLaneBias && viasAreBeforeTargets) {
21639
21975
  initialRouteOrderFactories.push(() => targetOrderedTerminals);
@@ -21645,9 +21981,15 @@ function* routeViaMinimalWindingAlternativesSteps(params, maximumAlternatives =
21645
21981
  ]);
21646
21982
  } else if (viasAreAfterTargets) {
21647
21983
  initialRouteOrderFactories.push(() => [...targetOrderedTerminals].reverse());
21984
+ if (includeReverseTargetRotation && targetOrderedTerminals.length > 2) {
21985
+ initialRouteOrderFactories.push(() => [
21986
+ ...targetOrderedTerminals.slice(0, -1).toReversed(),
21987
+ targetOrderedTerminals.at(-1)
21988
+ ]);
21989
+ }
21648
21990
  }
21649
21991
  initialRouteOrderFactories.push(
21650
- ...preferTargetDirectedLaneBias && bus.direction === boundaryDirection && meanTargetTrack < meanViaTrack - EPSILON6 && !viasAreBeforeTargets && !viasAreAfterTargets ? [
21992
+ ...preferTargetDirectedLaneBias && bus.direction === boundaryDirection && meanTargetTrack < meanViaTrack - EPSILON7 && !viasAreBeforeTargets && !viasAreAfterTargets ? [
21651
21993
  () => [...targetOrderedTerminals].reverse(),
21652
21994
  () => targetOrderedTerminals
21653
21995
  ] : [
@@ -23220,7 +23562,7 @@ function routePlaneTerminatedBus(params) {
23220
23562
  otherPlans: [...acceptedPlans, ...fixedPlans],
23221
23563
  staticClearanceCache,
23222
23564
  blockingBusCounts,
23223
- cacheKey: `plane-fixed:${bus.busId}:${targetLayer}:${preparedConnection.connectionIndex}:${candidateIndex}`,
23565
+ cacheKey: `plane-fixed:${bus.busId}:${targetLayer}:${preparedConnection.connectionIndex}:${fixedViaPoint.x}:${fixedViaPoint.y}:${candidateIndex}`,
23224
23566
  srj,
23225
23567
  sharedBoundary: bus.sharedBoundary,
23226
23568
  clearance,
@@ -23781,6 +24123,36 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
23781
24123
  const dogboneTerminalPatterns = acceptedBoundaryPlansExist && !allowBlindAndBuriedVias ? [...mixedDogboneTerminalPatterns, ...uniformDogboneTerminalPatterns] : [...uniformDogboneTerminalPatterns, ...mixedDogboneTerminalPatterns];
23782
24124
  const unreservedTerminalPatterns = canUseViaInPadTerminals ? planeTerminationsAlreadyOccupyTheFanout ? [viaInPadTerminalPattern, ...dogboneTerminalPatterns] : [...dogboneTerminalPatterns, viaInPadTerminalPattern] : dogboneTerminalPatterns;
23783
24125
  const terminalPatterns = fixedViaPointsByConnectionIndex ? fixedViaTerminalPatterns : [...fixedViaTerminalPatterns, ...unreservedTerminalPatterns];
24126
+ if (!fixedViaPointsByConnectionIndex && !allowBlindAndBuriedVias && bus.connections.length > 1 && bus.connections.every(
24127
+ (connection) => connection.sourceLayer !== targetLayer
24128
+ ) && viaHandednesses.includes(-1) && viaHandednesses.includes(1)) {
24129
+ const variants = getDogboneSideVariants(bus.connections, bus.direction);
24130
+ for (const handedness of viaHandednesses) {
24131
+ for (const flippedIndices of variants) {
24132
+ terminalPatterns.push({
24133
+ label: `local-dogbone-repair-${handedness}-${flippedIndices.join("-")}`,
24134
+ useViaInPad: false,
24135
+ getViaHandedness: () => handedness,
24136
+ maximumRouteOrderAttempts: 6,
24137
+ localDogboneRepair: true,
24138
+ getViaPoint: (connection) => {
24139
+ const point6 = getInitialViaPoint({
24140
+ preparedConnection: connection,
24141
+ bus,
24142
+ targetLayer,
24143
+ traceWidth,
24144
+ viaDiameter,
24145
+ clearance,
24146
+ viaHandedness: handedness
24147
+ });
24148
+ if (!flippedIndices.includes(connection.connectionIndex))
24149
+ return point6;
24150
+ return bus.direction === "up" || bus.direction === "down" ? { x: point6.x, y: 2 * connection.sourcePoint.y - point6.y } : { x: 2 * connection.sourcePoint.x - point6.x, y: point6.y };
24151
+ }
24152
+ });
24153
+ }
24154
+ }
24155
+ }
23784
24156
  const seenTerminalSignatures = /* @__PURE__ */ new Set();
23785
24157
  for (const terminalPattern of terminalPatterns) {
23786
24158
  const terminals = bus.connections.map((preparedConnection) => {
@@ -23823,12 +24195,45 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
23823
24195
  };
23824
24196
  });
23825
24197
  const alignGridToPads = alignWindingGridToPads || Boolean(terminalPattern.getViaPoint && !fixedViaPointsByConnectionIndex);
24198
+ if (terminalPattern.localDogboneRepair && !matchComponentDogboneViaSites([bus], {
24199
+ viaDiameter,
24200
+ viaHoleDiameter,
24201
+ traceWidth,
24202
+ clearance,
24203
+ additionalObstacles: srj.obstacles,
24204
+ fixedViaPointsByConnectionIndex: new Map(
24205
+ terminals.map((terminal) => [
24206
+ terminal.connection.connectionIndex,
24207
+ terminal.viaPoint
24208
+ ])
24209
+ ),
24210
+ blockingSegments: acceptedPlans.flatMap(
24211
+ (plan) => getPlanSegments(plan).map((segment2) => ({
24212
+ connectionIndex: plan.connectionIndex,
24213
+ segment: segment2
24214
+ }))
24215
+ ),
24216
+ blockingVias: [
24217
+ ...acceptedPlans.flatMap(
24218
+ (plan) => getPlanVias2(plan).map((via) => ({
24219
+ connectionIndex: plan.connectionIndex,
24220
+ center: via.center,
24221
+ diameter: via.diameter,
24222
+ spanLayers: via.spanLayers
24223
+ }))
24224
+ ),
24225
+ // These are future connections, not members of the current bus.
24226
+ ...reservedVias.map(({ via }) => ({ connectionIndex: -1, ...via }))
24227
+ ]
24228
+ })) {
24229
+ continue;
24230
+ }
23826
24231
  const gridStepDivisor = terminalPattern.getViaPoint && Math.min(bus.pitchX, bus.pitchY) - 2 * (viaDiameter / 2 + traceWidth / 2 + clearance) < traceWidth + clearance ? 2 : 1;
23827
24232
  const terminalSignature = `${terminals.map(
23828
24233
  (terminal) => `${terminal.connection.connectionIndex}:${terminal.viaPoint.x}:${terminal.viaPoint.y}:${terminal.exitPoint.x}:${terminal.exitPoint.y}`
23829
24234
  ).join(
23830
24235
  "|"
23831
- )}:${terminalPattern.maximumRouteOrderAttempts ?? "all"}:${gridStepDivisor}:${alignGridToPads}`;
24236
+ )}:${terminalPattern.maximumRouteOrderAttempts ?? "all"}:${gridStepDivisor}:${alignGridToPads}:${Boolean(terminalPattern.localDogboneRepair)}`;
23832
24237
  if (seenTerminalSignatures.has(terminalSignature)) continue;
23833
24238
  seenTerminalSignatures.add(terminalSignature);
23834
24239
  const windingSteps = routeViaMinimalWindingAlternativesSteps(
@@ -23848,6 +24253,7 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
23848
24253
  maximumRouteOrderAttempts: terminalPattern.maximumRouteOrderAttempts,
23849
24254
  adaptiveRouteOrder: adaptiveWindingRouteOrder,
23850
24255
  alignGridToPads,
24256
+ includeReverseTargetRotation: terminalPattern.localDogboneRepair,
23851
24257
  reservedVias,
23852
24258
  gridStepDivisor,
23853
24259
  preferTargetDirectedLaneBias: terminalPattern.preferTargetDirectedLaneBias
@@ -23897,41 +24303,46 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
23897
24303
  }
23898
24304
  }
23899
24305
  }
23900
- if (alternatives.length === 0 && allowBoundarySideViaFallback && fixedViaPointsByConnectionIndex && bus.connections.length === 1 && bus.exitEdge && bus.connections[0].sourceLayer !== targetLayer) {
23901
- const preparedConnection = bus.connections[0];
24306
+ if (alternatives.length === 0 && allowBoundarySideViaFallback && fixedViaPointsByConnectionIndex && bus.connections.length <= 2 && bus.exitEdge && bus.connections.every(
24307
+ (connection) => connection.sourceLayer === bus.connections[0].sourceLayer && connection.sourceLayer !== targetLayer
24308
+ )) {
24309
+ const sourceLayer2 = bus.connections[0].sourceLayer;
23902
24310
  const boundaryDirection = getDirectionForExitEdge(bus.exitEdge);
23903
24311
  const boundaryExitAxis = getExitAxis(bus, boundaryDirection);
23904
- const finalTrack = preferCornerBoundaryVia && getCornerSide(bus) ? getCornerTargetTrack({
23905
- bus,
23906
- connection: preparedConnection,
23907
- cornerExitLaneOffset: cornerLaneOffsets.exit,
23908
- traceWidth,
23909
- viaDiameter,
23910
- clearance,
23911
- layerNames,
23912
- targetLayer,
23913
- cornerBandTargetTrackOffset
23914
- }) : getBoundaryTargetTrack({
23915
- bus,
23916
- connection: preparedConnection,
23917
- boundaryDirection
23918
- });
23919
- const finalExitPoint = makePoint(
23920
- boundaryExitAxis,
23921
- finalTrack,
23922
- boundaryDirection
24312
+ const finalTracks = bus.connections.map(
24313
+ (preparedConnection) => preferCornerBoundaryVia && getCornerSide(bus) ? getCornerTargetTrack({
24314
+ bus,
24315
+ connection: preparedConnection,
24316
+ cornerExitLaneOffset: cornerLaneOffsets.exit,
24317
+ traceWidth,
24318
+ viaDiameter,
24319
+ clearance,
24320
+ layerNames,
24321
+ targetLayer,
24322
+ cornerBandTargetTrackOffset
24323
+ }) : getBoundaryTargetTrack({
24324
+ bus,
24325
+ connection: preparedConnection,
24326
+ boundaryDirection
24327
+ })
24328
+ );
24329
+ const finalExitPoints = finalTracks.map(
24330
+ (track) => makePoint(boundaryExitAxis, track, boundaryDirection)
23923
24331
  );
23924
24332
  const sourceLayerSrj = {
23925
24333
  ...srj,
23926
- obstacles: srj.obstacles.map(
23927
- (obstacle) => obstacle === preparedConnection.sourceObstacle || distance9(obstacle.center, preparedConnection.sourcePoint) <= 1e-7 && obstacle.layers.includes(preparedConnection.sourceLayer) ? {
24334
+ obstacles: srj.obstacles.map((obstacle) => {
24335
+ const connection = bus.connections.find(
24336
+ (connection2) => obstacle === connection2.sourceObstacle || distance9(obstacle.center, connection2.sourcePoint) <= 1e-7 && obstacle.layers.includes(connection2.sourceLayer)
24337
+ );
24338
+ return connection ? {
23928
24339
  ...obstacle,
23929
24340
  connectedTo: [
23930
24341
  ...obstacle.connectedTo,
23931
- preparedConnection.connection.name
24342
+ connection.connection.name
23932
24343
  ]
23933
- } : obstacle
23934
- )
24344
+ } : obstacle;
24345
+ })
23935
24346
  };
23936
24347
  const insetStep = Math.max(
23937
24348
  viaDiameter / 2 + clearance,
@@ -23940,34 +24351,104 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
23940
24351
  const cornerSide = preferCornerBoundaryVia ? getCornerSide(bus) : void 0;
23941
24352
  const boundaryViaCandidates = [1, 2, 3, 4, 5].flatMap((multiple) => {
23942
24353
  const inset = multiple * insetStep;
23943
- const straight = makePoint(
23944
- boundaryExitAxis - directionSign(boundaryDirection) * inset,
23945
- finalTrack,
23946
- boundaryDirection
24354
+ const straight = finalTracks.map(
24355
+ (finalTrack) => makePoint(
24356
+ boundaryExitAxis - directionSign(boundaryDirection) * inset,
24357
+ finalTrack,
24358
+ boundaryDirection
24359
+ )
23947
24360
  );
23948
24361
  if (!cornerSide) return [straight];
23949
24362
  return [
23950
- makePoint(
23951
- boundaryExitAxis - directionSign(boundaryDirection) * inset,
23952
- finalTrack + (cornerSide === "maximum" ? inset : -inset),
23953
- boundaryDirection
24363
+ finalTracks.map(
24364
+ (finalTrack) => makePoint(
24365
+ boundaryExitAxis - directionSign(boundaryDirection) * inset,
24366
+ finalTrack + (cornerSide === "maximum" ? inset : -inset),
24367
+ boundaryDirection
24368
+ )
23954
24369
  ),
23955
24370
  straight
23956
24371
  ];
23957
24372
  });
23958
- for (const boundaryViaPoint of boundaryViaCandidates) {
24373
+ const sourceCenter = {
24374
+ x: bus.connections.reduce(
24375
+ (sum, connection) => sum + connection.sourcePoint.x,
24376
+ 0
24377
+ ) / bus.connections.length,
24378
+ y: bus.connections.reduce(
24379
+ (sum, connection) => sum + connection.sourcePoint.y,
24380
+ 0
24381
+ ) / bus.connections.length
24382
+ };
24383
+ const directions = [
24384
+ { x: -1, y: 0, distance: sourceCenter.x - Math.min(...bus.xCoordinates) },
24385
+ { x: 1, y: 0, distance: Math.max(...bus.xCoordinates) - sourceCenter.x },
24386
+ { x: 0, y: -1, distance: sourceCenter.y - Math.min(...bus.yCoordinates) },
24387
+ { x: 0, y: 1, distance: Math.max(...bus.yCoordinates) - sourceCenter.y }
24388
+ ].toSorted((first, second) => first.distance - second.distance);
24389
+ const displacedViaCandidates = bus.connections.length === 2 ? directions.flatMap(
24390
+ (direction) => [3].map(
24391
+ (multiple) => bus.connections.map((connection) => {
24392
+ const original = fixedViaPointsByConnectionIndex.get(
24393
+ connection.connectionIndex
24394
+ );
24395
+ return {
24396
+ x: original.x + direction.x * multiple * bus.pitchX,
24397
+ y: original.y + direction.y * multiple * bus.pitchY
24398
+ };
24399
+ })
24400
+ )
24401
+ ) : [];
24402
+ const viaCandidates = [
24403
+ ...displacedViaCandidates.map((points) => ({
24404
+ points,
24405
+ boundarySide: false
24406
+ })),
24407
+ ...boundaryViaCandidates.map((points) => ({
24408
+ points,
24409
+ boundarySide: true
24410
+ }))
24411
+ ];
24412
+ for (const { points: boundaryViaPoints, boundarySide } of viaCandidates) {
24413
+ const boundaryVias = bus.connections.map((connection, index) => ({
24414
+ connectionName: connection.connection.name,
24415
+ via: {
24416
+ center: boundaryViaPoints[index],
24417
+ diameter: viaDiameter,
24418
+ spanLayers: getViaSpanLayers2({
24419
+ fromLayer: sourceLayer2,
24420
+ toLayer: targetLayer,
24421
+ layerNames,
24422
+ allowBlindAndBuriedVias
24423
+ })
24424
+ }
24425
+ }));
24426
+ if (boundaryVias.some(
24427
+ (candidate, index) => boundaryVias.some(
24428
+ (other, otherIndex) => index !== otherIndex && (distance9(candidate.via.center, other.via.center) < viaDiameter + clearance - 1e-9 || boundarySide && distancePointToSegment(
24429
+ candidate.via.center,
24430
+ other.via.center,
24431
+ finalExitPoints[otherIndex]
24432
+ ) < viaDiameter / 2 + traceWidth / 2 + clearance - 1e-9)
24433
+ )
24434
+ ))
24435
+ continue;
24436
+ if (boundaryVias.some(
24437
+ ({ via }) => srj.obstacles.some(
24438
+ (obstacle) => obstacle.layers.some((layer) => via.spanLayers.includes(layer)) && distancePointToObstacle(via.center, obstacle) < via.diameter / 2 + clearance - 1e-9
24439
+ )
24440
+ ))
24441
+ continue;
23959
24442
  const sourceLayerSteps = routeViaMinimalWindingAlternativesSteps(
23960
24443
  {
23961
24444
  srj: sourceLayerSrj,
23962
24445
  bus,
23963
- targetLayer: preparedConnection.sourceLayer,
23964
- terminals: [
23965
- {
23966
- connection: preparedConnection,
23967
- viaPoint: preparedConnection.sourcePoint,
23968
- exitPoint: boundaryViaPoint
23969
- }
23970
- ],
24446
+ targetLayer: sourceLayer2,
24447
+ terminals: bus.connections.map((preparedConnection, index) => ({
24448
+ connection: preparedConnection,
24449
+ viaPoint: preparedConnection.sourcePoint,
24450
+ exitPoint: boundaryViaPoints[index]
24451
+ })),
23971
24452
  acceptedPlans,
23972
24453
  layerNames,
23973
24454
  traceWidth,
@@ -23976,8 +24457,8 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
23976
24457
  clearance,
23977
24458
  allowBlindAndBuriedVias,
23978
24459
  allowSameNetMerges,
23979
- maximumRouteOrderAttempts: 3,
23980
- reservedVias,
24460
+ maximumRouteOrderAttempts: bus.connections.length === 1 ? 3 : 6,
24461
+ reservedVias: bus.connections.length > 1 ? [...reservedVias, ...boundaryVias] : reservedVias,
23981
24462
  gridStepDivisor: 2,
23982
24463
  allowSourceLayerRouting: true,
23983
24464
  alignGridToPads: true
@@ -23990,72 +24471,124 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
23990
24471
  yield {
23991
24472
  phase: "via-minimal-winding",
23992
24473
  busId: bus.busId,
23993
- targetLayer: preparedConnection.sourceLayer,
24474
+ targetLayer: sourceLayer2,
23994
24475
  winding: sourceLayerResult.value
23995
24476
  };
23996
24477
  sourceLayerResult = sourceLayerSteps.next();
23997
24478
  }
23998
- const sourceLayerPlan = sourceLayerResult.value[0]?.[0];
23999
- if (!sourceLayerPlan) continue;
24000
- const targetSegment = {
24001
- start: boundaryViaPoint,
24002
- end: finalExitPoint,
24003
- width: traceWidth,
24004
- layer: targetLayer
24005
- };
24006
- const via = {
24007
- center: boundaryViaPoint,
24008
- diameter: viaDiameter,
24009
- holeDiameter: viaHoleDiameter,
24010
- fromLayer: preparedConnection.sourceLayer,
24011
- toLayer: targetLayer,
24012
- spanLayers: getViaSpanLayers2({
24013
- fromLayer: preparedConnection.sourceLayer,
24014
- toLayer: targetLayer,
24015
- layerNames,
24016
- allowBlindAndBuriedVias
24017
- })
24018
- };
24019
- const sourceRoute = sourceLayerPlan.trace.route.filter(
24020
- (item) => item.route_type === "wire"
24021
- );
24022
- const plans = [
24023
- {
24024
- ...sourceLayerPlan,
24025
- ...preferCornerBoundaryVia ? { sourceEscapeSegmentCount: sourceLayerPlan.segments.length } : {},
24026
- targetLayer,
24027
- exitPoint: finalExitPoint,
24028
- via,
24029
- segments: [...sourceLayerPlan.segments, targetSegment],
24030
- length: sourceLayerPlan.length + distance9(boundaryViaPoint, finalExitPoint),
24031
- trace: {
24032
- ...sourceLayerPlan.trace,
24033
- route: [
24034
- ...sourceRoute,
24035
- {
24036
- route_type: "via",
24037
- ...boundaryViaPoint,
24038
- from_layer: preparedConnection.sourceLayer,
24039
- to_layer: targetLayer,
24040
- via_diameter: viaDiameter,
24041
- via_hole_diameter: viaHoleDiameter
24042
- },
24043
- {
24044
- route_type: "wire",
24045
- ...boundaryViaPoint,
24046
- width: traceWidth,
24047
- layer: targetLayer
24048
- },
24049
- {
24050
- route_type: "wire",
24051
- ...finalExitPoint,
24052
- width: traceWidth,
24053
- layer: targetLayer
24054
- }
24055
- ]
24056
- }
24479
+ const sourceLayerPlans = sourceLayerResult.value[0];
24480
+ if (!sourceLayerPlans || sourceLayerPlans.length !== bus.connections.length)
24481
+ continue;
24482
+ let targetLayerPlans;
24483
+ if (!boundarySide) {
24484
+ const targetSteps = routeViaMinimalWindingAlternativesSteps(
24485
+ {
24486
+ srj,
24487
+ bus,
24488
+ targetLayer,
24489
+ terminals: bus.connections.map((connection, index) => ({
24490
+ connection,
24491
+ viaPoint: boundaryViaPoints[index],
24492
+ exitPoint: finalExitPoints[index]
24493
+ })),
24494
+ acceptedPlans,
24495
+ layerNames,
24496
+ traceWidth,
24497
+ viaDiameter,
24498
+ viaHoleDiameter,
24499
+ clearance,
24500
+ allowBlindAndBuriedVias,
24501
+ allowSameNetMerges,
24502
+ maximumRouteOrderAttempts: 6,
24503
+ reservedVias,
24504
+ gridStepDivisor: 2,
24505
+ alignGridToPads: true
24506
+ },
24507
+ 1,
24508
+ includeVisualization
24509
+ );
24510
+ let targetResult = targetSteps.next();
24511
+ while (!targetResult.done) {
24512
+ yield {
24513
+ phase: "via-minimal-winding",
24514
+ busId: bus.busId,
24515
+ targetLayer,
24516
+ winding: targetResult.value
24517
+ };
24518
+ targetResult = targetSteps.next();
24519
+ }
24520
+ targetLayerPlans = targetResult.value[0];
24521
+ if (!targetLayerPlans) continue;
24522
+ }
24523
+ const plans = sourceLayerPlans.map(
24524
+ (sourceLayerPlan, index) => {
24525
+ const boundaryViaPoint = boundaryViaPoints[index];
24526
+ const finalExitPoint = finalExitPoints[index];
24527
+ const targetSegment = {
24528
+ start: boundaryViaPoint,
24529
+ end: finalExitPoint,
24530
+ width: traceWidth,
24531
+ layer: targetLayer
24532
+ };
24533
+ const targetSegments = targetLayerPlans ? targetLayerPlans[index].segments.filter(
24534
+ (segment2) => segment2.layer === targetLayer
24535
+ ) : [targetSegment];
24536
+ const via = {
24537
+ center: boundaryViaPoint,
24538
+ diameter: viaDiameter,
24539
+ holeDiameter: viaHoleDiameter,
24540
+ fromLayer: sourceLayer2,
24541
+ toLayer: targetLayer,
24542
+ spanLayers: getViaSpanLayers2({
24543
+ fromLayer: sourceLayer2,
24544
+ toLayer: targetLayer,
24545
+ layerNames,
24546
+ allowBlindAndBuriedVias
24547
+ })
24548
+ };
24549
+ const sourceRoute = sourceLayerPlan.trace.route.filter(
24550
+ (item) => item.route_type === "wire"
24551
+ );
24552
+ return {
24553
+ ...sourceLayerPlan,
24554
+ ...preferCornerBoundaryVia || bus.connections.length > 1 ? { sourceEscapeSegmentCount: sourceLayerPlan.segments.length } : {},
24555
+ targetLayer,
24556
+ exitPoint: finalExitPoint,
24557
+ via,
24558
+ segments: [...sourceLayerPlan.segments, ...targetSegments],
24559
+ length: sourceLayerPlan.length + targetSegments.reduce(
24560
+ (sum, segment2) => sum + distance9(segment2.start, segment2.end),
24561
+ 0
24562
+ ),
24563
+ trace: {
24564
+ ...sourceLayerPlan.trace,
24565
+ route: [
24566
+ ...sourceRoute,
24567
+ {
24568
+ route_type: "via",
24569
+ ...boundaryViaPoint,
24570
+ from_layer: sourceLayer2,
24571
+ to_layer: targetLayer,
24572
+ via_diameter: viaDiameter,
24573
+ via_hole_diameter: viaHoleDiameter
24574
+ },
24575
+ {
24576
+ route_type: "wire",
24577
+ ...boundaryViaPoint,
24578
+ width: traceWidth,
24579
+ layer: targetLayer
24580
+ },
24581
+ ...targetSegments.map((segment2) => ({
24582
+ route_type: "wire",
24583
+ ...segment2.end,
24584
+ width: traceWidth,
24585
+ layer: targetLayer
24586
+ }))
24587
+ ]
24588
+ }
24589
+ };
24057
24590
  }
24058
- ];
24591
+ );
24059
24592
  const boundaryViaPlansAreClear = fanoutPlansAreClear({
24060
24593
  plans: [...acceptedPlans, ...plans],
24061
24594
  srj,
@@ -24064,23 +24597,28 @@ function* routeBusAlternativesSteps(params, maxAlternatives = 1, includeVisualiz
24064
24597
  allowBlindAndBuriedVias,
24065
24598
  allowSameNetMerges
24066
24599
  });
24067
- const reservedViasAreClear = reservedVias.every((reserved) => {
24068
- if (reserved.connectionName === preparedConnection.connection.name || allowSameNetMerges && connectionsShareElectricalNet(
24069
- srj,
24070
- reserved.connectionName,
24071
- preparedConnection.connection.name
24072
- ))
24073
- return true;
24074
- if (via.spanLayers.some(
24075
- (layer) => reserved.via.spanLayers.includes(layer)
24076
- ) && distance9(via.center, reserved.via.center) < (via.diameter + reserved.via.diameter) / 2 + clearance - 1e-9)
24077
- return false;
24078
- return !reserved.via.spanLayers.includes(targetLayer) || distancePointToSegment(
24079
- reserved.via.center,
24080
- targetSegment.start,
24081
- targetSegment.end
24082
- ) >= reserved.via.diameter / 2 + traceWidth / 2 + clearance - 1e-9;
24083
- });
24600
+ const reservedViasAreClear = plans.every(
24601
+ (plan) => reservedVias.every((reserved) => {
24602
+ const via = plan.via;
24603
+ if (reserved.connectionName === plan.connectionName || allowSameNetMerges && connectionsShareElectricalNet(
24604
+ srj,
24605
+ reserved.connectionName,
24606
+ plan.connectionName
24607
+ ))
24608
+ return true;
24609
+ if (via.spanLayers.some(
24610
+ (layer) => reserved.via.spanLayers.includes(layer)
24611
+ ) && distance9(via.center, reserved.via.center) < (via.diameter + reserved.via.diameter) / 2 + clearance - 1e-9)
24612
+ return false;
24613
+ return plan.segments.every(
24614
+ (segment2) => !reserved.via.spanLayers.includes(segment2.layer) || distancePointToSegment(
24615
+ reserved.via.center,
24616
+ segment2.start,
24617
+ segment2.end
24618
+ ) >= reserved.via.diameter / 2 + traceWidth / 2 + clearance - 1e-9
24619
+ );
24620
+ })
24621
+ );
24084
24622
  if (boundaryViaPlansAreClear && reservedViasAreClear) {
24085
24623
  addAlternative(plans);
24086
24624
  return alternatives;
@@ -24197,9 +24735,9 @@ function routeBus(params) {
24197
24735
  }
24198
24736
 
24199
24737
  // node_modules/@tscircuit/fanout-solver/lib/match-bus-lengths.ts
24200
- var EPSILON7 = 1e-6;
24738
+ var EPSILON8 = 1e-6;
24201
24739
  function pointsMatch2(first, second) {
24202
- return distance9(first, second) <= EPSILON7;
24740
+ return distance9(first, second) <= EPSILON8;
24203
24741
  }
24204
24742
  function getPlanVias3(plan) {
24205
24743
  return [plan.via, ...plan.additionalVias ?? []].filter(
@@ -24304,26 +24842,26 @@ function splitSegmentAtDenseBounds(params) {
24304
24842
  const deltaY = segment2.end.y - segment2.start.y;
24305
24843
  const splitParameters = [0, 1];
24306
24844
  const addSplitParameter = (parameter) => {
24307
- if (parameter <= EPSILON7 || parameter >= 1 - EPSILON7) return;
24845
+ if (parameter <= EPSILON8 || parameter >= 1 - EPSILON8) return;
24308
24846
  const point6 = {
24309
24847
  x: segment2.start.x + deltaX * parameter,
24310
24848
  y: segment2.start.y + deltaY * parameter
24311
24849
  };
24312
- if (point6.x < expandedBounds.minX - EPSILON7 || point6.x > expandedBounds.maxX + EPSILON7 || point6.y < expandedBounds.minY - EPSILON7 || point6.y > expandedBounds.maxY + EPSILON7) {
24850
+ if (point6.x < expandedBounds.minX - EPSILON8 || point6.x > expandedBounds.maxX + EPSILON8 || point6.y < expandedBounds.minY - EPSILON8 || point6.y > expandedBounds.maxY + EPSILON8) {
24313
24851
  return;
24314
24852
  }
24315
24853
  splitParameters.push(parameter);
24316
24854
  };
24317
- if (Math.abs(deltaX) > EPSILON7) {
24855
+ if (Math.abs(deltaX) > EPSILON8) {
24318
24856
  addSplitParameter((expandedBounds.minX - segment2.start.x) / deltaX);
24319
24857
  addSplitParameter((expandedBounds.maxX - segment2.start.x) / deltaX);
24320
24858
  }
24321
- if (Math.abs(deltaY) > EPSILON7) {
24859
+ if (Math.abs(deltaY) > EPSILON8) {
24322
24860
  addSplitParameter((expandedBounds.minY - segment2.start.y) / deltaY);
24323
24861
  addSplitParameter((expandedBounds.maxY - segment2.start.y) / deltaY);
24324
24862
  }
24325
24863
  const parameters = splitParameters.toSorted((first, second) => first - second).filter(
24326
- (parameter, index, values) => index === 0 || Math.abs(parameter - values[index - 1]) > EPSILON7
24864
+ (parameter, index, values) => index === 0 || Math.abs(parameter - values[index - 1]) > EPSILON8
24327
24865
  );
24328
24866
  return parameters.slice(1).map((endParameter, index) => {
24329
24867
  const startParameter = parameters[index];
@@ -24370,7 +24908,7 @@ function hasNonAdjacentSelfIntersection(segments) {
24370
24908
  first.end,
24371
24909
  second.start,
24372
24910
  second.end
24373
- ) <= EPSILON7) {
24911
+ ) <= EPSILON8) {
24374
24912
  return true;
24375
24913
  }
24376
24914
  }
@@ -24412,7 +24950,7 @@ function replacementCopperIsSelfClear(params) {
24412
24950
  const replacement = segments[replacementIndex];
24413
24951
  for (const [otherIndex, other] of segments.entries()) {
24414
24952
  const requiredCenterlineClearance = replacement.width / 2 + other.width / 2 + clearance;
24415
- if (getConnectedPathDistance(replacementIndex, otherIndex) <= requiredCenterlineClearance + EPSILON7) {
24953
+ if (getConnectedPathDistance(replacementIndex, otherIndex) <= requiredCenterlineClearance + EPSILON8) {
24416
24954
  continue;
24417
24955
  }
24418
24956
  if (!segmentsAreClear(replacement, other, clearance)) return false;
@@ -24422,7 +24960,7 @@ function replacementCopperIsSelfClear(params) {
24422
24960
  if (pointsMatch2(via.center, replacement.start) || pointsMatch2(via.center, replacement.end)) {
24423
24961
  continue;
24424
24962
  }
24425
- if (distancePointToSegment(via.center, replacement.start, replacement.end) < via.diameter / 2 + replacement.width / 2 + clearance - EPSILON7) {
24963
+ if (distancePointToSegment(via.center, replacement.start, replacement.end) < via.diameter / 2 + replacement.width / 2 + clearance - EPSILON8) {
24426
24964
  return false;
24427
24965
  }
24428
24966
  }
@@ -24430,7 +24968,7 @@ function replacementCopperIsSelfClear(params) {
24430
24968
  return true;
24431
24969
  }
24432
24970
  function pointIsInsideBounds2(point6, bounds) {
24433
- return point6.x >= bounds.minX - EPSILON7 && point6.x <= bounds.maxX + EPSILON7 && point6.y >= bounds.minY - EPSILON7 && point6.y <= bounds.maxY + EPSILON7;
24971
+ return point6.x >= bounds.minX - EPSILON8 && point6.x <= bounds.maxX + EPSILON8 && point6.y >= bounds.minY - EPSILON8 && point6.y <= bounds.maxY + EPSILON8;
24434
24972
  }
24435
24973
  function addScaled(point6, vector2, scale3) {
24436
24974
  return {
@@ -24450,9 +24988,9 @@ function createMeanderPoints(params) {
24450
24988
  const dx = segment2.end.x - segment2.start.x;
24451
24989
  const dy = segment2.end.y - segment2.start.y;
24452
24990
  const segmentLength = Math.hypot(dx, dy);
24453
- if (segmentLength <= EPSILON7) return null;
24454
- const isAxisAligned = Math.abs(dx) <= EPSILON7 || Math.abs(dy) <= EPSILON7;
24455
- const isFortyFiveDegree = Math.abs(Math.abs(dx) - Math.abs(dy)) <= EPSILON7;
24991
+ if (segmentLength <= EPSILON8) return null;
24992
+ const isAxisAligned = Math.abs(dx) <= EPSILON8 || Math.abs(dy) <= EPSILON8;
24993
+ const isFortyFiveDegree = Math.abs(Math.abs(dx) - Math.abs(dy)) <= EPSILON8;
24456
24994
  if (!isAxisAligned && !isFortyFiveDegree) return null;
24457
24995
  const tangent = { x: dx / segmentLength, y: dy / segmentLength };
24458
24996
  const normal = {
@@ -24468,11 +25006,11 @@ function createMeanderPoints(params) {
24468
25006
  const toothGap = pitch;
24469
25007
  const occupiedLength = toothCount * toothSpan + Math.max(0, toothCount - 1) * toothGap;
24470
25008
  const minimumLead = pitch / 4;
24471
- if (occupiedLength + minimumLead * 2 > segmentLength + EPSILON7) {
25009
+ if (occupiedLength + minimumLead * 2 > segmentLength + EPSILON8) {
24472
25010
  return null;
24473
25011
  }
24474
25012
  const minimumAddedLengthPerTooth = 4 * chamfer * (Math.SQRT2 - 1);
24475
- if (targetAddedLength + EPSILON7 < minimumAddedLengthPerTooth * toothCount) {
25013
+ if (targetAddedLength + EPSILON8 < minimumAddedLengthPerTooth * toothCount) {
24476
25014
  return null;
24477
25015
  }
24478
25016
  const verticalRun = (targetAddedLength / toothCount - minimumAddedLengthPerTooth) / 2;
@@ -24605,10 +25143,10 @@ function* createSpreadLaneCandidates(plan, clearance) {
24605
25143
  (a, b) => distance9(b.segment.start, b.segment.end) - distance9(a.segment.start, a.segment.end)
24606
25144
  )) {
24607
25145
  const length7 = distance9(segment2.start, segment2.end);
24608
- if (length7 <= EPSILON7) continue;
25146
+ if (length7 <= EPSILON8) continue;
24609
25147
  const dx = Math.abs(segment2.end.x - segment2.start.x);
24610
25148
  const dy = Math.abs(segment2.end.y - segment2.start.y);
24611
- if (dx > EPSILON7 && dy > EPSILON7 && Math.abs(dx - dy) > EPSILON7) continue;
25149
+ if (dx > EPSILON8 && dy > EPSILON8 && Math.abs(dx - dy) > EPSILON8) continue;
24612
25150
  const tangent = {
24613
25151
  x: (segment2.end.x - segment2.start.x) / length7,
24614
25152
  y: (segment2.end.y - segment2.start.y) / length7
@@ -24678,15 +25216,15 @@ function matchBusPlanLengths(params) {
24678
25216
  }
24679
25217
  const maxLengthSkew = bus.maxLengthSkew;
24680
25218
  const skew = getBusSkew(busPlans);
24681
- if (skew <= maxLengthSkew + EPSILON7) break;
25219
+ if (skew <= maxLengthSkew + EPSILON8) break;
24682
25220
  const shortest = busPlans.toSorted(
24683
25221
  (first, second) => first.length - second.length || first.connectionName.localeCompare(second.connectionName)
24684
25222
  )[0];
24685
25223
  const longestLength = Math.max(...busPlans.map((plan) => plan.length));
24686
25224
  const deficit = longestLength - shortest.length;
24687
25225
  const minimumRequiredAddition = Math.max(
24688
- EPSILON7,
24689
- deficit - maxLengthSkew + EPSILON7
25226
+ EPSILON8,
25227
+ deficit - maxLengthSkew + EPSILON8
24690
25228
  );
24691
25229
  const targetAddedLengths = [
24692
25230
  minimumRequiredAddition,
@@ -24696,8 +25234,8 @@ function matchBusPlanLengths(params) {
24696
25234
  deficit,
24697
25235
  deficit + maxLengthSkew
24698
25236
  ].filter(
24699
- (value, index, values) => value > EPSILON7 && values.findIndex(
24700
- (candidate) => Math.abs(candidate - value) < EPSILON7
25237
+ (value, index, values) => value > EPSILON8 && values.findIndex(
25238
+ (candidate) => Math.abs(candidate - value) < EPSILON8
24701
25239
  ) === index
24702
25240
  ).toSorted((first, second) => first - second);
24703
25241
  let acceptedPlans = null;
@@ -24708,7 +25246,7 @@ function matchBusPlanLengths(params) {
24708
25246
  const nextBusPlans = nextPlans.filter(
24709
25247
  (plan) => plan.busId === bus.busId
24710
25248
  );
24711
- if (getBusSkew(nextBusPlans) > skew + EPSILON7) return null;
25249
+ if (getBusSkew(nextBusPlans) > skew + EPSILON8) return null;
24712
25250
  if (!fanoutPlansAreClear({
24713
25251
  plans: nextPlans,
24714
25252
  srj: inputSrj,
@@ -24742,7 +25280,7 @@ function matchBusPlanLengths(params) {
24742
25280
  const search = (currentPlan, stagesRemaining) => {
24743
25281
  const addedLength = currentPlan.length - shortest.length;
24744
25282
  const remainingAddition = targetAddedLength - addedLength;
24745
- if (remainingAddition <= EPSILON7) {
25283
+ if (remainingAddition <= EPSILON8) {
24746
25284
  return acceptCandidate(currentPlan);
24747
25285
  }
24748
25286
  if (stagesRemaining <= 0) return null;
@@ -24787,7 +25325,7 @@ function matchBusPlanLengths(params) {
24787
25325
  if (!acceptedPlans) continue;
24788
25326
  break;
24789
25327
  }
24790
- if (!acceptedPlans && Math.abs(targetAddedLength - minimumRequiredAddition) <= EPSILON7) {
25328
+ if (!acceptedPlans && Math.abs(targetAddedLength - minimumRequiredAddition) <= EPSILON8) {
24791
25329
  acceptedPlans = findMultiSpanCandidate(targetAddedLength);
24792
25330
  }
24793
25331
  if (acceptedPlans) break;
@@ -24829,7 +25367,7 @@ function matchBusPlanLengths(params) {
24829
25367
  const matchedBusPlans = matchedPlans.filter(
24830
25368
  (plan) => plan.busId === bus.busId
24831
25369
  );
24832
- if (getBusSkew(matchedBusPlans) > bus.maxLengthSkew + EPSILON7) {
25370
+ if (getBusSkew(matchedBusPlans) > bus.maxLengthSkew + EPSILON8) {
24833
25371
  return { plans: null, failedBus: bus };
24834
25372
  }
24835
25373
  }
@@ -25811,7 +26349,7 @@ function prepareFanoutBuses(srj, options) {
25811
26349
  }
25812
26350
 
25813
26351
  // node_modules/@tscircuit/fanout-solver/lib/route-single-layer-adaptive-exits.ts
25814
- var EPSILON8 = 1e-9;
26352
+ var EPSILON9 = 1e-9;
25815
26353
  var OBSTACLE_BIN_SIZE = 1;
25816
26354
  var FANOUT_FLOW_DEBUG_ENABLED = globalThis.process?.env?.FANOUT_FLOW_DEBUG === "1";
25817
26355
  function visualizeFlowProgress(params) {
@@ -25892,7 +26430,7 @@ function connectWith45DegreeSegments(start, end) {
25892
26430
  const deltaY = end.y - start.y;
25893
26431
  const absoluteX = Math.abs(deltaX);
25894
26432
  const absoluteY = Math.abs(deltaY);
25895
- if (absoluteX < EPSILON8 || absoluteY < EPSILON8 || Math.abs(absoluteX - absoluteY) < EPSILON8) {
26433
+ if (absoluteX < EPSILON9 || absoluteY < EPSILON9 || Math.abs(absoluteX - absoluteY) < EPSILON9) {
25896
26434
  return [start, end];
25897
26435
  }
25898
26436
  if (absoluteX > absoluteY) {
@@ -25921,7 +26459,7 @@ function enforceStraightOr45DegreeSegments(points) {
25921
26459
  const start = normalized.at(-1);
25922
26460
  const deltaX = Math.abs(end.x - start.x);
25923
26461
  const deltaY = Math.abs(end.y - start.y);
25924
- if (deltaX < EPSILON8 || deltaY < EPSILON8 || Math.abs(deltaX - deltaY) < EPSILON8) {
26462
+ if (deltaX < EPSILON9 || deltaY < EPSILON9 || Math.abs(deltaX - deltaY) < EPSILON9) {
25925
26463
  normalized.push(end);
25926
26464
  continue;
25927
26465
  }
@@ -25954,7 +26492,7 @@ function chamferOrthogonalPolyline3(points, requestedChamfer) {
25954
26492
  const next = points[index + 1];
25955
26493
  const incomingLength = distance9(previous, corner);
25956
26494
  const outgoingLength = distance9(corner, next);
25957
- if (incomingLength < EPSILON8 || outgoingLength < EPSILON8) continue;
26495
+ if (incomingLength < EPSILON9 || outgoingLength < EPSILON9) continue;
25958
26496
  const incoming = {
25959
26497
  x: (corner.x - previous.x) / incomingLength,
25960
26498
  y: (corner.y - previous.y) / incomingLength
@@ -25987,7 +26525,7 @@ function chamferOrthogonalPolyline3(points, requestedChamfer) {
25987
26525
  function getSegments2(points, traceWidth) {
25988
26526
  const segments = [];
25989
26527
  for (let index = 1; index < points.length; index++) {
25990
- if (distance9(points[index - 1], points[index]) < EPSILON8) continue;
26528
+ if (distance9(points[index - 1], points[index]) < EPSILON9) continue;
25991
26529
  segments.push({
25992
26530
  start: points[index - 1],
25993
26531
  end: points[index],
@@ -26138,7 +26676,7 @@ function* createFlowGridSteps(params) {
26138
26676
  for (let node = 0; node < nodeCount; node++) {
26139
26677
  const point6 = points[node];
26140
26678
  if (getNearbyObstacles(point6).every(
26141
- (obstacle) => distancePointToObstacle(point6, obstacle) >= requiredObstacleDistance - EPSILON8
26679
+ (obstacle) => distancePointToObstacle(point6, obstacle) >= requiredObstacleDistance - EPSILON9
26142
26680
  )) {
26143
26681
  obstacleFreeNodes[node] = 1;
26144
26682
  }
@@ -26177,7 +26715,7 @@ function* createFlowGridSteps(params) {
26177
26715
  layer: "top"
26178
26716
  };
26179
26717
  if (getNearbyObstacles(segment2.start, segment2.end).some(
26180
- (obstacle) => distanceSegmentToObstacle(segment2, obstacle) < requiredObstacleDistance - EPSILON8
26718
+ (obstacle) => distanceSegmentToObstacle(segment2, obstacle) < requiredObstacleDistance - EPSILON9
26181
26719
  )) {
26182
26720
  continue;
26183
26721
  }
@@ -26242,7 +26780,7 @@ function* routeDirectionGroupSteps(params) {
26242
26780
  const segments = getSegments2(points, traceWidth);
26243
26781
  return segments.every(
26244
26782
  (segment2) => obstacles.every(
26245
- (obstacle) => obstacleBelongsToItem(obstacle, item) || distanceSegmentToObstacle(segment2, obstacle) >= requiredObstacleDistance - EPSILON8
26783
+ (obstacle) => obstacleBelongsToItem(obstacle, item) || distanceSegmentToObstacle(segment2, obstacle) >= requiredObstacleDistance - EPSILON9
26246
26784
  )
26247
26785
  ) && segments.every(
26248
26786
  (segment2) => acceptedSegments.every(
@@ -26251,7 +26789,7 @@ function* routeDirectionGroupSteps(params) {
26251
26789
  segment2.end,
26252
26790
  acceptedSegment.start,
26253
26791
  acceptedSegment.end
26254
- ) >= requiredRouteDistance - EPSILON8
26792
+ ) >= requiredRouteDistance - EPSILON9
26255
26793
  )
26256
26794
  );
26257
26795
  };
@@ -26341,7 +26879,7 @@ function* routeDirectionGroupSteps(params) {
26341
26879
  segment2.end,
26342
26880
  otherSegment.start,
26343
26881
  otherSegment.end
26344
- ) >= requiredRouteDistance - EPSILON8
26882
+ ) >= requiredRouteDistance - EPSILON9
26345
26883
  )
26346
26884
  )
26347
26885
  );
@@ -26409,7 +26947,7 @@ function* routeDirectionGroupSteps(params) {
26409
26947
  const node = row * columnCount + column;
26410
26948
  if (!freeNodes[node] || terminalNodes.has(node)) continue;
26411
26949
  const point6 = pointForNode(node);
26412
- if (distanceSegmentToSegment(segment2.start, segment2.end, point6, point6) < requiredRouteDistance - EPSILON8) {
26950
+ if (distanceSegmentToSegment(segment2.start, segment2.end, point6, point6) < requiredRouteDistance - EPSILON9) {
26413
26951
  freeNodes[node] = 0;
26414
26952
  }
26415
26953
  }
@@ -26629,7 +27167,7 @@ function buildPlan3(route, traceWidth) {
26629
27167
  function getConnectorVariants2(start, end) {
26630
27168
  const first = connectWith45DegreeSegments(start, end);
26631
27169
  const second = connectWith45DegreeSegments(end, start).reverse();
26632
- return first.length === second.length && first.every((point6, index) => distance9(point6, second[index]) < EPSILON8) ? [first] : [first, second];
27170
+ return first.length === second.length && first.every((point6, index) => distance9(point6, second[index]) < EPSILON9) ? [first] : [first, second];
26633
27171
  }
26634
27172
  function plansHaveRequiredClearance(params) {
26635
27173
  const { plans, items, obstacles, traceWidth, clearance } = params;
@@ -26656,7 +27194,7 @@ function plansHaveRequiredClearance(params) {
26656
27194
  for (const [netKey, segments] of uniqueSegmentsByNet) {
26657
27195
  for (const segment2 of segments) {
26658
27196
  for (const obstacle of obstacles) {
26659
- if (obstacle.connectedTo.includes(netKey) || distanceSegmentToObstacle(segment2, obstacle) >= requiredObstacleDistance - EPSILON8) {
27197
+ if (obstacle.connectedTo.includes(netKey) || distanceSegmentToObstacle(segment2, obstacle) >= requiredObstacleDistance - EPSILON9) {
26660
27198
  continue;
26661
27199
  }
26662
27200
  if (FANOUT_FLOW_DEBUG_ENABLED) {
@@ -26683,7 +27221,7 @@ function plansHaveRequiredClearance(params) {
26683
27221
  first.end,
26684
27222
  second.start,
26685
27223
  second.end
26686
- ) < requiredRouteDistance - EPSILON8) {
27224
+ ) < requiredRouteDistance - EPSILON9) {
26687
27225
  if (FANOUT_FLOW_DEBUG_ENABLED) {
26688
27226
  console.error("single-layer routes violate copper clearance", {
26689
27227
  firstNetKey: entries[firstIndex][0],
@@ -26708,10 +27246,10 @@ function plansHaveRequiredClearance(params) {
26708
27246
  return true;
26709
27247
  }
26710
27248
  function getDirectionForBoundaryPoint(point6, boundary) {
26711
- if (Math.abs(point6.x - boundary.minX) < EPSILON8) return "left";
26712
- if (Math.abs(point6.x - boundary.maxX) < EPSILON8) return "right";
26713
- if (Math.abs(point6.y - boundary.minY) < EPSILON8) return "down";
26714
- if (Math.abs(point6.y - boundary.maxY) < EPSILON8) return "up";
27249
+ if (Math.abs(point6.x - boundary.minX) < EPSILON9) return "left";
27250
+ if (Math.abs(point6.x - boundary.maxX) < EPSILON9) return "right";
27251
+ if (Math.abs(point6.y - boundary.minY) < EPSILON9) return "down";
27252
+ if (Math.abs(point6.y - boundary.maxY) < EPSILON9) return "up";
26715
27253
  return null;
26716
27254
  }
26717
27255
  function* routeWithAdaptiveExitsSteps(params) {
@@ -26801,7 +27339,7 @@ function* routeWithAdaptiveExitsSteps(params) {
26801
27339
  const connectorSegments = getSegments2(connectorPoints, traceWidth);
26802
27340
  const clearsObstacles = connectorSegments.every(
26803
27341
  (segment2) => obstacles.every(
26804
- (obstacle) => obstacleBelongsToItem(obstacle, mergeItem) || distanceSegmentToObstacle(segment2, obstacle) >= requiredObstacleDistance - EPSILON8
27342
+ (obstacle) => obstacleBelongsToItem(obstacle, mergeItem) || distanceSegmentToObstacle(segment2, obstacle) >= requiredObstacleDistance - EPSILON9
26805
27343
  )
26806
27344
  );
26807
27345
  const clearsRoutes = connectorSegments.every(
@@ -26812,7 +27350,7 @@ function* routeWithAdaptiveExitsSteps(params) {
26812
27350
  segment2.end,
26813
27351
  otherSegment.start,
26814
27352
  otherSegment.end
26815
- ) >= requiredRouteDistance - EPSILON8
27353
+ ) >= requiredRouteDistance - EPSILON9
26816
27354
  )
26817
27355
  )
26818
27356
  );
@@ -27940,9 +28478,9 @@ function shortenBusPlans(params) {
27940
28478
  }
27941
28479
 
27942
28480
  // node_modules/@tscircuit/fanout-solver/lib/validate-fanout-solution.ts
27943
- var EPSILON9 = 1e-6;
28481
+ var EPSILON10 = 1e-6;
27944
28482
  function pointsMatch3(first, second) {
27945
- return distance9(first, second) <= EPSILON9;
28483
+ return distance9(first, second) <= EPSILON10;
27946
28484
  }
27947
28485
  function getPointLayers3(point6) {
27948
28486
  return "layer" in point6 ? [point6.layer] : point6.layers;
@@ -27961,26 +28499,26 @@ function connectionPointsMatch(first, second) {
27961
28499
  return pointsMatch3(first, second) && getPointLayers3(first).join("\0") === getPointLayers3(second).join("\0") && first.pointId === second.pointId && first.pcb_port_id === second.pcb_port_id;
27962
28500
  }
27963
28501
  function pointIsOnBoundary(point6, boundary) {
27964
- const inside = point6.x >= boundary.minX - EPSILON9 && point6.x <= boundary.maxX + EPSILON9 && point6.y >= boundary.minY - EPSILON9 && point6.y <= boundary.maxY + EPSILON9;
27965
- const onEdge = Math.abs(point6.x - boundary.minX) <= EPSILON9 || Math.abs(point6.x - boundary.maxX) <= EPSILON9 || Math.abs(point6.y - boundary.minY) <= EPSILON9 || Math.abs(point6.y - boundary.maxY) <= EPSILON9;
28502
+ const inside = point6.x >= boundary.minX - EPSILON10 && point6.x <= boundary.maxX + EPSILON10 && point6.y >= boundary.minY - EPSILON10 && point6.y <= boundary.maxY + EPSILON10;
28503
+ const onEdge = Math.abs(point6.x - boundary.minX) <= EPSILON10 || Math.abs(point6.x - boundary.maxX) <= EPSILON10 || Math.abs(point6.y - boundary.minY) <= EPSILON10 || Math.abs(point6.y - boundary.maxY) <= EPSILON10;
27966
28504
  return inside && onEdge;
27967
28505
  }
27968
28506
  function pointIsOnBoundaryEdge(point6, edge, boundary) {
27969
- const inside = point6.x >= boundary.minX - EPSILON9 && point6.x <= boundary.maxX + EPSILON9 && point6.y >= boundary.minY - EPSILON9 && point6.y <= boundary.maxY + EPSILON9;
28507
+ const inside = point6.x >= boundary.minX - EPSILON10 && point6.x <= boundary.maxX + EPSILON10 && point6.y >= boundary.minY - EPSILON10 && point6.y <= boundary.maxY + EPSILON10;
27970
28508
  if (!inside) return false;
27971
28509
  switch (edge) {
27972
28510
  case "left":
27973
- return Math.abs(point6.x - boundary.minX) <= EPSILON9;
28511
+ return Math.abs(point6.x - boundary.minX) <= EPSILON10;
27974
28512
  case "right":
27975
- return Math.abs(point6.x - boundary.maxX) <= EPSILON9;
28513
+ return Math.abs(point6.x - boundary.maxX) <= EPSILON10;
27976
28514
  case "top":
27977
- return Math.abs(point6.y - boundary.maxY) <= EPSILON9;
28515
+ return Math.abs(point6.y - boundary.maxY) <= EPSILON10;
27978
28516
  case "bottom":
27979
- return Math.abs(point6.y - boundary.minY) <= EPSILON9;
28517
+ return Math.abs(point6.y - boundary.minY) <= EPSILON10;
27980
28518
  }
27981
28519
  }
27982
28520
  function pointIsInsideBounds3(point6, bounds) {
27983
- return point6.x >= bounds.minX - EPSILON9 && point6.x <= bounds.maxX + EPSILON9 && point6.y >= bounds.minY - EPSILON9 && point6.y <= bounds.maxY + EPSILON9;
28521
+ return point6.x >= bounds.minX - EPSILON10 && point6.x <= bounds.maxX + EPSILON10 && point6.y >= bounds.minY - EPSILON10 && point6.y <= bounds.maxY + EPSILON10;
27984
28522
  }
27985
28523
  function addIssue2(issues, code, message, plan, otherConnectionName) {
27986
28524
  issues.push({
@@ -28113,7 +28651,7 @@ function validatePlanStructure(params) {
28113
28651
  );
28114
28652
  }
28115
28653
  const isAllowedViaInPadPlaneTermination = inputSrj.allowViaInPad === true && plan.termination.type === "plane" && plan.via !== void 0 && pointsMatch3(plan.via.center, plan.sourcePoint) && pointsMatch3(plan.exitPoint, plan.sourcePoint);
28116
- if (!isAllowedViaInPadPlaneTermination && (plan.segments.length === 0 || plan.length <= EPSILON9)) {
28654
+ if (!isAllowedViaInPadPlaneTermination && (plan.segments.length === 0 || plan.length <= EPSILON10)) {
28117
28655
  addIssue2(
28118
28656
  issues,
28119
28657
  "not-broken-out",
@@ -28196,7 +28734,7 @@ function validatePlanStructure(params) {
28196
28734
  });
28197
28735
  if (traceSegments.length !== plan.segments.length || traceSegments.some((segment2, index) => {
28198
28736
  const declared = plan.segments[index];
28199
- return !declared || segment2.layer !== declared.layer || Math.abs(segment2.width - declared.width) > EPSILON9 || !pointsMatch3(segment2.start, declared.start) || !pointsMatch3(segment2.end, declared.end);
28737
+ return !declared || segment2.layer !== declared.layer || Math.abs(segment2.width - declared.width) > EPSILON10 || !pointsMatch3(segment2.start, declared.start) || !pointsMatch3(segment2.end, declared.end);
28200
28738
  })) {
28201
28739
  addIssue2(
28202
28740
  issues,
@@ -28221,8 +28759,8 @@ function validatePlanStructure(params) {
28221
28759
  });
28222
28760
  if (endpointSegments.length !== plan.planeEndpointSegments.length || endpointSegments.some((segment2, index) => {
28223
28761
  const declared = plan.planeEndpointSegments?.[index];
28224
- return !declared || segment2.layer !== declared.layer || Math.abs(segment2.width - declared.width) > EPSILON9 || !pointsMatch3(segment2.start, declared.start) || !pointsMatch3(segment2.end, declared.end);
28225
- }) || distance9(plan.planeEndpointVia.center, plan.targetPoint) <= EPSILON9) {
28762
+ return !declared || segment2.layer !== declared.layer || Math.abs(segment2.width - declared.width) > EPSILON10 || !pointsMatch3(segment2.start, declared.start) || !pointsMatch3(segment2.end, declared.end);
28763
+ }) || distance9(plan.planeEndpointVia.center, plan.targetPoint) <= EPSILON10) {
28226
28764
  addIssue2(
28227
28765
  issues,
28228
28766
  "trace-plan-mismatch",
@@ -28302,7 +28840,7 @@ function plansHaveConnectedCopper(first, second) {
28302
28840
  firstSegment.end,
28303
28841
  secondSegment.start,
28304
28842
  secondSegment.end
28305
- ) <= (firstSegment.width + secondSegment.width) / 2 + EPSILON9) {
28843
+ ) <= (firstSegment.width + secondSegment.width) / 2 + EPSILON10) {
28306
28844
  return true;
28307
28845
  }
28308
28846
  }
@@ -28311,7 +28849,7 @@ function plansHaveConnectedCopper(first, second) {
28311
28849
  secondVia.center,
28312
28850
  firstSegment.start,
28313
28851
  firstSegment.end
28314
- ) <= secondVia.diameter / 2 + firstSegment.width / 2 + EPSILON9) {
28852
+ ) <= secondVia.diameter / 2 + firstSegment.width / 2 + EPSILON10) {
28315
28853
  return true;
28316
28854
  }
28317
28855
  }
@@ -28322,14 +28860,14 @@ function plansHaveConnectedCopper(first, second) {
28322
28860
  firstVia.center,
28323
28861
  secondSegment.start,
28324
28862
  secondSegment.end
28325
- ) <= firstVia.diameter / 2 + secondSegment.width / 2 + EPSILON9) {
28863
+ ) <= firstVia.diameter / 2 + secondSegment.width / 2 + EPSILON10) {
28326
28864
  return true;
28327
28865
  }
28328
28866
  }
28329
28867
  for (const secondVia of secondVias) {
28330
28868
  if (firstVia.spanLayers.some(
28331
28869
  (layer) => secondVia.spanLayers.includes(layer)
28332
- ) && distance9(firstVia.center, secondVia.center) <= (firstVia.diameter + secondVia.diameter) / 2 + EPSILON9) {
28870
+ ) && distance9(firstVia.center, secondVia.center) <= (firstVia.diameter + secondVia.diameter) / 2 + EPSILON10) {
28333
28871
  return true;
28334
28872
  }
28335
28873
  }
@@ -29628,7 +30166,10 @@ var FanoutSolver = class extends BaseSolver {
29628
30166
  buses: this.preparedBuses,
29629
30167
  escapeLayers: this.config.escapeLayers,
29630
30168
  escapeLayersByBusId: this.escapeLayersByBusId,
29631
- preferOrderedCoordinatedWindingLayers: this.config.densePlaneReservationBusIds.length > 0 || this.config.denseUnrestrictedPlaneRoutingBusIds.length > 0
30169
+ preferOrderedCoordinatedWindingLayers: this.config.densePlaneReservationBusIds.length > 0 || this.config.denseUnrestrictedPlaneRoutingBusIds.length > 0 || shouldUseAdaptiveDensePlaneRouting(
30170
+ this.preparedBuses,
30171
+ this.config.allowBlindAndBuriedVias
30172
+ )
29632
30173
  }),
29633
30174
  generatedAssignments,
29634
30175
  maxAssignments: this.config.maxLayerCombinations
@@ -29917,6 +30458,8 @@ var FanoutSolver = class extends BaseSolver {
29917
30458
  "plane-match:incremental-failed",
29918
30459
  "plane-route:alternate-candidate-counts",
29919
30460
  "plane-route:alternate-search",
30461
+ "plane-route:csp",
30462
+ "plane-reservation-core",
29920
30463
  "plane-route:promote-failed",
29921
30464
  "plane-route:alternate-choice",
29922
30465
  "plane-route:promote-zero-candidates",
@@ -29933,7 +30476,13 @@ var FanoutSolver = class extends BaseSolver {
29933
30476
  const unsortedBoundaryBuses = params.busesInRoutingOrder.filter(
29934
30477
  (bus) => bus.termination.type === "boundary"
29935
30478
  );
29936
- const useConfiguredDensePlaneRouting = this.config.densePlaneReservationBusIds.length > 0 || this.config.denseUnrestrictedPlaneRoutingBusIds.length > 0;
30479
+ const configuredDensePlaneRouting = this.config.densePlaneReservationBusIds.length > 0 || this.config.denseUnrestrictedPlaneRoutingBusIds.length > 0;
30480
+ const useAdaptiveDensePlaneRouting = !configuredDensePlaneRouting && shouldUseAdaptiveDensePlaneRouting(
30481
+ this.preparedBuses,
30482
+ this.config.allowBlindAndBuriedVias
30483
+ );
30484
+ const useConfiguredDensePlaneRouting = configuredDensePlaneRouting || useAdaptiveDensePlaneRouting;
30485
+ const useAdaptiveJointPlaneSelection = useAdaptiveDensePlaneRouting && (params.planeReservationRetryCount ?? 0) === 0;
29937
30486
  const matchLengthsAfterPlanes = useConfiguredDensePlaneRouting && params.lengthMatchingStage !== "before-planes";
29938
30487
  const useJointBoundaryViaReservation = shouldUseJointBoundaryViaReservation(
29939
30488
  unsortedBoundaryBuses.map((bus) => bus.connections.length)
@@ -30080,6 +30629,17 @@ var FanoutSolver = class extends BaseSolver {
30080
30629
  0,
30081
30630
  Number.isFinite(initialPlaneReservationCount) ? initialPlaneReservationCount : 8
30082
30631
  ) : planeBuses;
30632
+ if (params.promotedPlaneReservationBusIds) {
30633
+ activeBoundaryReservationPlaneBuses = [
30634
+ .../* @__PURE__ */ new Set([
30635
+ ...activeBoundaryReservationPlaneBuses,
30636
+ ...planeBuses.filter(
30637
+ (bus) => params.promotedPlaneReservationBusIds.includes(bus.busId)
30638
+ )
30639
+ ])
30640
+ ];
30641
+ }
30642
+ const unroutablePlaneBusIds = /* @__PURE__ */ new Set();
30083
30643
  debugDense(
30084
30644
  "start",
30085
30645
  boundaryBuses.map((bus) => `${bus.busId}:${bus.connections.length}`),
@@ -30112,7 +30672,7 @@ var FanoutSolver = class extends BaseSolver {
30112
30672
  const preferBoundaryOutwardByBusId = new Map(
30113
30673
  boundaryBuses.map((bus) => [
30114
30674
  bus.busId,
30115
- bus.connections.length === 1 ? hasThreeWideBoundaryBuses && narrowBusIsEmbeddedInWideSourceField(bus) ? true : useGeometryAwareSingletonOutwardPreference && getCornerBandSide(bus.exitEdge, bus.preferredExit) ? getDenseSingletonBoundaryGeometry(bus).targetProjection > 0 : getExitEdgeForDirection(bus.direction) !== bus.exitEdge : bus.exitEdge === "top" && bus.connections.length >= 8 ? false : getExitEdgeForDirection(bus.direction) !== bus.exitEdge
30675
+ bus.connections.length === 1 ? hasThreeWideBoundaryBuses && narrowBusIsEmbeddedInWideSourceField(bus) ? true : useGeometryAwareSingletonOutwardPreference && getCornerBandSide(bus.exitEdge, bus.preferredExit) ? getDenseSingletonBoundaryGeometry(bus).targetProjection > 0 : getExitEdgeForDirection(bus.direction) !== bus.exitEdge : hasThreeWideBoundaryBuses && bus.connections.length >= 8 ? false : getExitEdgeForDirection(bus.direction) !== bus.exitEdge
30116
30676
  ])
30117
30677
  );
30118
30678
  for (const bus of wideBoundaryBuses) {
@@ -30394,6 +30954,14 @@ var FanoutSolver = class extends BaseSolver {
30394
30954
  return [
30395
30955
  {
30396
30956
  connectionName: connection.connection.name,
30957
+ sourceEscapeSegment: matchedPlans.some(
30958
+ (plan) => plan.connectionIndex === connection.connectionIndex
30959
+ ) ? void 0 : {
30960
+ start: connection.sourcePoint,
30961
+ end: center,
30962
+ layer: connection.sourceLayer,
30963
+ width: this.config.traceWidth
30964
+ },
30397
30965
  via: {
30398
30966
  center,
30399
30967
  diameter: this.config.viaDiameter,
@@ -30448,7 +31016,7 @@ var FanoutSolver = class extends BaseSolver {
30448
31016
  fixedViaPointsByConnectionIndex,
30449
31017
  reservedVias: getReservedVias(bus),
30450
31018
  viaMinimalOnly: process.env.FANOUT_DEBUG_ALLOW_EXTRA_VIAS !== "1",
30451
- allowBoundarySideViaFallback: true,
31019
+ allowBoundarySideViaFallback: bus.connections.length === 1,
30452
31020
  preferCornerBoundaryVia: useConfiguredDensePlaneRouting,
30453
31021
  adaptiveWindingRouteOrder,
30454
31022
  alignWindingGridToPads: usePadAlignedDenseRouting && !useConfiguredDensePlaneRouting,
@@ -30559,7 +31127,21 @@ var FanoutSolver = class extends BaseSolver {
30559
31127
  ))[0];
30560
31128
  if (busPlans) break;
30561
31129
  }
30562
- if (!busPlans) fixedViaPointsByConnectionIndex = originalPoints;
31130
+ if (!busPlans) {
31131
+ fixedViaPointsByConnectionIndex = originalPoints;
31132
+ if (bus.connections.length === 2) {
31133
+ busPlans = (yield* routeAlternatives(
31134
+ {
31135
+ ...routeParams,
31136
+ fixedViaPointsByConnectionIndex: originalPoints,
31137
+ reservedVias: getReservedVias(bus),
31138
+ allowBoundarySideViaFallback: true,
31139
+ fixedViaFallbackRouteOrderAttempts: 1
31140
+ },
31141
+ 1
31142
+ ))[0];
31143
+ }
31144
+ }
30563
31145
  }
30564
31146
  if (busPlans && bus.maxLengthSkew !== void 0) {
30565
31147
  const lengths = busPlans.map((plan) => plan.length);
@@ -31164,6 +31746,73 @@ var FanoutSolver = class extends BaseSolver {
31164
31746
  }))
31165
31747
  })
31166
31748
  );
31749
+ if (useAdaptiveJointPlaneSelection) {
31750
+ const acceptedPlans = [
31751
+ ...candidatePlans,
31752
+ ...feasibleAlternatePlanePlans
31753
+ ];
31754
+ for (const planeBus of planeBuses) {
31755
+ if (matchedPlaneBuses.includes(planeBus) || candidateSets.some((set) => set.planeBus === planeBus))
31756
+ continue;
31757
+ const candidates = [];
31758
+ const sites = getComponentDogboneViaSiteCandidates(
31759
+ [planeBus],
31760
+ {
31761
+ viaDiameter: this.config.viaDiameter,
31762
+ viaHoleDiameter: this.config.viaHoleDiameter,
31763
+ traceWidth: this.config.traceWidth,
31764
+ clearance: this.config.clearance,
31765
+ blockingSegments: acceptedPlans.flatMap(
31766
+ (plan) => plan.segments.map((segment2) => ({
31767
+ connectionIndex: plan.connectionIndex,
31768
+ segment: segment2
31769
+ }))
31770
+ ),
31771
+ blockingVias: acceptedPlans.flatMap(
31772
+ (plan) => [
31773
+ plan.via,
31774
+ ...plan.additionalVias ?? [],
31775
+ plan.planeEndpointVia
31776
+ ].filter((via) => via !== void 0).map((via) => ({
31777
+ connectionIndex: plan.connectionIndex,
31778
+ center: via.center,
31779
+ diameter: via.diameter,
31780
+ spanLayers: via.spanLayers
31781
+ }))
31782
+ ),
31783
+ additionalObstacles: this.routingSrj.obstacles,
31784
+ preferPlaneCheckerboardSites: true,
31785
+ canShareCopper
31786
+ }
31787
+ );
31788
+ for (const site of sites) {
31789
+ const plans = routeBus({
31790
+ srj: this.routingSrj,
31791
+ bus: planeBus,
31792
+ targetLayer: params.busLayerAssignments[planeBus.busId],
31793
+ acceptedPlans,
31794
+ layerNames: this.config.layerNames,
31795
+ traceWidth: this.config.traceWidth,
31796
+ viaDiameter: this.config.viaDiameter,
31797
+ viaHoleDiameter: this.config.viaHoleDiameter,
31798
+ clearance: this.config.clearance,
31799
+ compactBusTracks: this.config.compactBusTracks,
31800
+ allowBlindAndBuriedVias: false,
31801
+ allowSameNetMerges: this.config.allowSameNetMerges,
31802
+ fixedViaPointsByConnectionIndex: /* @__PURE__ */ new Map([
31803
+ [site.connectionIndex, site.point]
31804
+ ])
31805
+ });
31806
+ if (plans)
31807
+ candidates.push({
31808
+ key: `${planeBus.busId}:local:${candidates.length}`,
31809
+ planeBus,
31810
+ plans
31811
+ });
31812
+ }
31813
+ candidateSets.push({ planeBus, candidates });
31814
+ }
31815
+ }
31167
31816
  debugDense(
31168
31817
  "plane-route:alternate-candidate-counts",
31169
31818
  candidateSets.map((candidateSet) => [
@@ -31171,6 +31820,10 @@ var FanoutSolver = class extends BaseSolver {
31171
31820
  candidateSet.candidates.length
31172
31821
  ])
31173
31822
  );
31823
+ for (const candidateSet of candidateSets) {
31824
+ if (candidateSet.candidates.length === 0)
31825
+ unroutablePlaneBusIds.add(candidateSet.planeBus.busId);
31826
+ }
31174
31827
  const compatibilityByCandidatePair = /* @__PURE__ */ new Map();
31175
31828
  const candidatesAreCompatible = (first, second) => {
31176
31829
  const cacheKey = [first.key, second.key].toSorted().join("|");
@@ -31263,10 +31916,70 @@ var FanoutSolver = class extends BaseSolver {
31263
31916
  );
31264
31917
  return null;
31265
31918
  };
31266
- const selectedCandidates = selectCompatiblePlaneRoutes(
31267
- candidateSets,
31268
- []
31269
- );
31919
+ let selectedCandidates;
31920
+ if (useAdaptiveDensePlaneRouting) {
31921
+ const getUniqueDomains = (sets) => sets.map((set) => [
31922
+ ...new Map(
31923
+ set.candidates.map((candidate) => [
31924
+ JSON.stringify(
31925
+ candidate.plans.map((plan) => [
31926
+ plan.segments,
31927
+ plan.via,
31928
+ plan.additionalVias,
31929
+ plan.planeEndpointSegments,
31930
+ plan.planeEndpointVia
31931
+ ])
31932
+ ),
31933
+ candidate
31934
+ ])
31935
+ ).values()
31936
+ ]);
31937
+ const runCandidateSelection = (sets, maximumSearchStates = maximumAlternatePlaneSearchStates) => selectCompatibleCandidates({
31938
+ candidateSets: getUniqueDomains(sets),
31939
+ maximumSearchStates,
31940
+ areCompatible: (first, second) => fanoutPlansAreMutuallyClear({
31941
+ plans: [...first.plans, ...second.plans],
31942
+ srj: this.routingSrj,
31943
+ clearance: this.config.clearance,
31944
+ allowSameNetMerges: this.config.allowSameNetMerges
31945
+ })
31946
+ });
31947
+ const result = runCandidateSelection(candidateSets);
31948
+ debugDense(
31949
+ "plane-route:csp",
31950
+ result.selection ? "complete" : "failed",
31951
+ result.searchStates,
31952
+ result.emptyDomainIndices.map(
31953
+ (index) => candidateSets[index].planeBus.busId
31954
+ )
31955
+ );
31956
+ selectedCandidates = result.selection;
31957
+ alternatePlaneSearchStates = result.searchStates;
31958
+ if (!result.selection && useAdaptiveDensePlaneRouting) {
31959
+ const expanded = result.emptyDomainIndices.map((index) => candidateSets[index].planeBus.busId).filter(
31960
+ (id) => !orderedAlternatePlaneBuses.some(
31961
+ (bus) => bus.busId === id
31962
+ )
31963
+ );
31964
+ if (expanded.length > 0) {
31965
+ debugDense("plane-route:expand-domains", expanded);
31966
+ return matchViaPointsAroundPlans(
31967
+ candidatePlans,
31968
+ /* @__PURE__ */ new Set([...promotedAlternatePlaneBusIds, ...expanded])
31969
+ );
31970
+ }
31971
+ }
31972
+ if (!result.selection)
31973
+ for (const index of (params.planeReservationRetryCount ?? 0) === 0 ? result.conflictDomainIndices : result.emptyDomainIndices.slice(0, 1))
31974
+ unroutablePlaneBusIds.add(
31975
+ candidateSets[index].planeBus.busId
31976
+ );
31977
+ } else {
31978
+ selectedCandidates = selectCompatiblePlaneRoutes(
31979
+ candidateSets,
31980
+ []
31981
+ );
31982
+ }
31270
31983
  alternatePlanePlans = selectedCandidates ? [
31271
31984
  ...feasibleAlternatePlanePlans,
31272
31985
  ...selectedCandidates.flatMap(
@@ -31290,6 +32003,15 @@ var FanoutSolver = class extends BaseSolver {
31290
32003
  );
31291
32004
  if (!alternatePlanePlans) return null;
31292
32005
  feasibleAlternatePlanePlans = alternatePlanePlans;
32006
+ if (useAdaptiveJointPlaneSelection) {
32007
+ matchedPlaneBusesInRoutingOrder = [];
32008
+ return new Map([
32009
+ ...fixedBoundaryViaPoints,
32010
+ ...alternatePlanePlans.filter((plan) => plan.via).map(
32011
+ (plan) => [plan.connectionIndex, plan.via.center]
32012
+ )
32013
+ ]);
32014
+ }
31293
32015
  }
31294
32016
  let planeBusIdsRoutedWithoutDogbones = /* @__PURE__ */ new Set();
31295
32017
  let allBlockingSegments = [...blockingSegments];
@@ -31681,6 +32403,30 @@ var FanoutSolver = class extends BaseSolver {
31681
32403
  return { plans: matchedPlans, failedBusIds: [] };
31682
32404
  }
31683
32405
  }
32406
+ if (useAdaptiveDensePlaneRouting && unroutablePlaneBusIds.size > 0 && (params.planeReservationRetryCount ?? 0) < 5) {
32407
+ const newlyPromoted = [...unroutablePlaneBusIds].filter(
32408
+ (id) => !activeBoundaryReservationPlaneBuses.some((bus) => bus.busId === id)
32409
+ );
32410
+ const refinedPromotions = (params.planeReservationRetryCount ?? 0) === 0 ? refineAdaptivePlaneReservationCore({
32411
+ candidateBusIds: newlyPromoted,
32412
+ activeBusIds: new Set(
32413
+ activeBoundaryReservationPlaneBuses.map((bus) => bus.busId)
32414
+ ),
32415
+ planeBuses
32416
+ }) : newlyPromoted;
32417
+ if (refinedPromotions.length > 0) {
32418
+ debugDense("plane-reservation-core", refinedPromotions);
32419
+ debugDense("promote-reservations", refinedPromotions);
32420
+ return yield* this.routeDenseThroughAllMixedTerminationSteps({
32421
+ ...params,
32422
+ promotedPlaneReservationBusIds: [
32423
+ ...params.promotedPlaneReservationBusIds ?? [],
32424
+ ...refinedPromotions
32425
+ ],
32426
+ planeReservationRetryCount: (params.planeReservationRetryCount ?? 0) + 1
32427
+ });
32428
+ }
32429
+ }
31684
32430
  if (matchLengthsAfterPlanes) {
31685
32431
  return yield* this.routeDenseThroughAllMixedTerminationSteps({
31686
32432
  ...params,
@@ -45343,7 +46089,7 @@ function Group_getRoutingPhasePlans(group) {
45343
46089
  var package_default = {
45344
46090
  name: "@tscircuit/core",
45345
46091
  type: "module",
45346
- version: "0.0.1832",
46092
+ version: "0.0.1833",
45347
46093
  types: "dist/index.d.ts",
45348
46094
  main: "dist/index.js",
45349
46095
  module: "dist/index.js",
@@ -45383,7 +46129,7 @@ var package_default = {
45383
46129
  "@tscircuit/common": "^0.0.20",
45384
46130
  "@tscircuit/copper-pour-solver": "^0.0.52",
45385
46131
  "@tscircuit/create-fdm-enclosure": "0.0.4",
45386
- "@tscircuit/fanout-solver": "0.0.55",
46132
+ "@tscircuit/fanout-solver": "0.0.56",
45387
46133
  "@tscircuit/footprinter": "^0.0.426",
45388
46134
  "@tscircuit/image-utils": "^0.0.8",
45389
46135
  "@tscircuit/implicit-copper-pour-solver": "github:tscircuit/implicit-copper-pour-solver#d880f80e4eb7a34d5da5a521f10a63c810d4728b",
@@ -50237,15 +50983,15 @@ import {
50237
50983
  } from "@tscircuit/copper-pour-solver";
50238
50984
 
50239
50985
  // lib/components/primitive-components/CopperPour/utils/mark-trace-segments-inside-copper-pour.ts
50240
- var EPSILON10 = 1e-9;
50986
+ var EPSILON11 = 1e-9;
50241
50987
  var isWireRoutePoint = (routePoint) => routePoint.route_type === "wire";
50242
50988
  var isPointOnSegment = (p, a, b) => {
50243
50989
  const cross3 = (p.y - a.y) * (b.x - a.x) - (p.x - a.x) * (b.y - a.y);
50244
- if (Math.abs(cross3) > EPSILON10) return false;
50990
+ if (Math.abs(cross3) > EPSILON11) return false;
50245
50991
  const dot = (p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y);
50246
- if (dot < -EPSILON10) return false;
50992
+ if (dot < -EPSILON11) return false;
50247
50993
  const squaredLength = (b.x - a.x) ** 2 + (b.y - a.y) ** 2;
50248
- if (dot - squaredLength > EPSILON10) return false;
50994
+ if (dot - squaredLength > EPSILON11) return false;
50249
50995
  return true;
50250
50996
  };
50251
50997
  var isPointInRing = (point6, ring) => {
@@ -50269,7 +51015,7 @@ var isPointInRectPour = (p, pour) => {
50269
51015
  const dy = p.y - center.y;
50270
51016
  const localX = dx * cosR - dy * sinR;
50271
51017
  const localY = dx * sinR + dy * cosR;
50272
- return Math.abs(localX) <= width / 2 + EPSILON10 && Math.abs(localY) <= height / 2 + EPSILON10;
51018
+ return Math.abs(localX) <= width / 2 + EPSILON11 && Math.abs(localY) <= height / 2 + EPSILON11;
50273
51019
  };
50274
51020
  var isPointInBrepPour = (p, pour) => {
50275
51021
  const outerRing = pour.brep_shape.outer_ring.vertices.map((v) => ({
@@ -50303,7 +51049,7 @@ var isSegmentFullyInsideCopperPour = (start, end, pour) => {
50303
51049
  const dx = end.x - start.x;
50304
51050
  const dy = end.y - start.y;
50305
51051
  const length7 = Math.hypot(dx, dy);
50306
- if (length7 <= EPSILON10) return false;
51052
+ if (length7 <= EPSILON11) return false;
50307
51053
  const samples = [0, 0.25, 0.5, 0.75, 1];
50308
51054
  return samples.every(
50309
51055
  (t) => isPointInCopperPour(