@tscircuit/schematic-trace-solver 0.0.137 → 0.0.141

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 (21) hide show
  1. package/.github/workflows/bun-pver-release.yml +7 -3
  2. package/dist/index.d.ts +6 -0
  3. package/dist/index.js +304 -36
  4. package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationObstacleIndex.ts +182 -0
  5. package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +67 -25
  6. package/lib/solvers/AvailableNetOrientationSolver/constants.ts +2 -0
  7. package/lib/solvers/Example28Solver/labelMovement.ts +71 -0
  8. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +12 -4
  9. package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapShiftSolver.ts +60 -4
  10. package/package.json +5 -1
  11. package/tests/bug-reports/bug-report-20260819T091818Z/__snapshots__/bug-report-20260819T091818Z.snap.svg +237 -0
  12. package/tests/bug-reports/bug-report-20260819T091818Z/bug-report-20260819T091818Z.json +1052 -0
  13. package/tests/bug-reports/bug-report-20260819T091818Z/bug-report-20260819T091818Z.test.ts +33 -0
  14. package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +542 -0
  15. package/tests/repros/__snapshots__/repro-board-648-esp12f-section.snap.svg +124 -0
  16. package/tests/repros/assets/board-1273-trace-overlap-cycle.input.json +3519 -0
  17. package/tests/repros/assets/repro-board-648-esp12f-section.input.json +537 -0
  18. package/tests/repros/board-1273-trace-overlap-cycle.test.ts +30 -0
  19. package/tests/repros/repro-board-648-esp12f-section.test.ts +13 -0
  20. package/tests/repros/repro161-power-section-autolayout.test.ts +7 -0
  21. package/tests/solvers/AvailableNetOrientationSolver/AvailableNetOrientationObstacleIndex.test.ts +122 -0
@@ -7,6 +7,10 @@ on:
7
7
  paths:
8
8
  - 'lib/**'
9
9
  workflow_dispatch:
10
+ permissions:
11
+ contents: write
12
+ id-token: write
13
+ pull-requests: write
10
14
  jobs:
11
15
  publish:
12
16
  runs-on: ubuntu-latest
@@ -18,14 +22,14 @@ jobs:
18
22
  uses: oven-sh/setup-bun@v2
19
23
  with:
20
24
  bun-version: 1.3.1
21
- - uses: actions/setup-node@v3
25
+ - uses: actions/setup-node@v6
22
26
  with:
23
- node-version: 20
27
+ node-version: 24
24
28
  registry-url: https://registry.npmjs.org/
29
+ package-manager-cache: false
25
30
  - run: npm install -g pver
26
31
  - run: bun install --frozen-lockfile
27
32
  - run: bun run build
28
33
  - run: pver release
29
34
  env:
30
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
31
35
  GITHUB_TOKEN: ${{ secrets.TSCIRCUIT_BOT_GITHUB_TOKEN }}
package/dist/index.d.ts CHANGED
@@ -279,6 +279,7 @@ declare class TraceOverlapIssueSolver extends BaseSolver {
279
279
  }
280
280
 
281
281
  type ConnNetId = string;
282
+ type TraceState = Record<MspConnectionPairId, SolvedTracePath>;
282
283
  /**
283
284
  * This solver finds traces that overlap or meet collinearly and aren't
284
285
  * connected via the globalConnMap, then shifts them apart in the direction
@@ -308,6 +309,7 @@ declare class TraceOverlapShiftSolver extends BaseSolver {
308
309
  */
309
310
  traceNetIslands: Record<ConnNetId, Array<SolvedTracePath>>;
310
311
  correctedTraceMap: Record<MspConnectionPairId, SolvedTracePath>;
312
+ recentTraceStates: TraceState[];
311
313
  cleanupPhase: "diagonals" | "done" | null;
