@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.
- package/.github/workflows/bun-pver-release.yml +7 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +261 -32
- package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationObstacleIndex.ts +182 -0
- package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +67 -25
- package/lib/solvers/AvailableNetOrientationSolver/constants.ts +2 -0
- package/lib/solvers/Example28Solver/labelMovement.ts +71 -0
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +12 -4
- package/package.json +5 -1
- package/tests/bug-reports/bug-report-20260819T091818Z/__snapshots__/bug-report-20260819T091818Z.snap.svg +237 -0
- package/tests/bug-reports/bug-report-20260819T091818Z/bug-report-20260819T091818Z.json +1052 -0
- package/tests/bug-reports/bug-report-20260819T091818Z/bug-report-20260819T091818Z.test.ts +33 -0
- package/tests/repros/__snapshots__/repro-board-648-esp12f-section.snap.svg +124 -0
- package/tests/repros/assets/repro-board-648-esp12f-section.input.json +537 -0
- package/tests/repros/repro-board-648-esp12f-section.test.ts +13 -0
- package/tests/repros/repro161-power-section-autolayout.test.ts +7 -0
- 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@
|
|
25
|
+
- uses: actions/setup-node@v6
|
|
22
26
|
with:
|
|
23
|
-
node-version:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
9074
|
-
|
|
9075
|
-
return "chip-collision";
|
|
9076
|
-
}
|
|
9286
|
+
if (this.obstacleIndex.doesTracePathCrossChip(connectorTrace)) {
|
|
9287
|
+
return "chip-collision";
|
|
9077
9288
|
}
|
|
9078
|
-
for (
|
|
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 (
|
|
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
|
-
|
|
9135
|
-
|
|
9136
|
-
|
|
9137
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
12970
|
-
for (const trace of
|
|
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:
|
|
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
|
+
}
|