@tscircuit/capacity-autorouter 0.0.50 → 0.0.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -559,6 +559,10 @@ declare class SingleHighDensityRouteSolver extends BaseSolver {
559
559
  debug_nodesTooCloseToObstacle: Set<string>;
560
560
  debug_nodePathToParentIntersectsObstacle: Set<string>;
561
561
  debugEnabled: boolean;
562
+ initialNodeGridOffset: {
563
+ x: number;
564
+ y: number;
565
+ };
562
566
  constructor(opts: {
563
567
  connectionName: string;
564
568
  obstacleRoutes: HighDensityIntraNodeRoute$1[];
@@ -1225,6 +1229,7 @@ declare class UnravelSectionSolver extends BaseSolver {
1225
1229
  colorMap: Record<string, string>;
1226
1230
  tunedNodeCapacityMap: Map<CapacityMeshNodeId, number>;
1227
1231
  MAX_CANDIDATES: number;
1232
+ iterationsSinceImprovement: number;
1228
1233
  selectedCandidateIndex: number | "best" | "original" | null;
1229
1234
  queuedOrExploredCandidatePointModificationHashes: Set<string>;
1230
1235
  constructor(params: {
package/dist/index.js CHANGED
@@ -2205,6 +2205,7 @@ var SingleHighDensityRouteSolver = class extends BaseSolver {
2205
2205
  debug_nodesTooCloseToObstacle;
2206
2206
  debug_nodePathToParentIntersectsObstacle;
2207
2207
  debugEnabled = true;
2208
+ initialNodeGridOffset;
2208
2209
  constructor(opts) {
2209
2210
  super();
2210
2211
  this.bounds = opts.bounds;
@@ -2250,11 +2251,18 @@ var SingleHighDensityRouteSolver = class extends BaseSolver {
2250
2251
  if (this.futureConnections && this.futureConnections.length === 0 && this.obstacleRoutes.length === 0) {
2251
2252
  this.handleSimpleCases();
2252
2253
  }
2254
+ const initialNodePosition = {
2255
+ x: Math.round(opts.A.x / (this.cellStep / 2)) * (this.cellStep / 2),
2256
+ y: Math.round(opts.A.y / (this.cellStep / 2)) * (this.cellStep / 2)
2257
+ };
2258
+ this.initialNodeGridOffset = {
2259
+ x: initialNodePosition.x - Math.round(opts.A.x / this.cellStep) * this.cellStep,
2260
+ y: initialNodePosition.y - Math.round(opts.A.y / this.cellStep) * this.cellStep
2261
+ };
2253
2262
  this.candidates = new SingleRouteCandidatePriorityQueue([
2254
2263
  {
2255
2264
  ...opts.A,
2256
- x: Math.floor(opts.A.x / this.cellStep) * this.cellStep,
2257
- y: Math.floor(opts.A.y / this.cellStep) * this.cellStep,
2265
+ ...initialNodePosition,
2258
2266
  z: opts.A.z ?? 0,
2259
2267
  g: 0,
2260
2268
  h: 0,
@@ -2317,7 +2325,7 @@ var SingleHighDensityRouteSolver = class extends BaseSolver {
2317
2325
  }
2318
2326
  }
2319
2327
  for (const via of route.vias) {
2320
- if (distance(node, via) < this.viaDiameter / 2 + margin) {
2328
+ if (distance(node, via) < this.viaDiameter / 2 + this.traceThickness / 2 + margin) {
2321
2329
  return true;
2322
2330
  }
2323
2331
  }
@@ -2484,7 +2492,13 @@ var SingleHighDensityRouteSolver = class extends BaseSolver {
2484
2492
  goalDist,
2485
2493
  currentNode.z === this.B.z
2486
2494
  );
2487
- if (goalDist <= this.cellStep * Math.SQRT2 && currentNode.z === this.B.z) {
2495
+ if (goalDist <= this.cellStep * Math.SQRT2 && currentNode.z === this.B.z && // Make sure the last segment doesn't intersect an obstacle
2496
+ !this.doesPathToParentIntersectObstacle({
2497
+ ...currentNode,
2498
+ parent: currentNode,
2499
+ x: this.B.x,
2500
+ y: this.B.y
2501
+ })) {
2488
2502
  this.solved = true;
2489
2503
  this.setSolvedPath(currentNode);
2490
2504
  }
@@ -2539,8 +2553,8 @@ z: ${this.B.z}`,
2539
2553
  if (this.debug_nodePathToParentIntersectsObstacle.has(nodeKey)) continue;
2540
2554
  graphics.rects.push({
2541
2555
  center: {
2542
- x: x + z * this.cellStep / 20,
2543
- y: y + z * this.cellStep / 20
2556
+ x: x + this.initialNodeGridOffset.x + z * this.cellStep / 20,
2557
+ y: y + this.initialNodeGridOffset.y + z * this.cellStep / 20
2544
2558
  },
2545
2559
  fill: z === 0 ? `rgba(255,0,255,${0.3 - i / this.debug_exploredNodesOrdered.length * 0.2})` : `rgba(0,0,255,${0.3 - i / this.debug_exploredNodesOrdered.length * 0.2})`,
2546
2560
  width: this.cellStep * 0.9,
@@ -4016,20 +4030,17 @@ function distance2(p1, p2) {
4016
4030
 
4017
4031
  // lib/solvers/HighDensitySolver/TwoRouteHighDensitySolver/calculateSideTraversal.ts
4018
4032
  var EPSILON2 = 1e-9;
4019
- function calculateSegmentTraversal(startPoint, endPoint, bounds) {
4033
+ function calculateSegmentTraversal(startPoint, endPoint, bounds, turnDirection = "cw") {
4020
4034
  const startAngle = pointToAngle(startPoint, bounds);
4021
- let endAngle = pointToAngle(endPoint, bounds);
4022
- if (endAngle < startAngle) {
4023
- endAngle += 2 * Math.PI;
4024
- }
4035
+ const endAngle = pointToAngle(endPoint, bounds);
4025
4036
  if (Math.abs(endAngle - startAngle) < EPSILON2) {
4026
4037
  return { left: 0, top: 0, right: 0, bottom: 0 };
4027
4038
  }
4028
- return calculateSidePercentages(startAngle, endAngle, bounds);
4039
+ return calculateSidePercentages(startAngle, endAngle, bounds, turnDirection);
4029
4040
  }
4030
- function calculateTraversalPercentages(A, B, C, bounds) {
4031
- const percentagesAB = calculateSegmentTraversal(A, B, bounds);
4032
- const percentagesBC = calculateSegmentTraversal(B, C, bounds);
4041
+ function calculateTraversalPercentages(A, B, C, bounds, turnDirection) {
4042
+ const percentagesAB = calculateSegmentTraversal(A, B, bounds, turnDirection);
4043
+ const percentagesBC = calculateSegmentTraversal(B, C, bounds, turnDirection);
4033
4044
  const totalPercentages = {
4034
4045
  left: Math.min(1, percentagesAB.left + percentagesBC.left),
4035
4046
  top: Math.min(1, percentagesAB.top + percentagesBC.top),
@@ -4066,7 +4077,7 @@ function pointToAngle(point, bounds) {
4066
4077
  distance6 = Math.max(0, Math.min(perimeter, distance6));
4067
4078
  return perimeter > EPSILON2 ? distance6 / perimeter * (2 * Math.PI) : 0;
4068
4079
  }
4069
- function calculateSidePercentages(startAngle, endAngle, bounds) {
4080
+ function calculateSidePercentages(startAngle, endAngle, bounds, turnDirection) {
4070
4081
  const width = bounds.maxX - bounds.minX;
4071
4082
  const height = bounds.maxY - bounds.minY;
4072
4083
  if (width < EPSILON2 && height < EPSILON2)
@@ -4090,17 +4101,54 @@ function calculateSidePercentages(startAngle, endAngle, bounds) {
4090
4101
  // Ends at 2PI
4091
4102
  ];
4092
4103
  const result = { left: 0, top: 0, right: 0, bottom: 0 };
4093
- const totalAngleTraversal = endAngle - startAngle;
4094
- if (totalAngleTraversal < EPSILON2) return result;
4104
+ const calculateTraversalOverlap = (sStart, sEnd, tStart, tEnd, wrapsAround) => {
4105
+ const effectiveSEnd = sEnd > 2 * Math.PI - EPSILON2 ? 2 * Math.PI : sEnd;
4106
+ if (effectiveSEnd <= sStart + EPSILON2) return 0;
4107
+ if (!wrapsAround) {
4108
+ const overlapStart = Math.max(sStart, tStart);
4109
+ const overlapEnd = Math.min(effectiveSEnd, tEnd);
4110
+ return Math.max(0, overlapEnd - overlapStart);
4111
+ } else {
4112
+ const overlap1Start = Math.max(sStart, tStart);
4113
+ const overlap1End = Math.min(effectiveSEnd, 2 * Math.PI);
4114
+ const overlap1 = Math.max(0, overlap1End - overlap1Start);
4115
+ const overlap2Start = Math.max(sStart, 0);
4116
+ const overlap2End = Math.min(effectiveSEnd, tEnd);
4117
+ const overlap2 = Math.max(0, overlap2End - overlap2Start);
4118
+ return overlap1 + overlap2;
4119
+ }
4120
+ };
4095
4121
  for (const side of sides) {
4096
4122
  const sideAngleRange = side.end - side.start;
4097
4123
  if (sideAngleRange < EPSILON2 || side.length < EPSILON2) continue;
4098
- const overlapStart = Math.max(startAngle, side.start);
4099
- const overlapEnd = Math.min(endAngle, side.end);
4100
- if (overlapStart < overlapEnd - EPSILON2) {
4101
- const traversedAngleOnSide = overlapEnd - overlapStart;
4124
+ let traversedAngleOnSide = 0;
4125
+ if (turnDirection === "cw") {
4126
+ const wraps = startAngle > endAngle + EPSILON2;
4127
+ traversedAngleOnSide = calculateTraversalOverlap(
4128
+ side.start,
4129
+ side.end,
4130
+ startAngle,
4131
+ endAngle,
4132
+ wraps
4133
+ );
4134
+ } else {
4135
+ const wraps = endAngle > startAngle + EPSILON2;
4136
+ traversedAngleOnSide = calculateTraversalOverlap(
4137
+ side.start,
4138
+ side.end,
4139
+ endAngle,
4140
+ // Start of equivalent CW traversal
4141
+ startAngle,
4142
+ // End of equivalent CW traversal
4143
+ wraps
4144
+ );
4145
+ }
4146
+ if (traversedAngleOnSide > EPSILON2) {
4102
4147
  const percentage = traversedAngleOnSide / sideAngleRange;
4103
- result[side.name] += Math.max(0, percentage);
4148
+ result[side.name] += Math.max(
4149
+ 0,
4150
+ Number.isFinite(percentage) ? percentage : 0
4151
+ );
4104
4152
  }
4105
4153
  }
4106
4154
  for (const key in result) {
@@ -4112,6 +4160,28 @@ function calculateSidePercentages(startAngle, endAngle, bounds) {
4112
4160
  return result;
4113
4161
  }
4114
4162
 
4163
+ // lib/solvers/HighDensitySolver/TwoRouteHighDensitySolver/computeTurnDirection.ts
4164
+ function triangleDirection({
4165
+ angleA,
4166
+ angleB,
4167
+ angleC
4168
+ }) {
4169
+ const Ax = Math.cos(angleA);
4170
+ const Ay = Math.sin(angleA);
4171
+ const Bx = Math.cos(angleB);
4172
+ const By = Math.sin(angleB);
4173
+ const Cx = Math.cos(angleC);
4174
+ const Cy = Math.sin(angleC);
4175
+ const signedArea = (Bx - Ax) * (Cy - Ay) - (By - Ay) * (Cx - Ax);
4176
+ return signedArea < 0 ? "ccw" : "cw";
4177
+ }
4178
+ function computeTurnDirection(A, B, C, bounds) {
4179
+ const angleA = pointToAngle(A, bounds);
4180
+ const angleB = pointToAngle(B, bounds);
4181
+ const angleC = pointToAngle(C, bounds);
4182
+ return triangleDirection({ angleA, angleB, angleC });
4183
+ }
4184
+
4115
4185
  // lib/solvers/HighDensitySolver/TwoRouteHighDensitySolver/SingleTransitionCrossingRouteSolver.ts
4116
4186
  var SingleTransitionCrossingRouteSolver = class extends BaseSolver {
4117
4187
  // Input parameters
@@ -4199,7 +4269,14 @@ var SingleTransitionCrossingRouteSolver = class extends BaseSolver {
4199
4269
  const A = flatRoute.A;
4200
4270
  const B = ntrP1;
4201
4271
  const C = flatRoute.B;
4202
- const sideTraversal = calculateTraversalPercentages(A, B, C, this.bounds);
4272
+ const turnDirection = computeTurnDirection(A, B, C, this.bounds);
4273
+ const sideTraversal = calculateTraversalPercentages(
4274
+ A,
4275
+ B,
4276
+ C,
4277
+ this.bounds,
4278
+ turnDirection
4279
+ );
4203
4280
  const viaBounds = {
4204
4281
  minX: this.bounds.minX + (sideTraversal.left > 0.5 ? marginFromBorderWithTrace : marginFromBorderWithoutTrace),
4205
4282
  minY: this.bounds.minY + (sideTraversal.bottom > 0.5 ? marginFromBorderWithTrace : marginFromBorderWithoutTrace),
@@ -4277,23 +4354,29 @@ var SingleTransitionCrossingRouteSolver = class extends BaseSolver {
4277
4354
  otherRouteStart.z !== flatStart.z ? otherRouteStart : otherRouteEnd,
4278
4355
  this.traceThickness
4279
4356
  );
4280
- const p1 = findPointToGetAroundCircle(flatStart, p2, {
4357
+ const viaCircle = {
4281
4358
  center: { x: via.x, y: via.y },
4282
4359
  radius: minDistFromViaToTrace
4283
- }).E;
4284
- const p3 = findPointToGetAroundCircle(p2, flatEnd, {
4285
- center: { x: via.x, y: via.y },
4286
- radius: minDistFromViaToTrace
4287
- }).E;
4288
- const p1IsNeeded = pointToSegmentDistance(via, flatStart, p2) < minDistFromViaToTrace;
4289
- const p3IsNeeded = pointToSegmentDistance(via, p2, flatEnd) < minDistFromViaToTrace;
4360
+ };
4361
+ const p1 = findPointToGetAroundCircle(flatStart, p2, viaCircle).E;
4362
+ const p3 = findPointToGetAroundCircle(p2, flatEnd, viaCircle).E;
4363
+ const p0_5 = findPointToGetAroundCircle(flatStart, p1, viaCircle).E;
4364
+ const p1_5 = findPointToGetAroundCircle(p1, p2, viaCircle).E;
4365
+ const p2_5 = findPointToGetAroundCircle(p2, p3, viaCircle).E;
4366
+ const p3_5 = findPointToGetAroundCircle(p3, flatEnd, viaCircle).E;
4367
+ const p2_better = findPointToGetAroundCircle(p1_5, p2_5, viaCircle).E;
4290
4368
  return {
4291
4369
  connectionName: flatRouteConnectionName,
4292
4370
  route: [
4293
4371
  { x: flatStart.x, y: flatStart.y, z: flatStart.z ?? 0 },
4294
- ...p1IsNeeded ? [{ x: p1.x, y: p1.y, z: flatStart.z ?? 0 }] : [],
4295
- { x: p2.x, y: p2.y, z: flatStart.z ?? 0 },
4296
- ...p3IsNeeded ? [{ x: p3.x, y: p3.y, z: flatStart.z ?? 0 }] : [],
4372
+ { x: p0_5.x, y: p0_5.y, z: flatStart.z ?? 0 },
4373
+ { x: p1.x, y: p1.y, z: flatStart.z ?? 0 },
4374
+ { x: p1_5.x, y: p1_5.y, z: flatStart.z ?? 0 },
4375
+ // { x: p2.x, y: p2.y, z: flatStart.z ?? 0 },
4376
+ { x: p2_better.x, y: p2_better.y, z: flatStart.z ?? 0 },
4377
+ { x: p2_5.x, y: p2_5.y, z: flatStart.z ?? 0 },
4378
+ { x: p3.x, y: p3.y, z: flatStart.z ?? 0 },
4379
+ { x: p3_5.x, y: p3_5.y, z: flatStart.z ?? 0 },
4297
4380
  { x: flatEnd.x, y: flatEnd.y, z: flatEnd.z ?? 0 }
4298
4381
  ],
4299
4382
  traceThickness: this.traceThickness,
@@ -5784,11 +5867,13 @@ var UnravelSectionSolver = class extends BaseSolver {
5784
5867
  colorMap;
5785
5868
  tunedNodeCapacityMap;
5786
5869
  MAX_CANDIDATES = 500;
5870
+ iterationsSinceImprovement = 0;
5787
5871
  selectedCandidateIndex = null;
5788
5872
  queuedOrExploredCandidatePointModificationHashes = /* @__PURE__ */ new Set();
5789
5873
  constructor(params) {
5790
5874
  super();
5791
5875
  this.MUTABLE_HOPS = params.MUTABLE_HOPS ?? this.MUTABLE_HOPS;
5876
+ this.MAX_ITERATIONS = 5e4;
5792
5877
  this.nodeMap = params.nodeMap;
5793
5878
  this.dedupedSegments = params.dedupedSegments;
5794
5879
  if (params.dedupedSegmentMap) {
@@ -6183,6 +6268,7 @@ var UnravelSectionSolver = class extends BaseSolver {
6183
6268
  }
6184
6269
  _step() {
6185
6270
  const candidate = this.candidates.shift();
6271
+ this.iterationsSinceImprovement++;
6186
6272
  if (!candidate) {
6187
6273
  this.solved = true;
6188
6274
  return;
@@ -6190,6 +6276,7 @@ var UnravelSectionSolver = class extends BaseSolver {
6190
6276
  this.lastProcessedCandidate = candidate;
6191
6277
  if (candidate.f < (this.bestCandidate?.f ?? Infinity)) {
6192
6278
  this.bestCandidate = candidate;
6279
+ this.iterationsSinceImprovement = 0;
6193
6280
  }
6194
6281
  this.getNeighbors(candidate).forEach((neighbor) => {
6195
6282
  const isPartialHashExplored = this.queuedOrExploredCandidatePointModificationHashes.has(
@@ -6591,8 +6678,7 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
6591
6678
  }
6592
6679
  this.activeSolver.step();
6593
6680
  const { bestCandidate, originalCandidate, lastProcessedCandidate } = this.activeSolver;
6594
- const giveUpFactor = 1 + 4 * (1 - Math.min(1, this.activeSolver.iterations / 40));
6595
- const shouldEarlyStop = lastProcessedCandidate && lastProcessedCandidate.g > bestCandidate.g * giveUpFactor;
6681
+ const shouldEarlyStop = this.activeSolver.iterationsSinceImprovement > 200;
6596
6682
  if (this.activeSolver.solved || shouldEarlyStop) {
6597
6683
  const foundBetterSolution = bestCandidate && bestCandidate.g < originalCandidate.g;
6598
6684
  if (foundBetterSolution) {
@@ -7909,13 +7995,13 @@ function minimumDistanceBetweenSegments(A1, A2, B1, B2) {
7909
7995
  if (segmentsIntersect(A1, A2, B1, B2)) {
7910
7996
  return 0;
7911
7997
  }
7912
- const distA1 = pointToSegmentDistance4(A1, B1, B2);
7913
- const distA2 = pointToSegmentDistance4(A2, B1, B2);
7914
- const distB1 = pointToSegmentDistance4(B1, A1, A2);
7915
- const distB2 = pointToSegmentDistance4(B2, A1, A2);
7998
+ const distA1 = pointToSegmentDistance5(A1, B1, B2);
7999
+ const distA2 = pointToSegmentDistance5(A2, B1, B2);
8000
+ const distB1 = pointToSegmentDistance5(B1, A1, A2);
8001
+ const distB2 = pointToSegmentDistance5(B2, A1, A2);
7916
8002
  return Math.min(distA1, distA2, distB1, distB2);
7917
8003
  }
7918
- function pointToSegmentDistance4(P, Q1, Q2) {
8004
+ function pointToSegmentDistance5(P, Q1, Q2) {
7919
8005
  const v = { x: Q2.x - Q1.x, y: Q2.y - Q1.y };
7920
8006
  const w = { x: P.x - Q1.x, y: P.y - Q1.y };
7921
8007
  const c1 = dotProduct(w, v);