312
314
  constructor(params: {
313
315
  inputProblem: InputProblem;
@@ -324,6 +326,8 @@ declare class TraceOverlapShiftSolver extends BaseSolver {
324
326
  private findNextDiagonalSegment;
325
327
  private findAndFixNextDiagonalSegment;
326
328
  _step(): void;
329
+ private rememberTraceState;
330
+ private returnsToPreviousTraceState;
327
331
  visualize(): graphics_debug.GraphicsObject;
328
332
  }
329
333
 
@@ -787,6 +791,7 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
787
791
  private chipObstacleSpatialIndex;
788
792
  private maxSearchDistance;
789
793
  private pinMap;
794
+ private obstacleIndex;
790
795
  constructor(params: AvailableNetOrientationSolverParams);
791
796
  getConstructorParams(): ConstructorParameters<typeof AvailableNetOrientationSolver>[0];
792
797
  _step(): void;
@@ -824,6 +829,7 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
824
829
  private findValidLateralShiftedCandidate;
825
830
  private findValidCandidateInShiftColumn;
826
831
  private evaluateCandidate;
832
+ private recordCandidateResult;
827
833
  private getCandidateConnectorTrace;
828
834
  private isStandaloneSinglePinNetLabel;
829
835
  private labelIntersectsDifferentNetTrace;
package/dist/index.js CHANGED
@@ -2102,6 +2102,7 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
2102
2102
  };
2103
2103
 
2104
2104
  // lib/solvers/TraceOverlapShiftSolver/TraceOverlapShiftSolver.ts
2105
+ var TRACE_STATE_POSITION_EPSILON = 1e-6;
2105
2106
  var TraceOverlapShiftSolver = class extends BaseSolver {
2106
2107
  inputProblem;
2107
2108
  inputTracePaths;
@@ -2112,6 +2113,9 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
2112
2113
  */
2113
2114
  traceNetIslands = {};
2114
2115
  correctedTraceMap = {};
2116
+ // Keep only the current and previous layouts to detect a two-state cycle
2117
+ // without accumulating routing history for the whole solve.
2118
+ recentTraceStates = [];
2115
2119
  cleanupPhase = null;
2116
2120
  constructor(params) {
2117
2121
  super();
@@ -2123,6 +2127,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
2123
2127
  const { mspPairId } = tracePath;
2124
2128
  this.correctedTraceMap[mspPairId] = tracePath;
2125
2129
  }
2130
+ this.rememberTraceState(this.correctedTraceMap);
2126
2131
  this.traceNetIslands = this.computeTraceNetIslands();
2127
2132
  }
2128
2133
  getConstructorParams() {
@@ -2337,11 +2342,17 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
2337
2342
  }
2338
2343
  _step() {
2339
2344
  if (this.activeSubSolver?.solved) {
2340
- for (const [mspPairId, newTrace] of Object.entries(
2341
- this.activeSubSolver.correctedTraceMap
2342
- )) {
2343
- this.correctedTraceMap[mspPairId] = newTrace;
2345
+ const nextTraceState = {
2346
+ ...this.correctedTraceMap,
2347
+ ...this.activeSubSolver.correctedTraceMap
2348
+ };
2349
+ if (this.returnsToPreviousTraceState(nextTraceState)) {
2350
+ this.activeSubSolver = null;
2351
+ this.solved = true;
2352
+ return;
2344
2353
  }
2354
+ this.correctedTraceMap = nextTraceState;
2355
+ this.rememberTraceState(nextTraceState);
2345
2356
  this.activeSubSolver = null;
2346
2357
  this.traceNetIslands = this.computeTraceNetIslands();
2347
2358
  }
@@ -2374,6 +2385,34 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
2374
2385
  traceIdsToShift: this.traceIdsToShift
2375
2386
  });
2376
2387
  }
