@tscircuit/schematic-trace-solver 0.0.163 → 0.0.165
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 +5 -1
- package/dist/index.js +160 -23
- package/lib/solvers/NetLabelToTraceSolver/NetLabelToTraceSolver.ts +225 -24
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +15 -4
- package/package.json +1 -1
- package/tests/assets/example51.json +6 -0
- package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +166 -166
- package/tests/examples/__snapshots__/example51.snap.svg +42 -78
- package/tests/examples/example51.test.ts +24 -0
- package/tests/fixtures/convertSolverOutputToCircuitJson.ts +22 -3
- package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +26 -26
- package/tests/repros/__snapshots__/repro-bluetooth-controller-ground-decoupling-groups.snap.svg +1 -15
- package/tests/repros/__snapshots__/repro-core-ground-inline-label-fallback.snap.svg +2 -16
- package/tests/repros/__snapshots__/repro-pga300-redundant-ground-traces.snap.svg +1 -15
- package/tests/repros/__snapshots__/repro-pmp11282-endpoint-trace-through-component-text.snap.svg +76 -0
- package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +114 -110
- package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +1681 -1690
- package/tests/repros/repro-pmp11282-endpoint-trace-through-component-text.test.ts +82 -0
- package/tests/repros/repro-pmp11282-isolated-dcdc.test.ts +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -1394,11 +1394,15 @@ declare class NetLabelToTraceSolver extends BaseSolver {
|
|
|
1394
1394
|
activeSubSolver: SchematicTraceSingleLineSolver2 | null;
|
|
1395
1395
|
constructor(input: InlineNetLabelOutput);
|
|
1396
1396
|
getConstructorParams(): [InlineNetLabelOutput];
|
|
1397
|
-
private
|
|
1397
|
+
private isPortOnlyFallbackLabel;
|
|
1398
|
+
private isDirectConnectionLabel;
|
|
1399
|
+
private getMultiPinNetConnection;
|
|
1398
1400
|
private buildCandidatePairs;
|
|
1401
|
+
private buildRoutedComponentCandidates;
|
|
1399
1402
|
private isSupersededConnectorTrace;
|
|
1400
1403
|
private routeIntersectsRemainingLabels;
|
|
1401
1404
|
private tryAcceptCurrentRoute;
|
|
1405
|
+
private areCandidatePinsAlreadyConnected;
|
|
1402
1406
|
_step(): void;
|
|
1403
1407
|
getOutput(): {
|
|
1404
1408
|
traces: SolvedTracePath[];
|
package/dist/index.js
CHANGED
|
@@ -1507,13 +1507,17 @@ var SchematicTraceSingleLineSolver2 = class extends BaseSolver {
|
|
|
1507
1507
|
excludeRectsForSegment: (segIndex2) => {
|
|
1508
1508
|
const lastSegIndex2 = path.length - 2;
|
|
1509
1509
|
const excludedRects = /* @__PURE__ */ new Set();
|
|
1510
|
-
|
|
1510
|
+
const segmentStart = path[segIndex2];
|
|
1511
|
+
const segmentEnd = path[segIndex2 + 1];
|
|
1512
|
+
const oppositeTerminalPin = segIndex2 === 0 ? PB : segIndex2 === lastSegIndex2 ? PA : null;
|
|
1513
|
+
if (oppositeTerminalPin) {
|
|
1511
1514
|
for (const textObstacle of this.textObstacles) {
|
|
1512
|
-
|
|
1515
|
+
const textBounds = getTextBoxBounds(textObstacle.textBox);
|
|
1516
|
+
if (textObstacle.textBox.chipId !== oppositeTerminalPin.chipId || !segmentIntersectsRect(segmentStart, segmentEnd, textBounds)) {
|
|
1517
|
+
excludedRects.add(textObstacle);
|
|
1518
|
+
}
|
|
1513
1519
|
}
|
|
1514
1520
|
}
|
|
1515
|
-
const segmentStart = path[segIndex2];
|
|
1516
|
-
const segmentEnd = path[segIndex2 + 1];
|
|
1517
1521
|
const endpointEpsilon = 1e-9;
|
|
1518
1522
|
const segmentTouchesPin = (pin) => [segmentStart, segmentEnd].some(
|
|
1519
1523
|
(point) => Math.abs(point.x - pin.x) <= endpointEpsilon && Math.abs(point.y - pin.y) <= endpointEpsilon
|
|
@@ -15047,6 +15051,8 @@ var getTraceRecoveryConnectivityMaps = (inputProblem) => {
|
|
|
15047
15051
|
// lib/solvers/NetLabelToTraceSolver/NetLabelToTraceSolver.ts
|
|
15048
15052
|
var AVAILABLE_NET_ORIENTATION_PREFIX = "available-net-orientation-";
|
|
15049
15053
|
var RECOVERED_TRACE_PREFIX = "net-label-to-trace-";
|
|
15054
|
+
var MAX_NAMED_NET_RECOVERY_PERPENDICULAR_OFFSET = 0.05;
|
|
15055
|
+
var MAX_ROUTED_COMPONENT_RECOVERY_PERPENDICULAR_OFFSET = 0.25;
|
|
15050
15056
|
var getCanonicalPairKey = (firstPinId, secondPinId) => [firstPinId, secondPinId].sort().join("--");
|
|
15051
15057
|
var pathIntersectsRenderedLabel = (path, label) => {
|
|
15052
15058
|
let width = label.width;
|
|
@@ -15067,12 +15073,21 @@ var pathIntersectsRenderedLabel = (path, label) => {
|
|
|
15067
15073
|
}
|
|
15068
15074
|
return false;
|
|
15069
15075
|
};
|
|
15070
|
-
var getPerpendicularOffset = (
|
|
15071
|
-
const xDistance = Math.abs(
|
|
15072
|
-
const yDistance = Math.abs(
|
|
15076
|
+
var getPerpendicularOffset = (firstPoint, secondPoint) => {
|
|
15077
|
+
const xDistance = Math.abs(firstPoint.x - secondPoint.x);
|
|
15078
|
+
const yDistance = Math.abs(firstPoint.y - secondPoint.y);
|
|
15073
15079
|
if (xDistance >= yDistance) return yDistance;
|
|
15074
15080
|
return xDistance;
|
|
15075
15081
|
};
|
|
15082
|
+
var arePinsCoFacingAlongSeparationAxis = (firstPin, secondPin) => {
|
|
15083
|
+
if (firstPin._facingDirection !== secondPin._facingDirection) return false;
|
|
15084
|
+
const xDistance = Math.abs(firstPin.x - secondPin.x);
|
|
15085
|
+
const yDistance = Math.abs(firstPin.y - secondPin.y);
|
|
15086
|
+
if (xDistance >= yDistance) {
|
|
15087
|
+
return firstPin._facingDirection === "x+" || firstPin._facingDirection === "x-";
|
|
15088
|
+
}
|
|
15089
|
+
return firstPin._facingDirection === "y+" || firstPin._facingDirection === "y-";
|
|
15090
|
+
};
|
|
15076
15091
|
var NetLabelToTraceSolver = class extends BaseSolver {
|
|
15077
15092
|
constructor(input) {
|
|
15078
15093
|
super();
|
|
@@ -15100,26 +15115,36 @@ var NetLabelToTraceSolver = class extends BaseSolver {
|
|
|
15100
15115
|
getConstructorParams() {
|
|
15101
15116
|
return [this.input];
|
|
15102
15117
|
}
|
|
15103
|
-
|
|
15104
|
-
|
|
15105
|
-
|
|
15106
|
-
|
|
15118
|
+
isPortOnlyFallbackLabel(label, groundGlobalConnNetIds) {
|
|
15119
|
+
return !(label.pinIds.length !== 1 || label.mspConnectionPairIds.length !== 0 || !label.netId || groundGlobalConnNetIds.has(label.globalConnNetId));
|
|
15120
|
+
}
|
|
15121
|
+
isDirectConnectionLabel(label) {
|
|
15122
|
+
if (!label.netId || label.pinIds.length !== 1) return false;
|
|
15107
15123
|
const pinId = label.pinIds[0];
|
|
15108
15124
|
return this.inputProblem.directConnections.some(
|
|
15109
15125
|
(connection) => connection.netId === label.netId && connection.pinIds.includes(pinId)
|
|
15110
15126
|
);
|
|
15111
15127
|
}
|
|
15128
|
+
getMultiPinNetConnection(label) {
|
|
15129
|
+
if (!label.netId || label.pinIds.length !== 1) return void 0;
|
|
15130
|
+
const pinId = label.pinIds[0];
|
|
15131
|
+
return this.inputProblem.netConnections.find(
|
|
15132
|
+
(connection) => connection.isGround === false && connection.pinIds.length > 2 && connection.netId === label.netId && connection.pinIds.includes(pinId)
|
|
15133
|
+
);
|
|
15134
|
+
}
|
|
15112
15135
|
buildCandidatePairs() {
|
|
15113
15136
|
const { netConnMap } = getConnectivityMapsFromInputProblem(
|
|
15114
15137
|
this.inputProblem
|
|
15115
15138
|
);
|
|
15116
|
-
const
|
|
15139
|
+
const groundGlobalConnNetIds = /* @__PURE__ */ new Set();
|
|
15140
|
+
for (const connection of this.inputProblem.netConnections) {
|
|
15141
|
+
if (!connection.isGround) continue;
|
|
15142
|
+
const globalConnNetId = netConnMap.getNetConnectedToId(connection.netId);
|
|
15143
|
+
if (globalConnNetId) groundGlobalConnNetIds.add(globalConnNetId);
|
|
15144
|
+
}
|
|
15117
15145
|
const labelsByGlobalNet = /* @__PURE__ */ new Map();
|
|
15118
15146
|
for (const label of this.input.netLabelPlacements) {
|
|
15119
|
-
if (!this.
|
|
15120
|
-
label,
|
|
15121
|
-
groundGlobalConnNetId
|
|
15122
|
-
)) {
|
|
15147
|
+
if (!this.isPortOnlyFallbackLabel(label, groundGlobalConnNetIds) || !this.isDirectConnectionLabel(label) && !this.getMultiPinNetConnection(label)) {
|
|
15123
15148
|
continue;
|
|
15124
15149
|
}
|
|
15125
15150
|
const labels = labelsByGlobalNet.get(label.globalConnNetId) ?? [];
|
|
@@ -15135,6 +15160,16 @@ var NetLabelToTraceSolver = class extends BaseSolver {
|
|
|
15135
15160
|
const firstPin = this.pinMap.get(firstLabel.pinIds[0]);
|
|
15136
15161
|
const secondPin = this.pinMap.get(secondLabel.pinIds[0]);
|
|
15137
15162
|
if (!firstPin || !secondPin) continue;
|
|
15163
|
+
const perpendicularOffset = getPerpendicularOffset(
|
|
15164
|
+
firstPin,
|
|
15165
|
+
secondPin
|
|
15166
|
+
);
|
|
15167
|
+
const bothLabelsBelongToDirectConnections = this.isDirectConnectionLabel(firstLabel) && this.isDirectConnectionLabel(secondLabel);
|
|
15168
|
+
const firstMultiPinNetConnection = this.getMultiPinNetConnection(firstLabel);
|
|
15169
|
+
const secondMultiPinNetConnection = this.getMultiPinNetConnection(secondLabel);
|
|
15170
|
+
if (!bothLabelsBelongToDirectConnections && (!firstMultiPinNetConnection || firstMultiPinNetConnection !== secondMultiPinNetConnection || perpendicularOffset > MAX_NAMED_NET_RECOVERY_PERPENDICULAR_OFFSET)) {
|
|
15171
|
+
continue;
|
|
15172
|
+
}
|
|
15138
15173
|
if (arePinsInDifferentSchematicSections(
|
|
15139
15174
|
this.inputProblem,
|
|
15140
15175
|
firstPin,
|
|
@@ -15155,18 +15190,103 @@ var NetLabelToTraceSolver = class extends BaseSolver {
|
|
|
15155
15190
|
firstLabel,
|
|
15156
15191
|
secondLabel,
|
|
15157
15192
|
pins: [firstPin, secondPin],
|
|
15158
|
-
perpendicularOffset
|
|
15193
|
+
perpendicularOffset,
|
|
15159
15194
|
routeDistance: Math.abs(firstPin.x - secondPin.x) + Math.abs(firstPin.y - secondPin.y),
|
|
15160
|
-
key: getCanonicalPairKey(firstPin.pinId, secondPin.pinId)
|
|
15195
|
+
key: getCanonicalPairKey(firstPin.pinId, secondPin.pinId),
|
|
15196
|
+
recoveryMode: "fallback_labels"
|
|
15161
15197
|
});
|
|
15162
15198
|
}
|
|
15163
15199
|
}
|
|
15164
15200
|
}
|
|
15201
|
+
candidates.push(...this.buildRoutedComponentCandidates());
|
|
15165
15202
|
candidates.sort(
|
|
15166
15203
|
(first, second) => first.perpendicularOffset - second.perpendicularOffset || first.routeDistance - second.routeDistance || first.key.localeCompare(second.key)
|
|
15167
15204
|
);
|
|
15168
15205
|
return candidates;
|
|
15169
15206
|
}
|
|
15207
|
+
buildRoutedComponentCandidates() {
|
|
15208
|
+
const { netConnMap } = getConnectivityMapsFromInputProblem(
|
|
15209
|
+
this.inputProblem
|
|
15210
|
+
);
|
|
15211
|
+
const candidates = [];
|
|
15212
|
+
for (const connection of this.inputProblem.netConnections) {
|
|
15213
|
+
if (connection.pinIds.length <= 2 || connection.isGround !== false)
|
|
15214
|
+
continue;
|
|
15215
|
+
const globalConnNetId = netConnMap.getNetConnectedToId(connection.netId);
|
|
15216
|
+
if (!globalConnNetId) continue;
|
|
15217
|
+
const traceConnectedPinComponents = getTraceConnectedPinComponents({
|
|
15218
|
+
pinIds: connection.pinIds,
|
|
15219
|
+
traces: this.outputTraces.filter(
|
|
15220
|
+
(trace) => trace.globalConnNetId === globalConnNetId
|
|
15221
|
+
)
|
|
15222
|
+
}).filter((component) => component.traces.length > 0);
|
|
15223
|
+
for (let firstIndex = 0; firstIndex < traceConnectedPinComponents.length; firstIndex++) {
|
|
15224
|
+
for (let secondIndex = firstIndex + 1; secondIndex < traceConnectedPinComponents.length; secondIndex++) {
|
|
15225
|
+
const firstComponent = traceConnectedPinComponents[firstIndex];
|
|
15226
|
+
const secondComponent = traceConnectedPinComponents[secondIndex];
|
|
15227
|
+
const firstLabel = this.input.netLabelPlacements.find(
|
|
15228
|
+
(label) => label.globalConnNetId === globalConnNetId && label.mspConnectionPairIds.length > 0 && label.pinIds.some(
|
|
15229
|
+
(pinId) => firstComponent.pinIds.includes(pinId)
|
|
15230
|
+
)
|
|
15231
|
+
);
|
|
15232
|
+
const secondLabel = this.input.netLabelPlacements.find(
|
|
15233
|
+
(label) => label.globalConnNetId === globalConnNetId && label.mspConnectionPairIds.length > 0 && label.pinIds.some(
|
|
15234
|
+
(pinId) => secondComponent.pinIds.includes(pinId)
|
|
15235
|
+
)
|
|
15236
|
+
);
|
|
15237
|
+
if (!firstLabel || !secondLabel || firstLabel === secondLabel)
|
|
15238
|
+
continue;
|
|
15239
|
+
if (getPerpendicularOffset(
|
|
15240
|
+
firstLabel.anchorPoint,
|
|
15241
|
+
secondLabel.anchorPoint
|
|
15242
|
+
) > MAX_ROUTED_COMPONENT_RECOVERY_PERPENDICULAR_OFFSET) {
|
|
15243
|
+
continue;
|
|
15244
|
+
}
|
|
15245
|
+
let bestCandidate;
|
|
15246
|
+
for (const firstPinId of firstComponent.pinIds) {
|
|
15247
|
+
for (const secondPinId of secondComponent.pinIds) {
|
|
15248
|
+
const firstPin = this.pinMap.get(firstPinId);
|
|
15249
|
+
const secondPin = this.pinMap.get(secondPinId);
|
|
15250
|
+
if (!firstPin || !secondPin) continue;
|
|
15251
|
+
const perpendicularOffset = getPerpendicularOffset(
|
|
15252
|
+
firstPin,
|
|
15253
|
+
secondPin
|
|
15254
|
+
);
|
|
15255
|
+
if (perpendicularOffset > MAX_ROUTED_COMPONENT_RECOVERY_PERPENDICULAR_OFFSET || arePinsCoFacingAlongSeparationAxis(firstPin, secondPin) || arePinsInDifferentSchematicSections(
|
|
15256
|
+
this.inputProblem,
|
|
15257
|
+
firstPin,
|
|
15258
|
+
secondPin
|
|
15259
|
+
) || doesPairCrossRestrictedCenterLines({
|
|
15260
|
+
inputProblem: this.inputProblem,
|
|
15261
|
+
chipMap: this.chipMap,
|
|
15262
|
+
pinIdMap: this.pinMap,
|
|
15263
|
+
p1: firstPin,
|
|
15264
|
+
p2: secondPin
|
|
15265
|
+
})) {
|
|
15266
|
+
continue;
|
|
15267
|
+
}
|
|
15268
|
+
const routeDistance = Math.abs(firstPin.x - secondPin.x) + Math.abs(firstPin.y - secondPin.y);
|
|
15269
|
+
const candidate = {
|
|
15270
|
+
firstLabel,
|
|
15271
|
+
secondLabel,
|
|
15272
|
+
pins: [firstPin, secondPin],
|
|
15273
|
+
perpendicularOffset,
|
|
15274
|
+
routeDistance,
|
|
15275
|
+
key: getCanonicalPairKey(firstPin.pinId, secondPin.pinId),
|
|
15276
|
+
recoveryMode: "routed_components",
|
|
15277
|
+
netConnectionPinIds: connection.pinIds
|
|
15278
|
+
};
|
|
15279
|
+
if (!bestCandidate || candidate.routeDistance < bestCandidate.routeDistance || candidate.routeDistance === bestCandidate.routeDistance && (candidate.perpendicularOffset < bestCandidate.perpendicularOffset || candidate.perpendicularOffset === bestCandidate.perpendicularOffset && candidate.key.localeCompare(bestCandidate.key) < 0)) {
|
|
15280
|
+
bestCandidate = candidate;
|
|
15281
|
+
}
|
|
15282
|
+
}
|
|
15283
|
+
}
|
|
15284
|
+
if (bestCandidate) candidates.push(bestCandidate);
|
|
15285
|
+
}
|
|
15286
|
+
}
|
|
15287
|
+
}
|
|
15288
|
+
return candidates;
|
|
15289
|
+
}
|
|
15170
15290
|
isSupersededConnectorTrace(trace, candidate) {
|
|
15171
15291
|
if (!trace.mspPairId.startsWith(AVAILABLE_NET_ORIENTATION_PREFIX)) {
|
|
15172
15292
|
return false;
|
|
@@ -15192,7 +15312,13 @@ var NetLabelToTraceSolver = class extends BaseSolver {
|
|
|
15192
15312
|
const retainedTraces = this.outputTraces.filter(
|
|
15193
15313
|
(trace) => !this.isSupersededConnectorTrace(trace, candidate)
|
|
15194
15314
|
);
|
|
15195
|
-
|
|
15315
|
+
let collisionTraces = retainedTraces;
|
|
15316
|
+
if (candidate.recoveryMode === "routed_components") {
|
|
15317
|
+
collisionTraces = retainedTraces.filter(
|
|
15318
|
+
(trace) => trace.globalConnNetId !== candidate.firstLabel.globalConnNetId
|
|
15319
|
+
);
|
|
15320
|
+
}
|
|
15321
|
+
if (doesTraceOverlapWithExistingTraces(tracePath, collisionTraces) || this.routeIntersectsRemainingLabels(tracePath, candidate)) {
|
|
15196
15322
|
return;
|
|
15197
15323
|
}
|
|
15198
15324
|
const [firstPin, secondPin] = candidate.pins;
|
|
@@ -15207,11 +15333,22 @@ var NetLabelToTraceSolver = class extends BaseSolver {
|
|
|
15207
15333
|
pinIds: [firstPin.pinId, secondPin.pinId]
|
|
15208
15334
|
};
|
|
15209
15335
|
this.outputTraces = [...retainedTraces, recoveredTrace];
|
|
15210
|
-
|
|
15211
|
-
|
|
15212
|
-
|
|
15336
|
+
if (candidate.recoveryMode === "fallback_labels") {
|
|
15337
|
+
this.outputNetLabelPlacements = this.outputNetLabelPlacements.filter(
|
|
15338
|
+
(label) => label !== candidate.firstLabel && label !== candidate.secondLabel
|
|
15339
|
+
);
|
|
15340
|
+
}
|
|
15213
15341
|
this.stats.recoveredTraceCount++;
|
|
15214
15342
|
}
|
|
15343
|
+
areCandidatePinsAlreadyConnected(candidate) {
|
|
15344
|
+
if (!candidate.netConnectionPinIds) return false;
|
|
15345
|
+
return getTraceConnectedPinComponents({
|
|
15346
|
+
pinIds: candidate.netConnectionPinIds,
|
|
15347
|
+
traces: this.outputTraces
|
|
15348
|
+
}).some(
|
|
15349
|
+
(component) => component.pinIds.includes(candidate.pins[0].pinId) && component.pinIds.includes(candidate.pins[1].pinId)
|
|
15350
|
+
);
|
|
15351
|
+
}
|
|
15215
15352
|
_step() {
|
|
15216
15353
|
if (this.activeSubSolver) {
|
|
15217
15354
|
this.activeSubSolver.step();
|
|
@@ -15226,7 +15363,7 @@ var NetLabelToTraceSolver = class extends BaseSolver {
|
|
|
15226
15363
|
return;
|
|
15227
15364
|
}
|
|
15228
15365
|
let candidate = this.queuedCandidates.shift();
|
|
15229
|
-
while (candidate && (!this.outputNetLabelPlacements.includes(candidate.firstLabel) || !this.outputNetLabelPlacements.includes(candidate.secondLabel))) {
|
|
15366
|
+
while (candidate && (!this.outputNetLabelPlacements.includes(candidate.firstLabel) || !this.outputNetLabelPlacements.includes(candidate.secondLabel) || this.areCandidatePinsAlreadyConnected(candidate))) {
|
|
15230
15367
|
candidate = this.queuedCandidates.shift();
|
|
15231
15368
|
}
|
|
15232
15369
|
if (!candidate) {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
getTraceRecoveryConnectivityMaps,
|
|
13
13
|
type TraceRecoveryPin,
|
|
14
14
|
} from "lib/solvers/NetLabelTraceRecovery/getTraceRecoveryConnectivityMaps"
|
|
15
|
+
import { getTraceConnectedPinComponents } from "lib/solvers/SchematicTraceLinesSolver/getTraceConnectedPinComponents"
|
|
15
16
|
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
16
17
|
import { SchematicTraceSingleLineSolver2 } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2"
|
|
17
18
|
import type {
|
|
@@ -37,10 +38,14 @@ interface CandidatePair {
|
|
|
37
38
|
perpendicularOffset: number
|
|
38
39
|
routeDistance: number
|
|
39
40
|
key: string
|
|
41
|
+
recoveryMode: "fallback_labels" | "routed_components"
|
|
42
|
+
netConnectionPinIds?: PinId[]
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
const AVAILABLE_NET_ORIENTATION_PREFIX = "available-net-orientation-"
|
|
43
46
|
const RECOVERED_TRACE_PREFIX = "net-label-to-trace-"
|
|
47
|
+
const MAX_NAMED_NET_RECOVERY_PERPENDICULAR_OFFSET = 0.05
|
|
48
|
+
const MAX_ROUTED_COMPONENT_RECOVERY_PERPENDICULAR_OFFSET = 0.25
|
|
44
49
|
|
|
45
50
|
const getCanonicalPairKey = (firstPinId: PinId, secondPinId: PinId) =>
|
|
46
51
|
[firstPinId, secondPinId].sort().join("--")
|
|
@@ -70,14 +75,28 @@ export const pathIntersectsRenderedLabel = (
|
|
|
70
75
|
return false
|
|
71
76
|
}
|
|
72
77
|
|
|
73
|
-
const getPerpendicularOffset = (
|
|
78
|
+
const getPerpendicularOffset = (firstPoint: Point, secondPoint: Point) => {
|
|
79
|
+
const xDistance = Math.abs(firstPoint.x - secondPoint.x)
|
|
80
|
+
const yDistance = Math.abs(firstPoint.y - secondPoint.y)
|
|
81
|
+
if (xDistance >= yDistance) return yDistance
|
|
82
|
+
return xDistance
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const arePinsCoFacingAlongSeparationAxis = (
|
|
74
86
|
firstPin: TraceRecoveryPin,
|
|
75
87
|
secondPin: TraceRecoveryPin,
|
|
76
88
|
) => {
|
|
89
|
+
if (firstPin._facingDirection !== secondPin._facingDirection) return false
|
|
77
90
|
const xDistance = Math.abs(firstPin.x - secondPin.x)
|
|
78
91
|
const yDistance = Math.abs(firstPin.y - secondPin.y)
|
|
79
|
-
if (xDistance >= yDistance)
|
|
80
|
-
|
|
92
|
+
if (xDistance >= yDistance) {
|
|
93
|
+
return (
|
|
94
|
+
firstPin._facingDirection === "x+" || firstPin._facingDirection === "x-"
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
return (
|
|
98
|
+
firstPin._facingDirection === "y+" || firstPin._facingDirection === "y-"
|
|
99
|
+
)
|
|
81
100
|
}
|
|
82
101
|
|
|
83
102
|
export class NetLabelToTraceSolver extends BaseSolver {
|
|
@@ -113,20 +132,20 @@ export class NetLabelToTraceSolver extends BaseSolver {
|
|
|
113
132
|
return [this.input]
|
|
114
133
|
}
|
|
115
134
|
|
|
116
|
-
private
|
|
135
|
+
private isPortOnlyFallbackLabel(
|
|
117
136
|
label: NetLabelPlacement,
|
|
118
|
-
|
|
137
|
+
groundGlobalConnNetIds: Set<GlobalConnNetId>,
|
|
119
138
|
) {
|
|
120
|
-
|
|
139
|
+
return !(
|
|
121
140
|
label.pinIds.length !== 1 ||
|
|
122
141
|
label.mspConnectionPairIds.length !== 0 ||
|
|
123
142
|
!label.netId ||
|
|
124
|
-
label.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return false
|
|
128
|
-
}
|
|
143
|
+
groundGlobalConnNetIds.has(label.globalConnNetId)
|
|
144
|
+
)
|
|
145
|
+
}
|
|
129
146
|
|
|
147
|
+
private isDirectConnectionLabel(label: NetLabelPlacement) {
|
|
148
|
+
if (!label.netId || label.pinIds.length !== 1) return false
|
|
130
149
|
const pinId = label.pinIds[0]!
|
|
131
150
|
return this.inputProblem.directConnections.some(
|
|
132
151
|
(connection) =>
|
|
@@ -134,20 +153,35 @@ export class NetLabelToTraceSolver extends BaseSolver {
|
|
|
134
153
|
)
|
|
135
154
|
}
|
|
136
155
|
|
|
156
|
+
private getMultiPinNetConnection(label: NetLabelPlacement) {
|
|
157
|
+
if (!label.netId || label.pinIds.length !== 1) return undefined
|
|
158
|
+
const pinId = label.pinIds[0]!
|
|
159
|
+
return this.inputProblem.netConnections.find(
|
|
160
|
+
(connection) =>
|
|
161
|
+
connection.isGround === false &&
|
|
162
|
+
connection.pinIds.length > 2 &&
|
|
163
|
+
connection.netId === label.netId &&
|
|
164
|
+
connection.pinIds.includes(pinId),
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
137
168
|
private buildCandidatePairs() {
|
|
138
169
|
const { netConnMap } = getConnectivityMapsFromInputProblem(
|
|
139
170
|
this.inputProblem,
|
|
140
171
|
)
|
|
141
|
-
const
|
|
142
|
-
|
|
172
|
+
const groundGlobalConnNetIds = new Set<GlobalConnNetId>()
|
|
173
|
+
for (const connection of this.inputProblem.netConnections) {
|
|
174
|
+
if (!connection.isGround) continue
|
|
175
|
+
const globalConnNetId = netConnMap.getNetConnectedToId(connection.netId)
|
|
176
|
+
if (globalConnNetId) groundGlobalConnNetIds.add(globalConnNetId)
|
|
177
|
+
}
|
|
143
178
|
const labelsByGlobalNet = new Map<GlobalConnNetId, NetLabelPlacement[]>()
|
|
144
179
|
|
|
145
180
|
for (const label of this.input.netLabelPlacements) {
|
|
146
181
|
if (
|
|
147
|
-
!this.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
)
|
|
182
|
+
!this.isPortOnlyFallbackLabel(label, groundGlobalConnNetIds) ||
|
|
183
|
+
(!this.isDirectConnectionLabel(label) &&
|
|
184
|
+
!this.getMultiPinNetConnection(label))
|
|
151
185
|
) {
|
|
152
186
|
continue
|
|
153
187
|
}
|
|
@@ -169,6 +203,25 @@ export class NetLabelToTraceSolver extends BaseSolver {
|
|
|
169
203
|
const firstPin = this.pinMap.get(firstLabel.pinIds[0]!)
|
|
170
204
|
const secondPin = this.pinMap.get(secondLabel.pinIds[0]!)
|
|
171
205
|
if (!firstPin || !secondPin) continue
|
|
206
|
+
const perpendicularOffset = getPerpendicularOffset(
|
|
207
|
+
firstPin,
|
|
208
|
+
secondPin,
|
|
209
|
+
)
|
|
210
|
+
const bothLabelsBelongToDirectConnections =
|
|
211
|
+
this.isDirectConnectionLabel(firstLabel) &&
|
|
212
|
+
this.isDirectConnectionLabel(secondLabel)
|
|
213
|
+
const firstMultiPinNetConnection =
|
|
214
|
+
this.getMultiPinNetConnection(firstLabel)
|
|
215
|
+
const secondMultiPinNetConnection =
|
|
216
|
+
this.getMultiPinNetConnection(secondLabel)
|
|
217
|
+
if (
|
|
218
|
+
!bothLabelsBelongToDirectConnections &&
|
|
219
|
+
(!firstMultiPinNetConnection ||
|
|
220
|
+
firstMultiPinNetConnection !== secondMultiPinNetConnection ||
|
|
221
|
+
perpendicularOffset > MAX_NAMED_NET_RECOVERY_PERPENDICULAR_OFFSET)
|
|
222
|
+
) {
|
|
223
|
+
continue
|
|
224
|
+
}
|
|
172
225
|
if (
|
|
173
226
|
arePinsInDifferentSchematicSections(
|
|
174
227
|
this.inputProblem,
|
|
@@ -194,16 +247,19 @@ export class NetLabelToTraceSolver extends BaseSolver {
|
|
|
194
247
|
firstLabel,
|
|
195
248
|
secondLabel,
|
|
196
249
|
pins: [firstPin, secondPin],
|
|
197
|
-
perpendicularOffset
|
|
250
|
+
perpendicularOffset,
|
|
198
251
|
routeDistance:
|
|
199
252
|
Math.abs(firstPin.x - secondPin.x) +
|
|
200
253
|
Math.abs(firstPin.y - secondPin.y),
|
|
201
254
|
key: getCanonicalPairKey(firstPin.pinId, secondPin.pinId),
|
|
255
|
+
recoveryMode: "fallback_labels",
|
|
202
256
|
})
|
|
203
257
|
}
|
|
204
258
|
}
|
|
205
259
|
}
|
|
206
260
|
|
|
261
|
+
candidates.push(...this.buildRoutedComponentCandidates())
|
|
262
|
+
|
|
207
263
|
candidates.sort(
|
|
208
264
|
(first, second) =>
|
|
209
265
|
first.perpendicularOffset - second.perpendicularOffset ||
|
|
@@ -213,6 +269,129 @@ export class NetLabelToTraceSolver extends BaseSolver {
|
|
|
213
269
|
return candidates
|
|
214
270
|
}
|
|
215
271
|
|
|
272
|
+
private buildRoutedComponentCandidates() {
|
|
273
|
+
const { netConnMap } = getConnectivityMapsFromInputProblem(
|
|
274
|
+
this.inputProblem,
|
|
275
|
+
)
|
|
276
|
+
const candidates: CandidatePair[] = []
|
|
277
|
+
|
|
278
|
+
for (const connection of this.inputProblem.netConnections) {
|
|
279
|
+
if (connection.pinIds.length <= 2 || connection.isGround !== false)
|
|
280
|
+
continue
|
|
281
|
+
const globalConnNetId = netConnMap.getNetConnectedToId(connection.netId)
|
|
282
|
+
if (!globalConnNetId) continue
|
|
283
|
+
|
|
284
|
+
const traceConnectedPinComponents = getTraceConnectedPinComponents({
|
|
285
|
+
pinIds: connection.pinIds,
|
|
286
|
+
traces: this.outputTraces.filter(
|
|
287
|
+
(trace) => trace.globalConnNetId === globalConnNetId,
|
|
288
|
+
),
|
|
289
|
+
}).filter((component) => component.traces.length > 0)
|
|
290
|
+
|
|
291
|
+
for (
|
|
292
|
+
let firstIndex = 0;
|
|
293
|
+
firstIndex < traceConnectedPinComponents.length;
|
|
294
|
+
firstIndex++
|
|
295
|
+
) {
|
|
296
|
+
for (
|
|
297
|
+
let secondIndex = firstIndex + 1;
|
|
298
|
+
secondIndex < traceConnectedPinComponents.length;
|
|
299
|
+
secondIndex++
|
|
300
|
+
) {
|
|
301
|
+
const firstComponent = traceConnectedPinComponents[firstIndex]!
|
|
302
|
+
const secondComponent = traceConnectedPinComponents[secondIndex]!
|
|
303
|
+
const firstLabel = this.input.netLabelPlacements.find(
|
|
304
|
+
(label) =>
|
|
305
|
+
label.globalConnNetId === globalConnNetId &&
|
|
306
|
+
label.mspConnectionPairIds.length > 0 &&
|
|
307
|
+
label.pinIds.some((pinId) =>
|
|
308
|
+
firstComponent.pinIds.includes(pinId),
|
|
309
|
+
),
|
|
310
|
+
)
|
|
311
|
+
const secondLabel = this.input.netLabelPlacements.find(
|
|
312
|
+
(label) =>
|
|
313
|
+
label.globalConnNetId === globalConnNetId &&
|
|
314
|
+
label.mspConnectionPairIds.length > 0 &&
|
|
315
|
+
label.pinIds.some((pinId) =>
|
|
316
|
+
secondComponent.pinIds.includes(pinId),
|
|
317
|
+
),
|
|
318
|
+
)
|
|
319
|
+
if (!firstLabel || !secondLabel || firstLabel === secondLabel)
|
|
320
|
+
continue
|
|
321
|
+
if (
|
|
322
|
+
getPerpendicularOffset(
|
|
323
|
+
firstLabel.anchorPoint,
|
|
324
|
+
secondLabel.anchorPoint,
|
|
325
|
+
) > MAX_ROUTED_COMPONENT_RECOVERY_PERPENDICULAR_OFFSET
|
|
326
|
+
) {
|
|
327
|
+
continue
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
let bestCandidate: CandidatePair | undefined
|
|
331
|
+
for (const firstPinId of firstComponent.pinIds) {
|
|
332
|
+
for (const secondPinId of secondComponent.pinIds) {
|
|
333
|
+
const firstPin = this.pinMap.get(firstPinId)
|
|
334
|
+
const secondPin = this.pinMap.get(secondPinId)
|
|
335
|
+
if (!firstPin || !secondPin) continue
|
|
336
|
+
const perpendicularOffset = getPerpendicularOffset(
|
|
337
|
+
firstPin,
|
|
338
|
+
secondPin,
|
|
339
|
+
)
|
|
340
|
+
if (
|
|
341
|
+
perpendicularOffset >
|
|
342
|
+
MAX_ROUTED_COMPONENT_RECOVERY_PERPENDICULAR_OFFSET ||
|
|
343
|
+
arePinsCoFacingAlongSeparationAxis(firstPin, secondPin) ||
|
|
344
|
+
arePinsInDifferentSchematicSections(
|
|
345
|
+
this.inputProblem,
|
|
346
|
+
firstPin,
|
|
347
|
+
secondPin,
|
|
348
|
+
) ||
|
|
349
|
+
doesPairCrossRestrictedCenterLines({
|
|
350
|
+
inputProblem: this.inputProblem,
|
|
351
|
+
chipMap: this.chipMap,
|
|
352
|
+
pinIdMap: this.pinMap,
|
|
353
|
+
p1: firstPin,
|
|
354
|
+
p2: secondPin,
|
|
355
|
+
})
|
|
356
|
+
) {
|
|
357
|
+
continue
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const routeDistance =
|
|
361
|
+
Math.abs(firstPin.x - secondPin.x) +
|
|
362
|
+
Math.abs(firstPin.y - secondPin.y)
|
|
363
|
+
const candidate: CandidatePair = {
|
|
364
|
+
firstLabel,
|
|
365
|
+
secondLabel,
|
|
366
|
+
pins: [firstPin, secondPin],
|
|
367
|
+
perpendicularOffset,
|
|
368
|
+
routeDistance,
|
|
369
|
+
key: getCanonicalPairKey(firstPin.pinId, secondPin.pinId),
|
|
370
|
+
recoveryMode: "routed_components",
|
|
371
|
+
netConnectionPinIds: connection.pinIds,
|
|
372
|
+
}
|
|
373
|
+
if (
|
|
374
|
+
!bestCandidate ||
|
|
375
|
+
candidate.routeDistance < bestCandidate.routeDistance ||
|
|
376
|
+
(candidate.routeDistance === bestCandidate.routeDistance &&
|
|
377
|
+
(candidate.perpendicularOffset <
|
|
378
|
+
bestCandidate.perpendicularOffset ||
|
|
379
|
+
(candidate.perpendicularOffset ===
|
|
380
|
+
bestCandidate.perpendicularOffset &&
|
|
381
|
+
candidate.key.localeCompare(bestCandidate.key) < 0)))
|
|
382
|
+
) {
|
|
383
|
+
bestCandidate = candidate
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (bestCandidate) candidates.push(bestCandidate)
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return candidates
|
|
393
|
+
}
|
|
394
|
+
|
|
216
395
|
private isSupersededConnectorTrace(
|
|
217
396
|
trace: SolvedTracePath,
|
|
218
397
|
candidate: CandidatePair,
|
|
@@ -247,8 +426,15 @@ export class NetLabelToTraceSolver extends BaseSolver {
|
|
|
247
426
|
const retainedTraces = this.outputTraces.filter(
|
|
248
427
|
(trace) => !this.isSupersededConnectorTrace(trace, candidate),
|
|
249
428
|
)
|
|
429
|
+
let collisionTraces = retainedTraces
|
|
430
|
+
if (candidate.recoveryMode === "routed_components") {
|
|
431
|
+
collisionTraces = retainedTraces.filter(
|
|
432
|
+
(trace) =>
|
|
433
|
+
trace.globalConnNetId !== candidate.firstLabel.globalConnNetId,
|
|
434
|
+
)
|
|
435
|
+
}
|
|
250
436
|
if (
|
|
251
|
-
doesTraceOverlapWithExistingTraces(tracePath,
|
|
437
|
+
doesTraceOverlapWithExistingTraces(tracePath, collisionTraces) ||
|
|
252
438
|
this.routeIntersectsRemainingLabels(tracePath, candidate)
|
|
253
439
|
) {
|
|
254
440
|
return
|
|
@@ -267,13 +453,27 @@ export class NetLabelToTraceSolver extends BaseSolver {
|
|
|
267
453
|
}
|
|
268
454
|
|
|
269
455
|
this.outputTraces = [...retainedTraces, recoveredTrace]
|
|
270
|
-
|
|
271
|
-
(
|
|
272
|
-
label
|
|
273
|
-
|
|
456
|
+
if (candidate.recoveryMode === "fallback_labels") {
|
|
457
|
+
this.outputNetLabelPlacements = this.outputNetLabelPlacements.filter(
|
|
458
|
+
(label) =>
|
|
459
|
+
label !== candidate.firstLabel && label !== candidate.secondLabel,
|
|
460
|
+
)
|
|
461
|
+
}
|
|
274
462
|
this.stats.recoveredTraceCount++
|
|
275
463
|
}
|
|
276
464
|
|
|
465
|
+
private areCandidatePinsAlreadyConnected(candidate: CandidatePair) {
|
|
466
|
+
if (!candidate.netConnectionPinIds) return false
|
|
467
|
+
return getTraceConnectedPinComponents({
|
|
468
|
+
pinIds: candidate.netConnectionPinIds,
|
|
469
|
+
traces: this.outputTraces,
|
|
470
|
+
}).some(
|
|
471
|
+
(component) =>
|
|
472
|
+
component.pinIds.includes(candidate.pins[0].pinId) &&
|
|
473
|
+
component.pinIds.includes(candidate.pins[1].pinId),
|
|
474
|
+
)
|
|
475
|
+
}
|
|
476
|
+
|
|
277
477
|
override _step() {
|
|
278
478
|
if (this.activeSubSolver) {
|
|
279
479
|
this.activeSubSolver.step()
|
|
@@ -292,7 +492,8 @@ export class NetLabelToTraceSolver extends BaseSolver {
|
|
|
292
492
|
while (
|
|
293
493
|
candidate &&
|
|
294
494
|
(!this.outputNetLabelPlacements.includes(candidate.firstLabel) ||
|
|
295
|
-
!this.outputNetLabelPlacements.includes(candidate.secondLabel)
|
|
495
|
+
!this.outputNetLabelPlacements.includes(candidate.secondLabel) ||
|
|
496
|
+
this.areCandidatePinsAlreadyConnected(candidate))
|
|
296
497
|
) {
|
|
297
498
|
candidate = this.queuedCandidates.shift()
|
|
298
499
|
}
|
|
@@ -325,15 +325,26 @@ export class SchematicTraceSingleLineSolver2 extends BaseSolver {
|
|
|
325
325
|
excludeRectsForSegment: (segIndex) => {
|
|
326
326
|
const lastSegIndex = path.length - 2
|
|
327
327
|
const excludedRects = new Set<ObstacleRect>()
|
|
328
|
+
const segmentStart = path[segIndex]!
|
|
329
|
+
const segmentEnd = path[segIndex + 1]!
|
|
328
330
|
|
|
329
|
-
|
|
331
|
+
const oppositeTerminalPin =
|
|
332
|
+
segIndex === 0 ? PB : segIndex === lastSegIndex ? PA : null
|
|
333
|
+
if (oppositeTerminalPin) {
|
|
330
334
|
for (const textObstacle of this.textObstacles) {
|
|
331
|
-
|
|
335
|
+
// Preserve the existing terminal-segment clearance exception, but
|
|
336
|
+
// do not let a segment that wraps around the opposite endpoint cut
|
|
337
|
+
// through that component's actual text.
|
|
338
|
+
const textBounds = getTextBoxBounds(textObstacle.textBox)
|
|
339
|
+
if (
|
|
340
|
+
textObstacle.textBox.chipId !== oppositeTerminalPin.chipId ||
|
|
341
|
+
!segmentIntersectsRect(segmentStart, segmentEnd, textBounds)
|
|
342
|
+
) {
|
|
343
|
+
excludedRects.add(textObstacle)
|
|
344
|
+
}
|
|
332
345
|
}
|
|
333
346
|
}
|
|
334
347
|
|
|
335
|
-
const segmentStart = path[segIndex]!
|
|
336
|
-
const segmentEnd = path[segIndex + 1]!
|
|
337
348
|
const endpointEpsilon = 1e-9
|
|
338
349
|
const segmentTouchesPin = (pin: MspConnectionPair["pins"][number]) =>
|
|
339
350
|
[segmentStart, segmentEnd].some(
|