@tscircuit/schematic-trace-solver 0.0.140 → 0.0.142
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 +31 -1
- package/dist/index.js +323 -22
- package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +216 -17
- package/lib/solvers/InlineNetLabelSolver/alignPortOnlyInlineNetLabelStubs.ts +233 -0
- package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapShiftSolver.ts +60 -4
- package/lib/types/InputProblem.ts +13 -0
- package/package.json +1 -1
- package/tests/assets/inline-net-label-port-stub.json +67 -0
- package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +542 -0
- package/tests/repros/__snapshots__/repro-tida010076-j4-ground-bus.snap.svg +52 -0
- package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +624 -0
- package/tests/repros/assets/board-1273-trace-overlap-cycle.input.json +3519 -0
- package/tests/repros/assets/repro-tida010076-page02.input.json +2964 -0
- package/tests/repros/board-1273-trace-overlap-cycle.test.ts +30 -0
- package/tests/repros/repro-tida010076-j4-ground-bus.test.ts +78 -0
- package/tests/repros/repro-tida010076-page02.test.ts +27 -0
- package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label-port-stub.snap.svg +64 -0
- package/tests/solvers/InlineNetLabelSolver/align-port-only-inline-net-label-stubs-conflict.test.ts +75 -0
- package/tests/solvers/InlineNetLabelSolver/inline-net-label-port-stub.test.ts +82 -0
package/dist/index.d.ts
CHANGED
|
@@ -115,6 +115,16 @@ interface InputNetConnection {
|
|
|
115
115
|
pinIds: Array<PinId>;
|
|
116
116
|
netLabelWidth?: number;
|
|
117
117
|
netLabelHeight?: number;
|
|
118
|
+
/**
|
|
119
|
+
* When true, a named single-pin net may be drawn as a short outward trace
|
|
120
|
+
* stub with its net name placed inline. Multi-pin net connections retain the
|
|
121
|
+
* regular anchored-label behavior.
|
|
122
|
+
*/
|
|
123
|
+
allowInlineNetLabel?: boolean;
|
|
124
|
+
/** Extent of the inline text along the generated trace stub. */
|
|
125
|
+
inlineNetLabelWidth?: number;
|
|
126
|
+
/** Height of the inline text perpendicular to the generated trace stub. */
|
|
127
|
+
inlineNetLabelHeight?: number;
|
|
118
128
|
}
|
|
119
129
|
interface InputProblem {
|
|
120
130
|
chips: Array<InputChip>;
|
|
@@ -279,6 +289,7 @@ declare class TraceOverlapIssueSolver extends BaseSolver {
|
|
|
279
289
|
}
|
|
280
290
|
|
|
281
291
|
type ConnNetId = string;
|
|
292
|
+
type TraceState = Record<MspConnectionPairId, SolvedTracePath>;
|
|
282
293
|
/**
|
|
283
294
|
* This solver finds traces that overlap or meet collinearly and aren't
|
|
284
295
|
* connected via the globalConnMap, then shifts them apart in the direction
|
|
@@ -308,6 +319,7 @@ declare class TraceOverlapShiftSolver extends BaseSolver {
|
|
|
308
319
|
*/
|
|
309
320
|
traceNetIslands: Record<ConnNetId, Array<SolvedTracePath>>;
|
|
310
321
|
correctedTraceMap: Record<MspConnectionPairId, SolvedTracePath>;
|
|
322
|
+
recentTraceStates: TraceState[];
|
|
311
323
|
cleanupPhase: "diagonals" | "done" | null;
|
|
312
324
|
constructor(params: {
|
|
313
325
|
inputProblem: InputProblem;
|
|
@@ -324,6 +336,8 @@ declare class TraceOverlapShiftSolver extends BaseSolver {
|
|
|
324
336
|
private findNextDiagonalSegment;
|
|
325
337
|
private findAndFixNextDiagonalSegment;
|
|
326
338
|
_step(): void;
|
|
339
|
+
private rememberTraceState;
|
|
340
|
+
private returnsToPreviousTraceState;
|
|
327
341
|
visualize(): graphics_debug.GraphicsObject;
|
|
328
342
|
}
|
|
329
343
|
|
|
@@ -1182,8 +1196,13 @@ declare const INLINE_NET_LABEL_MAX_SPAN_JOG = 0.4;
|
|
|
1182
1196
|
interface InlineNetLabelPlacement {
|
|
1183
1197
|
globalConnNetId: string;
|
|
1184
1198
|
netId?: string;
|
|
1185
|
-
mspPairId
|
|
1199
|
+
mspPairId?: string;
|
|
1186
1200
|
pinIds: PinId[];
|
|
1201
|
+
/**
|
|
1202
|
+
* A generated single-ended trace stub. Present only for an eligible
|
|
1203
|
+
* single-pin net connection; routed point-to-point traces omit it.
|
|
1204
|
+
*/
|
|
1205
|
+
stubTracePath?: [Point, Point];
|
|
1187
1206
|
/** Axis the text runs along: "x" reads left-to-right, "y" reads bottom-to-top */
|
|
1188
1207
|
axis: "x" | "y";
|
|
1189
1208
|
/** Midpoint of the trace segment the label is attached to */
|
|
@@ -1220,10 +1239,20 @@ declare class InlineNetLabelSolver extends BaseSolver {
|
|
|
1220
1239
|
inlineNetLabelPlacements: InlineNetLabelPlacement[];
|
|
1221
1240
|
/** Direct connections that opted in, still waiting to be processed */
|
|
1222
1241
|
queuedDirectConnections: InputDirectConnection[];
|
|
1242
|
+
/** Single-pin net connections that opted in, still waiting to be processed */
|
|
1243
|
+
queuedPortOnlyNetConnections: InputNetConnection[];
|
|
1223
1244
|
private tracesByPinPairKey;
|
|
1245
|
+
private hasAlignedPortOnlyStubs;
|
|
1224
1246
|
constructor(input: InlineNetLabelSolverInput);
|
|
1225
1247
|
getConstructorParams(): [InlineNetLabelSolverInput];
|
|
1226
1248
|
_step(): void;
|
|
1249
|
+
/**
|
|
1250
|
+
* Converts a conventional port-only anchored placement into an inline label
|
|
1251
|
+
* on a generated outward stub. The stub follows the pin's true facing
|
|
1252
|
+
* direction; an anchored label may finish in another direction after an
|
|
1253
|
+
* elbow, which is not the direction a terminal stub should leave the pin.
|
|
1254
|
+
*/
|
|
1255
|
+
private computePortOnlyInlinePlacement;
|
|
1227
1256
|
private computeInlinePlacement;
|
|
1228
1257
|
/**
|
|
1229
1258
|
* Places the label over the whole route when the route is straight except
|
|
@@ -1242,6 +1271,7 @@ declare class InlineNetLabelSolver extends BaseSolver {
|
|
|
1242
1271
|
* the other, never both.
|
|
1243
1272
|
*/
|
|
1244
1273
|
private getSupersededNetLabelKeys;
|
|
1274
|
+
private getOutputTraces;
|
|
1245
1275
|
getOutput(): {
|
|
1246
1276
|
traces: SolvedTracePath[];
|
|
1247
1277
|
netLabelPlacements: NetLabelPlacement[];
|
package/dist/index.js
CHANGED
|
@@ -2102,6 +2102,7 @@ var TraceOverlapIssueSolver = class extends BaseSolver {
|
|
|
2102
2102
|
};
|
|
2103
2103
|
|
|
2104
2104
|
// lib/solvers/TraceOverlapShiftSolver/TraceOverlapShiftSolver.ts
|
|
2105
|
+
var TRACE_STATE_POSITION_EPSILON = 1e-6;
|
|
2105
2106
|
var TraceOverlapShiftSolver = class extends BaseSolver {
|
|
2106
2107
|
inputProblem;
|
|
2107
2108
|
inputTracePaths;
|
|
@@ -2112,6 +2113,9 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
|
|
|
2112
2113
|
*/
|
|
2113
2114
|
traceNetIslands = {};
|
|
2114
2115
|
correctedTraceMap = {};
|
|
2116
|
+
// Keep only the current and previous layouts to detect a two-state cycle
|
|
2117
|
+
// without accumulating routing history for the whole solve.
|
|
2118
|
+
recentTraceStates = [];
|
|
2115
2119
|
cleanupPhase = null;
|
|
2116
2120
|
constructor(params) {
|
|
2117
2121
|
super();
|
|
@@ -2123,6 +2127,7 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
|
|
|
2123
2127
|
const { mspPairId } = tracePath;
|
|
2124
2128
|
this.correctedTraceMap[mspPairId] = tracePath;
|
|
2125
2129
|
}
|
|
2130
|
+
this.rememberTraceState(this.correctedTraceMap);
|
|
2126
2131
|
this.traceNetIslands = this.computeTraceNetIslands();
|
|
2127
2132
|
}
|
|
2128
2133
|
getConstructorParams() {
|
|
@@ -2337,11 +2342,17 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
|
|
|
2337
2342
|
}
|
|
2338
2343
|
_step() {
|
|
2339
2344
|
if (this.activeSubSolver?.solved) {
|
|
2340
|
-
|
|
2341
|
-
this.
|
|
2342
|
-
|
|
2343
|
-
|
|
2345
|
+
const nextTraceState = {
|
|
2346
|
+
...this.correctedTraceMap,
|
|
2347
|
+
...this.activeSubSolver.correctedTraceMap
|
|
2348
|
+
};
|
|
2349
|
+
if (this.returnsToPreviousTraceState(nextTraceState)) {
|
|
2350
|
+
this.activeSubSolver = null;
|
|
2351
|
+
this.solved = true;
|
|
2352
|
+
return;
|
|
2344
2353
|
}
|
|
2354
|
+
this.correctedTraceMap = nextTraceState;
|
|
2355
|
+
this.rememberTraceState(nextTraceState);
|
|
2345
2356
|
this.activeSubSolver = null;
|
|
2346
2357
|
this.traceNetIslands = this.computeTraceNetIslands();
|
|
2347
2358
|
}
|
|
@@ -2374,6 +2385,34 @@ var TraceOverlapShiftSolver = class extends BaseSolver {
|
|
|
2374
2385
|
traceIdsToShift: this.traceIdsToShift
|
|
2375
2386
|
});
|
|
2376
2387
|
}
|
|
2388
|
+
rememberTraceState(traceState) {
|
|
2389
|
+
this.recentTraceStates.push({ ...traceState });
|
|
2390
|
+
if (this.recentTraceStates.length > 2) {
|
|
2391
|
+
this.recentTraceStates.shift();
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
returnsToPreviousTraceState(candidateTraceState) {
|
|
2395
|
+
if (this.recentTraceStates.length < 2) return false;
|
|
2396
|
+
const previousTraceState = this.recentTraceStates[0];
|
|
2397
|
+
const traceIds = Object.keys(candidateTraceState);
|
|
2398
|
+
if (traceIds.length !== Object.keys(previousTraceState).length) return false;
|
|
2399
|
+
for (const traceId of traceIds) {
|
|
2400
|
+
const candidatePath = candidateTraceState[traceId];
|
|
2401
|
+
const previousPath = previousTraceState[traceId];
|
|
2402
|
+
if (!candidatePath || !previousPath) return false;
|
|
2403
|
+
if (candidatePath.tracePath.length !== previousPath.tracePath.length) {
|
|
2404
|
+
return false;
|
|
2405
|
+
}
|
|
2406
|
+
for (let i = 0; i < candidatePath.tracePath.length; i++) {
|
|
2407
|
+
const candidatePoint = candidatePath.tracePath[i];
|
|
2408
|
+
const previousPoint = previousPath.tracePath[i];
|
|
2409
|
+
if (Math.abs(candidatePoint.x - previousPoint.x) > TRACE_STATE_POSITION_EPSILON || Math.abs(candidatePoint.y - previousPoint.y) > TRACE_STATE_POSITION_EPSILON) {
|
|
2410
|
+
return false;
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
return true;
|
|
2415
|
+
}
|
|
2377
2416
|
visualize() {
|
|
2378
2417
|
if (this.activeSubSolver) {
|
|
2379
2418
|
return this.activeSubSolver.visualize();
|
|
@@ -12537,6 +12576,154 @@ var getAxisAlignedSegments = (path, epsilon = 1e-6) => {
|
|
|
12537
12576
|
return segments.sort((a, b) => b.length - a.length);
|
|
12538
12577
|
};
|
|
12539
12578
|
|
|
12579
|
+
// lib/solvers/InlineNetLabelSolver/alignPortOnlyInlineNetLabelStubs.ts
|
|
12580
|
+
var getStubDirection = (path) => {
|
|
12581
|
+
const [start, end] = path;
|
|
12582
|
+
if (Math.abs(end.x - start.x) >= Math.abs(end.y - start.y)) {
|
|
12583
|
+
return end.x >= start.x ? "x+" : "x-";
|
|
12584
|
+
}
|
|
12585
|
+
return end.y >= start.y ? "y+" : "y-";
|
|
12586
|
+
};
|
|
12587
|
+
var getStubLength = (path) => {
|
|
12588
|
+
const [start, end] = path;
|
|
12589
|
+
return Math.abs(end.x - start.x) + Math.abs(end.y - start.y);
|
|
12590
|
+
};
|
|
12591
|
+
var getLabelBounds2 = (placement) => {
|
|
12592
|
+
const isVertical5 = placement.axis === "y";
|
|
12593
|
+
const width = isVertical5 ? placement.height : placement.width;
|
|
12594
|
+
const height = isVertical5 ? placement.width : placement.height;
|
|
12595
|
+
return {
|
|
12596
|
+
minX: placement.center.x - width / 2,
|
|
12597
|
+
maxX: placement.center.x + width / 2,
|
|
12598
|
+
minY: placement.center.y - height / 2,
|
|
12599
|
+
maxY: placement.center.y + height / 2
|
|
12600
|
+
};
|
|
12601
|
+
};
|
|
12602
|
+
var getPathBounds = (path) => ({
|
|
12603
|
+
minX: Math.min(...path.map((point) => point.x)),
|
|
12604
|
+
maxX: Math.max(...path.map((point) => point.x)),
|
|
12605
|
+
minY: Math.min(...path.map((point) => point.y)),
|
|
12606
|
+
maxY: Math.max(...path.map((point) => point.y))
|
|
12607
|
+
});
|
|
12608
|
+
var doesPathIntersectBounds = (path, bounds) => {
|
|
12609
|
+
for (let index = 0; index < path.length - 1; index++) {
|
|
12610
|
+
const segmentBounds = getPathBounds([path[index], path[index + 1]]);
|
|
12611
|
+
if (boundsOverlap(segmentBounds, bounds)) return true;
|
|
12612
|
+
}
|
|
12613
|
+
return false;
|
|
12614
|
+
};
|
|
12615
|
+
var resizeStub = (placement, direction, targetLength) => {
|
|
12616
|
+
const [start] = placement.stubTracePath;
|
|
12617
|
+
const end = direction === "x+" ? { x: start.x + targetLength, y: start.y } : direction === "x-" ? { x: start.x - targetLength, y: start.y } : direction === "y+" ? { x: start.x, y: start.y + targetLength } : { x: start.x, y: start.y - targetLength };
|
|
12618
|
+
const anchorPoint = {
|
|
12619
|
+
x: (start.x + end.x) / 2,
|
|
12620
|
+
y: (start.y + end.y) / 2
|
|
12621
|
+
};
|
|
12622
|
+
return {
|
|
12623
|
+
...placement,
|
|
12624
|
+
stubTracePath: [start, end],
|
|
12625
|
+
anchorPoint,
|
|
12626
|
+
// Extending a short row only adds wire at its free end. Keep the label
|
|
12627
|
+
// itself next to the pin so terminal labels can be start/end aligned and
|
|
12628
|
+
// collision checks continue to describe the rendered text box.
|
|
12629
|
+
center: placement.center
|
|
12630
|
+
};
|
|
12631
|
+
};
|
|
12632
|
+
var alignPortOnlyInlineNetLabelStubs = ({
|
|
12633
|
+
placements,
|
|
12634
|
+
inputProblem,
|
|
12635
|
+
traces,
|
|
12636
|
+
netLabelPlacements
|
|
12637
|
+
}) => {
|
|
12638
|
+
const chipIdByPinId = /* @__PURE__ */ new Map();
|
|
12639
|
+
for (const chip of inputProblem.chips) {
|
|
12640
|
+
for (const pin of chip.pins) chipIdByPinId.set(pin.pinId, chip.chipId);
|
|
12641
|
+
}
|
|
12642
|
+
const groups = /* @__PURE__ */ new Map();
|
|
12643
|
+
for (const [placementIndex, placement] of placements.entries()) {
|
|
12644
|
+
if (!placement.stubTracePath || placement.pinIds.length !== 1) continue;
|
|
12645
|
+
const direction = getStubDirection(placement.stubTracePath);
|
|
12646
|
+
if (!direction) continue;
|
|
12647
|
+
const ownerChipId = chipIdByPinId.get(placement.pinIds[0]);
|
|
12648
|
+
const groupKey = `${ownerChipId ?? placement.pinIds[0]}::${direction}`;
|
|
12649
|
+
const group = groups.get(groupKey) ?? [];
|
|
12650
|
+
group.push({ placementIndex, placement, direction, ownerChipId });
|
|
12651
|
+
groups.set(groupKey, group);
|
|
12652
|
+
}
|
|
12653
|
+
const alignedPlacements = [...placements];
|
|
12654
|
+
const supersededGlobalNetIds = new Set(
|
|
12655
|
+
placements.map((placement) => placement.globalConnNetId)
|
|
12656
|
+
);
|
|
12657
|
+
const retainedAnchoredLabelBounds = netLabelPlacements.filter(
|
|
12658
|
+
(placement) => !supersededGlobalNetIds.has(placement.globalConnNetId)
|
|
12659
|
+
).map(getLabelBounds2);
|
|
12660
|
+
for (const group of groups.values()) {
|
|
12661
|
+
if (group.length < 2) continue;
|
|
12662
|
+
const targetLength = Math.max(
|
|
12663
|
+
...group.map(({ placement }) => getStubLength(placement.stubTracePath))
|
|
12664
|
+
);
|
|
12665
|
+
const proposals = group.map(
|
|
12666
|
+
({ placement, direction }) => resizeStub(placement, direction, targetLength)
|
|
12667
|
+
);
|
|
12668
|
+
const groupPlacementIndices = new Set(
|
|
12669
|
+
group.map(({ placementIndex }) => placementIndex)
|
|
12670
|
+
);
|
|
12671
|
+
const fixedInlinePlacements = placements.filter(
|
|
12672
|
+
(_, placementIndex) => !groupPlacementIndices.has(placementIndex)
|
|
12673
|
+
);
|
|
12674
|
+
const hasConflict = proposals.some((proposal, proposalIndex) => {
|
|
12675
|
+
const labelBounds = getLabelBounds2(proposal);
|
|
12676
|
+
const stubPath = proposal.stubTracePath;
|
|
12677
|
+
const stubBounds = getPathBounds(stubPath);
|
|
12678
|
+
const ownerChipId = group[proposalIndex].ownerChipId;
|
|
12679
|
+
for (const chip of inputProblem.chips) {
|
|
12680
|
+
const chipBounds = {
|
|
12681
|
+
minX: chip.center.x - chip.width / 2,
|
|
12682
|
+
maxX: chip.center.x + chip.width / 2,
|
|
12683
|
+
minY: chip.center.y - chip.height / 2,
|
|
12684
|
+
maxY: chip.center.y + chip.height / 2
|
|
12685
|
+
};
|
|
12686
|
+
if (boundsOverlap(labelBounds, chipBounds)) return true;
|
|
12687
|
+
if (chip.chipId !== ownerChipId && boundsOverlap(stubBounds, chipBounds))
|
|
12688
|
+
return true;
|
|
12689
|
+
}
|
|
12690
|
+
for (const textBox of inputProblem.textBoxes ?? []) {
|
|
12691
|
+
const textBounds = getTextBoxBounds(textBox);
|
|
12692
|
+
if (boundsOverlap(labelBounds, textBounds) || boundsOverlap(stubBounds, textBounds))
|
|
12693
|
+
return true;
|
|
12694
|
+
}
|
|
12695
|
+
for (const trace of traces) {
|
|
12696
|
+
if (trace.globalConnNetId === proposal.globalConnNetId) continue;
|
|
12697
|
+
if (doesPathIntersectBounds(trace.tracePath, labelBounds) || doesPathIntersectBounds(trace.tracePath, stubBounds))
|
|
12698
|
+
return true;
|
|
12699
|
+
}
|
|
12700
|
+
if (retainedAnchoredLabelBounds.some(
|
|
12701
|
+
(bounds) => boundsOverlap(labelBounds, bounds) || boundsOverlap(stubBounds, bounds)
|
|
12702
|
+
))
|
|
12703
|
+
return true;
|
|
12704
|
+
for (const fixedPlacement of fixedInlinePlacements) {
|
|
12705
|
+
const fixedLabelBounds = getLabelBounds2(fixedPlacement);
|
|
12706
|
+
if (boundsOverlap(labelBounds, fixedLabelBounds) || boundsOverlap(stubBounds, fixedLabelBounds))
|
|
12707
|
+
return true;
|
|
12708
|
+
if (fixedPlacement.stubTracePath && (doesPathIntersectBounds(fixedPlacement.stubTracePath, labelBounds) || doesPathIntersectBounds(stubPath, fixedLabelBounds)))
|
|
12709
|
+
return true;
|
|
12710
|
+
}
|
|
12711
|
+
for (const [otherIndex, otherProposal] of proposals.entries()) {
|
|
12712
|
+
if (otherIndex === proposalIndex) continue;
|
|
12713
|
+
const otherLabelBounds = getLabelBounds2(otherProposal);
|
|
12714
|
+
if (boundsOverlap(labelBounds, otherLabelBounds)) return true;
|
|
12715
|
+
if (doesPathIntersectBounds(stubPath, otherLabelBounds)) return true;
|
|
12716
|
+
}
|
|
12717
|
+
return false;
|
|
12718
|
+
});
|
|
12719
|
+
if (hasConflict) continue;
|
|
12720
|
+
for (const [groupIndex, { placementIndex }] of group.entries()) {
|
|
12721
|
+
alignedPlacements[placementIndex] = proposals[groupIndex];
|
|
12722
|
+
}
|
|
12723
|
+
}
|
|
12724
|
+
return alignedPlacements;
|
|
12725
|
+
};
|
|
12726
|
+
|
|
12540
12727
|
// lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts
|
|
12541
12728
|
var DEFAULT_INLINE_NET_LABEL_HEIGHT = 0.18;
|
|
12542
12729
|
var INLINE_NET_LABEL_TRACE_MARGIN = 0.05;
|
|
@@ -12549,7 +12736,10 @@ var InlineNetLabelSolver = class extends BaseSolver {
|
|
|
12549
12736
|
inlineNetLabelPlacements = [];
|
|
12550
12737
|
/** Direct connections that opted in, still waiting to be processed */
|
|
12551
12738
|
queuedDirectConnections;
|
|
12739
|
+
/** Single-pin net connections that opted in, still waiting to be processed */
|
|
12740
|
+
queuedPortOnlyNetConnections;
|
|
12552
12741
|
tracesByPinPairKey;
|
|
12742
|
+
hasAlignedPortOnlyStubs = false;
|
|
12553
12743
|
constructor(input) {
|
|
12554
12744
|
super();
|
|
12555
12745
|
this.inputProblem = input.inputProblem;
|
|
@@ -12558,6 +12748,9 @@ var InlineNetLabelSolver = class extends BaseSolver {
|
|
|
12558
12748
|
this.queuedDirectConnections = this.inputProblem.directConnections.filter(
|
|
12559
12749
|
(dc) => dc.allowInlineNetLabel && dc.netId
|
|
12560
12750
|
);
|
|
12751
|
+
this.queuedPortOnlyNetConnections = this.inputProblem.netConnections.filter(
|
|
12752
|
+
(nc) => nc.allowInlineNetLabel && nc.pinIds.length === 1 && nc.netId
|
|
12753
|
+
);
|
|
12561
12754
|
this.tracesByPinPairKey = /* @__PURE__ */ new Map();
|
|
12562
12755
|
for (const trace of this.traces) {
|
|
12563
12756
|
const key = getPinPairKey2(trace.pins.map((p) => p.pinId));
|
|
@@ -12580,15 +12773,97 @@ var InlineNetLabelSolver = class extends BaseSolver {
|
|
|
12580
12773
|
}
|
|
12581
12774
|
_step() {
|
|
12582
12775
|
const directConnection = this.queuedDirectConnections.shift();
|
|
12583
|
-
if (
|
|
12584
|
-
|
|
12585
|
-
|
|
12776
|
+
if (directConnection) {
|
|
12777
|
+
const placement = this.computeInlinePlacement(directConnection);
|
|
12778
|
+
if (placement) {
|
|
12779
|
+
this.inlineNetLabelPlacements.push(placement);
|
|
12780
|
+
}
|
|
12586
12781
|
return;
|
|
12587
12782
|
}
|
|
12588
|
-
const
|
|
12589
|
-
if (
|
|
12590
|
-
this.
|
|
12783
|
+
const portOnlyNetConnection = this.queuedPortOnlyNetConnections.shift();
|
|
12784
|
+
if (portOnlyNetConnection) {
|
|
12785
|
+
const placement = this.computePortOnlyInlinePlacement(
|
|
12786
|
+
portOnlyNetConnection
|
|
12787
|
+
);
|
|
12788
|
+
if (placement) {
|
|
12789
|
+
this.inlineNetLabelPlacements.push(placement);
|
|
12790
|
+
}
|
|
12791
|
+
return;
|
|
12792
|
+
}
|
|
12793
|
+
if (!this.hasAlignedPortOnlyStubs) {
|
|
12794
|
+
this.inlineNetLabelPlacements = alignPortOnlyInlineNetLabelStubs({
|
|
12795
|
+
placements: this.inlineNetLabelPlacements,
|
|
12796
|
+
inputProblem: this.inputProblem,
|
|
12797
|
+
traces: this.traces,
|
|
12798
|
+
netLabelPlacements: this.inputNetLabelPlacements
|
|
12799
|
+
});
|
|
12800
|
+
this.hasAlignedPortOnlyStubs = true;
|
|
12801
|
+
return;
|
|
12802
|
+
}
|
|
12803
|
+
this.solved = true;
|
|
12804
|
+
this.stats.inlineNetLabelCount = this.inlineNetLabelPlacements.length;
|
|
12805
|
+
}
|
|
12806
|
+
/**
|
|
12807
|
+
* Converts a conventional port-only anchored placement into an inline label
|
|
12808
|
+
* on a generated outward stub. The stub follows the pin's true facing
|
|
12809
|
+
* direction; an anchored label may finish in another direction after an
|
|
12810
|
+
* elbow, which is not the direction a terminal stub should leave the pin.
|
|
12811
|
+
*/
|
|
12812
|
+
computePortOnlyInlinePlacement(netConnection) {
|
|
12813
|
+
const [pinId] = netConnection.pinIds;
|
|
12814
|
+
if (!pinId) return null;
|
|
12815
|
+
const anchoredPlacement = this.inputNetLabelPlacements.find(
|
|
12816
|
+
(placement) => placement.netId === netConnection.netId && placement.pinIds.length === 1 && placement.pinIds[0] === pinId
|
|
12817
|
+
);
|
|
12818
|
+
if (!anchoredPlacement) return null;
|
|
12819
|
+
const inputChip = this.inputProblem.chips.find(
|
|
12820
|
+
(chip) => chip.pins.some((pin) => pin.pinId === pinId)
|
|
12821
|
+
);
|
|
12822
|
+
const inputPin = inputChip?.pins.find((pin) => pin.pinId === pinId);
|
|
12823
|
+
const height = netConnection.inlineNetLabelHeight ?? DEFAULT_INLINE_NET_LABEL_HEIGHT;
|
|
12824
|
+
const width = netConnection.inlineNetLabelWidth ?? netConnection.netLabelWidth ?? estimateInlineNetLabelWidth(netConnection.netId, height);
|
|
12825
|
+
const stubLength = Math.max(width + 0.2, 0.6);
|
|
12826
|
+
const start = inputPin ? { x: inputPin.x, y: inputPin.y } : anchoredPlacement.anchorPoint;
|
|
12827
|
+
const direction = inputPin && inputChip ? inputPin._facingDirection ?? getPinDirection(inputPin, inputChip) : anchoredPlacement.orientation;
|
|
12828
|
+
const end = direction === "x+" ? { x: start.x + stubLength, y: start.y } : direction === "x-" ? { x: start.x - stubLength, y: start.y } : direction === "y+" ? { x: start.x, y: start.y + stubLength } : { x: start.x, y: start.y - stubLength };
|
|
12829
|
+
const axis = direction === "x+" || direction === "x-" ? "x" : "y";
|
|
12830
|
+
const side = axis === "x" ? "y+" : "x-";
|
|
12831
|
+
const anchorPoint = {
|
|
12832
|
+
x: (start.x + end.x) / 2,
|
|
12833
|
+
y: (start.y + end.y) / 2
|
|
12834
|
+
};
|
|
12835
|
+
const offset = height / 2 + INLINE_NET_LABEL_TRACE_MARGIN;
|
|
12836
|
+
const center = side === "y+" ? { x: anchorPoint.x, y: anchorPoint.y + offset } : { x: anchorPoint.x - offset, y: anchorPoint.y };
|
|
12837
|
+
const halfAlong = width / 2;
|
|
12838
|
+
const halfAcross = height / 2;
|
|
12839
|
+
const bounds = axis === "x" ? {
|
|
12840
|
+
minX: center.x - halfAlong,
|
|
12841
|
+
maxX: center.x + halfAlong,
|
|
12842
|
+
minY: center.y - halfAcross,
|
|
12843
|
+
maxY: center.y + halfAcross
|
|
12844
|
+
} : {
|
|
12845
|
+
minX: center.x - halfAcross,
|
|
12846
|
+
maxX: center.x + halfAcross,
|
|
12847
|
+
minY: center.y - halfAlong,
|
|
12848
|
+
maxY: center.y + halfAlong
|
|
12849
|
+
};
|
|
12850
|
+
if (this.isObstructed(bounds, {
|
|
12851
|
+
ownGlobalConnNetId: anchoredPlacement.globalConnNetId
|
|
12852
|
+
})) {
|
|
12853
|
+
return null;
|
|
12591
12854
|
}
|
|
12855
|
+
return {
|
|
12856
|
+
globalConnNetId: anchoredPlacement.globalConnNetId,
|
|
12857
|
+
netId: netConnection.netId,
|
|
12858
|
+
pinIds: [pinId],
|
|
12859
|
+
stubTracePath: [start, end],
|
|
12860
|
+
axis,
|
|
12861
|
+
anchorPoint,
|
|
12862
|
+
center,
|
|
12863
|
+
width,
|
|
12864
|
+
height,
|
|
12865
|
+
side
|
|
12866
|
+
};
|
|
12592
12867
|
}
|
|
12593
12868
|
computeInlinePlacement(directConnection) {
|
|
12594
12869
|
const traces = this.tracesByPinPairKey.get(getPinPairKey2(directConnection.pinIds)) ?? [];
|
|
@@ -12618,7 +12893,11 @@ var InlineNetLabelSolver = class extends BaseSolver {
|
|
|
12618
12893
|
minY: center.y - halfAlong,
|
|
12619
12894
|
maxY: center.y + halfAlong
|
|
12620
12895
|
};
|
|
12621
|
-
if (this.isObstructed(bounds,
|
|
12896
|
+
if (this.isObstructed(bounds, {
|
|
12897
|
+
ownTrace: trace,
|
|
12898
|
+
ownGlobalConnNetId: trace.globalConnNetId
|
|
12899
|
+
}))
|
|
12900
|
+
continue;
|
|
12622
12901
|
return {
|
|
12623
12902
|
globalConnNetId: trace.globalConnNetId,
|
|
12624
12903
|
netId: directConnection.netId,
|
|
@@ -12703,7 +12982,11 @@ var InlineNetLabelSolver = class extends BaseSolver {
|
|
|
12703
12982
|
minY: center.y - halfAlong,
|
|
12704
12983
|
maxY: center.y + halfAlong
|
|
12705
12984
|
};
|
|
12706
|
-
if (this.isObstructed(bounds,
|
|
12985
|
+
if (this.isObstructed(bounds, {
|
|
12986
|
+
ownTrace: trace,
|
|
12987
|
+
ownGlobalConnNetId: trace.globalConnNetId
|
|
12988
|
+
}))
|
|
12989
|
+
continue;
|
|
12707
12990
|
const anchorPoint = axis === "x" ? { x: along, y: side === "y+" ? maxY : minY } : { x: side === "x-" ? minX : maxX, y: along };
|
|
12708
12991
|
return {
|
|
12709
12992
|
globalConnNetId: trace.globalConnNetId,
|
|
@@ -12725,7 +13008,10 @@ var InlineNetLabelSolver = class extends BaseSolver {
|
|
|
12725
13008
|
* An inline label may not sit on top of a chip, a component's text, or a
|
|
12726
13009
|
* trace belonging to another net.
|
|
12727
13010
|
*/
|
|
12728
|
-
isObstructed(bounds,
|
|
13011
|
+
isObstructed(bounds, {
|
|
13012
|
+
ownTrace,
|
|
13013
|
+
ownGlobalConnNetId
|
|
13014
|
+
}) {
|
|
12729
13015
|
for (const chip of this.inputProblem.chips) {
|
|
12730
13016
|
const chipBounds = {
|
|
12731
13017
|
minX: chip.center.x - chip.width / 2,
|
|
@@ -12739,9 +13025,9 @@ var InlineNetLabelSolver = class extends BaseSolver {
|
|
|
12739
13025
|
if (boundsOverlap(bounds, getTextBoxBounds(textBox))) return true;
|
|
12740
13026
|
}
|
|
12741
13027
|
for (const trace of this.traces) {
|
|
12742
|
-
if (trace.mspPairId === ownTrace.mspPairId) continue;
|
|
12743
|
-
if (trace.globalConnNetId ===
|
|
12744
|
-
if (
|
|
13028
|
+
if (ownTrace && trace.mspPairId === ownTrace.mspPairId) continue;
|
|
13029
|
+
if (trace.globalConnNetId === ownGlobalConnNetId) continue;
|
|
13030
|
+
if (doesPathIntersectBounds2(trace.tracePath, bounds)) return true;
|
|
12745
13031
|
}
|
|
12746
13032
|
return false;
|
|
12747
13033
|
}
|
|
@@ -12756,10 +13042,18 @@ var InlineNetLabelSolver = class extends BaseSolver {
|
|
|
12756
13042
|
}
|
|
12757
13043
|
return keys;
|
|
12758
13044
|
}
|
|
13045
|
+
getOutputTraces(supersededNetLabelKeys) {
|
|
13046
|
+
return this.traces.filter(
|
|
13047
|
+
(trace) => !(supersededNetLabelKeys.has(trace.globalConnNetId) && trace.mspPairId.startsWith("available-net-orientation-"))
|
|
13048
|
+
);
|
|
13049
|
+
}
|
|
12759
13050
|
getOutput() {
|
|
12760
13051
|
const superseded = this.getSupersededNetLabelKeys();
|
|
12761
13052
|
return {
|
|
12762
|
-
|
|
13053
|
+
// AvailableNetOrientationSolver may have routed an elbow from a port to
|
|
13054
|
+
// the anchored label that this inline placement supersedes. Keep the
|
|
13055
|
+
// actual net trace, but discard that now-orphaned label connector.
|
|
13056
|
+
traces: this.getOutputTraces(superseded),
|
|
12763
13057
|
netLabelPlacements: this.inputNetLabelPlacements.filter(
|
|
12764
13058
|
(placement) => !superseded.has(placement.globalConnNetId)
|
|
12765
13059
|
),
|
|
@@ -12772,7 +13066,8 @@ var InlineNetLabelSolver = class extends BaseSolver {
|
|
|
12772
13066
|
graphics.rects ??= [];
|
|
12773
13067
|
graphics.points ??= [];
|
|
12774
13068
|
graphics.texts ??= [];
|
|
12775
|
-
|
|
13069
|
+
const output = this.getOutput();
|
|
13070
|
+
for (const trace of output.traces) {
|
|
12776
13071
|
graphics.lines.push({
|
|
12777
13072
|
points: trace.tracePath,
|
|
12778
13073
|
strokeColor: "purple"
|
|
@@ -12798,6 +13093,12 @@ orientation: ${label.orientation}`
|
|
|
12798
13093
|
});
|
|
12799
13094
|
}
|
|
12800
13095
|
for (const inlineLabel of this.inlineNetLabelPlacements) {
|
|
13096
|
+
if (inlineLabel.stubTracePath) {
|
|
13097
|
+
graphics.lines.push({
|
|
13098
|
+
points: inlineLabel.stubTracePath,
|
|
13099
|
+
strokeColor: "purple"
|
|
13100
|
+
});
|
|
13101
|
+
}
|
|
12801
13102
|
const isHorizontal4 = inlineLabel.axis === "x";
|
|
12802
13103
|
graphics.rects.push({
|
|
12803
13104
|
center: inlineLabel.center,
|
|
@@ -12812,12 +13113,12 @@ orientation: ${label.orientation}`
|
|
|
12812
13113
|
].join("\n")
|
|
12813
13114
|
});
|
|
12814
13115
|
graphics.texts.push({
|
|
12815
|
-
x: inlineLabel.center.x,
|
|
12816
|
-
y: inlineLabel.center.y,
|
|
13116
|
+
x: inlineLabel.stubTracePath && inlineLabel.axis === "x" ? inlineLabel.center.x + (inlineLabel.stubTracePath[1].x > inlineLabel.stubTracePath[0].x ? -inlineLabel.width / 2 : inlineLabel.width / 2) : inlineLabel.center.x,
|
|
13117
|
+
y: inlineLabel.stubTracePath && inlineLabel.axis === "y" ? inlineLabel.center.y + (inlineLabel.stubTracePath[1].y > inlineLabel.stubTracePath[0].y ? -inlineLabel.width / 2 : inlineLabel.width / 2) : inlineLabel.center.y,
|
|
12817
13118
|
text: inlineLabel.netId ?? "",
|
|
12818
13119
|
color: "green",
|
|
12819
13120
|
fontSize: inlineLabel.height,
|
|
12820
|
-
anchorSide: "center",
|
|
13121
|
+
anchorSide: inlineLabel.stubTracePath ? inlineLabel.stubTracePath[1][inlineLabel.axis] > inlineLabel.stubTracePath[0][inlineLabel.axis] ? "center_left" : "center_right" : "center",
|
|
12821
13122
|
// Vertical labels read bottom-to-top, alongside the wire they name.
|
|
12822
13123
|
rotation: inlineLabel.axis === "y" ? 90 : void 0
|
|
12823
13124
|
});
|
|
@@ -12852,7 +13153,7 @@ var getAnchorCandidates = (segment, labelWidth, step = 0.1) => {
|
|
|
12852
13153
|
offsets.push(slack / 2, -slack / 2);
|
|
12853
13154
|
return offsets.map((offset) => pointAt(mid + offset * direction));
|
|
12854
13155
|
};
|
|
12855
|
-
var
|
|
13156
|
+
var doesPathIntersectBounds2 = (path, bounds) => {
|
|
12856
13157
|
for (let i = 0; i < path.length - 1; i++) {
|
|
12857
13158
|
const a = path[i];
|
|
12858
13159
|
const b = path[i + 1];
|