@tscircuit/schematic-trace-solver 0.0.166 → 0.0.168
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 +66 -31
- package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +3 -8
- package/lib/solvers/InlineNetLabelSolver/getAnchoredNetLabelRenderedBounds.ts +34 -0
- package/lib/solvers/InlineNetLabelSolver/pushAnchoredNetLabelsAwayFromInlineLabels.ts +15 -16
- package/lib/solvers/InlineNetLabelSolver/pushInlineTerminalLabelsAwayFromAnchoredLabels.ts +9 -11
- package/lib/solvers/MspConnectionPairSolver/getGroundConnectionPolicy.ts +38 -4
- package/lib/types/InputProblem.ts +5 -1
- package/package.json +1 -1
- package/tests/repros/__snapshots__/repro-core-ground-inline-label-fallback.snap.svg +151 -187
- package/tests/repros/__snapshots__/repro-pga300-redundant-ground-traces.snap.svg +44 -36
- package/tests/repros/repro-core-ground-inline-label-fallback.test.ts +6 -7
- package/tests/repros/repro-pga300-redundant-ground-traces.test.ts +35 -7
- package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label-anchored-label-clearance.snap.svg +24 -24
- package/tests/solvers/InlineNetLabelSolver/get-anchored-net-label-rendered-bounds.test.ts +39 -0
- package/tests/solvers/MspConnectionPairSolver/ground-direct-connection-groups.test.ts +120 -0
package/dist/index.d.ts
CHANGED
|
@@ -141,7 +141,11 @@ interface InputDirectConnection {
|
|
|
141
141
|
interface InputNetConnection {
|
|
142
142
|
netId: string;
|
|
143
143
|
pinIds: Array<PinId>;
|
|
144
|
-
/**
|
|
144
|
+
/**
|
|
145
|
+
* Preserve explicitly wired islands on this ground net, joining the islands
|
|
146
|
+
* electrically through labels instead of routing between them. Nets without
|
|
147
|
+
* explicit traces, or without this hint, retain automatic bus routing.
|
|
148
|
+
*/
|
|
145
149
|
isGround?: boolean;
|
|
146
150
|
/**
|
|
147
151
|
* User-facing text to render for this net label. `netId` remains the stable
|
package/dist/index.js
CHANGED
|
@@ -369,10 +369,17 @@ var MAX_LOCAL_GROUND_BRANCH_OFFSET = 1;
|
|
|
369
369
|
var getGroundConnectionPolicy = (inputProblem) => {
|
|
370
370
|
const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem);
|
|
371
371
|
const groundNetId = netConnMap.getNetConnectedToId("GND");
|
|
372
|
-
if (!groundNetId) return () => true;
|
|
373
372
|
const physicalConnMap = new ConnectivityMap2({});
|
|
373
|
+
const explicitlyWiredGroundNetIds = /* @__PURE__ */ new Set();
|
|
374
374
|
for (const connection of inputProblem.directConnections) {
|
|
375
375
|
physicalConnMap.addConnections([connection.pinIds]);
|
|
376
|
+
const globalNetId = netConnMap.getNetConnectedToId(connection.pinIds[0]);
|
|
377
|
+
const isMarkedGround = inputProblem.netConnections.some(
|
|
378
|
+
(net) => net.isGround && netConnMap.getNetConnectedToId(net.netId) === globalNetId
|
|
379
|
+
);
|
|
380
|
+
if (globalNetId && isMarkedGround) {
|
|
381
|
+
explicitlyWiredGroundNetIds.add(globalNetId);
|
|
382
|
+
}
|
|
376
383
|
}
|
|
377
384
|
const pins = new Map(
|
|
378
385
|
inputProblem.chips.flatMap(
|
|
@@ -388,13 +395,28 @@ var getGroundConnectionPolicy = (inputProblem) => {
|
|
|
388
395
|
({ pin: b }) => a.pinId !== b.pinId && Math.abs(a.y - b.y) < 1e-6 && Math.abs(a.x - b.x) > 1e-6 && physicalConnMap.getNetConnectedToId(b.pinId) === group
|
|
389
396
|
);
|
|
390
397
|
});
|
|
391
|
-
|
|
398
|
+
const areSeparateExplicitGroundIslands = (firstPinId, secondPinId) => {
|
|
399
|
+
const firstGlobalNetId = netConnMap.getNetConnectedToId(firstPinId);
|
|
400
|
+
const secondGlobalNetId = netConnMap.getNetConnectedToId(secondPinId);
|
|
401
|
+
if (!firstGlobalNetId || firstGlobalNetId !== secondGlobalNetId || !explicitlyWiredGroundNetIds.has(firstGlobalNetId)) {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
const firstPhysicalGroup = physicalConnMap.getNetConnectedToId(firstPinId) ?? `pin:${firstPinId}`;
|
|
405
|
+
const secondPhysicalGroup = physicalConnMap.getNetConnectedToId(secondPinId) ?? `pin:${secondPinId}`;
|
|
406
|
+
return firstPhysicalGroup !== secondPhysicalGroup;
|
|
407
|
+
};
|
|
408
|
+
if (!hasExplicitParallelGroundRail) {
|
|
409
|
+
return (firstPinId, secondPinId) => !areSeparateExplicitGroundIslands(firstPinId, secondPinId);
|
|
410
|
+
}
|
|
392
411
|
const isGroundFacingTerminal = (pinId) => {
|
|
393
412
|
const entry = pins.get(pinId);
|
|
394
413
|
return entry?.chip.pins.length === 2 && !physicalConnMap.getNetConnectedToId(pinId) && (entry.pin._facingDirection ?? getPinDirection(entry.pin, entry.chip)) === "y-";
|
|
395
414
|
};
|
|
396
415
|
return (firstPinId, secondPinId) => {
|
|
397
|
-
|
|
416
|
+
const firstGlobalNetId = netConnMap.getNetConnectedToId(firstPinId);
|
|
417
|
+
const secondGlobalNetId = netConnMap.getNetConnectedToId(secondPinId);
|
|
418
|
+
if (areSeparateExplicitGroundIslands(firstPinId, secondPinId)) return false;
|
|
419
|
+
if (!groundNetId || firstGlobalNetId !== groundNetId || secondGlobalNetId !== groundNetId) {
|
|
398
420
|
return true;
|
|
399
421
|
}
|
|
400
422
|
if (!isGroundFacingTerminal(firstPinId) && !isGroundFacingTerminal(secondPinId))
|
|
@@ -11560,10 +11582,10 @@ function isPointOnPath(point, path) {
|
|
|
11560
11582
|
}
|
|
11561
11583
|
function buildMergedObstacleLabel(seedLabel, allLabels) {
|
|
11562
11584
|
const TOUCH_MARGIN = 0.01;
|
|
11563
|
-
const
|
|
11585
|
+
const getBounds3 = (l) => getRectBounds(l.center, l.width, l.height);
|
|
11564
11586
|
const touches = (a, b) => {
|
|
11565
|
-
const ba =
|
|
11566
|
-
const bb =
|
|
11587
|
+
const ba = getBounds3(a);
|
|
11588
|
+
const bb = getBounds3(b);
|
|
11567
11589
|
return ba.minX <= bb.maxX + TOUCH_MARGIN && ba.maxX >= bb.minX - TOUCH_MARGIN && ba.minY <= bb.maxY + TOUCH_MARGIN && ba.maxY >= bb.minY - TOUCH_MARGIN;
|
|
11568
11590
|
};
|
|
11569
11591
|
const group = /* @__PURE__ */ new Set([seedLabel]);
|
|
@@ -11580,7 +11602,7 @@ function buildMergedObstacleLabel(seedLabel, allLabels) {
|
|
|
11580
11602
|
}
|
|
11581
11603
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
11582
11604
|
for (const l of group) {
|
|
11583
|
-
const b =
|
|
11605
|
+
const b = getBounds3(l);
|
|
11584
11606
|
if (b.minX < minX) minX = b.minX;
|
|
11585
11607
|
if (b.minY < minY) minY = b.minY;
|
|
11586
11608
|
if (b.maxX > maxX) maxX = b.maxX;
|
|
@@ -13814,12 +13836,28 @@ var getAxisAlignedSegments = (path, epsilon = 1e-6) => {
|
|
|
13814
13836
|
return segments.sort((a, b) => b.length - a.length);
|
|
13815
13837
|
};
|
|
13816
13838
|
|
|
13839
|
+
// lib/solvers/InlineNetLabelSolver/getAnchoredNetLabelRenderedBounds.ts
|
|
13840
|
+
var getAnchoredNetLabelRenderedBounds = (placement) => {
|
|
13841
|
+
if (placement.orientation !== "x-" && placement.orientation !== "x+") {
|
|
13842
|
+
return getRectBounds(placement.center, placement.width, placement.height);
|
|
13843
|
+
}
|
|
13844
|
+
const width = Math.max(placement.width, placement.height);
|
|
13845
|
+
const height = NET_LABEL_HORIZONTAL_HEIGHT;
|
|
13846
|
+
const center = getCenterFromAnchor(
|
|
13847
|
+
placement.anchorPoint,
|
|
13848
|
+
placement.orientation,
|
|
13849
|
+
width,
|
|
13850
|
+
height
|
|
13851
|
+
);
|
|
13852
|
+
return getRectBounds(center, width, height);
|
|
13853
|
+
};
|
|
13854
|
+
|
|
13817
13855
|
// lib/solvers/InlineNetLabelSolver/pushAnchoredNetLabelsAwayFromInlineLabels.ts
|
|
13818
13856
|
var LABEL_CLEARANCE2 = 0.05;
|
|
13819
13857
|
var POINT_EPSILON = 1e-6;
|
|
13820
13858
|
var CONTIGUOUS_LABEL_GAP = 0.01;
|
|
13821
13859
|
var MAX_OUTWARD_DISTANCE = 5;
|
|
13822
|
-
var
|
|
13860
|
+
var getInlineBounds = (placement) => {
|
|
13823
13861
|
const renderedWidth = placement.axis === "y" ? placement.height : placement.width;
|
|
13824
13862
|
const renderedHeight = placement.axis === "y" ? placement.width : placement.height;
|
|
13825
13863
|
return {
|
|
@@ -13861,7 +13899,7 @@ var isPointOnPath2 = (point, path) => {
|
|
|
13861
13899
|
return false;
|
|
13862
13900
|
};
|
|
13863
13901
|
var getRequiredOutwardDistance = (label, inlineBounds) => {
|
|
13864
|
-
const labelBounds =
|
|
13902
|
+
const labelBounds = getAnchoredNetLabelRenderedBounds(label);
|
|
13865
13903
|
if (label.orientation === "x-" || label.orientation === "x+") {
|
|
13866
13904
|
const nearby2 = inlineBounds.filter(
|
|
13867
13905
|
(bounds) => labelBounds.minY < bounds.maxY && labelBounds.maxY > bounds.minY
|
|
@@ -13988,8 +14026,8 @@ var getContiguousLabelGroup = ({
|
|
|
13988
14026
|
if (group.has(labelIndex)) continue;
|
|
13989
14027
|
if ([...group].some(
|
|
13990
14028
|
(memberIndex) => boundsGapOnPerpendicularAxis(
|
|
13991
|
-
|
|
13992
|
-
|
|
14029
|
+
getAnchoredNetLabelRenderedBounds(labels[memberIndex]),
|
|
14030
|
+
getAnchoredNetLabelRenderedBounds(label),
|
|
13993
14031
|
trigger.orientation
|
|
13994
14032
|
) <= CONTIGUOUS_LABEL_GAP
|
|
13995
14033
|
)) {
|
|
@@ -14011,7 +14049,7 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
|
|
|
14011
14049
|
tracePath: trace.tracePath.map((point) => ({ ...point }))
|
|
14012
14050
|
}));
|
|
14013
14051
|
const outputLabels = netLabelPlacements.map((label) => ({ ...label }));
|
|
14014
|
-
const inlineBounds = inlineNetLabelPlacements.map(
|
|
14052
|
+
const inlineBounds = inlineNetLabelPlacements.map(getInlineBounds);
|
|
14015
14053
|
const pinMap = getPinMap(inputProblem);
|
|
14016
14054
|
const chipIdByPinId = /* @__PURE__ */ new Map();
|
|
14017
14055
|
for (const chip of inputProblem.chips) {
|
|
@@ -14034,7 +14072,7 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
|
|
|
14034
14072
|
for (let iteration = 0; iteration < outputLabels.length; iteration++) {
|
|
14035
14073
|
let adjustedObstacle = false;
|
|
14036
14074
|
for (const [movingIndex, movingDistance] of distances) {
|
|
14037
|
-
const movingBounds =
|
|
14075
|
+
const movingBounds = getAnchoredNetLabelRenderedBounds(
|
|
14038
14076
|
moveLabel(
|
|
14039
14077
|
outputLabels[movingIndex],
|
|
14040
14078
|
trigger.orientation,
|
|
@@ -14045,7 +14083,7 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
|
|
|
14045
14083
|
if (group.has(obstacleIndex)) continue;
|
|
14046
14084
|
const obstacle = outputLabels[obstacleIndex];
|
|
14047
14085
|
const existingObstacleDistance = distances.get(obstacleIndex) ?? 0;
|
|
14048
|
-
const obstacleBounds =
|
|
14086
|
+
const obstacleBounds = getAnchoredNetLabelRenderedBounds(
|
|
14049
14087
|
moveLabel(obstacle, trigger.orientation, existingObstacleDistance)
|
|
14050
14088
|
);
|
|
14051
14089
|
if (!boundsOverlap(movingBounds, obstacleBounds)) continue;
|
|
@@ -14054,7 +14092,7 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
|
|
|
14054
14092
|
break;
|
|
14055
14093
|
}
|
|
14056
14094
|
const shoveDistance = getDistanceToShoveBoundsPast(
|
|
14057
|
-
|
|
14095
|
+
getAnchoredNetLabelRenderedBounds(obstacle),
|
|
14058
14096
|
movingBounds,
|
|
14059
14097
|
trigger.orientation
|
|
14060
14098
|
);
|
|
@@ -14083,7 +14121,7 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
|
|
|
14083
14121
|
}
|
|
14084
14122
|
const finalLabelAt = (labelIndex) => proposals.get(labelIndex) ?? outputLabels[labelIndex];
|
|
14085
14123
|
for (const [labelIndex, movedLabel] of proposals) {
|
|
14086
|
-
const movedBounds =
|
|
14124
|
+
const movedBounds = getAnchoredNetLabelRenderedBounds(movedLabel);
|
|
14087
14125
|
if (inlineBounds.some((bounds) => boundsOverlap(movedBounds, bounds))) {
|
|
14088
14126
|
failed = true;
|
|
14089
14127
|
break;
|
|
@@ -14102,7 +14140,10 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
|
|
|
14102
14140
|
break;
|
|
14103
14141
|
}
|
|
14104
14142
|
if (outputLabels.some(
|
|
14105
|
-
(_, otherIndex) => otherIndex !== labelIndex && boundsOverlap(
|
|
14143
|
+
(_, otherIndex) => otherIndex !== labelIndex && boundsOverlap(
|
|
14144
|
+
movedBounds,
|
|
14145
|
+
getAnchoredNetLabelRenderedBounds(finalLabelAt(otherIndex))
|
|
14146
|
+
)
|
|
14106
14147
|
)) {
|
|
14107
14148
|
failed = true;
|
|
14108
14149
|
break;
|
|
@@ -14147,7 +14188,7 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
|
|
|
14147
14188
|
) || outputLabels.some(
|
|
14148
14189
|
(_, otherIndex) => otherIndex !== labelIndex && pathIntersectsBounds(
|
|
14149
14190
|
connector.tracePath,
|
|
14150
|
-
|
|
14191
|
+
getAnchoredNetLabelRenderedBounds(finalLabelAt(otherIndex))
|
|
14151
14192
|
)
|
|
14152
14193
|
);
|
|
14153
14194
|
if (connectorObstructed) {
|
|
@@ -14183,7 +14224,7 @@ var getStubDirection2 = (path) => {
|
|
|
14183
14224
|
}
|
|
14184
14225
|
return end.y >= start.y ? "y+" : "y-";
|
|
14185
14226
|
};
|
|
14186
|
-
var
|
|
14227
|
+
var getInlineLabelBounds = (placement) => {
|
|
14187
14228
|
const renderedWidth = placement.axis === "y" ? placement.height : placement.width;
|
|
14188
14229
|
const renderedHeight = placement.axis === "y" ? placement.width : placement.height;
|
|
14189
14230
|
return {
|
|
@@ -14283,12 +14324,12 @@ var pushInlineTerminalLabelsAwayFromAnchoredLabels = ({
|
|
|
14283
14324
|
direction,
|
|
14284
14325
|
distance7
|
|
14285
14326
|
);
|
|
14286
|
-
const shiftedBounds =
|
|
14327
|
+
const shiftedBounds = getInlineLabelBounds(shiftedPlacement);
|
|
14287
14328
|
for (const anchoredPlacement of anchoredNetLabelPlacements) {
|
|
14288
14329
|
if (anchoredPlacement.globalConnNetId === placement.globalConnNetId) {
|
|
14289
14330
|
continue;
|
|
14290
14331
|
}
|
|
14291
|
-
const anchoredBounds =
|
|
14332
|
+
const anchoredBounds = getAnchoredNetLabelRenderedBounds(anchoredPlacement);
|
|
14292
14333
|
if (!boundsOverlap(shiftedBounds, anchoredBounds)) continue;
|
|
14293
14334
|
requiredAdditionalDistance = Math.max(
|
|
14294
14335
|
requiredAdditionalDistance,
|
|
@@ -14312,13 +14353,13 @@ var pushInlineTerminalLabelsAwayFromAnchoredLabels = ({
|
|
|
14312
14353
|
const originalEnd = originalPlacement.stubTracePath[1];
|
|
14313
14354
|
const shiftedEnd = proposal.stubTracePath[1];
|
|
14314
14355
|
const addedStubSegment = [originalEnd, shiftedEnd];
|
|
14315
|
-
const labelBounds =
|
|
14356
|
+
const labelBounds = getInlineLabelBounds(proposal);
|
|
14316
14357
|
const ownerChipId = group[proposalIndex].ownerChipId;
|
|
14317
14358
|
if (anchoredNetLabelPlacements.some((anchoredPlacement) => {
|
|
14318
14359
|
if (anchoredPlacement.globalConnNetId === proposal.globalConnNetId) {
|
|
14319
14360
|
return false;
|
|
14320
14361
|
}
|
|
14321
|
-
const anchoredBounds =
|
|
14362
|
+
const anchoredBounds = getAnchoredNetLabelRenderedBounds(anchoredPlacement);
|
|
14322
14363
|
return boundsOverlap(labelBounds, anchoredBounds) || doesPathIntersectBounds2(addedStubSegment, anchoredBounds);
|
|
14323
14364
|
})) {
|
|
14324
14365
|
return true;
|
|
@@ -14351,7 +14392,7 @@ var pushInlineTerminalLabelsAwayFromAnchoredLabels = ({
|
|
|
14351
14392
|
}
|
|
14352
14393
|
}
|
|
14353
14394
|
for (const fixedPlacement of fixedInlinePlacements) {
|
|
14354
|
-
const fixedBounds =
|
|
14395
|
+
const fixedBounds = getInlineLabelBounds(fixedPlacement);
|
|
14355
14396
|
if (boundsOverlap(labelBounds, fixedBounds) || doesPathIntersectBounds2(addedStubSegment, fixedBounds) || fixedPlacement.stubTracePath && doesPathIntersectBounds2(fixedPlacement.stubTracePath, labelBounds)) {
|
|
14356
14397
|
return true;
|
|
14357
14398
|
}
|
|
@@ -14398,12 +14439,6 @@ var getInlinePlacementBounds = (placement) => {
|
|
|
14398
14439
|
maxY: placement.center.y + renderedHeight / 2
|
|
14399
14440
|
};
|
|
14400
14441
|
};
|
|
14401
|
-
var getAnchoredPlacementBounds = (placement) => ({
|
|
14402
|
-
minX: placement.center.x - placement.width / 2,
|
|
14403
|
-
maxX: placement.center.x + placement.width / 2,
|
|
14404
|
-
minY: placement.center.y - placement.height / 2,
|
|
14405
|
-
maxY: placement.center.y + placement.height / 2
|
|
14406
|
-
});
|
|
14407
14442
|
var InlineNetLabelSolver = class extends BaseSolver {
|
|
14408
14443
|
inputProblem;
|
|
14409
14444
|
traces;
|
|
@@ -14973,7 +15008,7 @@ var InlineNetLabelSolver = class extends BaseSolver {
|
|
|
14973
15008
|
if (anchoredPlacement.globalConnNetId === inlinePlacement.globalConnNetId) {
|
|
14974
15009
|
continue;
|
|
14975
15010
|
}
|
|
14976
|
-
const anchoredBounds =
|
|
15011
|
+
const anchoredBounds = getAnchoredNetLabelRenderedBounds(anchoredPlacement);
|
|
14977
15012
|
if (boundsOverlap(inlineBounds, anchoredBounds) || inlinePlacement.stubTracePath && doesPathIntersectBounds3(
|
|
14978
15013
|
inlinePlacement.stubTracePath,
|
|
14979
15014
|
anchoredBounds
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
type AxisAlignedSegment,
|
|
22
22
|
getAxisAlignedSegments,
|
|
23
23
|
} from "./getAxisAlignedSegments"
|
|
24
|
+
import { getAnchoredNetLabelRenderedBounds } from "./getAnchoredNetLabelRenderedBounds"
|
|
24
25
|
import { pushAnchoredNetLabelsAwayFromInlineLabels } from "./pushAnchoredNetLabelsAwayFromInlineLabels"
|
|
25
26
|
import { pushInlineTerminalLabelsAwayFromAnchoredLabels } from "./pushInlineTerminalLabelsAwayFromAnchoredLabels"
|
|
26
27
|
|
|
@@ -157,13 +158,6 @@ const getInlinePlacementBounds = (
|
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
160
|
|
|
160
|
-
const getAnchoredPlacementBounds = (placement: NetLabelPlacement): Bounds => ({
|
|
161
|
-
minX: placement.center.x - placement.width / 2,
|
|
162
|
-
maxX: placement.center.x + placement.width / 2,
|
|
163
|
-
minY: placement.center.y - placement.height / 2,
|
|
164
|
-
maxY: placement.center.y + placement.height / 2,
|
|
165
|
-
})
|
|
166
|
-
|
|
167
161
|
/**
|
|
168
162
|
* Places "inline net labels" - net names drawn alongside the trace they belong
|
|
169
163
|
* to - for connections that opted in via `allowInlineNetLabel`.
|
|
@@ -1007,7 +1001,8 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
1007
1001
|
) {
|
|
1008
1002
|
continue
|
|
1009
1003
|
}
|
|
1010
|
-
const anchoredBounds =
|
|
1004
|
+
const anchoredBounds =
|
|
1005
|
+
getAnchoredNetLabelRenderedBounds(anchoredPlacement)
|
|
1011
1006
|
if (
|
|
1012
1007
|
boundsOverlap(inlineBounds, anchoredBounds) ||
|
|
1013
1008
|
(inlinePlacement.stubTracePath &&
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Bounds } from "@tscircuit/math-utils"
|
|
2
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
3
|
+
import {
|
|
4
|
+
getCenterFromAnchor,
|
|
5
|
+
getRectBounds,
|
|
6
|
+
NET_LABEL_HORIZONTAL_HEIGHT,
|
|
7
|
+
} from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Returns the visible schematic-world bounds of a conventional net label.
|
|
11
|
+
*
|
|
12
|
+
* Horizontal labels render as tags whose text remains horizontal. Rail inputs
|
|
13
|
+
* encode their vertical-symbol dimensions in width/height, so a rail label
|
|
14
|
+
* that later falls back to an x-facing tag can have those extents transposed.
|
|
15
|
+
* Normalize that tag to the renderer's horizontal envelope before using it as
|
|
16
|
+
* an obstacle.
|
|
17
|
+
*/
|
|
18
|
+
export const getAnchoredNetLabelRenderedBounds = (
|
|
19
|
+
placement: NetLabelPlacement,
|
|
20
|
+
): Bounds => {
|
|
21
|
+
if (placement.orientation !== "x-" && placement.orientation !== "x+") {
|
|
22
|
+
return getRectBounds(placement.center, placement.width, placement.height)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const width = Math.max(placement.width, placement.height)
|
|
26
|
+
const height = NET_LABEL_HORIZONTAL_HEIGHT
|
|
27
|
+
const center = getCenterFromAnchor(
|
|
28
|
+
placement.anchorPoint,
|
|
29
|
+
placement.orientation,
|
|
30
|
+
width,
|
|
31
|
+
height,
|
|
32
|
+
)
|
|
33
|
+
return getRectBounds(center, width, height)
|
|
34
|
+
}
|
|
@@ -8,6 +8,7 @@ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/Sche
|
|
|
8
8
|
import type { InputProblem } from "lib/types/InputProblem"
|
|
9
9
|
import { dir, type FacingDirection } from "lib/utils/dir"
|
|
10
10
|
import { boundsOverlap, getTextBoxBounds } from "lib/utils/textBoxBounds"
|
|
11
|
+
import { getAnchoredNetLabelRenderedBounds } from "./getAnchoredNetLabelRenderedBounds"
|
|
11
12
|
import type { InlineNetLabelPlacement } from "./InlineNetLabelSolver"
|
|
12
13
|
|
|
13
14
|
const LABEL_CLEARANCE = 0.05
|
|
@@ -15,12 +16,7 @@ const POINT_EPSILON = 1e-6
|
|
|
15
16
|
const CONTIGUOUS_LABEL_GAP = 0.01
|
|
16
17
|
const MAX_OUTWARD_DISTANCE = 5
|
|
17
18
|
|
|
18
|
-
const
|
|
19
|
-
center: Point
|
|
20
|
-
width: number
|
|
21
|
-
height: number
|
|
22
|
-
axis?: "x" | "y"
|
|
23
|
-
}): Bounds => {
|
|
19
|
+
const getInlineBounds = (placement: InlineNetLabelPlacement): Bounds => {
|
|
24
20
|
const renderedWidth =
|
|
25
21
|
placement.axis === "y" ? placement.height : placement.width
|
|
26
22
|
const renderedHeight =
|
|
@@ -79,7 +75,7 @@ const getRequiredOutwardDistance = (
|
|
|
79
75
|
label: NetLabelPlacement,
|
|
80
76
|
inlineBounds: Bounds[],
|
|
81
77
|
) => {
|
|
82
|
-
const labelBounds =
|
|
78
|
+
const labelBounds = getAnchoredNetLabelRenderedBounds(label)
|
|
83
79
|
|
|
84
80
|
if (label.orientation === "x-" || label.orientation === "x+") {
|
|
85
81
|
const nearby = inlineBounds.filter(
|
|
@@ -272,8 +268,8 @@ const getContiguousLabelGroup = ({
|
|
|
272
268
|
[...group].some(
|
|
273
269
|
(memberIndex) =>
|
|
274
270
|
boundsGapOnPerpendicularAxis(
|
|
275
|
-
|
|
276
|
-
|
|
271
|
+
getAnchoredNetLabelRenderedBounds(labels[memberIndex]!),
|
|
272
|
+
getAnchoredNetLabelRenderedBounds(label),
|
|
277
273
|
trigger.orientation,
|
|
278
274
|
) <= CONTIGUOUS_LABEL_GAP,
|
|
279
275
|
)
|
|
@@ -316,7 +312,7 @@ export const pushAnchoredNetLabelsAwayFromInlineLabels = ({
|
|
|
316
312
|
tracePath: trace.tracePath.map((point) => ({ ...point })),
|
|
317
313
|
}))
|
|
318
314
|
const outputLabels = netLabelPlacements.map((label) => ({ ...label }))
|
|
319
|
-
const inlineBounds = inlineNetLabelPlacements.map(
|
|
315
|
+
const inlineBounds = inlineNetLabelPlacements.map(getInlineBounds)
|
|
320
316
|
const pinMap = getPinMap(inputProblem)
|
|
321
317
|
const chipIdByPinId = new Map<string, string>()
|
|
322
318
|
for (const chip of inputProblem.chips) {
|
|
@@ -346,7 +342,7 @@ export const pushAnchoredNetLabelsAwayFromInlineLabels = ({
|
|
|
346
342
|
for (let iteration = 0; iteration < outputLabels.length; iteration++) {
|
|
347
343
|
let adjustedObstacle = false
|
|
348
344
|
for (const [movingIndex, movingDistance] of distances) {
|
|
349
|
-
const movingBounds =
|
|
345
|
+
const movingBounds = getAnchoredNetLabelRenderedBounds(
|
|
350
346
|
moveLabel(
|
|
351
347
|
outputLabels[movingIndex]!,
|
|
352
348
|
trigger.orientation,
|
|
@@ -361,7 +357,7 @@ export const pushAnchoredNetLabelsAwayFromInlineLabels = ({
|
|
|
361
357
|
if (group.has(obstacleIndex)) continue
|
|
362
358
|
const obstacle = outputLabels[obstacleIndex]!
|
|
363
359
|
const existingObstacleDistance = distances.get(obstacleIndex) ?? 0
|
|
364
|
-
const obstacleBounds =
|
|
360
|
+
const obstacleBounds = getAnchoredNetLabelRenderedBounds(
|
|
365
361
|
moveLabel(obstacle, trigger.orientation, existingObstacleDistance),
|
|
366
362
|
)
|
|
367
363
|
if (!boundsOverlap(movingBounds, obstacleBounds)) continue
|
|
@@ -374,7 +370,7 @@ export const pushAnchoredNetLabelsAwayFromInlineLabels = ({
|
|
|
374
370
|
break
|
|
375
371
|
}
|
|
376
372
|
const shoveDistance = getDistanceToShoveBoundsPast(
|
|
377
|
-
|
|
373
|
+
getAnchoredNetLabelRenderedBounds(obstacle),
|
|
378
374
|
movingBounds,
|
|
379
375
|
trigger.orientation,
|
|
380
376
|
)
|
|
@@ -409,7 +405,7 @@ export const pushAnchoredNetLabelsAwayFromInlineLabels = ({
|
|
|
409
405
|
const finalLabelAt = (labelIndex: number) =>
|
|
410
406
|
proposals.get(labelIndex) ?? outputLabels[labelIndex]!
|
|
411
407
|
for (const [labelIndex, movedLabel] of proposals) {
|
|
412
|
-
const movedBounds =
|
|
408
|
+
const movedBounds = getAnchoredNetLabelRenderedBounds(movedLabel)
|
|
413
409
|
if (inlineBounds.some((bounds) => boundsOverlap(movedBounds, bounds))) {
|
|
414
410
|
failed = true
|
|
415
411
|
break
|
|
@@ -434,7 +430,10 @@ export const pushAnchoredNetLabelsAwayFromInlineLabels = ({
|
|
|
434
430
|
outputLabels.some(
|
|
435
431
|
(_, otherIndex) =>
|
|
436
432
|
otherIndex !== labelIndex &&
|
|
437
|
-
boundsOverlap(
|
|
433
|
+
boundsOverlap(
|
|
434
|
+
movedBounds,
|
|
435
|
+
getAnchoredNetLabelRenderedBounds(finalLabelAt(otherIndex)),
|
|
436
|
+
),
|
|
438
437
|
)
|
|
439
438
|
) {
|
|
440
439
|
failed = true
|
|
@@ -503,7 +502,7 @@ export const pushAnchoredNetLabelsAwayFromInlineLabels = ({
|
|
|
503
502
|
otherIndex !== labelIndex &&
|
|
504
503
|
pathIntersectsBounds(
|
|
505
504
|
connector.tracePath,
|
|
506
|
-
|
|
505
|
+
getAnchoredNetLabelRenderedBounds(finalLabelAt(otherIndex)),
|
|
507
506
|
),
|
|
508
507
|
)
|
|
509
508
|
if (connectorObstructed) {
|
|
@@ -3,6 +3,7 @@ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetL
|
|
|
3
3
|
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
4
4
|
import type { InputProblem } from "lib/types/InputProblem"
|
|
5
5
|
import { boundsOverlap, getTextBoxBounds } from "lib/utils/textBoxBounds"
|
|
6
|
+
import { getAnchoredNetLabelRenderedBounds } from "./getAnchoredNetLabelRenderedBounds"
|
|
6
7
|
import type { InlineNetLabelPlacement } from "./InlineNetLabelSolver"
|
|
7
8
|
|
|
8
9
|
type StubDirection = "x+" | "x-" | "y+" | "y-"
|
|
@@ -18,12 +19,7 @@ const getStubDirection = (path: [Point, Point]): StubDirection => {
|
|
|
18
19
|
return end.y >= start.y ? "y+" : "y-"
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
const
|
|
22
|
-
center: Point
|
|
23
|
-
width: number
|
|
24
|
-
height: number
|
|
25
|
-
axis?: "x" | "y"
|
|
26
|
-
}): Bounds => {
|
|
22
|
+
const getInlineLabelBounds = (placement: InlineNetLabelPlacement): Bounds => {
|
|
27
23
|
const renderedWidth =
|
|
28
24
|
placement.axis === "y" ? placement.height : placement.width
|
|
29
25
|
const renderedHeight =
|
|
@@ -178,12 +174,13 @@ export const pushInlineTerminalLabelsAwayFromAnchoredLabels = ({
|
|
|
178
174
|
direction,
|
|
179
175
|
distance,
|
|
180
176
|
)
|
|
181
|
-
const shiftedBounds =
|
|
177
|
+
const shiftedBounds = getInlineLabelBounds(shiftedPlacement)
|
|
182
178
|
for (const anchoredPlacement of anchoredNetLabelPlacements) {
|
|
183
179
|
if (anchoredPlacement.globalConnNetId === placement.globalConnNetId) {
|
|
184
180
|
continue
|
|
185
181
|
}
|
|
186
|
-
const anchoredBounds =
|
|
182
|
+
const anchoredBounds =
|
|
183
|
+
getAnchoredNetLabelRenderedBounds(anchoredPlacement)
|
|
187
184
|
if (!boundsOverlap(shiftedBounds, anchoredBounds)) continue
|
|
188
185
|
requiredAdditionalDistance = Math.max(
|
|
189
186
|
requiredAdditionalDistance,
|
|
@@ -209,7 +206,7 @@ export const pushInlineTerminalLabelsAwayFromAnchoredLabels = ({
|
|
|
209
206
|
const originalEnd = originalPlacement.stubTracePath![1]
|
|
210
207
|
const shiftedEnd = proposal.stubTracePath![1]
|
|
211
208
|
const addedStubSegment: [Point, Point] = [originalEnd, shiftedEnd]
|
|
212
|
-
const labelBounds =
|
|
209
|
+
const labelBounds = getInlineLabelBounds(proposal)
|
|
213
210
|
const ownerChipId = group[proposalIndex]!.ownerChipId
|
|
214
211
|
|
|
215
212
|
if (
|
|
@@ -217,7 +214,8 @@ export const pushInlineTerminalLabelsAwayFromAnchoredLabels = ({
|
|
|
217
214
|
if (anchoredPlacement.globalConnNetId === proposal.globalConnNetId) {
|
|
218
215
|
return false
|
|
219
216
|
}
|
|
220
|
-
const anchoredBounds =
|
|
217
|
+
const anchoredBounds =
|
|
218
|
+
getAnchoredNetLabelRenderedBounds(anchoredPlacement)
|
|
221
219
|
return (
|
|
222
220
|
boundsOverlap(labelBounds, anchoredBounds) ||
|
|
223
221
|
doesPathIntersectBounds(addedStubSegment, anchoredBounds)
|
|
@@ -267,7 +265,7 @@ export const pushInlineTerminalLabelsAwayFromAnchoredLabels = ({
|
|
|
267
265
|
}
|
|
268
266
|
|
|
269
267
|
for (const fixedPlacement of fixedInlinePlacements) {
|
|
270
|
-
const fixedBounds =
|
|
268
|
+
const fixedBounds = getInlineLabelBounds(fixedPlacement)
|
|
271
269
|
if (
|
|
272
270
|
boundsOverlap(labelBounds, fixedBounds) ||
|
|
273
271
|
doesPathIntersectBounds(addedStubSegment, fixedBounds) ||
|
|
@@ -11,12 +11,21 @@ const MAX_LOCAL_GROUND_BRANCH_OFFSET = 1
|
|
|
11
11
|
export const getGroundConnectionPolicy = (inputProblem: InputProblem) => {
|
|
12
12
|
const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem)
|
|
13
13
|
const groundNetId = netConnMap.getNetConnectedToId("GND")
|
|
14
|
-
if (!groundNetId) return () => true
|
|
15
14
|
// Net identifiers do not constitute physical edges: two separate direct
|
|
16
15
|
// connections may have the same netId without requesting a wire between them.
|
|
17
16
|
const physicalConnMap = new ConnectivityMap({})
|
|
17
|
+
const explicitlyWiredGroundNetIds = new Set<string>()
|
|
18
18
|
for (const connection of inputProblem.directConnections) {
|
|
19
19
|
physicalConnMap.addConnections([connection.pinIds])
|
|
20
|
+
const globalNetId = netConnMap.getNetConnectedToId(connection.pinIds[0])
|
|
21
|
+
const isMarkedGround = inputProblem.netConnections.some(
|
|
22
|
+
(net) =>
|
|
23
|
+
net.isGround &&
|
|
24
|
+
netConnMap.getNetConnectedToId(net.netId) === globalNetId,
|
|
25
|
+
)
|
|
26
|
+
if (globalNetId && isMarkedGround) {
|
|
27
|
+
explicitlyWiredGroundNetIds.add(globalNetId)
|
|
28
|
+
}
|
|
20
29
|
}
|
|
21
30
|
const pins = new Map(
|
|
22
31
|
inputProblem.chips.flatMap((chip) =>
|
|
@@ -46,7 +55,29 @@ export const getGroundConnectionPolicy = (inputProblem: InputProblem) => {
|
|
|
46
55
|
)
|
|
47
56
|
)
|
|
48
57
|
})
|
|
49
|
-
|
|
58
|
+
const areSeparateExplicitGroundIslands = (
|
|
59
|
+
firstPinId: PinId,
|
|
60
|
+
secondPinId: PinId,
|
|
61
|
+
) => {
|
|
62
|
+
const firstGlobalNetId = netConnMap.getNetConnectedToId(firstPinId)
|
|
63
|
+
const secondGlobalNetId = netConnMap.getNetConnectedToId(secondPinId)
|
|
64
|
+
if (
|
|
65
|
+
!firstGlobalNetId ||
|
|
66
|
+
firstGlobalNetId !== secondGlobalNetId ||
|
|
67
|
+
!explicitlyWiredGroundNetIds.has(firstGlobalNetId)
|
|
68
|
+
) {
|
|
69
|
+
return false
|
|
70
|
+
}
|
|
71
|
+
const firstPhysicalGroup =
|
|
72
|
+
physicalConnMap.getNetConnectedToId(firstPinId) ?? `pin:${firstPinId}`
|
|
73
|
+
const secondPhysicalGroup =
|
|
74
|
+
physicalConnMap.getNetConnectedToId(secondPinId) ?? `pin:${secondPinId}`
|
|
75
|
+
return firstPhysicalGroup !== secondPhysicalGroup
|
|
76
|
+
}
|
|
77
|
+
if (!hasExplicitParallelGroundRail) {
|
|
78
|
+
return (firstPinId: PinId, secondPinId: PinId) =>
|
|
79
|
+
!areSeparateExplicitGroundIslands(firstPinId, secondPinId)
|
|
80
|
+
}
|
|
50
81
|
const isGroundFacingTerminal = (pinId: PinId) => {
|
|
51
82
|
const entry = pins.get(pinId)
|
|
52
83
|
return (
|
|
@@ -58,10 +89,13 @@ export const getGroundConnectionPolicy = (inputProblem: InputProblem) => {
|
|
|
58
89
|
}
|
|
59
90
|
|
|
60
91
|
return (firstPinId: PinId, secondPinId: PinId): boolean => {
|
|
92
|
+
const firstGlobalNetId = netConnMap.getNetConnectedToId(firstPinId)
|
|
93
|
+
const secondGlobalNetId = netConnMap.getNetConnectedToId(secondPinId)
|
|
94
|
+
if (areSeparateExplicitGroundIslands(firstPinId, secondPinId)) return false
|
|
61
95
|
if (
|
|
62
96
|
!groundNetId ||
|
|
63
|
-
|
|
64
|
-
|
|
97
|
+
firstGlobalNetId !== groundNetId ||
|
|
98
|
+
secondGlobalNetId !== groundNetId
|
|
65
99
|
) {
|
|
66
100
|
return true
|
|
67
101
|
}
|
|
@@ -98,7 +98,11 @@ export interface InputNetConnection {
|
|
|
98
98
|
netId: string
|
|
99
99
|
pinIds: Array<PinId>
|
|
100
100
|
|
|
101
|
-
/**
|
|
101
|
+
/**
|
|
102
|
+
* Preserve explicitly wired islands on this ground net, joining the islands
|
|
103
|
+
* electrically through labels instead of routing between them. Nets without
|
|
104
|
+
* explicit traces, or without this hint, retain automatic bus routing.
|
|
105
|
+
*/
|
|
102
106
|
isGround?: boolean
|
|
103
107
|
|
|
104
108
|
/**
|