@tscircuit/schematic-trace-solver 0.0.137 → 0.0.140

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.
@@ -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
@@ -787,6 +787,7 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
787
787
  private chipObstacleSpatialIndex;
788
788
  private maxSearchDistance;
789
789
  private pinMap;
790
+ private obstacleIndex;
790
791
  constructor(params: AvailableNetOrientationSolverParams);
791
792
  getConstructorParams(): ConstructorParameters<typeof AvailableNetOrientationSolver>[0];
792
793
  _step(): void;
@@ -824,6 +825,7 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
824
825
  private findValidLateralShiftedCandidate;
825
826
  private findValidCandidateInShiftColumn;
826
827
  private evaluateCandidate;
828
+ private recordCandidateResult;
827
829
  private getCandidateConnectorTrace;
828
830
  private isStandaloneSinglePinNetLabel;
829
831
  private labelIntersectsDifferentNetTrace;
package/dist/index.js CHANGED
@@ -7159,6 +7159,65 @@ var moveAttachedLabelsToReroutedTrace = ({
7159
7159
  }
7160
7160
  };
7161
7161
  });
7162
+ var moveNetLabelConnectorsToReroutedTraces = ({
7163
+ originalTraces,
7164
+ reroutedTraces,
7165
+ netLabelPlacements
7166
+ }) => {
7167
+ const traces = [...reroutedTraces];
7168
+ const reroutedTraceMap = new Map(
7169
+ reroutedTraces.map((trace) => [trace.mspPairId, trace])
7170
+ );
7171
+ const labels = netLabelPlacements.map((label) => {
7172
+ const originalHostTrace = originalTraces.find((trace) => {
7173
+ if (!label.mspConnectionPairIds.includes(trace.mspPairId)) return false;
7174
+ const reroutedTrace = reroutedTraceMap.get(trace.mspPairId);
7175
+ if (!reroutedTrace) return false;
7176
+ return reroutedTrace.tracePath !== trace.tracePath;
7177
+ });
7178
+ if (!originalHostTrace) return label;
7179
+ const reroutedHostTrace = reroutedTraceMap.get(originalHostTrace.mspPairId);
7180
+ const connectorIndex = originalTraces.findIndex((trace) => {
7181
+ if (trace.mspPairId === originalHostTrace.mspPairId) return false;
7182
+ if (trace.globalConnNetId !== label.globalConnNetId) return false;
7183
+ if (!tracePathContainsPoint(trace.tracePath, label.anchorPoint)) {
7184
+ return false;
7185
+ }
7186
+ return label.pinIds.every((pinId) => trace.pinIds.includes(pinId));
7187
+ });
7188
+ if (connectorIndex < 0) return label;
7189
+ const connector = originalTraces[connectorIndex];
7190
+ const originalJunction = connector.tracePath.find(
7191
+ (point) => tracePathContainsPoint(originalHostTrace.tracePath, point)
7192
+ );
7193
+ if (!originalJunction) return label;
7194
+ const reroutedJunction = getMovedAnchorPointForReroute(
7195
+ originalJunction,
7196
+ originalHostTrace.tracePath,
7197
+ reroutedHostTrace.tracePath
7198
+ );
7199
+ if (!reroutedJunction) return label;
7200
+ const delta = {
7201
+ x: reroutedJunction.x - originalJunction.x,
7202
+ y: reroutedJunction.y - originalJunction.y
7203
+ };
7204
+ const movedConnector = {
7205
+ ...connector,
7206
+ tracePath: connector.tracePath.map((point) => ({
7207
+ x: point.x + delta.x,
7208
+ y: point.y + delta.y
7209
+ }))
7210
+ };
7211
+ traces[connectorIndex] = movedConnector;
7212
+ return moveAttachedLabelsToReroutedTrace({
7213
+ trace: connector,
7214
+ originalTracePath: connector.tracePath,
7215
+ reroutedTracePath: movedConnector.tracePath,
7216
+ netLabelPlacements: [label]
7217
+ })[0];
7218
+ });
7219
+ return { traces, netLabelPlacements: labels };
7220
+ };
7162
7221
 