2388
+ rememberTraceState(traceState) {
2389
+ this.recentTraceStates.push({ ...traceState });
2390
+ if (this.recentTraceStates.length > 2) {
2391
+ this.recentTraceStates.shift();
2392
+ }
2393
+ }
2394
+ returnsToPreviousTraceState(candidateTraceState) {
2395
+ if (this.recentTraceStates.length < 2) return false;
2396
+ const previousTraceState = this.recentTraceStates[0];
2397
+ const traceIds = Object.keys(candidateTraceState);
2398
+ if (traceIds.length !== Object.keys(previousTraceState).length) return false;
2399
+ for (const traceId of traceIds) {
2400
+ const candidatePath = candidateTraceState[traceId];
2401
+ const previousPath = previousTraceState[traceId];
2402
+ if (!candidatePath || !previousPath) return false;
2403
+ if (candidatePath.tracePath.length !== previousPath.tracePath.length) {
2404
+ return false;
2405
+ }
2406
+ for (let i = 0; i < candidatePath.tracePath.length; i++) {
2407
+ const candidatePoint = candidatePath.tracePath[i];
2408
+ const previousPoint = previousPath.tracePath[i];
2409
+ if (Math.abs(candidatePoint.x - previousPoint.x) > TRACE_STATE_POSITION_EPSILON || Math.abs(candidatePoint.y - previousPoint.y) > TRACE_STATE_POSITION_EPSILON) {
2410
+ return false;
2411
+ }
2412
+ }
2413
+ }
2414
+ return true;
2415
+ }
2377
2416
  visualize() {
2378
2417
  if (this.activeSubSolver) {
2379
2418
  return this.activeSubSolver.visualize();
@@ -7159,6 +7198,65 @@ var moveAttachedLabelsToReroutedTrace = ({
7159
7198
  }
7160
7199
  };
7161
7200
  });
7201
+ var moveNetLabelConnectorsToReroutedTraces = ({
7202
+ originalTraces,
7203
+ reroutedTraces,
7204
+ netLabelPlacements
7205
+ }) => {
7206
+ const traces = [...reroutedTraces];
7207
+ const reroutedTraceMap = new Map(
7208
+ reroutedTraces.map((trace) => [trace.mspPairId, trace])
7209
+ );
7210
+ const labels = netLabelPlacements.map((label) => {
7211
+ const originalHostTrace = originalTraces.find((trace) => {
7212
+ if (!label.mspConnectionPairIds.includes(trace.mspPairId)) return false;
7213
+ const reroutedTrace = reroutedTraceMap.get(trace.mspPairId);
7214
+ if (!reroutedTrace) return false;
7215
+ return reroutedTrace.tracePath !== trace.tracePath;
7216
+ });
7217
+ if (!originalHostTrace) return label;
7218
+ const reroutedHostTrace = reroutedTraceMap.get(originalHostTrace.mspPairId);
7219
+ const connectorIndex = originalTraces.findIndex((trace) => {
7220
+ if (trace.mspPairId === originalHostTrace.mspPairId) return false;
7221
+ if (trace.globalConnNetId !== label.globalConnNetId) return false;
7222
+ if (!tracePathContainsPoint(trace.tracePath, label.anchorPoint)) {
7223
+ return false;
7224
+ }
7225
+ return label.pinIds.every((pinId) => trace.pinIds.includes(pinId));
7226
+ });
7227
+ if (connectorIndex < 0) return label;
7228
+ const connector = originalTraces[connectorIndex];
7229
+ const originalJunction = connector.tracePath.find(
7230
+ (point) => tracePathContainsPoint(originalHostTrace.tracePath, point)
7231
+ );
7232
+ if (!originalJunction) return label;
7233
+ const reroutedJunction = getMovedAnchorPointForReroute(
7234
+ originalJunction,
7235
+ originalHostTrace.tracePath,
7236
+ reroutedHostTrace.tracePath
7237
+ );
7238
+ if (!reroutedJunction) return label;
7239
+ const delta = {
7240
+ x: reroutedJunction.x - originalJunction.x,
7241
+ y: reroutedJunction.y - originalJunction.y
7242
+ };
7243
+ const movedConnector = {
7244
+ ...connector,
7245
+ tracePath: connector.tracePath.map((point) => ({
7246
+ x: point.x + delta.x,
7247
+ y: point.y + delta.y
7248
+ }))
7249
+ };
7250
+ traces[connectorIndex] = movedConnector;
7251
+ return moveAttachedLabelsToReroutedTrace({
7252
+ trace: connector,
7253
+ originalTracePath: connector.tracePath,
7254
+ reroutedTracePath: movedConnector.tracePath,
7255
+ netLabelPlacements: [label]
7256
+ })[0];
7257
+ });
7258
+ return { traces, netLabelPlacements: labels };
7259
+ };
7162
7260
 
