@tscircuit/schematic-trace-solver 0.0.141 → 0.0.143
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 +28 -1
- package/dist/index.js +348 -34
- package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +92 -16
- package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +216 -17
- package/lib/solvers/InlineNetLabelSolver/alignPortOnlyInlineNetLabelStubs.ts +233 -0
- 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/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +3 -3
- 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/repro-tida010076-page02.input.json +2964 -0
- package/tests/repros/repro-tida010076-j4-ground-bus.test.ts +78 -0
- package/tests/repros/repro-tida010076-page02.test.ts +46 -0
- package/tests/repros/repro-usb-power-vbus-label-detour.test.ts +23 -1
- 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>;
|
|
@@ -815,6 +825,7 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
|
|
|
815
825
|
private findValidTraceAnchorCandidate;
|
|
816
826
|
private findValidOutwardTraceAnchorCandidate;
|
|
817
827
|
private getTraceAnchorCandidatePoints;
|
|
828
|
+
private sharesVerticalRailWithAny;
|
|
818
829
|
private getAvailableOrientations;
|
|
819
830
|
private findValidShiftedCandidate;
|
|
820
831
|
/**
|
|
@@ -1186,8 +1197,13 @@ declare const INLINE_NET_LABEL_MAX_SPAN_JOG = 0.4;
|
|
|
1186
1197
|
interface InlineNetLabelPlacement {
|
|
1187
1198
|
globalConnNetId: string;
|
|
1188
1199
|
netId?: string;
|
|
1189
|
-
mspPairId
|
|
1200
|
+
mspPairId?: string;
|
|
1190
1201
|
pinIds: PinId[];
|
|
1202
|
+
/**
|
|
1203
|
+
* A generated single-ended trace stub. Present only for an eligible
|
|
1204
|
+
* single-pin net connection; routed point-to-point traces omit it.
|
|
1205
|
+
*/
|
|
1206
|
+
stubTracePath?: [Point, Point];
|
|
1191
1207
|
/** Axis the text runs along: "x" reads left-to-right, "y" reads bottom-to-top */
|
|
1192
1208
|
axis: "x" | "y";
|
|
1193
1209
|
/** Midpoint of the trace segment the label is attached to */
|
|
@@ -1224,10 +1240,20 @@ declare class InlineNetLabelSolver extends BaseSolver {
|
|
|
1224
1240
|
inlineNetLabelPlacements: InlineNetLabelPlacement[];
|
|
1225
1241
|
/** Direct connections that opted in, still waiting to be processed */
|
|
1226
1242
|
queuedDirectConnections: InputDirectConnection[];
|
|
1243
|
+
/** Single-pin net connections that opted in, still waiting to be processed */
|
|
1244
|
+
queuedPortOnlyNetConnections: InputNetConnection[];
|
|
1227
1245
|
private tracesByPinPairKey;
|
|
1246
|
+
private hasAlignedPortOnlyStubs;
|
|
1228
1247
|
constructor(input: InlineNetLabelSolverInput);
|
|
1229
1248
|
getConstructorParams(): [InlineNetLabelSolverInput];
|
|
1230
1249
|
_step(): void;
|
|
1250
|
+
/**
|
|
1251
|
+
* Converts a conventional port-only anchored placement into an inline label
|
|
1252
|
+
* on a generated outward stub. The stub follows the pin's true facing
|
|
1253
|
+
* direction; an anchored label may finish in another direction after an
|
|
1254
|
+
* elbow, which is not the direction a terminal stub should leave the pin.
|
|
1255
|
+
*/
|
|
1256
|
+
private computePortOnlyInlinePlacement;
|
|
1231
1257
|
private computeInlinePlacement;
|
|
1232
1258
|
/**
|
|
1233
1259
|
* Places the label over the whole route when the route is straight except
|
|
@@ -1246,6 +1272,7 @@ declare class InlineNetLabelSolver extends BaseSolver {
|
|
|
1246
1272
|
* the other, never both.
|
|
1247
1273
|
*/
|
|
1248
1274
|
private getSupersededNetLabelKeys;
|
|
1275
|
+
private getOutputTraces;
|
|
1249
1276
|
getOutput(): {
|
|
1250
1277
|
traces: SolvedTracePath[];
|
|
1251
1278
|
netLabelPlacements: NetLabelPlacement[];
|
package/dist/index.js
CHANGED
|
@@ -8842,13 +8842,14 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
8842
8842
|
}
|
|
8843
8843
|
findValidTraceAnchorCandidate(label, orientation, labelIndex) {
|
|
8844
8844
|
const direction = dir(orientation);
|
|
8845
|
-
const candidatePoints = this.getTraceAnchorCandidatePoints(
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
8845
|
+
const candidatePoints = this.getTraceAnchorCandidatePoints(
|
|
8846
|
+
label,
|
|
8847
|
+
orientation
|
|
8848
|
+
).sort((a, b) => {
|
|
8849
|
+
const aAlongDirection = a.x * direction.x + a.y * direction.y;
|
|
8850
|
+
const bAlongDirection = b.x * direction.x + b.y * direction.y;
|
|
8851
|
+
return bAlongDirection - aAlongDirection;
|
|
8852
|
+
});
|
|
8852
8853
|
for (const anchorPoint of candidatePoints) {
|
|
8853
8854
|
const candidate = this.createCandidate(label, anchorPoint, orientation);
|
|
8854
8855
|
const result = this.evaluateCandidate(
|
|
@@ -8867,13 +8868,14 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
8867
8868
|
}
|
|
8868
8869
|
findValidOutwardTraceAnchorCandidate(label, orientation, labelIndex) {
|
|
8869
8870
|
const direction = dir(orientation);
|
|
8870
|
-
const candidatePoints = this.getTraceAnchorCandidatePoints(
|
|
8871
|
-
|
|
8872
|
-
|
|
8873
|
-
|
|
8874
|
-
|
|
8875
|
-
|
|
8876
|
-
|
|
8871
|
+
const candidatePoints = this.getTraceAnchorCandidatePoints(
|
|
8872
|
+
label,
|
|
8873
|
+
orientation
|
|
8874
|
+
).sort((a, b) => {
|
|
8875
|
+
const aAlongDirection = a.x * direction.x + a.y * direction.y;
|
|
8876
|
+
const bAlongDirection = b.x * direction.x + b.y * direction.y;
|
|
8877
|
+
return bAlongDirection - aAlongDirection;
|
|
8878
|
+
});
|
|
8877
8879
|
for (const connectorSource of candidatePoints) {
|
|
8878
8880
|
const preservedColumnAnchor = this.getSearchStartAnchor(
|
|
8879
8881
|
label,
|
|
@@ -8931,10 +8933,40 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
8931
8933
|
}
|
|
8932
8934
|
return null;
|
|
8933
8935
|
}
|
|
8934
|
-
getTraceAnchorCandidatePoints(label) {
|
|
8936
|
+
getTraceAnchorCandidatePoints(label, orientation) {
|
|
8935
8937
|
const seen = /* @__PURE__ */ new Set();
|
|
8936
8938
|
const points = [];
|
|
8937
|
-
|
|
8939
|
+
const connectedTraceIds = new Set(label.mspConnectionPairIds ?? []);
|
|
8940
|
+
if (isYOrientation(orientation)) {
|
|
8941
|
+
const connectedPinIds = /* @__PURE__ */ new Set();
|
|
8942
|
+
for (const traceId of connectedTraceIds) {
|
|
8943
|
+
for (const pinId of this.traceMap[traceId]?.pinIds ?? []) {
|
|
8944
|
+
connectedPinIds.add(pinId);
|
|
8945
|
+
}
|
|
8946
|
+
}
|
|
8947
|
+
let foundConnectedTrace = true;
|
|
8948
|
+
while (foundConnectedTrace) {
|
|
8949
|
+
foundConnectedTrace = false;
|
|
8950
|
+
for (const trace of Object.values(this.traceMap)) {
|
|
8951
|
+
if (connectedTraceIds.has(trace.mspPairId)) continue;
|
|
8952
|
+
if (trace.mspPairId.startsWith("available-net-orientation-")) continue;
|
|
8953
|
+
if (trace.globalConnNetId !== label.globalConnNetId) continue;
|
|
8954
|
+
if (!trace.pinIds.some((pinId) => connectedPinIds.has(pinId)))
|
|
8955
|
+
continue;
|
|
8956
|
+
const connectedTraces = [...connectedTraceIds].flatMap((traceId) => {
|
|
8957
|
+
const connectedTrace = this.traceMap[traceId];
|
|
8958
|
+
return connectedTrace ? [connectedTrace] : [];
|
|
8959
|
+
});
|
|
8960
|
+
if (!this.sharesVerticalRailWithAny(trace, connectedTraces)) {
|
|
8961
|
+
continue;
|
|
8962
|
+
}
|
|
8963
|
+
connectedTraceIds.add(trace.mspPairId);
|
|
8964
|
+
for (const pinId of trace.pinIds) connectedPinIds.add(pinId);
|
|
8965
|
+
foundConnectedTrace = true;
|
|
8966
|
+
}
|
|
8967
|
+
}
|
|
8968
|
+
}
|
|
8969
|
+
for (const traceId of connectedTraceIds) {
|
|
8938
8970
|
const trace = this.traceMap[traceId];
|
|
8939
8971
|
if (!trace) continue;
|
|
8940
8972
|
for (const point of trace.tracePath) {
|
|
@@ -8946,6 +8978,26 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
|
|
|
8946
8978
|
}
|
|
8947
8979
|
return points;
|
|
8948
8980
|
}
|
|
8981
|
+
sharesVerticalRailWithAny(trace, otherTraces) {
|
|
8982
|
+
const verticalRailXs = /* @__PURE__ */ new Set();
|
|
8983
|
+
for (const otherTrace of otherTraces) {
|
|
8984
|
+
for (let i = 0; i < otherTrace.tracePath.length - 1; i++) {
|
|
8985
|
+
const start = otherTrace.tracePath[i];
|
|
8986
|
+
const end = otherTrace.tracePath[i + 1];
|
|
8987
|
+
if (Math.abs(start.x - end.x) <= EPS12 && Math.abs(start.y - end.y) > EPS12) {
|
|
8988
|
+
verticalRailXs.add(start.x);
|
|
8989
|
+
}
|
|
8990
|
+
}
|
|
8991
|
+
}
|
|
8992
|
+
for (let i = 0; i < trace.tracePath.length - 1; i++) {
|
|
8993
|
+
const start = trace.tracePath[i];
|
|
8994
|
+
const end = trace.tracePath[i + 1];
|
|
8995
|
+
if (Math.abs(start.x - end.x) <= EPS12 && Math.abs(start.y - end.y) > EPS12 && [...verticalRailXs].some((x) => Math.abs(x - start.x) <= EPS12)) {
|
|
8996
|
+
return true;
|
|
8997
|
+
}
|
|
8998
|
+
}
|
|
8999
|
+
return false;
|
|
9000
|
+
}
|
|
8949
9001
|
getAvailableOrientations(label) {
|
|
8950
9002
|
const effectiveNetId = label.netId ?? label.globalConnNetId;
|
|
8951
9003
|
return this.inputProblem.availableNetLabelOrientations[effectiveNetId] ?? [];
|
|
@@ -12576,6 +12628,154 @@ var getAxisAlignedSegments = (path, epsilon = 1e-6) => {
|
|
|
12576
12628
|
return segments.sort((a, b) => b.length - a.length);
|
|
12577
12629
|
};
|
|
12578
12630
|
|
|
12631
|
+
// lib/solvers/InlineNetLabelSolver/alignPortOnlyInlineNetLabelStubs.ts
|
|
12632
|
+
var getStubDirection = (path) => {
|
|
12633
|
+
const [start, end] = path;
|
|
12634
|
+
if (Math.abs(end.x - start.x) >= Math.abs(end.y - start.y)) {
|
|
12635
|
+
return end.x >= start.x ? "x+" : "x-";
|
|
12636
|
+
}
|
|
12637
|
+
return end.y >= start.y ? "y+" : "y-";
|
|
12638
|
+
};
|
|
12639
|
+
var getStubLength = (path) => {
|
|
12640
|
+
const [start, end] = path;
|
|
12641
|
+
return Math.abs(end.x - start.x) + Math.abs(end.y - start.y);
|
|
12642
|
+
};
|
|
12643
|
+
var getLabelBounds2 = (placement) => {
|
|
12644
|
+
const isVertical5 = placement.axis === "y";
|
|
12645
|
+
const width = isVertical5 ? placement.height : placement.width;
|
|
12646
|
+
const height = isVertical5 ? placement.width : placement.height;
|
|
12647
|
+
return {
|
|
12648
|
+
minX: placement.center.x - width / 2,
|
|
12649
|
+
maxX: placement.center.x + width / 2,
|
|
12650
|
+
minY: placement.center.y - height / 2,
|
|
12651
|
+
maxY: placement.center.y + height / 2
|
|
12652
|
+
};
|
|
12653
|
+
};
|
|
12654
|
+
var getPathBounds = (path) => ({
|
|
12655
|
+
minX: Math.min(...path.map((point) => point.x)),
|
|
12656
|
+
maxX: Math.max(...path.map((point) => point.x)),
|
|
12657
|
+
minY: Math.min(...path.map((point) => point.y)),
|
|
12658
|
+
maxY: Math.max(...path.map((point) => point.y))
|
|
12659
|
+
});
|
|
12660
|
+
var doesPathIntersectBounds = (path, bounds) => {
|
|
12661
|
+
for (let index = 0; index < path.length - 1; index++) {
|
|
12662
|
+
const segmentBounds = getPathBounds([path[index], path[index + 1]]);
|
|
12663
|
+
if (boundsOverlap(segmentBounds, bounds)) return true;
|
|
12664
|
+
}
|
|
12665
|
+
return false;
|
|
12666
|
+
};
|
|
12667
|
+
var resizeStub = (placement, direction, targetLength) => {
|
|
12668
|
+
const [start] = placement.stubTracePath;
|
|
12669
|
+
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 };
|
|
12670
|
+
const anchorPoint = {
|
|
12671
|
+
x: (start.x + end.x) / 2,
|
|
12672
|
+
y: (start.y + end.y) / 2
|
|
12673
|
+
};
|
|
12674
|
+
return {
|
|
12675
|
+
...placement,
|
|
12676
|
+
stubTracePath: [start, end],
|
|
12677
|
+
anchorPoint,
|
|
12678
|
+
// Extending a short row only adds wire at its free end. Keep the label
|
|
12679
|
+
// itself next to the pin so terminal labels can be start/end aligned and
|
|
12680
|
+
// collision checks continue to describe the rendered text box.
|
|
12681
|
+
center: placement.center
|
|
12682
|
+
};
|
|
12683
|
+
};
|
|
12684
|
+
var alignPortOnlyInlineNetLabelStubs = ({
|
|
12685
|
+
placements,
|
|
12686
|
+
inputProblem,
|
|
12687
|
+
traces,
|
|
12688
|
+
netLabelPlacements
|
|
12689
|
+
}) => {
|
|
12690
|
+
const chipIdByPinId = /* @__PURE__ */ new Map();
|
|
12691
|
+
for (const chip of inputProblem.chips) {
|
|
12692
|
+
for (const pin of chip.pins) chipIdByPinId.set(pin.pinId, chip.chipId);
|
|
12693
|
+
}
|
|
12694
|
+
const groups = /* @__PURE__ */ new Map();
|
|
12695
|
+
for (const [placementIndex, placement] of placements.entries()) {
|
|
12696
|
+
if (!placement.stubTracePath || placement.pinIds.length !== 1) continue;
|
|
12697
|
+
const direction = getStubDirection(placement.stubTracePath);
|
|
12698
|
+
if (!direction) continue;
|
|
12699
|
+
const ownerChipId = chipIdByPinId.get(placement.pinIds[0]);
|
|
12700
|
+
const groupKey = `${ownerChipId ?? placement.pinIds[0]}::${direction}`;
|
|
12701
|
+
const group = groups.get(groupKey) ?? [];
|
|
12702
|
+
group.push({ placementIndex, placement, direction, ownerChipId });
|
|
12703
|
+
groups.set(groupKey, group);
|
|
12704
|
+
}
|
|
12705
|
+
const alignedPlacements = [...placements];
|
|
12706
|
+
const supersededGlobalNetIds = new Set(
|
|
12707
|
+
placements.map((placement) => placement.globalConnNetId)
|
|
12708
|
+
);
|
|
12709
|
+
const retainedAnchoredLabelBounds = netLabelPlacements.filter(
|
|
12710
|
+
(placement) => !supersededGlobalNetIds.has(placement.globalConnNetId)
|
|
12711
|
+
).map(getLabelBounds2);
|
|
12712
|
+
for (const group of groups.values()) {
|
|
12713
|
+
if (group.length < 2) continue;
|
|
12714
|
+
const targetLength = Math.max(
|
|
12715
|
+
...group.map(({ placement }) => getStubLength(placement.stubTracePath))
|
|
12716
|
+
);
|
|
12717
|
+
const proposals = group.map(
|
|
12718
|
+
({ placement, direction }) => resizeStub(placement, direction, targetLength)
|
|
12719
|
+
);
|
|
12720
|
+
const groupPlacementIndices = new Set(
|
|
12721
|
+
group.map(({ placementIndex }) => placementIndex)
|
|
12722
|
+
);
|
|
12723
|
+
const fixedInlinePlacements = placements.filter(
|
|
12724
|
+
(_, placementIndex) => !groupPlacementIndices.has(placementIndex)
|
|
12725
|
+
);
|
|
12726
|
+
const hasConflict = proposals.some((proposal, proposalIndex) => {
|
|
12727
|
+
const labelBounds = getLabelBounds2(proposal);
|
|
12728
|
+
const stubPath = proposal.stubTracePath;
|
|
12729
|
+
const stubBounds = getPathBounds(stubPath);
|
|
12730
|
+
const ownerChipId = group[proposalIndex].ownerChipId;
|
|
12731
|
+
for (const chip of inputProblem.chips) {
|
|
12732
|
+
const chipBounds = {
|
|
12733
|
+
minX: chip.center.x - chip.width / 2,
|
|
12734
|
+
maxX: chip.center.x + chip.width / 2,
|
|
12735
|
+
minY: chip.center.y - chip.height / 2,
|
|
12736
|
+
maxY: chip.center.y + chip.height / 2
|
|
12737
|
+
};
|
|
12738
|
+
if (boundsOverlap(labelBounds, chipBounds)) return true;
|
|
12739
|
+
if (chip.chipId !== ownerChipId && boundsOverlap(stubBounds, chipBounds))
|
|
12740
|
+
return true;
|
|
12741
|
+
}
|
|
12742
|
+
for (const textBox of inputProblem.textBoxes ?? []) {
|
|
12743
|
+
const textBounds = getTextBoxBounds(textBox);
|
|
12744
|
+
if (boundsOverlap(labelBounds, textBounds) || boundsOverlap(stubBounds, textBounds))
|
|
12745
|
+
return true;
|
|
12746
|
+
}
|
|
12747
|
+
for (const trace of traces) {
|
|
12748
|
+
if (trace.globalConnNetId === proposal.globalConnNetId) continue;
|
|
12749
|
+
if (doesPathIntersectBounds(trace.tracePath, labelBounds) || doesPathIntersectBounds(trace.tracePath, stubBounds))
|
|
12750
|
+
return true;
|
|
12751
|
+
}
|
|
12752
|
+
if (retainedAnchoredLabelBounds.some(
|
|
12753
|
+
(bounds) => boundsOverlap(labelBounds, bounds) || boundsOverlap(stubBounds, bounds)
|
|
12754
|
+
))
|
|
12755
|
+
return true;
|
|
12756
|
+
for (const fixedPlacement of fixedInlinePlacements) {
|
|
12757
|
+
const fixedLabelBounds = getLabelBounds2(fixedPlacement);
|
|
12758
|
+
if (boundsOverlap(labelBounds, fixedLabelBounds) || boundsOverlap(stubBounds, fixedLabelBounds))
|
|
12759
|
+
return true;
|
|
12760
|
+
if (fixedPlacement.stubTracePath && (doesPathIntersectBounds(fixedPlacement.stubTracePath, labelBounds) || doesPathIntersectBounds(stubPath, fixedLabelBounds)))
|
|
12761
|
+
return true;
|
|
12762
|
+
}
|
|
12763
|
+
for (const [otherIndex, otherProposal] of proposals.entries()) {
|
|
12764
|
+
if (otherIndex === proposalIndex) continue;
|
|
12765
|
+
const otherLabelBounds = getLabelBounds2(otherProposal);
|
|
12766
|
+
if (boundsOverlap(labelBounds, otherLabelBounds)) return true;
|
|
12767
|
+
if (doesPathIntersectBounds(stubPath, otherLabelBounds)) return true;
|
|
12768
|
+
}
|
|
12769
|
+
return false;
|
|
12770
|
+
});
|
|
12771
|
+
if (hasConflict) continue;
|
|
12772
|
+
for (const [groupIndex, { placementIndex }] of group.entries()) {
|
|
12773
|
+
alignedPlacements[placementIndex] = proposals[groupIndex];
|
|
12774
|
+
}
|
|
12775
|
+
}
|
|
12776
|
+
return alignedPlacements;
|
|
12777
|
+
};
|
|
12778
|
+
|
|
12579
12779
|
// lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts
|
|
12580
12780
|
var DEFAULT_INLINE_NET_LABEL_HEIGHT = 0.18;
|
|
12581
12781
|
var INLINE_NET_LABEL_TRACE_MARGIN = 0.05;
|
|
@@ -12588,7 +12788,10 @@ var InlineNetLabelSolver = class extends BaseSolver {
|
|
|
12588
12788
|
inlineNetLabelPlacements = [];
|
|
12589
12789
|
/** Direct connections that opted in, still waiting to be processed */
|
|
12590
12790
|
queuedDirectConnections;
|
|
12791
|
+
/** Single-pin net connections that opted in, still waiting to be processed */
|
|
12792
|
+
queuedPortOnlyNetConnections;
|
|
12591
12793
|
tracesByPinPairKey;
|
|
12794
|
+
hasAlignedPortOnlyStubs = false;
|
|
12592
12795
|
constructor(input) {
|
|
12593
12796
|
super();
|
|
12594
12797
|
this.inputProblem = input.inputProblem;
|
|
@@ -12597,6 +12800,9 @@ var InlineNetLabelSolver = class extends BaseSolver {
|
|
|
12597
12800
|
this.queuedDirectConnections = this.inputProblem.directConnections.filter(
|
|
12598
12801
|
(dc) => dc.allowInlineNetLabel && dc.netId
|
|
12599
12802
|
);
|
|
12803
|
+
this.queuedPortOnlyNetConnections = this.inputProblem.netConnections.filter(
|
|
12804
|
+
(nc) => nc.allowInlineNetLabel && nc.pinIds.length === 1 && nc.netId
|
|
12805
|
+
);
|
|
12600
12806
|
this.tracesByPinPairKey = /* @__PURE__ */ new Map();
|
|
12601
12807
|
for (const trace of this.traces) {
|
|
12602
12808
|
const key = getPinPairKey2(trace.pins.map((p) => p.pinId));
|
|
@@ -12619,15 +12825,97 @@ var InlineNetLabelSolver = class extends BaseSolver {
|
|
|
12619
12825
|
}
|
|
12620
12826
|
_step() {
|
|
12621
12827
|
const directConnection = this.queuedDirectConnections.shift();
|
|
12622
|
-
if (
|
|
12623
|
-
|
|
12624
|
-
|
|
12828
|
+
if (directConnection) {
|
|
12829
|
+
const placement = this.computeInlinePlacement(directConnection);
|
|
12830
|
+
if (placement) {
|
|
12831
|
+
this.inlineNetLabelPlacements.push(placement);
|
|
12832
|
+
}
|
|
12833
|
+
return;
|
|
12834
|
+
}
|
|
12835
|
+
const portOnlyNetConnection = this.queuedPortOnlyNetConnections.shift();
|
|
12836
|
+
if (portOnlyNetConnection) {
|
|
12837
|
+
const placement = this.computePortOnlyInlinePlacement(
|
|
12838
|
+
portOnlyNetConnection
|
|
12839
|
+
);
|
|
12840
|
+
if (placement) {
|
|
12841
|
+
this.inlineNetLabelPlacements.push(placement);
|
|
12842
|
+
}
|
|
12843
|
+
return;
|
|
12844
|
+
}
|
|
12845
|
+
if (!this.hasAlignedPortOnlyStubs) {
|
|
12846
|
+
this.inlineNetLabelPlacements = alignPortOnlyInlineNetLabelStubs({
|
|
12847
|
+
placements: this.inlineNetLabelPlacements,
|
|
12848
|
+
inputProblem: this.inputProblem,
|
|
12849
|
+
traces: this.traces,
|
|
12850
|
+
netLabelPlacements: this.inputNetLabelPlacements
|
|
12851
|
+
});
|
|
12852
|
+
this.hasAlignedPortOnlyStubs = true;
|
|
12625
12853
|
return;
|
|
12626
12854
|
}
|
|
12627
|
-
|
|
12628
|
-
|
|
12629
|
-
|
|
12855
|
+
this.solved = true;
|
|
12856
|
+
this.stats.inlineNetLabelCount = this.inlineNetLabelPlacements.length;
|
|
12857
|
+
}
|
|
12858
|
+
/**
|
|
12859
|
+
* Converts a conventional port-only anchored placement into an inline label
|
|
12860
|
+
* on a generated outward stub. The stub follows the pin's true facing
|
|
12861
|
+
* direction; an anchored label may finish in another direction after an
|
|
12862
|
+
* elbow, which is not the direction a terminal stub should leave the pin.
|
|
12863
|
+
*/
|
|
12864
|
+
computePortOnlyInlinePlacement(netConnection) {
|
|
12865
|
+
const [pinId] = netConnection.pinIds;
|
|
12866
|
+
if (!pinId) return null;
|
|
12867
|
+
const anchoredPlacement = this.inputNetLabelPlacements.find(
|
|
12868
|
+
(placement) => placement.netId === netConnection.netId && placement.pinIds.length === 1 && placement.pinIds[0] === pinId
|
|
12869
|
+
);
|
|
12870
|
+
if (!anchoredPlacement) return null;
|
|
12871
|
+
const inputChip = this.inputProblem.chips.find(
|
|
12872
|
+
(chip) => chip.pins.some((pin) => pin.pinId === pinId)
|
|
12873
|
+
);
|
|
12874
|
+
const inputPin = inputChip?.pins.find((pin) => pin.pinId === pinId);
|
|
12875
|
+
const height = netConnection.inlineNetLabelHeight ?? DEFAULT_INLINE_NET_LABEL_HEIGHT;
|
|
12876
|
+
const width = netConnection.inlineNetLabelWidth ?? netConnection.netLabelWidth ?? estimateInlineNetLabelWidth(netConnection.netId, height);
|
|
12877
|
+
const stubLength = Math.max(width + 0.2, 0.6);
|
|
12878
|
+
const start = inputPin ? { x: inputPin.x, y: inputPin.y } : anchoredPlacement.anchorPoint;
|
|
12879
|
+
const direction = inputPin && inputChip ? inputPin._facingDirection ?? getPinDirection(inputPin, inputChip) : anchoredPlacement.orientation;
|
|
12880
|
+
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 };
|
|
12881
|
+
const axis = direction === "x+" || direction === "x-" ? "x" : "y";
|
|
12882
|
+
const side = axis === "x" ? "y+" : "x-";
|
|
12883
|
+
const anchorPoint = {
|
|
12884
|
+
x: (start.x + end.x) / 2,
|
|
12885
|
+
y: (start.y + end.y) / 2
|
|
12886
|
+
};
|
|
12887
|
+
const offset = height / 2 + INLINE_NET_LABEL_TRACE_MARGIN;
|
|
12888
|
+
const center = side === "y+" ? { x: anchorPoint.x, y: anchorPoint.y + offset } : { x: anchorPoint.x - offset, y: anchorPoint.y };
|
|
12889
|
+
const halfAlong = width / 2;
|
|
12890
|
+
const halfAcross = height / 2;
|
|
12891
|
+
const bounds = axis === "x" ? {
|
|
12892
|
+
minX: center.x - halfAlong,
|
|
12893
|
+
maxX: center.x + halfAlong,
|
|
12894
|
+
minY: center.y - halfAcross,
|
|
12895
|
+
maxY: center.y + halfAcross
|
|
12896
|
+
} : {
|
|
12897
|
+
minX: center.x - halfAcross,
|
|
12898
|
+
maxX: center.x + halfAcross,
|
|
12899
|
+
minY: center.y - halfAlong,
|
|
12900
|
+
maxY: center.y + halfAlong
|
|
12901
|
+
};
|
|
12902
|
+
if (this.isObstructed(bounds, {
|
|
12903
|
+
ownGlobalConnNetId: anchoredPlacement.globalConnNetId
|
|
12904
|
+
})) {
|
|
12905
|
+
return null;
|
|
12630
12906
|
}
|
|
12907
|
+
return {
|
|
12908
|
+
globalConnNetId: anchoredPlacement.globalConnNetId,
|
|
12909
|
+
netId: netConnection.netId,
|
|
12910
|
+
pinIds: [pinId],
|
|
12911
|
+
stubTracePath: [start, end],
|
|
12912
|
+
axis,
|
|
12913
|
+
anchorPoint,
|
|
12914
|
+
center,
|
|
12915
|
+
width,
|
|
12916
|
+
height,
|
|
12917
|
+
side
|
|
12918
|
+
};
|
|
12631
12919
|
}
|
|
12632
12920
|
computeInlinePlacement(directConnection) {
|
|
12633
12921
|
const traces = this.tracesByPinPairKey.get(getPinPairKey2(directConnection.pinIds)) ?? [];
|
|
@@ -12657,7 +12945,11 @@ var InlineNetLabelSolver = class extends BaseSolver {
|
|
|
12657
12945
|
minY: center.y - halfAlong,
|
|
12658
12946
|
maxY: center.y + halfAlong
|
|
12659
12947
|
};
|
|
12660
|
-
if (this.isObstructed(bounds,
|
|
12948
|
+
if (this.isObstructed(bounds, {
|
|
12949
|
+
ownTrace: trace,
|
|
12950
|
+
ownGlobalConnNetId: trace.globalConnNetId
|
|
12951
|
+
}))
|
|
12952
|
+
continue;
|
|
12661
12953
|
return {
|
|
12662
12954
|
globalConnNetId: trace.globalConnNetId,
|
|
12663
12955
|
netId: directConnection.netId,
|
|
@@ -12742,7 +13034,11 @@ var InlineNetLabelSolver = class extends BaseSolver {
|
|
|
12742
13034
|
minY: center.y - halfAlong,
|
|
12743
13035
|
maxY: center.y + halfAlong
|
|
12744
13036
|
};
|
|
12745
|
-
if (this.isObstructed(bounds,
|
|
13037
|
+
if (this.isObstructed(bounds, {
|
|
13038
|
+
ownTrace: trace,
|
|
13039
|
+
ownGlobalConnNetId: trace.globalConnNetId
|
|
13040
|
+
}))
|
|
13041
|
+
continue;
|
|
12746
13042
|
const anchorPoint = axis === "x" ? { x: along, y: side === "y+" ? maxY : minY } : { x: side === "x-" ? minX : maxX, y: along };
|
|
12747
13043
|
return {
|
|
12748
13044
|
globalConnNetId: trace.globalConnNetId,
|
|
@@ -12764,7 +13060,10 @@ var InlineNetLabelSolver = class extends BaseSolver {
|
|
|
12764
13060
|
* An inline label may not sit on top of a chip, a component's text, or a
|
|
12765
13061
|
* trace belonging to another net.
|
|
12766
13062
|
*/
|
|
12767
|
-
isObstructed(bounds,
|
|
13063
|
+
isObstructed(bounds, {
|
|
13064
|
+
ownTrace,
|
|
13065
|
+
ownGlobalConnNetId
|
|
13066
|
+
}) {
|
|
12768
13067
|
for (const chip of this.inputProblem.chips) {
|
|
12769
13068
|
const chipBounds = {
|
|
12770
13069
|
minX: chip.center.x - chip.width / 2,
|
|
@@ -12778,9 +13077,9 @@ var InlineNetLabelSolver = class extends BaseSolver {
|
|
|
12778
13077
|
if (boundsOverlap(bounds, getTextBoxBounds(textBox))) return true;
|
|
12779
13078
|
}
|
|
12780
13079
|
for (const trace of this.traces) {
|
|
12781
|
-
if (trace.mspPairId === ownTrace.mspPairId) continue;
|
|
12782
|
-
if (trace.globalConnNetId ===
|
|
12783
|
-
if (
|
|
13080
|
+
if (ownTrace && trace.mspPairId === ownTrace.mspPairId) continue;
|
|
13081
|
+
if (trace.globalConnNetId === ownGlobalConnNetId) continue;
|
|
13082
|
+
if (doesPathIntersectBounds2(trace.tracePath, bounds)) return true;
|
|
12784
13083
|
}
|
|
12785
13084
|
return false;
|
|
12786
13085
|
}
|
|
@@ -12795,10 +13094,18 @@ var InlineNetLabelSolver = class extends BaseSolver {
|
|
|
12795
13094
|
}
|
|
12796
13095
|
return keys;
|
|
12797
13096
|
}
|
|
13097
|
+
getOutputTraces(supersededNetLabelKeys) {
|
|
13098
|
+
return this.traces.filter(
|
|
13099
|
+
(trace) => !(supersededNetLabelKeys.has(trace.globalConnNetId) && trace.mspPairId.startsWith("available-net-orientation-"))
|
|
13100
|
+
);
|
|
13101
|
+
}
|
|
12798
13102
|
getOutput() {
|
|
12799
13103
|
const superseded = this.getSupersededNetLabelKeys();
|
|
12800
13104
|
return {
|
|
12801
|
-
|
|
13105
|
+
// AvailableNetOrientationSolver may have routed an elbow from a port to
|
|
13106
|
+
// the anchored label that this inline placement supersedes. Keep the
|
|
13107
|
+
// actual net trace, but discard that now-orphaned label connector.
|
|
13108
|
+
traces: this.getOutputTraces(superseded),
|
|
12802
13109
|
netLabelPlacements: this.inputNetLabelPlacements.filter(
|
|
12803
13110
|
(placement) => !superseded.has(placement.globalConnNetId)
|
|
12804
13111
|
),
|
|
@@ -12811,7 +13118,8 @@ var InlineNetLabelSolver = class extends BaseSolver {
|
|
|
12811
13118
|
graphics.rects ??= [];
|
|
12812
13119
|
graphics.points ??= [];
|
|
12813
13120
|
graphics.texts ??= [];
|
|
12814
|
-
|
|
13121
|
+
const output = this.getOutput();
|
|
13122
|
+
for (const trace of output.traces) {
|
|
12815
13123
|
graphics.lines.push({
|
|
12816
13124
|
points: trace.tracePath,
|
|
12817
13125
|
strokeColor: "purple"
|
|
@@ -12837,6 +13145,12 @@ orientation: ${label.orientation}`
|
|
|
12837
13145
|
});
|
|
12838
13146
|
}
|
|
12839
13147
|
for (const inlineLabel of this.inlineNetLabelPlacements) {
|
|
13148
|
+
if (inlineLabel.stubTracePath) {
|
|
13149
|
+
graphics.lines.push({
|
|
13150
|
+
points: inlineLabel.stubTracePath,
|
|
13151
|
+
strokeColor: "purple"
|
|
13152
|
+
});
|
|
13153
|
+
}
|
|
12840
13154
|
const isHorizontal4 = inlineLabel.axis === "x";
|
|
12841
13155
|
graphics.rects.push({
|
|
12842
13156
|
center: inlineLabel.center,
|
|
@@ -12851,12 +13165,12 @@ orientation: ${label.orientation}`
|
|
|
12851
13165
|
].join("\n")
|
|
12852
13166
|
});
|
|
12853
13167
|
graphics.texts.push({
|
|
12854
|
-
x: inlineLabel.center.x,
|
|
12855
|
-
y: inlineLabel.center.y,
|
|
13168
|
+
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,
|
|
13169
|
+
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,
|
|
12856
13170
|
text: inlineLabel.netId ?? "",
|
|
12857
13171
|
color: "green",
|
|
12858
13172
|
fontSize: inlineLabel.height,
|
|
12859
|
-
anchorSide: "center",
|
|
13173
|
+
anchorSide: inlineLabel.stubTracePath ? inlineLabel.stubTracePath[1][inlineLabel.axis] > inlineLabel.stubTracePath[0][inlineLabel.axis] ? "center_left" : "center_right" : "center",
|
|
12860
13174
|
// Vertical labels read bottom-to-top, alongside the wire they name.
|
|
12861
13175
|
rotation: inlineLabel.axis === "y" ? 90 : void 0
|
|
12862
13176
|
});
|
|
@@ -12891,7 +13205,7 @@ var getAnchorCandidates = (segment, labelWidth, step = 0.1) => {
|
|
|
12891
13205
|
offsets.push(slack / 2, -slack / 2);
|
|
12892
13206
|
return offsets.map((offset) => pointAt(mid + offset * direction));
|
|
12893
13207
|
};
|
|
12894
|
-
var
|
|
13208
|
+
var doesPathIntersectBounds2 = (path, bounds) => {
|
|
12895
13209
|
for (let i = 0; i < path.length - 1; i++) {
|
|
12896
13210
|
const a = path[i];
|
|
12897
13211
|
const b = path[i + 1];
|