7163
7222
  // lib/solvers/Example28Solver/doesPathRunAlongChipBoundary.ts
7164
7223
  var doesPathRunAlongChipBoundary = (path, chipObstacles) => {
@@ -7951,6 +8010,7 @@ var createPortOnlyLabelConnectorTrace = ({
7951
8010
 
7952
8011
  // lib/solvers/AvailableNetOrientationSolver/constants.ts
7953
8012
  var LABEL_SEARCH_STEP = 0.05;
8013
+ var MAX_RECORDED_CANDIDATES = 2e3;
7954
8014
  var WICK_CLEARANCE = 1e-3;
7955
8015
  var EPS12 = 1e-9;
7956
8016
  var TRACE_BOUNDARY_TOLERANCE = WICK_CLEARANCE + EPS12;
@@ -8007,14 +8067,6 @@ var segmentCrossesBoundsInterior = (p1, p2, bounds) => {
8007
8067
  }
8008
8068
  return false;
8009
8069
  };
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
8070
  var tracePathIntersectsBounds = (tracePath, bounds) => {
8019
8071
  for (let i = 0; i < tracePath.length - 1; i++) {
8020
8072
  if (segmentIntersectsRect2(tracePath[i], tracePath[i + 1], bounds)) {
@@ -8184,6 +8236,145 @@ var ensureGraphicsArrays = (graphics) => {
8184
8236
  if (!graphics.texts) graphics.texts = [];
8185
8237
  };
8186
8238
 
8239
+ // lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationObstacleIndex.ts
8240
+ import Flatbush2 from "flatbush";
8241
+ var AvailableNetOrientationObstacleIndex = class {
8242
+ chipObstacleSpatialIndex;
8243
+ labelIndex = null;
8244
+ traceSegmentIndex = null;
8245
+ traceSegments = [];
8246
+ constructor(params) {
8247
+ this.chipObstacleSpatialIndex = params.chipObstacleSpatialIndex;
8248
+ this.rebuild(params);
8249
+ }
8250
+ rebuild(params) {
8251
+ this.labelIndex = this.buildLabelIndex(params.netLabelPlacements);
8252
+ this.traceSegments = this.getTraceSegments(params.traces);
8253
+ this.traceSegmentIndex = this.buildTraceSegmentIndex(this.traceSegments);
8254
+ }
8255
+ getLabelIndicesInBounds(bounds) {
8256
+ if (!this.labelIndex) return [];
8257
+ return this.labelIndex.search(
8258
+ bounds.minX,
8259
+ bounds.minY,
8260
+ bounds.maxX,
8261
+ bounds.maxY
8262
+ );
8263
+ }
8264
+ getTraceSegmentsInBounds(bounds) {
8265
+ if (!this.traceSegmentIndex) return [];
8266
+ const searchBounds = getPaddedBounds(bounds, EPS12);
8267
+ return this.traceSegmentIndex.search(
8268
+ searchBounds.minX,
8269
+ searchBounds.minY,
8270
+ searchBounds.maxX,
8271
+ searchBounds.maxY
8272
+ ).map((segmentIndex) => this.traceSegments[segmentIndex]);
8273
+ }
8274
+ getLabelIndicesNearTracePath(tracePath) {
8275
+ const labelIndices = /* @__PURE__ */ new Set();
8276
+ for (let pointIndex = 0; pointIndex < tracePath.length - 1; pointIndex++) {
8277
+ const segmentBounds = getPaddedBounds(
8278
+ getSegmentBounds(tracePath[pointIndex], tracePath[pointIndex + 1]),
8279
+ EPS12
8280
+ );
8281
+ for (const labelIndex of this.getLabelIndicesInBounds(segmentBounds)) {
8282
+ labelIndices.add(labelIndex);
8283
+ }
8284
+ }
8285
+ return [...labelIndices].sort((a, b) => a - b);
8286
+ }
8287
+ doesTracePathCrossChip(tracePath) {
8288
+ for (let pointIndex = 0; pointIndex < tracePath.length - 1; pointIndex++) {
8289
+ const start = tracePath[pointIndex];
8290
+ const end = tracePath[pointIndex + 1];
8291
+ const nearbyChips = this.chipObstacleSpatialIndex.getChipsInBounds(
8292
+ getPaddedBounds(getSegmentBounds(start, end), EPS12)
8293
+ );
8294
+ for (const chip of nearbyChips) {
8295
+ if (segmentCrossesBoundsInterior(start, end, chip.bounds)) return true;
8296
+ }
8297
+ }
8298
+ return false;
8299
+ }
8300
+ doesTraceCrossBoundsInterior(bounds) {
8301
+ for (const segment of this.getTraceSegmentsInBounds(bounds)) {
8302
+ if (segmentCrossesBoundsInterior(segment.start, segment.end, bounds)) {
8303
+ return true;
8304
+ }
8305
+ }
8306
+ return false;
8307
+ }
8308
+ doesTraceIntersectBounds(params) {
8309
+ for (const segment of this.getTraceSegmentsInBounds(params.bounds)) {
8310
+ if (segment.trace.globalConnNetId === params.excludedGlobalConnNetId) {
8311
+ continue;
8312
+ }
8313
+ if (segmentIntersectsRect2(segment.start, segment.end, params.bounds)) {
8314
+ return true;
8315
+ }
8316
+ }
8317
+ return false;
8318
+ }
8319
+ buildLabelIndex(netLabelPlacements) {
8320
+ if (netLabelPlacements.length === 0) return null;
8321
+ const labelIndex = new Flatbush2(netLabelPlacements.length);
8322
+ for (const label of netLabelPlacements) {
8323
+ labelIndex.add(
8324
+ label.center.x - label.width / 2,
8325
+ label.center.y - label.height / 2,
8326
+ label.center.x + label.width / 2,
8327
+ label.center.y + label.height / 2
8328
+ );
8329
+ }
8330
+ labelIndex.finish();
8331
+ return labelIndex;
8332
+ }
8333
+ getTraceSegments(traces) {
8334
+ const traceSegments = [];
8335
+ for (const trace of traces) {
8336
+ for (let pointIndex = 0; pointIndex < trace.tracePath.length - 1; pointIndex++) {
8337
+ traceSegments.push({
8338
+ trace,
8339
+ start: trace.tracePath[pointIndex],
8340
+ end: trace.tracePath[pointIndex + 1]
8341
+ });
8342
+ }
8343
+ }
8344
+ return traceSegments;
8345
+ }
8346
+ buildTraceSegmentIndex(traceSegments) {
8347
+ if (traceSegments.length === 0) return null;
8348
+ const traceSegmentIndex = new Flatbush2(traceSegments.length);
8349
+ for (const segment of traceSegments) {
8350
+ traceSegmentIndex.add(
8351
+ Math.min(segment.start.x, segment.end.x),
8352
+ Math.min(segment.start.y, segment.end.y),
8353
+ Math.max(segment.start.x, segment.end.x),
8354
+ Math.max(segment.start.y, segment.end.y)
8355
+ );
8356
+ }
8357
+ traceSegmentIndex.finish();
8358
+ return traceSegmentIndex;
8359
+ }
8360
+ };
8361
+ function getSegmentBounds(start, end) {
8362
+ return {
8363
+ minX: Math.min(start.x, end.x),
8364
+ minY: Math.min(start.y, end.y),
8365
+ maxX: Math.max(start.x, end.x),
8366
+ maxY: Math.max(start.y, end.y)
8367
+ };
8368
+ }
8369
+ function getPaddedBounds(bounds, padding) {
8370
+ return {
8371
+ minX: bounds.minX - padding,
8372
+ minY: bounds.minY - padding,
8373
+ maxX: bounds.maxX + padding,
8374
+ maxY: bounds.maxY + padding
8375
+ };
8376
+ }
8377
+
8187
8378
  // lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts
8188
8379
  var LABEL_TRACE_CLEARANCE2 = 0.1;
8189
8380
  var AvailableNetOrientationSolver = class extends BaseSolver {
@@ -8202,6 +8393,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8202
8393
  chipObstacleSpatialIndex;
8203
8394
  maxSearchDistance;
8204
8395
  pinMap;
8396
+ obstacleIndex;
8205
8397
  constructor(params) {
8206
8398
  super();
8207
8399
  this.inputProblem = params.inputProblem;
@@ -8214,6 +8406,11 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8214
8406
  this.pinMap = getPinMap(params.inputProblem);
8215
8407
  this.chipObstacleSpatialIndex = params.inputProblem._chipObstacleSpatialIndex ?? new ChipObstacleSpatialIndex(params.inputProblem.chips);
8216
8408
  this.maxSearchDistance = getMaxSearchDistance(params.inputProblem);
8409
+ this.obstacleIndex = new AvailableNetOrientationObstacleIndex({
8410
+ chipObstacleSpatialIndex: this.chipObstacleSpatialIndex,
8411
+ netLabelPlacements: this.outputNetLabelPlacements,
8412
+ traces: this.traces
8413
+ });
8217
8414
  this.crowdedPortOnlyLabelIndices = this.getCrowdedPortOnlyLabelIndices();
8218
8415
  this.queuedLabelIndices = this.getProcessableLabelIndices();
8219
8416
  this.setCurrentLabel(this.queuedLabelIndices[0] ?? null);
@@ -8395,6 +8592,10 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8395
8592
  ...toNetLabelPlacementPatch(candidate)
8396
8593
  };
8397
8594
  this.addConnectorTrace(label, candidate, labelIndex);
8595
+ this.obstacleIndex.rebuild({
8596
+ netLabelPlacements: this.outputNetLabelPlacements,
8597
+ traces: this.traces
8598
+ });
8398
8599
  }
8399
8600
  addConnectorTrace(label, candidate, labelIndex) {
8400
8601
  if (candidate.phase === "trace-anchor") return;
@@ -8557,7 +8758,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8557
8758
  anchorY - label.anchorPoint.y,
8558
8759
  anchorX - label.anchorPoint.x
8559
8760
  );
8560
- this.currentCandidateResults.push(result);
8761
+ this.recordCandidateResult(result);
8561
8762
  if (result.status !== "valid") continue;
8562
8763
  result.selected = true;
8563
8764
  return result;
@@ -8592,7 +8793,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8592
8793
  labelIndex,
8593
8794
  "rotate"
8594
8795
  );
8595
- this.currentCandidateResults.push(result);
8796
+ this.recordCandidateResult(result);
8596
8797
  if (result.status === "valid") {
8597
8798
  result.selected = true;
8598
8799
  return result;
@@ -8617,7 +8818,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8617
8818
  labelIndex,
8618
8819
  "trace-anchor"
8619
8820
  );
8620
- this.currentCandidateResults.push(result);
8821
+ this.recordCandidateResult(result);
8621
8822
  if (result.status === "valid") {
8622
8823
  result.selected = true;
8623
8824
  return result;
@@ -8654,7 +8855,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8654
8855
  labelIndex,
8655
8856
  "outward-trace-anchor"
8656
8857
  );
8657
- this.currentCandidateResults.push(preservedColumnResult);
8858
+ this.recordCandidateResult(preservedColumnResult);
8658
8859
  if (preservedColumnResult.status === "valid") {
8659
8860
  preservedColumnResult.selected = true;
8660
8861
  return preservedColumnResult;
@@ -8682,7 +8883,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8682
8883
  "outward-trace-anchor",
8683
8884
  distance5
8684
8885
  );
8685
- this.currentCandidateResults.push(result);
8886
+ this.recordCandidateResult(result);
8686
8887
  if (result.status === "valid") {
8687
8888
  result.selected = true;
8688
8889
  return result;
@@ -8807,7 +9008,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8807
9008
  distance5,
8808
9009
  outwardDistance
8809
9010
  );
8810
- this.currentCandidateResults.push(result);
9011
+ this.recordCandidateResult(result);
8811
9012
  if (result.status === "valid") {
8812
9013
  result.selected = true;
8813
9014
  return result;
@@ -8831,6 +9032,18 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8831
9032
  })
8832
9033
  };
8833
9034
  }
9035
+ recordCandidateResult(candidate) {
9036
+ this.stats.candidateEvaluations = (this.stats.candidateEvaluations ?? 0) + 1;
9037
+ if (this.currentCandidateResults.length < MAX_RECORDED_CANDIDATES) {
9038
+ this.currentCandidateResults.push(candidate);
9039
+ } else if (candidate.status === "valid") {
9040
+ this.currentCandidateResults[MAX_RECORDED_CANDIDATES - 1] = candidate;
9041
+ }
9042
+ this.stats.maxRecordedCandidates = Math.max(
9043
+ this.stats.maxRecordedCandidates ?? 0,
9044
+ this.currentCandidateResults.length
9045
+ );
9046
+ }
8834
9047
  getCandidateConnectorTrace(label, candidate, labelIndex) {
8835
9048
  if (this.crowdedPortOnlyLabelIndices.has(labelIndex) && this.getChipSideForPoint(label.anchorPoint) === "top" && isYOrientation(candidate.orientation)) {
8836
9049
  return simplifyOrthogonalPath([
@@ -9070,12 +9283,12 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
9070
9283
  },
9071
9284
  labelIndex
9072
9285
  );
9073
- for (const chip of this.chipObstacleSpatialIndex.chips) {
9074
- if (tracePathCrossesAnyBounds(connectorTrace, chip.bounds)) {
9075
- return "chip-collision";
9076
- }
9286
+ if (this.obstacleIndex.doesTracePathCrossChip(connectorTrace)) {
9287
+ return "chip-collision";
9077
9288
  }
9078
- for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
9289
+ for (const i of this.obstacleIndex.getLabelIndicesNearTracePath(
9290
+ connectorTrace
9291
+ )) {
9079
9292
  if (i === labelIndex) continue;
9080
9293
  if (this.shouldIgnorePendingCrowdedLabel(labelIndex, i)) continue;
9081
9294
  const otherLabel = this.outputNetLabelPlacements[i];
@@ -9117,7 +9330,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
9117
9330
  if (rectIntersectsAnyTextBox(bounds, this.inputProblem)) {
9118
9331
  return "text-collision";
9119
9332
  }
9120
- if (traceCrossesBoundsInterior(bounds, this.traceMap)) {
9333
+ if (this.obstacleIndex.doesTraceCrossBoundsInterior(bounds)) {
9121
9334
  return "trace-collision";
9122
9335
  }
9123
9336
  if (this.isTraceTooCloseToLabel(bounds, label)) {
@@ -9131,13 +9344,10 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
9131
9344
  isTraceTooCloseToLabel(bounds, label) {
9132
9345
  if (!this.shouldCheckTraceClearanceForLabel(label)) return false;
9133
9346
  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;
9347
+ return this.obstacleIndex.doesTraceIntersectBounds({
9348
+ bounds: clearanceBounds,
9349
+ excludedGlobalConnNetId: label.globalConnNetId
9350
+ });
9141
9351
  }
9142
9352
  getLabelTraceClearanceBounds(bounds) {
9143
9353
  return {
@@ -9220,7 +9430,14 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
9220
9430
  return this.crowdedPortOnlyLabelIndices.has(labelIndex) && this.crowdedPortOnlyLabelIndices.has(otherLabelIndex) && this.queuedLabelIndices.includes(otherLabelIndex);
9221
9431
  }
9222
9432
  intersectsAnyOtherNetLabel(bounds, labelIndex) {
9223
- for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
9433
+ const searchBounds = {
9434
+ minX: bounds.minX - LABEL_SEARCH_STEP,
9435
+ minY: bounds.minY - LABEL_SEARCH_STEP,
9436
+ maxX: bounds.maxX + LABEL_SEARCH_STEP,
9437
+ maxY: bounds.maxY + LABEL_SEARCH_STEP
9438
+ };
9439
+ const nearbyLabelIndices = this.obstacleIndex.getLabelIndicesInBounds(searchBounds).sort((a, b) => a - b);
9440
+ for (const i of nearbyLabelIndices) {
9224
9441
  if (i === labelIndex) continue;
9225
9442
  if (this.shouldIgnorePendingCrowdedLabel(labelIndex, i)) continue;
9226
9443
  const label = this.outputNetLabelPlacements[i];
@@ -9234,7 +9451,14 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
9234
9451
  return false;
9235
9452
  }
9236
9453
  sharesChipBoundary(bounds) {
9237
- for (const chip of this.chipObstacleSpatialIndex.chips) {
9454
+ const boundarySearchBounds = {
9455
+ minX: bounds.minX - WICK_CLEARANCE - EPS12,
9456
+ minY: bounds.minY - WICK_CLEARANCE - EPS12,
9457
+ maxX: bounds.maxX + WICK_CLEARANCE + EPS12,
9458
+ maxY: bounds.maxY + WICK_CLEARANCE + EPS12
9459
+ };
9460
+ const nearbyChips = this.chipObstacleSpatialIndex.getChipsInBounds(boundarySearchBounds);
9461
+ for (const chip of nearbyChips) {
9238
9462
  const chipBounds = chip.bounds;
9239
9463
  const adjacentToVerticalSide = Math.abs(bounds.minX - chipBounds.maxX) <= WICK_CLEARANCE + EPS12 || Math.abs(bounds.maxX - chipBounds.minX) <= WICK_CLEARANCE + EPS12;
9240
9464
  const adjacentToHorizontalSide = Math.abs(bounds.minY - chipBounds.maxY) <= WICK_CLEARANCE + EPS12 || Math.abs(bounds.maxY - chipBounds.minY) <= WICK_CLEARANCE + EPS12;
@@ -12963,11 +13187,16 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
12963
13187
  (instance) => {
12964
13188
  const previousOutput = instance.preAlignmentNetLabelTraceCollisionSolver.getOutput();
12965
13189
  const alignmentOutput = instance.traceCleanupSolver2.getOutput();
13190
+ const connectorMovement = moveNetLabelConnectorsToReroutedTraces({
13191
+ originalTraces: previousOutput.traces,
13192
+ reroutedTraces: alignmentOutput.traces,
13193
+ netLabelPlacements: previousOutput.netLabelPlacements
13194
+ });
12966
13195
  const previousTraceMap = new Map(
12967
13196
  previousOutput.traces.map((trace) => [trace.mspPairId, trace])
12968
13197
  );
12969
- let netLabelPlacements = previousOutput.netLabelPlacements;
12970
- for (const trace of alignmentOutput.traces) {
13198
+ let netLabelPlacements = connectorMovement.netLabelPlacements;
13199
+ for (const trace of connectorMovement.traces) {
12971
13200
  const previousTrace = previousTraceMap.get(trace.mspPairId);
12972
13201
  if (!previousTrace || previousTrace.tracePath === trace.tracePath) {
12973
13202
  continue;
@@ -12987,7 +13216,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
12987
13216
  return [
12988
13217
  {
12989
13218
  inputProblem: instance.inputProblem,
12990
- traces: alignmentOutput.traces,
13219
+ traces: connectorMovement.traces,
12991
13220
  netLabelPlacements
12992
13221
  }
12993
13222
  ];
@@ -0,0 +1,182 @@
1
+ import type { Bounds, Point } from "@tscircuit/math-utils"
2
+ import Flatbush from "flatbush"
3
+ import type { ChipObstacleSpatialIndex } from "lib/data-structures/ChipObstacleSpatialIndex"
4
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
5
+ import { segmentIntersectsRect } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions"
6
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
7
+ import { EPS } from "./constants"
8
+ import { segmentCrossesBoundsInterior } from "./geometry"
9
+
10
+ export type IndexedTraceSegment = {
11
+ trace: SolvedTracePath
12
+ start: Point
13
+ end: Point
14
+ }
15
+
16
+ export class AvailableNetOrientationObstacleIndex {
17
+ private chipObstacleSpatialIndex: ChipObstacleSpatialIndex
18
+ private labelIndex: Flatbush | null = null
19
+ private traceSegmentIndex: Flatbush | null = null
20
+ private traceSegments: IndexedTraceSegment[] = []
21
+
22
+ constructor(params: {
23
+ chipObstacleSpatialIndex: ChipObstacleSpatialIndex
24
+ netLabelPlacements: NetLabelPlacement[]
25
+ traces: SolvedTracePath[]
26
+ }) {
27
+ this.chipObstacleSpatialIndex = params.chipObstacleSpatialIndex
28
+ this.rebuild(params)
29
+ }
30
+
31
+ rebuild(params: {
32
+ netLabelPlacements: NetLabelPlacement[]
33
+ traces: SolvedTracePath[]
34
+ }) {
35
+ this.labelIndex = this.buildLabelIndex(params.netLabelPlacements)
36
+ this.traceSegments = this.getTraceSegments(params.traces)
37
+ this.traceSegmentIndex = this.buildTraceSegmentIndex(this.traceSegments)
38
+ }
39
+
40
+ getLabelIndicesInBounds(bounds: Bounds) {
41
+ if (!this.labelIndex) return []
42
+ return this.labelIndex.search(
43
+ bounds.minX,
44
+ bounds.minY,
45
+ bounds.maxX,
46
+ bounds.maxY,
47
+ )
48
+ }
49
+
50
+ getTraceSegmentsInBounds(bounds: Bounds) {
51
+ if (!this.traceSegmentIndex) return []
52
+ const searchBounds = getPaddedBounds(bounds, EPS)
53
+ return this.traceSegmentIndex
54
+ .search(
55
+ searchBounds.minX,
56
+ searchBounds.minY,
57
+ searchBounds.maxX,
58
+ searchBounds.maxY,
59
+ )
60
+ .map((segmentIndex) => this.traceSegments[segmentIndex]!)
61
+ }
62
+
63
+ getLabelIndicesNearTracePath(tracePath: Point[]) {
64
+ const labelIndices = new Set<number>()
65
+ for (let pointIndex = 0; pointIndex < tracePath.length - 1; pointIndex++) {
66
+ const segmentBounds = getPaddedBounds(
67
+ getSegmentBounds(tracePath[pointIndex]!, tracePath[pointIndex + 1]!),
68
+ EPS,
69
+ )
70
+ for (const labelIndex of this.getLabelIndicesInBounds(segmentBounds)) {
71
+ labelIndices.add(labelIndex)
72
+ }
73
+ }
74
+ return [...labelIndices].sort((a, b) => a - b)
75
+ }
76
+
77
+ doesTracePathCrossChip(tracePath: Point[]) {
78
+ for (let pointIndex = 0; pointIndex < tracePath.length - 1; pointIndex++) {
79
+ const start = tracePath[pointIndex]!
80
+ const end = tracePath[pointIndex + 1]!
81
+ const nearbyChips = this.chipObstacleSpatialIndex.getChipsInBounds(
82
+ getPaddedBounds(getSegmentBounds(start, end), EPS),
83
+ )
84
+ for (const chip of nearbyChips) {
85
+ if (segmentCrossesBoundsInterior(start, end, chip.bounds)) return true
86
+ }
87
+ }
88
+ return false
89
+ }
90
+
91
+ doesTraceCrossBoundsInterior(bounds: Bounds) {
92
+ for (const segment of this.getTraceSegmentsInBounds(bounds)) {
93
+ if (segmentCrossesBoundsInterior(segment.start, segment.end, bounds)) {
94
+ return true
95
+ }
96
+ }
97
+ return false
98
+ }
99
+
100
+ doesTraceIntersectBounds(params: {
101
+ bounds: Bounds
102
+ excludedGlobalConnNetId: string
103
+ }) {
104
+ for (const segment of this.getTraceSegmentsInBounds(params.bounds)) {
105
+ if (segment.trace.globalConnNetId === params.excludedGlobalConnNetId) {
106
+ continue
107
+ }
108
+ if (segmentIntersectsRect(segment.start, segment.end, params.bounds)) {
109
+ return true
110
+ }
111
+ }
112
+ return false
113
+ }
114
+
115
+ private buildLabelIndex(netLabelPlacements: NetLabelPlacement[]) {
116
+ if (netLabelPlacements.length === 0) return null
117
+
118
+ const labelIndex = new Flatbush(netLabelPlacements.length)
119
+ for (const label of netLabelPlacements) {
120
+ labelIndex.add(
121
+ label.center.x - label.width / 2,
122
+ label.center.y - label.height / 2,
123
+ label.center.x + label.width / 2,
124
+ label.center.y + label.height / 2,
125
+ )
126
+ }
127
+ labelIndex.finish()
128
+ return labelIndex
129
+ }
130
+
131
+ private getTraceSegments(traces: SolvedTracePath[]) {
132
+ const traceSegments: IndexedTraceSegment[] = []
133
+ for (const trace of traces) {
134
+ for (
135
+ let pointIndex = 0;
136
+ pointIndex < trace.tracePath.length - 1;
137
+ pointIndex++
138
+ ) {
139
+ traceSegments.push({
140
+ trace,
141
+ start: trace.tracePath[pointIndex]!,
142
+ end: trace.tracePath[pointIndex + 1]!,
143
+ })
144
+ }
145
+ }
146
+ return traceSegments
147
+ }
148
+
149
+ private buildTraceSegmentIndex(traceSegments: IndexedTraceSegment[]) {
150
+ if (traceSegments.length === 0) return null
151
+
152
+ const traceSegmentIndex = new Flatbush(traceSegments.length)
153
+ for (const segment of traceSegments) {
154
+ traceSegmentIndex.add(
155
+ Math.min(segment.start.x, segment.end.x),
156
+ Math.min(segment.start.y, segment.end.y),
157
+ Math.max(segment.start.x, segment.end.x),
158
+ Math.max(segment.start.y, segment.end.y),
159
+ )
160
+ }
161
+ traceSegmentIndex.finish()
162
+ return traceSegmentIndex
163
+ }
164
+ }
165
+
166
+ function getSegmentBounds(start: Point, end: Point): Bounds {
167
+ return {
168
+ minX: Math.min(start.x, end.x),
169
+ minY: Math.min(start.y, end.y),
170
+ maxX: Math.max(start.x, end.x),
171
+ maxY: Math.max(start.y, end.y),
172
+ }
173
+ }
174
+
175
+ function getPaddedBounds(bounds: Bounds, padding: number): Bounds {
176
+ return {
177
+ minX: bounds.minX - padding,
178
+ minY: bounds.minY - padding,
179
+ maxX: bounds.maxX + padding,
180
+ maxY: bounds.maxY + padding,
181
+ }
182
+ }