7163
7261
  // lib/solvers/Example28Solver/doesPathRunAlongChipBoundary.ts
7164
7262
  var doesPathRunAlongChipBoundary = (path, chipObstacles) => {
@@ -7951,6 +8049,7 @@ var createPortOnlyLabelConnectorTrace = ({
7951
8049
 
7952
8050
  // lib/solvers/AvailableNetOrientationSolver/constants.ts
7953
8051
  var LABEL_SEARCH_STEP = 0.05;
8052
+ var MAX_RECORDED_CANDIDATES = 2e3;
7954
8053
  var WICK_CLEARANCE = 1e-3;
7955
8054
  var EPS12 = 1e-9;
7956
8055
  var TRACE_BOUNDARY_TOLERANCE = WICK_CLEARANCE + EPS12;
@@ -8007,14 +8106,6 @@ var segmentCrossesBoundsInterior = (p1, p2, bounds) => {
8007
8106
  }
8008
8107
  return false;
8009
8108
  };
8010
- var tracePathCrossesAnyBounds = (tracePath, bounds) => {
8011
- for (let i = 0; i < tracePath.length - 1; i++) {
8012
- if (segmentCrossesBoundsInterior(tracePath[i], tracePath[i + 1], bounds)) {
8013
- return true;
8014
- }
8015
- }
8016
- return false;
8017
- };
8018
8109
  var tracePathIntersectsBounds = (tracePath, bounds) => {
8019
8110
  for (let i = 0; i < tracePath.length - 1; i++) {
8020
8111
  if (segmentIntersectsRect2(tracePath[i], tracePath[i + 1], bounds)) {
@@ -8184,6 +8275,145 @@ var ensureGraphicsArrays = (graphics) => {
8184
8275
  if (!graphics.texts) graphics.texts = [];
8185
8276
  };
8186
8277
 
8278
+ // lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationObstacleIndex.ts
8279
+ import Flatbush2 from "flatbush";
8280
+ var AvailableNetOrientationObstacleIndex = class {
8281
+ chipObstacleSpatialIndex;
8282
+ labelIndex = null;
8283
+ traceSegmentIndex = null;
8284
+ traceSegments = [];
8285
+ constructor(params) {
8286
+ this.chipObstacleSpatialIndex = params.chipObstacleSpatialIndex;
8287
+ this.rebuild(params);
8288
+ }
8289
+ rebuild(params) {
8290
+ this.labelIndex = this.buildLabelIndex(params.netLabelPlacements);
8291
+ this.traceSegments = this.getTraceSegments(params.traces);
8292
+ this.traceSegmentIndex = this.buildTraceSegmentIndex(this.traceSegments);
8293
+ }
8294
+ getLabelIndicesInBounds(bounds) {
8295
+ if (!this.labelIndex) return [];
8296
+ return this.labelIndex.search(
8297
+ bounds.minX,
8298
+ bounds.minY,
8299
+ bounds.maxX,
8300
+ bounds.maxY
8301
+ );
8302
+ }
8303
+ getTraceSegmentsInBounds(bounds) {
8304
+ if (!this.traceSegmentIndex) return [];
8305
+ const searchBounds = getPaddedBounds(bounds, EPS12);
8306
+ return this.traceSegmentIndex.search(
8307
+ searchBounds.minX,
8308
+ searchBounds.minY,
8309
+ searchBounds.maxX,
8310
+ searchBounds.maxY
8311
+ ).map((segmentIndex) => this.traceSegments[segmentIndex]);
8312
+ }
8313
+ getLabelIndicesNearTracePath(tracePath) {
8314
+ const labelIndices = /* @__PURE__ */ new Set();
8315
+ for (let pointIndex = 0; pointIndex < tracePath.length - 1; pointIndex++) {
8316
+ const segmentBounds = getPaddedBounds(
8317
+ getSegmentBounds(tracePath[pointIndex], tracePath[pointIndex + 1]),
8318
+ EPS12
8319
+ );
8320
+ for (const labelIndex of this.getLabelIndicesInBounds(segmentBounds)) {
8321
+ labelIndices.add(labelIndex);
8322
+ }
8323
+ }
8324
+ return [...labelIndices].sort((a, b) => a - b);
8325
+ }
8326
+ doesTracePathCrossChip(tracePath) {
8327
+ for (let pointIndex = 0; pointIndex < tracePath.length - 1; pointIndex++) {
8328
+ const start = tracePath[pointIndex];
8329
+ const end = tracePath[pointIndex + 1];
8330
+ const nearbyChips = this.chipObstacleSpatialIndex.getChipsInBounds(
8331
+ getPaddedBounds(getSegmentBounds(start, end), EPS12)
8332
+ );
8333
+ for (const chip of nearbyChips) {
8334
+ if (segmentCrossesBoundsInterior(start, end, chip.bounds)) return true;
8335
+ }
8336
+ }
8337
+ return false;
8338
+ }
8339
+ doesTraceCrossBoundsInterior(bounds) {
8340
+ for (const segment of this.getTraceSegmentsInBounds(bounds)) {
8341
+ if (segmentCrossesBoundsInterior(segment.start, segment.end, bounds)) {
8342
+ return true;
8343
+ }
8344
+ }
8345
+ return false;
8346
+ }
8347
+ doesTraceIntersectBounds(params) {
8348
+ for (const segment of this.getTraceSegmentsInBounds(params.bounds)) {
8349
+ if (segment.trace.globalConnNetId === params.excludedGlobalConnNetId) {
8350
+ continue;
8351
+ }
8352
+ if (segmentIntersectsRect2(segment.start, segment.end, params.bounds)) {
8353
+ return true;
8354
+ }
8355
+ }
8356
+ return false;
8357
+ }
8358
+ buildLabelIndex(netLabelPlacements) {
8359
+ if (netLabelPlacements.length === 0) return null;
8360
+ const labelIndex = new Flatbush2(netLabelPlacements.length);
8361
+ for (const label of netLabelPlacements) {
8362
+ labelIndex.add(
8363
+ label.center.x - label.width / 2,
8364
+ label.center.y - label.height / 2,
8365
+ label.center.x + label.width / 2,
8366
+ label.center.y + label.height / 2
8367
+ );
8368
+ }
8369
+ labelIndex.finish();
8370
+ return labelIndex;
8371
+ }
8372
+ getTraceSegments(traces) {
8373
+ const traceSegments = [];
8374
+ for (const trace of traces) {
8375
+ for (let pointIndex = 0; pointIndex < trace.tracePath.length - 1; pointIndex++) {
8376
+ traceSegments.push({
8377
+ trace,
8378
+ start: trace.tracePath[pointIndex],
8379
+ end: trace.tracePath[pointIndex + 1]
8380
+ });
8381
+ }
8382
+ }
8383
+ return traceSegments;
8384
+ }
8385
+ buildTraceSegmentIndex(traceSegments) {
8386
+ if (traceSegments.length === 0) return null;
8387
+ const traceSegmentIndex = new Flatbush2(traceSegments.length);
8388
+ for (const segment of traceSegments) {
8389
+ traceSegmentIndex.add(
8390
+ Math.min(segment.start.x, segment.end.x),
8391
+ Math.min(segment.start.y, segment.end.y),
8392
+ Math.max(segment.start.x, segment.end.x),
8393
+ Math.max(segment.start.y, segment.end.y)
8394
+ );
8395
+ }
8396
+ traceSegmentIndex.finish();
8397
+ return traceSegmentIndex;
8398
+ }
8399
+ };
8400
+ function getSegmentBounds(start, end) {
8401
+ return {
8402
+ minX: Math.min(start.x, end.x),
8403
+ minY: Math.min(start.y, end.y),
8404
+ maxX: Math.max(start.x, end.x),
8405
+ maxY: Math.max(start.y, end.y)
8406
+ };
8407
+ }
8408
+ function getPaddedBounds(bounds, padding) {
8409
+ return {
8410
+ minX: bounds.minX - padding,
8411
+ minY: bounds.minY - padding,
8412
+ maxX: bounds.maxX + padding,
8413
+ maxY: bounds.maxY + padding
8414
+ };
8415
+ }
8416
+
8187
8417
  // lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts
8188
8418
  var LABEL_TRACE_CLEARANCE2 = 0.1;
8189
8419
  var AvailableNetOrientationSolver = class extends BaseSolver {
@@ -8202,6 +8432,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8202
8432
  chipObstacleSpatialIndex;
8203
8433
  maxSearchDistance;
8204
8434
  pinMap;
8435
+ obstacleIndex;
8205
8436
  constructor(params) {
8206
8437
  super();
8207
8438
  this.inputProblem = params.inputProblem;
@@ -8214,6 +8445,11 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8214
8445
  this.pinMap = getPinMap(params.inputProblem);
8215
8446
  this.chipObstacleSpatialIndex = params.inputProblem._chipObstacleSpatialIndex ?? new ChipObstacleSpatialIndex(params.inputProblem.chips);
8216
8447
  this.maxSearchDistance = getMaxSearchDistance(params.inputProblem);
8448
+ this.obstacleIndex = new AvailableNetOrientationObstacleIndex({
8449
+ chipObstacleSpatialIndex: this.chipObstacleSpatialIndex,
8450
+ netLabelPlacements: this.outputNetLabelPlacements,
8451
+ traces: this.traces
8452
+ });
8217
8453
  this.crowdedPortOnlyLabelIndices = this.getCrowdedPortOnlyLabelIndices();
8218
8454
  this.queuedLabelIndices = this.getProcessableLabelIndices();
8219
8455
  this.setCurrentLabel(this.queuedLabelIndices[0] ?? null);
@@ -8395,6 +8631,10 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8395
8631
  ...toNetLabelPlacementPatch(candidate)
8396
8632
  };
8397
8633
  this.addConnectorTrace(label, candidate, labelIndex);
8634
+ this.obstacleIndex.rebuild({
8635
+ netLabelPlacements: this.outputNetLabelPlacements,
8636
+ traces: this.traces
8637
+ });
8398
8638
  }
8399
8639
  addConnectorTrace(label, candidate, labelIndex) {
8400
8640
  if (candidate.phase === "trace-anchor") return;
@@ -8557,7 +8797,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8557
8797
  anchorY - label.anchorPoint.y,
8558
8798
  anchorX - label.anchorPoint.x
8559
8799
  );
8560
- this.currentCandidateResults.push(result);
8800
+ this.recordCandidateResult(result);
8561
8801
  if (result.status !== "valid") continue;
8562
8802
  result.selected = true;
8563
8803
  return result;
@@ -8592,7 +8832,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8592
8832
  labelIndex,
8593
8833
  "rotate"
8594
8834
  );
8595
- this.currentCandidateResults.push(result);
8835
+ this.recordCandidateResult(result);
8596
8836
  if (result.status === "valid") {
8597
8837
  result.selected = true;
8598
8838
  return result;
@@ -8617,7 +8857,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8617
8857
  labelIndex,
8618
8858
  "trace-anchor"
8619
8859
  );
8620
- this.currentCandidateResults.push(result);
8860
+ this.recordCandidateResult(result);
8621
8861
  if (result.status === "valid") {
8622
8862
  result.selected = true;
8623
8863
  return result;
@@ -8654,7 +8894,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8654
8894
  labelIndex,
8655
8895
  "outward-trace-anchor"
8656
8896
  );
8657
- this.currentCandidateResults.push(preservedColumnResult);
8897
+ this.recordCandidateResult(preservedColumnResult);
8658
8898
  if (preservedColumnResult.status === "valid") {
8659
8899
  preservedColumnResult.selected = true;
8660
8900
  return preservedColumnResult;
@@ -8682,7 +8922,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8682
8922
  "outward-trace-anchor",
8683
8923
  distance5
8684
8924
  );
8685
- this.currentCandidateResults.push(result);
8925
+ this.recordCandidateResult(result);
8686
8926
  if (result.status === "valid") {
8687
8927
  result.selected = true;
8688
8928
  return result;
@@ -8807,7 +9047,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8807
9047
  distance5,
8808
9048
  outwardDistance
8809
9049
  );
8810
- this.currentCandidateResults.push(result);
9050
+ this.recordCandidateResult(result);
8811
9051
  if (result.status === "valid") {
8812
9052
  result.selected = true;
8813
9053
  return result;
@@ -8831,6 +9071,18 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8831
9071
  })
8832
9072
  };
8833
9073
  }
9074
+ recordCandidateResult(candidate) {
9075
+ this.stats.candidateEvaluations = (this.stats.candidateEvaluations ?? 0) + 1;
9076
+ if (this.currentCandidateResults.length < MAX_RECORDED_CANDIDATES) {
9077
+ this.currentCandidateResults.push(candidate);
9078
+ } else if (candidate.status === "valid") {
9079
+ this.currentCandidateResults[MAX_RECORDED_CANDIDATES - 1] = candidate;
9080
+ }
9081
+ this.stats.maxRecordedCandidates = Math.max(
9082
+ this.stats.maxRecordedCandidates ?? 0,
9083
+ this.currentCandidateResults.length
9084
+ );
9085
+ }
8834
9086
  getCandidateConnectorTrace(label, candidate, labelIndex) {
8835
9087
  if (this.crowdedPortOnlyLabelIndices.has(labelIndex) && this.getChipSideForPoint(label.anchorPoint) === "top" && isYOrientation(candidate.orientation)) {
8836
9088
  return simplifyOrthogonalPath([
@@ -9070,12 +9322,12 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
9070
9322
  },
9071
9323
  labelIndex
9072
9324
  );
9073
- for (const chip of this.chipObstacleSpatialIndex.chips) {
9074
- if (tracePathCrossesAnyBounds(connectorTrace, chip.bounds)) {
9075
- return "chip-collision";
9076
- }
9325
+ if (this.obstacleIndex.doesTracePathCrossChip(connectorTrace)) {
9326
+ return "chip-collision";
9077
9327
  }
9078
- for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
9328
+ for (const i of this.obstacleIndex.getLabelIndicesNearTracePath(
9329
+ connectorTrace
9330
+ )) {
9079
9331
  if (i === labelIndex) continue;
9080
9332
  if (this.shouldIgnorePendingCrowdedLabel(labelIndex, i)) continue;
9081
9333
  const otherLabel = this.outputNetLabelPlacements[i];
@@ -9117,7 +9369,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
9117
9369
  if (rectIntersectsAnyTextBox(bounds, this.inputProblem)) {
9118
9370
  return "text-collision";
9119
9371
  }
9120
- if (traceCrossesBoundsInterior(bounds, this.traceMap)) {
9372
+ if (this.obstacleIndex.doesTraceCrossBoundsInterior(bounds)) {
9121
9373
  return "trace-collision";
9122
9374
  }
9123
9375
  if (this.isTraceTooCloseToLabel(bounds, label)) {
@@ -9131,13 +9383,10 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
9131
9383
  isTraceTooCloseToLabel(bounds, label) {
9132
9384
  if (!this.shouldCheckTraceClearanceForLabel(label)) return false;
9133
9385
  const clearanceBounds = this.getLabelTraceClearanceBounds(bounds);
9134
- for (const trace of Object.values(this.traceMap)) {
9135
- if (trace.globalConnNetId === label.globalConnNetId) continue;
9136
- if (tracePathIntersectsBounds(trace.tracePath, clearanceBounds)) {
9137
- return true;
9138
- }
9139
- }
9140
- return false;
9386
+ return this.obstacleIndex.doesTraceIntersectBounds({
9387
+ bounds: clearanceBounds,
9388
+ excludedGlobalConnNetId: label.globalConnNetId
9389
+ });
9141
9390
  }
9142
9391
  getLabelTraceClearanceBounds(bounds) {
9143
9392
  return {
@@ -9220,7 +9469,14 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
9220
9469
  return this.crowdedPortOnlyLabelIndices.has(labelIndex) && this.crowdedPortOnlyLabelIndices.has(otherLabelIndex) && this.queuedLabelIndices.includes(otherLabelIndex);
9221
9470
  }
9222
9471
  intersectsAnyOtherNetLabel(bounds, labelIndex) {
9223
- for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
9472
+ const searchBounds = {
9473
+ minX: bounds.minX - LABEL_SEARCH_STEP,
9474
+ minY: bounds.minY - LABEL_SEARCH_STEP,
9475
+ maxX: bounds.maxX + LABEL_SEARCH_STEP,
9476
+ maxY: bounds.maxY + LABEL_SEARCH_STEP
9477
+ };
9478
+ const nearbyLabelIndices = this.obstacleIndex.getLabelIndicesInBounds(searchBounds).sort((a, b) => a - b);
9479
+ for (const i of nearbyLabelIndices) {
9224
9480
  if (i === labelIndex) continue;
9225
9481
  if (this.shouldIgnorePendingCrowdedLabel(labelIndex, i)) continue;
9226
9482
  const label = this.outputNetLabelPlacements[i];
@@ -9234,7 +9490,14 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
9234
9490
  return false;
9235
9491
  }
9236
9492
  sharesChipBoundary(bounds) {
9237
- for (const chip of this.chipObstacleSpatialIndex.chips) {
9493
+ const boundarySearchBounds = {
9494
+ minX: bounds.minX - WICK_CLEARANCE - EPS12,
9495
+ minY: bounds.minY - WICK_CLEARANCE - EPS12,
9496
+ maxX: bounds.maxX + WICK_CLEARANCE + EPS12,
9497
+ maxY: bounds.maxY + WICK_CLEARANCE + EPS12
9498
+ };
9499
+ const nearbyChips = this.chipObstacleSpatialIndex.getChipsInBounds(boundarySearchBounds);
9500
+ for (const chip of nearbyChips) {
9238
9501
  const chipBounds = chip.bounds;
9239
9502
  const adjacentToVerticalSide = Math.abs(bounds.minX - chipBounds.maxX) <= WICK_CLEARANCE + EPS12 || Math.abs(bounds.maxX - chipBounds.minX) <= WICK_CLEARANCE + EPS12;
9240
9503
  const adjacentToHorizontalSide = Math.abs(bounds.minY - chipBounds.maxY) <= WICK_CLEARANCE + EPS12 || Math.abs(bounds.maxY - chipBounds.minY) <= WICK_CLEARANCE + EPS12;
@@ -12963,11 +13226,16 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
12963
13226
  (instance) => {
12964
13227
  const previousOutput = instance.preAlignmentNetLabelTraceCollisionSolver.getOutput();
12965
13228
  const alignmentOutput = instance.traceCleanupSolver2.getOutput();
13229
+ const connectorMovement = moveNetLabelConnectorsToReroutedTraces({
13230
+ originalTraces: previousOutput.traces,
13231
+ reroutedTraces: alignmentOutput.traces,
13232
+ netLabelPlacements: previousOutput.netLabelPlacements
13233
+ });
12966
13234
  const previousTraceMap = new Map(
12967
13235
  previousOutput.traces.map((trace) => [trace.mspPairId, trace])
12968
13236
  );
12969
- let netLabelPlacements = previousOutput.netLabelPlacements;
12970
- for (const trace of alignmentOutput.traces) {
13237
+ let netLabelPlacements = connectorMovement.netLabelPlacements;
13238
+ for (const trace of connectorMovement.traces) {
12971
13239
  const previousTrace = previousTraceMap.get(trace.mspPairId);
12972
13240
  if (!previousTrace || previousTrace.tracePath === trace.tracePath) {
12973
13241
  continue;
@@ -12987,7 +13255,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
12987
13255
  return [
12988
13256
  {
12989
13257
  inputProblem: instance.inputProblem,
12990
- traces: alignmentOutput.traces,
13258
+ traces: connectorMovement.traces,
12991
13259
  netLabelPlacements
12992
13260
  }
12993
13261
  ];